Call function in controller from a nested directive - javascript

I have two nested directives for building a treeview in Angular:
Parent directive:
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '='
},
template: "<ul><node ng-repeat='node in nodes' node='node'></node></ul>"
}
});
Child directive:
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '='
},
template: "<li>{{node.ObjectName}}</li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children'></nodes>");
$compile('<nodes nodes="node.Children"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
The controller:
function myController($scope, DataService) {
$scope.init = function() {
DataService.getData(0, 0).then(function(data) {
$scope.treeNodes = $.parseJSON(data.d);
});
}
$scope.focusNode = function(prmNode) {
console.log(prmNode);
}
}
HTML:
<div ng-app="testTree" ng-controller="myController">
<div ng-init="init()">
<nodes nodes='treeNodes'></nodes>
</div>
</div>
My question is how can I implement a click on the <li> which will call the "focusNode" function in the controller?

You could pass in the function through an attribute.
Javascript
var myApp = angular.module('myApp', []);
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '=',
clickFn: '&'
},
template: "<ul><node ng-repeat='node in nodes' node='node' click-fn='clickFn()'></node></ul>"
}
});
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '=',
clickFn: '&'
},
template: "<li><span ng-click='clickFn()(node)'>{{node.ObjectName}}</span></li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children' click-fn='clickFn()'></nodes>");
$compile('<nodes nodes="node.Children" click-fn="clickFn()"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
function myController($scope) {
$scope.focusNode = function(prmNode) {
console.log(prmNode);
}
$scope.root = {
ObjectName: 'Root',
Children:[{
ObjectName: 'A',
Children: [{
ObjectName: 'B'
}, {
ObjectName: 'C'
}]
}]
};
}
HTML
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.2.8" src="http://code.angularjs.org/1.2.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app='myApp' ng-controller="myController">
<node node="root" click-fn="focusNode"></node>
</body>
</html>

Since I can't comment until I get 50 rep cred :(...
I just wanted to add that I also got an error when trying the plunkr that W.L.Jared shared above. To fix the error, I changed the controller from a global function to:
angular.module('myApp').controller('myController', function($scope){...})
The error went away.
Good answer though :) Exactly what I was looking for.

Related

Angular directive to match multiple attributes

