JavaScript. Check if value is not in object array - javascript

I have 2 list. One bigger and one smaller. I have added infinite scroll to keep adding items from bigger list to smaller one.
// Bigger list (data from DB)
var data;
// Smaller list
$scope.projects = [];
$scope.$watch('projectSearch', function (val) {
if (typeof val !== 'undefined' && val !== "") {
//console.log("SEARCH: " + val);
for (var a = 0; a < data.length; a++) {
if (data[a].name.toLowerCase().indexOf(val) > -1) {
console.log("FOUND: " + data[a].name);
if($scope.projects.indexOf(data[a].name) === -1) {
console.log("PUSHED " + data[a].name);
$scope.projects.push( data[a]);
} else {
console.log("ALREADY IN " + data[a].name);
}
}
}
}
});
Search form:
<input id="search" ng-model="projectSearch" placeholder="Search ..."/>
The problem is it keeps pushing items to new list. It does not check this if statement correctly
if($scope.projects.indexOf(data[a].name) === -1) {
console.log("PUSHED " + data[a].name);
$scope.projects.push( data[a]);
}

you should check for data[a], not its property name
if($scope.projects.indexOf(data[a]) === -1) {
console.log("PUSHED " + data[a].name);
$scope.projects.push( data[a]);
}
also, you could remove data[a] after copy to make next search faster

$scope.projects should be type of string assuming data[a].name is string then your indexOf function properly

Related

logging array item by index returning undefiened

I was making a program that recoreded every keypress and pushed it to an array and it works fine. The problem is when I try to access the first element of the array and log it, it prints undefined. But the whole array logs fine.Why is it printing undefiened? I have added both console log of the array and the array item in my code and have commented besides them to indicate. Any help is appreciated. Thanks in advance.
EDIT: turn out what doesn't work is accessing the last item. I have updated the code above
var cacheW = []
var cacheA = []
var cacheD = []
var cacheS = []
// (B1) CAPTURE KEY STROKES
window.addEventListener("keydown", function(evt) {
if (evt.key == "w") {
cacheW.push('w');
//console.log("this: " + evt.key)
} else if (evt.key == "a") {
cacheA.push('a');
//console.log("this: " + evt.key)
} else if (evt.key == "d") {
cacheD.push('d');
//console.log("this: " + evt.key)
} else if (evt.key == "s") {
cacheS.push('s');
//console.log("this: " + evt.key)
}
});
window.addEventListener("keyup", function(evt) {
if (evt.key == "w") {
cacheW.push("!w");
//console.log("this: " + evt.key + " removed")
} else if (evt.key == "a") {
cacheA.push("!a");
//console.log("this: " + evt.key + " removed")
} else if (evt.key == "d") {
cacheD.push("!d");
//console.log("this: " + evt.key + " removed")
} else if (evt.key == "s") {
cacheS.push("!s");
//console.log("this: " + evt.key + " removed")
}
});
//works
setInterval(function() {
console.log(cacheW) //logs an array
}, 50)
//doesn't work
setInterval(function() {
console.log(cacheW[-1]) //logs undefined, should have logged the last element
}, 50)
Javascript access array items by their index. -1 is an invalid index. To access the last item, use arr[arr.length - 1].
Other languages such as python offer syntactic sugar to access items from the end of an array. JavaScript does not have such syntactic sugar. The closest which you can get is to write arr.slice(-1)[0], but this will create a temporary single-item array and then access the first item of this array.
In fact -1 is a property, not a valid index. Property names are first stringified, then added to the object (every array is an object):
a = [];
a[-1] = 42;
console.log(a); // [], array itself is still empty
console.log(a[-1]); // 42, property -1 on the object has value assigned
console.log(a['-1']); // 42, object property keys are always converted to string first
Instead of this:
//doesn't work
setInterval(function() {
console.log(cacheW[0])//logs undefined, should have logged the first element
}, 50)
This:
setInterval(function() {
if (cacheW.length > 0) {
console.log(cacheW[0]);
} else {
console.log("<empty>");
}
}, 50);
Update
If you want to print the last item:
setInterval(function() {
if (cacheW.length > 0) {
console.log(cacheW[cacheW.length-1]);
} else {
console.log("<empty>");
}
}, 50);

