A bug involving JavaScript's object structure - javascript

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.

Related

can't get function's global execution context variable in runtime

i guess this is a pure vanilla's javascript issue.
i needed to override a function showDatepicker , i did it this way :
const Base = (Handsontable.editors.DateEditor as any).prototype as any;
const DateEditorHelper = Base.extend();
DateEditorHelper.prototype.showDatepicker = function (event: any[]) {
Base.showDatepicker.apply(this, event)
this.$datePicker.config(this.getDatePickerConfig());
var offset = this.TD.getBoundingClientRect();
var dateFormat = this.cellProperties.dateFormat || this.defaultDateFormat;
var datePickerConfig = this.$datePicker.config();
var dateStr = void 0;
var isMouseDown = this.instance.view.isMouseDown();
var isMeta = event ? (0, _unicode.isMetaKey)(event.keyCode) : false;
this.datePickerStyle.top = offset.top >= $(window).height() - 224 ? window.pageYOffset + offset.top - 224 + (0, _element.outerHeight)(this.TD) + 'px' : window.pageYOffset + offset.top + (0, _element.outerHeight)(this.TD) + 'px';
this.datePickerStyle.left = window.pageXOffset + offset.left + 'px';
this.$datePicker._onInputFocus = function () { };
datePickerConfig.format = dateFormat;
if (this.originalValue) {
dateStr = this.originalValue;
if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
}
// workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
if (this.getValue() !== this.originalValue) {
this.setValue(this.originalValue);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else if (this.cellProperties.defaultDate) {
dateStr = this.cellProperties.defaultDate;
datePickerConfig.defaultDate = dateStr;
if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
}
if (!isMeta && !isMouseDown) {
this.setValue('');
}
} else {
// if a default date is not defined, set a soft-default-date: display the current day and month in the
// datepicker, but don't fill the editor input
this.$datePicker.gotoToday();
}
this.datePickerStyle.display = 'block';
this.$datePicker.show();
}
It's actually the original function's code in which i made some small modification.
i thought the function's inner code variables will be evaluated in runtime (i'm not talking about the inner function scope variables as its arguments or variables declared in it but about the global variables to the execution context).
Before the function get executed there are no errors at all (when the browser reads the function , it doesn't complain about that some variables are undefined) but when it runs , i got this error :
Uncaught ReferenceError: _unicode is not defined
at eval (eval at DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281), <anonymous>:1:1)
at e.DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281)
The overriden function is running in the same context as where the original function should runs , i don't know why a global variable is undefined.
The original function is a handsontable built-in editor (datePicker). The overriding idea i got it from this thread and this
You say that you copied the code from the original function and made minor changes. The problems seems to be that you're using a variable named _unicode. Are you defining that variable?
When replacing a method's code with the 'original' code modified, you must made sure that any other variable/function referenced by the old code is also copied.
You must take into account that the original method was defined in another context. Probably, the author didn't mean for this class to be derived, or to have its methods overriden.
The advantage of modules (or IIFEs) is that you can define a public API while encapsulating the private parts of the implementation. Of course, this means that overriding methods or functions is much more difficult.
In this case, the variable _unicode is clearly intended to be part of the private implementation. The fact that its name follows the convention of starting with an underscore is almost proof of that. It probably is defined in the same module as DatePickerHelper, or imported from another, but in any case I'm almost sure it is not exported, so you cannot have access to it and your code fails.
For your override to work, you must either change the code to avoid using that _unicode variable, or define it yourself the same way it is done in the original code. Of course, depending of how it is defined you may end up having to 'copy' lots of that code, but that's an issue that any JavaScript programmer who has ever tried to override a method from an external library has suffered.
Let's see an example of this 'private implementation':
(function() {
// this is a function I want to keep private.
function giveMeHello() {
return 'hello, ';
}
// this is the class I want to share
class Greeting {
greet(name) {
console.log(giveMeHello() + name);
}
}
window.Greeting = Greeting;
})();
const greet1 = new Greeting();
greet1.greet('Oscar'); // outputs 'hello, Oscar'.
// As a consumer of Greeting, I don't like the way if greets people. I want the name to be in uppercase
// implementation is practically identical to original code
Greeting.prototype.greet = function() {
console.log(giveMeHello() + name.toUpperCase());
};
// Let's use it!
greet1.greet('John'); // Oh! Error! giveMeHello is not defined!
As you see, in this example I doing the same you are. But my implementation relies, just as the original one, on the giveMeHello function. But giveMeHello is no a global or public function. I haven't got access to it from the point in which I am overriding the function. Therefore, when I execute the method, it fails.
If now I just copy the giveMeHello function before overriding the method, it will work.
Do you get it now? If you don't, I suggest you to read more about scopes in JavaScript, something that is beyond the purpose of StackOverflow.

