Promises and for loops - trying to validate multiple element text - javascript

I am working on a function that will read the text of elements after using a filter feature. I have printed out the returned text and it is getting the elements, however I do not think I understand js promises.. activeFilters is a var I have already identified.
this.verifyColorFilterFunctional = function(color) {
var bool = true;
activeFilters.count().then(function (count) {
var amt = count - 1;
for (var i = 0; i < amt; i++){
activeFilters.get(i).getText().then(function(text) {
bool = (color === text);
console.log(bool);
});
if (!bool) {
break;
}
}
});
return expect(bool).to.become(true);
};
The console.log prints out true and false as desired, however there are two things I have noticed. When false, it doesnt break like I told it to in the if statement. Also, I am getting a typeError: true is not a thenable error.. I believe the logic sounds good in my head but not to JS. Any help would be greatly appreciated.

Protractor's element.all() supports getText() method which will return you the text displayed in the elements as an array.Then you can easily compare the resultant array using expect method.
this.verifyColorFilterFunctional = function(color) {
activeFilters.getText().then(function (textArray) {
expect(textArray).to.equal(Array(textArray.length-1).fill(color));
});
}

Related

Using Javascript Array Filter method to apply logic [duplicate]

I have search through quite a lot of questions here, but havent found one that i think fits my bill, so if you know of one please link to it.
I have an array that i want to search through for a specific number and if that number is in the array, i then want to take an action and if not then another action.
I have something like this
var Array = ["1","8","17","14","11","20","2","6"];
for(x=0;x<=Array.length;x++)
{
if(Array[x]==8)
then change picture.src to srcpicture1
else
then change picture.src to srcpicture2
}
but this will run the lenght of the array and end up checking the last element of the array and since the last element is not 8 then it will change the picture to picture2.
Now i can see why this happens, i just dont have any ideas as to how to go about checking if an array contains a specific number.
Thanks in advance.
What you can do is write yourself a function to check if an element belongs to an array:
function inArray(array, value) {
for (var i = 0; i < array.length; i++) {
if (array[i] == value) return true;
}
return false;
}
And the just do:
var arr = ["1","8","17","14","11","20","2","6"];
if (inArray(arr, 8)) {
// change picture.src to srcpicture1
} else {
// change picture.src to srcpicture2
}
It's a lot more readable to me.
For extra points you can add the function to the array prototype like so:
Array.prototype.has = function (value) {
for (var i = 0; i < this.length; i++) {
if (this[i] === value) return true;
}
return false;
};
And then the call would be
if (arr.has(8)) // ...
Pushing this even further, you can check for indexOf() method on array and use it - if not - replace it with the code above.
P.S. Try not to use Array for a variable name, since it's reserved for the actual array type.
use this
http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/IndexOf
ie version
https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Array/IndexOf#Compatibility
Why don't just you abort the loop when you find the right number :
for(x=0;x<=Array.length;x++)
{
if(Array[x]==8) {
//change picture.src to srcpicture1
break;
}
}
You could sort the array first then check the array only up to the point at which a number would be in the array, were it to exist.
If you have unique keys and a faster retrieval is what you care about a lot, you can consider using a map instead of an array (if there's a hard-bound case of using an array, then it won't work of course). If using a map, you just check "if( num in arr ) ".

Check if array value is included in string

