Is there a way to provide a function to Handlebars that calculates the value when needed, instead of supplying all values up-front in the context?
For example, say I wanted to fill out any template with example data:
var exampleFunction = function (varName) {
return "Example " + varName;
};
If I know ahead of time what variables a Handlebars template needs, I could use this function to assemble a context. However, what I really want to do is:
var template = Handlebars.compile(templateString);
var html = template.fillFromFunction(exampleFunction);
Is this possible? If not, then are there any other template engines that support it?
Bonus question: can this be made asynchronous, e.g.:
var template = Handlebars.compile('{{foo.bar}}');
var dataFunction = function (path, callback) {
setTimeout(function () {
callback("Async " + path);
}, 100);
};
Short version:
It's a hack, but I've got a workaround.
var oldNameLookup = handlebars.JavaScriptCompiler.prototype.nameLookup;
handlebars.JavaScriptCompiler.prototype.nameLookup = function (parent, name) {
return '(typeof ' + parent + ' === "function" ? '
+ parent + '(' + JSON.stringify(name) + ') : '
+ oldNameLookup(parent, name) + ')';
}
Usage:
var template = handlebars.compile('{{foo}} {{bar}}');
var dataFunction = function (key) {
return 'data:' + key;
};
console.log(template(dataFunction));
// Outputs: "data:foo data:bar"
Sub-properties:
To enable sub-properties (e.g. "{{foo.bar}}"), we need a wrapper method:
function transformFunc(dataFunction) {
return function (key) {
if (typeof key !== 'string') {
return dataFunction('');
}
var pointerKey = '/' + key.replace(/~/g, '~0').replace(/\//g, '~1');
return transformFunc(function (path) {
return dataFunction(pointerKey + path);
});
};
}
Usage:
var template = handlebars.compile('{{foo}} {{foo.bar}}');
var dataFunction = transformFunc(function (path) {
// path is a JSON Pointer path
return "data:" + path;
});
console.log(template(dataFunction));
// Outputs: "data:/foo data:/foo/bar"
If one wanted, I suppose .compile() and .precompile() could be modified so that transformFunc() was applied to any incoming function when templating.
Explanation:
The hack includes altering the code-generation of Handlebars, but this code comment implies this is sort of OK. I tried finding a way to subclass, but couldn't see how to get this or this to use it.
Short version: override the nameLookup() method.
This method usually generates JavaScript code like depth0.foo or (depth1 && depth1.bar). We're extending it so the generated code first checks the parent to see if it's a function.
If it's a function, then it calls the function with the property name as the argument. Otherwise, it returns the same value as previously. For example, it will generate something like:
(typeof depth0 === "function" ? depth0("foo") : (depth0 && depth0.foo))
For simple variables (e.g. just "{{foo}}") you can now just supply a function and it will be called with the variable names.
Enabling sub-properties
However, for nested properties (i.e. "{{foo.bar.baz}}") we actually need our function to return another function, which can either return appropriate data or be called with another property name, depending which is needed.
So: if our transformed function is not given a string, then it is assumed we are at the end-point (we want the actual data, not a sub-property), so we just call through.
If our transformed function is given a string, then it's assumed to be a key, so another (transformed) function is return that calls back to the data function, prefixing the argument appropriately.
Related
I have been using functions as parameters. Now I need to pass a function A which requires parameters x generated by function B. I can do that too. by calling A in B with the parameters.
But my problem is, my function B accepts any kind of function, and it is not fixed. It may take function C also which requires parameter y or some function D that does not need any parameter.
Is this possible?
function B(done_function){
//some task generate some value
done_function();
}
function B(done_function){
//some task generate some value including args
done_function(args);
}
How can I make A, C and D functions execute with their arguments.
The top two examples won't work.
The normal way to handle this is to ignore it. Function B should simply not care about how other functions accept arguments. Instead it should only provide a standard and well documented interface to it's callback:
function B (done_function) {
// do some stuff to generate result
done_function(result);
}
Or if function B can possibly generate errors asynchronously then it should do done_function(err, result). Notice that all libraries do this. They don't care how you write your functions.
Now, how to pass various types of functions to B? Just wrap them around another function. For example, say you need to pass the result of B to a logger function and you need to pass a variable specifying the name of the file to log to. Just do this:
B(function(result) {
logToFile(debugLogFile, result);
});
Say for example you need to modify the result because the function you want to pass it to expect it to be in a specific format. Just do something like this:
B(function(result) {
var x = {
some_parameter: something,
result: result
};
doSomethingElse(x);
});
There is no scenario where function B needs to be aware of how you want to process the result it generates. It's you, the programmer, who is responsible to convert the result of function B appropriately before doing further processing.
You can make use of the functions call method:
function B(done_function){
//some task generate some value including args
done_function.call(done_function, args);
}
example jsfiddle
Let B call the callback with a single object as argument, which contains all information:
function B(done_function){
//some task generating some values, including args, for example:
var args = {
status: 3,
code: 'DEC',
location: 'Atlantic',
date: new Date('2017-01-01')
}
done_function(args);
}
Using ES6 destructuring in function parameters, you can filter out the information you need:
Function A could look like this:
function A({status}) {
console.log('status is ' + status);
}
B(A);
In the same way, C could look like this:
function C({code, date}) {
console.log('code is ' + code + ' on ' + date);
}
B(C);
Of course, ES6 destructuring is just a nice shortcut syntax, as you can do it also like this:
function A(response) {
console.log('status is ' + response.status);
}
B(A);
Alternative: use function's length property
If the distinction between different kinds of callbacks can be made on the basis of the number of parameters that are defined for them, then you could use the length property like this:
function B(done) {
var code = 'DEC';
var status = 1;
var location = 'Atlantic';
var date = new Date('2017-01-01');
switch (done.length) {
case 1:
done(status);
break;
case 2:
done(location, date);
break;
default:
done(code, status, location, date);
}
}
function A(status) {
console.log('status = ' + status);
}
function C(location, date) {
console.log('location = ' + location + ' on ' + date.toDateString());
}
B(A);
B(C);
Note the specific rules that apply for the length property's value.
I'm writing a whole client-side system in JavaScript. I have about 4 .js files, but there is no need to post code from them, nor the html.
Anyway, before explaining the problem, I'll try to explain what is the code about, and the code itself.
Basically, it's a finite state machine in a design level. For now, I only have 3 states: the initial, a transition and the final state (the state machine, for now, is a queue). The code is this:
var state = 0
var IntInsert = //my object that represents a namespace, or a set of functions
{
insertCtrl : function() //main function: it calls the others
{
if (lookup()) return
if (!state) state = 1
var key = document.getElementById("newegg").value
switch(state)
{
case 1:
this.preInsert(key)
break
case 2:
this.firstInsert(key)
break
}
},
preInsert : function(key)
{
$("#terminal").css("color", "green")
$("#terminal").text("Inserting element '" + String(key) + "'")
$("#t2cell" + String(cuckoohash.h2(key))).css("background-color", "White")
$("button").prop("disabled", true)
$("input").prop("disabled", true)
$("#nextstep").prop("disabled", false)
state++
},
firstInsert : function(key)
{
key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0] // key <-> t1[h1(key)]
if (!key)
{
$("#t1cell" + String(cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML))).css("background-color", "LightGreen")
this.finishedInsert()
}
},
finishedInsert : function()
{
$("#terminal").css("color", "green")
$("#terminal").text("Element '" + String(key) + "' inserted")
}
}
In the line this.firstInsert(key), a bug happens. Firebug tells me this: TypeError: this.firstInsert is not a function. This is weird, given that it's defined as a function and Firebug itself represents it as a function in it's high-level panel.
If I take the function out of the object and use it as a global function, there is no error at all. That's why I believe the whole semantics of the code can be ignored in order to answer my question, which is: why is this happening inside the object? What is it that I'm missing?
After reading the acknowledged answer, I realized it's important to state that the whole point of the code being in an object is to create a namespace for the whole state machine code I'm creating, so I definitely could put the state variable inside it. The idea is to avoid having large code base with global variables and functions.
Your issue is likely caused by how you are calling your methods. In Javascript, the value of this is determined by how a method is called, not by how it is declared. You don't show the calling code (where the problem likely is), but if you are calling one of your methods via some sort of callback, then you can easily lose the proper method call that will preserve the value of this for you. See this other answer for more info on how the value of this is determined based on how a function is called.
There are many different possible solutions.
Since your object is a singleton (there's only ever just one of them and you've given that one instance a name), I'd suggest that the simplest way to solve your problem is to just refer to the named singleton IntInsert rather than refer to this as that will solve any issues with the value of this in your methods.
You can do that like this:
var state = 0
var IntInsert = //my object that represents a namespace, or a set of functions
{
insertCtrl : function() //main function: it calls the others
{
if (lookup()) return
if (!state) state = 1
var key = document.getElementById("newegg").value
switch(state)
{
case 1:
IntInsert.preInsert(key)
break
case 2:
IntInsert.firstInsert(key)
break
}
},
preInsert : function(key)
{
$("#terminal").css("color", "green")
$("#terminal").text("Inserting element '" + String(key) + "'")
$("#t2cell" + String(cuckoohash.h2(key))).css("background-color", "White")
$("button").prop("disabled", true)
$("input").prop("disabled", true)
$("#nextstep").prop("disabled", false)
state++
},
firstInsert : function(key)
{
key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0] // key <-> t1[h1(key)]
if (!key)
{
$("#t1cell" + String(cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML))).css("background-color", "LightGreen")
IntInsert.finishedInsert()
}
},
finishedInsert : function()
{
$("#terminal").css("color", "green")
$("#terminal").text("Element '" + String(key) + "' inserted")
}
}
Some Other Comments About the Code:
Leaving out semi-colons at the end of each statement may expose you to some accidental bugs. It seems a less than desirable practice to me. The first time you see such a bug, you probably be confused for quite awhile why your code isn't working as intended too.
You can chain in jQuery so instead of this:
$("#terminal").css("color", "green");
$("#terminal").text("Element '" + String(key) + "' inserted");
You can use this:
$("#terminal").css("color", "green").text("Element '" + String(key) + "' inserted");
In Javascript, you rarely need to explicitly cast to a string with String(key), so instead of this:
$("#terminal").text("Element '" + String(key) + "' inserted");
You can use this:
$("#terminal").text("Element '" + key + "' inserted");
Adding anything to a string will auto-convert that to a string for your automatically.
You went to the trouble to make a singleton namespace object IntInsert, yet you declare your variable state that is not in that namespace object. It seems you should do one of the other, not some odd combination of the two. You either want relevant things in your namespace object or not and such a generically named variable as state is ripe for a naming conflict. I would think it should be in your object.
It is odd to use a mixture of jQuery selectors like $("#terminal") and document.getElementById("newegg"). Code is usually cleaner if you stick with one model or the other. If you're using jQuery for other reasons, then code like:
var key = document.getElementById("newegg").value;
can be this in jQuery:
var key = $("#newegg").val();
In .firstInsert(), you declare an argument named key, but then in the first line of code in that method, you assign to key so the code is just misleading. You declare an argument, but are never using that argument if it is passed in. You should either use the argument or change the key variable to be just a local variable declaration, not a function argument.
In .finishedInsert(), you refer to a variable named key, but there is no such argument or variable by that name.
In .firstInsert(), this line of code is just bizarre:
var key = [document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML, document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key][0];
You appear to be declaring an array with two elements in it, then assigning just the first element to key. The second element in the array is an assignment to a .innerHTML property. This is simply a weird and misleading way to write this code. It should be broken up into multiple lines of code.
The first line of code in .firstInsert() appears to be referring to some sort of global definition of a variable named key which does not appear anywhere in your code. That is likely a bad idea. The key you use here should either be in your namespace object or should be passed as an argument to .firstInsert(xxx).
Here's a version of your code with as many of those items above fixed/modified and FIXME comments where things are still unexplained:
//my object that represents a namespace, or a set of functions
var IntInsert = {
//main function: it calls the others
state: 0,
insertCtrl: function () {
if (lookup()) return;
if (!IntInsert.state) IntInsert.state = 1;
var key = $("#newegg").val();
switch (IntInsert.state) {
case 1:
IntInsert.preInsert(key);
break;
case 2:
IntInsert.firstInsert(key);
break;
}
},
preInsert: function (key) {
$("#terminal").css("color", "green").text("Inserting element '" + key + "'");
$("#t2cell" + cuckoohash.h2(key)).css("background-color", "White");
$("button").prop("disabled", true);
$("input").prop("disabled", true);
$("#nextstep").prop("disabled", false);
IntInsert.state++;
},
firstInsert: function () {
// key <-> t1[h1(key)]
// FIXME here: There is no variable named key anywhere
document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML = key;
if (!key) {
$("#t1cell" + cuckoohash.h1(document.getElementById("t1").rows[cuckoohash.h1(key)].cells[0].innerHTML)).css("background-color", "LightGreen");
IntInsert.finishedInsert();
}
},
finishedInsert: function () {
// FIXME here: There is no variable named key anywhere
$("#terminal").css("color", "green").text("Element '" + key + "' inserted");
}
};
I think you are having issue with what this is when you are calling your function. You have not shown how you are calling the function but most probably when you log this before that line, it won't be your IntInsert object but may be global Window object
The reason you are seeing the error is most likely because at runtime 'this' is changing to an object that doesn't have your function. Try restructuring your code and "closing" on this like this:
var IntInsert = function()
{
var self = this;
...
var insertCtrl = function() //main function: it calls the others
{
...
case 2:
self.firstInsert(key)
break
...
}
...
var firstInsert = function(key)
{
...
}
return {
insertCtrl: insertCtrl,
firstInsert: firstInsert
}
}
... or something similar that would fit your needs.
I am using breeze to communicate with Web.API 2.1
In my backend I save some values as a list of strings (instead of saving one-to-many relations). In the front end I want to break these values, edit them, put them back together and persist them to the DB.
emailsString is the actual property that is persisted to the DB and exists in the model.
fullName acts as an "interface" to reading and modifying the first and last name properties.
I have the following:
function registerUserProfile(metadataStore) {
metadataStore.registerEntityTypeCtor('UserProfile', profile, profileInitializer);
function profile() {
this.fullName = '';
this.emails = [];
}
function profileInitializer(newItem) {
if (!newItem.emailsString || newItem.emailsString.length === 0) newItem.emails = [{ email: '' }];
}
Object.defineProperty(profile.prototype, 'fullName', {
get: function() {
var fn = this.firstName;
var ln = this.lastName;
return ln ? fn + ' ' + ln : fn;
},
set: function (value) {
var parts = value.split(' ');
this.firstName = parts.shift();
this.lastName = parts.shift() || '';
}
});
Object.defineProperty(profile.prototype, 'emailsString', {
get: function () {
return objectToStringArray(this.emails, 'email');
},
set: function (value) {
this.emails = stringToObjArray(value, 'email');
}
});
function objectToStringArray(objectArray, objectValueKey) {
var retVal = '';
angular.forEach(objectArray, function (obj) {
retVal += obj[objectValueKey] + ';';
});
if (retVal.length > 0)
retVal = retVal.substring(0, retVal.length - 1); //remove last ;
return retVal;
}
function stringToObjArray(stringArray, objectValueKey) {
var objArray = [];
angular.forEach(stringArray.split(';'), function (str) {
var item = {};
item[objectValueKey] = str;
objArray.push(item);
});
return objArray;
}
If I modify the emailString value and call saveChanges on breeze nothing happens. If I modify the fullName property ALL changes are detected and saveChanges sends the correct JSON object for saving (including emailString value).
From what I understand, overriding the emailString property I somehow break the change tracking for this property. fullName is not a mapped property and thus is not overriding anything so it works. Am I going the correct way? If so is there a way to notify breeze that the overriden property has changed?
In general, Breeze takes over each property on an object and insures that internally it is informed about any changes to each property. How this is done is different depending on whether you are using Angular, Knockout or Backbone ( or a custom modelLibrary adapter).
But if you plan on modifying the property yourself to do something similar you need to insure that breeze is still getting notified.
Based on your posted code I'm assuming that you are using Angular. In that case you first need to determine whether your code is getting executed before or after Breeze's code.
My guess is that if you make your changes early enough then Breeze will be able to wrap them successfully. However, if your changes occur after Breeze's then you need to insure that Breeze's code is invoked as well. Debugging into the source for this is probably your best bet. And the Breeze Angular adapter is a good source as a example of how to wrap a property that might already be wrapped with another defineProperty.
Take a look at the snippet below. Is there any function I could write in replace of ... to generate the route, that could reused in another function? Something like var route = this.show.fullyQualifiedName perhaps?
var services = {
'github.com': {
api: {
v2: {
json: {
repos: {
show: function(username, fn) {
var route = ...;
// route now == 'github.com/api/v2/json/repos/show'
route += '/' + username;
return $.getJSON('http://' + route).done(fn);
}
}
}
}
}
}
}
No, there isn't, at least not using "reflection" style operations.
Objects have no knowledge of the name of the objects in which they're contained, not least because the same object (reference) could be contained within many objects.
The only way you could do it would be to start at the top object and work your way inwards, e.g.:
function fillRoutes(obj) {
var route = obj._route || '';
for (var key in obj) {
if (key === '_route') continue;
var next = obj[key];
next._route = route + '/' + key;
fillRoutes(next);
}
}
which will put a new _route property in each object that contains that object's path.
See http://jsfiddle.net/alnitak/WbMfW/
You can't do a recursive search, like Alnitak said, but you could do a top-down search, although it could be somewhat slow depending on the size of your object. You'd loop through the object's properties, checking to see if it has children. If it does have a child, loop through that, etc. When you reach the end of a chain and you haven't found your function, move to the next child and continue the search.
Don't have the time to write up an example now, but hopefully you can piece something together from this.
I'm receiving some 'body' content from a jquery's json call, where I can get the unique javascript element returned by doing:
script_element = $(data.body)[1]
This equals to:
<script type="text/javascript">
updater('foo', 'bar', {}, '0', constant='');
</script>
So, typeof script_element returns "object"
And, if I run script_element.innerText, I can get:
updater('foo', 'bar', {}, '0', constant='');
After receiving this script, what I'm doing right now is just run an eval on it, but searching around I couldn't get a way to run eval changing function call params.
What I'm trying to do is change the third param of the call, in this case the {}, that can change depending on the return of the json call, so I can't just search for {}.
I could also do script_element.text.split(',')[2] for example, and change this text on the fly, but I was thinking there should be a better way to do this.
I don't know if javascript can recognize and treat a "future method call", but still think there should be a better way.
Any idea?
What you could do is shadowing the function so as to be able to alter the third argument. You ought to define that shadowing function before fetching the JSON.
var originalUpdater = updater; // keep old function to call
// overwrite (shadowing)
updater = function(a, b, c, d, e) {
// change c appropriately here
originalUpdater(a, b, c, d, e);
}
Then you can still just eval it (which is not very safe, but that's not your point if I'm not mistaking), and it will call the shadow function.
A more generic shadowing method would be along the lines of:
var originalUpdater = updater; // keep old function to call
// overwrite (shadowing)
updater = function() {
// change arguments[2] appropriately here
originalUpdater.apply(this, arguments);
}
Fiddle: http://jsfiddle.net/n7dLX/
Change the server. Rather than returning
<script type="text/javascript">
updater('foo', 'bar', {}, '0', constant='');
</script>
Return
{
"method": "updater",
"params": [
"foo", "bar", {}, "0", ''
]
}
Assuming that you cannot change what is being sent over from the server, you can simply run through the innerText with a regular expression and pass update the HTML before you insert it.
var replacer = /\w+\(([^()]+)\)/gi;
script_element.innerText.replace(replacer, function(matched_text, func_params){
var orig_func_params = func_params;
// Make changes to func_params here.
return matched_text.replace(orig_func_params, func_params);
});
This can be functionized by doing the following:
var replacer = /\w+\(([^()]+)\)/gi;
function replace_arg(script_element, arg_index, replacement_value) {
script_element.innerHTML = script_element.innerHTML.replace(replacer,
function(matched_text, func_params){
var orig_func_params = func_params;
func_params = func_params.split(",");
if (arg_index >= func_params.length) {
throw new RangeError(arg_index + " is out of range. Total args in function:" + func_params.length);
}
func_params[arg_index] = JSON.stringify(replacement_value);
return matched_text.replace(orig_func_params, func_params.join(","));
});
return script_element;
}
This can be called in this way:
script_element = replace_arg(script_element, 3, {"new":"arg"});
I don't understand what you are doing, but in general if you don't want to rely on the order of parameters make the function take one parameter that is an object whose properties are the parameters:
function add(params) {
var a = params.hasOwnProperty("paramA") ? params.paramA : 0;
var b = params.hasOwnProperty("paramB") ? params.paramB : 0;
return a + b;
}
add({paramA: 1, paramB: 2});
In this case you should use hasOwnProperty to check if the function was passed the parameter you are looking for before trying to access it.