Javascript .indexOf matching HTML symbols - javascript

I solved a problem in a very sloppy manner, looking for a better solution.
Task
Check if a span contains an HTML symbol(an arrow), if present, replace the symbol with it's opposite.
First Attempt (not showing the second code snipet that does the reverse)
first=headers[0].querySelector('span')
if (first.innerHTML.indexOf('↘') !== -1)
first.innerHTML=first.innerHTML.substring(0, first.innerHTML.length - 13)
first.innerHTML+=' ↖'
Why doesn't that work?
because while ===
but for some reason ↘ === ↘
Seriously! Try this (tested in IE 11 and chrome)
<span>test</span>
<script>
first=document.querySelector('span')
first.innerHTML+='&nbsp&searr;'
console.log(first.innerHTML)
</script>
In the console you get: test ↘
My ugly solution
I copied the arrow into my JS code
if (first.innerHTML.indexOf('↘') !== -1)
first.innerHTML=first.innerHTML.substring(0, first.innerHTML.length - 7)
first.innerHTML+=' &nwarr;'
So...what's the correct way of solving this issue?

If you're worried about having that literal arrow in your code, you shouldn't be. If your toolchain is correctly configured, it's just fine to have that literal arrow in your code.
But if you want to avoid it, you can use a unicode escape for it instead:
const nwarr = "\u2196";
if (first.innerHTML.indexOf(nwarr) !== -1) {
first.innerHTML = first.innerHTML.substring(0, first.innerHTML.length - 7);
}
first.innerHTML += ' &nwarr;'; // <== Did you mean &searr; here? The opposite of nwarr?
Live example (using &searr; in the replacement:
const first = document.getElementById("first");
setTimeout(() => {
const nwarr = "\u2196";
if (first.innerHTML.indexOf(nwarr) !== -1) {
first.innerHTML = first.innerHTML.substring(0, first.innerHTML.length - 7);
}
first.innerHTML += ' &searr;';
}, 800);
<span id="first">Foo &nwarr;</span>
Two side notes:
1. To replace things, I'd use replace rather than substring and +=, not least so you don't have magic numbers (13, 7) in your code:
first.innerHTML = first.innerHTML.replace(/\ \u2196/g, " &searr;");
you might even allow for some browsers to use an character entity rather than character for it:
first.innerHTML = first.innerHTML.replace(/\ (?:\&nwarr;|\u2196)/g, " &searr;");
Live example (using &searr; in the replacement:
const first = document.getElementById("first");
setTimeout(() => {
first.innerHTML = first.innerHTML.replace(/\ (?:\&nwarr;|\u2196)/g, " &searr;");
}, 800);
<span id="first">Foo &nwarr;</span>
2. I would avoid textual manipulation of innerHTML. If you assign to it, even when assigning back what it already had, it tears down all of the elements (and other nodes) within the element you're doing it on, and creates new, replacement elements and nodes.
For instance, you could spin through the text nodes:
const first = document.getElementById("first");
setTimeout(() => {
handleReplacement(first);
}, 800);
function handleReplacement(el) {
for (let child = el.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 1) { // Element
handleReplacement(child);
} else if (child.nodeType === 3) { // Text
child.nodeValue = child.nodeValue.replace(/\u00A0\u2196/g, "\u00A0\u2198");
}
}
}
<span id="first">Foo &nwarr;</span>

Based on T.J. Crowder's very good insights. I patched my test code into this:
<span>test &nwarr;</span>
<script>
const nw="\u2196";
const se="\u2198";
first=document.querySelector('span')
if (first.innerHTML.indexOf(nw) !== -1)
{
console.log("found arrow");
first.innerHTML = first.innerHTML.replace(/\u2196/, se);
}
</script>
It still feels like we've stumbled into a messy/buggy corner of the browser world.

Related

How to remove generated node on page

I'm creating a node manually using vanilla JS. The idea is this alert appears after an input field if the maxlength limit has been reached and is removed if it returns below. This is being used on a CMS that users can use to create forms dynamically, so I won't know if the field will have a maxlength or not, or if it will have anything else after it. I'm using the following code:
document.querySelectorAll('input, textarea').forEach(element => {
if (element.hasAttribute('maxlength')) {
let maxChars = element.getAttribute('maxlength');
let elID = element.getAttribute('id');
let charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
element.addEventListener('input', () => {
let inputLength = element.value.length;
console.log(inputLength);
if (inputLength >= maxChars) {
if (charWarning.length == 0) {
let divAlert = document.createElement('div');
let divAlertText = document.createTextNode(maxChars + ' character limit reached on input');
divAlert.classList.add('text-danger', 'char-limit');
divAlert.setAttribute('aria-live', 'polite');
divAlert.setAttribute('aria-labelledby', elID);
divAlert.appendChild(divAlertText);
element.insertAdjacentElement('afterend', divAlert);
charWarning = document.querySelectorAll('#' + elID + ' + .char-limit');
}
} else {
// console.log(charWarning.length);
if (charWarning.length > 0) {
charWarning.remove(); // This is not working and I have no idea why.
}
}
});
}
});
For whatever reason, the .remove() function isn't working. It's throwing an error:
charWarning.remove is not a function at HTMLTextAreaElement
I don't really understand this. I thought it might be down to the fact that the initial setting of the charWarning prior to the listener was static, so I've added it again at the end of the function creating the warning element. It all works fine, but it's not removing the warning when below the maxlength and throwing that error.
For info, the commented out console.log:
// console.log(charWarning.length);
When uncommented does return 1, when the node has been added.
Can anyone point to what I'm doing wrong?
Ok, so after 2 days of no real responses, I took a late night stab at this again and somehow figured it out through trial and error. I thought the issue was to do with scoping but I couldn't figure out WHAT scope. In the end, I tried changing the "let" to "var" on the warning and that didn't work. So then I changed the selector from document.querySelectorAll to document.querySelector, cleaned up a little of the code and removed the else by using conditional chaining and voilà... result! Much cleaner, much better (and most importantly) functioning code...
However, on researching the properties of aria-live="polite" I discovered that creating and removing the node is the incorrect process. I can create it but when I want to update it for assistive technologies, it's the content I need to change. This kinda goes back to #ZainWilson-WCHStudent's first comment about showing and hiding the content. While I'm still not doing this as I believe it is incorrect for accessibility, it does kind of lean on that idea. I believe that this solution is much more elegant, efficient and (most importantly) accessible:
document.querySelectorAll("input, textarea").forEach((e) => {
if (e.hasAttribute("maxlength")) {
let maxChars = e.getAttribute("maxlength");
let elID = e.getAttribute("id");
let divAlert = document.createElement("div");
let divAlertText = document.createTextNode(
maxChars + " character limit reached on input"
);
divAlert.classList.add("text-danger", "char-limit");
divAlert.setAttribute("aria-live", "polite");
e.insertAdjacentElement("afterend", divAlert);
e.addEventListener("input", () => {
let charWarning = document.querySelector("#" + elID + " + .char-limit");
let inputLength = e.value.length;
if (inputLength >= maxChars) {
charWarning.appendChild(divAlertText);
} else {
if(charWarning.firstChild) {
charWarning.removeChild(charWarning.firstChild);
}
}
});
}
});
Here's a Codepen to test: https://codepen.io/tadywankenobi/pen/OJQXwwR

How to replace overlapping strings in Javascript without destroying the HTML structure

I have a string and an array of N items:
<div>
sometimes the fox can fly really high
</div>
const arr = ['the fox can', 'fox can fly', 'really high']`
I want to find a way to replace the text inside the div with HTML to highlight those specific phrases inside the array without breaking the HTML. This can be problematic because I can't do a simple loop and replace because then other words will not match after a replacement because the highlight span would break something like indexOf or includes on the innerHTML, sure I can use innerText to read the text but it doesn't provide anything that makes it so I can add the "next" span without breaking the original HTML highlights. Ideally, I also want to be able to customize the class name depending on the word I use rather than just a generic highlight class too.
The outcome should be
<div>
sometimes
<span class="highlight-1">the <span class="highlight-2">fox can</span></span><span class="highlight-2"> fly</span> <span class="highlight-3">really high</span>
</div>
What have I tried?
I've really thought about this and cannot find any resources online that help with this scenario and the main, Currently, I also need extra values such as charStart and charEnd of the word, I don't like this solution because it depends on using the DOMParser() API and it feels really hacky, definitely isn't performant and I just get a "vibe" that I shouldn't be doing this method and there must be better solutions, I am reaching out to SO for ideas on how I can accomplish this challenge.
let text = `<p id="content">${content}</p>`
let parser = new DOMParser().parseFromString(text, "text/html")
for (const str of strings) {
const content = parser.querySelector("#content")
let descLength = 0
for (const node of content.childNodes) {
const text = node.textContent
let newTextContent = ""
for (const letter in text) {
let newText = text[letter]
if (descLength === str.charStart) {
newText = `<em class="highlight ${str.type}" data-id="${str.id}">${text[letter]}`
} else if (descLength === str.charEnd) {
newText = `${text[letter]}</em>`
}
newTextContent += newText
descLength++
}
node.textContent = newTextContent
}
// Replace the < with `<` and replace > with `>` to construct the HTML as text inside lastHtml
const lastHtml = parser
.querySelector("#content")
.outerHTML.split("<")
.join("<")
.split(">")
.join(">")
// Redefine the parser variable with the updated HTML and let it automatically correct the element structure
parser = new DOMParser().parseFromString(lastHtml, "text/html")
/**
* Replace the placeholder `<em>` element with the span elements to prevent future issues. We need the HTML
* to be invalid for it to be correctly fixed by DOMParser, otherwise the HTML would be valid and *not* render how we'd like it to
* Invalid => `<span>test <em>title </span>here</em>
* Invalid (converted) => `<span>test <em>title </em></span><em>here</em>
* Valid => `<span>test <span>title </span>here</span>
*/
parser.querySelector("#content").innerHTML = parser
.querySelector("#content")
.innerHTML.replaceAll("<em ", "<span ")
.replaceAll("</em>", "</span>")
}
I'll go over your example just to give an idea. Below code is not a clean function, please adjust it according to your needs.
const str = "sometimes the fox can fly really high";
const arr = ['the fox can', 'fox can fly', 'really high'];
// First, find the indices of start and end positions for your substrings.
// Call them event points and push them to an array.
eventPoints = [];
arr.forEach((a, i) => {
let index = strLower.indexOf(a)
while (index !== -1) {
let tagClass = `highlight-${i}`
eventPoints.push({ pos: index, className: tagClass, eventType: "start" })
eventPoints.push({ pos: index + a.length, className: tagClass, eventType: "end" })
index = strLower.indexOf(a, index + 1)
}
return
});
// Sort the event points based on the position properties
eventPoints.sort((a, b) => a.pos < b.pos ? -1 : a.pos > b.pos ? 1 : 0);
// Init the final string, a stack and an index to keep track of the current position on the full string
let result = "";
let stack = [];
let index = 0;
// Loop over eventPoints
eventPoints.forEach(e => {
// concat the substring between index and e.pos to the result
result += str.substring(index, e.pos);
if (e.eventType === "start") {
// when there is a start event, open a span
result += `<span class="${e.className}">`;
// keep track of which span is opened
stack.push(e.className);
}
else {
// when there is an end event, close tags opened after this one, keep track of them, reopen them afterwards
let tmpStack = [];
while (stack.length > 0) {
result += "</span>";
let top = stack.pop();
if (top === e.className) {
break;
}
tmpStack.push(top);
}
while (tmpStack.length > 0) {
let tmp = tmpStack.pop();
result += `<span class="${tmp}">`;
stack.push(tmp);
}
}
index = e.pos;
});
result += str.substring(index, str.length)
console.log(result);

