1
votes

Select2 with MetroUI CSS works well with AngularJS. But when I try using the same in dependent drop down lists I face the issue that view of child drop-down list doesn't get updated accordingly to change in parent drop-down list.

Here is a Stack Snippet demonstrating the issue I am seeing.

(function () {
    "use strict";
    var app = angular.module("app", []);

    app.controller("AppCtrl", AppCtrl);

    function AppCtrl() {
        var vm = this;

        vm.ContextData = {
            selectParents: [{ "id": 1, "name": "item 1" },
                { "id": 2, "name": "item 2" },
                { "id": 3, "name": "item 3" },
                { "id": 4, "name": "item 4" }],
            selectChildren: [{ "id": 1, "parentId": 1, "name": "1 based on item1" },
                { "id": 2, "parentId": 1, "name": "2 based on item1" },
                { "id": 3, "parentId": 1, "name": "3 based on item1" },
                { "id": 4, "parentId": 1, "name": "4 based on item1" },
                { "id": 5, "parentId": 1, "name": "5 based on item1" },
                { "id": 6, "parentId": 1, "name": "6 based on item1" },
                { "id": 7, "parentId": 1, "name": "7 based on item1" },
                { "id": 8, "parentId": 2, "name": "8 based on item2" },
                { "id": 9, "parentId": 2, "name": "9 based on item2" },
                { "id": 10, "parentId": 2, "name": "10 based on item2" },
                { "id": 11, "parentId": 2, "name": "11 based on item2" },
                { "id": 12, "parentId": 2, "name": "12 based on item2" },
                { "id": 13, "parentId": 3, "name": "13 based on item3" },
                { "id": 14, "parentId": 3, "name": "14 based on item3" },
                { "id": 15, "parentId": 3, "name": "15 based on item3" },
                { "id": 16, "parentId": 3, "name": "16 based on item3" },
                { "id": 17, "parentId": 4, "name": "17 based on item4" },
                { "id": 18, "parentId": 4, "name": "18 based on item4" }]
        };            
    }

})();
<body ng-app="app" ng-controller="AppCtrl as vm">
    <div class="flex-grid">
        <div class="row">
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a parent" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedParent" ng-options="item.name for item in vm.ContextData.selectParents">
                        <option value=""></option>                        
                    </select>
                </div>
            </div>
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a child" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedChild" ng-options="item.name for item in vm.ContextData.selectChildren | filter:{parentId:vm.selectedParent.id}">
                        <option value=""></option>
                    </select>
                </div>
            </div>
        </div>
    </div>
    <div class="flex-grid margin10">
        <div class="row">
            <div class="cell colspan2">
                Selected Parent : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedParent}}
            </div>
        </div>
        <div class="row">
            <div class="cell colspan2">
                selected Child : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedChild}}
            </div>
        </div>
    </div>    
</body>

<link href="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/css/metro-responsive.min.css" rel="stylesheet"/>
<link href="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/css/metro.min.css" rel="stylesheet"/>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/js/select2.min.js"></script>
<script src="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/js/metro.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>

