-
-
Save akhoury/9118682 to your computer and use it in GitHub Desktop.
| // 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; | |
| // switched to Function construction instead of with+eval thanks to @Sayene and @GoldraK | |
| try { | |
| // create function returning expression; | |
| var func = new Function(...Object.keys(context), 'return ' + expression); | |
| // call function passing context values | |
| result = func(...Object.values(context)); | |
| } catch(e) { | |
| console.warn('•Expression: {{x \'' + expression + '\'}}\n•JS-Error: ', e, '\n•Context: ', context); | |
| } | |
| // No more `with`, everyone uses `strict mode` these days | |
| // Commenting this out, I will leave it in here for those of you who live on the edge. | |
| // // 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 are 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]); | |
| }); |
What's the license to use this? MIT? Thanks for sharing this!
This is wonderful. Thank you.
@bgashler1 MIT it is
This is great. Is there anyway to replace the eval/with statements to avoid a Parser Error when using strict mode?
I've asked the question in SO: https://stackoverflow.com/questions/50827130/extended-if-handlebars-helper-without-with-statement
This is the most legit handlebars helper from what i've seen.
I believe this is working according to the comments below, however, I found it difficult to run it properly on my express application
I have this code:
const express = require("express");
var exphbs = require("express-handlebars");
const app = express();
//this is not working
const hbs = exphbs.create({
helpers: {
xif: (expression, options) => {
return Handlebars.helpers["x"].apply(this, [expression, options])
? options.fn(this)
: options.inverse(this);
}
}
});
can some one point me on the right direction please
@edoves xif requires another helper x
Also, it is not recommend to use shorthand function syntax () => {} because the functions will get immediately bound to the context at the time they get created, and this will remain that context, and we don't want that, so i use the long syntax function () {}
const express = require("express");
var exphbs = require("express-handlebars");
const helpers = {
xif: function (expression, options) {
return helpers["x"].apply(this, [expression, options])
? options.fn(this)
: options.inverse(this);
},
x: function (expression, options) {
var result;
var context = this;
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;
}
};
const app = express();
//this is not working
const hbs = exphbs.create({
helpers
});Thanks, man. I appreciate it. What I really want to achieve is how I'm going to put a comparison of my if statement with handlebars
Here is my template sample:
`
this should work
{{#xif ' pageID == "home" ' }} jumbotron hidden-xs{{/xif}}
notice that the expression is a string, using singlequotes', but inside of it you can either use doublequotes, or single quotes with escaping.
Hi @akhoury fantastic men thank a lot but, seem like the {{ else }} part is not working
Here is my code
{{#xif ' pageID == "home" ' }} col-sm-8 {{else}} col-sm-4 text-center {{/xif}}">
@chrisross to avoid using with() and keep strict mode you may try this approach:
try {
let func = new Function(...Object.keys(context), 'return ' + expression); // create function returning expression;
result = func(...Object.values(context)); // call function passing context values
} catch(e) {
console.warn('•Expression: {{x \'' + expression + '\'}}\n•JS-Error: ', e, '\n•Context: ', context);
}@Sayene, not a bad idea at all.
@edoves can you create a snippet where the {{else}} isn't working? works fine when i test it, also which Handlebars version are you using, this is a very old helper, things might have changed since
there is one big problom, i cant use @root as a part of an input var...
•Expression: {{x '@root.methodsExist == longname'}}
•JS-Error: SyntaxError: Invalid or unexpected token
at Object.<anonymous> (/workspace/Roblox-File-Handler/dont_doc/customBlockHelpers.hbs.js:18:21)
at Object.<anonymous> (/workspace/Roblox-File-Handler/dont_doc/customBlockHelpers.hbs.js:22:8)
at Object.<anonymous> (/workspace/Roblox-File-Handler/dont_doc/customBlockHelpers.hbs.js:36:30)
at Object.wrapper (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/internal/wrapHelper.js:15:19)
at eval (eval at createFunctionContext (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:262:23), <anonymous>:10:130)
at Object.prog [as fn] (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/runtime.js:268:12)
at Object.<anonymous> (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/helpers/if.js:29:22)
at Object.wrapper (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/internal/wrapHelper.js:15:19)
at eval (eval at createFunctionContext (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:262:23), <anonymous>:21:47)
at prog (/workspace/Roblox-File-Handler/node_modules/handlebars/dist/cjs/handlebars/runtime.js:268:12)
•Context: {
id: 'Instance',
longname: 'Instance',
name: 'Instance',
kind: 'class',
scope: 'global',
description: 'Instance is the base class for all classes in the Roblox class hierarchy. Every other class that the Roblox engine defines inherits all of the members of Instance.',
thisvalue: undefined,
customTags: [ { tag: 'shortdescription', value: 'The main Baseclass' } ],
meta: {
lineno: 9,
filename: 'Instance.js',
path: '/workspace/Roblox-File-Handler/Classes'
},
order: 48
}
there are alot mor, but i dont thnk you need to see all of it
P.S: Tell me if you need to see code and hbs files...
can you create a code snippet maybe on https://codepen.io to show the error live?
Hi,
I tried the code but received this error
Uncaught (in promise) SyntaxError: Strict mode code may not include a with statement
I show with is deprecated.
do you know other system to work?
Thanks for the time.
yea you have to disabled strict mode to have with working.
I rewrite this functión to delete with and can use strict mode
Handlebars.registerHelper("x", function (expression, options) {
let result;
// Create a new object to hold variables from the context
let contextVars = {};
let context = this;
// Copy relevant properties from the context to the new object
for (var key in context) {
if (context.hasOwnProperty(key) && !(key in options)) { // Avoid overriding options
contextVars[key] = context[key];
}
}
let contextValues = Object.values(contextVars);
// Create a function with the context variables exposed as local variables
var func = new Function(...Object.keys(contextVars), 'return ' + expression);
try {
result = func(...contextValues);
} catch (e) {
console.warn('•Expression: {{x \'' + expression + '\'}}\n•JS-Error: ', e, '\n•Context: ', context);
}
return result;
});That works too! good idea
@GoldraK it seems that @Sayene did almost the same thing a few years back and I totally forgot! thank you both! Updated the gist to use new Function() instead
//i use your plugin and met some issues there is:
var data ={
test:{
firstName: 'Joan',
age: '21',
email: '[email protected]',
lastName:'check',
},
};
//and template is:
{{x "'Hi ' + firstName"}}, {{x 'lastName'}}
{{#xif "@key=='test'"}}
<span>good</span>
{{else}}
not good
{{/xif}}
{{#xif 'parseInt(age) >= 21'}} login here:
http://foo.bar?email={{x 'encodeURIComponent(email)'}}
{{else}} Please go back when you grow up. {{/xif}}
{{/each}}
Excellent work.thx.