I'm working on some client side validation for a contact form of sorts, the website currently isn't online so server side isn't relevant.
I am trying to create a 'word filter' to catch on any abusive of obscene language before the form is 'submitted'.
Heres the code, without the obscenities...
function filterInput(str) {
var inputFilter = ['word1', 'word2', 'word3'];
var arrayLength = inputFilter.length;
if (inputFilter.indexOf(str) > - 1) {
// Word caught...
} else {
// Clear...
}
If the user were to enter 'word1', it will catch the word. If the user enters 'word1word2' or 'John is a word3', it doesn't catch it.
I originally had a for loop which worked better, but still wouldn't work without whitespace between words('word1word2').
Any input would be greatly appreciated, I've been searching but nothing quite matches my needs.
EDIT: So I too have come up with a solution, but seeing the varying ways this can be achieved I am curious as to how it works and also why a particular way is better?
Heres what I came up with...
function filterInput(str) {
var inputFilter = ['word1', 'word2', 'word3'];
var arrayLength = inputFilter.length;
for (var i = 0; i < arrayLength; i++) {
if (str.includes(inputFilter[i])) {
window.alert('Message...');
return;
}
}
}
You're looking for some rather than indexOf, since you have to do custom matching:
if (inputFilter.some(function(word) { return str.indexOf(word) != -1; })) {
// Word caught...
} else {
// Clear...
}
Or with an ES2015+ arrow function and String.prototype.includes:
if (inputFilter.some(word => str.includes(word))) {
// Word caught...
} else {
// Clear...
}
some calls the callback repeatedly until the first time it returns a truthy value. If the callback ever returns a truthy value, some returns true; otherwise, some returns false. E.g., it's asking if "some" of the entries match the predicate function. (any may have been a better term, but when adding to the built-ins, the TC39 committee have to do a lot of work to avoid conflicts with libraries and such.)
If you ever need to get back the actual entry, use find which returns the entry or undefined if not found. If you need its index, use findIndex.
Side note: Just beware that it's notoriously complicated to do this well. Beware of the Scunthorpe problem, and of course people will routinely just confuse the sequence of letters or substitute asterisks or similar to defeat filters of this sort...
you can try something like this:-
function filterInput(str) {
var badWords = ['bad', 'worst'];
var isTrue = false;
if(str) {
for (var i = 0; i < badWords.length; i++) {
isTrue = !!(str.replace(/\W|\s/g, '').toLowerCase().indexOf(badWords[i]) + 1);
if(isTrue) break;
}
}
return isTrue;
}

How do I recurse DOM nodes to an arbitrary depth in Javascript?

I am really having trouble getting my head around crossbrowser recursion in the DOM. I want to get only the text content of a node, but not any HTML tags or other information. Through trial and error, I found that the textContent and innerText attributes don't hold across all browsers, so I have to use the data attribute.
Now the function I have so far is this:
getTextContentXBrowser: function(nodeIn) {
// Currently goes down two levels. Need to abstract further to handle arbitrary number of levels
var tempString = '';
for (i=0, len=nodeIn.childNodes.length; i < len; i++) {
if (nodeIn.childNodes[i].firstChild !== null) {
tempString += nodeIn.childNodes[i].firstChild.data;
} else {
if (nodeIn.childNodes[i].data && nodeIn.childNodes[i].data !== '\n') {
tempString += nodeIn.childNodes[i].data;
}
}
}
return tempString;
},
It's written in object notation, but otherwise it's a pretty standard unremarkable function. It goes down two levels, which is almost good enough for what I want to do, but I want to "set it and forget it" if possible.
I've been at it for four hours and I haven't been able to abstract this to an arbitrary number of levels. Is recursion even my best choice here? Am I missing a better option? How would I convert the above function to recurse?
Thanks for any help!
Update: I rewrote it per dsfq's model, but for some reason, it goes one level down and is unable to go back up afterwards. I realized that my problem previously was that I wasn't concatenating in the second if clause, but this seems to have stopped me short of the goal. Here is my updated function:
getTextContentXBrowser: function(nodeIn) {
var tempString = '';
for (i=0, len=nodeIn.childNodes.length; i < len; i++) {
if (nodeIn.childNodes[i].data) {
tempString += nodeIn.childNodes[i].data;
} else if (nodeIn.childNodes[i].firstChild) {
tempString += this.getTextContentXBrowser(nodeIn.childNodes[i]);
}
}
return tempString.replace(/ /g,'').replace(/\n/g,'');
},
Anyone see what I'm missing?
Have you considered doing this with jQuery?
getTextContentXBrowser: function(nodeIn) {
return $(nodeIn).text();
}
As simple as that!
It can be really simple function calling itself to to replace nodes with its contents. For example:
function flatten(node) {
for (var c = node.childNodes, i = c.length; i--;) {
if (c[i].nodeType == 1) {
c[i].parentNode.replaceChild(document.createTextNode(flatten(c[i]).innerHTML), c[i]);
}
}
}
Looks like in your case you getTextContentXBrowser is a method of some object, so you will need to call it from inside itself properly (in my example I just use function).
Demo: http://jsfiddle.net/7tyYA/
Note that this function replaces nodes with a text in place. If you want a function that just returns a text without modifying actual node right away consider this example with another version of the script:
Demo 2: http://jsfiddle.net/7tyYA/1/

Seeing if input matches array if not alert

var tagAllowed = true;
var allowedTags =["Person","People","Dance","Word"];
if(tagAllowed === true) {
for(var i=0;i<allowedTags.length;i++){
var aTags = allowedTags[i];
if(input.val().toLowerCase() === aTags.toLowerCase()) {
tagged.append('<span unselectable="on" class="tagged '+colorize+'" title="Click To Delete">'+inputVal.trim()+'</span>');
tagSize = $('.tagged').length;
var ele = $('.tagged').last(),
subtract = parseInt(ele.outerWidth(true),10);
input.width(input.width() - subtract);
tagged.width(tagged.width() + subtract);
input.css('marginLeft','5px');
input.val("");
input.css('color','#000');
} else {
errorMess.children('span').remove();
errorMess.prepend('<span>'+errorProcess+'<span>');
errorMess.slideDown();
}
}
The following code works in a way, if the input.val() does not match it will show the custom alert errorMess and well even if the word matches it still shows the custom alert. I am wondering if maybe I am doing something wrong in my conditional. As I don't need the custom alert to appear if the words match.
If any suggestions please post. I know this isn't the best example with just a code, but I hope all of you get what I am trying to say. I just don't want the custom alert to appear if the two words match together.
You have the if-statement inside the for-loop. The input value will never equal more than one of the tags in the array. You could use a for-loop to set a boolean. Then the if-statement could follow the for-loop.
boolean isAllowedTag = false;
for(var i=0;i<allowedTags.length;i++){
var aTags = allowedTags[i];
if(input.val().toLowerCase() === aTags.toLowerCase()) {
isAllowedTag = true;
break;
}
}
if (isAllowedTag) {
// ...
} else {
errorMess.children('span').remove();
errorMess.prepend('<span>'+errorProcess+'<span>');
errorMess.slideDown();
}
}
add a break; after your input.css('color, '#000'); line. also, you should really change those last 3 lines to: input.val("").css({marginLeft:'5px', color:'#000'});. Making calls to .css() is slow, so it's better to do as much as you can in one call.

Better way to see if an array contains an object?

I have an array of items (terms), which will be put as <option> tags in a <select>. If any of these items are in another array (termsAlreadyTaking), they should be removed first. Here is how I have done it:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
for (var i = 0; i < terms.length; i++)
{
for (var iAlreadyTaking = 0; iAlreadyTaking < termsAlreadyTaking.length; iAlreadyTaking++)
{
if (terms[i]['pk'] == termsAlreadyTaking[iAlreadyTaking]['pk'])
{
terms.splice(i, 1); // remove terms[i] without leaving a hole in the array
continue;
}
}
}
Is there a better way to do this? It feels a bit clumsy.
I'm using jQuery, if it makes a difference.
UPDATE Based on #Matthew Flaschen's answer:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
var already_chosen = false;
$.each(response_chosen_items, function(index, chosen_elem)
{
if (chosen_elem['pk'] == elem['pk'])
{
already_chosen = true;
return;
}
});
return ! already_chosen;
});
The reason it gets a bit more verbose in the middle is that $.inArray() is returning false, because the duplicates I'm looking for don't strictly equal one another in the == sense. However, all their values are the same. Can I make this more concise?
var terms = $.grep(terms, function(el)
{
return $.inArray(el, termsAlreadyTaking) == -1;
});
This still has m * n performance (m and n are the lengths of the arrays), but it shouldn't be a big deal as long as they're relatively small. To get m + n, you could use a hashtable
Note that ECMAScript provides the similar Array.filter and Array.indexOf. However, they're not implemented in all browsers yet, so you would have to use the MDC implementations as a fallback. Since you're using jQuery, grep and inArray (which uses native indexOf when available) are easier.
EDIT:
You could do:
var response_chosen_pk = $.map(response_chosen_items, function(elem)
{
return elem.pk;
});
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
return $.inArray(elem.pk, response_chosen_pk) == -1;
});
http://github.com/danstocker/jorder
Create a jOrder table on termsAlreadyTaking, and index it with pk.
var table = jOrder(termsAlreadyTaking)
.index('pk', ['pk']);
Then you can search a lot faster:
...
if ([] == table.where([{ pk: terms[i].pk }]))
{
...
}
...

Categories

Resources