I have troubles to find a solution to make a fully dynamic directive. I use the angular-gridster library to make an overview of tiles for a dashboard page. I want to dynamicly load some specific tiles by a flexible directive.
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles">
<div class="box" ng-controller="tileGrid_TileController">
<eg-tile template-url={{tile.tileEngine.tempUrl}}
controller-name={{tile.tileEngine.tileCtrl}}>
</eg-tile>
</div>
</li>
</ul>
</div>
I have created the egTile directive :
(function () {
function implementation() {
return {
restrict: 'E',
transclude: true,
scope: true,
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: function (elem, attrs) {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
};
}
var declaration = [implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
This directive will work if I use a fixed string in the egTile directive like
<eg-tile template-url="string_url" controller-name= "string_ctrl"></eg-tile>
but I want to dynamicly select the controller and templateUrl.
I already tried to use the $parse and $observe service but without succes.
Is this even possible to make the directive so flexible ?
Thanks in advance
I found a solution for my problem.....
I used 2 extra directives that will provide the controller-string and the templateUrl-string for the "flexible directive" egTile.
One for creating the controller-string :
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99999,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-controller'))(scope);
elem.removeAttr('eg-parse-controller');
elem.attr('controller-name', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseController', declaration);
}());
And one for creating the template-string:
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99998,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-template'))(scope);
elem.removeAttr('eg-parse-template');
elem.attr('template-url', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseTemplate', declaration);
}());
Than I can use it as :
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles" ng-controller="tileGrid_TileController">
<eg-tile tile="tile"
eg-parse-template=tile.tileEngine.tempUrl
eg-parse-controller='tile.tileEngine.tileCtrl'>
</eg-tile>
</li>
</ul>
with the directive definition :
(function () {
function implementation($parse) {
return {
restrict: 'E',
transclude: true,
scope: {
tile : '='
},
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: 'app/module/tileGrid/view/tileTemplate.html',
link: function(scope, element, attrs) {
scope.getContentUrl = function() {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
}
};
}
var declaration = ['$parse' , implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
With a tileTemplate.html :
<div class="box">
<div class="box-header">
<h3>{{ vm.tile.tileParams.title }}</h3>
<div class="box-header-btns pull-right">
<a title="Remove widget" ng-click="vm.removeTile(tile)">
<i class="fa fa-trash"></i>
</a>
</div>
</div>
<div class="box-content">
<div ng-include="getContentUrl()"></div>
</div>
</div>
With this aproach I have fully access to the tile that I passed to the egTile Directive in the dynamic loaded controllers and dynamic loaded views.
All remarks are welcome.
Related
Maybe the title of the question is not clear.
What I want to do is to create a directive that I can use like this:
<menu-item title="var1" state="var2">
<menu-item title="var3" state="var4">
</menu-item>
<menu-item title="var5" state="var6">
<menu-item title="var7" state="var8">
</menu-item>
</menu-item>
</menu-item>
And the directive menuItem will have to generate the proper html of course.
The answer was as easy as adding transclude: true' to the directive and then addng-transclude` to the template of this directive where you want the children to be rendered:
(function () {
'use strict';
angular.module('myModule')
.directive('menuItem', menuItem);
menuItem.$inject = [];
function menuItem() {
var menuItemTemplate =
'<li>\
<a ui-sref="{{state ? state : "javascript:;"}}">\
<span class="title"> {{label}} </span>\
</a>\
<ul ng-transclude class="sub-menu">\
</ul>\
</li>';
var directive = {
restrict: 'E',
replace: true,
template: menuItemTemplate,
transclude: true,
scope: {
state: '#',
label: '#'
},
link: function (scope, element, attrs) {
}
};
return directive;
}
})();
Now we use it like I mentioned in the question
So, I have this piece of code using transclude in angular.
angular.module('playground', []).directive('tagA', function() {
return {
replace: true,
transclude: true,
controller: function() {
console.log('controller tagA')
},
compile: function($element, $attrs, $link) {
console.log('compile tagA', $element.html());
return {
pre: function($scope, $element, $attrs, controller, $transclude) {
console.log('pre tagA', $element.html());
},
post: function($scope, $element, $attrs, controller, $transclude) {
console.log('post tagA', $element.html());
}
}
},
template: '<u><tag-b><div ng-transclude></div></tag-b></u>'
}
}).directive('tagB', function() {
return {
require: '^tagA',
transclude: true,
controller: function() {
console.log('controller tagB')
},
compile: function($element, $attrs, $transclude) {
console.log('compile tagB', $element.html());
return {
pre: function($scope, $element, $attrs, controller, $transclude) {
console.log('pre tagB', $element.html());
},
post: function($scope, $element, $attrs, controller, $transclude) {
console.log('post tagB', $element.html());
}
}
},
template: '<h1 ng-transclude></h1>'
}
});
And the markup
<tag-a>
Test
</tag-a>
What I am trying to do is to pass the transcluded content from the parent (tagA) to the child (tagB). The above code works but I don't want to have <div ng-transclude></div> in my tagA's template. Apparently, using <u><tag-b ng-transclude></tag-b></u> doesn't work.
Can someone explain why the later (<u><tag-b ng-transclude></tag-b></u>) doesn't work?
The latter form doesn't work, because transclude replaces the inner HTML of the element that ng-transclude is placed on.
In the first, (working you have something like the following):
<tag-a>
<u>
<tag-b>
<div ng-transclude>
<h1 ng-transclude>
Test
</h1>
</div>
</tag-b>
</u>
</tag-a>
since the inner html is replaced, what you end up with is:
<u>
<div>
<h1>
Test
</h1>
<div>
</u>
In the second example, you have:
<tag-a>
<u>
<tag-b ng-transclude>
<h1 ng-transclude>
Test
</h1>
</tag-b>
</u>
</tag-a>
again, with the Directives removed and the inner html replaced, you get:
<u>
</u>
I can't load controller's parent in a directive. I have two directives: h-menu and h-menu-item. h-menu uses a controller, and h-menu-item requires that controller.
But h-menu directive has terminal = true, and with this I can't load controller. When I set terminal to false, I can load the controller.
JsFiddle: http://jsfiddle.net/gspVe/4/
html:
<div ng-app="test">
<h-menu>
<h-menu-item>
</h-menu-item>
</h-menu>
</div>
Here is the code js:
angular.module("test", [])
.controller("hMenu", function () {
this.msg = "controller was loaded";
return this;
})
.directive("hMenu", function($compile) {
return {
restrict: "E",
// comment this and controller will be loaded
terminal: true,
controller: "hMenu",
require: "hMenu",
link: function (scope, element, attrs) {
var ul = $("<ul/>");
ul.append(element.children());
$compile(ul)(scope);
element.replaceWith(ul);
}
};
})
.directive("hMenuItem", function($compile) {
return {
restrict: "E",
terminal: true,
require: "?^hMenu",
link: function (scope, element, attrs, controller) {
var li = $("<li/>");
if (controller)
li.html(controller.msg);
else
li.html("contoller not loaded!");
$compile(li)(scope);
element.replaceWith(li);
}
};
})
Just trying to understand what you're trying to do. If you want to replace HTML elements, why not use some of the facilities that a directive provides already?
For instance, it looks like you're trying to replace the directive elements with <ul> and <li>. Rather than define this as you've indicated, why couldn't you do the following:
angular.module("test", [])
.directive("hMenu", function() {
return {
restrict: "E",
transclude : true,
replace : true,
template : "<ul ng-transclude></ul>"
};
})
.directive("hMenuItem", function() {
return {
restrict: "E",
replace : true,
transclude : true,
template : '<li ng-transclude></li>'
};
})
Then, you can use the power of Angular to indicate menu items declaratively, for example:
<div ng-app="test">
<h-menu ng-init="menuitems=['Menu #1', 'Menu #2', 'Menu #3']">
<h-menu-item ng-repeat="m in menuitems">
{{m}}
</h-menu-item>
</h-menu>
</div>
This would show:
Menu #1
Menu #2
Menu #3
I've updated a jsfiddle at http://jsfiddle.net/gspVe/9/
I couldn't get why this doesn't work. I thought dot-notation on ng-if makes the 2 way data binding but I could not change the parent $scope.modal.active within ng-if scope.
template/lupus/modal/modal.html:
<div class="lupus-modal">
<img ng-src="{{lupusModalImg}}" class="img-responsive"/>
<div ng-if="modal.active" class="backdrop modal-backdrop" ng-click="modal.active = false" > //this ng-click doesn't work as I expected
<img ng-src="{{lupusModalImg}}"/>
</div>
</div>
lupusModal directive:
.directive('lupusModal', [
function () {
return {
scope: {
lupusModalImg: '='
},
restrict: 'AE',
transclude: true,
templateUrl: 'template/lupus/modal/modal.html',
controller: function ($scope, $element, $attrs, $transclude) {
$scope.modal = {
active: false,
close: function () {
$scope.modal.active = false;
$scope.$apply();
}
};
$element.on('click', function () {
$scope.modal.active = true;
$scope.$apply();
});
},
link: function (scope, iElement, iAttrs, backdropCtrl) {
//backdropCtrl.$element.$scope.slideElm[0].getBoundingClientRect();
}
};
}
]);
index.html:
If i understand the question correctly, you are creating isolated scope in your directive, which does not inherit from its parent scope.
To use the modal.active set a two way binding using = in scope declaration in directive
scope: {
lupusModalImg: '=',
modal:'='
},
and declare your directive using
<a href="javascript:void(0);" lupus-modal lupus-modal img="'img/certificates/UygunlukSertifikasi1.jpg'" modal='modal'></a>
I want to know how to invoke a function residing in controller on the ng-click event of template element. I have to use this directive in many pages.Hence I need to handle the click event in respective controller pages.The below code invokes the click function (moreitemdetails) residing within the directive.I tried setting the scope as moreitemdetails: '=' . It is also not working.
I have been using the directive
app.directive('groceryList', function){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};
});
The call for directive is
<grocery-list array="items"></grocery-list>
This is the template file
<div ng-click="listItemClick($event)">
<div>
<div class="item">
<span class="item-details">
{{array[0].Item}}
</span>
<span class="down-arrow"></span>
</div>
<div class="next-items">
<ul>
<li class="item" ng-repeat="list in array">
<div class="item-details" ng-click="moreitemdetails(list.Name,list.Type)">{{list.Item}}</div>
</li>
</ul>
</div>
Is there a way to get around?
I also would like to know the use of $location within another directive. Quoting the previous example (everythin is same except the directive definition and action in moreitemdetails() )
app.ui.directive('groceryList', ['$location', function(location){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
$location.path('/home/');
}
}
};
}]);
Thanks in advance
So by declaring
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
you are creating an isolated scope for your directive. One solution might be to not declare this scope in your directive. This would mean that your ng-click="moreitemdetails(list.Name,list.Type) would trigger the function on your controllers scope.
Alternatively you could use an emit and listener. To do this, in your directive you could call:
scope.moreitemdetails=function(name,type){
var deets = {
name: name,
type: type
};
scope.emit('moreitemdetails',deets)
}
Then in your various controllers you implement:
scope.$on('moreitemdetails',function(event,details){
// do some code with your name and type
}
I'm not sure exactly what you would like to know about $location, if you could be a bit more specific I might be able to help a more.
Hope this helps in some way!
EDIT:
The directive without any scope declared would look like this:
return {
restrict: 'E',
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};