Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngSelect - Nesting options with arbitrary depth

In my app I have an array of objects with the following structure

[{"ID":1, "parentID":0, "name":"Parent #1"}, 
{"ID":2, "parentID":0, "name":"Parent #2"}, 
{"ID":3, "parentID":1, "name":"Child #1 1"}, 
{"ID":4, "parentID":3, "name":"child #1 2"},
{"ID":5, "parentID":2, "name":"child #2 1"},
{"ID":6, "parentID":5, "name":"child #2 2"}]

I'd like to present this as a select menu which would allow a user to select a leaf node while still outputting non-selectable parent nodes to display the hierarchy of the structure.

I've tried a few approaches, the most successful of which was, in my angular template, something along the lines of the following.

<div ng-repeat="(idx, category) in $scope.allCats">
    <select ng-model="$scope.cats[idx]" 
            ng-options="cat as cat.name group by $scope.parentName(cat.parentID, idx) for cat in $scope.allCategories track by cat.ID">
        <option value="">Select A Category</option>
    </select>
</div>

$scope.allCats is the array above and $scope.parentName() method returns a string.

The troubles with this are demonstrated in the following screenshot. Namely all parent items appear twice, once as an <option> and once as an <optgroup>, whereas I'd rather they only appeared as a selectable item but with them being obviously a parent item, and the hierarchy of the structure is not maintained; child nodes with ancestors and descendants do not appear in the correct 'family-tree' structure.

screenshot

How can I alter my injected data or my angular template to achieve the behaviour I desire?

That is, to display the entire hierarchy, as defined by the parentID attributes so each family shares a common ancestor, and with parent items only appearing once.

I suspect this is being complicated by the fact there is more than one level of descendants possible and because I would like to keep this as general as possible.

like image 870
Pedro del Sol Avatar asked Aug 20 '15 10:08

Pedro del Sol


1 Answers

What about sorting the array in the scope before passing it to the angular template?. If you have:

var arr = [
  {"ID":1, "parentID":0, "name":"Parent #1"}, 
    {"ID":3, "parentID":1, "name":"Child #1 1"}, 
      {"ID":4, "parentID":3, "name":"child #1 2"},
        {"ID":7, "parentID":4, "name":"asdfadsf"},
  {"ID":2, "parentID":0, "name":"Parent #2"}, 
    {"ID":5, "parentID":2, "name":"child #2 1"},
      {"ID":6, "parentID":5, "name":"child #2 2"}
];

it would be easier. Just show it as it is.

Code to sort:

var sortKeys = {};

// computes a unique sort key
function getSortKey(item) {
  if(sortKeys[item.ID] === undefined) {
    var parentItem = arr.filter(function(it) {
      return it.ID === item.parentID;
    })[0];

    if(parentItem != null) {
      return getSortKey(parentItem)  + '' + item.ID;
    }

    return item.parentID + '' + item.ID;
  }

  return sortKeys[item.ID];
}

// build sort keys to be used to sort items following their hierarchy
arr.forEach(function(item) {
  sortKeys[item.ID] = getSortKey(item);
});

// sort the array
arr.sort(function(item1, item2) {    
  var item1SortKey = sortKeys[item1.ID],
      item2SortKey = sortKeys[item2.ID];

  return item1SortKey < item2SortKey ?
    -1 :
    (item1SortKey > item2SortKey ?
      1:
      0);
});

var arr = [{"ID":1, "parentID":0, "name":"Parent #1"}, 
{"ID":2, "parentID":0, "name":"Parent #2"}, 
{"ID":3, "parentID":1, "name":"Child #1 1"}, 
{"ID":4, "parentID":3, "name":"child #1 2"},
{"ID":5, "parentID":2, "name":"child #2 1"},
{"ID":6, "parentID":5, "name":"child #2 2"},
{"ID":7, "parentID":4, "name":"asdfadsf"}];

var sortKeys = {};

// computes a unique sort key
function getSortKey(item) {
  if(sortKeys[item.ID] === undefined) {
    var parentItem = arr.filter(function(it) {
      return it.ID === item.parentID;
    })[0];

    if(parentItem != null) {
      return getSortKey(parentItem)  + '' + item.ID;
    }

    return item.parentID + '' + item.ID;
  }
  
  return sortKeys[item.ID];
}

// build sort keys to be used to sort items following their hierarchy
arr.forEach(function(item) {
  sortKeys[item.ID] = getSortKey(item);
});

// sort the array
arr.sort(function(item1, item2) {    
  var item1SortKey = sortKeys[item1.ID],
      item2SortKey = sortKeys[item2.ID];
  
  return item1SortKey < item2SortKey ?
    -1 :
    (item1SortKey > item2SortKey ?
      1:
      0);
});

document.getElementById('result').innerHTML = JSON.stringify(arr, null, 2);
<pre id="result"></pre>
like image 176
manji Avatar answered Oct 21 '22 02:10

manji