JavaScript constructor Methods calling other methods in same constuctor [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 years ago.
I'm trying to call an object method from another method within the same constructor, and it doesn't seem to be working - I just get the error TypeError: undefined is not a function (evaluating 'this.uiDisplayOptions()').
I'm instantiating the object using using the new keyword var ui = new _ui().
Anyone know why it's not working? I've seen examples of this kind of setup being suggested.
Here's the code:
function _ui() {
this.uiDisplayOptions = function() {
var len = channels[currentChannel].stepsChannel;
$("select#length option")
.prop('selected', false)
.filter('[value="' + len + '"]')
.prop('selected', true);
var rand = channels[currentChannel].randomAmtChannel;
$("select#randomness option")
.prop('selected', false)
.filter('[value="' + rand + '"]')
.prop('selected', true);
var dir = channels[currentChannel].readDirection;
$("select#readdirection option")
.prop('selected', false)
.filter('[value="' + dir + '"]')
.prop('selected', true);
}
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
// Select Row
$("#selectRow0").addClass("red");
$("#selectRow0").click(function() {
currentRow = 0;
$("#selectRow1").removeClass("red");
$(this).addClass("red");
});
$("#selectRow1").click(function() {
currentRow = 1;
$("#selectRow0").removeClass("red");
$(this).addClass("red");
});
// Increment/Decrement Selected Row Pattern
$("#patternInc").click(function() {
selectPatternRow(1);
displayPattern();
});
$("#patternDec").click(function() {
selectPatternRow(-1);
displayPattern();
});
// Shift Left/Right Selected Row Pattern
$("#shiftLeft").click(function() {
selectShiftRow(-1);
displayPattern();
});
$("#shiftRight").click(function() {
selectShiftRow(1);
displayPattern();
});
// Handle Row 'Pattern Locks'
$(".lock").click(function() {
var step = $(this).data("lockstep");
switch(toggleLockBit(step)) {
case 0:
$(this).removeClass("red green");
break;
case 1:
$(this).addClass("red");
break;
case 2:
$(this).removeClass("red").addClass("green");
break;
}
displayPattern();
});
// Handle Channel Length change
$("#length").change(function() {
selectCurrentChannelLength($(this).val());
displayChannelLength();
});
// Handle Channel Randomness change
$("#randomness").change(function() {
selectCurrentChannelRandomAmt($(this).val());
displayRandomAmt();
});
}
}
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
// this here does not refer to the this of the object being created.
// it refers to the anonymous function being created in the click call.
// jQuery is probably invoking this and binding this to undefined,
// but even if it wasn't then this code would behave incorrectly.
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
});
When inside a function the this's value may change. It has it's own binding called a ThisContext and cannot be guaranteed to be pointing at the object you are calling this from within (especially with the introduction of bind, apply and call). Inside uiSetListeners, this is generally bound to the function (which in turn is bound to the object, assuming you are invoking the constructor correctly, and not using any bind magic).
However inside your click, handler, you are delegating the function to jQuery. jQuery doesn't know about your object so it doesn't bind this (or binds it to undefined), and it isn't associated with an object by default (as the function is being declared anonymously and not bound to an object). In other words, your click handler is pointing to a different this than your this.uiSetListeners statement is.
The way to fix this is by using a var that = this; kind of mechanism. If you take this approach, you should probably define var that = this at the top of your constructor function (so others can see what's going on) and replace any incidence of this inside of the constructor function with that.
This ensures that should another user call your constructor with call, bind et al, the object will be bound correctly to the supplied this.
var that = this;
that.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(function() {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(that).data("channel");
displayUpdateChannel();
that.uiDisplayOptions();
});
});
Note that ES6 fixes this with the fat arrow notation.
this.uiSetListeners = function() {
// Select Channel
$("#selectChannel0").addClass("green");
$(".channelselect").click(() => {
$(".channelselect").removeClass("green");
$(this).addClass("green");
currentChannel = $(this).data("channel");
displayUpdateChannel();
this.uiDisplayOptions();
});
});
You should be perfectly able to invoke other methods from within the constructor as long as you remember to take care with your this.
It is generally preferable to use YourConstructor.prototype.methodName instead, as this will first of all reduce nesting but also uses the prototype chain. Assigning functions to this in the constructor does not assign them to the prototype chain, which also means they will be recreated each time a new object is created. You only really need to assign functions to this inside of a Constructor if their implementation is dependent on the values passed into the constructor and it is not appropriate to capture those values in the constructor as state on the created object.
you can't call a function like this inside a constructor, this will refer to global object window until you call your constructor function using new keyword.
var ui = new _ui();
refer the current object context on the top of you constructor function.
function _ui() {
var _that = this;
}
and refer all current constructor function using _that reference.
I hope it will solve your problem.

