Couple of days ago I found this interesting post at http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui and applied it into my website. However when I progressively using it there is a bug I identified, basically you can not move an item directly from one div to another's bottom, it has to go through the parts above and progress to the bottom. Anyone can suggest where does it goes wrong? The example is at http://www.smartjava.org/examples/dnd/double.html
Troubling me for days already.....
I did this a bit differently. Instead of attaching a jquery ui element inside the directive's controller, I instead did it inside the directive's link function. I came up with my solution, based on a blog post by Ben Farrell.
Note, that this is a Rails app, and I am using the acts_as_list gem to calculate positioning.
app.directive('sortable', function() {
return {
restrict: 'A',
link: function(scope, elt, attrs) {
// the card that will be moved
scope.movedCard = {};
return elt.sortable({
connectWith: ".deck",
revert: true,
items: '.card',
stop: function(evt, ui) {
return scope.$apply(function() {
// the deck the card is being moved to
// deck-id is an element attribute I defined
scope.movedCard.toDeck = parseInt(ui.item[0].parentElement.attributes['deck-id'].value);
// the id of the card being moved
// the card id is an attribute I definied
scope.movedCard.id = parseInt(ui.item[0].attributes['card-id'].value);
// edge case that handles a card being added to the end of the list
if (ui.item[0].nextElementSibling !== null) {
scope.movedCard.pos = parseInt(ui.item[0].nextElementSibling.attributes['card-pos'].value - 1);
} else {
// the card is being added to the very end of the list
scope.movedCard.pos = parseInt(ui.item[0].previousElementSibling.attributes['card-pos'].value + 1);
}
// broadcast to child scopes the movedCard event
return scope.$broadcast('movedCardEvent', scope.movedCard);
});
}
});
}
};
});
Important points
I utilize card attributes to store a card's id, deck, and position, in order to allow the jQuery sortable widget to grab onto.
After the stop event is called, I immediately execute a scope.$apply function to get back into, what Misko Hevery call,s the angular execution context.
I have a working example of this in action, up in a GitHub Repo of mine.
Related
How can I use jQuery UI with React? I have seen a couple examples by Googling, but all of them seem to be outdated.
If you really need to do that, here is an approach I am using.
The plan: Create a component to manage the jQuery plugin. This component will provide a React-centric view of the jQuery component. Moreover, it will:
Use React lifecycle methods to initialize and tear down the jQuery plugin;
Use React props as plugin configuration options and hook up to plugin's methods events;
Destroy the plugin when component unmounts.
Let's explore a practical example how to do that with the jQuery UI Sortable plugin.
TLDR: The Final Version
If you just want to grab the final version of the wrapped jQuery UI Sortable example:
here is a GIST I made with full annotated comments;
and here's a jsfiddle DEMO, full annotated comments too;
... plus, below is the shortened from the longer comments code snippet:
class Sortable extends React.Component {
componentDidMount() {
this.$node = $(this.refs.sortable);
this.$node.sortable({
opacity: this.props.opacity,
change: (event, ui) => this.props.onChange(event, ui)
});
}
shouldComponentUpdate() { return false; }
componentWillReceiveProps(nextProps) {
if (nextProps.enable !== this.props.enable)
this.$node.sortable(nextProps.enable ? 'enable' : 'disable');
}
renderItems() {
return this.props.data.map( (item, i) =>
<li key={i} className="ui-state-default">
<span className="ui-icon ui-icon-arrowthick-2-n-s"></span>
{ item }
</li>
);
}
render() {
return (
<ul ref="sortable">
{ this.renderItems() }
</ul>
);
}
componentWillUnmount() {
this.$node.sortable('destroy');
}
};
Optionally, you can set default props (in the case of none are passed) and the prop types:
Sortable.defaultProps = {
opacity: 1,
enable: true
};
Sortable.propTypes = {
opacity: React.PropTypes.number,
enable: React.PropTypes.bool,
onChange: React.PropTypes.func.isRequired
};
... and here's how to use the <Sortable /> component:
class MyComponent extends React.Component {
constructor(props) {
super(props);
// Use this flag to disable/enable the <Sortable />
this.state = { isEnabled: true };
this.toggleEnableability = this.toggleEnableability.bind(this);
}
toggleEnableability() {
this.setState({ isEnabled: ! this.state.isEnabled });
}
handleOnChange(event, ui) {
console.log('DOM changed!', event, ui);
}
render() {
const list = ['ReactJS', 'JSX', 'JavaScript', 'jQuery', 'jQuery UI'];
return (
<div>
<button type="button"
onClick={this.toggleEnableability}>
Toggle enable/disable
</button>
<Sortable
opacity={0.8}
data={list}
enable={this.state.isEnabled}
onChange={this.handleOnChange} />
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById('app'));
The Full Explanation
For those of you, who want to understand why and how. Here's a step by step guide:
Step 1: Create a component.
Our component will accept an array (list) of items (strings) as data prop.
class Sortable extends React.Component {
componentDidMount() {
// Every React component has a function that exposes the
// underlying DOM node that it is wrapping. We can use that
// DOM node, pass it to jQuery and initialize the plugin.
// You'll find that many jQuery plugins follow this same pattern
// and you'll be able to pass the component DOM node to jQuery
// and call the plugin function.
// Get the DOM node and store the jQuery element reference
this.$node = $(this.refs.sortable);
// Initialize the jQuery UI functionality you need
// in this case, the Sortable: https://jqueryui.com/sortable/
this.$node.sortable();
}
// jQuery UI sortable expects a <ul> list with <li>s.
renderItems() {
return this.props.data.map( (item, i) =>
<li key={i} className="ui-state-default">
<span className="ui-icon ui-icon-arrowthick-2-n-s"></span>
{ item }
</li>
);
}
render() {
return (
<ul ref="sortable">
{ this.renderItems() }
</ul>
);
}
};
Step 2: Pass configuration options via props
Let's say we want to configure the opacity of the helper while sorting. We'll use the opacity option in the plugin configuration, that takes values from 0.01 to 1.
class Sortable extends React.Component {
// ... omitted for brevity
componentDidMount() {
this.$node = $(this.refs.sortable);
this.$node.sortable({
// Get the incoming `opacity` prop and use it in the plugin configuration
opacity: this.props.opacity,
});
}
// ... omitted for brevity
};
// Optional: set the default props, in case none are passed
Sortable.defaultProps = {
opacity: 1
};
And here's how we can use the component in our code now:
<Sortable opacity={0.8} />
The same way, we can map any of the jQUery UI Sortable options.
Step 3: Hook-up functions on plugin events.
You will most probably need to hook-up on some of the plugin methods, in order to perform some React logic, for example, manipulate the state let's day.
Here's how to do that:
class Sortable extends React.Component {
// ... omitted for brevity
componentDidMount() {
this.$node = $(this.refs.sortable);
this.$node.sortable({
opacity: this.props.opacity,
// Get the incoming onChange function
// and invoke it on the Sortable `change` event
change: (event, ui) => this.props.onChange(event, ui)
});
}
// ... omitted for brevity
};
// Optional: set the prop types
Sortable.propTypes = {
onChange: React.PropTypes.func.isRequired
};
And here's how to use it:
<Sortable
opacity={0.8}
onChange={ (event, ui) => console.log('DOM changed!', event, ui) } />
Step 4: Pass the future updates control to jQuery
Right after ReactJS adds the element in the actual DOM, we need to pass the future control to jQuery. Otherwise, ReactJS will never re-render our component, but we don't want that. We want jQuery to be responsible for all updates.
React lifecycle methods comes to the rescue!
Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority, but we don't want this behavior!
shouldComponentUpdate() is invoked before rendering when new props or state are being received. If shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked.
Then, we use componentWillReceiveProps(), we compare this.props with nextProps and call jQuery UI sortable updates only when necessary. For this example, we will implement the enable/disable option of the jQuery UI Sortable.
class Sortable extends React.Component {
// Force a single-render of the component,
// by returning false from shouldComponentUpdate ReactJS lifecycle hook.
// Right after ReactJS adds the element in the actual DOM,
// we need to pass the future control to jQuery.
// This way, ReactJS will never re-render our component,
// and jQuery will be responsible for all updates.
shouldComponentUpdate() {
return false;
}
componentWillReceiveProps(nextProps) {
// Each time when component receives new props,
// we should trigger refresh or perform anything else we need.
// For this example, we'll update only the enable/disable option,
// as soon as we receive a different value for this.props.enable
if (nextProps.enable !== this.props.enable) {
this.$node.sortable(nextProps.enable ? 'enable' : 'disable');
}
}
// ... omitted for brevity
};
// Optional: set the default props, in case none are passed
Sortable.defaultProps = {
enable: true
};
// Optional: set the prop types
Sortable.propTypes = {
enable: React.PropTypes.bool
};
Step 5: Clean up the mess.
Many jQuery plugins provide a mechanism for cleaning up after themselves when they are no longer needed. jQuery UI Sortable provides an event that we can trigger to tell the plugin to unbind its DOM events and destroy. React lifecycle methods comes to the rescue again and provides a mechanism to hook into when the component is being unmounted.
class Sortable extends React.Component {
// ... omitted for brevity
componentWillUnmount() {
// Clean up the mess when the component unmounts
this.$node.sortable('destroy');
}
// ... omitted for brevity
};
Conclusion
Wrapping jQuery plugins with React is not always the best choice. However, it is nice to know that it is an option and how you can implement a solution. It is a viable option if you are migrating a legacy jQuery application to React or maybe you just can't find a React plugin that suits your needs in your case.
In the case that a library modifies the DOM, we try to keep React out of its way. React works best when it has full control of the DOM. In these cases, React components are more of wrappers for the 3rd party libraries. Mostly by using the componentDidMount/componentWillUnmount to initialize/destroy the third party library. And props as a way of giving the parent a way of customizing the behavior of the third party library that the child wraps and to hook-up on plugin events.
You can use this approach to integrate almost any jQuery plugin!
React doesn't play well with libraries that do direct DOM mutations. If something else mutates the DOM where React is attempting to render, it will throw errors. If you had to make this work, your best compromise is to have different parts of your page which are managed by different things, for example a div which houses your jquery component(s), and then some other div which contains your React component(s). Communicating between these disparate (jquery and react) components will be difficult however and honestly it's probably better to just choose one or the other.
While technically faultless, Kayolan's answer has a fatal flaw, IMHO: in passing the responsibility for future UI updates from React to jQuery, he's rather negated the point of React being there in the first place! React controls the initial render of the sortable list, but after that React's state data will become outdated as soon as the user does the first jQueryUI drag/sort operations. And the whole point of React is to represent your state data at the view level.
So, I took the reverse approach when I approached this problem: I tried to ensure that React was in control as much as possible. I don't let the jQueryUI Sortable control change the DOM at all.
How's that possible? Well jQuery-ui's sortable() method has a cancel call that sets the UI back to how it was before you started dragging and dropping stuff around. The trick is to read the state of the sortable control before you issue that cancel call. That way, we can pick up what the user's intentions were, before the cancel call sets the DOM back the way it was. Once we have those intentions, we can pass them back to React, and manipulate the state data to be in the new order that the user wanted. Finally, call a setState() on that data to have React render the new order.
Here's how I do that:
Attach the jquery-ui.sortable() method to a list of line items (generated by React of course!)
Let the user drag and drop those line items around the DOM.
When the user starts dragging, we read the index of the line item that user's dragging from.
When the user drops the line item, we:
Read from jQuery-ui.sortable() the new index position for the line item, i.e. where in the list user dropped it.
Pass a cancel call to jQuery-ui.sortable() so that the list goes backs to its original position, and the DOM is unchanged.
Pass the old and new indexes of the dragged line item as parameters to a JavaScript function in a React module.
Have that function reorder the list's back-end state data to be in the new order that the user dragged and dropped it into.
Make a React setState() call.
The list in the UI will now reflect the new order of our state data; this is standard React functionality.
So, we get to use jQueryUI Sortable's drag and drop functionality, but without it changing the DOM at all. React's happy, because it's in control of the DOM (where it should be).
Github repository example at https://github.com/brownieboy/react-dragdrop-test-simple. This includes a link to a live demo.
I could not get the jquery-ui npm package to work. What has worked for me is to use jquery-ui-bundle:
import $ from 'jquery';
import 'jquery-ui-bundle';
import 'jquery-ui-bundle/jquery-ui.min.css';
Concerning to Kaloyan Kosev's long answer, i must create a component for every jQueryUi feature that i want to use? No thanks! Why not simply update your state when you change the DOM? Followig works for me:
export default class Editor extends React.Component {
// ... constructor etc.
componentDidMount() {
this.initializeSortable();
}
initializeSortable() {
const that = this;
$('ul.sortable').sortable({
stop: function (event, ui) {
const usedListItem = ui.item;
const list = usedListItem.parent().children();
const orderedIds = [];
$.each(list, function () {
orderedIds.push($(this).attr('id'));
})
that.orderSortableListsInState(orderedIds);
}
});
}
orderSortableListsInState(orderedIds) {
// ... here you can sort the state of any list in your state tree
const orderedDetachedAttributes = this.orderListByIds(orderedIds, this.state.detachedAttributes);
if (orderedDetachedAttributes.length) {
this.state.detachedAttributes = orderedDetachedAttributes;
}
this.setState(this.state);
}
orderListByIds(ids, list) {
let orderedList = [];
for (let i = 0; i < ids.length; i++) {
let item = this.getItemById(ids[i], list);
if (typeof item === 'undefined') {
continue;
}
orderedList.push(item);
}
return orderedList;
}
getItemById(id, items) {
return items.find(item => (item.id === id));
}
// ... render etc.
}
The list element just needs an additional attribute for let jQuery select the element.
import React from 'react';
export default class Attributes extends React.Component {
render() {
const attributes = this.props.attributes.map((attribute, i) => {
return (<li key={attribute.id} id={attribute.id}>{attribute.name}</li>);
});
return (
<ul className="sortable">
{attributes}
</ul>
);
}
}
For ids i use UUID's so i havent conflicts when matching them in orderSortableListsInState().
You can use either useRef or the component id or class as usual...
import { useRef, useEffect } from 'react';
import $ from 'jquery';
import "jquery-ui-dist/jquery-ui"
export default function YourComponent() {
const ref = useRef()
useEffect(() => {
$(ref.current).sortable({
items: '>li',
});
}, []);
return (
<ul ref={ref}>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
)
}
I have two lists which are rendered by my directive. The requirement is that user can move an item from one list to another. I have a simplified implementation of this below:-
http://jsfiddle.net/yK7Lt/
The above shows a demo of how it should behave. Notice in this I manipulate the model and the DOM auto-syncs with it.
However, the problem is I am using jquery-ui-sortable plugin. So, the user can drag and drop the item from one list to another. Since jQuery is unaware of AngularJs so it modified the DOM. Now in my directive I have placed the code to sync the underlying model with the changed DOM.
The below jsfiddle code is a simplified version of my code.
http://jsfiddle.net/5Xuz2/1/
The relevant code snippet is:-
$('#btn').on('click', function () {
var li = $('#left li').first().detach();
$('#right').prepend(li);
console.log('moved top DOM to right list');
angular.element('#left').scope().$apply(function () {
// The moment this code runs, the DOM related to i is
// marked with $$NG_REMOVED, and is removed from page.
// Also somehow the DOM related to item D too is removed.
i = itemsl.shift(); // i is global variable.
});
angular.element('#right').scope().$apply(function () {
itemsr.unshift(i);
console.log('synced data with DOM');
});
});
The problem I am facing with my implementation is that the right list empties out as soon as I sync my left list model.
What is wrong with my implementation?
Is there a better approach?
the problem here is you are manipulating DOM with both Angular and jQuery... if you remove this piece of code
var li = $('#left li').first().detach();
$('#right').prepend(li);
it is working as expected
btw. I suggest trying angular-UI instead of jQueryUI
edit: OR you can try to refactor your code to something like this
var itemsl, itemsr, i, move;
function Model(name) {
this.name = name;
}
function Ctrl($scope) {
itemsl = $scope.itemsl = [new Model('A'), new Model('B'), new Model('C')];
itemsr = $scope.itemsr = [new Model('D')];
move = function() {
$scope.$apply(function() {
i = itemsl.slice(0,1);
itemsl.splice(0,1);
itemsr.unshift(i[0]);
i = null;
});
}
}
$(function () {
$('#btn').on('click', function () {
console.log('moved top DOM to right list');
move();
});
});
Currently I am working on a project for which I use the jQuery UI Accordion.
Therefore I initialise the accordion on an element by doing
<div id="accordion"></div>
$('#accordion').accordion({
collapsible: true,
active: false,
heightStyle: "content"
});
After init the accordion I append some data coming from an AJAX request. (depends on user interaction)
In a simplified jsfiddle - which does exact the same thing as the ajax call - you can see how this looks like.
So far it seems to be working quite well but there is one problem I face.
In my initialisation I say that I want all panels to be closed but after calling refresh on the accordion everything of those settings seems to be gone and one panel opens.
Note that I implemented jQuery UI v1.10.2 in my fiddle. Update notes say
The refresh method will now recognize panels that have been added or removed. This brings accordion in line with tabs and other widgets that parse the markup to find changes.
Well it does but why has it to "overwrite" the settings I defined for this accordion?
I also thought about the possibility that it might be wrong to create the accordion on an empty <div> so I tested it with a given entry and added some elements afterwards.
But the jsfiddle shows exactly the same results.
In a recent SO thread I found someone who basically does the same thing as I do but in his jsfiddle he faces the same "issue".
He adds a new panel and the first panel opens after the refresh.
My current solution for this issue is to destroy the accordion and recreate it each time there's new content for it.
But this seems quite rough to me and I thought the refresh method solves the need to destroy the accordion each time new content gets applied.
See the last jsfiddle
$(document).ready(function () {
//variable to show "new" content gets appended correctly
var foo = 1;
$('#clickMe').on('click', function () {
var data = '';
for (var i = 0; i < 3; i++) {
data += '<h3>title' + foo + '</h3><div>content</div>';
foo++;
}
if ($('#accordion').hasClass('ui-accordion')) {
$('#accordion').accordion('destroy');
}
$('#accordion').empty().append(data).accordion({
collapsible: true,
active: false,
heightStyle: "content"
});
});
});
Unfortunately it is not an option for me to change the content of the given 3 entries because the amount of panels varies.
So my questions are the one in the title and if this behaviour is wanted like that or if anybody faces the same problem?
For the explanation of this behaviour, have a look in the refresh() method of the jquery-ui accordion widget, the problem you are facing is at line 10 :
refresh: function() {
var options = this.options;
this._processPanels();
// was collapsed or no panel
if ((options.active === false && options.collapsible === true) || !this.headers.length) {
options.active = false;
this.active = $();
// active false only when collapsible is true
} if (options.active === false) {
this._activate(0); // <-- YOUR PROBLEM IS HERE
// was active, but active panel is gone
} else if (this.active.length && !$.contains(this.element[0], this.active[0])) {
// all remaining panel are disabled
if (this.headers.length === this.headers.find(".ui-state-disabled").length) {
options.active = false;
this.active = $();
// activate previous panel
} else {
this._activate(Math.max(0, options.active - 1));
}
// was active, active panel still exists
} else {
// make sure active index is correct
options.active = this.headers.index(this.active);
}
this._destroyIcons();
this._refresh();
}
I want to be able to get a reference to the menu object that autocomplete builds, (so I can get the .attr("id") for example), but I'm not very familiar with jQuery/javascript. In the source, I found this:
https://github.com/jquery/jquery-ui/blob/1-9-stable/ui/jquery.ui.autocomplete.js#L182
so there is an object flying around, I just can't seem to find how to get hold of it.
So, for example, if I've got an input with an autocomplete bound to it like this:
// input = reference to the input text box on the form
input.autocomplete({
select: function(event, ui) {
// how to get the reference here?
// some things I've tried
// return input.menu
// return input.data("menu")
// and a few others but they didn't work either
}
});
I tried looking at the data object itself, but there were so many options I could spend all day looking at it and still not find what I'm looking for.
You can get the widget's reference by looking into dataset assigned to its root element (input). Then fetching menu property (and its underlying element) is kinda trivial. )
select: function(event, ui) {
// that's how get the menu reference:
var widget = $(this).data('ui-autocomplete'),
menu = widget.menu,
$ul = menu.element,
id = $ul.attr('id'); // or $ul[0].id
}
... as this within select function refers to the <input> when this function called as an event handler.
A simpler way to do this:
$(this).autocomplete('widget');
It does the same as:
select: function(event, ui) {
// that's how get the menu reference:
var widget = $(this).data('ui-autocomplete'),
menu = widget.menu,
$ul = menu.element,
id = $ul.attr('id'); // or $ul[0].id
}
It gives the ul list
$(this).autocomplete('widget').attr('id');
I'm looking a way to binding the snap event.
When I'm dragging an element over my surface and the draggable element is snapped to a declared snap position I want to trigger an event.
Something like this:
$(".drag").draggable({
snap: ".grid",
snaped: function( event, ui ) {}
});
Bonus point: with a reference to the .grid element where the draggable element was snapped.
The draggable widget does not expose such an event out of the box (yet). You could modify it and maintain your custom version or, better, derive a new widget from it and implement the new event there. There is, however, a third way.
From this question, we know the widget stores an array of the potentially "snappable" elements in its snapElements property. In turn, each element in this array exposes a snapping property that is true if the draggable helper is currently snapped to this element and false otherwise (the helper can snap to several elements at the same time).
The snapElements array is updated for every drag event, so it is always up-to-date in drag handlers. From there, we only have to obtain the draggable widget instance from the associated element with data(), and call its _trigger() method to raise our own snapped event (actually dragsnapped under the hood). In passing, we can $.extend() the ui object with a jQuery object wrapping the snapped element:
$(".drag").draggable({
drag: function(event, ui) {
var draggable = $(this).data("draggable");
$.each(draggable.snapElements, function(index, element) {
if (element.snapping) {
draggable._trigger("snapped", event, $.extend({}, ui, {
snapElement: $(element.item)
}));
}
});
},
snap: ".grid",
snapped: function(event, ui) {
// Do something with 'ui.snapElement'...
}
});
The code above, however, can still be improved. As it stands, a snapped event will be triggered for every drag event (which occurs a lot) as long as the draggable helper remains snapped to an element. In addition, no event is triggered when snapping ends, which is not very practical, and detracts from the convention for such events to occur in pairs (snapped-in, snapped-out).
Luckily, the snapElements array is persistent, so we can use it to store state. We can add a snappingKnown property to each array element in order to track that we already have triggered a snapped event for that element. Moreover, we can use it to detect that an element has been snapped out since the last call and react accordingly.
Note that rather than introducing another snapped-out event, the code below chooses to pass an additional snapping property (reflecting the element's current state) in the ui object (which is, of course, only a matter of preference):
$(".drag").draggable({
drag: function(event, ui) {
var draggable = $(this).data("draggable");
$.each(draggable.snapElements, function(index, element) {
ui = $.extend({}, ui, {
snapElement: $(element.item),
snapping: element.snapping
});
if (element.snapping) {
if (!element.snappingKnown) {
element.snappingKnown = true;
draggable._trigger("snapped", event, ui);
}
} else if (element.snappingKnown) {
element.snappingKnown = false;
draggable._trigger("snapped", event, ui);
}
});
},
snap: ".grid",
snapped: function(event, ui) {
// Do something with 'ui.snapElement' and 'ui.snapping'...
var snapper = ui.snapElement.attr("id"),snapperPos = ui.snapElement.position(),
snappee = ui.helper.attr("id"), snappeePos = ui.helper.position(),
snapping = ui.snapping;
// ...
}
});
You can test this solution here.
In closing, another improvement might be to make the snapped event cancelable, as the drag event is. To achieve that, we would have to return false from our drag handler if one of the calls to _trigger() returns false. You may want to think twice before implementing this, though, as canceling a drag operation on snap-in or snap-out does not look like a very user-friendly feature in the general case.
Update: From jQuery UI 1.9 onwards, the data() key becomes the widget's fully qualified name, with dots replaced by dashes. Accordingly, the code used above to obtain the widget instance becomes:
var draggable = $(this).data("ui-draggable");
Instead of:
var draggable = $(this).data("draggable");
Using the unqualified name is still supported in 1.9 but is deprecated, and support will be dropped in 1.10.
In jquery-ui 1.10.0, the above code doesn't work. The drag function is instead:
drag: function(event, ui) {
var draggable = $(this).data("ui-draggable")
$.each(draggable.snapElements, function(index, element) {
if(element.snapping) {
draggable._trigger("snapped", event, $.extend({}, ui, {
snapElement: $(element.item)
}));
}
});
}