I have created an angularjs app with multiple select upon which I am having up and down button , within which when I click up and down button corresponding movement of items
persons
will return an array of of whatever items are selected in the list. One solution is to create a for loop that gets the indexOf
of each item in the persons
array. splice
that items out of the peoples
array, increment/decrement the index, and splice
it back in to the peoples
array.
Here is a new moveUp()
function that can move up multiple selected items:
$scope.moveUp = function () {
for(var i = 0; i < $scope.persons.length; i++) {
var idx = $scope.peoples.indexOf($scope.persons[i])
console.log(idx);
if (idx > 0) {
var itemToMove = $scope.peoples.splice(idx, 1)
console.log(itemToMove[0])
$scope.peoples.splice(idx-1, 0, itemToMove[0]);
}
}
};
Here is the updated moveDown()
function:
$scope.moveDown = function () {
for(var i = 0; i < $scope.persons.length; i++) {
var idx = $scope.peoples.indexOf($scope.persons[i])
console.log(idx);
if (idx < $scope.peoples.length) {
var itemToMove = $scope.peoples.splice(idx, 1)
console.log(itemToMove[0])
$scope.peoples.splice(idx+2, 0, itemToMove[0]);
}
}
};
Here is the Working Demo (Not working so well, just kept for reference - see below)
This solution also maintains the separation between the View and the Controller. The controller has the job of manipulating the data, the view displays that data. This way we can avoid any uncomely entanglement. DOM manipulations from within the controller are incredibly difficult to test.
EDIT after some tinkering: So my previous solution worked in some cases but would perform oddly with different select combinations. After some digging, I found it necessary to add track by:
<select id="select" size="9" ng-model="persons" ng-options="item as item.name for item in peoples track by item.name" multiple>
It seems the select would return the persons
object with arbitrary selection orders and this was messing things up, especially after you clicked a few times, it seemed to get confused about where things were.
Additionally, I had to clone and reverse the persons array when moving items down because when adding track by item.name
it returns the items in order, but if you try to iterate through the array, moving each one down, you are potentially impacting the location of other items in the array (further producing unpredictable behavior). So we need to start from the bottom and work our way up when moving multiple items down.
Here is a solution in which I seem to have eliminated any unpredictable behavior when making multiple arbitrary selections:
Working Demo
EDIT: One bug I have found is that weird things happen when you move multiple selected items all the way up or down, and then try to move it that direction one more time. Any further movement without reselecting produces unpredictable results.
EDIT: The unpredictable behavior mentioned in the previous edit was because the functions were seeing that, although the first item was in it's final position, the second, third, fourth, etc. items were not in an end position, and thus it tried to move them which led to crazy re-ordering of items that were already pushed all the one to the top or bottom. In order to solve this I set a var that would track the position of the previously moved item. If the current item was found to be in the adjacent position it would simply leave it there and move on.
The final functions look something like this:
$scope.moveUp = function () {
var prevIdx = -1;
var person = $scope.persons.concat();
console.log($scope.persons);
for(var i = 0; i < $scope.persons.length; i++) {
var idx = $scope.peoples.indexOf($scope.persons[i])
console.log(idx);
if (idx-1 === prevIdx) {
prevIdx = idx
} else if (idx > 0) {
var itemToMove = $scope.peoples.splice(idx, 1)
console.log(itemToMove[0])
$scope.peoples.splice(idx-1, 0, itemToMove[0]);
}
}
};
(Hopefully) Final Demo
EDIT:
I enjoyed this problem and wanted to have a better solution in case there were duplicate list items. This was fairly easy to solve by giving each object in the array a unique id key, and then changing track by item.name
to track by item.id
, and all works as before.
Working Demo for Duplicates
Demo
You need to update your swapIf
implementation so that it swaps the model, not options from the view:
function swapIf(sel,i1,i2) {
if ( ! select[i1].selected && select[i2].selected) {
var obj1 = $scope.peoples[i1];
var obj2 = $scope.peoples[i2];
$scope.peoples[i2] = obj1;
$scope.peoples[i1] = obj2;
select[i1].selected = true;
select[i2].selected = false;
}
}
Also, remove the orderBy
in the view, and initialize the ordering in the controller using the $filter
service. The reason you need to do this is because the list is re-ordered whenever the user clicks the up/down button.