Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Commit 83b7eb1

Browse files
committed
Allow sorting of multiple select
1 parent 4da2db4 commit 83b7eb1

File tree

7 files changed

+194
-9
lines changed

7 files changed

+194
-9
lines changed

examples/demo-multi-select.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ <h3>Array of strings</h3>
121121
</ui-select>
122122
<p>Selected: {{multipleDemo.colors}}</p>
123123
<hr>
124-
<h3>Array of objects</h3>
125-
<ui-select multiple ng-model="multipleDemo.selectedPeople" theme="bootstrap" ng-disabled="disabled" close-on-select="false" style="width: 800px;">
124+
<h3>Array of objects (sorting enabled)</h3>
125+
<ui-select multiple ng-model="multipleDemo.selectedPeople" theme="bootstrap" ng-disabled="disabled" sort-enabled="true" close-on-select="false" style="width: 800px;">
126126
<ui-select-match placeholder="Select person...">{{$item.name}} &lt;{{$item.email}}&gt;</ui-select-match>
127127
<ui-select-choices repeat="person in people | propsFilter: {name: $select.search, age: $select.search}">
128128
<div ng-bind-html="person.name | highlight: $select.search"></div>

examples/demo-tagging.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@
6565
<h1>Tagging Demos</h1>
6666

6767
<h3>Simple String Tags</h3>
68-
<h4>(With Custom Tag Label)</h4>
69-
<ui-select multiple tagging tagging-label="(custom 'new' label)" ng-model="multipleDemo.colors" theme="bootstrap" ng-disabled="disabled" style="width: 300px;" title="Choose a color">
68+
<h4>(With Custom Tag Label / Sort Enabled)</h4>
69+
<ui-select multiple tagging tagging-label="(custom 'new' label)" ng-model="multipleDemo.colors" theme="bootstrap" sort-enabled="true" ng-disabled="disabled" style="width: 300px;" title="Choose a color">
7070
<ui-select-match placeholder="Select colors...">{{$item}}</ui-select-match>
7171
<ui-select-choices repeat="color in availableColors | filter:$select.search">
7272
{{color}}

examples/select2-bootstrap3.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@
6464
</div>
6565
</div>
6666

67+
<div class="form-group">
68+
<label class="col-sm-3 control-label">Multiple</label>
69+
<div class="col-sm-6">
70+
71+
<ui-select multiple sort-enabled="true" ng-model="person.selected" theme="select2" class="form-control" title="Choose a person">
72+
<ui-select-match placeholder="Select or search a person in the list...">{{$select.selected.name}}</ui-select-match>
73+
<ui-select-choices repeat="item in people | filter: $select.search">
74+
<div ng-bind-html="item.name | highlight: $select.search"></div>
75+
<small ng-bind-html="item.email | highlight: $select.search"></small>
76+
</ui-select-choices>
77+
</ui-select>
78+
79+
</div>
80+
</div>
81+
6782
<div class="form-group">
6883
<label class="col-sm-3 control-label">Grouped</label>
6984
<div class="col-sm-6">

src/bootstrap/match-multiple.tpl.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<span class="ui-select-match">
22
<span ng-repeat="$item in $select.selected">
33
<span
4-
style="margin-right: 3px;"
54
class="ui-select-match-item btn btn-default btn-xs"
65
tabindex="-1"
76
type="button"
87
ng-disabled="$select.disabled"
98
ng-click="$select.activeMatchIndex = $index;"
10-
ng-class="{'btn-primary':$select.activeMatchIndex === $index, 'select-locked':$select.isLocked(this, $index)}">
9+
ng-class="{'btn-primary':$select.activeMatchIndex === $index, 'select-locked':$select.isLocked(this, $index)}"
10+
ui-select-sort="$select.selected">
1111
<span class="close ui-select-match-close" ng-hide="$select.disabled" ng-click="$select.removeChoice($index)">&nbsp;&times;</span>
1212
<span uis-transclude-append></span>
1313
</span>

src/select.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,27 +121,54 @@
121121
height: auto;
122122
max-height: 200px;
123123
overflow-x: hidden;
124+
margin-top: -1px;
124125
}
125126

126127
.ui-select-multiple.ui-select-bootstrap {
127128
height: auto;
128-
padding: .3em;
129+
padding: 3px 3px 0 3px;
129130
}
130131

