// 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

{{#xif " name == 'Sam' && age === '12' " }} BOOM {{else}} BAMM {{/xif}}

*/ 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' } )

Url: {{x " \"hi\" + name + \", \" + window.location.href + \" <---- this is your href,\" + " your Age is:" + window.parseInt(this.age, 10) "}}

OUTPUT:

Url: hi Sam, http://example.com <---- this is your href, your Age is: 20

*/ 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 ]); });