AngularJS - Create dynamic action in Super Directive - angularjs

I have a super directive made up of 2 directives. The second "child" directive is a add-new open dialog control:
Here is the plunker:
http://plnkr.co/edit/b6G2y3yqjhxpu059ZrWB
If you examine the super directive "selectAddNew", third line from the bottom, you will see this code:
<div txt-add-new text="{{text}}" action="openDialog(\'Front\')" style="display: inline-block"></div>
The problem is action="openDialog(\'Front\')" is hard coded.
The super directive's html is this:
<select-add-new select-model="$parent.selectedFrontAxle" text="add new"
select-phrase="Front Axle Type" preselected-filter="Front"
label-name="Front Axle" open-dialog="Front" <------ need to pass this value
select-options="axleTypes" var-ctrl="AxleTypesCtrl"></select-add-new>
I can live with the method OpenDialog, if I have too, but the paramater \'Front\', needs to get it's value from this part of the Html above:
open-dialog="Front"
Initially I tried this (making it an method action):
open-dialog="openDialog('Front')"
With this in my directive:
.directive('', function(){
..........
scope: {
open-dialog: "&"
},
......
},
template: .....
'<div txt-add-new text="{{text}}" action="openDialog()" style="display: inline-block">
......
};
But I found myself in an endless loop when reviewing the code in Chrome console

