Pass scope and parameters to directive - AngularJS - javascript

I'm trying to set a class in a ng-repeat with a directive.
I need to pass a parameter to this directive: wineQty
if I use scope: { wineQty: '=' } this works however $scope.bullet1Color is undefined in my html and thus doesn't affect the class that I want.
If I use scope: '#' I get the correct class however I can't specify wineQty
Is there a way to combine theses two syntaxes? so that I can access the scope and pass a paramater?
I've tried { wineQty: '#' } but with no luck sadly.
Here's my directive
angular.module('myApp').directive('wineQtyBullets', function () {
return {
restrict: 'A',
scope: { wineQty: '=', totalBullets: '=', numRedBullets: '=', numOrangeBullets: '#', bullet1Color: '#' },
link: function (scope, element, attrs) {
// defaults
var defaultNumRedBullets = 1;
var defaultNumOrangeBullets = 2;
var defaultTotalBullets = 12;
var defaultWineQty = 0;
// set values from attributes
var numRedBullets = scope.numRedBullets ? scope.numRedBullets : defaultNumRedBullets;
var numOrangeBullets = scope.numOrangeBullets ? scope.numOrangeBullets : defaultNumOrangeBullets;
var totalBullets = scope.totalBullets ? scope.totalBullets : defaultTotalBullets;
var wineQty = scope.wineQty ? scope.wineQty : defaultWineQty;
for (var currentBullet = 1; currentBullet <= totalBullets; currentBullet++) {
var bulletColorClass = 'bg-grey';
if (currentBullet <= wineQty) {
if (currentBullet <= numRedBullets) {
bulletColorClass = 'bg-red';
}
else if (currentBullet <= (numOrangeBullets + numRedBullets)) {
bulletColorClass = 'bg-orange';
}
else {
bulletColorClass = 'bg-green';
}
}
scope["bullet" + currentBullet + "Color"] = bulletColorClass;
}
console.log(scope.bullet1Color);
}
};
}
);
Here's my html
<div class="bullets pull-left ml15 mt6" wine-qty="wine.owned_qty" wine-qty-bullets>
<ul>
<li class="bullet"
ng-class="bullet1Color"></li>
</ul>
</div>

I managed to solve the problem, by changing scope to true and accessing the parameters through attrs
If this can help anyone here's the directive:
angular.module('myApp').directive('wineQtyBullets', function () {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
var options = { };
angular.forEach([
'numRedBullets',
'numOrangeBullets',
'totalBullets',
'wineQty'
], function (key) {
if (angular.isDefined(attrs[key]))
options[key] = attrs[key];
});
// defaults
var defaultNumRedBullets = 1;
var defaultNumOrangeBullets = 1;
var defaultTotalBullets = 12;
var defaultWineQty = 0;
// set values from attributes
var numRedBullets = parseInt(options.numRedBullets) ? parseInt(options.numRedBullets) : defaultNumRedBullets;
var numOrangeBullets = parseInt(options.numOrangeBullets) ? parseInt(options.numOrangeBullets) : defaultNumOrangeBullets;
var totalBullets = parseInt(options.totalBullets) ? parseInt(options.totalBullets) : defaultTotalBullets;
var wineQty = parseInt(options.wineQty) ? parseInt(options.wineQty) : defaultWineQty;
for (var currentBullet = 1; currentBullet <= totalBullets; currentBullet++) {
var bulletColorClass = 'bg-grey';
if (currentBullet <= wineQty) {
if (wineQty <= numRedBullets) {
bulletColorClass = 'bg-red';
}
else if (wineQty <= (numOrangeBullets + numRedBullets)) {
bulletColorClass = 'bg-orange';
}
else {
bulletColorClass = 'bg-green';
}
}
scope["bullet" + currentBullet + "Color"] = bulletColorClass;
}
}
};
});

Using "=" means 2-way data binding, and it's definitely fine.
The problem might be that your link function are executed only once at the very beginning, when it's possible that the values of your attributes are not yet assigned (may be caused by some AJAX calls).
I would suggest that you wrap all your link function into a scope.$watch function. Like:
link: function (scope, element, attrs) {
scope.$watch(function() {
return {
wineQty: scope.wineQty,
totalBullets: scope.totalBullets,
numRedBullets: scope.numRedBullets,
numOrangeBullets: scope.numOrangeBullets,
bullet1Color: scope.bullet1Color
}
}, function() {
// Your original code here.
})
}
Then your final result will automatically update if your directive got new attribute values.