Can I define an angular directive so that it would match multiple similar terms
i.e.
angular.module('search').directive('platformPreload', function() {
return {
link: function(scope, element, attrs) {
}
}
}
Would match both the following:
<div platform-preload-terms="[]"></div>
<div platform-preload-suggestions="[]"></div>
There are no wildcard directive declaration.
But you can isolate the function and repeat the definition:
angular.module('search')
.directive('platformPreload', PlatFunction)
.directive('platformPreloadSuggestions', PlatFunction)
PlatFunction() {
return {
link: function(scope, element, attrs) { }
}
}
You could create isolated scopes for the directive which will allow you to use these attributes in the isolated scope on the directive. Like this:
angular.module('myApp', [])
.controller('appController', function($scope) {
})
.directive('platformPreload', function() {
return {
restrict: 'A',
scope: {
platformTerms: '#',
platformSuggestions: '#'
},
link: function($scope, element, attrs) {
console.log('DIRECTIVE');
if ($scope.platformTerms) {
console.log($scope.platformTerms);
}
if ($scope.platformSuggestions) {
console.log($scope.platformSuggestions);
}
}
};
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body ng-app="myApp" ng-controller="appController">
<div platform-preload platform-terms="These are the terms"></div>
<div platform-preload platform-suggestions="These are the suggestions"></div>
</body>
</html>

How to tell angular where to render child directives inside parent template (multiple transclude)?

I have problem that is inability to render child directive (selected-item-template) in the parent template.
Code bellow:
HTML (Main/child directive)
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-model="customer"
cs-items="contacts"
>
<display-item-template>
<span>{{$parent.item.id}}</span>
<span>{{$parent.item.name}}</span>
</display-item-template>
<selected-item-template>
Your have selected customer: {{$parent.item.name}}
</selected-item-template>
</compact-select>
Directive
angular.module('core').directive('compactSelect', [function($timeout) {
return {
templateUrl : 'modules/core/views/components/compact-select-tpl.html',
bindToController: true,
transclude: true,
scope: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csModel: '=',
csItems: '=csItems'
},
controllerAs : 'ctrl',
controller : function($scope) {
}
};
}]).directive('displayItemTemplate', function() {
return {
require: '^compactSelect',
restrict: 'E'
}
}).directive('selectedItemTemplate', function() {
return {
require: '^compactSelect',
restrict: 'E'
}
});
Directive Template (modules/core/views/components/compact-select-tpl.html)
<div class="compact-select-repeater-box" style="" >
<div ng-transclude ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">
<span>{{item.name}}</span>
<span>{{item.id}}</span>
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
**HERE I WANT SELECTED ITEM TEMPLATE**
</div>
Question: How I can tell where child directive needs to be rendered?
Directive on ng-repeat works, but when I add two directives everything gets combined together and that's not what I want. Is there is a way to specify with ng-transclude where to render which directive? Like ng-transclude="displayItemTemplate" and ng-transclude="selectedItemTemplate" respectively?
This Example Based on Directives, and i want to show you how multi directives work together on one array.
I hope this helps you.
var app = angular.module("app", []);
app.controller("ctrl", function ($scope) {
$scope.selectedList = [];
$scope.data = [
{ name: "a", checked: true },
{ name: "b", checked: false }
];
$scope.getResult = function () {
console.log($scope.selectedList);
}
});
app.directive("directiveA", [function () {
return {
restrict: "E",
template: "<ul ng-repeat=\"item in items\">" +
"<li><directive-c data=\"item\"></directive-c> {{item.name}} <directive-b data=\"item\"></directive-b></li>" +
"</ul>",
scope: {
items: "="
}
};
}]);
app.directive("directiveB", function () {
return {
restrict: "E",
template: "<button ng-click=\"renderData(data)\">{{data.checked ? 'unchecked':'checked'}}</button>",
scope: {
data: "="
},
link: function (scope) {
scope.renderData = function (data) {
data.checked = !data.checked;
}
}
}
});
app.directive("directiveC", function () {
return {
restrict: "E",
template: "<input type=\"checkbox\" ng-model=\"data.checked\">",
scope: {
data: "="
}
}
});
app.directive("directiveD", function () {
return {
restrict: "E",
template: "<ul ng-repeat=\"item in items\">" +
"<li ng-if=\"item.checked\">{{item.name}}</li>" +
"</ul>",
scope: {
items: "=",
result: "="
},
link: function (scope) {
scope.$watch("items", function (newValue) {
scope.result = [];
if (newValue) {
angular.forEach(scope.items, function (item, index) {
if (item.checked) scope.result.push(item);
});
}
}, true);
}
}
});
<!DOCTYPE html>
<html ng-app="app" ng-controller="ctrl">
<head>
<title></title>
</head>
<body>
<h3>items</h3>
<directive-a items="data"></directive-a>
<h3>selected items</h3>
<directive-d items="data" result="selectedList"></directive-d>
<hr />
<button ng-click="getResult()">get selected list</button>
<small>after click, check your console</small>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
I have figured out how to achieve following. It can be done with multiple transcludsions, since angular 1.5.
I just need to define transcludeSlot.
Code bellow:
HTML (Main/child directive)
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-model="customer"
cs-items="contacts"
>
<display-item-template>
<span>{{$parent.item.id}}</span>
<span>{{$parent.item.name}}</span>
</display-item-template>
<item-selected-template>
Your have selected customer: {{$parent.csModel.name}}
</item-selected-template>
</compact-select>
Directive
angular.module('core').directive('compactSelect', [function($timeout) {
return {
templateUrl : 'modules/core/views/components/compact-select-tpl.html',
bindToController: true,
transclude: {
'repeaterItemSlot': 'displayItemTemplate',
'itemSelectedTemplateSlot' : 'itemSelectedTemplate'
},
scope: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csModel: '=',
csItems: '=csItems'
},
controllerAs : 'ctrl',
controller : function($scope) {
}
};
}]);
Directive Template (modules/core/views/components/compact-select-tpl.html)
<div class="compact-select-repeater-box" style="" >
<div ng-transclude="repeaterItemSlot" ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">
<span>{{item.name}}</span>
<span>{{item.id}}</span>
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
<div ng-transclude="itemSelectedTemplateSlot"></div>
</div>
So this works nicely similar to XAML.

Functions in compile isolated directive - AngularJS

I'm trying to understand how to call an external function from a built-in-compile directive.
Here is an example: http://plnkr.co/edit/bPDaxn3xleR8SmnEIrEf?p=preview
html:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="myController as vm">
<my-directive callback="vm.saveAction()" label="click me!"></my-directive>
</body>
</html>
js:
app = angular.module('app', []);
app.controller('myController', function() {
var vm = this;
vm.saveAction = function() {
alert("foo!");
}
});
app.directive('myDirective', function() {
var directive = {
restrict: 'E',
scope: {
callback: '&'
},
compile: compile,
link: link
};
return directive;
function compile(element, attrs) {
var template = '<button ng-click="action()">'+attrs.label+'</button>';
element.replaceWith(template);
}
function link(scope, element) {
scope.action = function() {
// ...something usefull to do...
scope.callback();
}
}
});
I know that I could easly do it from the link function (and it works from there), but I really need to do it from the compile method (this is just a simplified version to better point out the problem).
Could someone help me?
Thank you!
Use template directive to do this
app.directive('myDirective', function() {
var directive = {
restrict: 'E',
scope: {
callback: '&',
label: '#'
},
template: '<button ng-click="action()">{{label}}</button>',
link: link
};
return directive;
function link(scope, element, attrs) {
console.log(scope.label);
scope.action = function() {
// ...something usefull to do...
scope.callback();
}
}
});
Or if you want to use compile method, use pre or post method and compile yourself:
function compile(element, attrs) {
return {
pre: function(scope, elem, attrs) {
var template = $compile('<button ng-click="action()">'+attrs.label+'</button>')(scope);
element.replaceWith(template);
},
post: function (scope, elem, attrs) {
// or here
}
}
}

List of directives inside of an ng-repeat

I'm working on a page that is made up of 5 directives, for example:
<directive-one></directive-one>
<directive-two></directive-two>
<directive-three></directive-three>
<directive-four></directive-four>
<directive-five></directive-five>
I would like to be able to re-order these on demand so that a user can control how their page looks. The only way I could think of doing that was putting them in an ng-repeat:
$scope.directiveOrder = [{
name: "directive-one",
html: $sce.trustAsHtml('<directive-one></directive-one>'),
order: 1
}, ...
HTML:
<div ng-repeat="directive in directiveOrder" ng-bind-html="directive.html">
{{directive.html}}
</div>
This will give me the right tags, but they aren't processed as directives by angular. Is there a way around that? I'm assuming it's something to do with $sce not handling it, but I might be way off?
Try creating a new directive and using $compile to render each directive:
https://jsfiddle.net/HB7LU/18670/
http://odetocode.com/blogs/scott/archive/2014/05/07/using-compile-in-angular.aspx
HTML
<div ng-controller="MyCtrl">
<button ng-click="reOrder()">Re-Order</button>
<div ng-repeat="d in directives">
<render template="d.name"></render>
</div>
</div>
JS
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
})
.directive("render", function($compile){
return {
restrict: 'E',
scope: {
template: '='
},
link: function(scope, element){
var template = '<' + scope.template + '></' + scope.template + '>';
element.append($compile(template)(scope));
}
}
})
.controller('MyCtrl', function($scope, $compile) {
$scope.directives = [{
name: 'directive-one'
}, {
name: 'directive-two'
}];
$scope.reOrder = function () {
$scope.directives.push($scope.directives.shift());
console.log($scope.directives);
};
});
I hope You can easily done it.
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
});
myApp.controller('ctrl',function($scope){
$scope.data = [{name:'directive-one'},{name:'directive-two'}];
});
<html ng-app='myApp'>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
</head>
<body ng-controller='ctrl'>
<div ng-repeat='item in data'>
<item.name></item.name>
<directive-one></directive-one>
</body>
</html>

