How can I detect when a variable changes value in JavaScript? - javascript

Before I start, I'd just like to say that there are already answered questions on this topic, and I looked them up - but because I'm still somewhat of a beginner, I can't really figure out the answer as it's too complex for me. So I wanted to give my own example, a dead simple one, and try to understand based on that.
Essentially what I want to do is run a function when an already existing variable, with a defined value, changes its value. Something like this:
var testVar = 5;
function letMeKnow() {
console.log("The variable has changed!);
}
What I want is, when I go to the console and type, say testVar = 7, I want the letMeKnow() function to run, and print something out in the console. More specifically, I want JavaScript to detect that the variable has changed, and run the function automatically. I know that this used to be possible with Object.observe() or Object.watch(), but since those are deprecated I suppose that I have to use getters and setters to achieve this. My question is, how would I do that for this example, keeping it as simple as possible?
Thanks!

A simple example using getter/setter can be like:
var obj = {
value: '',
letMeKnow() {
console.log(`The variable has changed to ${this.testVar}`);
},
get testVar() {
return this.value;
},
set testVar(value) {
this.value = value;
this.letMeKnow();
}
}
console.log(obj.testVar)
obj.testVar = 5;
console.log(obj.testVar)
obj.testVar = 15;
console.log(obj.testVar)

Related

javascript: setting default value with IF statement instead of OR

I'm only sharing a small bit of code because there is so much going on, and I hope this is enough to answer my question.
I have some existing JS where a value is determined with an OR statement and I think I need to convert that to an IF statement. The final output is currently giving me both values if they both exist, and I only want "question" where both "question" and "name" values exist.
var question = new fq.Question(questionData.answerId, topicId,
questionData['question'] || questionData['name'],
questionData['text']);
Instead of using the OR operator (answerData['question'] || answerData['name']), I'd like to do something similar to the following:
if (questionData['question'] is undefined) {
use questionData['question'];
} else {
use instead questionData['name']
}
But, I don't know how I might accomplish such a statement within the () in the existing code pasted above. The name variable/value is always present, so there's no risk in defaulting to that. Question is only defined some of the time. And I don't ever want both appearing.
This is probably outside of the scope of my query here, but to fill in a little more detail, this code eventually outputs JSON files for topics and questions. Topics only have names values, and questions have both names and questions, but I only want the questions json to include questions values, not names. I'm pretty sure this is the key part in all of the JS to determin
Create a function and get value from there.
Need to remember scope of function:
Example Snippet:
var that = this;
var question = new fq.Question(questionData.answerId, topicId,
that.getValue(),
questionData['text']);
function getValue() {
if (questionData['question']) { //null and undefined both are false
return questionData['question']
} else {
return questionData['name']
}
}

Using a parameter with a Javascript object?

Currently I can do:
function addStat() {
player.str = player.str + 1;
}
But I want to be able to use things other than just "str" with my player object. So I decided with doing something like this:
function addStat(stat) {
player.stat = player.stat + 1;
}
But that doesn't seem to work, iv'e tried looking up the syntax for using parameters but could not find anything similar to the way I need.
I learned about "this" but I can't get it to work with my function.
I thought this:
function addStat(thing, stat) {
thing.stat = thing.stat + 1;
statReset();
}
would work but I can see why it won't. I made sure the rest of my javascript and html work and when I add those functions nothing breaks, it just doesn't work.
When assigning properties with a variable, you need to use bracket notation, as opposed to dot notation. This, then, looks like:
function addStat(stat) {
(stat in player) ? ++player[stat] : player[stat] = 1;
}
Due to comments (that I disagree with), I figured I should mention that since you are attempting to modify a property that may not exist, you should also add a safety check to see if you can modify it.
Otherwise you will be modifying undefined, and that will cause undesired output..
You can access properties with []:
function addStat(prop) {
player[prop] = player[prop] + 1;
}
so calling addStat("stat") will actually set player.stat.
In javascript, the syntax
object.key
is equivalent to
object["key"]
So your thing.stat is equivalent to thing["stat"], i.e. the key is the literal string "stat" when what you really want is to use the value referenced by the parameter stat as the key:
thing[stat] = thing[stat] + 1;

Javascript - Array of prototype functions

I'm a javascript newbie so I'm writing ugly code so far sometimes due to my lack of experience and how different it is to the languages I'm used to, so the code I'll post below works, but I'm wondering if I'm doing it the right way or perhaps it works but it's a horrible practice or there is a better way.
Basically, I have a little dude that moves within a grid, he receives from the server an action, he can move in 8 directions (int): 0:up, 1: up-right, 2: right... 7: up-left.
the server will send him this 0 <= action <= 7 value, and he has to take the correct action... now, instead of using a switch-case structure. I created a function goUp(), goLeft(), etc, and loaded them in an array, so I have a method like this:
var getActionFunction = actions[action];
actionFunction();
However, what to set all this up is this:
1) create a constructor function:
function LittleDude(container) {
this.element = container; //I will move a div around, i just save it in field here.
}
LittleDude.prototype.goUp() {
//do go up
this.element.animate(etc...);
}
LittleDude.prototype.actions = [LittleDude.prototype.goUp, LittleDude.prototype.goUpLeft, ...];
//In this array I can't use "this.goUp", because this points to the window object, as expected
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction(); //LOOK AT THIS LINE
}
Now if you pay attention, the last line won't work.. because: when i use the index to access the array, it returns a LittleDude.prototype.goUp for instance... so the "this" keyword is undefined..
goUp has a statement "this.element"... but "this" is not defined, so I have to write it like this:
actionFunction.call(this);
so my doAction will look like this:
LittleDude.prototype.doAction = function(action) {
var actionFunction = this.actions[action];
actionFunction.call(this); //NOW IT WORKS
}
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way. Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
What you are trying to do is one of the possible ways, but it is possible to make it more simple. Since object property names are not necessary strings, you can use action index directly on prototype. You even don't need doAction function.
LittleDude = function LittleDude(container) {
this.container = container;
}
LittleDude.prototype[0] = LittleDude.prototype.goUp = function goUp() {
console.log('goUp', this.container);
}
LittleDude.prototype[1] = LittleDude.prototype.goUpRight = function goUpRight() {
console.log('goUpRight', this.container);
}
var littleDude = new LittleDude(123),
action = 1;
littleDude[action](); // --> goUpRight 123
littleDude.goUp(); // --> goUp 123
actionFunction.call(this); //NOW IT WORKS
I need to know if this is hackish or if I'm violating some sort of "DO NOT DO THIS" rule. or perhaps it can be written in a better way.
No, using .call() is perfectly fine for binding the this keyword - that's what it's made for.
Since it seems to me kind of weird to add it to the prototype but then treating it like a function that stands on its own.
You don't have to define them on the prototype if you don't use them directly :-) Yet, if you do you might not store the functions themselves in the array, but the method names and then call them with bracket notation:
// or make that a local variable somewhere?
LittleDude.prototype.actions = ["goUp", "goUpLeft", …];
LittleDude.prototype.doAction = function(action) {
var methodName = this.actions[action];
this[methodName](); // calls the function in expected context as well
}

