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

Commit c81e9f4

Browse files
committed
Merge pull request #651 from homerjam/feat-sort
Allow sorting of multiple select
2 parents bacb9d0 + fb3d343 commit c81e9f4

9 files changed

+197
-11
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" sortable="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" sortable="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 sortable="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/bootstrap/select-multiple.tpl.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
ng-click="$select.activate()"
1414
ng-model="$select.search"
1515
role="combobox"
16-
aria-label="{{ $select.baseTitle }}">
16+
aria-label="{{ $select.baseTitle }}"
17+
ondrop="return false;">
1718
</div>
1819
<div class="ui-select-choices"></div>
1920
</div>

src/select.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,54 @@
111111
height: auto;
112112
max-height: 200px;
113113
overflow-x: hidden;
114+
margin-top: -1px;
114115
}
115116

116117
.ui-select-multiple.ui-select-bootstrap {
117118
height: auto;
118-
padding: .3em;
119+
padding: 3px 3px 0 3px;
119120
}
120121

121122
.ui-select-multiple.ui-select-bootstrap input.ui-select-search {
122123
background-color: transparent !important; /* To prevent double background when disabled */
123124
border: none;
124125
outline: none;
125126
height: 1.666666em;
127+
margin-bottom: 3px;
126128
}
127129

128130
.ui-select-multiple.ui-select-bootstrap .ui-select-match .close {
129131
font-size: 1.6em;
130132
line-height: 0.75;
131133
}
132134

133-
.ui-select-multiple.ui-select-bootstrap .ui-select-match-item{
135+
.ui-select-multiple.ui-select-bootstrap .ui-select-match-item {
134136
outline: 0;
137+
margin: 0 3px 3px 0;
138+
}
139+
140+
.ui-select-multiple .ui-select-match-item {
141+
position: relative;
142+
}
143+
144+
.ui-select-multiple .ui-select-match-item.dropping-before:before {
145+
content: "";
146+
position: absolute;
147+
top: 0;
148+
right: 100%;
149+
height: 100%;
150+
margin-right: 2px;
151+
border-left: 1px solid #428bca;
152+
}
153+
154+
.ui-select-multiple .ui-select-match-item.dropping-after:after {
155+
content: "";
156+
position: absolute;
157+
top: 0;
158+
left: 100%;
159+
height: 100%;
160+
margin-left: 2px;
161+
border-right: 1px solid #428bca;
135162
}
136163

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

src/select.js

Lines changed: 141 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+
sortable: 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.sortable = 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
@@ -1111,6 +1113,11 @@
11111113
$select.searchEnabled = searchEnabled !== undefined ? searchEnabled : uiSelectConfig.searchEnabled;
11121114
});
11131115

1116+
scope.$watch('sortable', function() {
1117+
var sortable = scope.$eval(attrs.sortable);
1118+
$select.sortable = sortable !== undefined ? sortable : uiSelectConfig.sortable;
1119+
});
1120+
11141121
attrs.$observe('disabled', function() {
11151122
// No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
11161123
$select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
@@ -1359,6 +1366,140 @@
13591366
};
13601367
}])
13611368

