Create a loop for adding content - javascript

I'm not that experienced in javascript and totally new to the whole loop idea but I know it can save a lot of code. So I have been trying to create a loop. This is my problem: I find every variable in the first line and want to add a value to the A. I know $(this) doesn't do the trick but I can't come up with something better what works.
Basically, I want for the first variable amount[0] and for the second amount[1] and so on..
This is what I have so far..
$(a).each(function() {
$(this).find("a").after( (" (" + amount[0] + ")"));
});

So I assume you want somthing like this:
$('a').each(function(index) {
if (index >= amount.length) return; // avoind IndexOutOfBounds Exception
$(this).after( " (" + amount[index] + ")"); // $(this) will refer to a link, in this each
});
After each link (a) you want to add a '(text_from_amount)' where text_from_amount is taken from amount array?
More about each, can be found here.

First of all, you're using jQuery, not native Javascript.
The $(selector).each function may take two parameters: The first one is the loop counter and the second one is the callback.
(Use the counter of the each() method, assuming that the amount array has all those entries. The better aproach would be to use a conditional to check if amount[counter] exists)
$(a).each(function(counter, value) {
//conditional to check array entry
if (amount[counter] !== undefined) {
$(value).find("a").after('(' + amount[counter] + ')');
}
});
The $.each() function (without selector) may be used to iterate over arrays or an array of objects:
// An array
var myArray = ['one', 'two', 'three'];
$.each(myArray, function (counter, value) {
console.log(counter);
console.log(value);
});
// An array of objects
var myArrayObject = [
{
one: 'foo',
two: 'bar'
},
{
one: 'another foo',
two: 'another bar'
}
];
$.each(myArrayObject, function (counter, value) {
console.log(counter);
console.log(value.one);
console.log(value.two);
});
See the docs for more information.

Related

How to pass a function only after an entire array has been read and not for each element?