Variable Dependency with knockoutJS

I'm building an application with KnockoutJS with a component that essentially acts as a sequential spreadsheet. On different lines users may define variables or use them to represent a value.
So for example
x =2
x //2
x = 4
x //4
I have this working in the straightforward case of continuing adding new lines. The output function for each line checks and iterates backwards to see if the variable was ever defined previously. If it was it uses the first example it finds and sets that as the value. This works when initially defining the lines, and also works when you edit a line after a previous line has changed.
However, I would like variables to update if a previous definition of that variable has changed, been removed, or been added. That behavior does not exist right now. I have tried adding my own custom dependency handling code using a map to track the variables, but it badly impacted performance. I would like to tap into Knockouts dependency management to solve this, but I'm not sure of the best way to do so. Here is a brief summary of my code structure, I would be happy to add more detail if needed.
calcFramework is the view-model object I bind to the map. It consists of an observable list of Lines, a varMap, and other unrelated properties and functions
Line is a custom object. The relevant code is below
var Line = function (linenum,currline) {
var self = this;
self.varMap = {};
self.input = ko.observable("");
self.linenum = ko.observable(linenum);
self.lnOutput = ko.computed({
read:function(){
return outputFunction(self,self.input());
},
write:function(){},
owner:self
});
};
function outputFunction(self,input) {
try{
var out = EQParser.parse(input,10,self);
return out.toString();
}
catch(ex){
//error handling
}
}
Line.prototype.getVar = function (varName, notCurrentLine) {
if(typeof varName === "undefined"){
return null;
}
//Actually don't want ones set in the current varMap, only past lines
if(varName in this.varMap && notCurrentLine){
return this.varMap[varName];
}
if (this.linenum() > 0) {
var nextLine = calcFramework.lines()[this.linenum() - 1];
return nextLine.getVar(varName,true);
} else {
//eventually go to global
return calcFramework.varMap[varName];
}
};
Line.prototype.setVar = function(varName,value){
this.varMap[varName] = value;
};
SetVar and getVar are passed to eqParser, which gets the value of the expression, calling those functions as needed if a variable is referenced. So the variable value is not explicitly passed to the function and thus knockout does not view it as a dependency. But I'm not sure how I would pass the variable as a parameter without traversing the list every time.
So my question is, given this setup, what is the best way to track changes to a variable assignment (and/or new assignments) and update the lines that reference that variable, while maintaining good performance.
I recognize my question is lengthy and I have attempted to trim out all unnecessary detail. Thanks for your patience in reading.
I would be tempted to use a publish/subscribe model, using something like Peter Higgins' PubSub jquery plugin
Your overall app would subscribe/listen out for lines publishing an event that they have a variable definition. This would store any variable names in a standard javascript hashtable, along with the value. When a variable found event is published by a line, the app would check through all the known variables, and if it finds that it is a change to an existing variable value, it would publish a variable changed event. All the lines would subscribe to that event. They can then check whether they have a variable matching that name, and update the value accordingly.
Here's some untested code to give you an idea of what I mean:
var app = function()
{
var self = this;
self.variables = {};
$.subscribe('/variableAssigned', function (key, value)
{
// I think that this is the best way of checking that there is a variable
// in the object
if(self.variables.hasOwnProperty(key))
{
if(self.variables[key] !== value)
{
$.publish('/variableChanged', [ key, value ]);
}
}
});
}
In your Line object:
$.subscribe('/variableChanged', function (key, value)
{
// loop through varMap and see if any of them need updating.
});