Mark I solved it, code below:
<select-add-new select-model="$parent.selectedFrontAxle" text="add new"
select-phrase="Front Axle Type" preselected-filter="Front"
label-name="Front Axle" dialog-param="openDialog('Front')"
select-options="axleTypes" var-ctrl="AxleTypesCtrl"></select-add-new>
.directive('selectAddNew', function () {
return {
replace: true,
restrict: "E",
scope: {
selectModel: "=",
selectOptions:"=",
labelName: "#",
preselectedFilter: "#",
selectPhrase: "#",
text: "#",
},
compile: function(tElement, attrs) {
var div = tElement.find('#ctrlId');
div.attr('ng-controller', attrs.varCtrl);
var div2 = tElement.find('#OpenWindow');
div2.attr('action', attrs.dialogParam);
},
template: '<div>' +
'<div class="local-label">{{labelName}}: </div>' +
'<name-value-select-control select-phrase="{{selectPhrase}}" selected-item="selectModel" preselected-filter="{{preselectedFilter}}" options="selectOptions"></name-value-select-control>' +
'<div id="ctrlId">' +
'<div id="OpenWindow" txt-add-new text="{{text}}" style="display: inline-block"></div>' +
'</div>' +
'</div>'
};

Related

How to use ng-show and ng-hide in an angularjs custom directive's template?

I have searched all day for an answer and cannot seem to get this right.
I have a scope value profile_edit_mode being set to either true or false by clicking an edit button. This value then activates the ng-hide and ng-show on the form.
when I move this into a custom directive the ng-hide or ng-show won't work.
I have the following.
HTML Code
<bos-input fieldname="firstname" title="First Name" place-holder="First Name" ng-model="user_profile_details.firstname" edit-mode="{{profile_edit_mode}}"></bos-input>
Custom directive.
enter code hereangular.module('aesthetics')
.directive('bosInput', function(){
return {
restrict: 'E',
require: 'ngModel',
template: '<div class="form-group">' +
'<label for="{{fieldname}}" class="col-lg-3 control-label">{{title}}</label>' +
'<div class="col-lg-9">' +
'<input ng-show="editMode" class="form-control" id="{{fieldname}}" ng-model="ngModel" placeholder="{{placeHolder}}">' +
'<div class="m-t-xs" ng-hide="editMode">{{ngModel}}</div>' +
'</div>' +
'</div>{{editMode}}',
scope: {
fieldname: '#',
placeHolder: '#',
title: '#',
editMode: '#',
ngModel: '='
}
}
})
The form input is rendering correctly and everything else is working except the ng-show or ng-hide.
I have outputted the value of the editMode to check it is getting updated correctly and it is.
Any help will be greatly appreciated.
Many thanks
Brent
Instead of using #, which passes profile_edit_mode as a string, use =, which will two way bind the actual value.
<bos-input ... edit-mode="profile_edit_mode"></bos-input>
Directive
.directive('bosInput', function(){
return {
// Previous code...
scope: {
fieldname: '#',
placeHolder: '#',
title: '#',
editMode: '=',
ngModel: '='
}
}
})
This works because when you use # and pass a string, 'false' still evaluates as truthy.

How can I make a directive accept some text that I pass in from inside an element?

I would like to create a directive to replace some code in my HTML.
Here's what I have right now:
<div class="gridFooter" ng-show="home.dataRetrieved">
<span ng-show="(home.grid.data).length">{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}</span>
<span ng-show="!(home.grid.data).length">There are no tests that match your selection criteria</span>
</div>
I created this basic directive but there are things missing:
app.directive('adminGridFooter', function () {
return {
template: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" >xx</span >\
</div>'
};
});
How can I make it so I can pass in the string "xx" inside the element when I call the directive and will my directive just assume the current scope so that the home.dataRetrieved will work without change? Something like
Another question. Howe can I make the directivecompletely replace my call to it <admin-grid-footer></admin-grid-footer>. How can I make it so it replaces the element?
You are looking for transclude and replace:
app.directive('adminGridFooter', function () {
return {
replace:true,
transclude:true,
template: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" ng-transclude></span >\
</div>'
};
});
PLUNKER
you can change your directive like this.
angular.module('onboardingApp').directive("adminGridFooter",function () {
return {
restrict: 'E',
link: function(scope, element, attributes) {
scope.customMessage = attributes["custommessage"];
},
templateUrl: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" >{{customMessage}}</span >\
</div>',
};
});
then pass the value that u want in html
<admin-grid-footer customMessage="what ever you want"></admin-grid-footer>
At first, you can use templateUrl property and point it to separate HTML file instead of writing the whole HTML as a string.
The second, you can restrict the directive type by element, so you can use it only as an element (not as attribute nor as class). Here is how to do that: restrict: 'E'.
Finally, you can also specify a link function where you can get the attributes of your element and do whatever you need.
So, after these changes your code may look like this:
app.directive('adminGridFooter', function () {
return {
restrict: 'E',
templateUrl: 'adminGridFooter.html', // this contains your HTML
link: function(scope, element, attrs) {
scope.xx = attrs.xx;
}
}
});
And you can use it like this:
<adminGridFooter xx="someValue"></adminGridFooter>
And the last question:
...and will my directive just assume the current scope so that the home.dataRetrieved will work without change?
YES, by default it uses the scope where the directive was called, BUT you can filter scope variables and only use some of them, which you need inside of your directive. You can achieve this using isolated scopes.
Also, I strongly recommend to read about directives to have a basic knowledge and then continue with them.
The official documentation is a good starting point.
set the scope property to true, So the scope will be accessible in directive.
or
You can pass the data as an attribute.
app.directive('adminGridFooter', function() {
return {
restrict: 'E',
replace: true,
scope: true,
template: '<div class="gridFooter" ng-show="home.dataRetrieved" >\
<span ng-show = "home.grid.data.length > 0" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="home.grid.data.length === 0">There are no tests that match your selection criteria</span>\
</div>'
};
});
Or
scope:{
home:'='
}
PLUNKER

AngularJS : after isolating scope and binding to a parentFunction, how to pass parameters to this parentFunction?

I'm trying very hard to understand scopes in AngularJS and am running into problems.
I've created a simple "comments" application that
has an input box for publishing a comment (text + 'Reply' button) [this is working fine]
clicking 'Reply' button unhides another input box for publishing a reply (with a 'PublishReply' button)
clicking 'PublishReply' button, publishes the reply below the original comment and also indents it.
I generate comments within 'commentsDirective' using ng-repeat and embed a 'replyDirective' within each ng-repeat. I'm able to bind the parent scope's functions from the child directive's isolated scope, but I'm just not able to pass the arguments to this function.
Again, I think, a scope related problem is preventing me to hide/unhide the 'replyDirective' from the on-click of 'Reply' button.
Grateful for your help.
Here is the code in plunker: http://plnkr.co/edit/5AmlbOh6iEPby9K2LJDE?p=preview
<body ng-app="comments">
<div ng-controller="mainController">
<div class="publishComment"><input type="text" ng-model="contentForPublishing"/><button ng-click="publishComment(null, 0, contentForPublishing)">Publish Comment</button></div>
<comments-directive></comments-directive>
</div>
</body>
<script>
angular.module('comments', [])
.controller('mainController', function($scope) {
$scope.comments = [
{ id: 1, parentId: 0, content:'first comment'},
{ id: 2, parentId: 0, content:'second comment'}
];
$scope.publishComment = function (commentId, commentParentId, contentForPublishing){
if (commentId === null) {commentId = $scope.comments.length + 1;} // this (commentId === null) is sent only from the publishComments and not from publishReply
$scope.comments.push( { id: commentId, parentId:commentParentId, content:contentForPublishing } );
$scope.contentForPublishing = "";
}
$scope.replyWidgetVisible = false;
$scope.showReplyWidget = function() {
$scope.replyWidgetVisible = true;
}
})
.directive('commentsDirective', function() {
return {
restrict: 'E',
// template: '<div id="{{comment.id}}" class="commentWrapper" ng-class="{{ {true: '', false: 'indentLeft'}[{{comment.parentId}} === 0] }}" ng-repeat="comment in comments">' +
template: '<div id="{{comment.id}}" class="commentWrapper" ng-repeat="comment in comments">' +
'id: {{comment.id}} parentId: {{comment.parentId}}<br>>> {{comment.content}}<br>' +
'<button class="reply" ng-click="showReplyWidget()">Reply</button>' +
// '<reply-directive publish-reply="publishComment()" ng-show="{{replyWidgetVisible}}" reply-widget-visible="replyWidgetVisible"></reply-directive>' +
'<reply-directive publish-reply="publishComment()" comments-array="comments"></reply-directive>' +
'</div>'
};
})
.directive('replyDirective', function() {
return {
restrict: 'E',
scope: {
publishReply: '&',
commentsArray: '=',
replyWidgetVisible: '='
},
template: '<div class="publishComment"><input type="text" ng-model="contentForPublishing"/><button ng-click="publishReply(5, 1, contentForPublishing)">Publish Reply</button></div>'
};
});
</script>
Basically you need to "fetch" the publishComment function, since with publish-reply="publishComment()" you are telling Angular to call publishComment without any arguments, regardless of the arguments you are passing on your isolated scope. So, to actually reach the publishComment function (and not only the predefined executing function), so you can pass in arguments, you need to:
.directive('commentsDirective', function() {
return {
restrict: 'E',
template: '<div id="{{comment.id}}" class="commentWrapper" ng-repeat="comment in comments">' +
'id: {{comment.id}} parentId: {{comment.parentId}}<br>>> {{comment.content}}<br>' +
'<button class="reply" ng-click="showReplyWidget()">Reply</button>' +
'<reply-directive publish-reply="publishReply()" comments-array="comments"></reply-directive>' +
'</div>',
link: function(scope){
scope.publishReply = function(){
return scope.publishComment;
}
}
};
})
.directive('replyDirective', function() {
return {
restrict: 'E',
scope: {
publishReply: '&',
commentsArray: '=',
replyWidgetVisible: '='
},
template: '<div class="publishComment"><input type="text" ng-model="contentForPublishing"/><button ng-click="publishReply(5, 1, contentForPublishing)">Publish Reply</button></div>',
link: function(scope) {
scope.publishReply = scope.publishReply();
}
};
});
Think it like if you were doing: (function(){ return scope.publishComment(); })(5, 1, contentForPublishing);
Doing a "get reference to function" parent scope binding is mainly useful when the passed function is mutable. for example, my-cool-function="doThis()" and on another part of your app my-cool-function="doThat()". they exist so you can reuse the same directive in many situations, which isn't the case here.
A much simpler way would to $emit a publish event from your isolated scope and catch it in your comments directive. Or create a scope with true so you can access, in your newly created child scope, the function directly from the parent.
See updated plnkr in here http://plnkr.co/edit/nOWwFJ35XRXaIoxNPlW4?p=preview
Here is the plnkr showing how to keep just one reply box opened (you can keep as many open if you wish) http://plnkr.co/edit/za16eHPzltGLjK5ra1Vb?p=preview (see the revision before it for a widget state for each comment)

ng-repeat with compile or link?

I'm trying to get forms with fields created from config, with validation and other fine goods.
Here: http://plnkr.co/edit/8cP5YMKUGu6LfBCsRxAZ?p=preview is a solution, which works, but when I try to go step more - to add logic e.g (if attrs.hasOwnProperty('required') then add something to template) - i'm stucked. (to be exact - in this plunker i'd like to remove all staff connected to remove and add it only in case if field-required is true.
So I THINK (but may be wrong) that I have to use some link or compile function which prepares the template for each field.
So I produce sth like this:
KPNDirectives.directive("formField", function () {
return {
restrict: 'E',
replace:true,
scope: {
fieldModel : '=ngModel'
},
link: function (scope, element, attrs) {
var type = attrs.fieldType || 'text'
var htmlText =
'<div class="form-group" ng-form="form" ng-class="{true: \'has-error\', false: \'has-success\'}[form.'+attrs.fieldName+'.$invalid]">'+
'<label class="control-label col-sm-2" for="'+attrs.fieldName+'">'+attrs.fieldLabel+'</label>'+
'<div class="col-sm-6">'+
'<input class="form-control" type="'+type+'" placeholder="enter valid name" name="'+attrs.fieldName+'" ng-model="'+scope.fieldModel+'" ng-minlength=3 ng-maxlength=20 required/>'+
'<span class="help-block">'+
'<span ng-show="form.'+attrs.fieldName+'.$error.required">Required field</span>'+
'<span ng-show="form.'+attrs.fieldName+'.$error.minlength">Too few chars - min is (6)</span>'+
'<span ng-show="form.'+attrs.fieldName+'.$error.maxlength">Too much chars - max is (20)</span>'+
' '+
'</span>'+
'</div>'+
'</div>';
element.replaceWith(htmlText);
}
}
});
But it doesn't work.
Here's plunker http://plnkr.co/edit/OZMuxzsnoVmATpeTdSW9?p=preview
Try this DEMO
There are some problems with your code
You have to define template for your directive: template:"<div></div>" and append html to it: element.append(htmlText);
Use fieldModel : '=' instead of fieldModel : '=ngModel' because you use field-model="field.model" in your html.
Use ng-model="fieldModel" instead of ng-model="'+scope.fieldModel+'"
You need to compile your html using $compile service: $compile(element.contents())(scope);

AngularJS error when setting class in directive

I'm creating a directive that renders a list of content from a method passed in via an attribute. In addition to passing a method to get the data from, it accepts two attributes to set a class on the ul and li elements rendered in the list. When I set the class on the li element, it works fine. When I try to set a class on the ul, chaos ensues: the ng-repeat no longer works as expected and it throws an exception "undefined is not a function"
Here's a simplified version of the directive that has the issue.
var myApp = angular.module('myApp',[]).
directive('contentList',function(){
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
method:'&',
ulclass: '#',
liclass: '#'
},
template: '<div class="content-list-wrapper"> ' +
'<div class="content-loader" ng-show="loading"><i>Loading...</i></div>' +
'<ul class="content-list {{ulclass}}">' +
'<li class="content-list-repeat {{liclass}}" ng-repeat="item in items" ng-transclude></li>' +
'</ul>' +
'</div>',
link: function(scope,iElement,iAttrs){
scope.items = scope.method();
}
} ;
});
If you remove {{ulclass}} in the class attribute of the ul element, everything works. I'm baffled why it works for the li, but not the ul.
I've setup a jsfiddle at http://jsfiddle.net/woogychuck/wqU63/

Resources