Related

AngularJS call directive function from the controller on data load

I have a directive function scope.automaticallySelectClosingTime(). It takes the value of first selected dropdown value and creates a list of time on the second select drop-down. It triggers on ng-change.
Directive:
.directive('closingTimeSync', function() {
return {
template: `<md-select ng-disabled="time.uiStoreOpen === false" ng-model="openCloseSet[1]">
<md-option ng-repeat="uiTime in closingTimes" ng-value="uiTime.msValue">
{{::uiTime.display}}
</md-option>
</md-select>`,
replace: true,
transclude: true,
link: function(scope) {
scope.automaticallySelectClosingTime = function(msValue) {
scope.closingTimes = scope.uiAvailableTimes;
var dayMS = 86400000 - 1;
var remainingTimings = [];
var index = scope.closingTimes.map(function(obj) {
return obj.msValue;
}).indexOf(msValue);
index = (index === scope.uiAvailableTimes.length - 1) ? 1 : index + 1;
scope.closingTimes = scope.closingTimes.slice(index, scope.uiAvailableTimes.length);
if (msValue !== dayMS) {
remainingTimings = scope.uiAvailableTimes.slice(1, index - 1);
}
scope.closingTimes = scope.closingTimes.concat(remainingTimings);
};
}
};
})
Controller .
.controller('TestCtrl', function($scope) {
init();
// CREATE AVAIABLE TIMES
$scope.uiAvailableTimes = [];
$scope.uiAvailableTimes.push({
'msValue': 0,
'display': '12:00 Morning'
});
for (var msValue = 900000; msValue <= 85500000; msValue += 900000) { // 90.000ms = 15 min, 85.500.000ms = 11:45PM
$scope.uiAvailableTimes.push({
'msValue': msValue,
'display': moment(msValue).utc().format("h:mm A")
})
}
var dayMS = 86400000 - 1;
$scope.uiAvailableTimes.push({
'msValue': dayMS,
'display': '12:00 Midnight'
});
$scope.closingTimes = $scope.uiAvailableTimes;
function init() {
$scope.uiHoursOfOperation = [] // FROM SERVER
}
});
This works fine. But I've data that coming from the server as well. That means my select fields are preselected via ng-model.
How can I call the $scope.automaticallySelectClosingTime() from the controller. Maybe inside init(). So that it also creates the list of time to second drop-down on init() function call or on page load. And I don't have to create $scope.uiAvailableTimes in the controller.
Working Example: PLUNKER
try to add scope parameter to the directive, you can use this:
.directive('closingTimeSync', function() {
return {
template: `<md-select ng-model="ngModel" ng-disabled="time.uiStoreOpen === false" ng-model="openCloseSet[1]">
<md-option ng-repeat="uiTime in closingTimes" ng-value="uiTime.msValue">
{{::uiTime.display}}
</md-option>
</md-select>`,
scope: {
ngModel: '='
},
replace: true,
transclude: true,
link: function(scope) {
scope.automaticallySelectClosingTime = function(msValue) {
scope.closingTimes = scope.uiAvailableTimes;
var dayMS = 86400000 - 1;
var remainingTimings = [];
var index = scope.closingTimes.map(function(obj) {
return obj.msValue;
}).indexOf(msValue);
index = (index === scope.uiAvailableTimes.length - 1) ? 1 : index + 1;
scope.closingTimes = scope.closingTimes.slice(index, scope.uiAvailableTimes.length);
if (msValue !== dayMS) {
remainingTimings = scope.uiAvailableTimes.slice(1, index - 1);
}
scope.closingTimes = scope.closingTimes.concat(remainingTimings);
};
}
};
})
and also you will need to add the ng-model inside the directive:
<closing-time-sync ng-model="paramFromController"></closing-time-sync>
hope that will resolve your issue.

How init a controller variable from the view? (Angular JS)

