-//knockout-sortable | (c) 2012 Ryan Niemeyer | http://www.opensource.org/licenses/mit-license
-define("knockout.sortable",["jquery","utils","knockout"], function() {
-
-(function(ko, $, undefined) {
- var prepareTemplateOptions = function(valueAccessor) {
- var result = {},
- options = ko.utils.unwrapObservable(valueAccessor());
-
- //build our options to pass to the template engine
- if (options.data) {
- result.foreach = options.data;
- result.name = options.template;
- result.afterAdd = options.afterAdd;
- result.beforeRemove = options.beforeRemove;
- result.afterRender = options.afterRender;
- result.includeDestroyed = options.includeDestroyed;
- result.templateEngine = options.templateEngine;
- } else {
- result.foreach = valueAccessor();
- }
-
- //use an afterRender function to add meta-data
- if (options.afterRender) {
- //wrap the existing function, if it was passed
- result.afterRender = function(element, data) {
- ko.bindingHandlers.sortable.afterRender.call(data, element, data);
- options.afterRender.call(data, element, data);
- };
- } else {
- result.afterRender = ko.bindingHandlers.sortable.afterRender;
- }
-
- //return options to pass to the template binding
- return result;
- };
-
- //connect items with observableArrays
- ko.bindingHandlers.sortable = {
- init: function(element, valueAccessor, allBindingsAccessor, data, context) {
- var value = ko.utils.unwrapObservable(valueAccessor()),
- templateOptions = prepareTemplateOptions(valueAccessor),
- sortable = ko.bindingHandlers.sortable,
- connectClass = value.connectClass || sortable.connectClass,
- allowDrop = value.allowDrop === undefined ? sortable.allowDrop : value.allowDrop,
- beforeMove = value.beforeMove || sortable.beforeMove,
- afterMove = value.afterMove || sortable.afterMove,
- options = value.options || sortable.options;
-
- //if allowDrop is an observable or a function, then execute it in a computed observable
- if (ko.isObservable(allowDrop) || typeof allowDrop == "function") {
- ko.computed({
- read: function() {
- var value = ko.utils.unwrapObservable(allowDrop),
- shouldAdd = typeof value == "function" ? value.call(this, templateOptions.foreach) : value;
- ko.utils.toggleDomNodeCssClass(element, connectClass, shouldAdd);
- },
- disposeWhenNodeIsRemoved: element
- }, this);
- } else {
- ko.utils.toggleDomNodeCssClass(element, connectClass, allowDrop);
- }
-
- //attach meta-data
- $(element).data("ko_sortList", templateOptions.foreach);
- $(element).sortable(ko.utils.extend(options, {
- update: function(event, ui) {
- var sourceParent, targetParent, targetIndex, arg,
- item = ui.item.data("ko_sortItem");
-
- if (item) {
- //identify parents
- sourceParent = ui.item.data("ko_parentList");
- targetParent = ui.item.parent().data("ko_sortList");
- targetIndex = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
-
- if (beforeMove || afterMove) {
- arg = {
- item: item,
- sourceParent: sourceParent,
- sourceIndex: sourceParent.indexOf(item),
- targetParent: targetParent,
- targetIndex: targetIndex,
- cancelDrop: false
- };
- }
-
- if (beforeMove) {
- beforeMove.call(this, arg, event, ui);
- if (arg.cancelDrop) {
- $(ui.sender).sortable('cancel');
- return;
- }
- }
-
- if (targetIndex >= 0) {
- sourceParent.remove(item);
- targetParent.splice(targetIndex, 0, item);
- }
- //rendering is handled by manipulating the observableArray; ignore dropped element
- ui.item.remove();
-
- //allow binding to accept a function to execute after moving the item
- if (afterMove) {
- afterMove.call(this, arg, event, ui);
- }
- }
- },
- connectWith: "." + connectClass
- }));
-
- //handle disposal
- ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
- $(element).sortable("destroy");
- });
- //we are wrapping the template binding
- return ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
- },
- update: function(element, valueAccessor, allBindingsAccessor, data, context) {
- var templateOptions = prepareTemplateOptions(valueAccessor);
-
- //call the actual template binding
- ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
- },
- afterRender: function(elements, data) {
- ko.utils.arrayForEach(elements, function(element) {
- if (element.nodeType === 1) {
- $(element).data("ko_sortItem", data);
- $(element).data("ko_parentList", $(element).parent().data("ko_sortList"));
- }
- });
- },
- connectClass: 'ko_container',
- allowDrop: true,
- afterMove: null,
- beforeMove: null,
- options: {}
- };
-})(ko, jQuery);
-
-})
\ No newline at end of file
+//knockout-sortable 0.6.6 | (c) 2012 Ryan Niemeyer | http://www.opensource.org/licenses/mit-license
+(function(factory) {
+ if (typeof define === "function" && define.amd) {
+ // AMD anonymous module
+ define(["knockout", "jquery", "jquery.ui"], factory);
+ } else {
+ // No module loader (plain <script> tag) - put directly in global namespace
+ factory(window.ko, jQuery);
+ }
+})(function(ko, $, undefined) {
+ var ITEMKEY = "ko_sortItem",
+ LISTKEY = "ko_sortList",
+ PARENTKEY = "ko_parentList",
+ DRAGKEY = "ko_dragItem";
+
+ //internal afterRender that adds meta-data to children
+ var addMetaDataAfterRender = function(elements, data) {
+ ko.utils.arrayForEach(elements, function(element) {
+ if (element.nodeType === 1) {
+ ko.utils.domData.set(element, ITEMKEY, data);
+ ko.utils.domData.set(element, PARENTKEY, ko.utils.domData.get(element.parentNode, LISTKEY));
+ }
+ });
+ };
+
+ //prepare the proper options for the template binding
+ var prepareTemplateOptions = function(valueAccessor, dataName) {
+ var result = {},
+ options = ko.utils.unwrapObservable(valueAccessor()),
+ actualAfterRender;
+
+ //build our options to pass to the template engine
+ if (options.data) {
+ result[dataName] = options.data;
+ result.name = options.template;
+ } else {
+ result[dataName] = valueAccessor();
+ }
+
+ ko.utils.arrayForEach(["afterAdd", "afterRender", "beforeRemove", "includeDestroyed", "templateEngine", "templateOptions"], function (option) {
+ result[option] = options[option] || ko.bindingHandlers.sortable[option];
+ });
+
+ //use an afterRender function to add meta-data
+ if (dataName === "foreach") {
+ if (result.afterRender) {
+ //wrap the existing function, if it was passed
+ actualAfterRender = result.afterRender;
+ result.afterRender = function(element, data) {
+ addMetaDataAfterRender.call(data, element, data);
+ actualAfterRender.call(data, element, data);
+ };
+ } else {
+ result.afterRender = addMetaDataAfterRender;
+ }
+ }
+
+ //return options to pass to the template binding
+ return result;
+ };
+
+ //connect items with observableArrays
+ ko.bindingHandlers.sortable = {
+ init: function(element, valueAccessor, allBindingsAccessor, data, context) {
+ var $element = $(element),
+ value = ko.utils.unwrapObservable(valueAccessor()) || {},
+ templateOptions = prepareTemplateOptions(valueAccessor, "foreach"),
+ sortable = {},
+ startActual, updateActual;
+
+ //remove leading/trailing text nodes from anonymous templates
+ ko.utils.arrayForEach(element.childNodes, function(node) {
+ if (node && node.nodeType === 3) {
+ node.parentNode.removeChild(node);
+ }
+ });
+
+ //build a new object that has the global options with overrides from the binding
+ $.extend(true, sortable, ko.bindingHandlers.sortable);
+ if (value.options && sortable.options) {
+ ko.utils.extend(sortable.options, value.options);
+ delete value.options;
+ }
+ ko.utils.extend(sortable, value);
+
+ //if allowDrop is an observable or a function, then execute it in a computed observable
+ if (sortable.connectClass && (ko.isObservable(sortable.allowDrop) || typeof sortable.allowDrop == "function")) {
+ ko.computed({
+ read: function() {
+ var value = ko.utils.unwrapObservable(sortable.allowDrop),
+ shouldAdd = typeof value == "function" ? value.call(this, templateOptions.foreach) : value;
+ ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, shouldAdd);
+ },
+ disposeWhenNodeIsRemoved: element
+ }, this);
+ } else {
+ ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, sortable.allowDrop);
+ }
+
+ //wrap the template binding
+ ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
+
+ //keep a reference to start/update functions that might have been passed in
+ startActual = sortable.options.start;
+ updateActual = sortable.options.update;
+
+ //initialize sortable binding after template binding has rendered in update function
+ setTimeout(function() {
+ var dragItem;
+ $element.sortable(ko.utils.extend(sortable.options, {
+ start: function(event, ui) {
+ //make sure that fields have a chance to update model
+ ui.item.find("input:focus").change();
+ if (startActual) {
+ startActual.apply(this, arguments);
+ }
+ },
+ receive: function(event, ui) {
+ dragItem = ko.utils.domData.get(ui.item[0], DRAGKEY);
+ if (dragItem && dragItem.clone) {
+ dragItem = dragItem.clone();
+ }
+ },
+ update: function(event, ui) {
+ var sourceParent, targetParent, targetIndex, i, targetUnwrapped, arg,
+ el = ui.item[0],
+ item = ko.utils.domData.get(el, ITEMKEY) || dragItem;
+
+ dragItem = null;
+
+ if (this === ui.item.parent()[0] && item) {
+ //identify parents
+ sourceParent = ko.utils.domData.get(el, PARENTKEY);
+ targetParent = ko.utils.domData.get(el.parentNode, LISTKEY);
+ targetIndex = ko.utils.arrayIndexOf(ui.item.parent().children(), el);
+
+ //take destroyed items into consideration
+ if (!templateOptions.includeDestroyed) {
+ if(targetParent){
+ targetUnwrapped = $.isFunction(targetParent)?targetParent():targetParent;
+ for (i = 0; i < targetIndex; i++) {
+ //add one for every destroyed item we find before the targetIndex in the target array
+ if (targetUnwrapped[i] && targetUnwrapped[i]._destroy) {
+ targetIndex++;
+ }
+ }
+ }
+ }
+
+ if (sortable.beforeMove || sortable.afterMove) {
+ arg = {
+ item: item,
+ sourceParent: sourceParent,
+ sourceParentNode: sourceParent && el.parentNode,
+ sourceIndex: sourceParent && sourceParent.indexOf(item),
+ targetParent: targetParent,
+ targetIndex: targetIndex,
+ cancelDrop: false
+ };
+ }
+
+ if (sortable.beforeMove) {
+ sortable.beforeMove.call(this, arg, event, ui);
+ if (arg.cancelDrop) {
+ //call cancel on the correct list
+ if (arg.sourceParent) {
+ $(arg.sourceParent === arg.targetParent ? this : ui.sender).sortable('cancel');
+ }
+ //for a draggable item just remove the element
+ else {
+ $(el).remove();
+ }
+
+ return;
+ }
+ }
+
+ if (targetIndex >= 0) {
+ if (sourceParent) {
+ if( $.isFunction(sourceParent.remove)) sourceParent.remove(item);
+ }
+
+ targetParent.splice(targetIndex, 0, item);
+ }
+
+ //rendering is handled by manipulating the observableArray; ignore dropped element
+ ko.utils.domData.set(el, ITEMKEY, null);
+ ui.item.remove();
+
+ //allow binding to accept a function to execute after moving the item
+ if (sortable.afterMove) {
+ sortable.afterMove.call(this, arg, event, ui);
+ }
+ }
+
+ if (updateActual) {
+ updateActual.apply(this, arguments);
+ }
+ },
+ connectWith: sortable.connectClass ? "." + sortable.connectClass : false
+ }));
+
+ //handle enabling/disabling sorting
+ if (sortable.isEnabled !== undefined) {
+ ko.computed({
+ read: function() {
+ $element.sortable(ko.utils.unwrapObservable(sortable.isEnabled) ? "enable" : "disable");
+ },
+ disposeWhenNodeIsRemoved: element
+ });
+ }
+ }, 0);
+
+ //handle disposal
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
+ $element.sortable("destroy");
+ });
+
+ return { 'controlsDescendantBindings': true };
+ },
+ update: function(element, valueAccessor, allBindingsAccessor, data, context) {
+ var templateOptions = prepareTemplateOptions(valueAccessor, "foreach");
+
+ //attach meta-data
+ ko.utils.domData.set(element, LISTKEY, templateOptions.foreach);
+
+ //call template binding's update with correct options
+ ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
+ },
+ connectClass: 'ko_container',
+ allowDrop: true,
+ afterMove: null,
+ beforeMove: null,
+ options: {}
+ };
+
+ //create a draggable that is appropriate for dropping into a sortable
+ ko.bindingHandlers.draggable = {
+ init: function(element, valueAccessor, allBindingsAccessor, data, context) {
+ var value = ko.utils.unwrapObservable(valueAccessor()) || {},
+ options = value.options || {},
+ draggableOptions = ko.utils.extend({}, ko.bindingHandlers.draggable.options),
+ templateOptions = prepareTemplateOptions(valueAccessor, "data"),
+ connectClass = value.connectClass || ko.bindingHandlers.draggable.connectClass,
+ isEnabled = value.isEnabled !== undefined ? value.isEnabled : ko.bindingHandlers.draggable.isEnabled;
+
+ value = value.data || value;
+
+ //set meta-data
+ ko.utils.domData.set(element, DRAGKEY, value);
+
+ //override global options with override options passed in
+ ko.utils.extend(draggableOptions, options);
+
+ //setup connection to a sortable
+
+ draggableOptions.connectToSortable = connectClass ? "." + connectClass : false;
+
+ //initialize draggable
+ $(element).draggable(draggableOptions);
+
+ //handle enabling/disabling sorting
+ if (isEnabled !== undefined) {
+ ko.computed({
+ read: function() {
+ $(element).draggable(ko.utils.unwrapObservable(isEnabled) ? "enable" : "disable");
+ },
+ disposeWhenNodeIsRemoved: element
+ });
+ }
+
+ return ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
+ },
+ update: function(element, valueAccessor, allBindingsAccessor, data, context) {
+ var templateOptions = prepareTemplateOptions(valueAccessor, "data");
+
+ return ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
+ },
+ connectClass: ko.bindingHandlers.sortable.connectClass,
+ options: {
+ helper: "clone"
+ }
+ };
+
+});