How to pass parameters to restrict: 'E' directives to make template dynamic? - angularjs

I wanna rewrite some html into a directive but I'm in the dark when it comes to directives that use a dynamic template. Basically what I need to do is being able to pass parameters to the template so that I can re-use it with different values.
Now I've just passed parameters to the directive function but I know this is wrong. When you create directives that uses restrict: 'A' you can pass parameters like so:
<div error="somevalue"></div>
Which would then be available in the attrs. But how does it work when it uses restrict: 'E'?
How can I achieve what I want here?
forumApp.directive('error', [function(row, isRowAvailable, isRowUnchanged) {
return {
restrict: 'E',
template: '<div ng-if="row.model === "' + row + '">
<span class="error">*</span>
<div class="error-cell-inner" ng-if="row.model === 'row' && user[' + row + '].length > 0">
<span ng-if="' + isRowUnchanged + '"></span>
<span ng-if="' + isRowAvailable + ' && !' + isRowUnchanged + '" class="available">The name is available</span>
<span ng-if="!' + isRowAvailable + ' && !' + isRowUnchanged + '" class="error weight-override">The name is already taken</span>
</div>
</div>'
}
}]);

This helps in understanding how custom directives work in a easy way.
http://www.ng-newsletter.com/posts/directives.html
Try something on these lines.
Here I am using AE which means I am allowing this directive to be used as Attribute or element. But you can restrict it to just E too.
<rb-add-button text="Add New Parcel" ng-disabled="!storeNumber" ng-click="addParcel()"></rb-add-button>
appRedB.directive('rbAddButton', function () {
return {
restrict: 'AE',
replace: true,
scope: {
text: '#'
},
template: '<button class="btn btn-primary btn-sm"><i class="fa fa-plus-circle"></i> {{ text || "Add"}}</button>'
};
});

Related

Angular js - Disable toggle switch

(using angular and typescript)
I have a toggle switch directive as:
return {
restrict: 'E',
scope: {
ngModel: '=',
onText: '#',
offText: '#',
title: '#',
},
template:
'<label class="switch-light well">' +
' <input type="checkbox" ng-model="ngModel">' +
' <span>' +
' {{title}}' +
' <span class="switch-light-option" ng-class="{ \'switch-light-checked\': !ngModel}">{{offText}}</span>' +
' <span class="switch-light-option" ng-class="{ \'switch-light-checked\': ngModel}">{{onText}}</span>' +
' </span>' +
' <a class="btn btn-primary"></a>' +
'</label>'
};
In my html I am using this as:
toggle-switch ng-model="myValues" on-text="Enabled" off-text="Disabled"
Now I want to disable the above one. How can I do so.
I am pretty new to angular so any inputs would be appreciated.
Thanks
Use:
<toggle-switch
ng-model="switchStatus"
is-disabled="true">
</toggle-switch>
http://jumplink.github.io/angular-toggle-switch/
try adding the link code to your directive :
link: function ($scope, element, attrs) {
$scope.somethingDisabled = true;
};
and in your html part ng-disabled = somethingDisabled
then all you have to do is just add to the link function how and when you want to disabled either by passing it as an directive attr value or in the controller scope.
makes sense?
EDIT2
as per request I did something that would just show a proof of concept...
here is the plunker..
plunker
link: function ($scope) {
$scope.myclick = function(elem){
setTimeout(function(){$scope.disabled = true;},0);
setTimeout(function(){
$scope.disabled=false;
alert('db call done');
},2000);
};
}
I fixed the buggyness code might still be kinda ugly but Ill leave it to you to prettify it... the second timeout is the db call... first on is pushing the disable to the end of the stack so you'll see the animation.
if you want you can also just use !ngModel in the ng-disabled and then change the value of the ngModel when the db call come back..
good luck
I have the same issue and I used disabled attribute:
<toggle-switch label-text="'Enable Internet Cash'" label-position="'top-center'" disabled="MyBankAccount.HasMoney"></toggle-switch>
the disabled attribute receive a Boolean expression. If the disabled attribute has true value it will disable the toogle-switch , in other case it will enable the toggle-switch.
consider that in js file there is $scope.MyBankAccount and $scope.MyBankAccount is json object with HasMoney property.

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

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);

