How to get nth number of an element child in js - javascript

i have this html:
<div id="helo">
<span id="hjhn">sample text</span>
<span id="uhed">melaps xtet</span>
<span id="kdhs">elpmas txet</span>
</div>
then i need something that you tell the id and it returns the number of the element.
so for example:
helo('#hjhn'); // Output: 0
helo('#uhed'); // Output: 1
helo('#kdhs'); // Output: 2
I've tried a lot of like 3 different ways but I just dont know how, so it would be great that you'd try to help me!

You could do something like this.
const parent = document.querySelector("#helo");
const getChildIndex = (_id) => {
let index = 0;
for (const child of parent.children) {
index++;
if (child.id === _id){
return index;
}
}
}
console.log(getChildIndex("kdhs"))
The main goal is to loop through the child elements and map them to an index number. It can be done in various ways.

Yes, there are a number of ways to do this. Here’s another.
function getSpanIndex(id) {
const div = document.getElementById('helo');
const spans = div.querySelectorAll('span');
for (let i = 0; i < spans.length; ++i) {
if (spans[i].id === id)
return i;
}
return -1;
}

The Following function is flexible in places where you don't know the parent of the selected element.
const getIndex = (id) => {
const c = document.getElementById(id);
const parent = c.parentNode;
return Array.from(parent.children).findIndex((i) => i.id == id);
};
console.log(getIndex("uhed"));
It selects an element with the given id and loops through the children of the parent of the selected element.
Additionally, you can add if statements to check whether c is undefined to avoid run time errors in cases where an element with the given id doesn't exist in the DOM.

Template string can be well done:
function test(ele) {
const currentEle = document.querySelector(`${ele}`);
return Array.from(currentEle.parentNode.children).findIndex(item => item === currentEle);
}

You can use the querySelector function for selecting nth-child. See the below example.
const document = document.querySelector('#helo span:nth-child(2)');
console.log(document); // 👉️ <span id="kdhs">elpmas txet</span>
Note: nth-child(2) this "2" is the index of your item. It starts from 0
Check this blog for more details -
https://bobbyhadz.com/blog/javascript-get-nth-child-of-element

Related

search an array with value from another array in javascript

I'm trying to search an array of HTML elements with search value from another array. Inside a function getElement() there is an array order which holds string value. This array determines the order of search. Also there is another array elements with all the html elements to be searched. The function block looks like
getElement(){
const order = ['email-error', 'name-error', 'age-error'];
const elements = document.querySelectorAll('[data-status]')
}
When there is a validation error, html elements will be attached with data-status property. In case of a validation error, the getElement() function is called. The value of the data-status will be email-error for email input, name-error for name input etc.
What I'm trying to achieve is; for eg: take a value from order array, say email-error. Search the elements array if it has an element with data-status="email-error". If it exists, return the element and stops the search; else search with all values from order array.
I've written some HTML to test the code.
This is is the way to do it, if you want to loop over all array values and check:
function getElement() {
const order = ['email-error', 'name-error', 'age-error'];
const elements = document.querySelectorAll('[data-status]')
let foundElement = null
elements.forEach((elem) => {
if (order.includes(elem.getAttribute('data-status')) && !foundElement) {
foundElement = elem
}
})
return foundElement
}
console.log(getElement())
<div data-status="email-error"></div>
<div data-status="name-error"></div>
<div data-status="test-class"></div>
Maybe you could do a function on your own like this:
https://stackoverflow.com/a/784015/13604954
Or use the built-in Array.prototype.includes:
https://stackoverflow.com/a/40717204/13604954
You could implement a nested loop.
for(let i = 0; i < order.length; i++) {
for(let j = 0; j < elements.length; j++) {
if( order[i] == elements[j] ) {
return orders[i]
}
}
}
This will loop through both arrays fully, matching each value from one array to each value from the other unless there's a match, in which case it would return the matching element and break the loop.
You could also use the built in JS .forEach() method, which automatically loops through arrays.
order.forEach(orderEl => {
elements.forEach(elementEl => {
if (orderEl === elementEl) {
return orderEl
}
})
})

How do I spot HTML element ID's that repeat themselves?

