I'm using handlebar to create template for my backbone application and I'm using handlebar ifCond helper (source code already available). And using this helper function I can easily check between two values e.g
{{#ifCond val1 val2 operator="!="}}
{{/ifCond}}
Similarly I can use "==", ">", "<" operators.
But now I want to use &&, || operators within the if condition block, e.g.
{{#ifCond val1 && val2}}
{{/ifCond}}
As well as I want to use the mathematical operators within the if block, e.g
{{#ifCond val1+1 val2 operator=">"}}
{{/ifCond}}
Please suggest me what will be the best way to do this.
You can use the the "eval" method of Javascript to do this within you helper function.
HandleBars.registerHelper("evalExpression", function(){
var me = this, result,
args = Array.prototype.slice.call(arguments),
options = args.pop(),
params = args,
expression = options.hash.expression;
expression = expression.replace(/\#([0-9]+)/g, function(match, val){
return params[val];
});
result = eval(expression);
if(options.hash.returnBool == "true"){
if(result){
return options.fn(this)
}else{
return options.inverse(this);
}
}else{
return result;
}
})
And then in the handlebar template use:-
{{#evalExpression val1 val2 expression="#0 && #1" returnBool="true"}}
{{else}}
{{/evalExpression }}
And:
{{#evalExpression val1 1 val2 expression="(#0+#1) > #2" returnBool="true"}}
{{else}}
{{/evalExpression }}
Related
When using attribute binding in components, the data passed to the controller is always a string. I'm trying to pass an integer, however, and am having trouble converting it from a string and having the conversion stick.
I've tried saving the data as an integer in $onInit() but outside of this function, the data returns to its original state (type and value). I understand that components should not modify the data passed in as a general rule, but since this is an attribute binding, and the data is passed by value, I didn't think that applied.
function IntegerBindingController() {
this.$onInit = function() {
// Assuming 'number="2"' in the HTML
// This only changes the data inside this function
this.number = parseInt(this.number)
this.typeofNumber = typeof this.number // evaluates to 'number'
this.simpleAdd = this.number + 5 // evaluates to 7
this.dataAdd = this.numberOneWay + 5
console.log(this)
}
this.test = function() {
// this.number is a string inside this function
this.typeofNumber = typeof this.number // evaluates to 'string'
this.simpleAdd = this.number + 5 // evaluates to 25
}
}
I can solve this by copying the data to a new property on the controller, but I'm curious if someone can explain what's happening here. See this Plunker for a working example of the issue.
Passing number with '#' will always pass it as a string. If you want the object value pass number with '=' instead in the components bindings.
So:
var IntegerBindingComponent = {
controller: IntegerBindingController,
bindings: {
string: '#',
number: '=',
numberOneWay: '<'
},
template: _template
}
A decent explanation can be found here: http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
or here: Need some examples of binding attributes in custom AngularJS tags
"The '=' notation basically provides a mechanism for passing an object into your directive. It always pulls this from the parent scope of the directive..."
The solution I ended up going with was to use $onChanges to handle the bound data values. In my case, at least one of the values could potentially change after an async call in the parent component, so this made sense overall. As Prinay Panday notes above, the # binding always comes through as a string. The $onInit() method guarantees that bindings will be available, but it doesn't guarantee that they will change, so even if you change the value on the component, Angular can change it later. That's another reason why the documentation recommends copying the bound values to a local variable if you need to manipulate them at all. As for the $onChanges() solution, it would look like this
function IntegerBindingController() {
this.$onChanges(changes) {
if (changes.number && changes.number.currentValue) {
this.number = parseInt(changes.number.currentValue)
}
}
this.test = function() {
this.typeofNumber = typeof this.number // evaluates to 'number'
this.simpleAdd = this.number + 5 // evaluates to 7 (assuming this.number was 2)
}
}
I have this in the translation json file:
{
"test_key" : "Var1: {{var1}} Var2: {{var2}} Var3: {{var3}}"
}
For this to work, I need to provide var1, var2, and var 3. For example:
$translate('test_key', { var1: 1, var2: 2, var3: 3 });
The issue now is that var1, var2, var3 could be any dynamic variables. What I need now is to get all the list of dynamic variables so I can provide whatever values it may need.
Ideally, this is the goal I am trying to achieve (pseudo code)
var dynamicVars = getDynamicVarList(); // I need this, but how?
dynamicVars.forEach(function (key) {
switch (key) {
case "var1":
return 1;
case "var2":
return 2;
case "var3":
return 3;
default:
return null;
}
});
If it is Pascal Precht translate, then you set JSON file in module options, before application init. It's rather not possible with standard mechanisms. It offer JSON, but when it is loaded, then it's hard to change something, without changing source code of angular translate module.
If the reason you want this is to have many languages, then you can set many languages codes in $translate.
Another solution is to load JSON from server, which performs operations on var1, var2, var3 and hence returns static json, but you can do $http calls with commands to change variables in switch statement.
It looks somehow linguistic approach, Java is good for this. Grails may be fine framework for returning REST services.
Again, to restate the problem, the issue is that you do not know ahead of time which dynamic variables are to be used.
I solved the issue by using a customer interpolator.
So when you do
{{'testkey'|translate}}
and your lang.json has:
"testkey" :"this is number {{variable1}}"
It will get resolved to
this is number 1
Code:
app.factory('customTranslateInterpolator',
["$interpolate",
function ($interpolate) {
var $locale;
var customTranslateInterpolator = {};
/**
* This will be your custom dynamic vars resolution method
*/
var resolveExpressions = function (expressions) {
var values = {};
expressions.forEach(function (key) {
values[key] = resolveVariable(key);
});
return values;
}
/**
* The actual method for key:value mapping
*/
var resolveVariable = function(key) {
var retVal;
switch (key) {
case "variable1":
retVal = 1;
break;
default:
retVal = "";
}
return retVal;
}
customTranslateInterpolator.setLocale = function (locale) {
$locale = locale;
}
customTranslateInterpolator.getInterpolationIdentifier = function () {
return 'custom';
},
/**
* Custom interpolate
* interpolateParams will overwrite resolve expressions. This will allow developers
* to pass values to the translate directives or by using $translate service.
*
* #param {string} string the string retrieved from the language json file
* #param {object} interpolateParams the {key:value} object passed to the angular-translate features.
*/
customTranslateInterpolator.interpolate = function (string, interpolateParams) {
var result;
interpolateParams = interpolateParams || {
};
// Retrieve expressions and resolve them
var interpolatedString = $interpolate(string);
var resolvedExpressions = resolveExpressions(interpolatedString.expressions);
// Merge interpolateParams onto resolvedExpressions so that interpolateParams overwrites resolvedExpressions
angular.extend(resolvedExpressions, interpolateParams);
result = interpolatedString(resolvedExpressions);
return result;
}
return customTranslateInterpolator;
}]);
make sure you let angular-translate know you are using a custom interpolator
$translateProvider.useInterpolation('customTranslateInterpolator');
Note that you can still provide your own translate-values, and this will override whatever you have in resolveVariable()
So if you do
{{'testkey' | translate:"{variable1:'2'}"}}
It will resolve to
this is number 2
Hope this helps others.
-Lucky M.
I wrote an app to convert between decimal values and negabinary.
http://dev.golightlyplus.com/playground/negabinary/
I wrote a custom filter so I could do
{{decimal | negabinary}}
The code for it being..
var negabinaryApp = angular.module('negabinaryApp', []);
negabinaryApp.filter('negabinary', function() {
return function (decimal) {
if (isNaN(decimal)) return "not a number";
var negabinary = [];
var base = -2;
var remainder;
while(decimal != 0) {
remainder = decimal % base;
decimal = Math.ceil(decimal / base);
negabinary.push(remainder >= 0 ? remainder : -remainder);
}
return negabinary.reverse().join('');
}
});
What I'd like to be able to do is to also show the calculations on the page.
I could create an array of the calculations for each cycle through the while loop. But how do I then bind them to the HTML? Or is there a better way to go about this?
Thanks.
The actual functionality of your particular filter is distracting from the purpose of the question. To simplify, your question is: "how can I display that output of a filter that takes a string, and parses it into HTML"?
This would apply equally to your case as it would to a case where you just wrap the text in <pre>, for example.
The key, is to bind the output to ng-bind-html:
<div ng-bind-html="decimal | negabinary">
Here's a simple (and useless) example:
.filter("strongify", function(){
return function(str){
return "<strong>" + str + "</strong>";
}
});
which can be used similarly:
<div ng-bind-html="name | strongify">
Here's a plunker that breaks the text into paragraphs of <p>: http://plnkr.co/edit/RN5TqwNRRjMjynwInNyn?p=preview
Note, that you will also need to add ngSanitize dependency or otherwise do $sce.trustAsHtml on the output.
According to this the "currency" filter takes amount as the first parameter given the following syntax:
{{ currency_expression | currency : amount : symbol}}
But in the following example it never passed the amount as a parameter:
<span id="currency-default">{{amount | currency}}</span>
I'm assuming that amount in the example refers to the currency_expression in the syntax as written in the documentation. They could have written it in the documentation in this way:
{{ currency_expression | currency : symbol}}
Another example is the filter filter with the following syntax:
{{ filter_expression | filter : array : expression : comparator}}
But in the following example it never specified the "source array" parameter:
<tr ng-repeat="friendObj in friends | filter:search:strict">
I'm assuming that friendObj in friends in the example refers to the filter_expression and search refers to the array if we're going to follow the syntax as written in the documentation. They could have written it in the documentation in this way:
{{ filter_expression_that_returns_array | filter : expression : comparator}}
I'm not so sure if I'm missing something but the documentation doesn't make sense to me given their examples.
My question is, should I simply ignore what the documentation says that the first parameter must be the input?
For what it's worth (since by experience we already know it is like that), the source code indicates that the expression only needs to be before the |.
Using the source for version 1.2.16 and without going into much detail:
ng/parse.js#L103
// In the OPERATORS hash:
var OPERATORS = {
...
'|': function (self, locals, a, b) {
return b(self, locals)(self, locals, a(self, locals));
},
...
ng/parse.js#L579
// `Parser`'s `filter()` method:
Parser.prototype = {
...
filter: function() {
var token = this.expect();
var fn = this.$filter(token.text);
var argsFn = [];
while (true) {
if ((token = this.expect(':'))) {
argsFn.push(this.expression());
} else {
var fnInvoke = function(self, locals, input) {
var args = [input];
for (var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
}
return fn.apply(self, args);
};
return function() {
return fnInvoke;
};
}
}
},
...
So, what did we learn ?
if ((token = this.expect(':'))) {
argsFn.push(this.expression());
The parser will get all tokens after the filter (separated by :) and put them in an array (argsFn).
var args = [input];
for (var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
}
At "runtime" (when the actual filtering is hapenning) an new array will be created (args) which will contain input (but what is input ? more on that later) and each parameter token that was previosuly stored in argsFn.
return fn.apply(self, args);
This args array will be the arguments list to the filtering function.
So, args contains input and the tokens after filter_name (as in expression | filter_name : param1 : param2).
If we can convince ourselves that input is indeed the expression (appearing on the left of the |, then we should be convinced that there is no need to have the expression appear as the first parameter after filter_name.
var fnInvoke = function(self, locals, input) {
...
return function() {
return fnInvoke;
};
filter() returns an anonymous function that when executed returns the function fnInvoke.
input is the third argument assed to fnInvoke when it is executed.
Now lets get back to the | operator:
'|': function (self, locals, a, b) {
...
It will result in calling this anonymous function with a and b being the left-hand side (expression) and the right-hand side (filter_name:param1:param2) respectively.
(In fact a and b are not the left- and right-hand sides, but they are functions that when executed return the result of evaluating the left- and right-hand sides in a given context (i.e. scope).
return b(self, locals)(self, locals, a(self, locals));
This tells us that the function returned by calling the anonymous function returned by filter() (b(self, locals)) will be executed with the following arguments:
`self`, `locals`, `a(self, locals)`
Which means that the mysterious input parameter (remember it was the 3rd argument of fnInvoke ?) is a(self, locals).
And a(self, locals) is basically the result of evaluating the left-hand side argument of the | operator in the context of the current scope, e.g. the result of evaluating a string ('someExpression') to the value of the property in the current scope ($scope.someExpression).
I don't know if you are convinced (I don't think I would have been).
I left much detail out of the explanation, but the interested reader can delve into that source and convince themselves :)
I feel kind of bad for posting such a longish answer with so little practical value. Sigh...
Personally, I would always read the source code when in doubt.
For your <span id="currency-default">{{amount | currency}}</span> example:
https://github.com/angular/angular.js/blob/master/src/ng/filter/filters.js#L50
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(amount, currencySymbol){
if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
replace(/\u00A4/g, currencySymbol);
};
}
Looks to me like amount then currency, so input must be first parameter.
Update.
Source for filter in HTML Template Binding context.
https://github.com/angular/angular.js/blob/master/src/ng/filter/filter.js#L116
function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
First if is a check for array, aka input, as first parameter.
I'm writing a custom Angular filter that randomly capitalizes the input passed to it.
Here's the code:
angular.module('textFilters', []).filter('goBananas', function() {
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(Math.random())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});
I call it in my view like so:
<a class='menu_button_news menu_button' ng-href='#/news'>
{{"News" | goBananas}}
</a>
It works, but in my console I'm seeing a rootScope:infdig (infinite digest) loop.
I'm having some trouble understanding why this is happening and what I can do to resolve it. If I understand correctly, this is due to the fact that there are more than 5 digest actions called by this function. But the input is only called once by the filter, right?
Any help appreciated.
The problem is that the filter will produce a new result every time it is called, and Angular will call it more than once to ensure that the value is done changing, which it never is. For example, if you use the uppercase filter on the word 'stuff' then the result is 'STUFF'. When Angular calls the filter again, the result is 'STUFF' again, so the digest cycle can end. Contrast that with a filter that returns Math.random(), for example.
The technical solution is to apply the transformation in the controller rather than in the view. However, I do prefer to transform data in the view with filters, even if the filter applies an unstable transformation (returns differently each time) like yours.
In most cases, an unstable filter can be fixed by memoizing the filter function. Underscore and lodash have a memoize function included. You would just wrap that around the filter function like this:
.filter('myFilter', function() {
return _memoize(function(input) {
// your filter logic
return result;
});
});
Since digest will continue to run until consistent state of the model will be reached or 10 iterations will run, you need your own algorithm to generate pseudo-random numbers that will return the same numbers for the same strings in order to avoid infinite digest loop. It will be good if algorithm will use character value, character position and some configurable seed to generate numbers. Avoid using date/time parameters in such algorithm. Here is one of possible solutions:
HTML
<h1>{{ 'Hello Plunker!' | goBananas:17 }}</h1>
JavaScript
angular.module('textFilters', []).
filter('goBananas', function() {
return function(input, seed) {
seed = seed || 1;
(input = input.split('')).forEach(function(c, i, arr) {
arr[i] = c[(c.charCodeAt(0) + i + Math.round(seed / 3)) % 2 ? 'toUpperCase' : 'toLowerCase']();
});
return input.join('');
}
});
You can play with seed parameter to change a bit an algorithm. For example it may be $index of ngRepeat
Plunker: http://plnkr.co/edit/oBSGQjVZjhaIMWNrPXRh?p=preview
An alternative, if you want the behaviour to be truly random, is to do deal with the randomness only once during linking by creating a seed, and then use a seeded random number generator in the actual filter:
angular.module('textFilters', []).filter('goBananas', function() {
var seed = Math.random()
var rnd = function () {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(rnd())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});