prevent function from recalling itself at the last statement of the function in java script

I've been learning javascript and jquery and I've encountered a problem when I'm trying to validate my form fields using a jquery function. The problem is its working fine the first two times it is called (when I press the update button for a specific element )and whenever I'm trying to call it a third time (by pressing the update button for the same element as earlier ) it is calling itself but I clearly did not mention any recursive calls and am not calling it within the function again. I'm not sure why it is calling itself. Kindly help me out. I will be attaching the fiddle. After triggering reset in main.updateData(Object.assign({}, main.newObject), keys); in the third time its showing the name empty error which shouldn't be happening.
I've tried giving breakpoints and inspecting the reason behind this weird behaviour but I couldn't
The name field should show an error only when it is empty but third time it is showing error even when it is not empty
FIDDLE
validateFormData: function(value, keys, idCount) {
var keyIndex = 0;
main.newObject[keys[keyIndex++]] = idCount;
if (value == "update") {
main.newObject[keys[0]] = $(".active-contact").attr('id');
//alert("new updated id is " + main.newObject[keys[0]]);
}
var validElementsCount = 0;
var alphabet_pattern = /^[a-z]+\s*/i;
var email_pattern = /[a-z]{0,}[0-9]{0,4}[.]{0,1}[0-9]{0,4}[a-z]{0,8}[0-9]{0,4}[#][a-z]{0,20}[.](com)/i;
var number_pattern = /^[0-9]{10}$/;
var website_pattern = /^(www)[.][a-z]{1,20}[.](com)$/i;
/*Validating the form inputs against the regex pattern*/
if ($("#employee-name").val() == "") {
$("#employee-name-error").text("name cannot be empty");
} else if (!alphabet_pattern.test($("#employee-name").val())) {
$("#employee-name-error").text("Only alphabets are allowed");
} else {
validElementsCount++;
$("#employee-name-error").text("");
main.newObject[keys[keyIndex++]] = $("#employee-name").val();
//alert("object is " + JSON.stringify(main.newObject[keys[keyIndex-1]]) + " key is " + keys[keyIndex-1]);
}
//employee email validation
if (email_pattern.test($("#employee-email").val()) || $("#employee-email").val() == "") {
$("#employee-email-error").text("");
validElementsCount++;
main.newObject[keys[keyIndex++]] = $("#employee-email").val();
//alert("object is " + JSON.stringify(main.newObject[keys[keyIndex - 1]]) + " key is " + keys[keyIndex - 1]);
} else {
$("#employee-email-error").text("Follow email pattern");
}
//employee mobile validation
if (number_pattern.test($("#employee-mobile").val()) || $("#employee-mobile").val() == "") {
$("#employee-mobile-error").text("");
validElementsCount++;
main.newObject[keys[keyIndex++]] = $("#employee-mobile").val();
//alert("object is " + JSON.stringify(main.newObject[keys[keyIndex - 1]]) + " key is " + keys[keyIndex - 1]);
} else {
$("#employee-mobile-error").text("Only 10 digit number is allowed");
}
//employee landline no validataion
if (number_pattern.test($("#employee-land-line").val()) || $("#employee-land-line").val() == "") {
$("#employee-land-line-error").text("");
validElementsCount++;
main.newObject[keys[keyIndex++]] = $("#employee-land-line").val();
//alert("object is " + JSON.stringify(main.newObject[keys[keyIndex - 1]]) + " key is " + keys[keyIndex - 1]);
} else {
$("#employee-land-line-error").text("Only 10 digit number is allowed");
}
//employee website validation
if (website_pattern.test($("#employee-website").val()) || $("#employee-website").val() == "") {
$("#employee-website-error").text("");
validElementsCount++;
main.newObject[keys[keyIndex++]] = $("#employee-website").val();
} else {
$("#employee-website-error").text("Follow website pattern");
}
main.newObject[keys[keyIndex++]] = $("#employee-address").val();
if (validElementsCount == 5) {
if (value == "add") {
main.addEmployeeClick(Object.assign({}, main.newObject));
$(".employee-details-form").trigger("reset");
} else if (value == "update") {
//alert("new object is " + JSON.stringify(Object.assign({}, main.newObject), keys));
main.updateData(Object.assign({}, main.newObject), keys);
$(".employee-details-form").trigger("reset");
}
}
},
You can add .off() before #update-employee-btn click event binding in line 34.
$("#update-employee-btn").off().click(....)
Let me know if it works for you as well.

Convert html string to selector syntax [duplicate]

I got this function to get a cssPath :
var cssPath = function (el) {
var path = [];
while (
(el.nodeName.toLowerCase() != 'html') &&
(el = el.parentNode) &&
path.unshift(el.nodeName.toLowerCase() +
(el.id ? '#' + el.id : '') +
(el.className ? '.' + el.className.replace(/\s+/g, ".") : ''))
);
return path.join(" > ");
}
console.log(cssPath(document.getElementsByTagName('a')[123]));
But i got something like this :
html > body > div#div-id > div.site > div.clearfix > ul.choices > li
But to be totally right, it should look like this :html > body > div#div-id > div.site:nth-child(1) > div.clearfix > ul.choices > li:nth-child(5)
Did someone have any idea to implement it simply in javascript ?
The answer above actually has a bug in it — the while loop breaks prematurely when it encounters a non-element node (e.g. a text node) resulting in an incorrect CSS selector.
Here's an improved version that fixes that problem plus:
Stops when it encounters the first ancestor element with an id assigned to it
Uses nth-of-type() to make the selectors more readable
var cssPath = function(el) {
if (!(el instanceof Element))
return;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector += '#' + el.id;
path.unshift(selector);
break;
} else {
var sib = el, nth = 1;
while (sib = sib.previousElementSibling) {
if (sib.nodeName.toLowerCase() == selector)
nth++;
}
if (nth != 1)
selector += ":nth-of-type("+nth+")";
}
path.unshift(selector);
el = el.parentNode;
}
return path.join(" > ");
}
To always get the right element, you will need to use :nth-child() or :nth-of-type() for selectors that do not uniquely identify an element. So try this:
var cssPath = function(el) {
if (!(el instanceof Element)) return;
var path = [];
while (el.nodeType === Node.ELEMENT_NODE) {
var selector = el.nodeName.toLowerCase();
if (el.id) {
selector += '#' + el.id;
} else {
var sib = el, nth = 1;
while (sib.nodeType === Node.ELEMENT_NODE && (sib = sib.previousSibling) && nth++);
selector += ":nth-child("+nth+")";
}
path.unshift(selector);
el = el.parentNode;
}
return path.join(" > ");
}
You could add a routine to check for unique elements in their corresponding context (like TITLE, BASE, CAPTION, etc.).
The two other provided answers had a couple of assumptions with browser compatibility that I ran into. Below code will not use nth-child and also has the previousElementSibling check.
function previousElementSibling (element) {
if (element.previousElementSibling !== 'undefined') {
return element.previousElementSibling;
} else {
// Loop through ignoring anything not an element
while (element = element.previousSibling) {
if (element.nodeType === 1) {
return element;
}
}
}
}
function getPath (element) {
// False on non-elements
if (!(element instanceof HTMLElement)) { return false; }
var path = [];
while (element.nodeType === Node.ELEMENT_NODE) {
var selector = element.nodeName;
if (element.id) { selector += ('#' + element.id); }
else {
// Walk backwards until there is no previous sibling
var sibling = element;
// Will hold nodeName to join for adjacent selection
var siblingSelectors = [];
while (sibling !== null && sibling.nodeType === Node.ELEMENT_NODE) {
siblingSelectors.unshift(sibling.nodeName);
sibling = previousElementSibling(sibling);
}
// :first-child does not apply to HTML
if (siblingSelectors[0] !== 'HTML') {
siblingSelectors[0] = siblingSelectors[0] + ':first-child';
}
selector = siblingSelectors.join(' + ');
}
path.unshift(selector);
element = element.parentNode;
}
return path.join(' > ');
}
Doing a reverse CSS selector lookup is an inherently tricky thing. I've generally come across two types of solutions:
Go up the DOM tree to assemble the selector string out of a combination of element names, classes, and the id or name attribute. The problem with this method is that it can result in selectors that return multiple elements, which won't cut it if we require them to select only one unique element.
Assemble the selector string using nth-child() or nth-of-type(), which can result in very long selectors. In most cases the longer a selector is the higher specificity it has, and the higher the specificity the more likely it will break when the DOM structure changes.
The solution below is an attempt at tackling both of these issues. It is a hybrid approach that outputs a unique CSS selector (i.e., document.querySelectorAll(getUniqueSelector(el)) should always return a one-item array). While the returned selector string is not necessarily the shortest, it is derived with an eye towards CSS selector efficiency while balancing specificity by prioritizing nth-of-type() and nth-child() last.
You can specify what attributes to incorporate into the selector by updating the aAttr array. The minimum browser requirement is IE 9.
function getUniqueSelector(elSrc) {
if (!(elSrc instanceof Element)) return;
var sSel,
aAttr = ['name', 'value', 'title', 'placeholder', 'data-*'], // Common attributes
aSel = [],
// Derive selector from element
getSelector = function(el) {
// 1. Check ID first
// NOTE: ID must be unique amongst all IDs in an HTML5 document.
// https://www.w3.org/TR/html5/dom.html#the-id-attribute
if (el.id) {
aSel.unshift('#' + el.id);
return true;
}
aSel.unshift(sSel = el.nodeName.toLowerCase());
// 2. Try to select by classes
if (el.className) {
aSel[0] = sSel += '.' + el.className.trim().replace(/ +/g, '.');
if (uniqueQuery()) return true;
}
// 3. Try to select by classes + attributes
for (var i=0; i<aAttr.length; ++i) {
if (aAttr[i]==='data-*') {
// Build array of data attributes
var aDataAttr = [].filter.call(el.attributes, function(attr) {
return attr.name.indexOf('data-')===0;
});
for (var j=0; j<aDataAttr.length; ++j) {
aSel[0] = sSel += '[' + aDataAttr[j].name + '="' + aDataAttr[j].value + '"]';
if (uniqueQuery()) return true;
}
} else if (el[aAttr[i]]) {
aSel[0] = sSel += '[' + aAttr[i] + '="' + el[aAttr[i]] + '"]';
if (uniqueQuery()) return true;
}
}
// 4. Try to select by nth-of-type() as a fallback for generic elements
var elChild = el,
sChild,
n = 1;
while (elChild = elChild.previousElementSibling) {
if (elChild.nodeName===el.nodeName) ++n;
}
aSel[0] = sSel += ':nth-of-type(' + n + ')';
if (uniqueQuery()) return true;
// 5. Try to select by nth-child() as a last resort
elChild = el;
n = 1;
while (elChild = elChild.previousElementSibling) ++n;
aSel[0] = sSel = sSel.replace(/:nth-of-type\(\d+\)/, n>1 ? ':nth-child(' + n + ')' : ':first-child');
if (uniqueQuery()) return true;
return false;
},
// Test query to see if it returns one element
uniqueQuery = function() {
return document.querySelectorAll(aSel.join('>')||null).length===1;
};
// Walk up the DOM tree to compile a unique selector
while (elSrc.parentNode) {
if (getSelector(elSrc)) return aSel.join(' > ');
elSrc = elSrc.parentNode;
}
}
I somehow find all the implementations unreadable due to unnecessary mutation. Here I provide mine in ClojureScript and JS:
(defn element? [x]
(and (not (nil? x))
(identical? (.-nodeType x) js/Node.ELEMENT_NODE)))
(defn nth-child [el]
(loop [sib el nth 1]
(if sib
(recur (.-previousSibling sib) (inc nth))
(dec nth))))
(defn element-path
([el] (element-path el []))
([el path]
(if (element? el)
(let [tag (.. el -nodeName (toLowerCase))
id (and (not (string/blank? (.-id el))) (.-id el))]
(if id
(element-path nil (conj path (str "#" id)))
(element-path
(.-parentNode el)
(conj path (str tag ":nth-child(" (nth-child el) ")")))))
(string/join " > " (reverse path)))))
Javascript:
const isElement = (x) => x && x.nodeType === Node.ELEMENT_NODE;
const nthChild = (el, nth = 1) => {
if (el) {
return nthChild(el.previousSibling, nth + 1);
} else {
return nth - 1;
}
};
const elementPath = (el, path = []) => {
if (isElement(el)) {
const tag = el.nodeName.toLowerCase(),
id = (el.id.length != 0 && el.id);
if (id) {
return elementPath(
null, path.concat([`#${id}`]));
} else {
return elementPath(
el.parentNode,
path.concat([`${tag}:nth-child(${nthChild(el)})`]));
}
} else {
return path.reverse().join(" > ");
}
};
There are some js libraries that do exactly this:
https://github.com/antonmedv/finder
https://github.com/gmmorris/simmerjs
I am using the first one and with success so far
function cssPath (e, anchor) {
var selector;
var parent = e.parentNode, child = e;
var tagSelector = e.nodeName.toLowerCase();
while (anchor && parent != anchor || !anchor && parent.nodeType === NodeTypes.ELEMENT_NODE) {
var cssAttributes = ['id', 'name', 'class', 'type', 'alt', 'title', 'value'];
var childSelector = tagSelector;
if (!selector || parent.querySelectorAll (selector).length > 1) {
for (var i = 0; i < cssAttributes.length; i++) {
var attr = cssAttributes[i];
var value = child.getAttribute(attr);
if (value) {
if (attr === 'id') {
childSelector = '#' + value;
} else if (attr === 'class') {
childSelector = childSelector + '.' + value.replace(/\s/g, ".").replace(/\.\./g, ".");
} else {
childSelector = childSelector + '[' + attr + '="' + value + '"]';
}
}
}
var putativeSelector = selector? childSelector + ' ' + selector: childSelector;
if (parent.querySelectorAll (putativeSelector).length > 1) {
var siblings = parent.querySelectorAll (':scope > ' + tagSelector);
for (var index = 0; index < siblings.length; index++)
if (siblings [index] === child) {
childSelector = childSelector + ':nth-of-type(' + (index + 1) + ')';
putativeSelector = selector? childSelector + ' ' + selector: childSelector;
break;
}
}
selector = putativeSelector;
}
child = parent;
parent = parent.parentNode;
}
return selector;
};
Better late than never: I came to this question and tried to use the selected answer, but in my case, it didn't worked because it wasn't very specific for my case. So I decided to write my own solution - I hope it may help some.
This solution goes like this: tag.class#id[name][type]:nth-child(?), and targeted with >.
function path(e) {
let a = [];
while (e.parentNode) {
let d = [
e.tagName.toLowerCase(),
e.hasAttribute("class") ? e.getAttribute("class") : "",
e.hasAttribute("id") ? e.getAttribute("id") : "",
e.hasAttribute("name") ? e.getAttribute("name") : "",
e.hasAttribute("type") ? e.getAttribute("type") : "",
0 // nth-child
];
// Trim
for (let i = 0; i < d.length; i++) d[i] = typeof d[i] == "string" ? d[i].trim() : d[i];
if (d[1] != "") d[1] = "."+d[1].split(" ").join(".");
if (d[2] != "") d[2] = "#"+d[2];
if (d[3] != "") d[3] = '[name="'+d[3]+'"]';
if (d[4] != "") d[4] = '[type="'+d[4]+'"]';
// Get child index...
let s = e;
while (s) {
d[5]++;
s = s.previousElementSibling;
}
d[5] = d[5] != "" ? ":nth-child("+d[5]+")" : ":only-child";
// Build the String
s = "";
for (let i = 0; i < d.length; i++) s += d[i];
a.unshift(s);
// Go to Parent
e = e.parentNode;
}
return a.join(">");
}
I know it's not that readable (I use it in my messy code), but it will give you the exact element(s) you're looking for. Just try it.

