spree_enhanced_option_types 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE +23 -0
  3. data/README.markdown +126 -0
  4. data/README.md +13 -0
  5. data/Rakefile +29 -0
  6. data/app/controllers/admin/prototypes_controller_decorator.rb +21 -0
  7. data/app/controllers/admin/variants_controller_decorator.rb +8 -0
  8. data/app/controllers/orders_controller_decorator.rb +58 -0
  9. data/app/helpers/variant_selection.rb +47 -0
  10. data/app/models/option_types_prototype.rb +2 -0
  11. data/app/models/option_value_decorator.rb +9 -0
  12. data/app/models/product_decorator.rb +40 -0
  13. data/app/models/prototype.rb +6 -0
  14. data/app/models/variant_decorator.rb +41 -0
  15. data/app/views/admin/option_types/_option_value_fields.html.erb +10 -0
  16. data/app/views/admin/option_types/edit.html.erb +34 -0
  17. data/app/views/admin/products/new.html.erb +58 -0
  18. data/app/views/admin/prototypes/_form.html.erb +57 -0
  19. data/app/views/admin/prototypes/_sortable_header.rhtml +3 -0
  20. data/app/views/admin/variants/_form.html.erb +45 -0
  21. data/app/views/admin/variants/index.html.erb +62 -0
  22. data/app/views/products/_cart_form.html.erb +37 -0
  23. data/app/views/products/_eot_includes.html.erb +25 -0
  24. data/app/views/products/_radio_2d.html.erb +82 -0
  25. data/app/views/products/_radio_sets.html.erb +31 -0
  26. data/app/views/products/_selects.html.erb +26 -0
  27. data/config/locales/en.yml +14 -0
  28. data/config/locales/ru.yml +10 -0
  29. data/config/routes.rb +3 -0
  30. data/db/migrate/20100825095803_add_sku_to_option_values.rb +9 -0
  31. data/db/migrate/20101019122221_add_amount_to_option_value.rb +9 -0
  32. data/db/migrate/20101019122559_add_position_to_option_type_prototype.rb +9 -0
  33. data/db/migrate/20101019122611_set_default_for_option_value_amount.rb +9 -0
  34. data/doc/2d.jpg +0 -0
  35. data/doc/selects.jpg +0 -0
  36. data/doc/sets.jpg +0 -0
  37. data/lib/spree_enhanced_option_types.rb +50 -0
  38. data/lib/spree_enhanced_option_types_hooks.rb +3 -0
  39. data/lib/tasks/enhanced_option_types.rake +29 -0
  40. data/lib/tasks/install.rake +27 -0
  41. data/public/javascripts/enhanced-option-types.js +115 -0
  42. data/public/javascripts/jquery-ui-1.7.2.custom.min.js +46 -0
  43. data/public/javascripts/ui.core.js +519 -0
  44. data/public/javascripts/ui.draggable.js +766 -0
  45. data/public/javascripts/ui.selectable.js +257 -0
  46. data/public/javascripts/ui.sortable.js +1019 -0
  47. data/spree-enhanced-option-types.gemspec +22 -0
  48. metadata +132 -0