Transcluding Attributes in an AngularJS Directive

I was creating a select replacement directive to make it easy to style up selects according to the design without having to always right a bunch of markup (i.e. the directive does it for you!).
I didn't realize that attributes don't transclude to where you put ng-transclude and just go to the root element.
I have an example here: http://plnkr.co/edit/OLLntqMzbGCJS7g7h1j4?p=preview
You can see that it looks great... but there's one major flaw. The id and name attributes aren't being transferred. Which, ya know, without name, it doesn't post to the server (this form ties into an existing system, so AJAXing the model isn't an option).
For example, this is what I start with:
<select class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
...this is what I want it to look like:
<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">
<span class="faux-value">{{viewVal}}</span>
<span class="icon-arrow-down"></span>
<select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
...but this is what actually happens:
<div class="faux-select my-select irrelevant-class" ng-class="{ placeholder: default == viewVal, focus: obj.focus }" name="reason" id="reason" data-anything="banana">
<span class="faux-value">{{viewVal}}</span>
<span class="icon-arrow-down"></span>
<select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
Specifically, the issue is that there's no name attribute on the select, so it doesn't actually send the data to the server.
Obviously, I can use a pre-compile phase to transfer the name and id attributes (that's what I am doing for now), but it would be nice if it would just automatically transfer all of the attributes so they can add any classes, arbitrary data, (ng-)required, (ng-)disabled attributes, etc, etc.
I tried getting transclude: 'element' to work, but then I couldn't the other attributes from the template onto it.
Note, I saw the post here: How can I transclude into an attribute?, but it looks like they just manually transfer the data, and I am aiming to get it to auto-transfer all the attributes.
You could use the compile function to access the element's attributes and build the template.
app.directive('mySelect', [function () {
return {
transclude: true,
scope: true,
restrict: 'C',
compile: function (element, attrs) {
var template = '<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">' +
'<span class="faux-value">{{viewVal}}</span>' +
'<span class="icon-arrow-down entypo-down-open-mini"></span>' +
'<select id="' + attrs.id + '" name="' + attrs.name + '" ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>';
'</select>' +
'</div>';
element.replaceWith(template);
//return the postLink function
return function postLink(scope, elem, attrs) {
var $select = elem.find('select');
scope.default = scope.viewVal = elem.find('option')[0].innerHTML;
scope.$watch('val', function(val) {
if(val === '') scope.viewVal = scope.default;
else scope.viewVal = val;
});
if(!scope.val) scope.val = $select.find('option[selected]').val() || '';
}
}
};
}]);
The compile function is returning the postLink function, there are other ways to do this, you'll find more info here.
Here is a plunker
ng-transclude transcludes the content of an element on which the directive was placed. I would have assigned the attribute to its parent div and transcluded the entire select box in the template:
First Approach:
http://plnkr.co/edit/fEaJXh?p=preview
<div class="form-control my-select">
<select class="irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
And remove replace option from the definition:
app.directive('mySelect', [function () {
return {
template:
'<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">' +
'<span class="faux-value">{{viewVal}}</span>' +
'<span class="icon-arrow-down entypo-down-open-mini"></span>' +
'<div ng-transclude></div>' +
'</div>',
transclude: true,
//replace: true,
scope: true,
restrict: 'C',
link: function (scope, elem, attrs) {
var $select = elem.find('select');
scope.default = scope.viewVal = elem.find('option')[0].innerHTML;
scope.$watch('val', function(val) {
if(val === '') scope.viewVal = scope.default;
else scope.viewVal = val;
});
if(!scope.val) scope.val = $select.find('option[selected]').val() || '';
}
};
}]);
Second Approach:
In your demo, just include following line at the end of the link method:
$select.attr({'id': elem.attr('id'), 'name': elem.attr('name')});

AngularJS - Create dynamic action in Super Directive

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>'
};

Resources