Advanced Javascript: The ALT way to do Pub/Priv in JS - is a mystery

Eric Miraglia of Yahoo/Google presents a very clean looking way to implement information hiding in JavaScript:
http://www.yuiblog.com/blog/2007/06/12/module-pattern/
Please note some experiments here:
http://jsfiddle.net/TvsW6/5/
My question is, why can I access the seemingly "public" variable "this.setting2" (and of course not _setting1) YET I cannot access the function "this.logSetting_priv" although it is in the same scope as this.setting2 (isn't it!?!?!?) Does any one know why?
Also, with the use of the return object for the public methods, I can't seem to add a function as I might normally with "LogSystem.prototype.publicFunc1." Why is that?
Mystery of the ages . . .
Pls checkout my JSFiddle but the JS is also below:
function LogSystem() {
//default
var _divId = "log";
var _setting1 = "default stuff";
this.setting2 = "default stuff as well";; //This is accessible!
function _printLog(msg) {
msg = msg || "";
$("#" + _divId).append(msg + "<br/>");
};
//this is **not** accessible - bc of return object below?
this.logSetting_priv = function () {
_printLog("PRIV: Setting1 is: " + _setting1);
_printLog("PRIV: Setting2 is: " + this.setting2);
};
/*
* Key Distinguishing feature of this pattern
*/
return {
printLog: function (msg) {
console.log("PRINTING:" + msg);
_printLog(msg);
},
logSetting_pub: function () {
this.printLog("PUB: Setting1 is: " + _setting1);
this.printLog("PUB: Setting2 is: " + this.setting2);
},
publicFunc2: function () {
_setting1 = "Fixed Deal returnFunction";
this.setting2 = "floating hamster";
}
};
};
//THIS DOESNT WORK!! . . . . bc of the return object??
LogSystem.prototype.publicFunc1 = function () {
_setting1 = "Fixed Deal";
this.setting2 = "floating midget";
};
/*******************************/
/*********Testing Code**********/
/*******************************/
$(document).ready(function () {
var logInst = new LogSystem();
//TESTING METHODS!
try {
logInst.publicFunc1(); //THIS DOESNT WORK!!
} catch (e) {
logInst.printLog("The call to the prototype function does not work - WHY?");
logInst.publicFunc2();
}
try {
logInst.logSetting_pub();
logInst.logSetting_priv();
} catch (e) {
logInst.printLog("ERR!!: " + e.message);
}
//TESTING MEMBERS!
logInst.printLog("We know this does not work? " + logInst._setting1); //undef
logInst.printLog("Why Does THIS WORK? " + logInst.setting2); //def
});
Thank you!
EDIT: Holy crap - and when I manipulate the prototype of the INSTANCE variable, i seem to break the whole object that was returned: http://jsfiddle.net/TvsW6/7/
If any one understands JS at this level, PLEASE explain that! :)
Thank you all so much. Obviously any one in this conversation is at a level way beyond "I do some jQuery" :)
Using private instance variables prevents you from using prototype (functions that need to access them need to be in the constructor body where the privates are declared with var) at the end of this answer is link to a pattern that implements protected. It may take some time to understand how prototpe works and would advice trying to understand the basic workings first before trying to mix it with closures to simulate private/public modifier.
Pointy answered you question correctly that when invoking a function with new but then returning an object would not return the Object referred to as this in the function:
function Test(){
this.name="some test";
return {name:"something else"};
}
console.log((new Test()).name);//name:something else
Not returning an object or returning a primitive (string, boolean, number) would cause the this object to be returned:
function Test(){
this.name="some test";
return "hello";
}
console.log((new Test()).name);//name:some test
Your constructor is returning a different object than the one build implicitly with new. Thus, inside the constructor this refers to a different object than the one you actually end up with outside, and that object doesn't have a property called "logSetting_priv".