How to retrieve a conditionally set variable from inside a function?

What can I do to retrieve a conditional variable from inside a function in the webpage being parsed by greasemonkey?
Something like this:
var myglobal = 5;
function myfunc() {
myglobal = myglobal -1;
if (myglobal == -1) {
var this1 = 'Test';
document.getElementById("mybutton").href = this1;
}
}
In this case I would like to read what is in 'this1', either directly or modifying 'myglobal', calling 'myfunc' and somehow getting the value of href from 'mybutton'... any ideas?
You usually can't get a variable value from outside a scope like that, but you may not have to in this case.
"myfunc is a counter, and I want to skip it, otherwise mybutton.href won't be set yet."
Based on the sample code, you might be able to cheat that timer just by using:
unsafeWindow.myglobal = 0;
(In fact, I use this exact technique on one very misguided training site.)
To answer the stated question further, you cannot get dynamic values from within such a scope but the initial state may be enough, as it appears to be for this question.
So you could get myfunc()s code and parse it with regex to obtain the desired value:
var theFunc = unsafeWindow.myfunc.toString ();
var desiredHref = theFunc.match (/var\s+this1\s*=\s*["']([^"']+)["']/);
if (desiredHref.length > 1) {
// Found!
desiredHref = desiredHref[1];
}

Categories

Resources