Check if string is starting with prefix

I would like to check with javascript if array items starts with the word "prefix".
So far I've done something like this:
let array = ["prefix-word1", "prefix-word2"];
array.forEach(function(element) {
if (element.lastIndexOf('prefix', 0) == 0) {
console.log('prefix');
}
}
For some reason I keep get an error that prefix is not defined. Please help.
This one works (check the comments on the code):
let array = ["prefix-word1", "prefix-word2" , "does not start with prefix"];
array.forEach(function(element) {
// Check if the first word is prefix
if (element.indexOf('prefix') == 0) {
console.log('prefix');
console.log(element);
}
});
console.log("Second approach which is not suggested");
array.forEach(function(element) {
// not the correct approach as suggested by #Barmar though it works
if (element.lastIndexOf('prefix',0) == 0) {
console.log('prefix');
console.log(element);
}
});
Try using double quotation marks instead of single like this "prefix" in functions.

How do you find the (string) length of a starting tag or ending tag?

I'm trying to write a jQuery or pure Javascript function (preferring the more readable solution) that can count the length of a starting tag or ending tag in an HTML document.
For example,
<p>Hello.</p>
would return 3 and 4 for the starting and ending tag lengths. Adding attributes,
<span class="red">Warning!</span>
would return 18 and 7 for the starting and ending tag lengths. Finally,
<img src="foobar.png"/>
would return 23 and 0 (or -1) for the starting and ending tag lengths.
I'm looking for a canonical, guaranteed-to-work-according-to-spec solution, so I'm trying to use DOM methods rather than manual text manipulations. For example, I would like the solution to work even for weird cases like
<p>spaces infiltrating the ending tag</ p >
and
<img alt="unended singleton tags" src="foobar.png">
and such. That is, my hope is that as long as we use proper DOM methods, we should be able to find the number of characters between < and > no matter how weird things get, even
<div data-tag="<div>">HTML-like strings within attributes</div>
I have looked at the jQuery API (especially the Manipulation section, including DOM Insertion and General Attributes subsections), but I don't see anything that would help.
Currently the best idea I have, given an element node is
lengthOfEndTag = node.tagName.length + 3;
lengthOfStartTag = node.outerHTML.length
- node.innerHTML.length
- lengthOfEndTag;
but of course I don't want to make such an assumption for the end tag.
(Finally, I'm familiar with regular expressions—but trying to avoid them if at all possible.)
EDIT
#Pointy and #squint helped me understand that it's not possible to see </ p >, for example, because the HTML is discarded once the DOM is created. That's fine. The objective, adjusted, is to find the length of the start and end tags as would be rendered in outerHTML.
An alternate way to do this could be to use XMLSerializer's serializeToString on a clone copy of the node (with id set) to avoid having to parse innerHTML, then split over "><"
var tags = (function () {
var x = new XMLSerializer(); // scope this so it doesn't need to be remade
return function tags(elm) {
var s, a, id, n, o = {open: null, close: null}; // spell stuff with var
if (elm.nodeType !== 1) throw new TypeError('Expected HTMLElement');
n = elm.cloneNode(); // clone to get rid of innerHTML
id = elm.getAttribute('id'); // re-apply id for clone
if (id !== null) n.setAttribute('id', id); // if it was set
s = x.serializeToString(n); // serialise
a = s.split('><');
if (a.length > 1) { // has close tag
o.close = '<' + a.pop();
o.open = a.join('><') + '>'; // join "just in case"
}
else o.open = a[0]; // no close tag
return o;
}
}()); // self invoke to init
After running this, you can access .length of open and close properties
tags(document.body); // {open: "<body class="question-page">", close: "</body>"}
What if an attribute's value has >< in it? XMLSerializer escapes this to >< so it won't change the .split.
What about no close tag? close will be null.
This answer helped me understand what #Pointy and #squint were trying to say.
The following solution works for me:
$.fn.lengthOfStartTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfStartTag on non-element node.");
}
if (!$(node).is(":empty")) {
return node.outerHTML.indexOf(node.innerHTML);
}
return node.outerHTML.length;
}
$.fn.lengthOfEndTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfEndTag on non-element node.");
}
if (!$(node).is(":empty")) {
var indexOfInnerHTML = node.outerHTML.indexOf(node.innerHTML);
return node.outerHTML.length - (indexOfInnerHTML + node.innerHTML.length);
}
return -1;
}
Sample jsFiddle here.