Linking and controller function of directive which is represented as an attribute of another directive don't work

Have the following problem. I want to make two directives. One of them will be an attribute for another.
Something like this.
<html>
<title>Directives</title>
<head lang="en">
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js" type="text/javascript"></script>
<script src="main.js"></script>
</head>
<body ng-app="app">
<outer inner></outer>
</body>
</html>
The directive source code is here:
var app = angular.module('app', []);
app.directive('inner', function() {
return {
require: "^ngModel",
restrict: "AC",
transclude: true,
replace: false,
templateUrl: /* here is a path to template it's not interesting*/,
controller: function($scope) {
console.log('controller...');
},
link: function(scope, element, attrs) {
console.log('link...');
}
};
});
app.directive('outer', function($q, $rootScope) {
return {
require: "^ngModel",
restrict: "E",
replace: true,
scope: { /* isolated scope */ },
controller: function($scope) {},
templateUrl: /* path to template */,
link: function (scope, elem, attrs, ctrl) {}
}
});
The problem is that controller of outer works, but inner doesn't... Neither link nor controller function works... Can't understand what is wrong...
Any ideas?
The reason its not working is because both directives have been asked to render a template on the same element, and its ambiguous as to which one should be given priority.
You can fix this by giving the inner directive priority over the outer directive (higher numbers indicate higher priority).
Inner:
app.directive('inner', function() {
return {
priority:2,
restrict: "AC",
transclude: true,
replace: false,
template: "<div>{{say()}}<span ng-transclude/></div>",
controller: function($scope) {
$scope.message = "";
$scope.say = function() {
return "this is message";
};
// $scope.say(); // this doesn't work as well
console.log('controller...');
},
link: function(scope, element, attrs) {
// alert('hey');
// console.log('link...');
}
};
});
Also, both directives cannot transclude their contents. One must be 'transclude:false' and the other must be transclude:true.
app.directive('outer', function($q, $rootScope) {
return {
priority:1,
restrict: "E",
transclude:false,
scope: { /* isolated scope */ },
controller: function($scope) {
$scope.message = "";
$scope.sayAgain = function() {
return "one more message";
};
$scope.sayAgain(); // this doesn't work as well
},
template: "<div>{{sayAgain()}}</div>",
link: function (scope, elem, attrs, ctrl) {}
}
});
Here is a working fiddle:
JSFiddle

Categories

Resources