rlcrews rlcrews - 3 months ago 18
AngularJS Question

Update an angular treeview directive when a new object is added to the collection

I have a treeview directive credit to http://codepen.io/bachly/pen/KwWrzG for being my starting block. that I am trying to update when I add objects to the collection. I can update the object and insert the new objects but the treeview directive is never called once the

$scoped
item is updated.
Ultimately the data used will come from a service at this point I am just testing with mock data.

The original collection looks like this

$scope.myList = {
children: [
{
name: "Event",
children: [
{
name: "Event Date",
parent:"Event",
children: [
{
name: "2008",
filterType: '_eventStartDate',
parent: 'Event'
},
{
name: "2009",
filterType: '_eventStartDate',
parent: 'Event'
}
]
},
{
name: "Event Attendee",
parent: "Event",
children: [
{
name: "Person 1",
filterType: '_eventAttenddeeName',
parent: 'Event Attendee'
},
{
name: "Person 2",
filterType: '_eventAttenddeeName',
parent: 'Event Attendee'
}
]
}
]
}]
};


var TheOtherCollection = {
children: [
{
name: "A New Event",
children: [
{
name: "The Other Date",
parent: " A New Event",
children: [
{
name: "2010",
FilterType: '_eventStartDate',
Parent: '_event'
},
{
name: "2011",
FilterType: '_eventStartDate',
Parent: '_event'
}
]
}
]
}]
};


This generates a tree view with checkboxes using the following directive and html

app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children track by $index" src="c" filter="doSomething(object, isSelected)"></branch></ul>'
};
});

app.directive('branch', function($compile) {

return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
checked: '=ngModel'
},

template: '<li><input type="checkbox" ng-click="innerCall()" ng-model="b.$$hashKey" ng-change="stateChanged(b.$$hashKey)" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {

var clicked = '';
var hasChildren = angular.isArray(scope.b.children);
scope.visible = hasChildren;
if (hasChildren) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (hasChildren) {
element.toggleClass('collapsed');
}
});
scope.stateChanged = function(b) {
clicked = b;
};

scope.innerCall = function() {
scope.filter({ object: scope.b, isSelected: clicked });
};
}
};
});


And then the html

<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething(object, isSelected)"></tree>

<a ng-click="clicked()"> link</a>
</div>


When a checkbox is clicked the new collection is added to the existing one using lodashjs

ng-click event

$scope.doSomething = function (object, isSelected) {
if (isSelected) {
var item = object;
console.log(item);
nestAssociation(object, $scope.myList, TheOtherCollection);
}
}


which creates the new array and adds it within the children array

function nestAssociation(node, oldCollection, newAggregates) {
// var item = fn(oldCollection, node.parent);
var updatedArray = _.concat(oldCollection.children, newAggregates);
console.log(updatedArray);
if (updatedArray != null)
updateMyList(updatedArray);
}


I can see in the output I have a new object but I can't get the treeview to update. I have tried within the directive to add a $compile(element) on the click event in the directive but since the array is not built yet nothing changes.

Do I need to add a $watch to this directive and if so where or is there some other way I can get the directive to re-render and display the new nested collection?

Update

Base on some of the feedback and questions here is a little more detail around the question. The issue I am seeing is not in the directive as far as moving data around the issue is I cannot get the treeview to re-render once an array is added to the existing model.
The following link is a working plunker that shows the project as it currently works.
Running chrome dev tools I can see in the output the model is updated after a checkbox is selected
enter image description here

While I see the object is updated, the directive never updates to show the new array added to the object. This is the part that I need help understanding.

thanks in advance

Answer

You pass the function to the inner directives (which is best practice), but you have access to scope.filter. Not doSomethingFunction. This one is undefined there.

filter="doSomething(object, isSelected)"
=>
filter="filter(object, isSelected)"

app.directive('tree', function () {
    return {
        restrict: 'E', 
        replace: true,
        scope: {
            t: '=src',
            filter: '&'
        },
        controller: 'treeController',
        template: '<ul>  
                    <branch ng-repeat="c in t.children track by $index"       
                   src="c" filter="filter(object, isSelected)">  
                    </branch>  
                  </ul>'
    };
});

Next :

You can never access $$ variables in angularJS, because they are private. Maybe you should make one from your DB..., but the $$hashkey seems a easy solution though.

checked attribute might throw an error, because ngModel does not exist on your tree directive template. (put at least a ? before)

A checkbox can not have as model a $$hashkey.
Ng-change and ng-click will always be called at the same time, use the simplest one.

app.directive('branch', function($compile) {

    return {
        restrict: 'E', 
        replace: true, 
        scope: {
            b: '=src',
            filter: '&'
        },

            template: '<li><input type="checkbox" ng-click="innerCall(b.$$hashKey)" ng-model="isChecked" ng-hide="visible" /><a>{{ b.name }}</a></li>',
            link: function (scope, element, attrs) {

               scope.isChecked = false;
               var hasChildren = angular.isArray(scope.b.children);
                scope.visible = hasChildren;
                if (hasChildren) {
                    element.append('<tree src="b"></tree>');
                    $compile(element.contents())(scope);
                }
                element.on('click', function(event) {
                    event.stopPropagation();
                    if (hasChildren) {
                        element.toggleClass('collapsed');
                    }
                });

                scope.innerCall = function(hash) {
                    if(scope.isChecked){
                       scope.filter({ object: scope.b, isSelected: hash });
                    }

                };
            }
        };
    });

UPDATE

You have the same treeController in your tree directive and in your index.html view. This is what causes the view not to update!

I deleted the one in your directive, otherwise you'll have a controller for each child.
You saw the good console.log message in your controller, but it was in a controller for ONE directive.
You were not accessing the controller of the index.html.

Then I fixed the filter function communication between childs :

You forgot to communicate the filter function when you append new tree's :

element.append('<tree src="b" filter="filter({ object: object, isSelected: isSelected })"></tree>');

Also, in your parent directive template, you also need the hash to send parameters to the function :
filter="filter({ object: object, isSelected: isSelected })"

I edited your Plunker HERE without changing the code with the above comments I made.
(I'm not sure what you write is not what you want and because you did not comment I rather not change it so you still undertand your code fast)
But the view is updating now!
I think a little debug with what you want and the comments above should be enough.

EDIT 2
You forgot to return an object with the property chrilden. You returned an array, which caused the problem.

function updateMyList(data) {
      var transformed = { children : data };
        $scope.myList = transformed;
    }

Here is a working PLUNKER