Calculate value for Handlebars template on-the-fly?

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.

in jaydata: pass variables to filter, only global works

I have this function:
function db_borrarServer(idABorrar){
serversDB.servidores
.filter(function(elementoEncontrado) {
return elementoEncontrado.id_local == this.idABorrar;
})
.forEach(function(elementoEncontrado){
console.log('Starting to remove ' + elementoEncontrado.nombre);
serversDB.servidores.remove(elementoEncontrado);
serversDB.saveChanges();
});
}
does not work, but it does if I replace the variable "this.idABorrar" with a number, it does
return elementoEncontrado.id_local == 3;
or if I declare idABorrar as a global, works to.
I need to pass idABorrar as variable. How can I do this?
The EntitySet filter() function (as any other predicate functions) are not real closure blocks, rather an expression tree written as a function. To resolve variables in this scope you can only rely on the Global and the this which represents the param context. This follows HTML5 Array.filter syntax. To access closure variables you need to pass them via the param. Some examples
inside an event handler, the longest syntax is:
$('#myelement').click(function() {
var element = this;
context.set.filter(function(it) { return it.id_local == this.id; },
{ id: element.id});
});
you can also however omit the this to reference the params as of JayData 1.2 and also use string predicates
$('#myelement').click(function() {
var element = this;
context.set.filter("it.id_local == id", { id: element.id});
});
Note that in the string syntax the use of it to denote the lambda argument is mandatory.
In JayData 1.3 we will have an even simplex calling syntax
$('#myelement').click(function() {
var element = this;
context.set.filter("it.id_local", "==", element.id);
});
In the filter you should pass an object which is the this object, like this:
.filter(function(){},{idABorrar: foo})
foo can be const or any variable which is in scope.
The .filter() function takes an optional 2nd parameter which is assigned to this inside of the first parameter function.
So you can modify your code like so :
function db_borrarServer(idABorrar){
serversDB.servidores
.filter(function(elementoEncontrado) {
return elementoEncontrado.id_local == this;
}, idABorrar)
.forEach(function(elementoEncontrado){
console.log('Starting to remove ' + elementoEncontrado.nombre);
serversDB.servidores.remove(elementoEncontrado);
serversDB.saveChanges();
});
}
Let me know how you go - I'm very new to jaydata too and I've also been going a bit crazy trying to get my head into this paradigm.
But I came across your question trying to solve the same issue, and this is how I resolved it for me.

Categories

Resources