Jquery find() class with regular expression - javascript

I have div elements that have an icon inside, and I want to use the find() method to return which of the 4 types of icons is inside. Is there a way to use find() with a regular expression to return the class?
EDIT: I've updated the divs to show how the <i> is "buried" within the div and not a direct child
<div id="a" class="tile">
<span>
<span>
<i class="led-red"></i>
Led Red
</span>
</span>
</div>
<div id="b" class="tile">
<span>
<span>
<i class="led-green"></i>
Led Green
</span>
</span>
</div>
<div id="c" class="tile">
<span>
<span>
<i class="led-yellow"></i>
Led Yellow
</span>
</span>
</div>
var rexValues = /(led-green|led-yellow|led-red|led-null)/;
//this would work for element "a" if element "a" had the class attached,
//but because the class is buried inside I need to use find()
var aclass = a.className.match(rexValues);
//I'm looking to do something more like
var aclass = $(a).find(rexValues);
This is part of a sort function where I am sorting the divs based on their icon. The solution I'm basing off of is here: https://stackoverflow.com/a/46026203/9537489

Don't use regex when not needed
I am using the sort order from your linked answer
container can be document.body if you do not have a static container near the tiles, but then the tiles will be moved if you have other stuff in the container. Let me know if you need to replace in place
I use the [...nodeList] spread syntax because sort is not a native method of a nodeList. NOTE: the spread has to be on the querySelectorAll statement or the tiles will not retain their sort order
const container = document.body; // document.getElementById("container");
const sortOrder = { red: 0, orange: 1, yellow: 2, green: 3 };
const tiles = [...container.querySelectorAll(".tile")]; // needed for the .sort
const getClassStartingWith = (elem, cls) => [...elem.classList].filter(clss => clss.startsWith(cls))[0];
const prefix = "led-";
tiles.sort((a, b) => {
const aClass = getClassStartingWith(a.querySelector("i"), prefix).replace(prefix, "");;
const bClass = getClassStartingWith(b.querySelector("i"), prefix).replace(prefix, "");
console.log(aClass,sortOrder[aClass],bClass, sortOrder[bClass],sortOrder[aClass] - sortOrder[bClass])
return sortOrder[aClass] - sortOrder[bClass]
});
tiles.forEach(tile => container.appendChild(tile))
<div id="c" class="tile">
<span>
<span>
<i class="led-yellow"></i>
Led Yellow
</span>
</span>
</div>
<div id="b" class="tile">
<span>
<span>
<i class="led-green"></i>
Led Green
</span>
</span>
</div>
<div id="a" class="tile">
<span>
<span>
<i class="led-red"></i>
Led Red
</span>
</span>
</div>

the find method in jQuery doesn't support this. There is an extension that you could use to add this functionality
Are those classes with the prefix limited or unique? You could use the selector startsWith ([<attr>^=<value>], i.e. [class^=led-]). Here's the CSS selector reference if you don't want to add the jQuery based functionality

Related

get a nested element inside a div by class name