what's wrong with my variable, it's undefined

my variable todoHtmlLi is undefined, really can't get it why.. I had declared it early before assign it to some html. I use console.log() to check the priority value, it work just fine..
$(document).on('click', '#addTodoBtn', function () {
var todoDialog = {
state0: {
html: dialogContent,
buttons: {
Cancel: -1,
Add: 0
},
focus: 1,
submit: function (e, v, m, f) {
e.preventDefault();
var todoHtmlLi;
var todoNameVal;
var todoNoteVal;
//Task Name
todoNameVal = $("#todoName").val();
todoNameVal.trim();
//Note
todoNoteVal = $("#todoNote").val();
todoNoteVal.trim();
//Priority
priority = $("#priority").val();
if ($(priority) === 1) {
todoHtmlLi = "<li style='background:red'><a href='#'>" + todoNameVal + "<input type='checkbox'></a></li>"
} else if ($(priority) === 2) {
todoHtmlLi = "<li style='background:green'><a href='#'>" + todoNameVal + "<input type='checkbox'></a></li>"
} else if ($(priority) === 3) {
todoHtmlLi = "<li style='background:blue'><a href='#'>" + todoNameVal + "<input type='checkbox'></a></li>"
}
if (v == 0) {
if (todoNameVal !== "") {
$("div#tab").find('#todoUl').prepend(todoHtmlLi);
$.prompt.close();
} else {
$("#todoName").focus();
}
} else {
$.prompt.close();
}
}
}
}
$.prompt(todoDialog);
});
if(v == 0){ mean the 'yes' button is clicked
First: You only assign values to todoHtmlLi based on comparing the return value of a call to val() (which will be a String) to a Number using === (which checks type).
Since "1" === 1 is not true, you never assign a value.
Either use ==, compare to Strings or convert to a Number.
Second: You pass the value as an argument to $, so you get a jQuery object back instead of that String. This doesn't make any sense, so don't do that.
if (priority == 1){
if (priority === "1"){
if (parseInt(priority,10) === 1){
Because your conditions are wrong.
see ,
priority = $("#priority").val();
That returns a string.
Then
if($(priority) === 1){
That is wrong ,Since 1 never equals to "1",So no condition satisfied.and it;s undefined.
Your if condition should be
if(priority === "1"){
And also remaining if conditions needs to be change.

Trying to add and remove items from an array

The script works by asking user for add or remove an item in the array. Then asks to continue this loop. The problem here is that my script doesn't seem to match my user's input (removeItem) to the item in the list (myList[i]). I'm at a lost as to why this is failing to match.
// new method for removing specific items from a list
Array.prototype.remove = function(from,to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
printList = function() {
var listLength = myList.length;
for (i = 0; i < listLength; i++) {
document.write(i + ":");
document.write(myList[i] + "<br/>");
};
document.write("<br/><br/>");
};
// initial list
var myList = new Array ();
if (myList.length === 0) {
document.write("I have " + myList.length + " item in my list. It is: <br/>");
}
else {
document.write("I have " + myList.length + " items in my list. They are: <br/>");
}
printList();
var continueAdding = "yes";
var askToContinue = "";
while (continueAdding === "yes") {
// loop
var askUser = prompt("What do you want to [A]dd or [R]emove an item to your inventory?").toUpperCase();
switch (askUser) {
case "A": { // add an user specified item to the list
var addItem = prompt("Add something to the list");
myList.push(addItem);
printList();
break;
}
case "R": { // remove an user specified item from the list
var removeItem = prompt("what do you want to remove?");
var listLength = myList.length;
for (i = 0; i < listLength; i++) {
if (removeItem === myList[i]) {
document.write("I found your " + removeItem + " and removed it.<br/>");
myList.remove(i);
}
else {
document.write(removeItem + " does not exist in this list.<br/>");
break;
}
if (myList.length === 0) {
myList[0] = "Nada";
}
};
printList();
break;
}
default: {
document.write("That is not a proper choice.");
}
};
askToContinue = prompt("Do you wish to continue? [Y]es or [N]o?").toUpperCase(); // ask to continue
if (askToContinue === "Y") {
continueAdding = "yes";
}
else {
continueAdding = "no";
}
}
Your loop never allows it to loop through all the items, because it breaks on the first iteration if the item doesn't match.
The break statement should be in the if block, not in the else block - use this instead:
for (i = 0; i < listLength; i++) {
if (removeItem === myList[i]) {
document.write("I found your " + removeItem + " and removed it.<br/>");
myList.remove(i);
break;
}
else {
document.write(removeItem + " does not exist in this list.<br/>");
}
};
if (myList.length === 0) {
myList[0] = "Nada";
}
Also, note that it's looking for an exact match, case sensitive, same punctuation, and everything. If you want it to be a little more lenient you'll need to modify the script to convert both strings to lowercase and strip punctuation before comparing them.
Edit: Just noticed something else -- testing for an empty list needs to be done outside the loop. I updated the above code to reflect this.

Categories