Stupidfrog Stupidfrog - 5 months ago 20
Javascript Question

AngularJS : how to create DOM and bind to a model based on arbitrary hierarchical data

Angularjs: complex directive in ng-repeat, how to bind the ngModel or ngChecked in directive and make it work?
given the array:

+----+-----------+----------+----------+-------+
| id | name | parentid | haschild | value |
+----+-----------+----------+----------+-------+
| 1 | parent | null | false | true |
| 2 | id1 child | 1 | true | true |
| 3 | id2 child | 2 | true | true |
| 4 | id3 child | 3 | false | true |
| 5 | id1 child | 1 | true | true |
| 6 | id5 child | 5 | false | true |
+----+-----------+----------+----------+-------+
$scope.permission = [
{"id":1,"name":"parent","value":true,"parentid":"","haschild":false},
{"id":2,"name":"id-1 child","value":true,"parentid":1,"haschild":true},
{"id":3,"name":"id-2 child","value":true,"parentid":2,"haschild":true},
{"id":4,"name":"id-3 child","value":true,"parentid":3,"haschild":false},
{"id":5,"name":"id-1 child","value":true,"parentid":1,"haschild":true},
{"id":6,"name":"id-5 child","value":true,"parentid":5,"haschild":false}
];


so in html

<div ng-repeat="x in permission" ng-if="x.parentid == undefined" has-child="{{x}}"></div>


and the directive

myApp.directive('hasChild', function($compile) {
return function(scope, element, attrs) {
var j = JSON.parse(attrs.hasChild);
function generatePermissionDom(thisElement,thisParent)
{
angular.forEach(scope.permission,function(o,i){
if(thisParent.id==o.parentid)
{
var e = $('<div><input type="checkbox" ng-model="o.value"/>'+o.name+'</div>');
generatePermissionDom(e,o);
$(thisElement).append(e);
}
});
}

if(j.haschild)
angular.forEach(scope.permission,function(o,i){
if(o.parentid==j.id)
{
var e = $('<div><input type="checkbox" ng-model="o.value"/>'+o.name+'</div>');
if(o.haschild)
generatePermissionDom(e,o);
$(element).append(e);
}
});

var p = $(element);
$compile(element.contents())(scope);
}
});


So how to bind the model value in the directive?
here is the plunker
http://plnkr.co/edit/QYqfEVaQF8WmUvB1aHB6?p=preview

additional info:

the haschild represent the the parent has child element

for example:

1.people A own multiple company, so the people A has child

2.then one of the company has multiple employee, then that company has child.
so in my directive, if this element(assume this is element A) has child then generate the child element and input and bind to the ngModel.

My Answer: this is how i think and simple way to achieve
http://plnkr.co/edit/olKb5oBoxv0utlNcBQPg?p=preview

var app = angular.module('plunk', []);
app.directive('generatePermissions', function($compile) {
return {
restrict : "E",
scope : true,
require : 'ngModel',
link:function(scope,element,attrs,ngModel)
{
function generatePermissionDom(parent,parentElement)
{

angular.forEach(scope.list,function(o,i){
if(parent.id==o.parentid)
{
var e = angular.element('<div style="border:1px solid red"><input type="checkbox" ng-model="list['+i+'].value"/></div>');
if(o.haschild)
generatePermissionDom(o,e);
parentElement.append(e);
}
});
}
angular.forEach(scope.list,function(o,i){

if(o.parentid == null)
{
var e = angular.element('<div style="border:1px solid red"><input type="checkbox" ng-model="list['+i+'].value"/></div>');
if(o.haschild)
generatePermissionDom(o,e);
element.append(e);
}
});
var p = $(element);
$compile(p.contents())(scope);
}
}
});


app.controller('MainCtrl', function($scope) {

$scope.list = [{"id":1,"name":"parent","value":true,"parentid":null,"haschild":true},{"id":2,"name":"id-1 child","value":false,"parentid":1,"haschild":true},{"id":3,"name":"id-2 child","value":false,"parentid":2,"haschild":true},{"id":4,"name":"id-3 child","value":false,"parentid":3,"haschild":false},{"id":5,"name":"id-1 child","value":false,"parentid":1,"haschild":true},{"id":6,"name":"id-5 child","value":false,"parentid":5,"haschild":false}];
$scope.checkValue = function()
{
angular.forEach($scope.list,function(o,i){
console.log("id:"+o.id+" name:"+o.name+" value:"+o.value);
});
}
});


html

<body ng-controller="MainCtrl">
<generate-permissions ng-model="list"></generate-permissions>
<button ng-click="checkValue()">check</button>
</body>


however, i think @NewDev's answer is the correct answer!

Answer

Basically, what you are looking for is to create an arbitrary hierarchical DOM structure and bind it to some equal hierarchical data structure (or ViewModel).

How you represent your hierarchy - I'll leave it up to you. It's not essential for the question.

For simplicity, suppose we model the hierarchy as a tree structure:

$scope.tree = {n: "root", v: false, 
      c: [
        {n: "L11", v: false, c: []},
        {n: "L12", v: false, c: [
          {n: "L21", v: true, c: [
            {n: "L31", v: false, c:[]}
            ]}
          ]}
        ]};

You then need to traverse this tree and create DOM elements bound to some object o (bear with me):

<div><input ng-model="o.v">{{o.n}}</div>

Then, this needs to be compiled against a scope. But since the structure is arbitrary, you could create a new child scope for each node.

So, what is o? o will be the object we will create on each child scope created for each node of the tree.

So, suppose you have a recursive function traverseTree, that 1) creates the DOM element, 2) compiles against this scope, and 3) creates a child scope for each child. It could look like something as follows:

function traverseTree(n, scope, parent){
  var me = angular.element("<div><input type='checkbox' ng-model='o.v'>{{o.n}}</div>");
  $compile(me)(scope);
  parent.append(me);

  for (var i = 0; i < n.c.length; i++) {
    var c = n.c[i];
    var childScope = scope.$new(true);
    childScope.o = c; // set object "o" on the scope

    traverseTree(c, childScope, me);
  }
}

The directive's link function kicks off the tree traversal:

app.directive("tree", function($compile){

  function traverseTree(n, scope, parent){
     // as above
  }

  return {
    restrict: "A",
    scope: {
      root: "=tree"
    },
    link: function(scope, element, attrs){
      var childScope = scope.$new(true);
      childScope.o = scope.root;

      traverseTree(scope.root, childScope, element);
    }
  };

});

The usage is:

<div tree="tree"></div>

Here's a plunker