131132
.ui-select-multiple.ui-select-bootstrap input.ui-select-search {
132133
background-color: transparent !important; /* To prevent double background when disabled */
133134
border: none;
134135
outline: none;
135136
height: 1.666666em;
137+
margin-bottom: 3px;
136138
}
137139

138140
.ui-select-multiple.ui-select-bootstrap .ui-select-match .close {
139141
font-size: 1.6em;
140142
line-height: 0.75;
141143
}
142144

143-
.ui-select-multiple.ui-select-bootstrap .ui-select-match-item{
145+
.ui-select-multiple.ui-select-bootstrap .ui-select-match-item {
144146
outline: 0;
147+
margin: 0 3px 3px 0;
148+
}
149+
150+
.ui-select-multiple .ui-select-match-item {
151+
position: relative;
152+
}
153+
154+
.ui-select-multiple .ui-select-match-item.dropping-before:before {
155+
content: "";
156+
position: absolute;
157+
top: 0;
158+
right: 100%;
159+
height: 100%;
160+
margin-right: 2px;
161+
border-left: 1px solid #428bca;
162+
}
163+
164+
.ui-select-multiple .ui-select-match-item.dropping-after:after {
165+
content: "";
166+
position: absolute;
167+
top: 0;
168+
left: 100%;
169+
height: 100%;
170+
margin-left: 2px;
171+
border-right: 1px solid #428bca;
145172
}
146173

147174
.ui-select-bootstrap .ui-select-choices-row>a {

src/select.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
.constant('uiSelectConfig', {
9191
theme: 'bootstrap',
9292
searchEnabled: true,
93+
sortEnabled: false,
9394
placeholder: '', // Empty by default, like HTML tag <select>
9495
refreshDelay: 1000, // In milliseconds
9596
closeOnSelect: true,
@@ -183,6 +184,7 @@
183184
ctrl.focusser = undefined; //Reference to input element used to handle focus events
184185
ctrl.disabled = undefined; // Initialized inside uiSelect directive link function
185186
ctrl.searchEnabled = undefined; // Initialized inside uiSelect directive link function
187+
ctrl.sortEnabled = undefined; // Initialized inside uiSelect directive link function
186188
ctrl.resetSearchInput = undefined; // Initialized inside uiSelect directive link function
187189
ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
188190
ctrl.multiple = false; // Initialized inside uiSelect directive link function
@@ -1078,6 +1080,11 @@
10781080
$select.searchEnabled = searchEnabled !== undefined ? searchEnabled : uiSelectConfig.searchEnabled;
10791081
});
10801082

1083+
scope.$watch('sortEnabled', function() {
1084+
var sortEnabled = scope.$eval(attrs.sortEnabled);
1085+
$select.sortEnabled = sortEnabled !== undefined ? sortEnabled : uiSelectConfig.sortEnabled;
1086+
});
1087+
10811088
attrs.$observe('disabled', function() {
10821089
// No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
10831090
$select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
@@ -1317,6 +1324,141 @@
13171324
};
13181325
}])
13191326

