Skip to content

Instantly share code, notes, and snippets.

@dhamaso
Forked from akhoury/handlebars-helper-x.js
Created June 15, 2020 15:19
Show Gist options
  • Save dhamaso/bd3cd60c56c92709a6e3f1448ea3325f to your computer and use it in GitHub Desktop.
Save dhamaso/bd3cd60c56c92709a6e3f1448ea3325f to your computer and use it in GitHub Desktop.
Handlebars random JavaScript expression execution, with an IF helper with whatever logical operands and whatever arguments, and few more goodies.
// for detailed comments and demo, see my SO answer here http://stackoverflow.com/questions/8853396/logical-operator-in-a-handlebars-js-if-conditional/21915381#21915381
/* a helper to execute an IF statement with any expression
USAGE:
-- Yes you NEED to properly escape the string literals, or just alternate single and double quotes
-- to access any global function or property you should use window.functionName() instead of just functionName()
-- this example assumes you passed this context to your handlebars template( {name: 'Sam', age: '20' } ), notice age is a string, just for so I can demo parseInt later
<p>
{{#xif " name == 'Sam' && age === '12' " }}
BOOM
{{else}}
BAMM
{{/xif}}
</p>
*/
Handlebars.registerHelper("xif", function (expression, options) {
return Handlebars.helpers["x"].apply(this, [expression, options]) ? options.fn(this) : options.inverse(this);
});
/* a helper to execute javascript expressions
USAGE:
-- Yes you NEED to properly escape the string literals or just alternate single and double quotes
-- to access any global function or property you should use window.functionName() instead of just functionName(), notice how I had to use window.parseInt() instead of parseInt()
-- this example assumes you passed this context to your handlebars template( {name: 'Sam', age: '20' } )
<p>Url: {{x " \"hi\" + name + \", \" + window.location.href + \" <---- this is your href,\" + " your Age is:" + window.parseInt(this.age, 10) "}}</p>
OUTPUT:
<p>Url: hi Sam, http://example.com <---- this is your href, your Age is: 20</p>
*/
Handlebars.registerHelper("x", function(expression, options) {
var result;
// you can change the context, or merge it with options.data, options.hash
var context = this;
// yup, i use 'with' here to expose the context's properties as block variables
// you don't need to do {{x 'this.age + 2'}}
// but you can also do {{x 'age + 2'}}
// HOWEVER including an UNINITIALIZED var in a expression will return undefined as the result.
with(context) {
result = (function() {
try {
return eval(expression);
} catch (e) {
console.warn('•Expression: {{x \'' + expression + '\'}}\n•JS-Error: ', e, '\n•Context: ', context);
}
}).call(context); // to make eval's lexical this=context
}
return result;
});
/*
if you want access upper level scope, this one is slightly different
the expression is the JOIN of all arguments
usage: say context data looks like this:
// data
{name: 'Sam', age: '20', address: { city: 'yomomaz' } }
// in template
// notice how the expression wrap all the string with quotes, and even the variables
// as they will become strings by the time they hit the helper
// play with it, you will immediately see the errored expressions and figure it out
{{#with address}}
{{z '"hi " + "' ../this.name '" + " you live with " + "' city '"' }}
{{/with}}
*/
Handlebars.registerHelper("z", function () {
var options = arguments[arguments.length - 1]
delete arguments[arguments.length - 1];
return Handlebars.helpers["x"].apply(this, [Array.prototype.slice.call(arguments, 0).join(''), options]);
});
Handlebars.registerHelper("zif", function () {
var options = arguments[arguments.length - 1]
delete arguments[arguments.length - 1];
return Handlebars.helpers["x"].apply(this, [Array.prototype.slice.call(arguments, 0).join(''), options]) ? options.fn(this) : options.inverse(this);
});
/*
More goodies since you're reading this gist.
*/
// say you have some utility object with helpful functions which you want to use inside of your handlebars templates
util = {
// a helper to safely access object properties, think ot as a lite xpath accessor
// usage:
// var greeting = util.prop( { a: { b: { c: { d: 'hi'} } } }, 'a.b.c.d');
// greeting -> 'hi'
// [IMPORTANT] THIS .prop function is REQUIRED if you want to use the handlebars helpers below,
// if you decide to move it somewhere else, update the helpers below accordingly
prop: function() {
if (typeof props == 'string') {
props = props.split('.');
}
if (!props || !props.length) {
return obj;
}
if (!obj || !Object.prototype.hasOwnProperty.call(obj, props[0])) {
return null;
} else {
var newObj = obj[props[0]];
props.shift();
return util.prop(newObj, props);
}
},
// some more helpers .. just examples, none is required
isNumber: function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
daysInMonth: function (m, y) {
y = y || (new Date).getFullYear();
return /8|3|5|10/.test(m) ? 30 : m == 1 ? (!(y % 4) && y % 100) || !(y % 400) ? 29 : 28 : 31;
},
uppercaseFirstLetter: function (str) {
str || (str = '');
return str.charAt(0).toUpperCase() + str.slice(1);
},
hasNumber: function (n) {
return !isNaN(parseFloat(n));
},
truncate: function (str, len) {
if (typeof str != 'string') return str;
len = util.isNumber(len) ? len : 20;
return str.length <= len ? str : str.substr(0, len - 3) + '...';
}
};
// a helper to execute any util functions and get its return
// usage: {{u 'truncate' this.title 30}} to truncate the title
Handlebars.registerHelper('u', function() {
var key = '';
var args = Array.prototype.slice.call(arguments, 0);
if (args.length) {
key = args[0];
// delete the util[functionName] as the first element in the array
args.shift();
// delete the options arguments passed by handlebars, which is the last argument
args.pop();
}
if (util.hasOwnProperty(key)) {
// notice the reference to util here
return typeof util[key] == 'function' ?
util[key].apply(util, args) :
util[key];
} else {
log.error('util.' + key + ' is not a function nor a property');
}
});
// a helper to execute any util function as an if helper,
// that util function should have a boolean return if you want to use this properly
// usage: {{uif 'isNumber' this.age}} {{this.age}} {{else}} this.dob {{/uif}}
Handlebars.registerHelper('uif', function() {
var options = arguments[arguments.length - 1];
return Handlebars.helpers['u'].apply(this, arguments) ? options.fn(this) : options.inverse(this);
});
// a helper to execute any global function or get global.property
// say you have some globally accessible metadata i.e
// window.meta = {account: {state: 'MA', foo: function() { .. }, isBar: function() {...} } }
// usage:
// {{g 'meta.account.state'}} to print the state
// or will execute a function
// {{g 'meta.account.foo'}} to print whatever foo returns
Handlebars.registerHelper('g', function() {
var path, value;
if (arguments.length) {
path = arguments[0];
delete arguments[0];
// delete the options arguments passed by handlebars
delete arguments[arguments.length - 1];
}
// notice the util.prop is required here
value = util.prop(window, path);
if (typeof value != 'undefined' && value !== null) {
return typeof value == 'function' ?
value.apply({}, arguments) :
value;
} else {
log.warn('window.' + path + ' is not a function nor a property');
}
});
// global if
// usage:
// {{gif 'meta.account.isBar'}} // to execute isBar() and behave based on its truthy or not return
// or just check if a property is truthy or not
// {{gif 'meta.account.state'}} State is valid ! {{/gif}}
Handlebars.registerHelper('gif', function() {
var options = arguments[arguments.length - 1];
return Handlebars.helpers['g'].apply(this, arguments) ? options.fn(this) : options.inverse(this);
});
// just an {{#each}} warpper to iterate over a global array,
// usage say you have: window.meta = { data: { countries: [ {name: 'US', code: 1}, {name: 'UK', code: '44'} ... ] } }
// {{geach 'meta.data.countries'}} {{this.code}} {{/geach}}
Handlebars.registerHelper('geach', function(path, options) {
var value = util.prop(window, arguments[0]);
if (!_.isArray(value))
value = [];
return Handlebars.helpers['each'].apply(this, [value, options]);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment