Nested Directives - angularjs

I have two directives one which translates strings and one which creates a container with a title. (and some other functionality that was removed to make the example shorter)
Groupbox:
myapp.directive('groupbox', function () {
return {
restrict: 'E',
priority: 200,
template:
'<fieldset>' +
'<legend data-translate>{{title}}</legend>' +
'<div data-ng-transclude></div>' +
'</fieldset>',
transclude: true,
replace: true,
scope: {title: '#'}
};
});
Translate: (also simplified)
myapp.directive('translate', ['$filter', function ($filter) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var e = $(element);
var data = attrs.translate;
var results = $filter('I')(e.html(), data);
e.html(results.text);
if (results.tooltip) e.attr('data-tooltip', results.tooltip);
}
};
}]);
I use it like this:
<groupbox title='settings'>
content
</groupbox>
The idea is that the content of the "groupbox" gets put in the div and the title in the "legend". After this the legend needs to be translated by the translate directive. This translation does not happen (it just prints settings). When i replace '{{title}}' with 'settings' it does get translated.
How can i get the translate directive to operate on the results of the groupbox directive.

I fixed it by adding a compile function that puts the title in the legend directly (without binding). That way it is no different then any other use of my translate directive.
myapp.directive('groupbox', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
template:
'<fieldset>' +
'<legend>' +
'<span data-translate></span> - ' +
'<a data-ng-click="open = !open" data-translate>toggle</a>' +
'</legend>' +
'<div data-ng-show="open" data-ng-transclude></div>' +
'</fieldset>',
compile: function(element, attrs) {
element.children('legend').children('span').html(attrs.title);
}
};
});

Related

ng-repeat inside a directive template - access index

I have the following problem:
I have a custom directive, which shows one or more tables, by using ng-repeat inside the template string. Inside each table, several other custom directives are placed. I want these to know the index of the element used, but cant manage to get this done. My code looks like this now:
.directive('myStuffText', function ($rootScope){
return {
restrict: 'A',
require: '^form',
replace: true,
scope: true,
template:
......
'<table border="1" ng-repeat="elt in myModel.newStuffList">
......
'<tr>' +
'<td colspan="3"><div my-add-some-editor my-element-index="$index"/></td>' +
'</tr>'
'</table>',
link: function (scope, elt, attrs){
scope.cockpitPolicyModel.newPolicyList = [];
}
};
})
Independently from how I try, I always get the string $index or {{$index}} in the template function of the my-add-some-editor directive, not the value of it..
Edit - added the nested directive:
.directive('myAddSomeEditor', function($rootScope){
return {
restrict: 'A',
require: '^form',
scope: true,
template: function ($scope, $attr){
return
.....
'<span id="myAddSomeEditor" name="myAddSomeEditor" class="form-control" my-generic-editor '+
'my-input-mapping="myModel.someText"></span>'
.....
;
}
};
})
That probably happens because in your my-add-some-editor directive you have this definition in the isolate scope:
myElementIndex: '#'
That's why you're getting the literal string of what you're writing there in the HTML.
Change that to:
myElementIndex: '='
EDIT: Since you said you're not using isolated scope, try this in the parent directive: try doing my-element-index="{{$index}}". And this in the child directive's link function:
link: function (scope, elem, attr) {
attr.$observe('index', function(val) {
scope.index = val;
});
}

How to make a template repeat

I have a directive, I want that template to show all the rows from data set
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<tr><td>{{Name}} </td><td>{{Surname}}</td></tr>',
replace: true,
link: function(scope, elm, attrs) {
var dat= TestFactory.dataReturn();
for (var i = 0; i < dat.length; i++) {
scope.Name = dat[i].Name;
scope.Surname = dat[i].Surname;
console.log(dat[i]);
}
// alert("hah");
}
};
}]);
How can I make it repeat like ng-repeat ?
Assuming your service returning a promise. Here is the simple code to repeat your data in table.
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<table ng-repeat="person in persons"><tr><td>{{person.Name }} </td><td>{{person.Surname}}</td></tr></table>',
replace: true,
link: function(scope, elm, attrs) {
TestFactory.dataReturn().then(function(resp){
scope.persons = resp.data;
});
}
};
}]);
Alternatively, you could redefine your element directive to represent a single object, and then ng-repeat the directive itself. Obviously, this would require moving the location where you use the factory. Your application architecture may or may not be able to accommodate this change.
directive:
app.directive('exampleDirective', [function () {
return {
restrict: 'E',
template: '<tr><td>{{person.Name}} </td><td>{{person.Surname}}</td></tr>',
replace: true,
scope: {
person: '='
}
};
}]);
And usage:
<example-directive ng-repeat="data in dataFromFactory" person="data"></example-directive>

Angular Directive Compile function: replace is ignored, and compiled content is nested