1369+
// Make multiple matches sortable
1370+
.directive('uiSelectSort', ['$timeout', 'uiSelectConfig', 'uiSelectMinErr', function($timeout, uiSelectConfig, uiSelectMinErr) {
1371+
return {
1372+
require: '^uiSelect',
1373+
link: function(scope, element, attrs, $select) {
1374+
if (scope[attrs.uiSelectSort] === null) {
1375+
throw uiSelectMinErr('sort', "Expected a list to sort");
1376+
}
1377+
1378+
var options = angular.extend({
1379+
axis: 'horizontal'
1380+
},
1381+
scope.$eval(attrs.uiSelectSortOptions));
1382+
1383+
var axis = options.axis,
1384+
draggingClassName = 'dragging',
1385+
droppingClassName = 'dropping',
1386+
droppingBeforeClassName = 'dropping-before',
1387+
droppingAfterClassName = 'dropping-after';
1388+
1389+
scope.$watch(function(){
1390+
return $select.sortable;
1391+
}, function(n){
1392+
if (n) {
1393+
element.attr('draggable', true);
1394+
} else {
1395+
element.removeAttr('draggable');
1396+
}
1397+
});
1398+
1399+
element.on('dragstart', function(e) {
1400+
element.addClass(draggingClassName);
1401+
1402+
(e.dataTransfer || e.originalEvent.dataTransfer).setData('text/plain', scope.$index);
1403+
});
1404+
1405+
element.on('dragend', function() {
1406+
element.removeClass(draggingClassName);
1407+
});
1408+
1409+
var move = function(from, to) {
1410+
/*jshint validthis: true */
1411+
this.splice(to, 0, this.splice(from, 1)[0]);
1412+
};
1413+
1414+
var dragOverHandler = function(e) {
1415+
e.preventDefault();
1416+
1417+
var offset = axis === 'vertical' ? e.offsetY || e.layerY || (e.originalEvent ? e.originalEvent.offsetY : 0) : e.offsetX || e.layerX || (e.originalEvent ? e.originalEvent.offsetX : 0);
1418+
1419+
if (offset < (this[axis === 'vertical' ? 'offsetHeight' : 'offsetWidth'] / 2)) {
1420+
element.removeClass(droppingAfterClassName);
1421+
element.addClass(droppingBeforeClassName);
1422+
1423+
} else {
1424+
element.removeClass(droppingBeforeClassName);
1425+
element.addClass(droppingAfterClassName);
1426+
}
1427+
};
1428+
1429+
var dropTimeout;
1430+
1431+
var dropHandler = function(e) {
1432+
e.preventDefault();
1433+
1434+
var droppedItemIndex = parseInt((e.dataTransfer || e.originalEvent.dataTransfer).getData('text/plain'), 10);
1435+
1436+
// prevent event firing multiple times in firefox
1437+
$timeout.cancel(dropTimeout);
1438+
dropTimeout = $timeout(function() {
1439+
_dropHandler(droppedItemIndex);
1440+
}, 20);
1441+
};
1442+
1443+
var _dropHandler = function(droppedItemIndex) {
1444+
var theList = scope.$eval(attrs.uiSelectSort),
1445+
itemToMove = theList[droppedItemIndex],
1446+
newIndex = null;
1447+
1448+
if (element.hasClass(droppingBeforeClassName)) {
1449+
if (droppedItemIndex < scope.$index) {
1450+
newIndex = scope.$index - 1;
1451+
} else {
1452+
newIndex = scope.$index;
1453+
}
1454+
} else {
1455+
if (droppedItemIndex < scope.$index) {
1456+
newIndex = scope.$index;
1457+
} else {
1458+
newIndex = scope.$index + 1;
1459+
}
1460+
}
1461+
1462+
move.apply(theList, [droppedItemIndex, newIndex]);
1463+
1464+
scope.$apply(function() {
1465+
scope.$emit('uiSelectSort:change', {
1466+
array: theList,
1467+
item: itemToMove,
1468+
from: droppedItemIndex,
1469+
to: newIndex
1470+
});
1471+
});
1472+
1473+
element.removeClass(droppingClassName);
1474+
element.removeClass(droppingBeforeClassName);
1475+
element.removeClass(droppingAfterClassName);
1476+
1477+
element.off('drop', dropHandler);
1478+
};
1479+
1480+
element.on('dragenter', function() {
1481+
if (element.hasClass(draggingClassName)) {
1482+
return;
1483+
}
1484+
1485+
element.addClass(droppingClassName);
1486+
1487+
element.on('dragover', dragOverHandler);
1488+
element.on('drop', dropHandler);
1489+
});
1490+
1491+
element.on('dragleave', function() {
1492+
element.removeClass(droppingClassName);
1493+
element.removeClass(droppingBeforeClassName);
1494+
element.removeClass(droppingAfterClassName);
1495+
1496+
element.off('dragover', dragOverHandler);
1497+
element.off('drop', dropHandler);
1498+
});
1499+
}
1500+
};
1501+
}])
1502+
13621503
/**
13631504
* Highlights text that matches $select.search.
13641505
*

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>

src/select2/select-multiple.tpl.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
ng-hide="$select.disabled"
2222
ng-model="$select.search"
2323
ng-click="$select.activate()"
24-
style="width: 34px;">
24+
style="width: 34px;"
25+
ondrop="return false;">
2526
</li>
2627
</ul>
2728
<div class="select2-drop select2-with-searchbox select2-drop-active"

0 commit comments

Comments
 (0)