2
2
jQuery UI Sortable plugin wrapper
3
3
4
4
@param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
5
- */
5
+ */
6
6
angular . module ( 'ui.sortable' , [ ] )
7
- . value ( 'uiSortableConfig' , { } )
8
- . directive ( 'uiSortable' , [ 'uiSortableConfig' , '$log' ,
9
- function ( uiSortableConfig , log ) {
7
+ . value ( 'uiSortableConfig' , { } )
8
+ . directive ( 'uiSortable' , [
9
+ 'uiSortableConfig' , '$timeout' , '$log' ,
10
+ function ( uiSortableConfig , $timeout , $log ) {
10
11
return {
11
12
require : '?ngModel' ,
12
13
link : function ( scope , element , attrs , ngModel ) {
14
+ var savedNodes ;
13
15
14
- function combineCallbacks ( first , second ) {
15
- if ( second && ( typeof second === "function" ) ) {
16
+ function combineCallbacks ( first , second ) {
17
+ if ( second && ( typeof second === "function" ) ) {
16
18
return function ( e , ui ) {
17
19
first ( e , ui ) ;
18
20
second ( e , ui ) ;
@@ -25,100 +27,140 @@ angular.module('ui.sortable', [])
25
27
26
28
var callbacks = {
27
29
receive : null ,
28
- remove : null ,
29
- start : null ,
30
- stop : null ,
31
- update : null
32
- } ;
33
-
34
- var apply = function ( e , ui ) {
35
- if ( ui . item . sortable . resort || ui . item . sortable . relocate ) {
36
- scope . $apply ( ) ;
37
- }
30
+ remove :null ,
31
+ start :null ,
32
+ stop :null ,
33
+ update :null
38
34
} ;
39
35
40
36
angular . extend ( opts , uiSortableConfig ) ;
41
37
42
38
if ( ngModel ) {
43
39
44
- ngModel . $render = function ( ) {
45
- element . sortable ( "refresh" ) ;
46
- } ;
40
+ // When we add or remove elements, we need the sortable to 'refresh'
41
+ // so it can find the new/removed elements.
42
+ scope . $watch ( attrs . ngModel + '.length' , function ( ) {
43
+ // Timeout to let ng-repeat modify the DOM
44
+ $timeout ( function ( ) {
45
+ element . sortable ( "refresh" ) ;
46
+ } ) ;
47
+ } ) ;
47
48
48
49
callbacks . start = function ( e , ui ) {
49
- // Save position of dragged item
50
- ui . item . sortable = {
51
- index : ui . item . index ( )
52
- } ;
50
+ // Save the starting position of dragged item
51
+ ui . item . sortable = { index : ui . item . index ( ) } ;
53
52
} ;
54
53
55
- callbacks . update = function ( e , ui ) {
56
- // For some reason the reference to ngModel in stop() is wrong
57
- ui . item . sortable . resort = ngModel ;
54
+ callbacks . activate = function ( e , ui ) {
55
+ // We need to make a copy of the current element's contents so
56
+ // we can restore it after sortable has messed it up.
57
+ // This is inside activate (instead of start) in order to save
58
+ // both lists when dragging between connected lists.
59
+ savedNodes = element . contents ( ) ;
60
+
61
+ // If this list has a placeholder (the connected lists won't),
62
+ // don't inlcude it in saved nodes.
63
+ var placeholder = element . sortable ( 'option' , 'placeholder' ) ;
64
+
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 ) {
71
+ savedNodes = savedNodes . not ( element . find (
72
+ "." + placeholder . element ( )
73
+ . attr ( 'class' ) . split ( / \s + / ) . join ( '.' ) ) ) ;
74
+ }
58
75
} ;
59
76
60
- callbacks . receive = function ( e , ui ) {
61
- ui . item . sortable . relocate = true ;
62
- // added item to array into correct position and set up flag
63
- ngModel . $modelValue . splice ( ui . item . index ( ) , 0 , ui . item . sortable . moved ) ;
64
- } ;
77
+ callbacks . update = function ( e , ui ) {
78
+ // Save current drop position but only if this is not a second
79
+ // update that happens when moving between lists because then
80
+ // the value will be overwritten with the old value
81
+ if ( ! ui . item . sortable . received ) {
82
+ ui . item . sortable . dropindex = ui . item . index ( ) ;
83
+
84
+ // Cancel the sort (let ng-repeat do the sort for us)
85
+ // Don't cancel if this is the received list because it has
86
+ // already been canceled in the other list, and trying to cancel
87
+ // here will mess up the DOM.
88
+ element . sortable ( 'cancel' ) ;
89
+ }
65
90
66
- callbacks . remove = function ( e , ui ) {
67
- // copy data into item
68
- if ( ngModel . $modelValue . length === 1 ) {
69
- ui . item . sortable . moved = ngModel . $modelValue . splice ( 0 , 1 ) [ 0 ] ;
70
- } else {
71
- ui . item . sortable . moved = ngModel . $modelValue . splice ( ui . item . sortable . index , 1 ) [ 0 ] ;
91
+ // Put the nodes back exactly the way they started (this is very
92
+ // important because ng-repeat uses comment elements to delineate
93
+ // the start and stop of repeat sections and sortable doesn't
94
+ // respect their order (even if we cancel, the order of the
95
+ // comments are still messed up).
96
+ savedNodes . detach ( ) . appendTo ( element ) ;
97
+
98
+ // If received is true (an item was dropped in from another list)
99
+ // then we add the new item to this list otherwise wait until the
100
+ // stop event where we will know if it was a sort or item was
101
+ // moved here from another list
102
+ if ( ui . item . sortable . received ) {
103
+ scope . $apply ( function ( ) {
104
+ ngModel . $modelValue . splice ( ui . item . sortable . dropindex , 0 ,
105
+ ui . item . sortable . moved ) ;
106
+ } ) ;
72
107
}
73
108
} ;
74
109
75
110
callbacks . stop = function ( e , ui ) {
76
- // digest all prepared changes
77
- if ( ui . item . sortable . resort && ! ui . item . sortable . relocate ) {
78
-
79
- // Fetch saved and current position of dropped element
80
- var end , start ;
81
- start = ui . item . sortable . index ;
82
- end = ui . item . index ( ) ;
111
+ // If the received flag hasn't be set on the item, this is a
112
+ // normal sort, if dropindex is set, the item was moved, so move
113
+ // the items in the list.
114
+ if ( ! ui . item . sortable . received && ( 'dropindex' in ui . item . sortable ) ) {
115
+ scope . $apply ( function ( ) {
116
+ ngModel . $modelValue . splice (
117
+ ui . item . sortable . dropindex , 0 ,
118
+ ngModel . $modelValue . splice ( ui . item . sortable . index , 1 ) [ 0 ] ) ;
119
+ } ) ;
120
+ }
121
+ } ;
83
122
84
- // Reorder array and apply change to scope
85
- ui . item . sortable . resort . $modelValue . splice ( end , 0 , ui . item . sortable . resort . $modelValue . splice ( start , 1 ) [ 0 ] ) ;
123
+ callbacks . receive = function ( e , ui ) {
124
+ // An item was dropped here from another list, set a flag on the
125
+ // item.
126
+ ui . item . sortable . received = true ;
127
+ } ;
86
128
87
- }
129
+ callbacks . remove = function ( e , ui ) {
130
+ // Remove the item from this list's model and copy data into item,
131
+ // so the next list can retrive it
132
+ scope . $apply ( function ( ) {
133
+ ui . item . sortable . moved = ngModel . $modelValue . splice (
134
+ ui . item . sortable . index , 1 ) [ 0 ] ;
135
+ } ) ;
88
136
} ;
89
137
90
138
scope . $watch ( attrs . uiSortable , function ( newVal , oldVal ) {
91
139
angular . forEach ( newVal , function ( value , key ) {
92
-
93
- if ( callbacks [ key ] ) {
94
- // wrap the callback
95
- value = combineCallbacks ( callbacks [ key ] , value ) ;
96
-
97
- if ( key === 'stop' ) {
140
+ if ( callbacks [ key ] ) {
141
+ if ( key === 'stop' ) {
98
142
// call apply after stop
99
- value = combineCallbacks ( value , apply ) ;
143
+ value = combineCallbacks (
144
+ value , function ( ) { scope . $apply ( ) ; } ) ;
100
145
}
146
+ // wrap the callback
147
+ value = combineCallbacks ( callbacks [ key ] , value ) ;
101
148
}
102
-
103
149
element . sortable ( 'option' , key , value ) ;
104
150
} ) ;
105
151
} , true ) ;
106
152
107
153
angular . forEach ( callbacks , function ( value , key ) {
108
-
109
154
opts [ key ] = combineCallbacks ( value , opts [ key ] ) ;
110
155
} ) ;
111
156
112
- // call apply after stop
113
- opts . stop = combineCallbacks ( opts . stop , apply ) ;
114
-
115
157
} else {
116
- log . info ( 'ui.sortable: ngModel not provided!' , element ) ;
158
+ $ log. info ( 'ui.sortable: ngModel not provided!' , element ) ;
117
159
}
118
160
119
161
// Create sortable
120
162
element . sortable ( opts ) ;
121
163
}
122
164
} ;
123
165
}
124
- ] ) ;
166
+ ] ) ;
0 commit comments