Check if an element contains a class in JavaScript?

Using plain JavaScript (not jQuery), Is there any way to check if an element contains a class?
Currently, I'm doing this:
var test = document.getElementById("test");
var testClass = test.className;
switch (testClass) {
case "class1":
test.innerHTML = "I have class1";
break;
case "class2":
test.innerHTML = "I have class2";
break;
case "class3":
test.innerHTML = "I have class3";
break;
case "class4":
test.innerHTML = "I have class4";
break;
default:
test.innerHTML = "";
}
<div id="test" class="class1"></div>
The issue is that if I change the HTML to this...
<div id="test" class="class1 class5"></div>
...there's no longer an exact match, so I get the default output of nothing (""). But I still want the output to be I have class1 because the <div> still contains the .class1 class.
Use element.classList .contains method:
element.classList.contains(class);
This works on all current browsers and there are polyfills to support older browsers too.
Alternatively, if you work with older browsers and don't want to use polyfills to fix them, using indexOf is correct, but you have to tweak it a little:
function hasClass(element, className) {
return (' ' + element.className + ' ').indexOf(' ' + className+ ' ') > -1;
}
Otherwise you will also get true if the class you are looking for is part of another class name.
DEMO
jQuery uses a similar (if not the same) method.
Applied to the example:
As this does not work together with the switch statement, you could achieve the same effect with this code:
var test = document.getElementById("test"),
classes = ['class1', 'class2', 'class3', 'class4'];
test.innerHTML = "";
for(var i = 0, j = classes.length; i < j; i++) {
if(hasClass(test, classes[i])) {
test.innerHTML = "I have " + classes[i];
break;
}
}
It's also less redundant ;)
The easy and effective solution is trying .contains method.
test.classList.contains(testClass);
In modern browsers, you can just use the contains method of Element.classList :
testElement.classList.contains(className)
Demo
var testElement = document.getElementById('test');
console.log({
'main' : testElement.classList.contains('main'),
'cont' : testElement.classList.contains('cont'),
'content' : testElement.classList.contains('content'),
'main-cont' : testElement.classList.contains('main-cont'),
'main-content' : testElement.classList.contains('main-content'),
'main main-content' : testElement.classList.contains('main main-content')
});
<div id="test" class="main main-content content"></div>
Supported browsers
(from CanIUse.com)
Polyfill
If you want to use Element.classList but you also want to support older browsers, consider using this polyfill by Eli Grey.
Element.matches()
element.matches(selectorString)
According to MDN Web Docs:
The Element.matches() method returns true if the element would be selected by the specified selector string; otherwise, returns false.
Therefore, you can use Element.matches() to determine if an element contains a class.
const element = document.querySelector('#example');
console.log(element.matches('.foo')); // true
<div id="example" class="foo bar"></div>
View Browser Compatibility
This question is pretty solidly answered by element.classList.contains(), but people got pretty extravagant with their answers and made some bold claims, so I ran a benchmark.
Remember that each test is doing 1000 iterations, so most of these are still very fast. Unless you rely extensively on this for a specific operation, you won't see a performance difference.
I ran some tests with basically every way to do this. On my machine, (Win 10, 24gb, i7-8700), classList.contains performed super well. So did className.split(' ') which is effectively the same.
The winner though is classList.contains(). If you're not checking for classList to be undefined, ~(' ' + v.className + ' ').indexOf(' ' + classToFind + ' ') creeps ahead 5-15%
Since he wants to use switch(), I'm surprised no one has put this forth yet:
var test = document.getElementById("test");
var testClasses = test.className.split(" ");
test.innerHTML = "";
for(var i=0; i<testClasses.length; i++) {
switch(testClasses[i]) {
case "class1": test.innerHTML += "I have class1<br/>"; break;
case "class2": test.innerHTML += "I have class2<br/>"; break;
case "class3": test.innerHTML += "I have class3<br/>"; break;
case "class4": test.innerHTML += "I have class4<br/>"; break;
default: test.innerHTML += "(unknown class:" + testClasses[i] + ")<br/>";
}
}
Here is a little snippet If you’re trying to check wether element contains a class, without using jQuery.
function hasClass(element, className) {
return element.className && new RegExp("(^|\\s)" + className + "(\\s|$)").test(element.className);
}
This accounts for the fact that element might contain multiple class names separated by space.
OR
You can also assign this function to element prototype.
Element.prototype.hasClass = function(className) {
return this.className && new RegExp("(^|\\s)" + className + "(\\s|$)").test(this.className);
};
And trigger it like this (very similar to jQuery’s .hasClass() function):
document.getElementById('MyDiv').hasClass('active');
className is just a string so you can use the regular indexOf function to see if the list of classes contains another string.
This is a little old, but maybe someone will find my solution helpfull:
// Fix IE's indexOf Array
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement) {
if (this == null) throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) return -1;
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n != n) n = 0;
else if (n != 0 && n != Infinity && n != -Infinity) n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
if (n >= len) return -1;
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) if (k in t && t[k] === searchElement) return k;
return -1;
}
}
// add hasClass support
if (!Element.prototype.hasClass) {
Element.prototype.hasClass = function (classname) {
if (this == null) throw new TypeError();
return this.className.split(' ').indexOf(classname) === -1 ? false : true;
}
}
A simplified oneliner:1
function hasClassName(classname,id) {
return String ( ( document.getElementById(id)||{} ) .className )
.split(/\s/)
.indexOf(classname) >= 0;
}
1 indexOf for arrays is not supported by IE (ofcourse). There are plenty of monkey patches to be found on the net for that.
I know there a lot of answers but most of these are for additional functions and additional classes. This is the one I personally use; much cleaner and much less lines of code!
if( document.body.className.match('category-page') ) {
console.log('yes');
}
I've created a prototype method which uses classList, if possible, else resorts to indexOf:
Element.prototype.hasClass = Element.prototype.hasClass ||
function(classArr){
var hasClass = 0,
className = this.getAttribute('class');
if( this == null || !classArr || !className ) return false;
if( !(classArr instanceof Array) )
classArr = classArr.split(' ');
for( var i in classArr )
// this.classList.contains(classArr[i]) // for modern browsers
if( className.split(classArr[i]).length > 1 )
hasClass++;
return hasClass == classArr.length;
};
///////////////////////////////
// TESTS (see browser's console when inspecting the output)
var elm1 = document.querySelector('p');
var elm2 = document.querySelector('b');
var elm3 = elm1.firstChild; // textNode
var elm4 = document.querySelector('text'); // SVG text
console.log( elm1, ' has class "a": ', elm1.hasClass('a') );
console.log( elm1, ' has class "b": ', elm1.hasClass('b') );
console.log( elm1, ' has class "c": ', elm1.hasClass('c') );
console.log( elm1, ' has class "d": ', elm1.hasClass('d') );
console.log( elm1, ' has class "a c": ', elm1.hasClass('a c') );
console.log( elm1, ' has class "a d": ', elm1.hasClass('a d') );
console.log( elm1, ' has class "": ', elm1.hasClass('') );
console.log( elm2, ' has class "a": ', elm2.hasClass('a') );
// console.log( elm3, ' has class "a": ', elm3.hasClass('a') );
console.log( elm4, ' has class "a": ', elm4.hasClass('a') );
<p class='a b c'>This is a <b>test</b> string</p>
<svg xmlns="http://www.w3.org/2000/svg" width="100px" height="50px">
<text x="10" y="20" class='a'>SVG Text Example</text>
</svg>
Test page
Here's a case-insensitive trivial solution:
function hasClass(element, classNameToTestFor) {
var classNames = element.className.split(' ');
for (var i = 0; i < classNames.length; i++) {
if (classNames[i].toLowerCase() == classNameToTestFor.toLowerCase()) {
return true;
}
}
return false;
}
Felix's trick of adding spaces to flank the className and the string you're searching for is the right approach to determining whether the elements has the class or not.
To have different behaviour according to the class, you may use function references, or functions, within a map:
function fn1(element){ /* code for element with class1 */ }
function fn2(element){ /* code for element with class2 */ }
function fn2(element){ /* code for element with class3 */ }
var fns={'class1': fn1, 'class2': fn2, 'class3': fn3};
for(var i in fns) {
if(hasClass(test, i)) {
fns[i](test);
}
}
for(var i in fns) iterates through the keys within the fns map.
Having no break after fnsi allows the code to be executed whenever there is a match - so that if the element has, f.i., class1 and class2, both fn1 and fn2 will be executed.
The advantage of this approach is that the code to execute for each class is arbitrary, like the one in the switch statement; in your example all the cases performed a similar operation, but tomorrow you may need to do different things for each.
You may simulate the default case by having a status variable telling whether a match was found in the loop or not.
If the element only has one class name you can quickly check it by getting the class attribute. The other answers are much more robust but this certainly has it's use cases.
if ( element.getAttribute('class') === 'classname' ) {
}
See this Codepen link for faster and easy way of checking an element if it has a specific class using vanilla JavaScript~!
hasClass (Vanilla JS)
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
This is supported on IE8+.
First we check if classList exists if it does we can use the contains method which is supported by IE10+. If we are on IE9 or 8 it falls back to using a regex, which is not as efficient but is a concise polyfill.
if (el.classList) {
el.classList.contains(className);
} else {
new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
}
Alternatively if you are compiling with babel you can simply use:
el.classList.contains(className);
To check if an element contains a class, you use the contains() method of the classList property of the element:*
element.classList.contains(className);
*Suppose you have the following element:
<div class="secondary info">Item</div>*
To check if the element contains the secondary class, you use the following code:
const div = document.querySelector('div');
div.classList.contains('secondary'); // true
The following returns false because the element doesn’t have the class error:
const div = document.querySelector('div');
div.classList.contains('error'); // false
I think that perfect solution will be this
if ($(this).hasClass("your_Class"))
alert("positive");
else
alert("Negative");
I would Poly fill the classList functionality and use the new syntax. This way newer browser will use the new implementation (which is much faster) and only old browsers will take the performance hit from the code.
https://github.com/remy/polyfills/blob/master/classList.js
This is a bit off, but if you have an event that triggers switch, you can do without classes:
<div id="classOne1"></div>
<div id="classOne2"></div>
<div id="classTwo3"></div>
You can do
$('body').click( function() {
switch ( this.id.replace(/[0-9]/g, '') ) {
case 'classOne': this.innerHTML = "I have classOne"; break;
case 'classTwo': this.innerHTML = "I have classTwo"; break;
default: this.innerHTML = "";
}
});
.replace(/[0-9]/g, '') removes digits from id.
It is a bit hacky, but works for long switches without extra functions or loops
As the accepted answer suggests, Element.className returns a string, so you can easily check if a class exists by using the indexOf() method:
element.className.indexOf('animated') > -1
If you are interested in the performance difference between indexOf vs classList.contains, using indexOf seems to be slightly faster. I did a quick benchmark performance test to check that. Here are my findings: ClassName.indexOf vs ClassList.contains.
Try this one:
document.getElementsByClassName = function(cl) {
var retnode = [];
var myclass = new RegExp('\\b'+cl+'\\b');
var elem = this.getElementsByTagName('*');
for (var i = 0; i < elem.length; i++) {
var classes = elem[i].className;
if (myclass.test(classes)) retnode.push(elem[i]);
}
return retnode;
};
in which element is currently the class '.bar' ? Here is another solution but it's up to you.
var reg = /Image/g, // regexp for an image element
query = document.querySelector('.bar'); // returns [object HTMLImageElement]
query += this.toString(); // turns object into a string
if (query.match(reg)) { // checks if it matches
alert('the class .bar is attached to the following Element:\n' + query);
}
jsfiddle demo
Of course this is only a lookup for 1 simple element <img>(/Image/g) but you can put all in an array like <li> is /LI/g, <ul> = /UL/g etc.
Just to add to the answer for people trying to find class names within inline SVG elements.
Change the hasCLass() function to:
function hasClass(element, cls) {
return (' ' + element.getAttribute('class') + ' ').indexOf(' ' + cls + ' ') > -1;
}
Instead of using the className property you'll need to use the getAttribute() method to grab the class name.
I created these functions for my website, I use only vanilla javascript, maybe it will help someone.
First I created a function to get any HTML element:
//return an HTML element by ID, class or tag name
var getElement = function(selector) {
var elements = [];
if(selector[0] == '#') {
elements.push(document.getElementById(selector.substring(1, selector.length)));
} else if(selector[0] == '.') {
elements = document.getElementsByClassName(selector.substring(1, selector.length));
} else {
elements = document.getElementsByTagName(selector);
}
return elements;
}
Then the function that recieve the class to remove and the selector of the element:
var hasClass = function(selector, _class) {
var elements = getElement(selector);
var contains = false;
for (let index = 0; index < elements.length; index++) {
const curElement = elements[index];
if(curElement.classList.contains(_class)) {
contains = true;
break;
}
}
return contains;
}
Now you can use it like this:
hasClass('body', 'gray')
hasClass('#like', 'green')
hasClass('.button', 'active')
Hope it will help.
Tip: Try to remove dependencies of jQuery in your projects as much as you can - VanillaJS.
document.firstElementChild returns <html> tag then the classList attribute returns all classes added to it.
if(document.firstElementChild.classList.contains("your-class")){
// <html> has 'your-class'
} else {
// <html> doesn't have 'your-class'
}
Since .className is a string, you can use the string includes() method to check if your .className includes your class name:
element.className.includes("class1")
Using the classList is also ideal
HTML
<div id="box" class="myClass"></div>
JavaScript
const element = document.querySelector("#box");
element.classList.contains("myClass");
For me the most elegant and faster way to achieve it is:
function hasClass(el, cl) {
return el.classList ? el.classList.contains(cl) : !!el.className && !!el.className.match(new RegExp('(?: |^)' + cl + '(?: |$)'));
}

Categories

Resources