Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

move up and down elements in a multiple select not working

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 should be done within the multiple select, I have a sample stuff which has been done with normal javascript which does the similar thing correctly like as shown in this fiddle, but when I tried to implement the same stuff in AngularJS its not working properly

Can anyone please tell me some solution for this

My code is as given below

JSFiddle

html

<div ng-app='myApp' ng-controller="ArrayController">
    <select id="select" size="9" ng-model="persons" ng-options="item as item.name for item in peoples | orderBy:'name'" multiple></select>
    <br/>
    <button ng-click="moveUp()">Up</button>
    <br/>
    <button ng-click="moveDown()">Down</button>
    <br/>
</div>

script

var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
    $scope.peoples = [{
        name: 'Jacob'
    }, {
        name: 'Sunny'
    }, {
        name: 'Lenu'
    }, {
        name: 'Mathew'
    }, {
        name: 'Ferix'
    }, {
        name: 'Kitex'
    }];

    $scope.moveUp = function () {
        var select = document.getElementById("select");
        var i1=0, i2=1;
        while (i2 < select.options.length) {
            swapIf(select,i1++,i2++);
        }
    };

    $scope.moveDown = function () {
        var select = document.getElementById("select");
        var i1=select.options.length-1, i2=i1-1;
        while (i1 > 0) {
            swapIf(select,i1--,i2--);
        }
    };

    var swapVar = '';
    function swapIf(sel,i1,i2) {
        if ( ! select[i1].selected && select[i2].selected) {
            swapVar = select[i2].text;
            select[i2].text = select[i1].text;
            select[i1].text = swapVar;
            swapVar = select[i2].value;
            select[i2].value = select[i1].value;
            select[i1].value = swapVar;
            select[i1].selected = true;
            select[i2].selected = false;
        }
    }
});
like image 945
Alex Man Avatar asked Dec 14 '22 15:12

Alex Man


2 Answers

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

like image 127
tpie Avatar answered Dec 17 '22 04:12

tpie


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.

like image 25
pixelbits Avatar answered Dec 17 '22 05:12

pixelbits