I'm learning how to use ractive and can't solve a problem, the code is at the following jsfiddle.
The gist of what I'm doing is counters for a queue (last object in array is current person):
1. A counter to display the queue number of the current person
2. A counter to display the size of the queue
A) works correctly but it is bloated with logic so I tried to convert it into a separate variable, as shown in B) but it doesn't update at all.
I put an observer in the code to watch when there is any change to the queue variable. I expect it to show an alert every time I click on "skip current person" or "delete current person" but the alert only shows up in the first time I load the page.
ractive.observe({
'queue.0.queueNo': alert('here')
});
Wim's answer is a good one - {{num(queue)}} is an elegant solution.
The reason you can't use queueNum is that when you do
queueNum = people[(people.length-1)].queueNo
the value of queueNum is set to whatever the value of people[(people.length-1)].queueNo is at the time of the statement. When the queue is altered, queueNum doesn't get re-evaluated. This isn't a Ractive thing so much as a JavaScript thing.
Another way of saying it is
a = 1;
b = 2;
foo = a + b; // foo === 3
a = 3;
b = 4;
alert( foo ); // alerts '3' - foo is the same, even though a and b changed
This is actually the same reason that the alert('here') was only triggering when the page loaded - rather than telling Ractive to trigger the alert when the value changed by wrapping it in a function, as in the second code block of Wim's answer, the code was executed immediately.
you can make queueNum a function based on queue like this:
num: function(q) { return q[(q.length-1)].queueNo;}
and call it like so: {{num(queue)}}
now when queue or people gets updated reactive knows it has to update num to. You don't even have to call ractive.update().
For the observe also make it a function and it will work:
ractive.observe({
'queue.0.queueNo': function(a,b) { alert('here');}
});
Related
i was a reading book named async javascript , one of the example of this book tried to show that events in javascript will trigger when processor has no instruction to do in the same process . for the proof of his word he brought an example :
for (var i = 1; i <= 3; i++) {
setTimeout(function(){ console.log(i); }, 0);
};
the output is :
4
4
4
4
everyhting is ok and i expected the result . but when i put the "console.log(i)" out of function the result will change .
for (var i = 1; i <= 3; i++) {
setTimeout(console.log(i), 0);
};
the output is :
1
2
3
i don't know why the output is different in those examples . i know its about variable scopes , but don't know exactly the reason . i hope if someone can help.
i don't know why the output is different in those examples
Because in the second case you are calling console.log(i) immediately and pass the return value undefined to setTimeout (instead of passing a function like in the first case).
Arguments are always evaluated first, so if you have foo(bar()), bar is called first and its return value is passed to foo.
You can verify this by adding console.log('after'); after the loop. In the first case, you should get
after
4
4
4
and in the second case, you will get
1
2
3
after
demonstrating that setTimeout is not adding anything to the event queue.
See also Calling functions with setTimeout()
i know its about variable scopes
Actually it's not, it's all about timing, the moment when console.log is executed.
When you call setTimeout(expr,t), expr gets evaluated, hoping it returns a function. In your second case console.log(...) doesn't return anything, but it gets evaluated nonetheless. What you see is the side-effect of this evaluation prior to passing the result to setTimeout.
There is something wrong with my code, but I can't find what it is. The first time I call registerStartDateValidation, the value of the startDateValidation variable in the defineKeyword closure is of course the same than what was affected outside the closure. But the second time, the variable is reaffected with another value, but the value of startDateValidation in the defineKeyword is still equal to the value of the first time we call registerStartDateValidation. Does the closure cache the variable?
Tv4.prototype.registerStartDateValidation = function (attributes) {
var helper = this;
var dateNames = Object.keys(attributes);
var startDateName = dateNames[0];
var startDateString = attributes[startDateName];
var endDateName = dateNames[1];
var endDateString = attributes[endDateName];
var startDateValidation = helper.startDateValidation(startDateString, startDateName, endDateString, endDateName);
console.log(startDateValidation)//First time equal "true", second time equal "false"
tv4.defineKeyword('startDate', function (data, value) {
if (value === 'startDate') {
console.log(startDateValidation)//First time equal "true", second time still equal "true"
return startDateValidation
}
});
};
Note that there is nothing making an ajax request, that could mess up the order the functions are called.
Short answer: no, a closure won't cache the variable, so there is something else going on in your code. Reducing the problem to a simple complete example that demonstrates the problem will usually make it obvious where the problem is coming from, or at least help other people figure it out.
However, since it looks like you're referring to is this project, it may be possible to figure out what's going on. Tv4.defineKeyword() appends new functions to a list if it is called multiple times for the same keyword. Since you're calling tv4.defineKeyword (note the lower-case 't') and not helper.defineKeyword or this.defineKeyword, it looks like each call to registerStartDateValidation will affect the same instance, so you are almost certainly adding a validation function when you meant to replace it.
The Situation
I need to loop through an array, and run a tween based on each item's value. Thus, for each item in the array, there must be a separate Tween instance. According to the TweenJS documentation, the get() method "Returns a new tween instance".
The Problem
It appears as though there is only a single instance when running a new tween for each item in the array inside a for loop
The Code
HTML
<div id='log'></div>
Javascript
var items = [3,6,2,8,9,6];
var log = document.getElementById('log');
for(var i = 0; i < items.length; i++){
var item = items[i];
var start = {
x: item * 10,
y: item * 15
};
var end = {
x: item * 10,
y: item * 30
};
createjs.Tween
.get(start, {
onChange: function () {
log.innerHTML = log.innerHTML + i + " - " + JSON.stringify(start) + "<br/>";
}
})
.to(end, 1000)
};
The Demo
http://codepen.io/anon/pen/pilAL - Notice the output. The number at the beginning is the index of the current array's value. It only shows the last item.
Ideally...
Ideally, each item in the array would tween separately between its values.
Thanks in advance!
The quick-and-dirty solution is to use the "event" argument in onChange, and inspect its target.target property.
The trick is that it is properly firing for everything, but the variable scoping is such that i and start no longer contain the values you seek by the time the event method fires.
Here it is with that change: http://codepen.io/anon/pen/FhsHz
Aye, as I expected, it's coming from a lexical scoping issue... I had to refresh myself on it, as it's a while since I've had to deal with Javascript in such a way (and thus worry about it!)
Read through this guide, it's very insightful and starts to address your issue and ways to work around it in the subsection "Creating closures in loops":
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
The gist is that, if you want that anonymous function you create to use the values captured upon creation of the function, you have to get the scoping of the variable you end up using into such a state that it actually is pointing to the desired value.
It's annoying, and they really should provide a more intuitive parameter to you than "event.target.target" as a place to get the value you seek, but the alternative to that is to do something like what they do in this example and essentially make a function generating function, into which you pass the variable. At that point, the scope of the variable that the function uses becomes the scope of the passed-in argument in the "generating function", and it will be pointing to the proper value. Y u c k.
How do I increment an integer inside a variable, every time that variable is called? Javascript.
var a=0;
var t=loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+x[a].getElementsByTagName("name")[0].childNodes[0].nodeValue+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en").getElementsByTagName("bio");
for (i=0;i<20;i++)
{
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=t[0].getElementsByTagName(\"summary\")[0].childNodes[1].nodeValue;'>Open Bio</button></div>");
}
I'm not sure how I would go about incrementing variable a. I need it to increase by 1 every time variable t is called in the for loop.
When I put all of the code in the for loop I get [object node list] returned so this method is not desired.
If I understood your question correctly, you could define your own getters and setters for the property.
var o = {}
o.__defineSetter__('property', function(value) { this._counter = 0; this._holder = value; })
o.__defineGetter__('property', function() { console.log(this._counter++); return this._holder; })
The counter would be reset every time o.property is assigned a value
o.property = 'Some value'
and then increase every time the property is accessed.
So,
console.log(o.property)
would print
0
Some value
to the console. And if you do it again, it would print
1
Some value
After your edit I think I can see your problem now. You will need to put the loadXMLDoc statement in the loop (since you want to load 20 different XML files), but you can't assign the result of every call to the same variable t - as once the button is clicked, the handler will evaluate t and get only the last value.
Instead, use an array:
var bios = []; // empty array
for (var i=0; i<20; i++) {
var artist = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
doc = loadXMLDoc("http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+artist+"&api_key=83e386b0ba08735e3dee9b118478e56d&lang=en"),
bio = doc.getElementsByTagName("bio")[0].getElementsByTagName("summary")[0].childNodes[1].nodeValue;
bios[i] = bio; // store it in the array
document.write("<div><button type='button' onclick='document.getElementById("+i+").innerHTML=bios["+i+"];'>Open Bio</button></div>");
}
Of course, while that will work it's a bunch of bad practises, including
unsecured accessing of DOM nodes/properties. If the xml changes its format, you will get lots of exceptions here. You might be sure now that this never happens, but wrapping artist and bio in try-catch might not be a bad idea.
snychronous Ajax. One can do better than that.
loading 20 documents (and that sequentially!) even if you don't need them. It might be worth to try loading each of them only when the respective button is clicked.
document.write
Inline attribute event handlers
…and creating them even by JS.
Below is my code fragment:
<div onclick = "myClick('value 1')">
button 1
</div>
<div onclick = "myClick('value 2')">
button 2
</div>
Basically when I for each click on a different div, a different value will be passed to the JavaScript function.
My Question is how can I keep track of the value passed in the previous click?
For example, I click "button 1", and "value 1" will be passed to the function. Later, I click on "button 2", I want to be able to know whether I have clicked "button 1" before and get "value 1".
Just add it to a variable in your script:
var lastClicked;
var myClick = function(value) {
lastClicked = value;
};
You can define somekind of variable, like var lastUsed;
add additional line to your function:
var lastUsed = null;
function myClick(value){
prevClicked = lastUsed; //get the last saved value
...
lastUsed = value; //update the saved value to the new value
...
}
And here you go
You need a variable. Variables are like little boxes in which you can store values. In this case, we can store the value that was last passed to the function myClick.
In Javascript, you can define a variable like this:
var lastClickedValue;
You can "put" a value into that variable. Let's say you want to put your name in there. You would do this:
lastClickedValue = 'sams5817';
Now here's the tricky bit. Variables have "scope". You might want to think about it as their "life-time". When a variable reaches the end of its scope, you cannot read or write to it anymore. It's as if it's never been. Functions define a scope. So any variable you define in a function will disappear at the end of the function. For example:
function myClick(value)
{
var lastClickedValue;
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
That looks almost right, doesn't it? We declared a variable, display its last value, and update it with the new value.
However, since the lastClickedValue was declared in the function myClick, once we've reached the end of that function, it's gone. So the next time we call myClick, lastClickedValue will be create all over again. It will be empty. We call that an "uninitialized" variable.
So what's the problem? We're trying to remember a value even after the end of myClick. But we declared lastClickedValue inside myClick, so it stops existing at the end of myClick.
The solution is to make sure that lastClickedValue continues to exist after myClick is done.
So we must delcare lastClickedValue in a different scope. Luckily, there's a larger scope called the "global scope". It exists from the moment your page loads, and until the user moves on to another webpage. So let's do it this way:
var lastClickedValue;
function myClick(value)
{
alert('lastClickedValue is = ' + value);
lastClickedValue = value;
}
It's a very small difference. We moved the declaration of the variable lastClickedValue to be outside the function myClick. Since it's outside, it will keep existing after myClick is done. Which means that each time we call myClick, then lastClickedValue will still be there.
This will let you know what the last value passed to myClick was.
Finally, I'd like to advise you to look for some kind of Javascript tutorials. I wish I knew of some good ones to recommend, but I'm certain you can find a few on the Internet. If you try to write programs before understanding what you're doing, you'll find yourself producing work that is less than what you're capable of. Good luck!
I suppose you need something like this
var clickedButtons = [];
function myClick(value){
...
clickedButtons.push(value);
...
}
I am surprised that no one else mentioned this, but since functions are first class objects in JavaScript, you can also assign attributes and methods to functions. So in order to remember a value between function calls you can do something like I have with this function here:
function toggleHelpDialog() {
if (typeof toggleHelpDialog.status === 'undefined')
toggleHelpDialog.status = true;
else
toggleHelpDialog.status = !toggleHelpDialog.status;
var layer = this.getLayer();
if (toggleHelpDialog.status) layer.add(helpDialog);
else helpDialog.remove();
layer.draw();
}
Here I have added an attribute named 'status' to the toggleHelpDialog function. This value is associated with the function itself and has the same scope as the toggleHelpDialog function. Values stored in the status attribute will persist over multiple calls to the function. Careful though, as it can be accessed by other code and inadvertently changed.
we can leverage javascript static variables
One interesting aspect of the nature of functions as objects is that you can create static
variables. A static variable is a variable in a function‘s local scope whose value persists across
function invocations. Creating a static variable in JavaScript is achieved by adding an instance
property to the function in question. For example, consider the code here that defines a function
doSum that adds two numbers and keeps a running sum:
function doSum(x,y){
if (typeof doSum.static==='undefined'){
doSum.static = x+y;
}else{
doSum.static += x+y;
}
if (doSum.static >= 100){doSum.static = 0;doSum.static += x+y;}
return doSum.static;
}
alert(doSum(5,15))
alert(doSum(10,10))
alert(doSum(10,30))
alert(doSum(20,30))