I have the following code:
if (array.indexOf("undefined")===-1){
do something...
}
My initial arrays is this:
array=[,,,,,,];
It gets filled with values as the program goes on but i want the function to stop when there are no undefined spaces. The above syntax though is not correct. Anybody can tell me why.
Your code looks for the string "undefined". You want to look for the first index containing an undefined value. The best way is to use findIndex:
if(array.findIndex(x=>x===undefined) !== -1) {
//do something
}
for(var ind in array) {
if(array[ind] === undefined) {
doSomething();
}
}
Your check didn't work because you passed the string "undefined" instead the the value itself. Also, .indexOf() is designed to explicitly ignore holes in the array.
It seems wasteful to use iteration to detect holes in the array. Instead you could just track how many holes have been filled by using a counter, and execute your code when the counter matches the length.
Either way, the proper way to detect a hole at a particular index is to use the in operator.
Here's a class-based solution for reuse:
class SpacesArray extends Array {
constructor(n, callback) {
super(n);
this.callback = callback;
this.counter = 0;
}
fillHole(n, val) {
if (!(n in this)) {
this.counter++;
console.log("filling hole", n);
}
this[n] = val;
if (this.counter === this.length) {
this.callback();
}
}
}
// Usage
var sa = new SpacesArray(10, function() {
console.log("DONE! " + this);
});
// Emulate delayed, random filling of each hole.
for (let i = 0; i < sa.length; i++) {
const ms = Math.floor(Math.random() * 5000);
setTimeout(() => sa.fillHole(i, i), ms);
}
Related
I am quite new to JavaScript and have a task for generating numbers in order of the functions being called with the use of callbacks.
The exercise should be done with a variable outside of the functions which should hold the returned values.
The starting code is as follows:
function asynchronousResponse(value, callback) {
var delay = Math.floor(Math.random() * 10) * 1000;
setTimeout(() => callback(value), delay);
}
function getAsyncNumber(number) {
/*
Add implementation of getAsyncNumber function in a way that numbers
appear on a console in order in which they have been called.
Use asynchronousResponse to generate this responses.
*/
}
getAsyncNumber(0);
getAsyncNumber(1);
getAsyncNumber(2);
getAsyncNumber(3);
getAsyncNumber(4);
I am not clear on how this is supposed to work and how to check if the previous numbers have alreay been called out.
The callback inside setTimeout is going to run in an async manner. Making use of the asynchronousResponse we can pass our own function as a callback. Notice the statement setTimeout(() => callback(value), delay);, this callback function will have access to our value and we can make use of that.
The implementation:
Now the idea is to keep storing the values as the functions are called. That way we have the definite order of our calls to getAsyncNumber. I think this refers to the variable outside the function which holds the returned values. We also keep a field for the status of the items. So we have an array of objects like {status,value}.
We update our array as our callbacks are called, but never log the resolved values. We only log something based on certain conditions. ONLY WHEN IT IS READY. Any resolved value will only be ready when everything before it is either already logged or in READY
state.
Whenever any callback is called and returns a value, we check whether everything before the item is done, if yes, then we log the resolved value. If everything before it is in ready state, we log them too and then log our resolved value.
There is an edge case, where some items later in order are ready but they do not get logged. So as a cleanup, we keep track of the number of resolved values too using a counter variable. If all our items have been resolved then we just log everything in the array that is still in READY state.
Here is a working code:
function asynchronousResponse(value, callback) {
var delay = Math.floor(Math.random() * 10) * 1000;
setTimeout(() => callback(value), delay);
}
const vals = [];
let resolved = 0;
function getAsyncNumber(number) {
vals[vals.length] = {status : "NOT_READY", number };
const func = () => {
resolved++;
let ready = 0;
let found = -1;
for (let i = 0 ; i < vals.length;i++) {
if (vals[i].number === number) {
found = i;
if (ready === i) {
vals[i] = {status : "DONE", number : vals[i].number}
for (let j= 0 ; j < i;j++) {
if (vals[j].status === "READY") {
vals[j] = {status : "DONE", number : vals[j].number}
console.log(vals[j].number);
}
}
console.log(number);
} else {
vals[i] = {status : "READY", number : vals[i].number}
}
} else if(found === -1) {
if (vals[i].status === "READY" || vals[i].status === "DONE" ) {
ready++;
}
}
}
if (resolved === vals.length) {
for (let i = 0 ;i < vals.length;i++) {
if (vals[i].status === "READY") {
console.log(vals[i].number);
}
}
}
}
asynchronousResponse(number,func);
}
getAsyncNumber('a');
getAsyncNumber('b');
getAsyncNumber(2);
getAsyncNumber(3);
getAsyncNumber(4);
getAsyncNumber('5');
I am wondering if there is anyway for me to correct my code so that I can return the object I need to my original calling function.
onAccountSelected calls my recursive function. Is there any way here for me to get my final match inside of my initial calling function - onAccountSelected - by returning that final match from my recursive function?
I have tried this:
private findMatch(accountNodeName, currentNode) {
if (currentNode.children) {
for (var i = 0; i < currentNode.children.length; i++) {
var current = currentNode.children[i];
if (accountNodeName === current.name) {
return current;
}
// return won't work here because I'm returning even if a match isn't found
return this.findMatch(accountNodeName, current);
// this gives me undefined a bunch and then the actual answer once a match is found
this.currentTreeNode = this.findMatch(accountNodeName, current);
// console.log(this.currentTreeNode);
}
}
}
private onAccountSelected(account) {
if (account.nodes) {
this.currentTreeNode = this.findMatch(account.nodes.name, this.gridTreeNodes.BssNode);
//getting undefined here, would like the matching node to come here
console.log(this.currentTreeNode); //getting undefined here
}
}
I try to use jquery(jquery-2.1.1) and constructor function to build html. But when I tried this method in for loop, the loop can't stop. Can anyone tell me why it happens?
Here' s the code. Thanks a lot.
function tag() {
this.addClass = function(...theArgs) {
for (index in theArgs) {
this.$html.addClass(theArgs[index]);
}
}
this.setAttr = function(attr, value) {
this.$html.attr(attr, value);
}
this.append = function(...theArgs) {
for (index in theArgs) {
this.$html.append(theArgs[index]);
}
}
this.find = function(value) {
return this.$html.find(value);
}
this.empty = function() {
this.$html.empty();
}
this.remove = function(value) {
this.find(value).remove();
}
this.clone = function() {
return jQuery.extend(true, {}, this);
}
this.show = function() {
return this.$html[0];
}
}
function label(text) {
tag.call(this);
this.$html = $("<label></label>");
this.append(text);
}
for(var index = 0; index < 2; index++) {
var fieldLabel = new label(1);
console.log(index);
}
The problem here is you use the index (without var) as the running variable for all loops in your tag function. That index variable is still effective in the outer scope of the for-loop at the end (which should stop with the condition >=2).
At the beginning of the loop, index is 0. The next loop it should be 1. But when going into the inner append method, it's reset back to 0 due to the loop for-in (the argument passed in is just 1, so the spread notation makes an array of 1 length, and for-in stops with index set to 0) . So at the end of the second loop it is still 0. That means it will never become greater the value 1 (which is increased only at the beginning of the for-loop). The condition < 2 will always be satisfied and the for-loop just runs forever.
You can either use another name for the running variable in for-in. Or just declare another local-scoped index by using var, like this:
this.append = function(...theArgs) {
for (var index in theArgs) {
this.$html.append(theArgs[index]);
}
}
Or better using for-of as someone suggested.
I need to pass certain parameters into a function and have that function pull from an array based on the arguments passed to it. It's hard to explain, so I'll show you what I'm trying to do.
function SearchDeck(deck,...){
var tryagain = true;
do{
if(deck[0].property == value){
//do something;
tryagain = false;
}
else{
deck.splice(0,1);
}
}
while(tryagain);
}
There are multiple decks to look in, the proper deck will be passed in. I want to always be drawing off the top of the deck (index 0 of the array). I need to draw continuously until I find a card that matches what I'm after. I splice out the 0 index if it doesn't match. What I'm after is dynamic, varying across the properties or even the operators I would use.
Some examples of if statements I would have are...
deck[0].color == "orange"
deck[0].value >= 5
deck[0].value < -4
I could make multiple functions or have the function fork based on an argument, but that doesn't seem like the best way to go about this.
If I'm understanding this correctly, you want the behavior of the if(deck[0].property == value) to be different for each invocation of the SearchDeck(...) function?
My recommendation would be to pass in a function:
function SearchDeck(deck, validationFunction, ...){
var tryagain = true;
do{
if(validationFunction(deck[0])){
//do something;
tryagain = false;
}
else{
deck.splice(0,1);
}
}
while(tryagain);
}
Then when you call the code, you can do:
SearchDeck(deck, function(firstCard) { return firstCard.color == "orange" }, ...);
SearchDeck(deck, function(firstCard) { return firstCard.value >= 5 }, ...);
SearchDeck(deck, function(firstCard) { return firstCard.value < -4 }, ...);
Or, if the cases you're looking for might be reused, it might also be cleaner to make those named functions:
function validateColor(firstCard) {
return firstCard.color == "orange";
}
function validateHighValue(firstCard) {
return firstCard.value >= 5;
}
function validateLowValue(firstCard) {
return firstCard.value < -4;
}
SearchDeck(deck, validateColor, ...);
SearchDeck(deck, validateHighValue, ...);
SearchDeck(deck, validateLowValue, ...);
It sounds like you may be interested in the typeof operator:
if (typeof deck == 'object') { ... }
if (typeof deck[0].color == 'string') { ... }
if (typeof deck[0].value == 'number') { ... }
Alternatively:
if (deck[0].hasOwnProperty('color')) { ... }
This is what I came up with. You need to push the way of check (either "== 'oragne'" or "<3") as string.
function searchDeck() {
var deck = Array.prototype.slice.call(arguments, 1),
tryagain = true,
string = deck[deck.length - 1];
deck.pop();
while (tryagain) {
if (eval('deck[0].property' + string)) {
//do something;
alert('card found');
tryagain = false;
} else {
deck.splice(0, 1);
}
}
}
Hope this is what you wanted ;)
Here is a working jsfiddle example, of course there might be a more elegant way, this is just what I came up with. Note that eval can be dangerous, so you should be carefull if user picks what the test will be (the string pushed into the array).
The jQuery documentation for the .toggle() method states:
The .toggle() method is provided for convenience. It is relatively straightforward to implement the same behavior by hand, and this can be necessary if the assumptions built into .toggle() prove limiting.
The assumptions built into .toggle have proven limiting for my current task, but the documentation doesn't elaborate on how to implement the same behavior. I need to pass eventData to the handler functions provided to toggle(), but it appears that only .bind() will support this, not .toggle().
My first inclination is to use a flag that's global to a single handler function to store the click state. In other words, rather than:
$('a').toggle(function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
});
do this:
var clicks = true;
$('a').click(function() {
if (clicks) {
alert('odd number of clicks');
clicks = false;
} else {
alert('even number of clicks');
clicks = true;
}
});
I haven't tested the latter, but I suspect it would work. Is this the best way to do something like this, or is there a better way that I'm missing?
Seems like a reasonable way to do it... I'd just suggest that you make use of jQuery's data storage utilities rather than introducing an extra variable (which could become a headache if you wanted to keep track of a whole bunch of links). So based of your example:
$('a').click(function() {
var clicks = $(this).data('clicks');
if (clicks) {
alert('odd number of clicks');
} else {
alert('even number of clicks');
}
$(this).data("clicks", !clicks);
});
Here is a plugin that implements an alternative to .toggle(), especially since it has been removed in jQuery 1.9+.
How to use:
The signature for this method is:
.cycle( functions [, callback] [, eventType])
functions [Array]: An array of functions to cycle between
callback [Function]: A function that will be executed on completion of each iteration. It will be passed the current iteration and the output of the current function. Can be used to do something with the return value of each function in the functions array.
eventType [String]: A string specifying the event types to cycle on, eg. "click mouseover"
An example of usage is:
$('a').cycle([
function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
}
]);
I've included a demonstration here.
Plugin code:
(function ($) {
if (!Array.prototype.reduce) {
Array.prototype.reduce = function reduce(accumulator) {
if (this === null || this === undefined) throw new TypeError("Object is null or undefined");
var i = 0,
l = this.length >> 0,
curr;
if (typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
throw new TypeError("First argument is not callable");
if (arguments.length < 2) {
if (l === 0) throw new TypeError("Array length is 0 and no second argument");
curr = this[0];
i = 1; // start accumulating at the second element
} else curr = arguments[1];
while (i < l) {
if (i in this) curr = accumulator.call(undefined, curr, this[i], i, this);
++i;
}
return curr;
};
}
$.fn.cycle = function () {
var args = Array.prototype.slice.call(arguments).reduce(function (p, c, i, a) {
if (i == 0) {
p.functions = c;
} else if (typeof c == "function") {
p.callback = c;
} else if (typeof c == "string") {
p.events = c;
}
return p;
}, {});
args.events = args.events || "click";
console.log(args);
if (args.functions) {
var currIndex = 0;
function toggler(e) {
e.preventDefault();
var evaluation = args.functions[(currIndex++) % args.functions.length].apply(this);
if (args.callback) {
callback(currIndex, evaluation);
}
return evaluation;
}
return this.on(args.events, toggler);
} else {
//throw "Improper arguments to method \"alternate\"; no array provided";
}
};
})(jQuery);