1

I have hierarchical data set. There is one fixed root unit.

What I want to do is to make this tree browsable with dependent selects.

I have created a simple plunkr example with a fixed dataset.

http://plnkr.co/edit/Bz5A1cbDLmcjoHbs5PID?p=preview

The data format in the example mimics the format I would get from a server request in "real" life.

This working fine in this simple first step. What is missing is, that when a user changes a selection somewhere in the middle, the select boxes and the ng-model binding below the new selection need to be destroyed.

So when I select Europe->France->Quimper and change "Europe" to "Asia" - then there should be "Asia" as the first select box and a second one the Asia countries.

Is there an "Angular" way to deal to deal with this? Any other hint is appreciated also ;)

<!DOCTYPE html>
<html ng-app="app">

<head>
  <link data-require="[email protected]" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
  <script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="[email protected]"></script>
</head>

<body>

  <div ng-controller="Ctrl">

    <select ng-repeat="select in selects track by $index" ng-model="$parent.boxes[$index]">
      <option ng-repeat="child in select.children" ng-click="expandSelects(child)">{{child.name}}</option>
    </select>


    <ul>
      <li ng-repeat="item in boxes">{{ item }}</li>
    </ul>

  </div>


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

    app.controller('Ctrl', ['$scope', function($scope) {

      var data = {
        'europe': {
          name: 'europe',
          children: [{
            name: 'france',
            parent: 'europe'
          }, {
            name: 'italy',
            parent: 'europe'
          }],
        },
        'asia': {
          name: 'asia',
          children: [{
            name: 'japan',
            parent: 'asia'
          }, {
            name: 'china',
            parent: 'asia'
          }],
        },
        'france': {
          name: 'france',
          children: [{
            name: 'paris',
            parent: 'france'
          }, {
            name: 'quimper',
            parent: 'france'
          }]
        }
      };

      var root = {
        name: 'world',
        children: [{
          name: 'europe',
          parent: 'world'
        }, {
          name: 'asia',
          parent: 'world'
        }, ]
      };

      $scope.selects = [root];

      $scope.expandSelects = function(item) {
        var select = data[item.name];
        if (select) {
          $scope.selects.push(select);
        }
      }

      $scope.$watch('boxes', function(item, old) {

      }, true);

    }]);
  </script>
</body>

</html>

3 Answers 3

1

This is a classic example of cascading dropdowns, with the added challenge of an unknown number of levels in the cascade. I combined the data set into one object for simplicity, added labels for the dropdowns, and simplified the select element.

This solution allows for any number of levels, so if you needed data below the city level, you could add it without changing any code, as illustrated by the "Street" example I added to Paris.

select {
  display: block;
}
<!DOCTYPE html>
<html ng-app="app">

<head>
  <link data-require="[email protected]" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
  <script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="[email protected]"></script>
</head>

<body>
  <div ng-controller="Ctrl">
      <div ng-repeat="select in selects track by $index" ng-if="select.children">
          <label>{{ select.optionType }}</label>
          <select ng-model="selects[$index + 1]" ng-options="child.name for child in select.children" ng-change="clearChildren($index)"></select>
        <hr />
      </div>
  </div>

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

    app.controller('Ctrl', ['$scope', function($scope) {
      var data = {
        optionType: 'Continent',
        name: 'World',
        children: [
          {
            optionType: 'Country',
            name: 'Europe',
            children: [
              {
                optionType: 'City',
                name: 'France',
                children: [
                  {
                    optionType: 'Street',
                    name: 'Paris',
                    children: [
                    {
                      name: 'First'
                    },
                    {
                      name: 'Second'
                    }
                  ]
                  },
                  {
                    name: 'Quimper'
                  }
                ]
              },
              {
                name: 'Italy'
              }
            ]
          },
          {
            optionType: 'Country',
            name: 'Asia',
            children: [
              {
                name: 'Japan'
              },
              {
                name: 'China'
              }
            ]
          }
        ]
      };

      $scope.selects = [data]
      $scope.clearChildren = function (index) {
        $scope.selects.length = index + 2;
      };
    }]);
  </script>
</body>
</html>

Sign up to request clarification or add additional context in comments.

1 Comment

This is great but I was wondering whether on initially loading the page we can set the ng-selected attribute of each of the select items e.g set first one to Europe, second one to France etc.
0

To go to the children in your hierachy is not as hard as it may seem. If you set up your select with angular and let it do most of the selection for you (for example using ng-options instead of ng-repeating the tag itself), and tell it what options there are, then the list of children you are trying to render just becomes a standard ng-repeat of the children that were picked from the select above.

I modified your plunker to show you how you could accomplish that a slightly different way.

http://plnkr.co/edit/zByFaVKWqAqlR9ulxEBt?p=preview

Main points I changed were

$scope.expandSelects = function() {
    var select = data[$scope.selected.name];
    if (select) {
      console.log('changed');
      console.log(select);
      $scope.chosen = select;
    }
  }

Here i just grab the chosen item which the will use. Then the ends up looking like.

<ul>
  <li ng-repeat="item in chosen.children">{{ item.name }}</li>
</ul>

The only other set up that was really needed was setting up the with ng-options and giving it a model to bind to.

<select ng-options="child.name for child in selects.children" 
    ng-model="selected" ng-change="expandSelects()">
</select>

Comments

0

Use can use a filter on the second select to filter de options based on the previous selection.

For example, you can have a first selection to choose the continent:

<select ng-options="c for c in continents" ng-model="selectedContinent" ></select>

and a second selection for the coutries:

<select ng-options="c.name for c in countries | filter : {parent:selectedContinent}" ng-model="selectedCountry" ></select>

Made a fiddle with a simplified data structured just to show how the filter works: http://jsfiddle.net/marcosspn/oarL4n78/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.