I just got a major bug that was really hard to solve and found out after a lot of work it was because two HTML elements had the same ID attribute.
Is there a command that spots double IDs across the whole DOM?
Update:
After reading all the comments I tested the different approaches in a relatively big website (http://www.powtoon.com html5 studio) and here are the results.
Please comment if you have better ways then these two.
Both answers returned the same results, but looks like the querySelectorAll was indeed faster:
QuerySelectorAll
function getDuplicateIdsUsingQuerySelector(){
const idsOccurances = Array.from(document.querySelectorAll('[id]'))
.map(elem => elem.id)
.reduce((allIDs, id) => {
allIDs[id] = (allIDs[id] || 0) + 1
return allIDs
}, {})
return Object.entries(idsOccurances)
.filter(([id, occurances]) => occurances > 1)
.map(([id]) => id)
}
1.5ms per call on average.
Regex
function getDuplicateIdsUsingRegEx(){
const idRegex = / id=".*?"/ig
const ids = document.body.innerHTML.match(idRegex)
const idsOccurances = ids.reduce((allIDs, id) => {
allIDs[id] = (allIDs[id] || 0) + 1
return allIDs
}, {})
return Object.entries(idsOccurances)
.filter(([id, occurrences]) => occurrences > 1)
.map(([id]) => id.slice(4, -1))
}
5.5ms per call average.
document.querySelectorAll('[id="dupid"]') would give you multiple results
[edit]
I made this jsfiddle with another way of finding all duplicates if you don't know what IDs to expect.
https://jsfiddle.net/edc4h5k2/
function checkAllIds() {
var counts = {};
var duplicates = new Set();
function addCount(id) {
if (counts[id]) {
counts[id] += 1;
duplicates.add(id);
} else {
counts[id] = 1;
}
}
document.querySelectorAll("[id]").forEach(function(elem) {
addCount(elem.id);
})
return Array.from(duplicates);
}
Is there a command that spots double IDs across the whole DOM?
Simply, put this in your console
$("[id='idValue']").length
If the value is more than one, then you have a duplicate!
Once, you have spotted the fact that there are duplicates, then you need to check the hierarchy of the individual elements
$("[id='idValue']").each( function(){
//traverse parents till body to see the dom structure to locate those duplicates
});
If you don't know the id value in advance, then first create the array of duplicate ids
var allIds = {};
var duplicateIds = [];
$( "[id]" ).each( function(){
var idValue = $(this).attr("id");
if ( allIds[ idValue ] )
{
duplicateIds.push ( idValue );
}
else
{
allIds[ idValue ] = true;
}
});
Now duplicateIds is the array containing the duplicate ids. Iterate the same to check its DOM hierarchy so that you can spot it in the entire DOM.
duplicateIds.forEach( function( idValue ){
var $elementWithDuplicateId = $( "#" + idValue );
//logic to traverse parents
});
If you are using Chrome you could run an audit by opening the console->Audits and check the checkbox for Best practices. If there are any double id's they show up there.
This will give you an array of all IDs that occur more than once.
First all elements that have an ID are selected, then their IDs are extracted, then everything is reduced into an object that counts the occurrences. Of that object, the entries are converted into an array, which is filtered to only contain those that occur more than once, then only the ID names are extracted.
const duplicateIDs = Object.entries(Array.from(document.querySelectorAll("[id]"), ({id}) => id)
.reduce((allIDs, id) => (allIDs[id] = (allIDs[id] || 0) + 1, allIDs), {}))
.filter(([id, occurrences]) => occurrences > 1)
.map(([id]) => id);
console.log(duplicateIDs);
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
<div id="e"></div>
<div id="f"></div>
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
Here’s an alternative that uses Sets and Maps, and a lower number of intermediate arrays between Array methods:
const duplicateIDs = Array.from(Array.from(document.querySelectorAll("[id]"), ({id}) => id)
.reduce((allIDs, id) => {
if(allIDs.map.has(id)){
allIDs.dupes.add(id);
}
else{
allIDs.map.set(id, 1);
}
return allIDs;
}, {
map: new Map(),
dupes: new Set()
}).dupes);
console.log(duplicateIDs);
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
<div id="e"></div>
<div id="f"></div>
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="a"></div>
Ideally you shouldn't have multiple elements with same id. I know your question is about detecting if there are elements with same id. IMHO, whatever you are trying with this probably would be a short term fix. You need to fix the root of the problem i.e. no two elements should have the same id.
After reading all the comments I tested the different approaches in a relatively big website (http://www.powtoon.com html5 studio) and here are the results.
Both answers returned the same results, but looks like the querySelectorAll was indeed faster:
QuerySelectorAll
function getDuplicateIdsUsingQuerySelector(){
const idsOccurances = Array.from(document.querySelectorAll('[id]'))
.map(elem => elem.id)
.reduce((allIDs, id) => {
allIDs[id] = (allIDs[id] || 0) + 1
return allIDs
}, {})
return Object.entries(idsOccurances)
.filter(([id, occurances]) => occurances > 1)
.map(([id]) => id)
}
1.5ms per call on average.
Regex
function getDuplicateIdsUsingRegEx(){
const idRegex = / id=".*?"/ig
const ids = document.body.innerHTML.match(idRegex)
const idsOccurances = ids.reduce((allIDs, id) => {
allIDs[id] = (allIDs[id] || 0) + 1
return allIDs
}, {})
return Object.entries(idsOccurances)
.filter(([id, occurrences]) => occurrences > 1)
.map(([id]) => id.slice(4, -1))
}
5.5ms per call average.

Javascript traversing DOM upwards

In jQuery to traverse DOM several steps upward instead of
$(this).parent().parent().parent().parent().parent().click();
I can write short version:
$(this).parent(5).click();
So, I want to know, if there's a way to shorten code instead of spamming '.parentNode'?
Rather trivial:
function parent (element, n = 1) {
let {parentNode} = element;
for (let i = 1; parentNode && i < n; i++) {
({parentNode} = parentNode);
}
return parentNode;
}
const span = document.querySelector('span');
const directParent = parent(span); // direct parent node
const greatGreatGreatGrandParent = parent(span, 5); // parent node 5 times
console.log(directParent.getAttribute('data-foo')); // baz
console.log(greatGreatGreatGrandParent.getAttribute('data-foo')); // bar
<div data-foo="bar">
<div>
<div>
<div>
<div data-foo="baz">
<span>Hello, World!</span>
</div>
</div>
</div>
</div>
</div>
I check for parentNode because it might be null. In that case, I break the loop and return null, because continuing the loop would result in an error:
({parentNode} = null); // TypeError: can't convert null to object
The parentheses are necessary to indicate that the opening { doesn't start a block, but a destructuring assignment:
{parentNode} = parentNode; // SyntaxError: expected expression, got '='
Edit:
I must admit that my solution is rather condensed and uses some new JavaScript features to make it even more succinct.
What does let {parentNode} = element; mean?
Object destructuring lets you read properties from an object in a more succinct way:
let {parentNode} = element;
is equivalent to:
let parentNode = element.parentNode;
As explained above,
({parentNode} = parentNode);
is equivalent to:
parentNode = parentNode.parentNode;
What does parentNode && i < n exactly?
Every for-loop has a condition. If this condition is true, the loop is continued. If the condition is false, it breaks out of the loop.
I could write:
for (let i = 1; i < n; i++) {
// ...
}
That would run the loop n - 1 iterations. After the last iteration, i is equal to n, so the condition evaluates to false and it stops, which is fine.
parentNode && i < n extends this condition. It not only checks whether i is less than n, but it also checks if parentNode contains a truthy value. If parentNode is null (which might happen is an element has no parent node), the loop will break, because it can't read the property parentNode of null.
I hope you understand the explanations with the following function which is equivalent to the original one:
function parent (element, times) {
var n; // how many times 'parentNode'?
if (times) {
n = times;
} else {
n = 1;
}
var elementToReturn = element.parentNode;
for (var i = 1; i < n; i++) {
if (elementToReturn === null) {
break;
} else {
elementToReturn = elementToReturn.parentNode;
}
}
return elementToReturn;
}
The sorcerer's one-liner...
It's the same as #petermader's only shorter. I guess there's less of a need to be super explicit here, as this would probably be just an imported utility function, and it's still more reliable than a for loop.
const getParentElement = ({ parentNode }, n = 1) =>
Array.from({ length: n - 1 }, () => ({ parentNode } = parentNode)) && parentNode

How do I replace all instances of prices (beginning with "US$") in html page using jquery?

Say I have the following HTML:
<div class="L-shaped-icon-container">
<span class="L-shaped-icon">Something is US$50.25 and another thing is US$10.99.</span>
</div>
What I'd like to do is replace all instances of US$XX.xx with GBP£YY.yy on the live page using jquery.
The value of GBP would be determined by my own currency conversion ratio.
So I'm assuming what I'd first need to do is use a regular expression to get all instances of the prices which would be anything beginning with USD$ and ending after .xx? Prices will always have cents displayed.
Then I'm stuck what would be the best way to accomplish the next part.
Should I wrap these instances in a span tag with a class, then use jquery.each() function to loop through each and replace the contents with a jquery(this).html("GBP£YY.yy")?
Any help setting me on the right path would be greatly appreciated. Thanks guys.
base method for text replacements:
var textWalker = function (node, callback) {
var nodes = [node];
while (nodes.length > 0) {
node = nodes.shift();
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === child.TEXT_NODE)
callback(child);
else
nodes.push(child);
}
}
};
stuff you need to do:
var USDinGBP = 0.640573954;
textWalker(document, function (n) {
n.nodeValue = n.nodeValue.replace(/(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
})
})
you can fire that on ANY site. it will even replace titles etc.
so.. to tell you about the benefits of not using jquery for this:
jquery will process and wrap every single element in a browser compatible way.
using a native javascript solution would speed up this process alot.
using native textnodes also is benefitial since it will not break event handlers for child elements.
you should also consider using fastdom.
it does not matter if you are using jquery or native js. after writing to elements the dom has to do certain tasks before it can be read again. in the end you will loose some time for each edited element.
to give you a fastdom example:
var textWalker = function (node, callback) {
var nodes = [node];
while (nodes.length > 0) {
node = nodes.shift();
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === child.TEXT_NODE)
callback(child);
else
nodes.push(child);
}
}
};
var textReplace = function (node, regex, callback) {
textWalker(node, function (n) {
fastdom.read(function () {
var text = n.nodeValue;
if (!regex.test(text)) {
return;
}
text = text.replace(regex, callback);
fastdom.write(function () {
n.nodeValue = text;
});
});
});
};
// put this function call into your onload function:
var USDinGBP = 0.640573954;
textReplace(document, /(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
});
this will basically do the job in an instant.
if you want to go even further you could add this to jquery as following:
jQuery.fn.textReplace = function (regex, callback) {
this.each(function () {
textReplace(this, regex, callback);
});
};
and call it like that:
var USDinGBP = 0.640573954;
jQuery(".L-shaped-icon").textReplace(/(USD?\$(\d+(\.\d+)?))/g, function($0, $1, $2){
return "GBP£" + (parseFloat($2) * USDinGBP).toFixed(2);
});
If all of these values are directly in the span, if not you can give them a unique class and use it to iterate over them, you can use the following
You first get the numeric part of the string in a variable
convert the currency store it in other variable.
replace US$ with GBP
replace numeric part of the string with converted value
jQuery:
("span").each(function() {
var currencyVal=$(this).text().match(/\d/);
var convertedVal=currencyVal * 100; // just for example.
$(this).text($(this).text().replace(/^US$/,'GBP£'));
$(this).text($(this).text().replace(/\d/,convertedVal));
});
I hope this will helps you. Here is working Fiddle
HTML:
<div class="L-shaped-icon-container">
<span class="L-shaped-icon">Something is US$50.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is BR$20.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is US$10.25 and another thing is US$10.99.</span>
<span class="L-shaped-icon">Something is US$50.20 and another thing is GR$10.99.</span>
</div>
<button class="btnUpdate">Update</button>
JavaScript Code:
function UpdateCurrency(){
var UStoGB=10;
$('span').each(function(e){
var matchedText = $(this).text().match(/US\$\S+/g);
var updatedText = $(this).text();
if(matchedText){
for(var i=0;i<= matchedText.length;i++){
if(matchedText[i]){
var currentValue=matchedText[i].replace('US$','');
if(!currentValue) currentValue=0;
var newCurrency = ( parseFloat(currentValue) * UStoGB);
updatedText= updatedText.replace(matchedText[i],'GBP£'+newCurrency);
}
}
}
$(this).text(updatedText);
});
return false;
}

