0
votes

I have an angularjs app, which has a select filled with options from an arry, using ngOptions. Every time the user clicks the button to add one, the ngRepeat directive generates a new select at the bottom.

I want to make sure that the user cannot select duplicate values.

So if my list contains 3 items: Item1, Item2 and Item3, and the user selects Item3 and then presses the button, the last generated select list should contain only items 'Item1' and 'Item2'. If the user would then select 'Item1' and presses the button, the user should see the next select be generated with only the 'Item2' option.

So generally, in the case above, the generated HTML should be something like this:

<div data-ng-repeat="item in selectedOptions">
  <select>
    <option value="1">Item1</option>
    <option value="2">Item2</option>
    <option value="3">Item3</option>
  </select>

 <select>
  <option value="1">Item1</option>
  <option value="2">Item2</option>
 </select>

 <select>
  <option value="2">Item2</option>
 </select>
</div> 

Keep in mind: the user will keep seeing all THREE of the selects, once with every option available, once with just two options available, and once with just one option available.

I've been thinking of a lot of ways to make this work, but so far I haven't had any luck. Does anyone know of a pattern I can use in angular to achieve this behavior?

This is something that I've tried so far.

<div data-ng-repeat="function in item.Functions">
    <select data-ng-model="function.Id" data-ng-options="j.Id as j.Name for j in getCorrectFunctions(functionsList)">
        <option selected="selected" value="">---</option>
     </select>

     <a data-ng-click="addFunction()">
        <i class="fa fa-plus fa-plus-lower"></i>
     </a>
</div>

and in my directive code I have following function:

  function getCorrectFunctions(functionList) {
                var item = scope.item;

                var list = functionList.slice();

                //excluded for brevity: this was a loop which would remove every item that wasn't available anymore

                return list;
            }

I thought this would be executed once for every item in the list, but that does not seem to be case. I don't think applying a filter would be any different, would it?

1
Could you show a bit more code that you have done so far, for example create a jsfiddle/plunker? Depending on what you have done so far, one approach might be to use a custom filter on your data-ng-repeat, that would filter out those options that have already been chosen. Another might be to do the filtering already beforehand, when generating the new selectedOptions array.jmustonen
I've updated my question to include some of the things I've triedSteven Lemmens
I'm not sure I fully understand what you're trying to achieve... Is the initial state such that there are 3 items in your functions array, and you'd like to show 3 selects with 3, 2 and 1 options respectively? How would that prevent your user from choosing the same option twice, for example in your first and last selects?jmustonen
You're right, it doesn't actually prevent my user from selecting the same option twice. Hadn't thought of the possibility yet. Can you think of a way that all the selects show only the data that can be used? For instance, with a filter? Because the first select will have to keep showing the selected function, but can't show the functions that have been selected in the other selects ... That's where my mind really draws a blank.Steven Lemmens
I'd say yes, filter should work there. You'll need to keep the user selections stored somewhere (indexed by select, so you'll know which selection is chosed where -> don't filter that out), and then in the filter function just filter out those already picked out.jmustonen

1 Answers

1
votes

Here's one take on this. This does not have support for dynamically adding new functions, but however it does prevent user from selecting any given item twice.

See Plunker for working example and more details.

First the Angular setup part. Here I've defined a mock array of function objects ($scope.functions) and array for user made selections ($scope.selected)

var app = angular.module('app', []);

app.controller('SelectCtrl', function ($scope) {
  $scope.functions = [
    {id: 1, name: 'First'},
    {id: 2, name: 'Second'},
    {id: 3, name: 'Third'},
  ];
  $scope.selected = [];

  $scope.selectedFilter = function (selectNumber) {
    // snipped for now
  };
});

In html, showing only one select, but similar approach used for all 3 selects: set the selected value to the selected array in given index (0 for the case shown below), and use selectedFilter to filter functions with same index value.

  <select ng-options="j.id as j.name for j in functions | filter:selectedFilter(0)" ng-model="selected[0]">
    <option value="" selected="selected">---</option>
  </select>

Then finally the filtering function. It returns true for all unselected functions and for the selected function of the given select.

  $scope.selectedFilter = function (selectNumber) {
    return function (func) {
      if ($scope.selected.length === 0) {
        return true;
      } else {
        var unselectedFunctions = _.filter($scope.functions, function (fn) {
          return _.findIndex($scope.selected, function (sel) {
            return fn.id === sel;
          }) === -1;
        });
        var selectedForCurrentId = $scope.selected[selectNumber];
        var selectedForCurrent = _.find($scope.functions, {id: selectedForCurrentId});
        return func === selectedForCurrent || _.findIndex(unselectedFunctions, {id: func.id}) > -1;
      }
    };
  };

Here I've used Lodash for some nice helper functions. Not affiliated with Lodash in any way, but I really suggest you to take a look at it, or any other similar library.

Hopefully this helps you to get things moving on!