@@ -0,0 +1,257 @@
1
+ /*
2
+ * jQuery UI Selectable 1.7.2
3
+ *
4
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
5
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
6
+ * and GPL (GPL-LICENSE.txt) licenses.
7
+ *
8
+ * http://docs.jquery.com/UI/Selectables
9
+ *
10
+ * Depends:
11
+ * ui.core.js
12
+ */
13
+ (function($) {
14
+
15
+ $.widget("ui.selectable", $.extend({}, $.ui.mouse, {
16
+
17
+ _init: function() {
18
+ var self = this;
19
+
20
+ this.element.addClass("ui-selectable");
21
+
22
+ this.dragged = false;
23
+
24
+ // cache selectee children based on filter
25
+ var selectees;
26
+ this.refresh = function() {
27
+ selectees = $(self.options.filter, self.element[0]);
28
+ selectees.each(function() {
29
+ var $this = $(this);
30
+ var pos = $this.offset();
31
+ $.data(this, "selectable-item", {
32
+ element: this,
33
+ $element: $this,
34
+ left: pos.left,
35
+ top: pos.top,
36
+ right: pos.left + $this.outerWidth(),
37
+ bottom: pos.top + $this.outerHeight(),
38
+ startselected: false,
39
+ selected: $this.hasClass('ui-selected'),
40
+ selecting: $this.hasClass('ui-selecting'),
41
+ unselecting: $this.hasClass('ui-unselecting')
42
+ });
43
+ });
44
+ };
45
+ this.refresh();
46
+
47
+ this.selectees = selectees.addClass("ui-selectee");
48
+
49
+ this._mouseInit();
50
+
51
+ this.helper = $(document.createElement('div'))
52
+ .css({border:'1px dotted black'})
53
+ .addClass("ui-selectable-helper");
54
+ },
55
+
56
+ destroy: function() {
57
+ this.element
58
+ .removeClass("ui-selectable ui-selectable-disabled")
59
+ .removeData("selectable")
60
+ .unbind(".selectable");
61
+ this._mouseDestroy();
62
+ },
63
+
64
+ _mouseStart: function(event) {
65
+ var self = this;
66
+
67
+ this.opos = [event.pageX, event.pageY];
68
+
69
+ if (this.options.disabled)
70
+ return;
71
+
72
+ var options = this.options;
73
+
74
+ this.selectees = $(options.filter, this.element[0]);
75
+
76
+ this._trigger("start", event);
77
+
78
+ $(options.appendTo).append(this.helper);
79
+ // position helper (lasso)
80
+ this.helper.css({
81
+ "z-index": 100,
82
+ "position": "absolute",
83
+ "left": event.clientX,
84
+ "top": event.clientY,
85
+ "width": 0,
86
+ "height": 0
87
+ });
88
+
89
+ if (options.autoRefresh) {
90
+ this.refresh();
91
+ }
92
+
93
+ this.selectees.filter('.ui-selected').each(function() {
94
+ var selectee = $.data(this, "selectable-item");
95
+ selectee.startselected = true;
96
+ if (!event.metaKey) {
97
+ selectee.$element.removeClass('ui-selected');
98
+ selectee.selected = false;
99
+ selectee.$element.addClass('ui-unselecting');
100
+ selectee.unselecting = true;
101
+ // selectable UNSELECTING callback
102
+ self._trigger("unselecting", event, {
103
+ unselecting: selectee.element
104
+ });
105
+ }
106
+ });
107
+
108
+ $(event.target).parents().andSelf().each(function() {
109
+ var selectee = $.data(this, "selectable-item");
110
+ if (selectee) {
111
+ selectee.$element.removeClass("ui-unselecting").addClass('ui-selecting');
112
+ selectee.unselecting = false;
113
+ selectee.selecting = true;
114
+ selectee.selected = true;
115
+ // selectable SELECTING callback
116
+ self._trigger("selecting", event, {
117
+ selecting: selectee.element
118
+ });
119
+ return false;
120
+ }
121
+ });
122
+
123
+ },
124
+
125
+ _mouseDrag: function(event) {
126
+ var self = this;
127
+ this.dragged = true;
128
+
129
+ if (this.options.disabled)
130
+ return;
131
+
132
+ var options = this.options;
133
+
134
+ var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
135
+ if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
136
+ if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
137
+ this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
138
+
139
+ this.selectees.each(function() {
140
+ var selectee = $.data(this, "selectable-item");
141
+ //prevent helper from being selected if appendTo: selectable
142
+ if (!selectee || selectee.element == self.element[0])
143
+ return;
144
+ var hit = false;
145
+ if (options.tolerance == 'touch') {
146
+ hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
147
+ } else if (options.tolerance == 'fit') {
148
+ hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
149
+ }
150
+
151
+ if (hit) {
152
+ // SELECT
153
+ if (selectee.selected) {
154
+ selectee.$element.removeClass('ui-selected');
155
+ selectee.selected = false;
156
+ }
157
+ if (selectee.unselecting) {
158
+ selectee.$element.removeClass('ui-unselecting');
159
+ selectee.unselecting = false;
160
+ }
161
+ if (!selectee.selecting) {
162
+ selectee.$element.addClass('ui-selecting');
163
+ selectee.selecting = true;
164
+ // selectable SELECTING callback
165
+ self._trigger("selecting", event, {
166
+ selecting: selectee.element
167
+ });
168
+ }
169
+ } else {
170
+ // UNSELECT
171
+ if (selectee.selecting) {
172
+ if (event.metaKey && selectee.startselected) {
173
+ selectee.$element.removeClass('ui-selecting');
174
+ selectee.selecting = false;
175
+ selectee.$element.addClass('ui-selected');
176
+ selectee.selected = true;
177
+ } else {
178
+ selectee.$element.removeClass('ui-selecting');
179
+ selectee.selecting = false;
180
+ if (selectee.startselected) {
181
+ selectee.$element.addClass('ui-unselecting');
182
+ selectee.unselecting = true;
183
+ }
184
+ // selectable UNSELECTING callback
185
+ self._trigger("unselecting", event, {
186
+ unselecting: selectee.element
187
+ });
188
+ }
189
+ }
190
+ if (selectee.selected) {
191
+ if (!event.metaKey && !selectee.startselected) {
192
+ selectee.$element.removeClass('ui-selected');
193
+ selectee.selected = false;
194
+
195
+ selectee.$element.addClass('ui-unselecting');
196
+ selectee.unselecting = true;
197
+ // selectable UNSELECTING callback
198
+ self._trigger("unselecting", event, {
199
+ unselecting: selectee.element
200
+ });
201
+ }
202
+ }
203
+ }
204
+ });
205
+
206
+ return false;
207
+ },
208
+
209
+ _mouseStop: function(event) {
210
+ var self = this;
211
+
212
+ this.dragged = false;
213
+
214
+ var options = this.options;
215
+
216
+ $('.ui-unselecting', this.element[0]).each(function() {
217
+ var selectee = $.data(this, "selectable-item");
218
+ selectee.$element.removeClass('ui-unselecting');
219
+ selectee.unselecting = false;
220
+ selectee.startselected = false;
221
+ self._trigger("unselected", event, {
222
+ unselected: selectee.element
223
+ });
224
+ });
225
+ $('.ui-selecting', this.element[0]).each(function() {
226
+ var selectee = $.data(this, "selectable-item");
227
+ selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
228
+ selectee.selecting = false;
229
+ selectee.selected = true;
230
+ selectee.startselected = true;
231
+ self._trigger("selected", event, {
232
+ selected: selectee.element
233
+ });
234
+ });
235
+ this._trigger("stop", event);
236
+
237
+ this.helper.remove();
238
+
239
+ return false;
240
+ }
241
+
242
+ }));
243
+
244
+ $.extend($.ui.selectable, {
245
+ version: "1.7.2",
246
+ defaults: {
247
+ appendTo: 'body',
248
+ autoRefresh: true,
249
+ cancel: ":input,option",
250
+ delay: 0,
251
+ distance: 0,
252
+ filter: '*',
253
+ tolerance: 'touch'
254
+ }
255
+ });
256
+
257
+ })(jQuery);
@@ -0,0 +1,1019 @@
1
+ /*
2
+ * jQuery UI Sortable 1.7.2
3
+ *
4
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
5
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
6
+ * and GPL (GPL-LICENSE.txt) licenses.
7
+ *
8
+ * http://docs.jquery.com/UI/Sortables
9
+ *
10
+ * Depends:
11
+ * ui.core.js
12
+ */
13
+ (function($) {
14
+
15
+ $.widget("ui.sortable", $.extend({}, $.ui.mouse, {
16
+ _init: function() {
17
+
18
+ var o = this.options;
19
+ this.containerCache = {};
20
+ this.element.addClass("ui-sortable");
21
+
22
+ //Get the items
23
+ this.refresh();
24
+
25
+ //Let's determine if the items are floating
26
+ this.floating = this.items.length ? (/left|right/).test(this.items[0].item.css('float')) : false;
27
+
28
+ //Let's determine the parent's offset
29
+ this.offset = this.element.offset();
30
+
31
+ //Initialize mouse events for interaction
32
+ this._mouseInit();
33
+
34
+ },
35
+
36
+ destroy: function() {
37
+ this.element
38
+ .removeClass("ui-sortable ui-sortable-disabled")
39
+ .removeData("sortable")
40
+ .unbind(".sortable");
41
+ this._mouseDestroy();
42
+
43
+ for ( var i = this.items.length - 1; i >= 0; i-- )
44
+ this.items[i].item.removeData("sortable-item");
45
+ },
46
+
47
+ _mouseCapture: function(event, overrideHandle) {
48
+
49
+ if (this.reverting) {
50
+ return false;
51
+ }
52
+
53
+ if(this.options.disabled || this.options.type == 'static') return false;
54
+
55
+ //We have to refresh the items data once first
56
+ this._refreshItems(event);
57
+
58
+ //Find out if the clicked node (or one of its parents) is a actual item in this.items
59
+ var currentItem = null, self = this, nodes = $(event.target).parents().each(function() {
60
+ if($.data(this, 'sortable-item') == self) {
61
+ currentItem = $(this);
62
+ return false;
63
+ }
64
+ });
65
+ if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target);
66
+
67
+ if(!currentItem) return false;
68
+ if(this.options.handle && !overrideHandle) {
69
+ var validHandle = false;
70
+
71
+ $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
72
+ if(!validHandle) return false;
73
+ }
74
+
75
+ this.currentItem = currentItem;
76
+ this._removeCurrentsFromItems();
77
+ return true;
78
+
79
+ },
80
+
81
+ _mouseStart: function(event, overrideHandle, noActivation) {
82
+
83
+ var o = this.options, self = this;
84
+ this.currentContainer = this;
85
+
86
+ //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
87
+ this.refreshPositions();
88
+
89
+ //Create and append the visible helper
90
+ this.helper = this._createHelper(event);
91
+
92
+ //Cache the helper size
93
+ this._cacheHelperProportions();
94
+
95
+ /*
96
+ * - Position generation -
97
+ * This block generates everything position related - it's the core of draggables.
98
+ */
99
+
100
+ //Cache the margins of the original element
101
+ this._cacheMargins();
102
+
103
+ //Get the next scrolling parent
104
+ this.scrollParent = this.helper.scrollParent();
105
+
106
+ //The element's absolute position on the page minus margins
107
+ this.offset = this.currentItem.offset();
108
+ this.offset = {
109
+ top: this.offset.top - this.margins.top,
110
+ left: this.offset.left - this.margins.left
111
+ };
112
+
113
+ // Only after we got the offset, we can change the helper's position to absolute
114
+ // TODO: Still need to figure out a way to make relative sorting possible
115
+ this.helper.css("position", "absolute");
116
+ this.cssPosition = this.helper.css("position");
117
+
118
+ $.extend(this.offset, {
119
+ click: { //Where the click happened, relative to the element
120
+ left: event.pageX - this.offset.left,
121
+ top: event.pageY - this.offset.top
122
+ },
123
+ parent: this._getParentOffset(),
124
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
125
+ });
126
+
127
+ //Generate the original position
128
+ this.originalPosition = this._generatePosition(event);
129
+ this.originalPageX = event.pageX;
130
+ this.originalPageY = event.pageY;
131
+
132
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
133
+ if(o.cursorAt)
134
+ this._adjustOffsetFromHelper(o.cursorAt);
135
+
136
+ //Cache the former DOM position
137
+ this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
138
+
139
+ //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
140
+ if(this.helper[0] != this.currentItem[0]) {
141
+ this.currentItem.hide();
142
+ }
143
+
144
+ //Create the placeholder
145
+ this._createPlaceholder();
146
+
147
+ //Set a containment if given in the options
148
+ if(o.containment)
149
+ this._setContainment();
150
+
151
+ if(o.cursor) { // cursor option
152
+ if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
153
+ $('body').css("cursor", o.cursor);
154
+ }
155
+
156
+ if(o.opacity) { // opacity option
157
+ if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
158
+ this.helper.css("opacity", o.opacity);
159
+ }
160
+
161
+ if(o.zIndex) { // zIndex option
162
+ if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
163
+ this.helper.css("zIndex", o.zIndex);
164
+ }
165
+
166
+ //Prepare scrolling
167
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
168
+ this.overflowOffset = this.scrollParent.offset();
169
+
170
+ //Call callbacks
171
+ this._trigger("start", event, this._uiHash());
172
+
173
+ //Recache the helper size
174
+ if(!this._preserveHelperProportions)
175
+ this._cacheHelperProportions();
176
+
177
+
178
+ //Post 'activate' events to possible containers
179
+ if(!noActivation) {
180
+ for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
181
+ }
182
+
183
+ //Prepare possible droppables
184
+ if($.ui.ddmanager)
185
+ $.ui.ddmanager.current = this;
186
+
187
+ if ($.ui.ddmanager && !o.dropBehaviour)
188
+ $.ui.ddmanager.prepareOffsets(this, event);
189
+
190
+ this.dragging = true;
191
+
192
+ this.helper.addClass("ui-sortable-helper");
193
+ this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
194
+ return true;
195
+
196
+ },
197
+
198
+ _mouseDrag: function(event) {
199
+
200
+ //Compute the helpers position
201
+ this.position = this._generatePosition(event);
202
+ this.positionAbs = this._convertPositionTo("absolute");
203
+
204
+ if (!this.lastPositionAbs) {
205
+ this.lastPositionAbs = this.positionAbs;
206
+ }
207
+
208
+ //Do scrolling
209
+ if(this.options.scroll) {
210
+ var o = this.options, scrolled = false;
211
+ if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
212
+
213
+ if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
214
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
215
+ else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
216
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
217
+
218
+ if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
219
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
220
+ else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
221
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
222
+
223
+ } else {
224
+
225
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
226
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
227
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
228
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
229
+
230
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
231
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
232
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
233
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
234
+
235
+ }
236
+
237
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
238
+ $.ui.ddmanager.prepareOffsets(this, event);
239
+ }
240
+
241
+ //Regenerate the absolute position used for position checks
242
+ this.positionAbs = this._convertPositionTo("absolute");
243
+
244
+ //Set the helper position
245
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
246
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
247
+
248
+ //Rearrange
249
+ for (var i = this.items.length - 1; i >= 0; i--) {
250
+
251
+ //Cache variables and intersection, continue if no intersection
252
+ var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
253
+ if (!intersection) continue;
254
+
255
+ if(itemElement != this.currentItem[0] //cannot intersect with itself
256
+ && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
257
+ && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
258
+ && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
259
+ ) {
260
+
261
+ this.direction = intersection == 1 ? "down" : "up";
262
+
263
+ if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
264
+ this._rearrange(event, item);
265
+ } else {
266
+ break;
267
+ }
268
+
269
+ this._trigger("change", event, this._uiHash());
270
+ break;
271
+ }
272
+ }
273
+
274
+ //Post events to containers
275
+ this._contactContainers(event);
276
+
277
+ //Interconnect with droppables
278
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
279
+
280
+ //Call callbacks
281
+ this._trigger('sort', event, this._uiHash());
282
+
283
+ this.lastPositionAbs = this.positionAbs;
284
+ return false;
285
+
286
+ },
287
+
288
+ _mouseStop: function(event, noPropagation) {
289
+
290
+ if(!event) return;
291
+
292
+ //If we are using droppables, inform the manager about the drop
293
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
294
+ $.ui.ddmanager.drop(this, event);
295
+
296
+ if(this.options.revert) {
297
+ var self = this;
298
+ var cur = self.placeholder.offset();
299
+
300
+ self.reverting = true;
301
+
302
+ $(this.helper).animate({
303
+ left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
304
+ top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
305
+ }, parseInt(this.options.revert, 10) || 500, function() {
306
+ self._clear(event);
307
+ });
308
+ } else {
309
+ this._clear(event, noPropagation);
310
+ }
311
+
312
+ return false;
313
+
314
+ },
315
+
316
+ cancel: function() {
317
+
318
+ var self = this;
319
+
320
+ if(this.dragging) {
321
+
322
+ this._mouseUp();
323
+
324
+ if(this.options.helper == "original")
325
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
326
+ else
327
+ this.currentItem.show();
328
+
329
+ //Post deactivating events to containers
330
+ for (var i = this.containers.length - 1; i >= 0; i--){
331
+ this.containers[i]._trigger("deactivate", null, self._uiHash(this));
332
+ if(this.containers[i].containerCache.over) {
333
+ this.containers[i]._trigger("out", null, self._uiHash(this));
334
+ this.containers[i].containerCache.over = 0;
335
+ }
336
+ }
337
+
338
+ }
339
+
340
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
341
+ if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
342
+ if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
343
+
344
+ $.extend(this, {
345
+ helper: null,
346
+ dragging: false,
347
+ reverting: false,
348
+ _noFinalSort: null
349
+ });
350
+
351
+ if(this.domPosition.prev) {
352
+ $(this.domPosition.prev).after(this.currentItem);
353
+ } else {
354
+ $(this.domPosition.parent).prepend(this.currentItem);
355
+ }
356
+
357
+ return true;
358
+
359
+ },
360
+
361
+ serialize: function(o) {
362
+
363
+ var items = this._getItemsAsjQuery(o && o.connected);
364
+ var str = []; o = o || {};
365
+
366
+ $(items).each(function() {
367
+ var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
368
+ if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
369
+ });
370
+
371
+ return str.join('&');
372
+
373
+ },
374
+
375
+ toArray: function(o) {
376
+
377
+ var items = this._getItemsAsjQuery(o && o.connected);
378
+ var ret = []; o = o || {};
379
+
380
+ items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
381
+ return ret;
382
+
383
+ },
384
+
385
+ /* Be careful with the following core functions */
386
+ _intersectsWith: function(item) {
387
+
388
+ var x1 = this.positionAbs.left,
389
+ x2 = x1 + this.helperProportions.width,
390
+ y1 = this.positionAbs.top,
391
+ y2 = y1 + this.helperProportions.height;
392
+
393
+ var l = item.left,
394
+ r = l + item.width,
395
+ t = item.top,
396
+ b = t + item.height;
397
+
398
+ var dyClick = this.offset.click.top,
399
+ dxClick = this.offset.click.left;
400
+
401
+ var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
402
+
403
+ if( this.options.tolerance == "pointer"
404
+ || this.options.forcePointerForContainers
405
+ || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
406
+ ) {
407
+ return isOverElement;
408
+ } else {
409
+
410
+ return (l < x1 + (this.helperProportions.width / 2) // Right Half
411
+ && x2 - (this.helperProportions.width / 2) < r // Left Half
412
+ && t < y1 + (this.helperProportions.height / 2) // Bottom Half
413
+ && y2 - (this.helperProportions.height / 2) < b ); // Top Half
414
+
415
+ }
416
+ },
417
+
418
+ _intersectsWithPointer: function(item) {
419
+
420
+ var isOverElementHeight = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
421
+ isOverElementWidth = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
422
+ isOverElement = isOverElementHeight && isOverElementWidth,
423
+ verticalDirection = this._getDragVerticalDirection(),
424
+ horizontalDirection = this._getDragHorizontalDirection();
425
+
426
+ if (!isOverElement)
427
+ return false;
428
+
429
+ return this.floating ?
430
+ ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
431
+ : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
432
+
433
+ },
434
+
435
+ _intersectsWithSides: function(item) {
436
+
437
+ var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
438
+ isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
439
+ verticalDirection = this._getDragVerticalDirection(),
440
+ horizontalDirection = this._getDragHorizontalDirection();
441
+
442
+ if (this.floating && horizontalDirection) {
443
+ return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
444
+ } else {
445
+ return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
446
+ }
447
+
448
+ },
449
+
450
+ _getDragVerticalDirection: function() {
451
+ var delta = this.positionAbs.top - this.lastPositionAbs.top;
452
+ return delta != 0 && (delta > 0 ? "down" : "up");
453
+ },
454
+
455
+ _getDragHorizontalDirection: function() {
456
+ var delta = this.positionAbs.left - this.lastPositionAbs.left;
457
+ return delta != 0 && (delta > 0 ? "right" : "left");
458
+ },
459
+
460
+ refresh: function(event) {
461
+ this._refreshItems(event);
462
+ this.refreshPositions();
463
+ },
464
+
465
+ _connectWith: function() {
466
+ var options = this.options;
467
+ return options.connectWith.constructor == String
468
+ ? [options.connectWith]
469
+ : options.connectWith;
470
+ },
471
+
472
+ _getItemsAsjQuery: function(connected) {
473
+
474
+ var self = this;
475
+ var items = [];
476
+ var queries = [];
477
+ var connectWith = this._connectWith();
478
+
479
+ if(connectWith && connected) {
480
+ for (var i = connectWith.length - 1; i >= 0; i--){
481
+ var cur = $(connectWith[i]);
482
+ for (var j = cur.length - 1; j >= 0; j--){
483
+ var inst = $.data(cur[j], 'sortable');
484
+ if(inst && inst != this && !inst.options.disabled) {
485
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper"), inst]);
486
+ }
487
+ };
488
+ };
489
+ }
490
+
491
+ queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper"), this]);
492
+
493
+ for (var i = queries.length - 1; i >= 0; i--){
494
+ queries[i][0].each(function() {
495
+ items.push(this);
496
+ });
497
+ };
498
+
499
+ return $(items);
500
+
501
+ },
502
+
503
+ _removeCurrentsFromItems: function() {
504
+
505
+ var list = this.currentItem.find(":data(sortable-item)");
506
+
507
+ for (var i=0; i < this.items.length; i++) {
508
+
509
+ for (var j=0; j < list.length; j++) {
510
+ if(list[j] == this.items[i].item[0])
511
+ this.items.splice(i,1);
512
+ };
513
+
514
+ };
515
+
516
+ },
517
+
518
+ _refreshItems: function(event) {
519
+
520
+ this.items = [];
521
+ this.containers = [this];
522
+ var items = this.items;
523
+ var self = this;
524
+ var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
525
+ var connectWith = this._connectWith();
526
+
527
+ if(connectWith) {
528
+ for (var i = connectWith.length - 1; i >= 0; i--){
529
+ var cur = $(connectWith[i]);
530
+ for (var j = cur.length - 1; j >= 0; j--){
531
+ var inst = $.data(cur[j], 'sortable');
532
+ if(inst && inst != this && !inst.options.disabled) {
533
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
534
+ this.containers.push(inst);
535
+ }
536
+ };
537
+ };
538
+ }
539
+
540
+ for (var i = queries.length - 1; i >= 0; i--) {
541
+ var targetData = queries[i][1];
542
+ var _queries = queries[i][0];
543
+
544
+ for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
545
+ var item = $(_queries[j]);
546
+
547
+ item.data('sortable-item', targetData); // Data for target checking (mouse manager)
548
+
549
+ items.push({
550
+ item: item,
551
+ instance: targetData,
552
+ width: 0, height: 0,
553
+ left: 0, top: 0
554
+ });
555
+ };
556
+ };
557
+
558
+ },
559
+
560
+ refreshPositions: function(fast) {
561
+
562
+ //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
563
+ if(this.offsetParent && this.helper) {
564
+ this.offset.parent = this._getParentOffset();
565
+ }
566
+
567
+ for (var i = this.items.length - 1; i >= 0; i--){
568
+ var item = this.items[i];
569
+
570
+ //We ignore calculating positions of all connected containers when we're not over them
571
+ if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
572
+ continue;
573
+
574
+ var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
575
+
576
+ if (!fast) {
577
+ item.width = t.outerWidth();
578
+ item.height = t.outerHeight();
579
+ }
580
+
581
+ var p = t.offset();
582
+ item.left = p.left;
583
+ item.top = p.top;
584
+ };
585
+
586
+ if(this.options.custom && this.options.custom.refreshContainers) {
587
+ this.options.custom.refreshContainers.call(this);
588
+ } else {
589
+ for (var i = this.containers.length - 1; i >= 0; i--){
590
+ var p = this.containers[i].element.offset();
591
+ this.containers[i].containerCache.left = p.left;
592
+ this.containers[i].containerCache.top = p.top;
593
+ this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
594
+ this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
595
+ };
596
+ }
597
+
598
+ },
599
+
600
+ _createPlaceholder: function(that) {
601
+
602
+ var self = that || this, o = self.options;
603
+
604
+ if(!o.placeholder || o.placeholder.constructor == String) {
605
+ var className = o.placeholder;
606
+ o.placeholder = {
607
+ element: function() {
608
+
609
+ var el = $(document.createElement(self.currentItem[0].nodeName))
610
+ .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder")
611
+ .removeClass("ui-sortable-helper")[0];
612
+
613
+ if(!className)
614
+ el.style.visibility = "hidden";
615
+
616
+ return el;
617
+ },
618
+ update: function(container, p) {
619
+
620
+ // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
621
+ // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
622
+ if(className && !o.forcePlaceholderSize) return;
623
+
624
+ //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
625
+ if(!p.height()) { p.height(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); };
626
+ if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); };
627
+ }
628
+ };
629
+ }
630
+
631
+ //Create the placeholder
632
+ self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem));
633
+
634
+ //Append it after the actual current item
635
+ self.currentItem.after(self.placeholder);
636
+
637
+ //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
638
+ o.placeholder.update(self, self.placeholder);
639
+
640
+ },
641
+
642
+ _contactContainers: function(event) {
643
+ for (var i = this.containers.length - 1; i >= 0; i--){
644
+
645
+ if(this._intersectsWith(this.containers[i].containerCache)) {
646
+ if(!this.containers[i].containerCache.over) {
647
+
648
+ if(this.currentContainer != this.containers[i]) {
649
+
650
+ //When entering a new container, we will find the item with the least distance and append our item near it
651
+ var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[i].floating ? 'left' : 'top'];
652
+ for (var j = this.items.length - 1; j >= 0; j--) {
653
+ if(!$.ui.contains(this.containers[i].element[0], this.items[j].item[0])) continue;
654
+ var cur = this.items[j][this.containers[i].floating ? 'left' : 'top'];
655
+ if(Math.abs(cur - base) < dist) {
656
+ dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
657
+ }
658
+ }
659
+
660
+ if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
661
+ continue;
662
+
663
+ this.currentContainer = this.containers[i];
664
+ itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[i].element, true);
665
+ this._trigger("change", event, this._uiHash());
666
+ this.containers[i]._trigger("change", event, this._uiHash(this));
667
+
668
+ //Update the placeholder
669
+ this.options.placeholder.update(this.currentContainer, this.placeholder);
670
+
671
+ }
672
+
673
+ this.containers[i]._trigger("over", event, this._uiHash(this));
674
+ this.containers[i].containerCache.over = 1;
675
+ }
676
+ } else {
677
+ if(this.containers[i].containerCache.over) {
678
+ this.containers[i]._trigger("out", event, this._uiHash(this));
679
+ this.containers[i].containerCache.over = 0;
680
+ }
681
+ }
682
+
683
+ };
684
+ },
685
+
686
+ _createHelper: function(event) {
687
+
688
+ var o = this.options;
689
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
690
+
691
+ if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
692
+ $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
693
+
694
+ if(helper[0] == this.currentItem[0])
695
+ this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
696
+
697
+ if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
698
+ if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
699
+
700
+ return helper;
701
+
702
+ },
703
+
704
+ _adjustOffsetFromHelper: function(obj) {
705
+ if(obj.left != undefined) this.offset.click.left = obj.left + this.margins.left;
706
+ if(obj.right != undefined) this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
707
+ if(obj.top != undefined) this.offset.click.top = obj.top + this.margins.top;
708
+ if(obj.bottom != undefined) this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
709
+ },
710
+
711
+ _getParentOffset: function() {
712
+
713
+
714
+ //Get the offsetParent and cache its position
715
+ this.offsetParent = this.helper.offsetParent();
716
+ var po = this.offsetParent.offset();
717
+
718
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
719
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
720
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
721
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
722
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
723
+ po.left += this.scrollParent.scrollLeft();
724
+ po.top += this.scrollParent.scrollTop();
725
+ }
726
+
727
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
728
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
729
+ po = { top: 0, left: 0 };
730
+
731
+ return {
732
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
733
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
734
+ };
735
+
736
+ },
737
+
738
+ _getRelativeOffset: function() {
739
+
740
+ if(this.cssPosition == "relative") {
741
+ var p = this.currentItem.position();
742
+ return {
743
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
744
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
745
+ };
746
+ } else {
747
+ return { top: 0, left: 0 };
748
+ }
749
+
750
+ },
751
+
752
+ _cacheMargins: function() {
753
+ this.margins = {
754
+ left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
755
+ top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
756
+ };
757
+ },
758
+
759
+ _cacheHelperProportions: function() {
760
+ this.helperProportions = {
761
+ width: this.helper.outerWidth(),
762
+ height: this.helper.outerHeight()
763
+ };
764
+ },
765
+
766
+ _setContainment: function() {
767
+
768
+ var o = this.options;
769
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
770
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
771
+ 0 - this.offset.relative.left - this.offset.parent.left,
772
+ 0 - this.offset.relative.top - this.offset.parent.top,
773
+ $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
774
+ ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
775
+ ];
776
+
777
+ if(!(/^(document|window|parent)$/).test(o.containment)) {
778
+ var ce = $(o.containment)[0];
779
+ var co = $(o.containment).offset();
780
+ var over = ($(ce).css("overflow") != 'hidden');
781
+
782
+ this.containment = [
783
+ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
784
+ co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
785
+ co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
786
+ co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
787
+ ];
788
+ }
789
+
790
+ },
791
+
792
+ _convertPositionTo: function(d, pos) {
793
+
794
+ if(!pos) pos = this.position;
795
+ var mod = d == "absolute" ? 1 : -1;
796
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
797
+
798
+ return {
799
+ top: (
800
+ pos.top // The absolute mouse position
801
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
802
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
803
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
804
+ ),
805
+ left: (
806
+ pos.left // The absolute mouse position
807
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
808
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
809
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
810
+ )
811
+ };
812
+
813
+ },
814
+
815
+ _generatePosition: function(event) {
816
+
817
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
818
+
819
+ // This is another very weird special case that only happens for relative elements:
820
+ // 1. If the css position is relative
821
+ // 2. and the scroll parent is the document or similar to the offset parent
822
+ // we have to refresh the relative offset during the scroll so there are no jumps
823
+ if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
824
+ this.offset.relative = this._getRelativeOffset();
825
+ }
826
+
827
+ var pageX = event.pageX;
828
+ var pageY = event.pageY;
829
+
830
+ /*
831
+ * - Position constraining -
832
+ * Constrain the position to a mix of grid, containment.
833
+ */
834
+
835
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
836
+
837
+ if(this.containment) {
838
+ if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
839
+ if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
840
+ if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
841
+ if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
842
+ }
843
+
844
+ if(o.grid) {
845
+ var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
846
+ pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
847
+
848
+ var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
849
+ pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
850
+ }
851
+
852
+ }
853
+
854
+ return {
855
+ top: (
856
+ pageY // The absolute mouse position
857
+ - this.offset.click.top // Click offset (relative to the element)
858
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
859
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
860
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
861
+ ),
862
+ left: (
863
+ pageX // The absolute mouse position
864
+ - this.offset.click.left // Click offset (relative to the element)
865
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
866
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
867
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
868
+ )
869
+ };
870
+
871
+ },
872
+
873
+ _rearrange: function(event, i, a, hardRefresh) {
874
+
875
+ a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
876
+
877
+ //Various things done here to improve the performance:
878
+ // 1. we create a setTimeout, that calls refreshPositions
879
+ // 2. on the instance, we have a counter variable, that get's higher after every append
880
+ // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
881
+ // 4. this lets only the last addition to the timeout stack through
882
+ this.counter = this.counter ? ++this.counter : 1;
883
+ var self = this, counter = this.counter;
884
+
885
+ window.setTimeout(function() {
886
+ if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
887
+ },0);
888
+
889
+ },
890
+
891
+ _clear: function(event, noPropagation) {
892
+
893
+ this.reverting = false;
894
+ // We delay all events that have to be triggered to after the point where the placeholder has been removed and
895
+ // everything else normalized again
896
+ var delayedTriggers = [], self = this;
897
+
898
+ // We first have to update the dom position of the actual currentItem
899
+ // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
900
+ if(!this._noFinalSort && this.currentItem[0].parentNode) this.placeholder.before(this.currentItem);
901
+ this._noFinalSort = null;
902
+
903
+ if(this.helper[0] == this.currentItem[0]) {
904
+ for(var i in this._storedCSS) {
905
+ if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
906
+ }
907
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
908
+ } else {
909
+ this.currentItem.show();
910
+ }
911
+
912
+ if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
913
+ if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
914
+ if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
915
+ if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
916
+ for (var i = this.containers.length - 1; i >= 0; i--){
917
+ if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
918
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
919
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
920
+ }
921
+ };
922
+ };
923
+
924
+ //Post events to containers
925
+ for (var i = this.containers.length - 1; i >= 0; i--){
926
+ if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
927
+ if(this.containers[i].containerCache.over) {
928
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
929
+ this.containers[i].containerCache.over = 0;
930
+ }
931
+ }
932
+
933
+ //Do what was originally in plugins
934
+ if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
935
+ if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset cursor
936
+ if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
937
+
938
+ this.dragging = false;
939
+ if(this.cancelHelperRemoval) {
940
+ if(!noPropagation) {
941
+ this._trigger("beforeStop", event, this._uiHash());
942
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
943
+ this._trigger("stop", event, this._uiHash());
944
+ }
945
+ return false;
946
+ }
947
+
948
+ if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
949
+
950
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
951
+ this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
952
+
953
+ if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
954
+
955
+ if(!noPropagation) {
956
+ for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
957
+ this._trigger("stop", event, this._uiHash());
958
+ }
959
+
960
+ this.fromOutside = false;
961
+ return true;
962
+
963
+ },
964
+
965
+ _trigger: function() {
966
+ if ($.widget.prototype._trigger.apply(this, arguments) === false) {
967
+ this.cancel();
968
+ }
969
+ },
970
+
971
+ _uiHash: function(inst) {
972
+ var self = inst || this;
973
+ return {
974
+ helper: self.helper,
975
+ placeholder: self.placeholder || $([]),
976
+ position: self.position,
977
+ absolutePosition: self.positionAbs, //deprecated
978
+ offset: self.positionAbs,
979
+ item: self.currentItem,
980
+ sender: inst ? inst.element : null
981
+ };
982
+ }
983
+
984
+ }));
985
+
986
+ $.extend($.ui.sortable, {
987
+ getter: "serialize toArray",
988
+ version: "1.7.2",
989
+ eventPrefix: "sort",
990
+ defaults: {
991
+ appendTo: "parent",
992
+ axis: false,
993
+ cancel: ":input,option",
994
+ connectWith: false,
995
+ containment: false,
996
+ cursor: 'auto',
997
+ cursorAt: false,
998
+ delay: 0,
999
+ distance: 1,
1000
+ dropOnEmpty: true,
1001
+ forcePlaceholderSize: false,
1002
+ forceHelperSize: false,
1003
+ grid: false,
1004
+ handle: false,
1005
+ helper: "original",
1006
+ items: '> *',
1007
+ opacity: false,
1008
+ placeholder: false,
1009
+ revert: false,
1010
+ scroll: true,
1011
+ scrollSensitivity: 20,
1012
+ scrollSpeed: 20,
1013
+ scope: "default",
1014
+ tolerance: "intersect",
1015
+ zIndex: 1000
1016
+ }
1017
+ });
1018
+
1019
+ })(jQuery);