1327+
// Make multiple matches sortable
1328+
.directive('uiSelectSort', ['$timeout', '$log', 'uiSelectConfig', 'uiSelectMinErr', function($timeout, $log, uiSelectConfig, uiSelectMinErr) {
1329+
return {
1330+
require: '^uiSelect',
1331+
link: function(scope, element, attrs, $select) {
1332+
if (scope[attrs.uiSelectSort] === null) {
1333+
throw uiSelectMinErr('sort', "Expected a list to sort");
1334+
}
1335+
1336+
var options = angular.extend({
1337+
axis: 'horizontal'
1338+
},
1339+
scope.$eval(attrs.uiSelectSortOptions));
1340+
1341+
var axis = options.axis,
1342+
draggingClassName = 'dragging',
1343+
droppingClassName = 'dropping',
1344+
droppingBeforeClassName = 'dropping-before',
1345+
droppingAfterClassName = 'dropping-after';
1346+
1347+
scope.$watch(function(){
1348+
return $select.sortEnabled;
1349+
}, function(n){
1350+
$log.log(n);
1351+
if (n) {
1352+
element.attr('draggable', true);
1353+
} else {
1354+
element.removeAttr('draggable');
1355+
}
1356+
});
1357+
1358+
element.on('dragstart', function(e) {
1359+
element.addClass(draggingClassName);
1360+
1361+
(e.dataTransfer || e.originalEvent.dataTransfer).setData('text/plain', scope.$index);
1362+
});
1363+
1364+
element.on('dragend', function() {
1365+
element.removeClass(draggingClassName);
1366+
});
1367+
1368+
var move = function(from, to) {
1369+
/*jshint validthis: true */
1370+
this.splice(to, 0, this.splice(from, 1)[0]);
1371+
};
1372+
1373+
var dragOverHandler = function(e) {
1374+
e.preventDefault();
1375+
1376+
var offset = axis === 'vertical' ? e.offsetY || e.layerY || (e.originalEvent ? e.originalEvent.offsetY : 0) : e.offsetX || e.layerX || (e.originalEvent ? e.originalEvent.offsetX : 0);
1377+
1378+
if (offset < (this[axis === 'vertical' ? 'offsetHeight' : 'offsetWidth'] / 2)) {
1379+
element.removeClass(droppingAfterClassName);
1380+
element.addClass(droppingBeforeClassName);
1381+
1382+
} else {
1383+
element.removeClass(droppingBeforeClassName);
1384+
element.addClass(droppingAfterClassName);
1385+
}
1386+
};
1387+
1388+
var dropTimeout;
1389+
1390+
var dropHandler = function(e) {
1391+
e.preventDefault();
1392+
1393+
var droppedItemIndex = parseInt((e.dataTransfer || e.originalEvent.dataTransfer).getData('text/plain'), 10);
1394+
1395+
// prevent event firing multiple times in firefox
1396+
$timeout.cancel(dropTimeout);
1397+
dropTimeout = $timeout(function() {
1398+
_dropHandler(droppedItemIndex);
1399+
}, 20);
1400+
};
1401+
1402+
var _dropHandler = function(droppedItemIndex) {
1403+
var theList = scope.$eval(attrs.uiSelectSort),
1404+
itemToMove = theList[droppedItemIndex],
1405+
newIndex = null;
1406+
1407+
if (element.hasClass(droppingBeforeClassName)) {
1408+
if (droppedItemIndex < scope.$index) {
1409+
newIndex = scope.$index - 1;
1410+
} else {
1411+
newIndex = scope.$index;
1412+
}
1413+
} else {
1414+
if (droppedItemIndex < scope.$index) {
1415+
newIndex = scope.$index;
1416+
} else {
1417+
newIndex = scope.$index + 1;
1418+
}
1419+
}
1420+
1421+
move.apply(theList, [droppedItemIndex, newIndex]);
1422+
1423+
scope.$apply(function() {
1424+
scope.$emit('uiSelectSort:change', {
1425+
array: theList,
1426+
item: itemToMove,
1427+
from: droppedItemIndex,
1428+
to: newIndex
1429+
});
1430+
});
1431+
1432+
element.removeClass(droppingClassName);
1433+
element.removeClass(droppingBeforeClassName);
1434+
element.removeClass(droppingAfterClassName);
1435+
1436+
element.off('drop', dropHandler);
1437+
};
1438+
1439+
element.on('dragenter', function() {
1440+
if (element.hasClass(draggingClassName)) {
1441+
return;
1442+
}
1443+
1444+
element.addClass(droppingClassName);
1445+
1446+
element.on('dragover', dragOverHandler);
1447+
element.on('drop', dropHandler);
1448+
});
1449+
1450+
element.on('dragleave', function() {
1451+
element.removeClass(droppingClassName);
1452+
element.removeClass(droppingBeforeClassName);
1453+
element.removeClass(droppingAfterClassName);
1454+
1455+
element.off('dragover', dragOverHandler);
1456+
element.off('drop', dropHandler);
1457+
});
1458+
}
1459+
};
1460+
}])
1461+
13201462
/**
13211463
* Highlights text that matches $select.search.
13221464
*

src/select2/match-multiple.tpl.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
-->
66
<span class="ui-select-match">
77
<li class="ui-select-match-item select2-search-choice" ng-repeat="$item in $select.selected"
8-
ng-class="{'select2-search-choice-focus':$select.activeMatchIndex === $index, 'select2-locked':$select.isLocked(this, $index)}">
8+
ng-class="{'select2-search-choice-focus':$select.activeMatchIndex === $index, 'select2-locked':$select.isLocked(this, $index)}"
9+
ui-select-sort="$select.selected">
910
<span uis-transclude-append></span>
1011
<a href="javascript:;" class="ui-select-match-close select2-search-choice-close" ng-click="$select.removeChoice($index)" tabindex="-1"></a>
1112
</li>

0 commit comments

Comments
 (0)