├── .gitignore
├── .jshintrc
├── LICENSE
├── README.md
├── bower.json
├── demo
├── css
│ └── demo.css
├── demo-connected.html
├── demo-handle.html
├── demo.html
└── js
│ ├── demo-connected.js
│ ├── demo-handle.js
│ └── demo.js
├── package.json
└── src
└── ui.sortable.multiselection.js
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | node_modules/
3 | coverage/
4 | dist/
5 | out/
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "boss": true,
3 | "browser": true,
4 | "curly": true,
5 | "eqnull": true,
6 | "expr": true,
7 | "immed": true,
8 | "indent": 2,
9 | "laxbreak": true,
10 | "loopfunc": true,
11 | "newcap": true,
12 | "noarg": true,
13 | "noempty": true,
14 | "nonew": true,
15 | "quotmark": true,
16 | "smarttabs": true,
17 | "sub": true,
18 | "trailing": true,
19 | "undef": true,
20 | "unused": true,
21 | "globals": {
22 | "angular": false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Thodoris Greasidis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ui-sortable-multiselection
2 | ==========================
3 | [](https://www.npmjs.com/package/angular-ui-sortable-multiselection)
4 | [](https://docs.angularjs.org/guide/production#disabling-debug-data)
5 |
6 | Provide multiple element sorting in [UI-Sortable](https://github.com/angular-ui/ui-sortable)
7 |
8 | [Simple Demo pen with selection count](http://codepen.io/vanatallin/pen/zqXzmJ)
9 |
10 | [Simple Demo pen](http://codepen.io/thgreasi/pen/mJAcL)
11 |
12 | [Connected Lists Demo pen](http://codepen.io/thgreasi/pen/FJrxo)
13 |
14 | [Handle Demo pen](http://codepen.io/feus4177/pen/zvWvpY)
15 |
16 | [MultiSelect on Click Demo pen](http://codepen.io/thgreasi/pen/KgoPmY)
17 |
18 | ## ui.item.sortableMultiSelect API documentation
19 |
20 | ### Allow multi-selection on click
21 | For better touch device support, use the option 'multiSelectOnClick'. This will allow click/tap to select and deselect individual items instead of requiring a modifier key to be held down.
22 |
23 | Example usage:
24 |
25 | ```javascript
26 | // set the sortable options
27 | $scope.sortableOptions = uiSortableMultiSelectionMethods.extendOptions({
28 | 'multiSelectOnClick': true,
29 | start: function() {
30 | // ...
31 | },
32 | stop: function() {
33 | // ...
34 | }
35 | });
36 | ```
37 |
38 | ```html
39 |
40 | -
41 |
{item.name}
42 |
43 |
44 | ```
45 |
46 | The `ui` argument of the available callbacks gets enriched with some extra properties as specified below:
47 |
48 |
49 | ### selectedIndexes
50 | Type: [Array](http://api.jquery.com/Types/#Array)<[Integer](http://api.jquery.com/Types/#Integer)>
51 | Holds the original indexes of the items dragged.
52 |
53 | ### selectedModels
54 | Type: [Array](http://api.jquery.com/Types/#Array)<[Object](http://api.jquery.com/Types/#Object)> /`undefined`
55 | Holds the JavaScript objects that is used as the model of the dragged items, as specified by the ng-repeat of the [`source`](#source) ui-sortable element and the [`ui.item.sortableMultiSelect.selectedIndexes`](#selectedindexes) property.
56 |
57 |
58 |
59 | ### indexes
60 | Type: [Array](http://api.jquery.com/Types/#Array)<[Integer](http://api.jquery.com/Types/#Integer)>
61 | Holds the original indexes of the sibling items dragged.
62 |
63 | ### models
64 | Type: [Array](http://api.jquery.com/Types/#Array)<[Object](http://api.jquery.com/Types/#Object)>
65 | Holds the JavaScript objects that is used as the model of the siblings of the dragged item, as specified by the ng-repeat of the [`source`](#source) ui-sortable element and the [`ui.item.sortableMultiSelect.indexes`](#indexes) property.
66 |
67 | ### moved
68 | Type: [Object](http://api.jquery.com/Types/#Object) /`undefined`
69 | Holds the models of the dragged sibling items only when a sorting happens between two connected ui-sortable elements.
70 |
71 | ### sourceElement
72 | Type: [jQuery](http://api.jquery.com/Types/#jQuery)
73 | Holds the ui-sortable element that the dragged item originated from.
74 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-sortable-multiselection",
3 | "version": "0.7.0",
4 | "description": "This directive allows multiple element sorting in UI-Sortable.",
5 | "author": "https://github.com/thgreasi/ui-sortable-multiselection/graphs/contributors",
6 | "license": "MIT",
7 | "homepage": "https://github.com/thgreasi/ui-sortable-multiselection",
8 | "main": "./src/ui.sortable.multiselection.js",
9 | "ignore": [
10 | "**/.*",
11 | "node_modules",
12 | "bower_components",
13 | "test*",
14 | "demo*",
15 | "gruntFile.js",
16 | "package.json"
17 | ],
18 | "dependencies": {
19 | "angular-ui-sortable": ">=0.13.x"
20 | },
21 | "devDependencies": {}
22 | }
23 |
--------------------------------------------------------------------------------
/demo/css/demo.css:
--------------------------------------------------------------------------------
1 | .list {
2 | list-style: none outside none;
3 | margin: 10px 0 30px;
4 | }
5 |
6 | .items-container {
7 | border: 2px dashed blue;
8 | margin: 10px 10px 0 0;
9 | padding: 5px;
10 | }
11 |
12 | .item {
13 | width: 170px;
14 | padding: 5px 10px;
15 | margin: 5px 0;
16 | border: 2px solid #444;
17 | border-radius: 5px;
18 | background-color: #EA8A8A;
19 | font-size: 1.1em;
20 | font-weight: bold;
21 | text-align: center;
22 | }
23 |
24 | td.item {
25 | width: initial;
26 | border-radius: 0;
27 | }
28 |
29 | .cursor-grab {
30 | cursor: grab;
31 | cursor: -moz-grab;
32 | cursor: -webkit-grab;
33 | }
34 |
35 | .cursor-grab:active {
36 | cursor: grabbing;
37 | cursor: -moz-grabbing;
38 | cursor: -webkit-grabbing;
39 | }
40 |
41 | .ui-sortable-selected,
42 | .ui-sortable-selected > td {
43 | background-color: yellow;
44 | }
45 | /*** Extra ***/
46 |
47 | body {
48 | font-family: Verdana, 'Trebuchet ms', Tahoma;
49 | }
50 |
51 | .logList {
52 | margin-top: 20px;
53 | width: 250px;
54 | min-height: 300px;
55 | padding: 5px 15px;
56 | border: 5px solid #000;
57 | border-radius: 15px;
58 | }
59 |
60 | .logItem {
61 | margin-bottom: 10px;
62 | }
63 |
64 | .logList:before {
65 | content: 'log';
66 | padding: 0 5px;
67 | position: relative;
68 | top: -1.1em;
69 | background-color: #FFF;
70 | }
71 |
72 | .container {
73 | width: 750px;
74 | margin: auto;
75 | }
76 |
77 | h2 {
78 | text-align: center;
79 | }
80 |
81 | .floatleft {
82 | float: left;
83 | }
84 |
85 | .floatright {
86 | float: right;
87 | }
88 |
89 | .clear {
90 | clear: both;
91 | }
--------------------------------------------------------------------------------
/demo/demo-connected.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ui-sortable-multiselection connected lists
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
ui-sortable-multiselection
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 | {{item.text}}
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{item.text}}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | -
47 | {{entry}}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/demo/demo-handle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ui-sortable-multiselection
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
ui-sortable-multiselection
19 |
20 |
21 |
22 |
23 | |
24 | {{item.text}} |
25 |
26 |
27 |
28 |
29 |
30 |
31 | -
32 | {{entry}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ui-sortable-multiselection
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
ui-sortable-multiselection
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 | {{item.text}}
30 |
31 |
32 |
33 |
34 |
35 |
36 | -
37 | {{entry}}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/demo/js/demo-connected.js:
--------------------------------------------------------------------------------
1 | var myapp = angular.module('sortableApp', ['ui.sortable', 'ui.sortable.multiselection'], function ($compileProvider) {
2 | if (typeof $compileProvider.debugInfoEnabled === 'function') {
3 | $compileProvider.debugInfoEnabled(false);
4 | }
5 | });
6 |
7 |
8 | myapp.controller('sortableController', function ($scope, uiSortableMultiSelectionMethods) {
9 | function makeList (letter) {
10 | var tmpList = [];
11 |
12 | for (var i = 1; i <= 6; i++){
13 | tmpList.push({
14 | text: 'Item ' + i + letter,
15 | value: i + letter
16 | });
17 | }
18 | return tmpList;
19 | }
20 |
21 | $scope.list1 = makeList('a');
22 | $scope.list2 = makeList('b');
23 |
24 | $scope.rawScreens = [
25 | $scope.list1,
26 | $scope.list2
27 | ];
28 |
29 |
30 | $scope.sortingLog = [];
31 |
32 | $scope.sortableOptions = uiSortableMultiSelectionMethods.extendOptions({
33 | update: function(e, ui) {
34 | if ($scope.lockNodesWithTwo) {
35 | // if it's not declared as received yet
36 | // aka if we are in the update callback
37 | // of the source list
38 | if (!ui.item.sortable.received) {
39 | var movesTwo = ui.item.scope().$eval(ui.item.parent().attr('ng-model'))
40 | .filter(function(x,i) { return ui.item.sortableMultiSelect.indexes.concat(ui.item.sortable.index).indexOf(i) >= 0; })
41 | .filter(function(x) { return x.text.indexOf('2') >= 0; })
42 | .length;
43 | if (movesTwo) {
44 | ui.item.sortable.cancel();
45 | }
46 | }
47 | }
48 | },
49 | connectWith: '.items-container'
50 | });
51 |
52 | $scope.logModels = function () {
53 | $scope.sortingLog = [];
54 | for (var i = 0; i < $scope.rawScreens.length; i++) {
55 | var logEntry = $scope.rawScreens[i].map(function (x) {
56 | return x.text;
57 | }).join(', ');
58 | logEntry = 'container ' + (i+1) + ': ' + logEntry;
59 | $scope.sortingLog.push(logEntry);
60 | }
61 | };
62 | });
63 |
64 | myapp.controller('dummyController', function ($scope) {
65 | // this is required since ui-sortable v0.12.x is not creating a new scope
66 | });
--------------------------------------------------------------------------------
/demo/js/demo-handle.js:
--------------------------------------------------------------------------------
1 | var myapp = angular.module('sortableApp', ['ui.sortable', 'ui.sortable.multiselection'], function ($compileProvider) {
2 | if (typeof $compileProvider.debugInfoEnabled === 'function') {
3 | $compileProvider.debugInfoEnabled(false);
4 | }
5 | });
6 |
7 |
8 | myapp.controller('sortableController', function ($scope, uiSortableMultiSelectionMethods) {
9 | var tmpList = [];
10 |
11 | for (var i = 1; i <= 6; i++){
12 | tmpList.push({
13 | text: 'Row ' + i,
14 | value: i
15 | });
16 | }
17 |
18 | $scope.list = tmpList;
19 |
20 | $scope.sortingLog = [];
21 |
22 | $scope.sortableOptions = uiSortableMultiSelectionMethods.extendOptions({
23 | handle: '.cursor-grab',
24 | stop: function(e, ui) {
25 | // this callback has the changed model
26 | var logEntry = tmpList.map(function(i){
27 | return i.value;
28 | }).join(', ');
29 | $scope.sortingLog.push('Stop: ' + logEntry);
30 | }
31 | });
32 | });
--------------------------------------------------------------------------------
/demo/js/demo.js:
--------------------------------------------------------------------------------
1 | var myapp = angular.module('sortableApp', ['ui.sortable', 'ui.sortable.multiselection'], function ($compileProvider) {
2 | if (typeof $compileProvider.debugInfoEnabled === 'function') {
3 | $compileProvider.debugInfoEnabled(false);
4 | }
5 | });
6 |
7 | myapp.controller('sortableController', function ($scope, uiSortableMultiSelectionMethods) {
8 | var tmpList = [];
9 |
10 | for (var i = 1; i <= 6; i++){
11 | tmpList.push({
12 | text: 'Item ' + i,
13 | value: i
14 | });
15 | }
16 |
17 | $scope.list = tmpList;
18 |
19 | $scope.sortingLog = [];
20 |
21 | $scope.sortableOptions = {
22 | helper: uiSortableMultiSelectionMethods.helper,
23 | update: function(e, ui) {
24 | if ($scope.lockNodesWithTwo) {
25 | var movesTwo = ui.item.scope().list
26 | .filter(function(x,i) { return ui.item.sortableMultiSelect.indexes.concat(ui.item.sortable.index).indexOf(i) >= 0; })
27 | .filter(function(x) { return x.text.indexOf('2') >= 0; })
28 | .length;
29 | if (movesTwo) {
30 | ui.item.sortable.cancel();
31 | }
32 | }
33 | },
34 | stop: function(e, ui) {
35 | uiSortableMultiSelectionMethods.stop(e, ui);
36 |
37 | // this callback has the changed model
38 | var logEntry = tmpList.map(function(i){
39 | return i.value;
40 | }).join(', ');
41 | $scope.sortingLog.push('Stop: ' + logEntry);
42 | }
43 | };
44 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-sortable-multiselection",
3 | "version": "0.7.0",
4 | "description": "This directive allows multiple element sorting in UI-Sortable.",
5 | "author": "https://github.com/thgreasi/ui-sortable-multiselection/graphs/contributors",
6 | "license": "MIT",
7 | "homepage": "https://github.com/thgreasi/ui-sortable-multiselection",
8 | "main": "./src/ui.sortable.multiselection.js",
9 | "dependencies": {},
10 | "devDependencies": {},
11 | "scripts": {},
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/thgreasi/ui-sortable-multiselection.git"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/ui.sortable.multiselection.js:
--------------------------------------------------------------------------------
1 | angular.module('ui.sortable.multiselection', [])
2 | .constant('uiSortableMultiSelectionClass', 'ui-sortable-selected')
3 | .directive('uiSortableSelectable', [
4 | 'uiSortableMultiSelectionClass',
5 | function(selectedItemClass) {
6 | return {
7 | link: function(scope, element/*, attrs*/) {
8 | element.on('click', function (e) {
9 | var $this = angular.element(this);
10 |
11 | var $parent = $this.parent();
12 |
13 | var multiSelectOnClick = $parent.sortable('option', 'multiSelectOnClick') || false;
14 |
15 | var isDisabled = $parent.sortable('option', 'disabled');
16 |
17 | if (isDisabled){
18 | return;
19 | }
20 | var jquerySortableCancelOption = $parent.sortable('option', 'cancel');
21 | var jquerySortableHandleOption = $parent.sortable('option', 'handle');
22 |
23 | // Respect jQuery UI Sortable's cancel property by testing if element matches selector
24 | if (jquerySortableCancelOption !== undefined && $this.is(jquerySortableCancelOption)) {
25 | return;
26 | }
27 |
28 | // Mimic jQuery UI Sortable's handle property when determining if an item is selected
29 | if (jquerySortableHandleOption) {
30 | var validHandle = false;
31 |
32 | $parent.find(jquerySortableHandleOption).find('*').addBack().each(function () {
33 | if (this === e.target) {
34 | validHandle = true;
35 | }
36 | });
37 |
38 | if (!validHandle) {
39 | return;
40 | }
41 | }
42 |
43 | var sortableMultiSelectState = $parent.data('uiSortableMultiSelectionState') || {};
44 |
45 | var lastIndex = sortableMultiSelectState.lastIndex;
46 | var index = $this.index();
47 |
48 | if (e.ctrlKey || e.metaKey || multiSelectOnClick) {
49 | $this.toggleClass(selectedItemClass);
50 | } else if (e.shiftKey && lastIndex !== undefined && lastIndex >= 0) {
51 | if (index > lastIndex) {
52 | $parent.children().slice(lastIndex, index + 1).addClass(selectedItemClass);
53 | } else if(index < lastIndex) {
54 | $parent.children().slice(index, lastIndex).addClass(selectedItemClass);
55 | }
56 | } else {
57 | $parent.children('.'+selectedItemClass).not($this).removeClass(selectedItemClass);
58 | $this.toggleClass(selectedItemClass);
59 | }
60 | sortableMultiSelectState.lastIndex = index;
61 | $parent.data('uiSortableMultiSelectionState', sortableMultiSelectState);
62 |
63 | $parent.trigger('ui-sortable-selectionschanged');
64 | });
65 |
66 | element.parent().on('$destroy', function() {
67 | element.parent().removeData('uiSortableMultiSelectionState');
68 | });
69 | }
70 | };
71 | }
72 | ])
73 | .factory('uiSortableMultiSelectionMethods', [
74 | 'uiSortableMultiSelectionClass',
75 | function (selectedItemClass) {
76 | var uiSelectionCount;
77 |
78 | function fixIndex (oldPosition, newPosition, x) {
79 | if (oldPosition < x && (newPosition === undefined || (oldPosition < newPosition && x <= newPosition))) {
80 | return x - 1;
81 | } else if (x < oldPosition && newPosition !== undefined && newPosition < oldPosition && newPosition <= x) {
82 | return x + 1;
83 | }
84 | return x;
85 | }
86 |
87 | function groupIndexes (indexes, oldPosition, newPosition) {
88 | var above = [],
89 | below = [];
90 |
91 | for (var i = 0; i < indexes.length; i++) {
92 | var x = indexes[i];
93 | if (x < oldPosition) {
94 | above.push(fixIndex(oldPosition, newPosition, x));
95 | } else if (oldPosition < x) {
96 | below.push(fixIndex(oldPosition, newPosition, x));
97 | }
98 | }
99 |
100 | return {
101 | above: above,
102 | below: below
103 | };
104 | }
105 |
106 | function extractModelsFromIndexes (ngModel, indexes) {
107 | var result = [];
108 | for (var i = indexes.length - 1; i >= 0; i--) {
109 | result.push(ngModel.splice(indexes[i], 1)[0]);
110 | }
111 | result.reverse();
112 | return result;
113 | }
114 |
115 | function extractGroupedModelsFromIndexes (ngModel, aboveIndexes, belowIndexes) {
116 | var models = {
117 | below: extractModelsFromIndexes(ngModel, belowIndexes),
118 | above: extractModelsFromIndexes(ngModel, aboveIndexes)
119 | };
120 | return models;
121 | }
122 |
123 | function combineCallbacks(first,second){
124 | if(second && (typeof second === 'function')) {
125 | return function() {
126 | first.apply(this, arguments);
127 | second.apply(this, arguments);
128 | };
129 | }
130 | return first;
131 | }
132 |
133 | return {
134 | extendOptions: function (sortableOptions) {
135 | sortableOptions = sortableOptions || {};
136 | var result = angular.extend({}, this, sortableOptions);
137 |
138 | for (var prop in sortableOptions) {
139 | if (sortableOptions.hasOwnProperty(prop)) {
140 | if (this[prop]) {
141 | if (prop === 'helper') {
142 | result.helper = this.helper;
143 | } else {
144 | result[prop] = combineCallbacks(this[prop], sortableOptions[prop]);
145 | }
146 | }
147 | }
148 | }
149 | uiSelectionCount = result['ui-selection-count'];
150 | return result;
151 | },
152 | helper: function (e, item) {
153 | // when starting to sort an unhighlighted item ,
154 | // deselect any existing highlighted items
155 | if (!item.hasClass(selectedItemClass)) {
156 | item.addClass(selectedItemClass)
157 | .siblings()
158 | .removeClass(selectedItemClass);
159 | }
160 |
161 | var selectedElements = item.parent().children('.' + selectedItemClass);
162 | var selectedSiblings = item.siblings('.' + selectedItemClass);
163 |
164 | var selectedIndexes = angular.element.map(selectedElements, function (element) {
165 | return angular.element(element).index();
166 | });
167 |
168 | // indexes of the selected siblings
169 | var indexes = angular.element.map(selectedSiblings, function (element) {
170 | return angular.element(element).index();
171 | });
172 |
173 | item.sortableMultiSelect = {
174 | indexes: indexes,
175 | selectedIndexes: selectedIndexes
176 | };
177 |
178 | //Calculate the selectionCount number and initialize the selectionCount attributes if dragging multiple elements
179 | var selectionCount = selectedIndexes ? selectedIndexes.length : 0;
180 |
181 | // Clone the selected items and to put them inside the helper
182 | var elements = selectedElements.clone();
183 |
184 | // like `helper: 'clone'` does, hide the dragged elements
185 | selectedSiblings.hide();
186 |
187 | // Create the helper to act as a bucket for the cloned elements
188 | var helperTag = item[0].tagName;
189 | var helper = angular.element('<' + helperTag + '/>');
190 | if (uiSelectionCount && selectionCount > 1) {
191 | helper.addClass('ui-selection-count').attr('data-ui-selection-count', selectionCount);
192 | }
193 | return helper.append(elements);
194 | },
195 | start: function(e, ui) {
196 | ui.item.sortableMultiSelect.sourceElement = ui.item.parent();
197 |
198 | var sourceModel = ui.item.sortable.sourceModel;
199 | var indexes = ui.item.sortableMultiSelect.indexes;
200 | var selectedIndexes = ui.item.sortableMultiSelect.selectedIndexes;
201 | var models = [];
202 | var selectedModels = [];
203 | for (var i = 0, len = selectedIndexes.length; i < len; i++) {
204 | var index = selectedIndexes[i];
205 | selectedModels.push(sourceModel[index]);
206 | if (indexes.indexOf(index) >= 0) {
207 | models.push(sourceModel[index]);
208 | }
209 | }
210 | ui.item.sortableMultiSelect.models = models;
211 | ui.item.sortableMultiSelect.selectedModels = selectedModels;
212 | },
213 | update: function(e, ui) {
214 | if (ui.item.sortable.received) {
215 | if (!ui.item.sortable.isCanceled()) {
216 | var ngModel = ui.item.sortable.droptargetModel,
217 | newPosition = ui.item.sortable.dropindex,
218 | models = ui.item.sortableMultiSelect.moved;
219 |
220 | // add the models to the target list
221 | Array.prototype.splice.apply(
222 | ngModel,
223 | [newPosition+ 1, 0]
224 | .concat(models.below));
225 |
226 | Array.prototype.splice.apply(
227 | ngModel,
228 | [newPosition, 0]
229 | .concat(models.above));
230 | } else {
231 | ui.item.sortableMultiSelect.sourceElement.find('> .' + selectedItemClass).show();
232 | }
233 | }
234 | },
235 | remove: function(e, ui) {
236 | if (!ui.item.sortable.isCanceled()) {
237 | var ngModel = ui.item.sortable.sourceModel,
238 | oldPosition = ui.item.sortable.index;
239 |
240 | var indexes = groupIndexes(ui.item.sortableMultiSelect.indexes, oldPosition);
241 |
242 | // get the models and remove them from the original list
243 | // the code should run in reverse order,
244 | // so that the indexes will not break
245 | ui.item.sortableMultiSelect.moved = extractGroupedModelsFromIndexes(ngModel, indexes.above, indexes.below);
246 | } else {
247 | ui.item.sortableMultiSelect.sourceElement.find('> .' + selectedItemClass).show();
248 | }
249 | },
250 | stop: function (e, ui) {
251 | var sourceElement = ui.item.sortableMultiSelect.sourceElement || ui.item.parent();
252 | if (!ui.item.sortable.received &&
253 | // ('dropindex' in ui.item.sortable) &&
254 | !ui.item.sortable.isCanceled()) {
255 | var ngModel = ui.item.sortable.sourceModel,
256 | oldPosition = ui.item.sortable.index,
257 | newPosition = ui.item.sortable.dropindex;
258 |
259 | var draggedElementIndexes = ui.item.sortableMultiSelect.indexes;
260 | if (!draggedElementIndexes.length) {
261 | ui.item.removeClass('' + selectedItemClass);
262 | return;
263 | }
264 |
265 | if (newPosition === undefined) {
266 | newPosition = oldPosition;
267 | }
268 |
269 | var indexes = groupIndexes(draggedElementIndexes, oldPosition, newPosition);
270 |
271 | // get the model of the dragged item
272 | // so that we can locate its position
273 | // after we remove the co-dragged elements
274 | var draggedModel = ngModel[newPosition];
275 |
276 | // get the models and remove them from the list
277 | // the code should run in reverse order,
278 | // so that the indexes will not break
279 | var models = extractGroupedModelsFromIndexes(ngModel, indexes.above, indexes.below);
280 |
281 | // add the models to the list
282 | Array.prototype.splice.apply(
283 | ngModel,
284 | [ngModel.indexOf(draggedModel) + 1, 0]
285 | .concat(models.below));
286 |
287 | Array.prototype.splice.apply(
288 | ngModel,
289 | [ngModel.indexOf(draggedModel), 0]
290 | .concat(models.above));
291 |
292 | ui.item.parent().find('> .' + selectedItemClass).removeClass('' + selectedItemClass).show();
293 | } else if (ui.item.sortable.isCanceled()) {
294 | sourceElement.find('> .' + selectedItemClass).show();
295 | }
296 | }
297 | };
298 | }]);
--------------------------------------------------------------------------------