Steps to reproduce:

  1. Select 'Item 1' in parent select (parent select's place-holder is 'select a parent')
  2. In "Selected parent" section correct value of selected parent is shown.
  3. Child select is bound with correct child option.
  4. Select any option from child select.
  5. In "Selected child" section correct value of selected child is shown.
  6. In Parent select, select 'Item 4'.
  7. In "Selected parent" section correct value of selected parent is shown.
  8. Child select is bound but the options are not updated and it still shows the options for parent Item1.
  9. Select any item in child select, In "Selected Child" section correct value of child is shown, but child drop down list shows wrong Item!

Hence my issue is that even though child select gets the correct options bound to it, it's option list view doesn't refreshes.

[I tried select2.val(") but still the option view of child dropdown did not refresh.]

Another issue is this: if I clear the selection in Parent select, it doesn't clear out the selection in Child select.

2
Your HTML doesn't include your scripts, so it's not clear how Select2 is being initialized. It's also not clear why your select boxes are hidden and why it looks like you are putting your settings for Select2 on the div element.Kevin Brown-Silva
Hi Kevin. I am using MetroUI CSS and that is why you see setting of Select2 on Div element. This is how MetroUI CSS has provided support for Select2 in their libraries. Do not know why my Codepen path is not visible here, there I have shown whole code with data and all.alencdave
This is the Codepen.io pen for the problem in hand:codepen.io/alencdave/pen/EPZWNRalencdave

2 Answers

0
votes

The issue here involves how Select2 caches options once the dropdown is open, because it is saving what it thinks is the correct data object to the <option> even though AngularJS is actually changing the option later on. As a result, Select2 will still display the cached data while AngularJS will recognize that the option has changed, and you end up seeing strange edge cases like this.

There is no known fix to this issue right now, except for forcing AngularJS to somehow not reuse options in the dependent dropdown.

0
votes

See this codepen.io to get the dependent drop-down list working.

Key points of solution are:

  1. Give an ID to child drop-down
  2. Then provide an ng-change event to parent drop-down
  3. In ng-change event of parent drop-down, search for the child drop-down via it's ID
  4. Then call select2 functions on child drop-down to empty the selected value and html
  5. Do not forget to trigger change event on child drop-down.

so it would be something like this in the ng-change event (childSelect2 is ID of child drop-down):

var ele = angular.element('#childSelect2');            
ele.html("").val("").trigger("change");

Also note that on clicking the cross mark in parent drop-down, we are clearing the child drop-down as for any change we are calling ele.html("").val("").trigger("change");

and this is expected because if there is no selection in parent then it has no meaning to have selection in child. but with this we also need to clear out the ng-model of child drop-down so for that in ng-change function set the model of child drop-down to null (vm.selectedChild is ng-model for child drop-down)

vm.selectedChild = null;

though to have this any effect I found out that this setting of child model to null should be before calling ele.html("").val("").trigger("change"); hence I have made it the first statement in ng-change event. [I do not know why this placement-order is important, but it worked only after this change :)]

Hope the answer helps other and save their time.

quick reference: HTML Code:

<body ng-app="app" ng-controller="AppCtrl as vm">
    <div class="flex-grid">
        <div class="row">
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a parent" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedParent" ng-options="item.name for item in vm.ContextData.selectParents" ng-change="vm.SetSelect2()">
                        <option value=""></option>                        
                    </select>
                </div>
            </div>
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a child" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedChild" ng-options="item.name for item in vm.ContextData.selectChildren | filter:{parentId:vm.selectedParent.id}" id="childSelect2">
                        <option value=""></option>
                    </select>
                </div>
            </div>
        </div>
    </div>
    <div class="flex-grid margin10">
        <div class="row">
            <div class="cell colspan2">
                Selected Parent : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedParent}}
            </div>
        </div>
        <div class="row">
            <div class="cell colspan2">
                selected Child : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedChild}}
            </div>
        </div>
    </div>    
</body>

JS Code (angular Module and controller):

(function () {
    "use strict";
    var app = angular.module("app", []);

    app.controller("AppCtrl", AppCtrl);

    function AppCtrl() {
        var vm = this;

        vm.ContextData = {
            selectParents: [{ "id": 1, "name": "item 1" },
                { "id": 2, "name": "item 2" },
                { "id": 3, "name": "item 3" },
                { "id": 4, "name": "item 4" }],
            selectChildren: [{ "id": 1, "parentId": 1, "name": "1 based on item1" },
                { "id": 2, "parentId": 1, "name": "2 based on item1" },
                { "id": 3, "parentId": 1, "name": "3 based on item1" },
                { "id": 4, "parentId": 1, "name": "4 based on item1" },
                { "id": 5, "parentId": 1, "name": "5 based on item1" },
                { "id": 6, "parentId": 1, "name": "6 based on item1" },
                { "id": 7, "parentId": 1, "name": "7 based on item1" },
                { "id": 8, "parentId": 2, "name": "8 based on item2" },
                { "id": 9, "parentId": 2, "name": "9 based on item2" },
                { "id": 10, "parentId": 2, "name": "10 based on item2" },
                { "id": 11, "parentId": 2, "name": "11 based on item2" },
                { "id": 12, "parentId": 2, "name": "12 based on item2" },
                { "id": 13, "parentId": 3, "name": "13 based on item3" },
                { "id": 14, "parentId": 3, "name": "14 based on item3" },
                { "id": 15, "parentId": 3, "name": "15 based on item3" },
                { "id": 16, "parentId": 3, "name": "16 based on item3" },
                { "id": 17, "parentId": 4, "name": "17 based on item4" },
                { "id": 18, "parentId": 4, "name": "18 based on item4" }]
        };

        vm.SetSelect2 = function () {
            ////debugger;
            vm.selectedChild = null;
            var ele = angular.element('#childSelect2');
            //ele.html("");
            //ele.val("").trigger("change");  

            ele.html("").val("").trigger("change");
        }
    }

})();