Performance increase, code optimization of JQuery + json array code - javascript

By looking into many resources, I've figured out how to write following code, which process json array (data) and with 250ms delay shows result and adds class 'petstatusok' (or 'petstatusno') to existing element with class 'pet-items':
if (data.cat & data.cat !== 'null') {
setTimeout(function() {
$('.pet-items:nth-child(2)').addClass('petstatusok');
$('#label-cat').val((data.cat).trim());
}, 250);
} else {
setTimeout(function() {
$('.pet-items:nth-child(2)').addClass('petstatusno');
}, 250)
}
if (data.dog & data.dog !== 'null') {
setTimeout(function() {
$('.pet-items:nth-child(3)').addClass('petstatusok');
$('#label-dog').val((data.dog).trim());
}, 500);
} else {
setTimeout(function() {
$('.pet-items:nth-child(3)').addClass('petstatusno');
}, 500)
}
and so on... I have 24 blocks like this...
I do believe that there is a place to optimize the code...
My thoughts:
Is there any possibility to have a loop to reduce the number of code lines/increase performance?
Is there any possibility with setTimeout(function () to place it somewhere in code only once?
Any other opportunities?
Thank you!

You can use a simple forEach and calculate the child number and delay based on the index.
As #Ray said is his answer, null is not a string (unless your JSON is wrong) so you should remove the quotes and use && in this case. You actually don't even need to check for the null because it is falsy so if (data[pet]) will suffice.
I have assumed that cat is the first 'pet' child in .pet-items.
const pets = [
'cat',
'dog',
]
pets.forEach((pet, index) => {
const child = index += 2
const delay = (index + 1) * 250
if (data[pet]) {
setTimeout(function() {
$(`.pet-items:nth-child(${child})`).addClass('petstatusok');
$(`#label-${pet}`).val(data[pet].trim());
}, delay);
} else {
setTimeout(function() {
$(`.pet-items:nth-child(${child})`).addClass('petstatusno');
}, delay)
}
})

First create a function that can be called multiple times, with all the repeatable variables being used as input parameters - petType (name of the pet, which is a string) and index, which starts from 1.
function setStatusAndLabel(petType, index){
if (data[petType] && data[petType] !== null){
setTimeout(function() {
$('.pet-items:nth-child('+index+')').addClass('petstatusok');
$('#label-'+petType).val((data[petType]).trim());
}, 250);
} else {
setTimeout(function() {
$('.pet-items:nth-child('+index+')').addClass('petstatusno');
}, 500)
}
}
Next,
If data is an object with exclusively 24 items and nothing else, you could do a for loop through data like this:
var counter = 1;
for (var petType in data) {
if (data.hasOwnProperty(petType)) {
setStatusAndLabel(petType.toString(), counter++);
if (counter === 24){
// so that it doesn't continue to loop through the object's prototype methods, if any, after your work is done.
break;
}
}
}
OR
If data has other stuff in addition to the 24 items, then you need to specify what the required items are. You can do this with an array, then loop through it.
var requiredItems = ['cat', 'dog', 'cow',... (so on)];
requiredItems.forEach(function(petType, index){
setStatusAndLabel(petType, index);
});
(End)
By the way,
you should check for null like this data[dataType] !== null and not like this data[dataType] !== 'null'
& is the bitwise operator and && is the logical operator. You should use &&.

Related

Removing element from array in class method

I was working on a hackerrank problem and test cases showed that something was wrong with my 'remove' method. I always got undefined instead of true/false.
I know splice returns array of deleted elements from an array. When I console.log inside map, it looked like everything was fine when I was deleting first element (I was getting what I expected except true/false). But when 'name' I am deleting is not first element, I didn't get what I expected to get. Could you help me fix this? And of course, I never get true or false...
class StaffList {
constructor() {
this.members = [];
}
add(name, age) {
if (age > 20) {
this.members.push(name)
} else {
throw new Error("Staff member age must be greater than 20")
}
}
remove(name) {
this.members.map((item, index) => {
if(this.members.includes(name)) {
console.log(name)
let removed = this.members.splice(item, 1);
console.log(removed)
return true;
} else {
return false;
}
})
}
getSize() {
return this.members.length;
}
}
let i = new StaffList;
i.add('michelle', 25)
i.add('john', 30);
i.add('michael', 30);
i.add('jane', 26);
i.remove('john');
console.log(i);
Your return statements are wrapped within .map() (which you misuse in this particular case, so you, essentially, build the array of true/false), but your remove method does not return anything.
Instead, I would suggest something, like that:
remove(name){
const matchIdx = this.members.indexOf(name)
if(matchIdx === -1){
return false
} else {
this.members.splice(matchIdx, 1)
return true
}
}
In the remove method, you're using map with the array, which runs the function you give as argument for each array element. But I believe you don't want to do that.
Using the example you have bellow, basically what you do there is check if the array contains the name 'john', and if so, you delete the first item that appears in the array (which would be 'michelle'). This happens because the map function will run for every element, starting on the first one, and then you use that item to be removed from the array. After that, it returns the function, and no other elements get removed.
So my suggestion is just getting rid of the map function and running its callback code directly in the remove method (you would need to get the name's index in the array to use the splice method).
It is not clear why you need to use iterative logic to remove an item. You can simply use findIndex() to get the position of the member in the array. If the index is not -1, then you can use Array.prototype.slice(index, 1) to remove it. See proof-of-concept example below:
class StaffList {
constructor() {
this.members = [];
}
add(name, age) {
if (age > 20) {
this.members.push(name)
} else {
throw new Error("Staff member age must be greater than 20")
}
}
remove(name) {
const index = this.members.findIndex(x => x === name);
if (index !== -1) {
this.members.splice(index, 1);
}
}
getSize() {
return this.members.length;
}
}
let i = new StaffList;
i.add('michelle', 25)
i.add('john', 30);
i.add('michael', 30);
i.add('jane', 26);
i.remove('john');
console.log(i);
Use a filter method instead of map it's more elegant and you can return the rest of the array as well instead of true or false unless the problem you're working on requires true of false specifically.
You could write something like this:
remove(name) {
if (!this.members.some(el => el === name)) return false;
this.members = this.members.filter(item => item !== name);
return true;
}

When boolean turns false, check 5 seconds if it will turn back to true, else do some action

I have in NodeJS a variable that updates every second. I want to monitor it to see if it turns below a certain threshold (e.g. 1000).
If it does go below the threshold, it should wait 5 seconds and monitor if the variable goes back above again. If not, it should return a function. If it does go above, it can stop the times and start monitoring again.
Can't get any code to work.
Not sure if the code below is even in the right direction..!
var waitedSoFar = 0;
var imageDisplayed = CheckIfImageIsDisplayed(); //this function is where you check the condition
while(waitedSoFar < 5000)
{
imageDisplayed = CheckIfImageIsDisplayed();
if(imageDisplayed)
{
//success here
break;
}
waitedSoFar += 100;
Thread.Sleep(100);
}
if(!imageDisplayed)
{
//failed, do something here about that.
}
You could have a function that observe and wrap this variable that change so often. Something as simple as
function observeValue(initialValue) {
let value = initialValue
let clearEffects = []
let subscribers = []
return {
value: () => value,
change(val) {
clearEffects.forEach(oldEffect => oldEffect && oldEffect())
value = val
clearEffects = subscribers.map(subscriber => subscriber(value))
},
subscribe(listener) {
subscribers.push(listener)
}
}
}
is generic enough. The function returns three methods:
one to get the current value
one to change the value
on to subscribe on every value change
I would suggest to leverage the last one to monitor and trigger any side effects.
You can use it like this:
const monitor = observeValue(true)
monitor.subscribe((value) => {
if (value !== false) {
return
}
const timeout = setTimeout(() => {
console.log('value was false for five seconds')
}, 5000)
return () => {
clearTimeout(timeout)
}
})
and somewhere else you can change the value with
monitor.change(false)

Check if an ES6 class exist by name

I'm lazy loading part of my code, some file containing classes that are then called in other parts, so I wrote a simple code that checks if a class instance exists and then execute it, or retry after 100ms.
let checkExist = setInterval(function() {
if (typeof(ClassInstanceName) === "object") {
ClassInstanceName.DoSomething();
clearInterval(checkExist);
}
}, 100);
This code works, but I need to generalize it in a self contained function
function WaitForClass(args) {
let instance = (args.instance === undefined ? "" : args.instance);
let callback = (args.callback === undefined ? "" : args.callback);
if (instance == "" || callback == "") {
console.log("Error -> " + instance + " " + callback);
}
else {
if (document[instance]) {
//it never find the instance
callback();
}
else {
setInterval(function() {
WaitForClass(args);
}, 100);
}
}
}
WaitForClass({instance:"ClassInstanceName", callback:function(){ ClassInstanceName.DoSomething();}});
The function is simple, but can't make it work.
For compatibility with other code portions I need to check if the instance exist using a string containing the name, and not just the object representing it.

jquery each as 'named function'

I want to use this syntax
var x = function() {
//do something here
}
on an each function
$("#firstname, #surname").each(function( index ) {
if ($(this).val() == '') {
errors += 1;
}
});
Is this possible, and what is this called (I called it 'named functions' is this right)?
EDIT
So something like this
var errors = $("#firstname, #surname").each(function( index ) {
if ($(this).val() == '') {
errors += 1;
}
return errors;
});
console.log("you have".errors()."errors");
When a function is used like this:
$(...).each(function() {
...
});
it is called an anonymous function or lambda function.
I have never heard the term "named function" but I guess you could call it like that, when you assign the function to a variable.
jQuery.each returns like nearly all jQuery-methods the first argument, which is the equivalent of $("#firstname, #surname"). This is very important to understand as it is one of the key-mechanics of jQuery and enables you to do cool stuff like method chaining, e.g. $("#firstname, #surname").each(do something).each(do even more). That means, you can not return your errors in the each method and pass it to a variable outside of the construct.
You could increment a global variable:
var errors = 0
$("#firstname, #surname").map(function() {
if ($(this).val() == '') {
errors++
}
});
console.log("you have" + errors + "errors");
Note the string concatenator +, not ..
Another idea might be to use the jQuery.map method, which indeed enables you to return a value:
var errors = $("#firstname, #surname").map(function() {
if ($(this).val() == '') {
return 1
}
return 0
});
But this would just leave you with an array of 1 and 0 elements which is more or less the same with what you have started. You would need to add another loop to summ the elements together.
Finally let's get to the jQuery.filter method, which should be just what you need:
var errors = $("#firstname, #surname").filter(function() {
return $(this).val() == "";
}).length;
console.log("you have " + errors + " errors");
Filter will return only those elements where the passed function returned a truthy value, so in this case, all elements that had an empty value. You could not only count the elements but immediately highlight the empty form elements:
var errors = $("#firstname, #surname").filter(function() {
return $(this).val() == "";
}).css("background-color", "red").length;
console.log("you have " + errors + " errors");
Your example has a simple solution because you can just use filter and observe the length of the result, but in general, reduce() is the operation to use when you want to aggregate something about all of the items in a collection into a single value.
jQuery doesn't have a reduce() method, but ES5 arrays do, so you could use it like this:
var errorCount = $("#firstname, #surname").toArray().reduce(function(count, item) {
return count + ($(item).val() === '' ? 1 : 0);
}, 0);
As already stated, filter() is what you should be using here, but hopefully this will help you at some point in the future.

How to slow down a loop with setTimeout or setInterval

I have an array called RotatorNames. It contains random things but let's just say that it contains ["rotatorA","rotatorB","rotatorC"].
I want to loop through the array, and for each item i want to trigger a click event. I have got some of this working, except that the everything get's triggered instantly. How can i force the loop to wait a few seconds before it continues looping.
Here's what i have.
function Rotator() {
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
RotatorNames.forEach(function(entry){
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000);
});
}
You can run this to see what my issue is. http://jsfiddle.net/BxDtp/
Also, sometimes the alerts do A,C,B instead of A,B,C.
While I'm sure the other answers work, I would prefer using this setup:
function rotator(arr) {
var iterator = function (index) {
if (index >= arr.length) {
index = 0;
}
console.log(arr[index]);
setTimeout(function () {
iterator(++index);
}, 1500);
};
iterator(0);
};
rotator(["rotatorA", "rotatorB", "rotatorC"]);
DEMO: http://jsfiddle.net/BxDtp/4/
It just seems more logical to me than trying to get the iterations to line up correctly by passing the "correct" value to setTimeout.
This allows for the array to be continually iterated over, in order. If you want it to stop after going through it once, change index = 0; to return;.
You can increase the timeout based on the current index:
RotatorNames.forEach(function(entry, i) {
window.setTimeout(function() {
//Trigger that elements button.
var elemntBtn = $('#btn_' + entry);
elemntBtn.trigger('click');
}, 5000 + (i * 1000)); // wait for 5 seconds + 1 more per element
});
Try:
var idx = 0;
function Rotator() {
var RotatorNames = ["rotatorA", "rotatorB", "rotatorC"];
setTimeout(function () {
console.log(RotatorNames[idx]);
idx = (idx<RotatorNames.length-1) ? idx+1:idx=0;
Rotator();
}, 5000);
}
Rotator();
jsFiddle example
(note that I used console.log instead of alert)
Something like this should do what you're after:
function Rotator(index){
var RotatorNames = ["rotatorA","rotatorB","rotatorC"];
index = (index === undefined ? 0 : index);
var $btn = $("#btn_"+RotatorNames[index]);
$btn.click();
if(RotatorNames[index+1]!==undefined){
window.setTimeout(function(){
Rotator(index+1);
}, 500);
}
}

Categories

Resources