I'm working with a custom label witch controller contains a max value, this is for a horizontal bar that reperesents a percentage.
Since the max value for the horizontal bar it's defined dynamically I need to set it directly on the view, so when the ng-repeat starts, it set the caorrect max value.
Currently i have it like this (the view):
<div class="scroll">
<table class="tbody">
<tr ng-repeat="specialItem in specialItems">
<td class="tcellsmall alg_justify" title="{{specialItem.label | translate}}">
<label class='{{specialItem.class}}' ng-click="openLightbox(specialItem)">{{specialItem.label | translate}}</label>
</td>
<td class="tcell" ng-repeat="area in areas">
<span ng-if="specialItem[area] > 0">{{specialItem[area]}}</span>
<horizontal-bar type="{{item}}" value="{{specialItem[area]}}" ng-init="hValues.maxValue = specialItem.itemTotal " ng-if="specialItem[area] > 0" ng-cloak ng-click="clickMeter(area, specialItem.type, specialItem.custom)" area="{{area}}"/>
</td>
<td class="tcellsmall alg_right" ng-click="clickMeter('Plant', specialItem.type, specialItem.custom)">
<label class="cur_pointer">{{specialItem.itemTotal}}</label>
</td>
</tr>
</table>
</div>
And my controller is:
app.directive('horizontalBar', function () {
return {
restrict: 'E',
scope: {
type: '#',
value: '#',
barColor: '#',
threshold: '#', // only used by the Restriktionsverfolgung page
area: '#'
},
templateUrl: 'views/tools/horizontalBar/horizontalBar.html',
replace: true,
controller: ['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.hValues = {};
var min = 0;
$rootScope.hValues.maxValue = 0;
var max = $rootScope.hValues.maxValue;
// var max = 300;
var ranges = new AssemblyLive.Models.RangeCollection();
// Init component
$scope.valuePercent = 0;
if ($scope.value !== undefined) {
$scope.$watchCollection('value', function (newValue) {
$scope.SetValue($scope.value);
});
}
// If data is found in the Config file, load it
if (Config.Areas[$scope.type] !== undefined) {
min = Config.Areas[$scope.type].minimum;
max = Config.Areas[$scope.type].maximum;
if (Config.Areas[$scope.type].ranges !== undefined) {
for (var u in Config.Areas[$scope.type].ranges)
ranges.Add(new AssemblyLive.Models.Range(parseFloat(Config.Areas[$scope.type].ranges[u].from), parseFloat(Config.Areas[$scope.type].ranges[u].to), Config.Areas[$scope.type].ranges[u].style));
}
}
//Functions
$scope.SetColor = function (color) {
$scope.backgroundColor = color;
};
$scope.SetValue = function (value) {
value = Math.round(value);
if (value <= min) value = min;
if (value >= max) value = max;
$scope.valuePercent = value / max * 100;
$scope.color = ranges.getValue($scope.valuePercent);
// Only for the Restriktionsverfolgung page
if ($scope.threshold !== undefined) {
if ($scope.valuePercent >= Number($scope.threshold))
$scope.color = "div_error";
else if ($scope.valuePercent >= Number($scope.threshold - 2))
$scope.color = "div_warning";
else
$scope.color = "div_success";
} else if (Utils.isEmpty($scope.color)) {
if (!Utils.isEmpty($scope.barColor)) {
$scope.color = $scope.barColor;
} else {
$scope.color = "grayfill";
}
} else {
$scope.color = $scope.barColor;
}
};
// Meter was clicked
$scope.clickMeter = function () {
if ($scope.type !== '') {
if (Config.Areas[$scope.type] !== undefined && Config.Areas[$scope.type].onClickURL !== undefined) {
window.location.href = Config.Areas[$scope.type].onClickURL;
}
}
};
}],
controllerAs: 'hmCtrl',
link: function ($scope, element, attrs) {
}
};
});
As you can see in the code, I have my $rootScope variables ready for set the value on the view, but it's not working.
Do you have any idea why?
----update----
Using a plugin for check the scopes in firefox, i can see that the max value is been set correctly.
Thanks
Using a plugin for check the scopes in firefox, i can see that the max value is been set correctly.