I am trying to get an span element which has a class of main-tag inside a nested div. But i don't want to get that element through querySelector as there are many elements in my html file with the same class and i don't intend to use IDs for that.
I know i am making some mistake in my recursive function. That's why this code is not working.
So for this i'm recursively calling a function to get the desired element. But undefined is returned by found variable.
function getElem(cell, name) {
let found;
if (cell.classList.contains(name)) return cell;
else if (cell.children.length === 0) return null;
else {
found = Array.from(cell.children).find((element) =>
element.classList.contains(name)
);
if (found === undefined) {
Array.from(cell.children).forEach((element) => {
found = getElem(element, name);
});
} else return found;
}
}
console.log(getElem(document.getElementById("block-one"), "main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</div>
<span class="abc">not found</div>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</div>
<span class="bogus-tag">never find out</div>
</div>
</div>
Your markup is using </div> where </span> is expected.
You can use full CSS selectors with querySelector. So for instance, to find the first element with the class main-tag inside the element with id="block-one", you can do this:
const element = document.querySelector("#block-one .main-tag");
Live Example:
console.log(document.querySelector("#block-one .main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
Or alternatively, you can call querySelector on an element to only look at its descendants:
const element = document.getElementById("block-one").querySelector(".main-tag");
Live Example:
console.log(document.getElementById("block-one").querySelector(".main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
A key difference between those is that the second one will throw an error if there is no id="block-one" element; the first will just return null. You could fix that using the newish optional chaining operator:
const element = document.getElementById("block-one")?.querySelector(".main-tag");
element will be either A) The matching element if there is one; B) null if there's an id="block-one" element but there are no .main-tag elements in it; or C) undefined if there is no id="block-one" element at all. (Even though document.getElementById returns null when it doesn't find something, the optional chaining operator results in undefined when its left-hand operand is either undefined or null.)
Or just use the document.querySelector option (the first one above), which gives you the matching element or null.
You don't seem to want this from your HTML, but: If you want to allow for the possibility the element itself has the class (getElem seems to allow for that possibility), you can use a group selector:
const element = document.querySelector("#block-one.main-tag, #block-one .main-tag");
Live Example:
console.log(document.querySelector("#block-one.main-tag, #block-one .main-tag"));
<div id="block-one" class="main-tag">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
That works because the first member of the group, #block-one.main-tag (without a space) only matches the id="block-one" element if it has the class. If block-one doesn't have the class but one of its descendants does, that's found by the other member of the group, #block-one .main-tag (with the space). If the block-one element has the class and one of its descendants does, the block-one element is the one found, since it's first in document order (a parent is before its children in document order).
Your HTML is invalid.
When fixed, you can get the span using selectors directly
console.log(document.querySelector("#block-one > div.here > span.main-tag").textContent)
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, I am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>

Identifying an HTML element that has *no* attributes of any kind with JavaScript?

I have a tool that is used for cleaning up crappy HTML in order to make sense of the underlying structure. Having stripped class, style attributes and various Angular attributes, often the resulting markup is a series of nested <div> or <span> elements that have no attributes. What I would like to do is provide option to do a second pass where a <div> or <span> with no attributes can be removed, to flatten the structure more.
Is there a way in JavaScript to confirm that an HTML element has no attributes of any kind?
And if that is possible, how might I approach this stripping of an element?
For example, assuming I have this:
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
That should end up as:
<div id="blah">
<span dir="auto">
Joe Bloggs
</span>
</div>
Which I would then format to:
<div id="blah">
<span dir="auto">
Joe Bloggs
</span>
</div>
So I'd need a function that can walk the DOM and remove a div (or span) that has no attributes while leaving the inner contents intact (unless of course any of those inner elements can also be stripped for same reason).
Any pointers before I go ahead and construct a shoddy (but working) script would be appreciated!
The attributes property will tell you how many attributes an element has.
const countAttributes = element => console.log({
count: element.attributes.length,
list: [...element.attributes].map(attribute => attribute.name)
});
const divs = document.querySelectorAll('div');
divs.forEach(countAttributes);
<div></div>
<div class="one attribute"></div>
<div class="two attributes" id="second attribute"></div>
Do note that an element without attributes might still be used for something (e.g. a stylesheet might reference it in relation to other elements).
Here's how I did it.
I created a demo element, to get the elements, then I checked the number of elements, I checked if the element should be stripped.
I replaced the element with its children, and if it didn't have any, I used its text
function strip(startElement, toStrip) {
const test = document.createElement('div');
test.innerHTML = startElement.outerHTML;
[...test.querySelectorAll('*')].forEach(elem => {
if (!elem.attributes.length && toStrip.includes(elem.tagName.toLowerCase())) {
if (elem.children.length) elem.replaceWith(...elem.children);
else elem.replaceWith(elem.innerText);
} ;
});
return test.innerHTML;
}
console.log(strip(document.querySelector('div'), ['span', 'div']));
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
Updated Code
Here you go.
document.querySelectorAll("div").forEach((ele) => {
if (ele.attributes.length === 0) {
var fragment = document.createDocumentFragment();
while (ele.firstChild) {
fragment.appendChild(ele.firstChild);
}
ele.parentNode.replaceChild(fragment, ele);
}
});
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
So final output would be
<div id="blah">
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>

Append element in JavaScript

I have the below HTML structure.
<div class="SomeClassName">
<a href="https://abc.xyz">
<span>
<img id="SomeImgID1" />
</span>
</a>
<a href="https://xyz.abc">
<span>
<img id="SomeImgID2" />
</span>
</a>
</div>
I want to append <div> tag before each <img> tag, how do I do it with pure javascript?
Something like
let divElement=document.querySelection('div.someClass');
Array.from(divElement.querySelectionAll('img'))
.forEach(el=>divElement.insertBefore(document.createElement('div'),el));
Again, if you want to wrap img then you can use next snippet:
let divElement=document.querySelection('div.someClass');
Array.from(divElement.querySelectionAll('img'))
.forEach(el=>{
let div=document.createElement('div');
divElement.insertBefore(div,el));
div.appendChild(el);
});
For more information see Element.insertBefore on MDN
iaMsUPerNOva finally i got you and i am here with solution .
<a class="image" href="https://abc.xyz"> <!--note this-->
$(function() {
var node = $("a.image"); // This will copy your a tag so that img tag will also get copied.
$(".SomeClassName").append('<div>hello</div>');//This will create a new div containing hello text
$("a.image").remove(); // This will remove a tag which is already copied to node.
$("SomeClassName").append(node); //Finally we are going to add newly created div to SomeClassName.
});
Okay, so what we want to do here involves two steps:
Find each IMG element inside your DIV with class SomeClassName.
For each of these IMG elements, create a new DIV and insert it before the element.
Here is how you do that.
/* document.querySelectorAll returns a NodeList.
The spread operator "..." converts it into an array,
so that we can call the forEach method on it. */
[...document.querySelectorAll("div.SomeClassName img")].forEach(x => {
// Here we create the new DIV
let div = document.createElement("div");
// Just some sample text to show
div.textContent = "Hello!";
// x.parentNode finds the parent of the IMG element.
// The frst parameter of node.InsertBefore is the
// element we want to insert as its child, and the
// the second parameter is the reference element
// before which this new element will be inserted
x.parentNode.insertBefore(div, x);
});
Thank you, everyone, for your responses. I forgot to mention before that I had another parent <div> and the final structure was like this.
<div class="ParentClassName">
<div class="ChildClassName">Some Content</div>
<div class="ChildClassName">Some Content2</div>
<div class="ChildClassName">
<a href="https://abc.xyz">
<span>
<img id="SomeImgID1" />
</span>
</a>
<a href="https://xyz.abc">
<span>
<img id="SomeImgID2" />
</span>
</a>
</div>
</div>
I could able to wrap all <img> tags inside <div> by doing the below step.
Note: If your classname is dynamic you can remove it from the query selectors and you can put only the selector element like document.querySelector("div");
let divElement = document.querySelector("div.ParentClassName");
Array.from(divElement.querySelectorAll("div.ChildClassName img")).forEach(
(el) => {
let div = document.createElement("div");
el.parentElement.insertBefore(div, el);
div.appendChild(el);
}
);
and my final output was
<div class="ParentClassName">
<div class="ChildClassName">Some Content</div>
<div class="ChildClassName">Some Content2</div>
<div class="ChildClassName">
<a href="https://abc.xyz">
<span>
<div>
<img id="SomeImgID1" />
</div>
</span>
</a>
<a href="https://xyz.abc">
<span>
<div>
<img id="SomeImgID2" />
</div>
</span>
</a>
</div>
</div>

Getting text content of a div element excluding its children

I have the following HTML code and I need to console.log only Shipping.
I tried a few methods but can't seem to get it to work.
I tried selecting first its children and printing out the textContent of its parent - no go. I could delete its children and print out what's left but I can't do that.
Any suggestions?
<div class="accordion shadowed-box shipping collapsed summary">
<fieldset>
<legend>
Shipping
<div id="shippingTooltip" class="form-field-tooltip cvnship-tip" role="tooltip">
<span class="tooltip">
<div class="tooltip-content" data-layout="small tooltip-cvn">
<div id="cart-checkout-shipping-tooltip" class="html-slot-container">
<p>We ship UPS, FedEx and/or USPS Priority Mail.<br>
<a class="dialogify" data-dlg-options="{"height":200}" href="https://www.payless.com/customer-service/ordering-and-shipping/cs-ordering-shipping-schedule.html" title="shipping information">Learn more about our shipping methods and prices.</a>
</p>
</div>
</div>
</span>
</div>
Edit
</legend>
</fieldset>
</div>
I tried this:
var accordionChildren = document.querySelector('.accordion.shadowed-box.shipping>fieldset>legend *');//selects the first child
var accordionTitle = accordionChildren.parentElement;
var text = accordionTitle.textContent;
console.log(text);
I want to get Shipping but instead I get still all the text contents of the legend element.
you can access Text nodes by iterating over the child nodes (or access the intended node directly) of the accordionTitle variable.
let textNode = accordionTitle.childNodes[0],
text = textNode.textContent;
console.log(text);
See https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes and https://developer.mozilla.org/en-US/docs/Web/API/Text
You just need to find the TextNode child from all of the elements children, you do this by iterating over all of the childNodes and when the node type matches TextNode, return its textContext.
For a jQuery based solution on how to pick the TextNode child of an element see this question - but my example shows how to do it in vanilla ES (with a for loop over childNodes):
Object.defineProperty(HTMLElement.prototype, 'onlyTextContent', {
enumerable: false,
configurable: false,
get: function() {
for(let i = 0; i < this.childNodes.length; i++) {
if(this.childNodes[i].nodeType === Node.TEXT_NODE) {
return this.childNodes[i].textContent.trim();
}
}
return null;
}
});
document.addEventListener('DOMContentLoaded', function() {
console.log(
document.getElementById('legend1').onlyTextContent
);
});
<div class="accordion shadowed-box shipping collapsed summary">
<fieldset>
<legend id="legend1">
Shipping
<div id="shippingTooltip" class="form-field-tooltip cvnship-tip" role="tooltip">
<span class="tooltip">
<div class="tooltip-content" data-layout="small tooltip-cvn">
<div id="cart-checkout-shipping-tooltip" class="html-slot-container">
<p>We ship UPS, FedEx and/or USPS Priority Mail.<br>
<a class="dialogify" data-dlg-options="{"height":200}" href="https://www.payless.com/customer-service/ordering-and-shipping/cs-ordering-shipping-schedule.html" title="shipping information">Learn more about our shipping methods and prices.</a>
</p>
</div>
</div>
</span>
</div>
Edit
</legend>
</fieldset>
</div>
You can get the contents of the <legend> tag as a string and then use a regular expression to remove the HTML tags and their content inside. Like this:
let legends = document.querySelector('.accordion.shadowed-box.shipping>fieldset>legend');
let title = legends.innerHTML.replace(/<.*/s, '');
// title = "Shipping"
The regular expression matches the first < character and everything that follows. So we replace that match with an empty string ''.

Grabbing number from selected class based on string match

I need to grab the number between [ and ] within the selected class of an li list, and store the number in a variable. I've tried the following, but I'm missing something. I'm not sure of the regex required to look between brackets and grab a string.
Javascript
var assetID = $(".selected:contains('on-air-date').find('[]');
HTML
<ul id="asset-list" class="expandable selectable normal" style="height: 671px;">
<li class="selected">
<div class="thumb">
<a href="/content/assets/750">
<img src="https://www.google.com/images/srpr/logo11w.png">
</a>
</div>
<div class="title">
<div>
<strong>Title of story</strong>
<br>
<span class="on-air-date">
On air: 10/28/14 05:30:00pm
[750]
</span>
<br>
<span class="blue radius label">Staging</span>
<span class="green radius label">Live</span>
</div>
</div>
</li>
<li>
<div class="thumb">
<a href="/content/assets/4200">
<img src="https://www.google.com/images/srpr/logo11w.png">
</a>
</div>
<div class="title">
<div>
<strong>Another story title</strong>
<br>
<span class="on-air-date">
On air: 12/10/14 02:09:18pm
[4200]
</span>
<br>
<span class="blue radius label">type label</span>
</div>
</div>
</li>
<li>
<div class="thumb">
<a href="/content/assets/4201">
<img src="https://www.google.com/images/srpr/logo11w.png">
</a>
</div>
<div class="title">
<div>
<strong>Yet another story title</strong>
<br>
<span class="on-air-date">
On air: 12/10/14 02:09:18pm
[4201]
</span>
<br>
<span class="blue radius label">type label</span>
</div>
</div>
</li>
</ul>
JSFiddle: link
Your current code is invalid, as :contains is used to look for a text value within an element, not a class. You need to use find() and text() to retrieve the value in the element. From there you can use a regular expression to extract the value in the braces. Try this:
var selectedAirDateText = $('.selected').find('.on-air-date').text();
var matches = /\[(.+)\]/gi.exec(selectedAirDateText);
console.log(matches[1]); // = '750'
Example fiddle
A regular expression can help you get the number as follows:
var num = $('.selected span.on-air-date').text().replace(/[^\[]*\[(\d+)\].*/,'$1');
Demo
:contains('on-air-date') not valid, you cannot use contains to access the child elements with the specified class. Also .find('[]') not valid. The following code worked for me:
$('.selected').click(function () {
var assetID = $(this).find('.on-air-date').text().split('[')[1].replace(']', '');
//this first splits the text into two by '['
//then we get the second half by [1]
//finally we remove the last character ']' by using .replace
alert(assetID);
})
Demo: https://jsfiddle.net/k3keq3vL/1/
You'll need to first get the single item you need or run an $.each to get all in the page.
//run the each statement
$(".on-air-date").each(function(index,value) {
//set the string (str) variable to the value's text which is inside the <span>
var str = $(value).text();
// match the string with [ ] with anything inside. and then remove the last ]. Then take the substring of the content after the [
var value = str.match(/\[(.*?)\]/g)[0].replace(/\]/g,'').substring(1,str.length-1));
});
http://jsfiddle.net/k3keq3vL/8/
Open your console to see the list of numbers returned in the console.log of the string match and substring

Categories