When having called .before on elements that are detached from the DOM, .end behaves differently than it does with attached elements:
var $div1 = $("div");
console.log($div1.after("foo").end()); // [document]
$div1.detach();
console.log($div1.after("foo").end()); // [<div></div>]
(Fiddle: http://jsfiddle.net/R2uc7/2/)
Apparently, .before causes different behaviour to .end depending on the node being attached or detached. I don't see the logic and I'm not sure what I can rely on.
Could someone enlighten me on the defined behaviour of .end combined with .before?
jQuery v1.7.2 uses pushStack to build the new DOM elements.
pushStack adds items to the jQuery object's stack (go figure!), and end pops the last one off, returning the rest of the stack (whatever remains).
jQuery v1.7.2 line #5860:
annotation mine
before: function() {
if ( this[0] && this[0].parentNode ) {
return this.domManip(arguments, false, function( elem ) {
this.parentNode.insertBefore( elem, this );
});
} else if ( arguments.length ) {
var set = jQuery.clean( arguments );
set.push.apply( set, this.toArray() );
return this.pushStack( set, "before", arguments ); //pushStack in use
}
}
Related
I have a Shiny app containing a Highcharter graph. The graph is a scatter, and has draggable and clickable points. The action associated with dragging should, in theory, be mutually exclusive with the action associated with clicking.
However, in practice, the click action sometimes fires when a point's dragged. Clicking only (without a drag) and dragging (which should preclude the ability to click until the mouse button's released and the drag action's completed) need to be mutually exclusive in this app. My full code has different JavaScript actions getting passed back to the Shiny server depending on the event's type. Both the events firing together is causing trouble.
I suspect the solution involves adding additional JavaScript actions associated with each event. My JavaScript skills aren't strong enough to know what those tweaks might be, though. Googling several different variants of this question didn't turn any potential solutions. The closest discussion I found is in the Highcharts context, here, but the solution has to do with the master Highchart/draggable-events JS files.
Suggestions?
For a toy example where you can see this behavior in action:
rm(list=ls())
library(highcharter)
library(shiny)
ui <- fluidPage(
highchartOutput("hcontainer")
)
# MUTUAL EXCLUSIVITY:
# if you drag in the plot, you should either drag (and get no alert) OR
# get an alert (and the point shouldn't/won't move), but never both.
server <- function(input, output, session) {
output$hcontainer <- renderHighchart({
hc <- highchart() %>%
hc_chart(animation = FALSE) %>%
hc_title(text = "draggable points demo") %>%
hc_plotOptions(
series = list(
point = list(
events = list(
drop = JS("function(){ /* # do nothing, for demo */
}"
),
click = JS("function(){
alert('You clicked') /* # give alert, for demo */
}"
)
)
),
stickyTracking = FALSE
),
column = list(
stacking = "normal"
),
line = list(
cursor = "ns-resize"
)
) %>%
hc_tooltip(yDecimals = 2) %>%
hc_add_series(
type = "scatter",
data = stars,
draggableY = TRUE,
draggableX = TRUE,
hcaes(x = absmag, y=radiussun)
)
hc
})
}
shinyApp(ui = ui, server = server)
This example may help you: http://jsfiddle.net/kkulig/th37vnv5/
I added a flag in the core function that prevents from firing the click action right after the drag event:
var dragHappened;
(function(H) {
var addEvent = H.addEvent;
H.Pointer.prototype.setDOMEvents = function() {
var pointer = this,
container = pointer.chart.container,
ownerDoc = container.ownerDocument;
container.onmousedown = function(e) {
dragHappened = false;
pointer.onContainerMouseDown(e);
};
container.onmousemove = function(e) {
pointer.onContainerMouseMove(e);
};
container.onclick = function(e) {
if (!dragHappened) {
pointer.onContainerClick(e);
}
};
(...)
}; // setDOMEvents
})(Highcharts);
(...)
plotOptions: {
series: {
point: {
events: {
drag: function() {
console.log('drag');
dragHappened = true;
},
click: function() {
console.log('click');
}
}
}
}
},
I have a sortable list in React which is powered by jQuery UI. When I drag and drop an item in the list, I want to update the array so that the new order of the list is stored there. Then re-render the page with the updated array. i.e. this.setState({data: _todoList});
Currently, when you drag and drop an item, jQuery UI DnD works, but the position of the item in the UI does not change, even though the page re-renders with the updated array. i.e. in the UI, the item reverts to where it used to be in the list, even though the array that defines its placement has updated successfully.
If you drag and drop the item twice, then it moves to the correct position.
// Enable jQuery UI Sortable functionality
$(function() {
$('.bank-entries').sortable({
axis: "y",
containment: "parent",
tolerance: "pointer",
revert: 150,
start: function (event, ui) {
ui.item.indexAtStart = ui.item.index();
},
stop: function (event, ui) {
var data = {
indexStart: ui.item.indexAtStart,
indexStop: ui.item.index(),
accountType: "bank"
};
AppActions.sortIndexes(data);
},
});
});
// This is the array that holds the positions of the list items
var _todoItems = {bank: []};
var AppStore = assign({}, EventEmitter.prototype, {
getTodoItems: function() {
return _todoItems;
},
emitChange: function(change) {
this.emit(change);
},
addChangeListener: function(callback) {
this.on(AppConstants.CHANGE_EVENT, callback);
},
sortTodo: function(todo) {
// Dynamically choose which Account to target
targetClass = '.' + todo.accountType + '-entries';
// Define the account type
var accountType = todo.accountType;
// Loop through the list in the UI and update the arrayIndexes
// of items that have been dragged and dropped to a new location
// newIndex is 0-based, but arrayIndex isn't, hence the crazy math
$(targetClass).children('form').each(function(newIndex) {
var arrayIndex = Number($(this).attr('data-array-index'));
if (newIndex + 1 !== arrayIndex) {
// Update the arrayIndex of the element
_todoItems[accountType][arrayIndex-1].accountData.arrayIndex = newIndex + 1;
}
});
// Sort the array so that updated array items move to their correct positions
_todoItems[accountType].sort(function(a, b){
if (a.accountData.arrayIndex > b.accountData.arrayIndex) {
return 1;
}
if (a.accountData.arrayIndex < b.accountData.arrayIndex) {
return -1;
}
// a must be equal to b
return 0;
});
// Fire an event that re-renders the UI with the new array
AppStore.emitChange(AppConstants.CHANGE_EVENT);
},
}
function getAccounts() {
return { data: AppStore.getTodoItems() }
}
var Account = React.createClass({
getInitialState: function(){
return getAccounts();
},
componentWillMount: function(){
AppStore.addChangeListener(this._onChange);
// Fires action that triggers the initial load
AppActions.loadComponentData();
},
_onChange: function() {
console.log('change event fired');
this.setState(getAccounts());
},
render: function(){
return (
<div className="component-wrapper">
<Bank data={this.state.data} />
</div>
)
}
});
The trick is to call sortable('cancel') in the stop event of the Sortable, then let React update the DOM.
componentDidMount() {
this.domItems = jQuery(React.findDOMNode(this.refs["items"]))
this.domItems.sortable({
stop: (event, ui) => {
// get the array of new index (http://api.jqueryui.com/sortable/#method-toArray)
const reorderedIndexes = this.domItems.sortable('toArray', {attribute: 'data-sortable'})
// cancel the sort so the DOM is untouched
this.domItems.sortable('cancel')
// Update the store and let React update (here, using Flux)
Actions.updateItems(Immutable.List(reorderedIndexes.map( idx => this.state.items.get(Number(idx)))))
}
})
}
The reason jQuery UI Sortable doesn't work with React is because it directly mutates the DOM, which is a big no no in React.
To make it work, you would have to modify jQuery UI Sortable so that you keep the DnD functionality, but when you drop the element, it does not modify the DOM. Instead, it could fire an event which triggers a React render with the new position of the elements.
Since React uses a Virtual DOM, you have to use the function React.findDOMNode() to access an actual DOM element.
I would call the jQuery UI function inside the componentDidMount method of your component because your element has to be already rendered to be accessible.
// You have to add a ref attribute to the element with the '.bank-entries' class
$( React.findDOMNode( this.refs.bank_entries_ref ) ).sortable( /.../ );
Documentation - Working with the browser (everything you need to know is here)
Hope that makes sense and resolves your issue
As of now (Dojo 1.9.2) I haven't been able to find a Dojo autocomplete widget that would satisfy all of the following (typical) requirements:
Only executes a query to the server when a predefined number of characters have been entered (without this, big datasets should not be queried)
Does not require a full REST service on the server, only a URL which can be parametrized with a search term and simply returns JSON objects containing an ID and a label to display (so the data-query to the database can be limited just to the required data fields, not loading full data-entities and use only one field thereafter)
Has a configurable time-delay between the key-releases and the start of the server-query (without this excessive number of queries are fired against the server)
Capable of recognizing when there is no need for a new server-query (since the previously executed query is more generic than the current one would be).
Dropdown-stlye (has GUI elements indicating that this is a selector field)
I have created a draft solution (see below), please advise if you have a simpler, better solution to the above requirements with Dojo > 1.9.
The AutoComplete widget as a Dojo AMD module (placed into /gefc/dijit/AutoComplete.js according to AMD rules):
//
// AutoComplete style widget which works together with an ItemFileReadStore
//
// It will re-query the server whenever necessary.
//
define([
"dojo/_base/declare",
"dijit/form/FilteringSelect"
],
function(declare, _FilteringSelect) {
return declare(
[_FilteringSelect], {
// minimum number of input characters to trigger search
minKeyCount: 2,
// the term for which we have queried the server for the last time
lastServerQueryTerm: null,
// The query URL which will be set on the store when a server query
// is needed
queryURL: null,
//------------------------------------------------------------------------
postCreate: function() {
this.inherited(arguments);
// Setting defaults
if (this.searchDelay == null)
this.searchDelay = 500;
if (this.searchAttr == null)
this.searchAttr = "label";
if (this.autoComplete == null)
this.autoComplete = true;
if (this.minKeyCount == null)
this.minKeyCount = 2;
},
escapeRegExp: function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
replaceAll: function (find, replace, str) {
return str.replace(new RegExp(this.escapeRegExp(find), 'g'), replace);
},
startsWith: function (longStr, shortStr) {
return (longStr.match("^" + shortStr) == shortStr)
},
// override search method, count the input length
_startSearch: function (/*String*/ key) {
// If there is not enough text entered, we won't start querying
if (!key || key.length < this.minKeyCount) {
this.closeDropDown();
return;
}
// Deciding if the server needs to be queried
var serverQueryNeeded = false;
if (this.lastServerQueryTerm == null)
serverQueryNeeded = true;
else if (!this.startsWith(key, this.lastServerQueryTerm)) {
// the key does not start with the server queryterm
serverQueryNeeded = true;
}
if (serverQueryNeeded) {
// Creating a query url templated with the autocomplete term
var url = this.replaceAll('${autoCompleteTerm}', key, this.queryURL);
this.store.url = url
// We need to close the store in order to allow the FilteringSelect
// to re-open it with the new query term
this.store.close();
this.lastServerQueryTerm = key;
}
// Calling the super start search
this.inherited(arguments);
}
}
);
});
Notes:
I included some string functions to make it standalone, these should go to their proper places in your JS library.
The JavaScript embedded into the page which uses teh AutoComplete widget:
require([
"dojo/ready",
"dojo/data/ItemFileReadStore",
"gefc/dijit/AutoComplete",
"dojo/parser"
],
function(ready, ItemFileReadStore, AutoComplete) {
ready(function() {
// The initially displayed data (current value, possibly null)
// This makes it possible that the widget does not fire a query against
// the server immediately after initialization for getting a label for
// its current value
var dt = null;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
</g:if>
// If there is no current value, this will have no data
var partnerStore = new ItemFileReadStore(
{ data: dt,
urlPreventCache: true,
clearOnClose: true
}
);
var partnerSelect = new AutoComplete({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
queryURL: '<g:createLink controller="partner"
action="listForAutoComplete"
absolute="true"/>?term=\$\{autoCompleteTerm\}',
store: partnerStore,
searchAttr: "label",
autoComplete: true
},
"technicalContactAC"
);
})
})
Notes:
This is not standalone JavaScript, but generated with Grails on the server side, thus you see <g:if... and other server-side markup in the code). Replace those sections with your own markup.
<g:createLink will result in something like this after server-side page generation: /Limes/partner/listForAutoComplete?term=${autoCompleteTerm}
As of dojo 1.9, I would start by recommending that you replace your ItemFileReadStore by a store from the dojo/store package.
Then, I think dijit/form/FilteringSelect already has the features you need.
Given your requirement to avoid a server round-trip at the initial page startup, I would setup 2 different stores :
a dojo/store/Memory that would handle your initial data.
a dojo/store/JsonRest that queries your controller on subsequent requests.
Then, to avoid querying the server at each keystroke, set the FilteringSelect's intermediateChanges property to false, and implement your logic in the onChange extension point.
For the requirement of triggering the server call after a delay, implement that in the onChange as well. In the following example I did a simple setTimeout, but you should consider writing a better debounce method. See this blog post and the utility functions of dgrid.
I would do this in your GSP page :
require(["dojo/store/Memory", "dojo/store/JsonRest", "dijit/form/FilteringSelect", "dojo/_base/lang"],
function(Memory, JsonRest, FilteringSelect, lang) {
var initialPartnerStore = undefined;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
initialPartnerStore = new Memory({
data : dt
});
</g:if>
var partnerStore = new JsonRest({
target : '<g:createLink controller="partner" action="listForAutoComplete" absolute="true"/>',
});
var queryDelay = 500;
var select = new FilteringSelect({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
store: initialPartnerStore ? initialPartnerStore : partnerStore,
query : { term : ${autoCompleteTerm} },
searchAttr: "label",
autoComplete: true,
intermediateChanges : false,
onChange : function(newValue) {
// Change to the JsonRest store to query the server
if (this.store !== partnerStore) {
this.set("store", partnerStore);
}
// Only query after your desired delay
setTimeout(lang.hitch(this, function(){
this.set('query', { term : newValue }
}), queryDelay);
}
}).startup();
});
This code is untested, but you get the idea...
As per answer of the question Ember.js draggable and droppable jqueryUI / native Drag and drop mixin.
I have implemented JQUERY UI drag, drop, resize mixins in EmberJS. But my problem is i want the same view to do drag and resize. I tried to implement in different ways. You can check in this jsfiddle http://jsfiddle.net/codejack/TGwxf/1/ The view gets UI behaviour of last called mixin only.
Is there any way to get more than 1 behaviour in drag,drop,resize for same view?
EDIT I found out the reason is the 2nd mixin overrides the uievents,uiOptions,uiType variables. But still dont know how to avoid that... only way i can see is writing own Widgets with own events...any way to solve that?
Though the #user1128571 gave a solution which partly solves the problem, here is how i corrected the issue. I added different mixins for Interactions as that will solve the problem.
https://github.com/thecodejack/jquery-ui-ember/blob/master/js/app.js#L104
check the github pages of the module to know how exactly it works
You might want the JQ.Widget to look like this, warning it's not pretty:
Here, JQ.UiBase is the same thing as JQ.Widget
JQ.UiBase = Ember.Mixin.create({
uiWidgets: {},
uiAttributes: {},
// ----------------------------------------------------------------------------
// setup and teardown
didInsertElement: function(){
this._super();
this._createUiWidgets();
},
willDestroyElement: function(){
this._super();
// implement tear down
},
// ----------------------------------------------------------------------------
// #Private
// #Function: for each $.ui specified in the view, create a $.ui widget
// add ui widgets to uiWidgets hash, ui widget setting to uiAttributes hash
_createUiWidgets: function(){
var widgetTypes = this._gatherWidgetTypes();
uiWidgets = this.get('uiWidgets'),
uiAttributes = this.get('uiAttributes'),
thisView = this;
widgetTypes.forEach( function( widget ){
var options = thisView.get( widget + 'Options' ) || {},
handlers = thisView._registerEventHandlers( widget ),
attributes = $.extend( options, handlers ),
uiWidget = $.ui[widget]( attributes, thisView.$() );
uiWidgets[widget] = uiWidget;
uiAttributes[widget] = attributes;
});
},
// #Function: collects named $.ui events from Widget mixin
// for each event, if there is an associated callback, wrap it in a function and call the view's context
// #Return: a hash map $.ui event to callback function defined in view
_registerEventHandlers: function( widget_name ){
var widgetName = widget_name + 'Events',
events = this.get( widgetName ) || [],
thisView = this,
eventHandlers = {};
if ( events.length === 0 ) return;
// note the iterator is not scoped to the view
events.forEach( function( event ){
var callBack = thisView.get( event );
if ( callBack ){
eventHandlers[ event ] = function ( event, ui ){ callBack.call( thisView, event, ui ); };
};
});
return eventHandlers;
},
// TODO --> find alternate implementation that does not break if structure of ui mixins or namespace change
_gatherWidgetTypes: function() {
var nameSpace = 'JQ',
widgetTypes = [];
Ember.Mixin.mixins(this).forEach( function( mixin ){
// find widget with correct namespace
if ( mixin.toString().substring(0,2) === nameSpace ){
// console.log( 'gather: ', mixin, ' --> ', mixin.mixins[1] )
// access widget mixin and check widget mixin have properties
if ( mixin.mixins && mixin.mixins[1] && mixin.mixins[1].properties ){
if ( mixin.mixins[1].properties.widgetType ) widgetTypes.push( mixin.mixins[1].properties.widgetType)
}
}
});
return widgetTypes;
},
});
And then your resizable mixin would look like this:
JQ.Resizable = Ember.Mixin.create( JQ.UiBase, {
widgetType: 'resizable',
resizableOptions: { 'aspectRatio': 1/1 },
resizableEvents: [ 'resize' ],
resize: function( event, ui ){
// do stuff
},
});
The most important function here is the _gatherWidgetTypes, which gathers all JQ-namespaced mixins in the ember object. In my opinion, it's bit of a hack and I ended up not using the JQ.UiBase after making it, favoring to mix logic to create the widget and specifying the event handlers and options into one mixin, which ended up looking cleaner, but that's just me.
I want to make "jQuery UI TAB" blink (like notification).
I have diffrent tabs (Inbox | Sent | Important). My timer function checks if there is a new message in inbox, if so, I want the Inbox tab to start blinking/ flashing unless its clicked open.
Have tried diffrent options like .effect(..), .tabs(fx: {..}) but nothing seems to work :(
Any idea if its possible or not?
Yes it's definitely possible.
To give me some practice, I've written a jQuery blinker plugin for you:
jQuery:
(function($){
// **********************************
// ***** Start: Private Members *****
var pluginName = 'blinker';
var blinkMain = function(data){
var that = this;
this.css(data.settings.css_1);
clearTimeout(data.timeout);
data.timeout = setTimeout(function(){
that.css(data.settings.css_0);
}, data.settings.cycle * data.settings.ratio);
};
// ***** Fin: Private Members *****
// ********************************
// *********************************
// ***** Start: Public Methods *****
var methods = {
init : function(options) {
//"this" is a jquery object on which this plugin has been invoked.
return this.each(function(index){
var $this = $(this);
var data = $this.data(pluginName);
// If the plugin hasn't been initialized yet
if (!data){
var settings = {
css_0: {
color: $this.css('color'),
backgroundColor: $this.css('backgroundColor')
},
css_1: {
color: '#000',
backgroundColor: '#F90'
},
cycle: 2000,
ratio: 0.5
};
if(options) { $.extend(true, settings, options); }
$this.data(pluginName, {
target : $this,
settings: settings,
interval: null,
timeout: null,
blinking: false
});
}
});
},
start: function(){
return this.each(function(index){
var $this = $(this);
var data = $this.data(pluginName);
if(!data.blinking){
blinkMain.call($this, data);
data.interval = setInterval(function(){
blinkMain.call($this, data);
}, data.settings.cycle);
data.blinking = true;
}
});
},
stop: function(){
return this.each(function(index){
var $this = $(this);
var data = $this.data(pluginName);
clearInterval(data.interval);
clearTimeout(data.timeout);
data.blinking = false;
this.style = '';
});
}
};
// ***** Fin: Public Methods *****
// *******************************
// *****************************
// ***** Start: Supervisor *****
$.fn[pluginName] = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
})( jQuery );
See it in action here
The plugin and the fiddle are pretty raw in that I haven't tried to integrate with jQuery-ui-tabs. This may be easy or hard, I don't know, but providing each tab is addressable by class or id then it shouldn't be too difficult.
Something you may need to consider is stopping a blinking tab when it is clicked. For this you may wish to call the .blinker('stop') method directly (with a .on('click') handler) or from an appropriate jQuery-ui-tabs callback.
API
The plugin is properly written in jQuery's preferred pattern. It puts just one member in the jQuery.fn namespace and .blinker(...) will chain like standard jQuery methods.
Methods :
.blinker('init' [,options]) : Initialises selected element(s) with blinker behaviour. Called automatically with .blinker(options), or just .blinker() in its simplest form.
.blinker('start') : causes selected element(s) to start blinking between two styles as determined by plugin defaults and/or options.
.blinker('stop') : causes selected element(s) to stop blinking and return to their natural CSS style(s).
Options : a map of properties, which determine blinker styles and timing:
css_0 : (optional) a map of css properties representing the blink OFF-state.
css_1 : a map of CSS properties representing the blink ON-state.
cycle : the blink cycle time in milliseconds (default 2000).
ratio : ON time as a proportion of cycle time (default 0.5).
By omitting css_0 from the options map, the OFF state is determined by the element(s)' natural CSS styling defined elsewhere (typically in a stylesheet).
Default values are hard-coded for css_1.color, css_1.backgroundColor, cycle time and ratio. Changing the default settings programmatically is not handled, so for different default styling the plugin will need to be edited.
jQuery comes by default with a slew of effects to pick from. You can easily use them wherever you see the need for them and they can be applied like so:
$('#newmsg').effect("pulsate", {}, 1000);
Demo
yes... this is what you need...!!!
this is javascript
if(newmessage==true){
$('#chat-86de45de47-tab').effect("pulsate", {}, 1000);
}
i think it's