I'm trying to run a function when reading an array, but instead of running the function to each element, which I'm currently using forEach for, I want to make the script to read the entire array and then pass a function.
What I'm trying to say is this:
data.forEach(movie => {
// Log each movie's title
//console.log(movie.title);
// Check if the userWord matches
if (movie.title.toUpperCase().includes(userWord.toUpperCase())) {
alert("YES");
} else {
alert("NO").
}
});
Let's say my array is: array = ["Aa", "Ba", "Ca", "Da"];
If the user enters: a, then the script would alert("YES") four times, and I want to make it alert just once, at the end of the iteration.
For the same example, if the users enters: B, then the script would first alert("NO") once, then alert("YES"), then alert("YES") 2 times, and I want to make it just alert("YES")once, in the end.
Finally, if the users enters: Ferrari, then the script would alert("NO") four times, and I just want it to alert("NO") at the end.
I tried to make it very clear here, that's why the three "cases" of what is happening.
In the end, I want to know if there is a method that is kinda the opposite of the forEach or the common for. Something that just executes the function after reading the entire array.
Change the alert to a bool variable
Remove else (it would only overwrite any match)
if bool statement outside the loop to perform actions
if you want a list of the results, you should store the names in an array and outside of the loop - print.
see below:
Non-loop method:
data = ["test","hello", "hello1"];
search = "lo";
const matches = data.filter(movie => movie.includes(search));
alert(matches) //array of only matches - only has hello and hello 1
I don't know if there are performance gains against a loop... I suppose you could do a side by side comparison on a large dataset
Loop method:
var matches = "";
data.forEach(movie => {
// Check if the userWord matches
if (movie.title.toUpperCase().includes(userWord.toUpperCase())) {
matches += movie.title + "<br> ";
}
});
if (matches.length > 0)
{
document.getElementById("results").innerHTML = matches;
} else {
alert("No match found");
}
You'll see more customization on the first loop, but I think filtering data first is the way to go.
I think closest what you can get is some. Here is example
let data = ["test","hello", "hello1"];
let userWord = "el";
let res = data.some(movie => movie.toUpperCase().includes(userWord.toUpperCase()));
console.log(res) // prints true- means there was at least one "match", so you can alert
You could use the filter array function to filter array elements that match your criteria and then finally alert only if a match is found. Using this method you could get all the elements that have matched to your userWord. That is, the match array will contain all the possible matches.
var data = ['Aa', 'Bb', 'Cc', 'Dd'];
var flag = false;
var userWord = 'F'
var match = data.filter(movie => movie.indexOf(userWord) !== -1);
if(match.length)
console.log('YES');
else
console.log('NO');
If I understand the question correctly, I believe you want to execute something if a predicate matches at least one of the items in the array (correct me if I'm wrong). For that you can use the some method like so:
if (movies.some((movie) => movie.toUpperCase().includes(userWord.toUpperCase()))) {
alert('YES');
} else {
alert('NO');
}

Jasmine testing runs code in illogical order when iterating over mutliple results in a div?

I'm trying to write a test to grab the contents of a div and compare them with my expectation.
I know I can expect multiple results, so I've been trying to iterate over the results one at a time and concatenate them into one string which I can then compare against my expectation.
Code snippets below don't seem to work as expected. My console returns "result to be returned: " with no results before it returns "result INSIDE while: 1" and "result INSIDE while: 1 2".
This doesn't make any sense to me... Hopefully i've explained well enough what i'm trying to do. The code isn't great at the moment because i've been chopping and changing bits to test what is happening.
A 'tl;dr' summary of what i'm hoping someone can help with :)
How can I iterate over each item returned by the element.all line and concatenate the values into a string for the function to return to my test?
Can anyone help me understand why the if/else appears to be true when i has been set to 0 beforehand (I'm sure that items.length is 2 in this case)
Can anyone point me to a good place to read up on promises? I have a feeling my poor understanding of promises is holding me back...
Test:
var resultPage = require('../page/result.page.js');
var ExpectedArticle1Details = browser.params.articleInTransitInTransit[0];
var ExpectedArticle2Details = browser.params.articleNotCompliantPossibleDelay[0];
var ExpectedIDs = ' ' + ExpectedArticle1Details.TrackingID + ' ' + ExpectedArticle2Details.TrackingID
expect(resultPage.allTrackingIDsAreVisible()).toEqual(ExpectedIDs);
resultPage:
this.allTrackingIDsAreVisible = function () {
var result = "";
element.all(by.css(trackingIDs)).then(function (items) { var i = 0;
while (i <= items.length) {
if (i === items.length) {
console.log("result to be returned: " + result);
return result;
}
else
{
items[i].getAttribute("innerHTML").then(function (value){result += value; console.log("result INSIDE while: " + result)})
}
i = i + 1;
}
})
};
I'll explain why that happens first, then suggest a solution.
you call a while loop, which always does something async (a promise), so it always adds it to the queue, and then goes and does the next line synchronously, that would be i+=1;, so the loop ends before all the promises could resolve, result didn't even change yet, and it already returned empty.
What you should use, is a recursive function on element.all(by.css(trackingIDs)), whith the signature:
addToResult(elementArray, i, result) {};
and each time you do:
items[i].getAttribute("innerHTML").then(function (value){
addToResult(elementArray, i + 1, result += value)
})
dont forget to stop the recursion when i === elementArray.length, and then return result
Simple way to solve this problem is by using protractor's inbuilt .each() function, rather than looping through the elements using for or while loops. Here's how -
element.all(by.css(trackingIDs)).each(function (eachItem){
eachItem.getAttribute("innerHTML").then(function (value){
addToResult(elementArray, i + 1, result += value);
});
});
Now there are chances that the above data isn't synchronised and may vary in the order which usually occurs because of the protractor's async nature. In such case, use .map() function to retrieves the data in synchronised form. Here's an example -
element.all(by.css(trackingIDs)).map(function (items){
return items.getAttribute("innerHTML").then(function (value){
return value;
});
}).then(function(val){
addToResult(elementArray, i + 1, result += val);
});
more details about each function
more details about map function
Hope it helps.

how to save the same object type in an array

I want to save the same object type in an Array()
For example when I want to select all checkboxes and save the value in an Array:
selectAllCbs: function() {
var self = this;
$('#' + instanceName + 'table_form input[type=checkbox]').each(function(index,cb) {
if($(cb).prop('disabled') == false) {
$(cb).prop('checked',true);
console.log("Select all clicked:" + cb); //gives me [object HTMLInputElement] result
selected.push(cb); //save to an array
self.changeRowStyle(cb);
}
});
When I use almost the same code when user clicks on the row I'm getting different type:
rowClicked: function(id) {
var cb = $('#' + instanceName + 'cb_' + id);
if($(cb).is(':checked')) {
$(cb).prop('checked',false);
selected.splice(selected.indexOf(this.value),1);
} else {
$(cb).prop('checked',true);
console.log("Row clicked:" + cb); //gives me [object Object]
selected.push(cb);
}
this.changeRowStyle(cb);
},
So the main question is how to save the rowClicked element in the [object HTMLInputElement] type?
Because in the first example I can read values with selected[i].value and with the second one I need to read it with the selected[i][0].value.
In rowClicked, replace:
selected.push(cb);
With:
selected.push(cb[0]);
And do the same in rowClicked for this.changeRowStyle(cb[0]) method
What may be confusing you is the comparison of your array elements at a later stage. You cannot compare two JQuery objects, but you can compare two DOM Elements.
JQuery wraps everything up as a JQuery object in order to super-charge it with all that sexy functionality. What you are storing in the first lot of code is a DOM element, because you don't wrap cb in the JQuery selector method. In the second lot of code, you're storing a JQuery selector.
In order to get the DOM element for the second lot of code, simply use the get method at the end of your rowClicked function:
this.changeRowStyle(cb.get(0));
And, obviously, change your push arguments for this function to be the same:
selected.push(cb.get(0));

Build a switch based on array

I want to create a Javascript switch based on an array I'm creating from a query string. I'm not sure how to proceed.
Let's say I have an array like this :
var myArray = ("#general","#controlpanel","#database");
I want to create this...
switch(target){
case "#general":
$("#general").show();
$("#controlpanel, #database").hide();
break;
case "#controlpanel":
$("#controlpanel").show();
$("#general, #database").hide();
break;
case "#database":
$("#database").show();
$("#general, #controlpanel").hide();
break;
}
myArray could contain any amount of elements so I want the switch to be created dynamically based on length of the array. The default case would always be the first option.
The array is created from a location.href with a regex to extract only what I need.
Thanks alot!
#Michael has the correct general answer, but here's a far simpler way to accomplish the same goal:
// Once, at startup
var $items = $("#general,#controlpanel,#database");
// When it's time to show a target
$items.hide(); // Hide 'em all, even the one to show
$(target).show(); // OK, now show just that one
If you really only have an array of selectors then you can create a jQuery collection of them via:
var items = ["#general","#controlpanel","#database"];
var $items = $(items.join(','));
Oh, and "Thanks, Alot!" :)
I think you want an object. Just define keys with the names of your elements to match, and functions as the values. e.g.
var switchObj = {
"#general": function () {
$("#general").show();
$("#controlpanel, #database").hide();
},
"#controlpanel": function () {
$("#controlpanel").show();
$("#general, #database").hide();
},
"#database": function () {
$("#database").show();
$("#general, #controlpanel").hide();
}
}
Then you can just call the one you want with
switchObj[target]();
Granted: this solution is better if you need to do explicitly different things with each element, and unlike the other answers it focused on what the explicit subject of the question was, rather than what the OP was trying to accomplish with said data structure.
Rather than a switch, you need two statements: first, to show the selected target, and second to hide all others.
// Array as a jQuery object instead of a regular array of strings
var myArray = $("#general,#controlpanel,#database");
$(target).show();
// Loop over jQuery list and unless the id of the current
// list node matches the value of target, hide it.
myArray.each(function() {
// Test if the current node's doesn't matche #target
if ('#' + $(this).prop('id') !== target) {
$(this).hide();
}
});
In fact, the first statement can be incorporated into the loop.
var myArray = $("#general,#controlpanel,#database");
myArray.each(function() {
if ('#' + $(this).prop('id') !== target) {
$(this).hide();
}
else {
$(this).show();
}
});
Perhaps you're looking for something like this? Populate myArray with the elements you're using.
var myArray = ["#general","#controlpanel","#database"];
var clone = myArray.slice(0); // Clone the array
var test;
if ((test = clone.indexOf(target)) !== -1) {
$(target).show();
clone.splice(test,1); // Remove the one we've picked up
$(clone.join(',')).hide(); // Hide the remaining array elements
}
here you dont need to explicitly list all the cases, just let the array define them. make sure though, that target exists in the array, otherwise you'll need an if statement.
var target = "#controlpanel";
var items = ["#general","#controlpanel","#database"];
items.splice($.inArray(target, items), 1);
$(target).show();
$(items.join(",")).hide();
items.push(target);

Binding listeners inside of a for loop : variable scope miscomprehension

I've a variable scope problem and I don't understand why this occurs and how to get rid of it :
var items = ['foo', 'bar'];
for (var index in items) {
var item = items[index];
var selector = '.'+item+'-class';
$(selector).bind('click', function() {
console.log("class: "+$(this).attr('class'));
console.log("selector: "+selector);
console.log("item: "+item);
});
}
Considers that this code execute itself over the following HTML :
<div class="foo-class">Foo</div>
<div class="bar-class">Bar</div>
Clicking on "Foo" echoes the right class (i.e. "foo-class") in the first line but the selector and the item name following are related to bar. I think that the problem is that the second iteration of the loop reset the variables used in the first one.
I thought that the declaration inside of the loop should clearly declare their scope at this level. Am I wrong ? Why ? How can I fix it ?
I'm not seeking a workaround, I want something clean and a better comprehension of javascript variable scope mecanism.
Here the jsfiddle.
Thanks !
Here's your fiddle example updated.
var items = ['foo', 'bar'];
for (var index in items) {
(function() {
var item = items[index];
var selector = '.' + item + '-class';
$(selector).bind('click', function() {
console.log("class: " + $(this).attr('class'));
console.log("selector: " + selector);
console.log("item: " + item);
});
})();
}​
Creating an anonymous function will define a new scope for each of your defined variables
TIP: Try to create a separate function to do the bind, just to keep your code cleaner.
It's always the same with these for-loops (google it). JavaScript does not have block scope but function scope, so when an item is clicked the one variable selector has the value it had after the last loop run (same for the variable item).
To solve the problem, you need another closure in you loop which stores the variables in its own scope. That means you need to execute a function for each loop run.
The issue is not strictly about variable scope. The anonymous function runs at the time the click event is triggered, not when you are defining it in the loop. Consider the following which is functionally identical to your example:
var items = ['foo', 'bar'];
for (var index in items) {
var item = items[index];
var selector = '.'+item+'-class';
$(selector).bind( 'click', test );
}
​
function test() {
console.log("selector: "+selector);
}
This (hopefully) demonstrates what's happening: the global variable selector in the function is, at the time the function is being called, the same in both cases ("bar").
var items = ['foo', 'bar'];
for (var index in items) {
(function(i){
var item = items[i];
var selector = '.'+item+'-class';
$(selector).bind('click', function() {
console.log("class: "+$(this).attr('class'));
console.log("selector: "+selector);
console.log("item: "+item);
});
})(index);
}
Fiddle here.
Vars "selector" and "item" are references to a location where you store values, and the values of those two at the moment you click one of the htl elements is the one frome the last loop.

Categories

Resources