How to pass functions from factory into controller angularJS

Right now I have this JS bin: http://jsbin.com/uhabed/64/ in which you can hopefully see the infite scroll loading of more images. These images are added when the page bottom is reached by the scroll bar on line 13 of the javascript:
angular.module('infinitescroll', []).directive('onScrolled', function () {
return function (scope, elm, attr) {
var el = elm[0];
elm.bind('scroll', function () {
if (el.scrollTop + el.offsetHeight >= el.scrollHeight) {
scope.$apply(attr.onScrolled);
}
});
};
}).controller("scrollCtrl", function($scope, getStuff){
$scope.data = getStuff;
$scope.loaddata = function(){
var length = $scope.data.length;
for(var i = length; i < length + 10; i ++){
$scope.data.push(i);
}
};
$scope.loaddata();
}).factory('getStuff',function($http) {
var images_list = ["www.google.com","www.facebook.com","www.supercuber.com","www.snappiesticker.com"];
images_list.addStuff = function(){ $http.get("http://jsbin.com/yehag/2").success(function(data){
var returned_list = JSON.parse(data.javascript);
console.log(returned_list);
for (var i=0;i<8;i++){
images_list.push(returned_list[i].name);
}
});
};
console.log(images_list);
return images_list;
});
I want to replace line 13 with $scope.loaddata = images_list.addStuff(); from the factory below. basically to use the $http function and add the data from that instead. images_list is already being returned properly seeing as the first 4 items in the output are the ones defined in the factory on line 21. However, the optomistic $scope.loaddata = images_list.addStuff(); doesn't seem to be working.
How can I pass this function up into the $scope.loaddata?
images_list is an array. You can't arbitrarily assign a property to it like images_list.addStuff
Create an object and return that object from the factory
factory('getStuff',function($http) {
var images_list = ["www.google.com","www.facebook.com","www.supercuber.com","www.snappiesticker.com"];
var addStuff = function(){....};
return{
images_list: images_list,
addStuff: addStuff
}
});
Then in controller:
$scope.data = getStuff.images_list;
$scope.loaddata = getStuff.addStuff
This is not a clean way of having an array-like object that has additional properties on it, however the above answer that you 'cannot add functions onto an array' is incorrect. While creating array-like objects is kind of messy and should be avoided where possible. If you feel it is absolutely necessary, I would handle it like this (this is similar to how jQuery does it.
function ImagesList($http) {
this.push.apply(this, [
"www.google.com",
"www.facebook.com",
"www.supercuber.com",
"www.snappiesticker.com"
]);
this._$http = $http;
}
ImagesList.prototype = {
push: [].push,
splice: [].splice,
pop: [].pop,
indexOf: [].indexOf,
addStuff: function () {
this._$http.get("http://jsbin.com/yehag/2").then(function(data){
var returnedList = angular.toJson(data.javascript);
for (var i=0; i<8; i++) {
this.push(returnedList[i].name);
}
}.bind(this));
}
};
angular
.module('infinitescroll', [])
.service('imageList', ImagesList);
.directive('onScrolled', function () {
return {
scope: {
onScrolled: '&'
},
link: function (scope, elm, attr) {
var el = elm[0];
// Original implementation will end up causing memory leak because
// the handler is never destroyed. Use as follows
elm.on('scroll.iScroll', function () {
if (el.scrollTop + el.offsetHeight >= el.scrollHeight) {
scope.$apply(attr.onScrolled);
}
}).on('$destroy', function () {
elm.off('.iScroll');
});
}
};
}).controller("scrollCtrl", function($scope, imageList){
$scope.data = imageList;
$scope.loaddata = function(){
var length = $scope.data.length;
for(var i = length; i < length + 10; i++){
$scope.data.push(i);
}
};
$scope.loaddata();
})

How to define multiple similar directives?

I need a couple of directives performing input field cleanup and validation, just like in this question. All of them are the same except for the cleanup and validation functions themselves and the field name. Currently, I'm copying it like
angular.module('myModule')
.directive('validateFoo', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attrs, ngModel) {
// THESE THREE LINES SHOULD BE ARGUMENTS
var isValid = isValidFoo;
var clean = cleanFoo;
var name = "foo";
element = $(element);
var cleanAndValidate = function(x) {
var y = clean(x);
var ok = isValid(y);
ngModel.$setValidity(name, ok);
return y;
};
ngModel.$parsers.push(cleanAndValidate);
var fix = function() {
var x = element.val();
var y = clean(x);
if (x===y) return y;
var e = element[0];
var start = e.selectionStart;
var end = e.selectionEnd;
element.val(y);
var delta = y.length - x.length;
e.setSelectionRange(start + delta, end + delta);
return y;
};
element.keyup(function() {
fix();
});
}
};
})
which is obviously a bad idea. I guess I should be able to do it using a closure, but I'd also like to preserve the overall structure (all my files start with angular.module followed by a definition). If I had access to the directive name in the body, I could get the three variables from their defining object.
All of them are the same except for the cleanup and validation
functions themselves and the field name
I think you need to add a scope to your custom directive; then you can pass in the functions and field that need to be processed. Something like this:
.directive('validateFoo', function() {
return {
restrict: 'A',
require: 'ngModel',
scope : {
// DEFINE These Arguments in the scope
isvalid : "=isvalid",
clean : "=clean",
name : "=name"
}
link: function($scope, element, attrs, ngModel) {
element = $(element);
// modify this method to access your clean/isvalid/name values in the $scope
var cleanAndValidate = function(x) {
var y = $scope.clean(x);
var ok = $scope.isValid(y);
ngModel.$setValidity($scope.name, ok);
LOG name, x, y, ok
return y;
};
ngModel.$parsers.push(cleanAndValidate);
var fix = function() {
var x = element.val();
var y = clean(x);
if (x===y) return y;
var e = element[0];
var start = e.selectionStart;
var end = e.selectionEnd;
element.val(y);
var delta = y.length - x.length;
e.setSelectionRange(start + delta, end + delta);
return y;
};
element.keyup(function() {
fix();
});
}
};
})
When you use the directive, you can pass in the function and values, sort of like this:
<validate-foo isvalid="isValidFoo" clean="cleanfoo" name="foo" />

