I have this directive:
app.directive('recursiveListItem', ['$http', 'RecursionHelper', function ($http, RecursionHelper) {
return {
restrict: 'E',
scope: {
parent: '=',
onNodeClick: '&',
},
compile: function (element, attributes) {
return RecursionHelper.compile(element);
},
template:
'<div class="list-group-item-heading text-muted parent "> \
<input type="checkbox" data-ng-click="visible = !visible" id="{{parent.Name}}">\
<label for="{{parent.Name}}"> </label>\
<a href="javascript:void(0)" data-ng-click="onNodeClick({node: parent})">{{parent.Name}}</a> \
</div> \
<ul data-ng-if="parent.Children.length > 0" data-ng-show="visible"> \
<li ng-repeat="child in parent.Children"> \
<recursive-list-item data-parent="child" data-on-node-click="onNodeClick"></recursive-list-item> \
</li> \
</ul>',
};
}]);
and here is the helper:
app.factory('RecursionHelper', ['$compile', function ($compile) {
var RecursionHelper = {
compile: function (element) {
var contents = element.contents().remove();
var compiledContents;
return function (scope, element) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function (clone) {
element.append(clone);
});
};
}
};
return RecursionHelper;
}]);
Everything works like a charm, but I don't get my on-node-click
to work. For the all the root items the 'callback' works fine, but from there on, all the children won't fire the callback. I think it has something to do with passing the function reference to the next child in the template.
I've also tried data-on-node-click="onNodeClick(node)"
, but that doesn't work either.
Does someone know how to pass the function reference to the child nodes?
As a lead in its helpful to look at '&' which the docs describe this way:
& or &attr - provides a way to execute an expression in the context of the parent scope.
Often it's desirable to pass data from the isolated scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22})
In order to achieve this the function that is passed through an &
is wrapped in another function parentGet()
. We can see this in action by looking at the contents of the click handler function variable. First, before it's passed into the &
it's as we'd expect:
function (node) {
console.log("called with ",node);
}
But, then, inside your directive, after passing through '&', it looks like this:
function (locals) {
return parentGet(scope, locals);
}
So instead of a direct reference to your click handler function it is now a call that will apply your function using both the parent scope and any locals
which you pass in.
The problem is that, from what I can tell, as we nest down that scope variable keeps getting updated to the new nested parent scope. That's not good for us. We want the execution context to be that top scope where your click handler is. Otherwise we lose reference to your function- as we see happening.
To fix this we keep a reference to the original function (that isn't encumbered by parentGet()
as we nest down). Then when we pass it in, the scope used in parentGet()
is the one with click handler function on it.
First let's use a different name for the actual function than you'll use for the parameter. I set up the function like so $scope.onNodeClickFn = function(node) {...}
Then we make 3 changes:
1) Add a second scope variable (topFunc
):
scope: {
onNodeClick: '&',
topFunc: '='
}
onNodeClick
is the wrapped function (including scope parameter). And topFunc
is a reference to the unwrapped function (note that we pass that in using =
so it's a reference to the function absent the wrapper that &
applies).
2) At the top level, we pass both function references in.
<recursive-list-item on-node-click="onNodeClickFn(node)" top-func="onNodeClickFn" parent=parent ></recursive-list-item>
(note the lack of () on the top-func
parameter.)
3) Pass the new topFunc
parameter in to the template:
<recursive-list-item data-parent="child" data-on-node-click="topFunc(node)" top-func="topFunc"></recursive-list-item> \
So we are now maintaining a reference, at each nested scope, to the original function and using that in the template.
You can see it working in this fiddle: http://jsfiddle.net/uf6Dn/9/
I just doing the same thing with a questionnaire app, and the way that I keep the function passed in isolated scope through the all levels was putting the function in each item in function({param:param}) notation
In your could be as well:
<ul data-ng-if="parent.Children.length > 0" data-ng-show="visible"> \
<li ng-repeat="child in parent.Children"> \
<recursive-list-item data-parent="child" data-on-node-click="onNodeClick({param:param})"></recursive-list-item> \
</li> \
</ul>',
from the main controller(the page where is being added the directive) onNodeClick has params that will be set into the directive you must specify the name in the on-node-click attribute of the root directive, something like that
on-node-click="onNodeClick(param1,param2)"
and in the directive controller you can invoke it as well:
onNodeClick({param1: value1, param2:value2})
Otherwise if onNodeClick only affects vars in the main controller(the page where is being added the directive) you can pass to directive the name of the function with a param existent in the main controller scope and use it with no params in the directive controller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With