Assiging timeout/break on multiple observables in knockout - javascript

Bear with me on this one cause this one is a bit tricky for me to explain.
So I have multiple observables assigned, say:-
var self = this;
self.amount = ko.observableArray();
self.data0 = ko.observable([10,11,12]);
self.data1 = ko.observable([1,2,3]);
self.data2 = ko.observable([3,4,5]);
self.data3 = ko.observable([6,7,8]);
self.data4 = ko.observable([9,10,11]);
And there is some button that changes the value on each of them with the following function (what this functions does isn't really important, it's merely to show that there is some change going on in the observables)
self.bindOneByOne = function(){
var self = this;
var i = 0;
while(self['data' + i]){
for(var j = 0, len = self['data'+i]().length; j < len; j++){
self['data'+i]()[j] *= 2;
}
self.amount.push(i);
i++;
}
};
Now what I'm wanting to do is to display the changes as it happens in the UI side, one at a time (first self.data0 and then data1 and so on..) when I call a function (click on a button in this case)
My attempt for that behavior so far:-
self.changeValues = function(){
var i = 0;
while(self['data' + i]){
setTimeout(self['data' +i].valueHasMutated,1000);
i++;
}
}
Shouldn't my code first bind self.data0 first and shouldn't it immediately reflect on my UI? Currently, I'm only seeing changes all at once which is not the behavior I wanted.
Here's the fiddle for what I'm trying to do. (Click on Populate/Change to populate the data and change it after it's been populated...and then Mutate to see the changes on the UI side. You can also see that the data is indeed changing when you press Populate/Change button if you check your console prior to clicking on Mutate button)

The key with the timeout is to capture the loop values (i and J) in a closure with the use of an IEFE(immediately invoked function execution)
for(var j = 0, len = self['data'+i]().length; j < len; j++){
(function(){
//this captures the item to set using the current value of i and J
var itemToSet = self['data'+i]()[j];
setTimeout(function(){
console.log('itemToSet',itemToSet());
itemToSet(itemToSet() * 2);
},1000*(i+1));
})() //the () brackets immediately invoke this function that is also in brackets
}
self.amount.push(i);
I've created a fiddle to show it working, you only need the 1 button to show it really, I have just made the second button update each item individually, rather than all values in the array that the first button does.
Fiddle here
Hope it helps.

Related

How can I create and modify multiple SVGs dynamically

I am adding multiple SVGs dynamically, then modifying each of them. Without an event listener, the map was not being seen. Now, however, the event listener appears to create multiple instances of the last loop, instead of one for each loop, only the last instance gets modified, but multiple times with the same mygroup,svgID.
for (var i=0; i<path.length; i++) {
var mygroup = path[i], svgID = "svgMap" + i
const iSVG = document.createElement("object")
document.getElementById("summary-mygroup").appendChild(iSVG)
iSVG.id = svgID
iSVG.type = "image/svg+xml"
iSVG.data = "Maps/mygroup_basemap.svg"
iSVG.addEventListener('load', function(){
createMap(mygroup,svgID)
})
}
TL;DR:
use const instead of var
const mygroup = path[i], divID = "div" + i, svgID = "svgMap" + i
What you are seeing is due to function() using mygroup, divID , and svgID form the loop's scope which keeps updating until the functions execute (all with the latest value). This happens because the same variable is used.
var and const/let do not behave the same
var scopes to the function, whereas let/const are scoped to the block. (var also gets hoisted, but that's less related to the issue)
so if you run:
for (var i=0; i < 3; i++){
var b = i
setTimeout(function(){console.log(b)},1)// 😡 2,2,2
}
console.log("B:", b) // 😬 2
you wouldn't expect to have console.log("B:", b) run without an error, but it does, because the scope of var exists outside of the function.
whereas if you use let or const
for (var i=0; i < 3; i++){
let b = i;
setTimeout(function(){console.log(b)},1)// 👍 0,1,2
}
console.log("B:", b) // 👍 throws error
you will have expected behaviour, including an error on the console.log
And because it is a function-vs-block-scope issue, you could move the entire functionality inside a function and call it, which will lock the scope to the function:
for (var i=0; i < 3; i++){
(function(){
var b = i
setTimeout(function(){console.log(b)},1)// 👍 0,1,2
})()
}

Detect a button and then press it in JavaScript

I want to make a function that would detect a button on a web page and then click it. But I want it to click a specific item.
function imready()
{
var btn = document.getElementsByClassName('text-xxxs mb-02');
for (var i = 0; i < btn.length; i++)
{
if (btn[i].innerText.indexOf('AK-47') > -1)
{
console.log('runtime');
chrome.runtime.sendMessage({ type: 'dontrun', update: 1 }, function (response) {
});
btn[i].click();
pressok();
}
}
How do I make it so that the var "btn" should equal to document.getElementsbyClassName('x') and also a different className ('y')?
Quoting from https://stackoverflow.com/a/29366682/10450049
getElementsByClassName() returns an HTMLcollection object which is similar to an array but not really an array so you can't call
array methods using the returned value. One hack is to use Array's
prototype methods along with .call()/.apply() to pass the returned
object as the context.
var elems = document.getElementsByClassName("royal") ;
var collapsedElems = document.getElementsByClassName("collapsed");
var earray = Array.prototype.slice.call(elems, 0);
var concatenated = earray.concat.apply(earray, collapsedElems) ;
console.log(concatenated)
Demo Fiddle
As far as i understand your question, you can use document.querySelector('.classX.classY') to select the needed button with both classes.
That works for the case if you only need one button on the page selected, from your code i assume exactly that.

Javsacript for loop giving out weird results

I wrote a simple for loop to perform some DOM manipulation based on the json response. Here's the code I have
onSuccess: function(a) {
var b = a.items.length;
for (i = 0; i < b; i++)
user_id = a.items[i].id;
$('#user_id').checked = true;
selectUserSettings(user_id);
}
},
In one example I was working with, the resultset 'a' had 14 items in them. The for loop, when adding a breakpoint, shows the value of i at 0,1,2,2,3,2,3,4,2,3,4,5... Basically it resets and begins at 2 and goes upto 1 additional index before doing it over again... What am I doing wrong here ?Any help would be greatly appreciated.
Most probably because of this line for (i = 0; i < b; i++) when i is declared without a let or var keyword , it is in global scope.
Change this to
for (let i = 0; i < b; i++)
Also if my understanding is correct you want to use user_id variable to access the element. If it is so then change
user_id = a.items[i].id;
$('#user_id').checked = true;
to
let user_id = a.items[i].id;
$('#'+user_id).checked = true;

Javascript function with dynamically generated arguments

Below code :
loop(n times)
create HTML Button Element
count++;
assign onclick event = function(){
openSomething("Value_"+count)
}
so if i create 3 input elements (n=3) and then go back click any of the three buttons then every time openSomething("Value_"+3) only gets called.
why openSomething("Value_"+1) and openSomething("Value_"+2) does not get called?
I am not sure what is going on may be it the scope issue but i dont know much about scope either, any help to push me in the right direction is much appreciated.
My original code
var count = 0;
for(var i =0;i<someValue;i++){
count++;
var button = document.createElement("img");
button.src = "/images/small_button.gif";
button.imageButton = true;
button.srcBase = "/images/small_button";
button.onclick = function () {
selectSomething("someIdText_"+count);};
cell.appendChild(button);
}
Because JavaScript doesn't have block-level scoping of variables, and as a result everything is scoped to the function. That means that when you have code that uses a variable (like your loop counter n or your count variable) at a later point (i.e. after the full execution of the function), it will have its value set to the last value for the loop. You need to create a closure (a new scope for the variable) inside of your loop. Something like this (since you didn't post your actual code):
for(var i = 0, l = list.length; i < l; i++) {
(function(count) {
something.onclick = function() {
openSomething("Value_" + count);
}
})(i);
}
For a more modern approtce use let,
works for firefox, chrome, and node
if you need to target all the browsers, use Anthony approach
for(var count = 0, l = list.length; count < l; count++) {
let count;
something.onclick = function() {
openSomething("Value_" + count);
}
}

Variable Becomes Undefined in Last Iteration of Loop

I have a application where I am getting route info from one location to another, possibly between multiple locations. In my function, I'm looping over checked locations and writing out the directions to the page with jQuery. On the last iteration of the loop the 'myRoute' variable becomes undefined. So, if I have three locations, it bombs on the third, but if I use those same three and add a fourth, the first three works and the fourth bombs. I traced the behavior in Firebug, and while the myRoute variable does get filled properly, as soon as it moves to the next line, it is all of a sudden undefined. I removed instances of myRoute and replaced with directionResult.routes[0].legs[i] but still got the undefined error. What's going on here?
function setInstructions(directionResult, idname, start) {
//clean tour list
$('#tours_list .tour').remove();
$('#routeTitle').show();
var checkboxArray = $(".selector.Favs" + idname).find("li");
var idx = 0;
var linkMap = $('#routeTitle').find('.link-map')[0];
linkMap.href = __mapLink+'saddr=' + start;
var firstStop = true;
//iterate selected properties
for (var i = 0; i < checkboxArray.length; i++) {
var curChk = checkboxArray[i];
if ($(curChk).hasClass('active')) {
//get steps
var myRoute = directionResult.routes[0].legs[i]; //this is what becomes undfined
var title = $('<div>').addClass('mileage').append($('<p>').append($('<strong>').html(myRoute.distance.text + "<br /> about " + myRoute.duration.text)));
var ol = $('<ol>').addClass('directions');
for (var j = 0; j < myRoute.steps.length; j++) {
var step = myRoute.steps[j];
var li = $('<li>').append($('<div>').addClass('direction').html(step.instructions));
li.append($('<div>').addClass('distance').html(step.distance.text + " - " + step.duration.text));
ol.append(li);
}
//add tour with directions
$('#tours_list').append(temp);
}
}
}
The issue is that the array in directionResult.routes[0].legs is not as long as checkboxArray.length so your code is trying to access beyond the end of directionResult.routes[0].legs and thus gets undefined.
It isn't helping you that you are testing for only active class items because i goes from 0 to checkboxArray.length - 1 regardless.
I don't follow exactly what you're trying to do, but you might be able to work around this by only iterating the items that have .active in the first place so i never goes beyond the number of active items. You might be able to do that by changing this:
var checkboxArray = $(".selector.Favs" + idname).find("li");
to this:
var checkboxArray = $(".selector.Favs" + idname).find("li.active");
And, then remove the if check for the active class. This will make it so your index i never goes higher than the number of active items.
You could also just keep a counter of the active items you've processed and use that counter to index into directionResult.routes[0].legs[cntr] instead of using i.

Categories