How do I write an Angular directive to transform text to mark-up

I have this template
<span highlight>{{player.Name}}</span>
and this directive
wcApp.directive('highlight', function() {
return {
restrict : 'A',
link : function (scope, element, attrs) {
var nameStr = element.text();
nameStr = $.map(nameStr,function(letter){
return letter == ' ' ? ' ' : letter;
});
element.html('<span>' + nameStr.join('</span><span>') + '</span>');
}
};
});
But it's not working. I want to grab the text in the span, break-up the letters and wrap each character in a span.
What am I doing wrong?
The problem is the binding, element haven't been compiled yet, there is diffrent way to solve this (prioritize you directive after compile, binding & compiling it yourself with the compileProvider)
wcApp.directive('highlight', function() {
return {
restrict : 'A',
link : function (scope, element, attrs) {
var nameStr = element.text();
alert(nameStr); // {{player.Name}}
nameStr = $.map(nameStr,function(letter){
return letter == ' ' ? ' ' : letter;
});
element.html('<span>' + nameStr.join('</span><span>') + '</span>');
}
};
});
Alternative solution:
<span highlight="player.name"></span>
...and js
wcApp.directive('highlight', function() {
return {
restrict : 'A',
scope: {highlight: "="},
link : function (scope, element, attrs) {
var nameStr = scope.highlight;
// alert(nameStr); // not {{player.Name}}
nameStr = $.map(nameStr,function(letter){
return letter == ' ' ? ' ' : letter;
});
element.html('<span>' + nameStr.join('</span><span>') + '</span>');
}
};
});
simpulton have written a very good blog post about this worth reading:
http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
try
.directive('highlight', function() {
return {
restrict : 'A',
template: '<span ng-repeat="l in letters">{{ l.letter }}</span>',
link : function (scope, element, attrs) {
var nameStr = attrs.highlight;
nameStr = nameStr.split("");
var map = [];
var i=0, len=nameStr.length;
for (; i<len; i++) {
map.push({letter: nameStr[i] = nameStr[i].replace(" ", " ")});
}
scope.letters = map;
}
};
});

Categories

Resources