1+ ( function ( window , angular , undefined ) { 'use strict' ;
12/*
23 jQuery UI Sortable plugin wrapper
34
45 @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
56 */
6- angular . module ( 'ui.sortable' , [ ] ) . value ( 'uiSortableConfig' , { } ) . directive ( 'uiSortable' , [
7- 'uiSortableConfig' ,
8- '$timeout' ,
9- '$log' ,
10- function ( uiSortableConfig , $timeout , $log ) {
11- 'use strict' ;
12- return {
13- require : '?ngModel' ,
14- link : function ( scope , element , attrs , ngModel ) {
15- var savedNodes ;
16- function combineCallbacks ( first , second ) {
17- if ( second && typeof second === 'function' ) {
18- return function ( e , ui ) {
19- first ( e , ui ) ;
20- second ( e , ui ) ;
21- } ;
7+ angular . module ( 'ui.sortable' , [ ] )
8+ . value ( 'uiSortableConfig' , { } )
9+ . directive ( 'uiSortable' , [
10+ 'uiSortableConfig' , '$timeout' , '$log' ,
11+ function ( uiSortableConfig , $timeout , $log ) {
12+ 'use strict' ;
13+
14+ return {
15+ require : '?ngModel' ,
16+ link : function ( scope , element , attrs , ngModel ) {
17+ var savedNodes ;
18+
19+ function combineCallbacks ( first , second ) {
20+ if ( second && ( typeof second === 'function' ) ) {
21+ return function ( e , ui ) {
22+ first ( e , ui ) ;
23+ second ( e , ui ) ;
24+ } ;
25+ }
26+ return first ;
2227 }
23- return first ;
24- }
25- var opts = { } ;
26- var callbacks = {
28+
29+ var opts = { } ;
30+
31+ var callbacks = {
2732 receive : null ,
28- remove : null ,
29- start : null ,
30- stop : null ,
31- update : null
33+ remove :null ,
34+ start :null ,
35+ stop :null ,
36+ update :null
3237 } ;
33- angular . extend ( opts , uiSortableConfig ) ;
34- if ( ngModel ) {
35- // When we add or remove elements, we need the sortable to 'refresh'
36- // so it can find the new/removed elements.
37- scope . $watch ( attrs . ngModel + '.length' , function ( ) {
38- // Timeout to let ng-repeat modify the DOM
39- $timeout ( function ( ) {
40- element . sortable ( 'refresh' ) ;
38+
39+ angular . extend ( opts , uiSortableConfig ) ;
40+
41+ if ( ngModel ) {
42+
43+ // When we add or remove elements, we need the sortable to 'refresh'
44+ // so it can find the new/removed elements.
45+ scope . $watch ( attrs . ngModel + '.length' , function ( ) {
46+ // Timeout to let ng-repeat modify the DOM
47+ $timeout ( function ( ) {
48+ element . sortable ( 'refresh' ) ;
49+ } ) ;
4150 } ) ;
42- } ) ;
43- callbacks . start = function ( e , ui ) {
44- // Save the starting position of dragged item
45- ui . item . sortable = {
46- index : ui . item . index ( ) ,
47- cancel : function ( ) {
48- ui . item . sortable . _isCanceled = true ;
49- } ,
50- isCanceled : function ( ) {
51- return ui . item . sortable . _isCanceled ;
52- } ,
53- _isCanceled : false
51+
52+ callbacks . start = function ( e , ui ) {
53+ // Save the starting position of dragged item
54+ ui . item . sortable = {
55+ index : ui . item . index ( ) ,
56+ cancel : function ( ) {
57+ ui . item . sortable . _isCanceled = true ;
58+ } ,
59+ isCanceled : function ( ) {
60+ return ui . item . sortable . _isCanceled ;
61+ } ,
62+ _isCanceled : false
63+ } ;
5464 } ;
55- } ;
56- callbacks . activate = function ( ) {
57- // We need to make a copy of the current element's contents so
58- // we can restore it after sortable has messed it up.
59- // This is inside activate (instead of start) in order to save
60- // both lists when dragging between connected lists.
61- savedNodes = element . contents ( ) ;
62- // If this list has a placeholder (the connected lists won't),
63- // don't inlcude it in saved nodes.
64- var placeholder = element . sortable ( 'option' , 'placeholder' ) ;
65- // placeholder.element will be a function if the placeholder, has
66- // been created (placeholder will be an object). If it hasn't
67- // been created, either placeholder will be false if no
68- // placeholder class was given or placeholder.element will be
69- // undefined if a class was given (placeholder will be a string)
70- if ( placeholder && placeholder . element && typeof placeholder . element === 'function' ) {
71- var phElement = placeholder . element ( ) ;
72- // workaround for jquery ui 1.9.x,
73- // not returning jquery collection
74- if ( ! phElement . jquery ) {
75- phElement = angular . element ( phElement ) ;
65+
66+ callbacks . activate = function ( /*e, ui*/ ) {
67+ // We need to make a copy of the current element's contents so
68+ // we can restore it after sortable has messed it up.
69+ // This is inside activate (instead of start) in order to save
70+ // both lists when dragging between connected lists.
71+ savedNodes = element . contents ( ) ;
72+
73+ // If this list has a placeholder (the connected lists won't),
74+ // don't inlcude it in saved nodes.
75+ var placeholder = element . sortable ( 'option' , 'placeholder' ) ;
76+
77+ // placeholder.element will be a function if the placeholder, has
78+ // been created (placeholder will be an object). If it hasn't
79+ // been created, either placeholder will be false if no
80+ // placeholder class was given or placeholder.element will be
81+ // undefined if a class was given (placeholder will be a string)
82+ if ( placeholder && placeholder . element && typeof placeholder . element === 'function' ) {
83+ var phElement = placeholder . element ( ) ;
84+ // workaround for jquery ui 1.9.x,
85+ // not returning jquery collection
86+ if ( ! phElement . jquery ) {
87+ phElement = angular . element ( phElement ) ;
88+ }
89+
90+ // exact match with the placeholder's class attribute to handle
91+ // the case that multiple connected sortables exist and
92+ // the placehoilder option equals the class of sortable items
93+ var excludes = element . find ( '[class="' + phElement . attr ( 'class' ) + '"]' ) ;
94+
95+ savedNodes = savedNodes . not ( excludes ) ;
7696 }
77- // exact match with the placeholder's class attribute to handle
78- // the case that multiple connected sortables exist and
79- // the placehoilder option equals the class of sortable items
80- var excludes = element . find ( '[class="' + phElement . attr ( 'class' ) + '"]' ) ;
81- savedNodes = savedNodes . not ( excludes ) ;
82- }
83- } ;
84- callbacks . update = function ( e , ui ) {
85- // Save current drop position but only if this is not a second
86- // update that happens when moving between lists because then
87- // the value will be overwritten with the old value
88- if ( ! ui . item . sortable . received ) {
89- ui . item . sortable . dropindex = ui . item . index ( ) ;
90- ui . item . sortable . droptarget = ui . item . parent ( ) ;
91- // Cancel the sort (let ng-repeat do the sort for us)
92- // Don't cancel if this is the received list because it has
93- // already been canceled in the other list, and trying to cancel
94- // here will mess up the DOM.
95- element . sortable ( 'cancel' ) ;
96- }
97- // Put the nodes back exactly the way they started (this is very
98- // important because ng-repeat uses comment elements to delineate
99- // the start and stop of repeat sections and sortable doesn't
100- // respect their order (even if we cancel, the order of the
101- // comments are still messed up).
102- if ( element . sortable ( 'option' , 'helper' ) === 'clone' ) {
103- // restore all the savedNodes except .ui-sortable-helper element
104- // (which is placed last). That way it will be garbage collected.
105- savedNodes = savedNodes . not ( savedNodes . last ( ) ) ;
106- }
107- savedNodes . appendTo ( element ) ;
108- // If received is true (an item was dropped in from another list)
109- // then we add the new item to this list otherwise wait until the
110- // stop event where we will know if it was a sort or item was
111- // moved here from another list
112- if ( ui . item . sortable . received && ! ui . item . sortable . isCanceled ( ) ) {
113- scope . $apply ( function ( ) {
114- ngModel . $modelValue . splice ( ui . item . sortable . dropindex , 0 , ui . item . sortable . moved ) ;
115- } ) ;
116- }
117- } ;
118- callbacks . stop = function ( e , ui ) {
119- // If the received flag hasn't be set on the item, this is a
120- // normal sort, if dropindex is set, the item was moved, so move
121- // the items in the list.
122- if ( ! ui . item . sortable . received && 'dropindex' in ui . item . sortable && ! ui . item . sortable . isCanceled ( ) ) {
123- scope . $apply ( function ( ) {
124- ngModel . $modelValue . splice ( ui . item . sortable . dropindex , 0 , ngModel . $modelValue . splice ( ui . item . sortable . index , 1 ) [ 0 ] ) ;
125- } ) ;
126- } else {
127- // if the item was not moved, then restore the elements
128- // so that the ngRepeat's comment are correct.
129- if ( ( ! ( 'dropindex' in ui . item . sortable ) || ui . item . sortable . isCanceled ( ) ) && element . sortable ( 'option' , 'helper' ) !== 'clone' ) {
130- savedNodes . appendTo ( element ) ;
97+ } ;
98+
99+ callbacks . update = function ( e , ui ) {
100+ // Save current drop position but only if this is not a second
101+ // update that happens when moving between lists because then
102+ // the value will be overwritten with the old value
103+ if ( ! ui . item . sortable . received ) {
104+ ui . item . sortable . dropindex = ui . item . index ( ) ;
105+ ui . item . sortable . droptarget = ui . item . parent ( ) ;
106+
107+ // Cancel the sort (let ng-repeat do the sort for us)
108+ // Don't cancel if this is the received list because it has
109+ // already been canceled in the other list, and trying to cancel
110+ // here will mess up the DOM.
111+ element . sortable ( 'cancel' ) ;
131112 }
132- }
133- } ;
134- callbacks . receive = function ( e , ui ) {
135- // An item was dropped here from another list, set a flag on the
136- // item.
137- ui . item . sortable . received = true ;
138- } ;
139- callbacks . remove = function ( e , ui ) {
140- // Remove the item from this list's model and copy data into item,
141- // so the next list can retrive it
142- if ( ! ui . item . sortable . isCanceled ( ) ) {
143- scope . $apply ( function ( ) {
144- ui . item . sortable . moved = ngModel . $modelValue . splice ( ui . item . sortable . index , 1 ) [ 0 ] ;
145- } ) ;
146- }
147- } ;
148- scope . $watch ( attrs . uiSortable , function ( newVal ) {
149- angular . forEach ( newVal , function ( value , key ) {
150- if ( callbacks [ key ] ) {
151- if ( key === 'stop' ) {
152- // call apply after stop
153- value = combineCallbacks ( value , function ( ) {
154- scope . $apply ( ) ;
155- } ) ;
113+
114+ // Put the nodes back exactly the way they started (this is very
115+ // important because ng-repeat uses comment elements to delineate
116+ // the start and stop of repeat sections and sortable doesn't
117+ // respect their order (even if we cancel, the order of the
118+ // comments are still messed up).
119+ if ( element . sortable ( 'option' , 'helper' ) === 'clone' ) {
120+ // restore all the savedNodes except .ui-sortable-helper element
121+ // (which is placed last). That way it will be garbage collected.
122+ savedNodes = savedNodes . not ( savedNodes . last ( ) ) ;
123+ }
124+ savedNodes . appendTo ( element ) ;
125+
126+ // If received is true (an item was dropped in from another list)
127+ // then we add the new item to this list otherwise wait until the
128+ // stop event where we will know if it was a sort or item was
129+ // moved here from another list
130+ if ( ui . item . sortable . received && ! ui . item . sortable . isCanceled ( ) ) {
131+ scope . $apply ( function ( ) {
132+ ngModel . $modelValue . splice ( ui . item . sortable . dropindex , 0 ,
133+ ui . item . sortable . moved ) ;
134+ } ) ;
135+ }
136+ } ;
137+
138+ callbacks . stop = function ( e , ui ) {
139+ // If the received flag hasn't be set on the item, this is a
140+ // normal sort, if dropindex is set, the item was moved, so move
141+ // the items in the list.
142+ if ( ! ui . item . sortable . received &&
143+ ( 'dropindex' in ui . item . sortable ) &&
144+ ! ui . item . sortable . isCanceled ( ) ) {
145+
146+ scope . $apply ( function ( ) {
147+ ngModel . $modelValue . splice (
148+ ui . item . sortable . dropindex , 0 ,
149+ ngModel . $modelValue . splice ( ui . item . sortable . index , 1 ) [ 0 ] ) ;
150+ } ) ;
151+ } else {
152+ // if the item was not moved, then restore the elements
153+ // so that the ngRepeat's comment are correct.
154+ if ( ( ! ( 'dropindex' in ui . item . sortable ) || ui . item . sortable . isCanceled ( ) ) && element . sortable ( 'option' , 'helper' ) !== 'clone' ) {
155+ savedNodes . appendTo ( element ) ;
156156 }
157- // wrap the callback
158- value = combineCallbacks ( callbacks [ key ] , value ) ;
159157 }
160- element . sortable ( 'option' , key , value ) ;
158+ } ;
159+
160+ callbacks . receive = function ( e , ui ) {
161+ // An item was dropped here from another list, set a flag on the
162+ // item.
163+ ui . item . sortable . received = true ;
164+ } ;
165+
166+ callbacks . remove = function ( e , ui ) {
167+ // Remove the item from this list's model and copy data into item,
168+ // so the next list can retrive it
169+ if ( ! ui . item . sortable . isCanceled ( ) ) {
170+ scope . $apply ( function ( ) {
171+ ui . item . sortable . moved = ngModel . $modelValue . splice (
172+ ui . item . sortable . index , 1 ) [ 0 ] ;
173+ } ) ;
174+ }
175+ } ;
176+
177+ scope . $watch ( attrs . uiSortable , function ( newVal /*, oldVal*/ ) {
178+ angular . forEach ( newVal , function ( value , key ) {
179+ if ( callbacks [ key ] ) {
180+ if ( key === 'stop' ) {
181+ // call apply after stop
182+ value = combineCallbacks (
183+ value , function ( ) { scope . $apply ( ) ; } ) ;
184+ }
185+ // wrap the callback
186+ value = combineCallbacks ( callbacks [ key ] , value ) ;
187+ }
188+ element . sortable ( 'option' , key , value ) ;
189+ } ) ;
190+ } , true ) ;
191+
192+ angular . forEach ( callbacks , function ( value , key ) {
193+ opts [ key ] = combineCallbacks ( value , opts [ key ] ) ;
161194 } ) ;
162- } , true ) ;
163- angular . forEach ( callbacks , function ( value , key ) {
164- opts [ key ] = combineCallbacks ( value , opts [ key ] ) ;
165- } ) ;
166- } else {
167- $log . info ( 'ui.sortable: ngModel not provided!' , element ) ;
195+
196+ } else {
197+ $log . info ( 'ui.sortable: ngModel not provided!' , element ) ;
198+ }
199+
200+ // Create sortable
201+ element . sortable ( opts ) ;
168202 }
169- // Create sortable
170- element . sortable ( opts ) ;
171- }
172- } ;
173- }
174- ] ) ;
203+ } ;
204+ }
205+ ] ) ;
206+
207+ } ) ( window , window . angular ) ;
0 commit comments