Javascript getElementById based on a partial string

I need to get the ID of an element but the value is dynamic with only the beginning of it is the same always.
Heres a snippet of the code.
<form class="form-poll" id="poll-1225962377536" action="/cs/Satellite">
The ID always starts with poll- then the numbers are dynamic.
How can I get the ID using just JavaScript and not jQuery?
You can use the querySelector for that:
document.querySelector('[id^="poll-"]').id;
The selector means: get an element where the attribute [id] begins with the string "poll-".
^ matches the start
* matches any position
$ matches the end
jsfiddle
Try this.
function getElementsByIdStartsWith(container, selectorTag, prefix) {
var items = [];
var myPosts = document.getElementById(container).getElementsByTagName(selectorTag);
for (var i = 0; i < myPosts.length; i++) {
//omitting undefined null check for brevity
if (myPosts[i].id.lastIndexOf(prefix, 0) === 0) {
items.push(myPosts[i]);
}
}
return items;
}
Sample HTML Markup.
<div id="posts">
<div id="post-1">post 1</div>
<div id="post-12">post 12</div>
<div id="post-123">post 123</div>
<div id="pst-123">post 123</div>
</div>
Call it like
var postedOnes = getElementsByIdStartsWith("posts", "div", "post-");
Demo here: http://jsfiddle.net/naveen/P4cFu/
querySelectorAll with modern enumeration
polls = document.querySelectorAll('[id ^= "poll-"]');
Array.prototype.forEach.call(polls, callback);
function callback(element, iterator) {
console.log(iterator, element.id);
}
The first line selects all elements in which id starts ^= with the string poll-.
The second line evokes the enumeration and a callback function.
Given that what you want is to determine the full id of the element based upon just the prefix, you're going to have to do a search of the entire DOM (or at least, a search of an entire subtree if you know of some element that is always guaranteed to contain your target element). You can do this with something like:
function findChildWithIdLike(node, prefix) {
if (node && node.id && node.id.indexOf(prefix) == 0) {
//match found
return node;
}
//no match, check child nodes
for (var index = 0; index < node.childNodes.length; index++) {
var child = node.childNodes[index];
var childResult = findChildWithIdLike(child, prefix);
if (childResult) {
return childResult;
}
}
};
Here is an example: http://jsfiddle.net/xwqKh/
Be aware that dynamic element ids like the ones you are working with are typically used to guarantee uniqueness of element ids on a single page. Meaning that it is likely that there are multiple elements that share the same prefix. Probably you want to find them all.
If you want to find all of the elements that have a given prefix, instead of just the first one, you can use something like what is demonstrated here: http://jsfiddle.net/xwqKh/1/
I'm not entirely sure I know what you're asking about, but you can use string functions to create the actual ID that you're looking for.
var base = "common";
var num = 3;
var o = document.getElementById(base + num); // will find id="common3"
If you don't know the actual ID, then you can't look up the object with getElementById, you'd have to find it some other way (by class name, by tag type, by attribute, by parent, by child, etc...).
Now that you've finally given us some of the HTML, you could use this plain JS to find all form elements that have an ID that starts with "poll-":
// get a list of all form objects that have the right type of ID
function findPollForms() {
var list = getElementsByTagName("form");
var results = [];
for (var i = 0; i < list.length; i++) {
var id = list[i].id;
if (id && id.search(/^poll-/) != -1) {
results.push(list[i]);
}
}
return(results);
}
// return the ID of the first form object that has the right type of ID
function findFirstPollFormID() {
var list = getElementsByTagName("form");
var results = [];
for (var i = 0; i < list.length; i++) {
var id = list[i].id;
if (id && id.search(/^poll-/) != -1) {
return(id);
}
}
return(null);
}
You'll probably have to either give it a constant class and call getElementsByClassName, or maybe just use getElementsByTagName, and loop through your results, checking the name.
I'd suggest looking at your underlying problem and figure out a way where you can know the ID in advance.
Maybe if you posted a little more about why you're getting this, we could find a better alternative.
You use the id property to the get the id, then the substr method to remove the first part of it, then optionally parseInt to turn it into a number:
var id = theElement.id.substr(5);
or:
var id = parseInt(theElement.id.substr(5));
<form class="form-poll" id="poll-1225962377536" action="/cs/Satellite" target="_blank">
The ID always starts with 'post-' then the numbers are dynamic.
Please check your id names, "poll" and "post" are very different.
As already answered, you can use querySelector:
var selectors = '[id^="poll-"]';
element = document.querySelector(selectors).id;
but querySelector will not find "poll" if you keep querying for "post": '[id^="post-"]'
If you need last id, you can do that:
var id_list = document.querySelectorAll('[id^="image-"]')
var last_id = id_list.length
alert(last_id)

Categories

Resources