Directive:
app.directive('myCarousel', function ($compile) {
return {
restrict: 'E'
, transclude: false
, replace: true
, scope: true
, compile: function compile($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.html(html)
return function ($scope) {
$compile($element.contents())($scope);
}
}
};
})
Usage:
<my-carousel>
<li>Todd</li>
<li>Andrej</li>
</my-carousel>
Output
<my-carousel class="ng-scope">
<div id="carousel-1" class="rn-carousel-container ng-scope" style="width: 1600px;">
<div id="carousel-2" class="rn-carousel-container" style="width: 1600px;">
<blah>
</div>
</div>
</my-carousel>
The problems I have (at least known problems
1 - I still have the my-carousel element, why didn't the replace remove it? Do I need to do this myself because I am writing the compile function? HOw would I go about that?
2 - for some reason it looks like the rn-carousel inner directive is getting compiled inside of itself? This could very well be my lack of understanding on this inner directive on how it works. But does it look like anything is terribly wrong with this compile function?
When you use the compile function, you are expected to do DOM manipulation by yourself. You can achieve the same effect as replace: true using replaceWith:
compile: function($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.replaceWith(html);
return function(scope) {
/*probably don't need to compile element contents here*/
}
}
But... It doesn't look like you need to use compile at all. By giving the directive a template, and using transclusion, you can accomplish the same thing:
app.directive('myCarousel', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: true,
template: '<ul rn-carousel="" rn-carousel-indicator="" ng-transclude></ul>'
};
})
Current working solution (albeit without replacing the parent element):
eido.directive('myCarousel', function ($compile) {
return {
restrict: 'E'
, transclude: false
, replace: true
, scope: true
, compile: function compile($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.html(html)
return function ($scope) {
}
}
};
})

Directive scope unreachable

I am trying to "share" a scope between two directives as follow:
toolbarActions.directive('toolbarActions', function (toolbar) {
return {
restrict: 'E',
scope: true,
replace: true,
link: function (scope, element, attrs) {
scope.toolbarActions = toolbar.getActions();
},
template: "<div class='centered-div-container'>" +
"<toolbar-action ng-repeat='toolbarAction in toolbarActions' icon-source='{{toolbarAction.icon}}'></toolbar-action>>"+
"</div>",
};
});
The inner directive looks like this:
toolbarActions.directive('toolbarAction', function () {
return {
restrict: 'E',
scope: {
iconSource: '&'
},
replace: true,
link: function (scope, element, attrs) {
scope.imageUrl = attrs.iconSource;
},
template: "<div class='centered-div-content header-control' ng-click='actionFunction()'>" +
"<img ng-src='{{imageUrl}}' /> "+
"</div>",
};
});
In the following simple HTML:
<div class="header-content">
<toolbar-actions></toolbar-actions>
</div>
However, no matter what I do, I can't make the icon-source retrieve the correct value (toolbarAction.icon), but rather an exception is thrown:
Error: [$parse:syntax] Syntax Error: Token 'toolbarAction.icon' is unexpected, expecting [:] at column 3 of the expression [{{toolbarAction.icon}}] starting at [toolbarAction.icon}}]. http://errors.angularjs.org/1.2.0-rc.2/$parse/syntax?p0=toolbarAction.icon&p1=is%20unexpected%2C%20expecting%20%5B%3A%5D&p2=3&p3=%7B%7BtoolbarAction.icon%7D%7D&p4=toolbarAction.icon%7D%7D minErr/<#http://localhost:9000/bower_components/angular/angular.js:78
I've tried many versions of replacing the scope definition on the toolbarAction directive (such as:)
scope:true
or
scope:false
And tried many transculsion combinations as well.
What am I doing wrong?
I think the best solution in your case is to use the $parse service, remove the scope of your toolbarAction directive and watch for any modification of the parsed attribute.
In toolbarActions directive replace {{toolbarAction.icon}} by toolbarAction.icon only :
template: "<div class='centered-div-container'>" +
"<toolbar-action ng-repeat='toolbarAction in toolbarActions' icon-source='toolbarAction.icon'></toolbar-action>"+
"</div>"
And your toolbarAction directive becomes something like :
.directive('toolbarAction', function ($parse, toolbar) {
return {
restrict: 'E',
replace: true,
link: function (scope, element, attrs) {
var getImgURL = $parse(attrs.iconSource); //get the raw json path and create a function out of it
//Get notified when the value is reversed
scope.$watch(getImgURL, function(value) {
//Finally store the real value to be used in your directive template
scope.imageUrl = value;
});
},
template: "<div class='centered-div-content header-control' ng-click='actionFunction()'>" +
"<img ng-src='{{imageUrl}}' /> "+
"</div>",
};
});
I have assembled a working plunker accessible here with this you should be all set :)

What's the right way to use filters with transclusion in directives?

I'm trying to apply a filter to the transcluded text in my directive but I'm not sure what's the best way to do this. A working copy of idea is at the following link but I feel I'm sorting of cheating by using the compile function to get at the transcluded text. See JSFiddle.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
phrase: '#',
text: '#'
},
template: '<span ng-bind-html-unsafe=" text | highlight:phrase:false "></span>',
compile: function (elem, attr, transclude) {
transclude(elem, function (clone) {
// grab content and store in text attribute
var txt = clone[0].textContent;
attr.text = txt;
});
}
};
});
Other way to do would be http://jsfiddle.net/3vknn/, I guess.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
scope: {
phrase: '#'
},
controller: function($scope, $filter) {
$scope.highlight = $filter('highlight');
},
link: function (scope, elem, attr) {
scope.$watch('phrase', function(phrase) {
var html = scope.highlight( elem.text(), phrase );
elem.html( html );
});
}
};
});

Resources