From 1cfa392fba3ac9549a352b18db6f4456334a0a4d Mon Sep 17 00:00:00 2001 From: "AngularUI (via TravisCI)" Date: Tue, 18 Feb 2014 20:03:38 +0000 Subject: [PATCH] Travis commit : build 163 --- bower.json | 4 +- sortable.js | 156 +++++++++++++++++++++++++++++++++++------------- sortable.min.js | 4 +- 3 files changed, 119 insertions(+), 45 deletions(-) diff --git a/bower.json b/bower.json index 7130c3e..f52d14e 100644 --- a/bower.json +++ b/bower.json @@ -1,9 +1,9 @@ { "name": "angular-ui-sortable", - "version": "0.10.1", + "version": "0.12.0", "main": "./sortable.js", "dependencies": { - "angular": "~1.0.x", + "angular": "~1.2.x", "jquery-ui": ">= 1.9" } } diff --git a/sortable.js b/sortable.js index 34d899d..380813a 100644 --- a/sortable.js +++ b/sortable.js @@ -3,14 +3,16 @@ jQuery UI Sortable plugin wrapper @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config -*/ + */ angular.module('ui.sortable', []).value('uiSortableConfig', {}).directive('uiSortable', [ 'uiSortableConfig', + '$timeout', '$log', - function (uiSortableConfig, log) { + function (uiSortableConfig, $timeout, $log) { return { require: '?ngModel', link: function (scope, element, attrs, ngModel) { + var savedNodes; function combineCallbacks(first, second) { if (second && typeof second === 'function') { return function (e, ui) { @@ -28,60 +30,134 @@ angular.module('ui.sortable', []).value('uiSortableConfig', {}).directive('uiSor stop: null, update: null }; - var apply = function (e, ui) { - if (ui.item.sortable.resort || ui.item.sortable.relocate) { - scope.$apply(); - } - }; angular.extend(opts, uiSortableConfig); if (ngModel) { - ngModel.$render = function () { - element.sortable('refresh'); - }; + // When we add or remove elements, we need the sortable to 'refresh' + // so it can find the new/removed elements. + scope.$watch(attrs.ngModel + '.length', function () { + // Timeout to let ng-repeat modify the DOM + $timeout(function () { + element.sortable('refresh'); + }); + }); callbacks.start = function (e, ui) { - // Save position of dragged item - ui.item.sortable = { index: ui.item.index() }; + // Save the starting position of dragged item + ui.item.sortable = { + index: ui.item.index(), + cancel: function () { + ui.item.sortable._isCanceled = true; + }, + isCanceled: function () { + return ui.item.sortable._isCanceled; + }, + _isCanceled: false + }; }; - callbacks.update = function (e, ui) { - // For some reason the reference to ngModel in stop() is wrong - ui.item.sortable.resort = ngModel; + callbacks.activate = function () { + // We need to make a copy of the current element's contents so + // we can restore it after sortable has messed it up. + // This is inside activate (instead of start) in order to save + // both lists when dragging between connected lists. + savedNodes = element.contents(); + // If this list has a placeholder (the connected lists won't), + // don't inlcude it in saved nodes. + var placeholder = element.sortable('option', 'placeholder'); + // placeholder.element will be a function if the placeholder, has + // been created (placeholder will be an object). If it hasn't + // been created, either placeholder will be false if no + // placeholder class was given or placeholder.element will be + // undefined if a class was given (placeholder will be a string) + if (placeholder && placeholder.element && typeof placeholder.element === 'function') { + var phElement = placeholder.element(); + // workaround for jquery ui 1.9.x, + // not returning jquery collection + if (!phElement.jquery) { + phElement = angular.element(phElement); + } + // exact match with the placeholder's class attribute to handle + // the case that multiple connected sortables exist and + // the placehoilder option equals the class of sortable items + var excludes = element.find('[class="' + phElement.attr('class') + '"]'); + savedNodes = savedNodes.not(excludes); + } }; - callbacks.receive = function (e, ui) { - ui.item.sortable.relocate = true; - // if the item still exists (it has not been cancelled) - if ('moved' in ui.item.sortable) { - // added item to array into correct position and set up flag - ngModel.$modelValue.splice(ui.item.index(), 0, ui.item.sortable.moved); + callbacks.update = function (e, ui) { + // Save current drop position but only if this is not a second + // update that happens when moving between lists because then + // the value will be overwritten with the old value + if (!ui.item.sortable.received) { + ui.item.sortable.dropindex = ui.item.index(); + ui.item.sortable.droptarget = ui.item.parent(); + // Cancel the sort (let ng-repeat do the sort for us) + // Don't cancel if this is the received list because it has + // already been canceled in the other list, and trying to cancel + // here will mess up the DOM. + element.sortable('cancel'); + } + // Put the nodes back exactly the way they started (this is very + // important because ng-repeat uses comment elements to delineate + // the start and stop of repeat sections and sortable doesn't + // respect their order (even if we cancel, the order of the + // comments are still messed up). + savedNodes.detach(); + if (element.sortable('option', 'helper') === 'clone') { + // first detach all the savedNodes and then restore all of them + // except .ui-sortable-helper element (which is placed last). + // That way it will be garbage collected. + savedNodes = savedNodes.not(savedNodes.last()); + } + savedNodes.appendTo(element); + // If received is true (an item was dropped in from another list) + // then we add the new item to this list otherwise wait until the + // stop event where we will know if it was a sort or item was + // moved here from another list + if (ui.item.sortable.received && !ui.item.sortable.isCanceled()) { + scope.$apply(function () { + ngModel.$modelValue.splice(ui.item.sortable.dropindex, 0, ui.item.sortable.moved); + }); } }; - callbacks.remove = function (e, ui) { - // copy data into item - if (ngModel.$modelValue.length === 1) { - ui.item.sortable.moved = ngModel.$modelValue.splice(0, 1)[0]; + callbacks.stop = function (e, ui) { + // If the received flag hasn't be set on the item, this is a + // normal sort, if dropindex is set, the item was moved, so move + // the items in the list. + if (!ui.item.sortable.received && 'dropindex' in ui.item.sortable && !ui.item.sortable.isCanceled()) { + scope.$apply(function () { + ngModel.$modelValue.splice(ui.item.sortable.dropindex, 0, ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]); + }); } else { - ui.item.sortable.moved = ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]; + // if the item was not moved, then restore the elements + // so that the ngRepeat's comment are correct. + if ((!('dropindex' in ui.item.sortable) || ui.item.sortable.isCanceled()) && element.sortable('option', 'helper') !== 'clone') { + savedNodes.detach().appendTo(element); + } } }; - callbacks.stop = function (e, ui) { - // digest all prepared changes - if (ui.item.sortable.resort && !ui.item.sortable.relocate) { - // Fetch saved and current position of dropped element - var end, start; - start = ui.item.sortable.index; - end = ui.item.index(); - // Reorder array and apply change to scope - ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]); + callbacks.receive = function (e, ui) { + // An item was dropped here from another list, set a flag on the + // item. + ui.item.sortable.received = true; + }; + callbacks.remove = function (e, ui) { + // Remove the item from this list's model and copy data into item, + // so the next list can retrive it + if (!ui.item.sortable.isCanceled()) { + scope.$apply(function () { + ui.item.sortable.moved = ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0]; + }); } }; scope.$watch(attrs.uiSortable, function (newVal) { angular.forEach(newVal, function (value, key) { if (callbacks[key]) { - // wrap the callback - value = combineCallbacks(callbacks[key], value); if (key === 'stop') { // call apply after stop - value = combineCallbacks(value, apply); + value = combineCallbacks(value, function () { + scope.$apply(); + }); } + // wrap the callback + value = combineCallbacks(callbacks[key], value); } element.sortable('option', key, value); }); @@ -89,10 +165,8 @@ angular.module('ui.sortable', []).value('uiSortableConfig', {}).directive('uiSor angular.forEach(callbacks, function (value, key) { opts[key] = combineCallbacks(value, opts[key]); }); - // call apply after stop - opts.stop = combineCallbacks(opts.stop, apply); } else { - log.info('ui.sortable: ngModel not provided!', element); + $log.info('ui.sortable: ngModel not provided!', element); } // Create sortable element.sortable(opts); diff --git a/sortable.min.js b/sortable.min.js index 79bb350..6317ba0 100644 --- a/sortable.min.js +++ b/sortable.min.js @@ -1,7 +1,7 @@ /** * angular-ui-sortable - This directive allows you to jQueryUI Sortable. - * @version v0.10.1 - 2014-02-12 + * @version v0.12.0 - 2014-02-18 * @link http://angular-ui.github.com * @license MIT */ -"use strict";angular.module("ui.sortable",[]).value("uiSortableConfig",{}).directive("uiSortable",["uiSortableConfig","$log",function(a,b){return{require:"?ngModel",link:function(c,d,e,f){function g(a,b){return b&&"function"==typeof b?function(c,d){a(c,d),b(c,d)}:a}var h={},i={receive:null,remove:null,start:null,stop:null,update:null},j=function(a,b){(b.item.sortable.resort||b.item.sortable.relocate)&&c.$apply()};angular.extend(h,a),f?(f.$render=function(){d.sortable("refresh")},i.start=function(a,b){b.item.sortable={index:b.item.index()}},i.update=function(a,b){b.item.sortable.resort=f},i.receive=function(a,b){b.item.sortable.relocate=!0,"moved"in b.item.sortable&&f.$modelValue.splice(b.item.index(),0,b.item.sortable.moved)},i.remove=function(a,b){b.item.sortable.moved=1===f.$modelValue.length?f.$modelValue.splice(0,1)[0]:f.$modelValue.splice(b.item.sortable.index,1)[0]},i.stop=function(a,b){if(b.item.sortable.resort&&!b.item.sortable.relocate){var c,d;d=b.item.sortable.index,c=b.item.index(),b.item.sortable.resort.$modelValue.splice(c,0,b.item.sortable.resort.$modelValue.splice(d,1)[0])}},c.$watch(e.uiSortable,function(a){angular.forEach(a,function(a,b){i[b]&&(a=g(i[b],a),"stop"===b&&(a=g(a,j))),d.sortable("option",b,a)})},!0),angular.forEach(i,function(a,b){h[b]=g(a,h[b])}),h.stop=g(h.stop,j)):b.info("ui.sortable: ngModel not provided!",d),d.sortable(h)}}}]); \ No newline at end of file +"use strict";angular.module("ui.sortable",[]).value("uiSortableConfig",{}).directive("uiSortable",["uiSortableConfig","$timeout","$log",function(a,b,c){return{require:"?ngModel",link:function(d,e,f,g){function h(a,b){return b&&"function"==typeof b?function(c,d){a(c,d),b(c,d)}:a}var i,j={},k={receive:null,remove:null,start:null,stop:null,update:null};angular.extend(j,a),g?(d.$watch(f.ngModel+".length",function(){b(function(){e.sortable("refresh")})}),k.start=function(a,b){b.item.sortable={index:b.item.index(),cancel:function(){b.item.sortable._isCanceled=!0},isCanceled:function(){return b.item.sortable._isCanceled},_isCanceled:!1}},k.activate=function(){i=e.contents();var a=e.sortable("option","placeholder");if(a&&a.element&&"function"==typeof a.element){var b=a.element();b.jquery||(b=angular.element(b));var c=e.find('[class="'+b.attr("class")+'"]');i=i.not(c)}},k.update=function(a,b){b.item.sortable.received||(b.item.sortable.dropindex=b.item.index(),b.item.sortable.droptarget=b.item.parent(),e.sortable("cancel")),i.detach(),"clone"===e.sortable("option","helper")&&(i=i.not(i.last())),i.appendTo(e),b.item.sortable.received&&!b.item.sortable.isCanceled()&&d.$apply(function(){g.$modelValue.splice(b.item.sortable.dropindex,0,b.item.sortable.moved)})},k.stop=function(a,b){!b.item.sortable.received&&"dropindex"in b.item.sortable&&!b.item.sortable.isCanceled()?d.$apply(function(){g.$modelValue.splice(b.item.sortable.dropindex,0,g.$modelValue.splice(b.item.sortable.index,1)[0])}):"dropindex"in b.item.sortable&&!b.item.sortable.isCanceled()||"clone"===e.sortable("option","helper")||i.detach().appendTo(e)},k.receive=function(a,b){b.item.sortable.received=!0},k.remove=function(a,b){b.item.sortable.isCanceled()||d.$apply(function(){b.item.sortable.moved=g.$modelValue.splice(b.item.sortable.index,1)[0]})},d.$watch(f.uiSortable,function(a){angular.forEach(a,function(a,b){k[b]&&("stop"===b&&(a=h(a,function(){d.$apply()})),a=h(k[b],a)),e.sortable("option",b,a)})},!0),angular.forEach(k,function(a,b){j[b]=h(a,j[b])})):c.info("ui.sortable: ngModel not provided!",e),e.sortable(j)}}}]); \ No newline at end of file