I have a validation script that validates two different classes. I want to add an additional class for some select boxes that generate an array onmouseover. Problem is, the select boxes have two classes, one for the validation, one for the array, and my script doesn't recognize it unless it has the exact name. If using the == it doesn't work at all, but using the = it tries to validate all the elements on my page, even those without any classes.
I'm wondering how I can make this the most efficient without breaking the rest of my pages using this script.
This is an example of the HTML
<div class="select-styled">
<select id="pm" name="pm" class="selValidate pmpop" onmouseover="pmpop();">
<option value="Foo">Foo</option>
<option value="">- Remove -</option>
</select>
</div>
Here is the validation script
for (i=0; i<thisform.elements.length; i++) {
if (thisform.elements[i].className = "selValidate" || thisform.elements[i].className == "req" || thisform.elements[i].className == "valfocus") {
//do stuff
}
}
Why don't you use match()?
if (thisform.elements[i].className.match('selValidate')) {
// do stuff
}
You can't use the assignment operator in an if because it will do that...assign. The == is a comparison operator. But you seem to want to check if one css class is set for an element that has many..
So match() would work better as it searches for the occurrence of a string within a string, rather than comparing two string literals to see if they're equal.
Also, match() can accept regular expressions, which can be powerful. :)
EDIT:
Per Barney's comment, I recommend using a regular expression (as I mentioned earlier) to avoid false matches where you might have a similar class name (e.g. selValidate and selValidateOff or something like that).
if (thisform.elements[i].className.match(/\bselValidate\b/)) {
// now, really do stuff
}
Both are good, really. I just depends on how narrow you want your search to be.
Here is a nice cheat sheet for getting started with regular expressions in JavaScript.
The problem is that with multiple classes, the className property lists each class separated by a space. So markup like:
<select id="pm" name="pm" class="selValidate pmpop" onmouseover="pmpop();">
has a className equal to "selValidate pmpop".
To use JavaScript to find an individual class using className, you either need to use the String .match method to test against a regex that will find the class in the string, or you need to break the string up into searchable tokens.
Example of .match:
for (i = 0; i < thisform.elements.length; i += 1) {
if (thisform.elements[i].className.match(/\bselValidate\b|\breq\b|\bvalfocus\b/g) { // relies on .match returning null for no match
//do stuff
}
}
Example of breaking the string into searchable tokens:
for (i = 0; i < thisform.elements.length; i += 1) {
var allClasses = thisform.elements[i].className.split(' ');
if (allClasses.indexOf('selValidate') > -1 || allClasses.indexOf('req') > -1 || allClasses.indexOf('valfocus') > -1) { // relies on ECMAScript 5 Array.indexOf support
//do stuff
}
}
// or
for (i = 0; i < thisform.elements.length; i += 1) {
var allClasses = thisform.elements[i].className.split(' ');
for (var j = 0; j < allClasses.length; j += 1) {
if (allClasses[j] == 'selValidate' || allClasses[j] == 'req' || allClasses[j] == 'valfocus') { // should work with any browser that supports JavaScript
//do stuff
}
}
}
You could also use something like .classList, but I don't know what browsers you need to target.
Example of .classList:
for (i = 0; i < thisform.elements.length; i += 1) {
if (thisform.elements[i].classList.contains('selValidate') || thisform.elements[i].classList.contains('req') || thisform.elements[i].classList.contains('valfocus')) { // relies on ECMAScript 5 Array.indexOf support
//do stuff
}
}
Also, only use = when you want to assign a value to the lefthand operator (that's what it's for).
== is a type-coercive equality (or "equal-ish") test, and === is a strict (non-coercive) equality test.
The DOM method for this is classList. If you only care about IE 10+, it's your best bet:
thisform.elements[ i ].classList.contains( 'selValidate' );
If you need to support older browsers, use RegExp and className:
thisform.elements[ i ].className.match( /\bselValidate\b/ );
EDIT: after running a few tests on jsperf, it appears that classList is often much faster (the only browser that this isn't the case for is IE11). For this reason – and because classList.contains is an API designed to do precisely the task in question (whereas className.match is a kludge) – I'd recommend using the classList method if you can afford to ditch IE9 and below.
If you want to reap the speed benefits where possible but still support all browsers, here's a polyfill:
function hasClass( element, className ){
if( element.classList && element.classList.contains ){
hasClass = function classList_contains( element, className ){
return element.classList.contains( className );
}
}
else {
hasClass = function className_match( element, className ){
return new RegExp( '\\b' + className + '\\b' ).test( element.className );
}
}
return hasClass( element, className );
}
If you are using jQuery, check for class existence instead of class-name comparison using
$(thisform.elements[i]).hasClass('selValidate');
If you want Javascript only solution the use this library classie which helps to check the existence of a class using hasClass() method.
Related
How can I check if one DOM element is a child of another DOM element? Are there any built in methods for this? For example, something like:
if (element1.hasDescendant(element2))
or
if (element2.hasParent(element1))
If not then any ideas how to do this? It also needs to be cross browser. I should also mention that the child could be nested many levels below the parent.
You should use Node.contains, since it's now standard and available in all browsers.
https://developer.mozilla.org/en-US/docs/Web/API/Node.contains
Update: There's now a native way to achieve this. Node.contains(). Mentioned in comment and below answers as well.
Old answer:
Using the parentNode property should work. It's also pretty safe from a cross-browser standpoint. If the relationship is known to be one level deep, you could check it simply:
if (element2.parentNode == element1) { ... }
If the the child can be nested arbitrarily deep inside the parent, you could use a function similar to the following to test for the relationship:
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
I just had to share 'mine'.
Although conceptually the same as Asaph's answer (benefiting from the same cross-browser compatibility, even IE6), it is a lot smaller and comes in handy when size is at a premium and/or when it is not needed so often.
function childOf(/*child node*/c, /*parent node*/p){ //returns boolean
while((c=c.parentNode)&&c!==p);
return !!c;
}
..or as one-liner (just 64 chars!):
function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}
and jsfiddle here.
Usage:
childOf(child, parent) returns boolean true|false.
Explanation:
while evaluates as long as the while-condition evaluates to true.
The && (AND) operator returns this boolean true/false after evaluating the left-hand side and the right-hand side, but only if the left-hand side was true (left-hand && right-hand).
The left-hand side (of &&) is: (c=c.parentNode).
This will first assign the parentNode of c to c and then the AND operator will evaluate the resulting c as a boolean.
Since parentNode returns null if there is no parent left and null is converted to false, the while-loop will correctly stop when there are no more parents.
The right-hand side (of &&) is: c!==p.
The !== comparison operator is 'not exactly equal to'. So if the child's parent isn't the parent (you specified) it evaluates to true, but if the child's parent is the parent then it evaluates to false.
So if c!==p evaluates to false, then the && operator returns false as the while-condition and the while-loop stops. (Note there is no need for a while-body and the closing ; semicolon is required.)
So when the while-loop ends, c is either a node (not null) when it found a parent OR it is null (when the loop ran through to the end without finding a match).
Thus we simply return that fact (converted as boolean value, instead of the node) with: return !!c;: the ! (NOT operator) inverts a boolean value (true becomes false and vice-versa).
!c converts c (node or null) to a boolean before it can invert that value. So adding a second ! (!!c) converts this false back to true (which is why a double !! is often used to 'convert anything to boolean').
Extra:
The function's body/payload is so small that, depending on case (like when it is not used often and appears just once in the code), one could even omit the function (wrapping) and just use the while-loop:
var a=document.getElementById('child'),
b=document.getElementById('parent'),
c;
c=a; while((c=c.parentNode)&&c!==b); //c=!!c;
if(!!c){ //`if(c)` if `c=!!c;` was used after while-loop above
//do stuff
}
instead of:
var a=document.getElementById('child'),
b=document.getElementById('parent'),
c;
function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}
c=childOf(a, b);
if(c){
//do stuff
}
Another solution that wasn't mentioned:
Example Here
var parent = document.querySelector('.parent');
if (parent.querySelector('.child') !== null) {
// .. it's a child
}
It doesn't matter whether the element is a direct child, it will work at any depth.
Alternatively, using the .contains() method:
Example Here
var parent = document.querySelector('.parent'),
child = document.querySelector('.child');
if (parent.contains(child)) {
// .. it's a child
}
You can use the contains method
var result = parent.contains(child);
or you can try to use compareDocumentPosition()
var result = nodeA.compareDocumentPosition(nodeB);
The last one is more powerful: it return a bitmask as result.
Take a look at Node#compareDocumentPosition.
function isDescendant(ancestor,descendant){
return ancestor.compareDocumentPosition(descendant) &
Node.DOCUMENT_POSITION_CONTAINS;
}
function isAncestor(descendant,ancestor){
return descendant.compareDocumentPosition(ancestor) &
Node.DOCUMENT_POSITION_CONTAINED_BY;
}
Other relationships include DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_PRECEDING, and DOCUMENT_POSITION_FOLLOWING.
Not supported in IE<=8.
I came across a wonderful piece of code to check whether or not an element is a child of another element. I have to use this because IE doesn't support the .contains element method. Hope this will help others as well.
Below is the function:
function isChildOf(childObject, containerObject) {
var returnValue = false;
var currentObject;
if (typeof containerObject === 'string') {
containerObject = document.getElementById(containerObject);
}
if (typeof childObject === 'string') {
childObject = document.getElementById(childObject);
}
currentObject = childObject.parentNode;
while (currentObject !== undefined) {
if (currentObject === document.body) {
break;
}
if (currentObject.id == containerObject.id) {
returnValue = true;
break;
}
// Move up the hierarchy
currentObject = currentObject.parentNode;
}
return returnValue;
}
Consider using closest('.selector')
It returns null if neither element nor any of its ancestors matches the selector. Alternatively returns the element which was found
try this one:
x = document.getElementById("td35");
if (x.childElementCount > 0) {
x = document.getElementById("LastRow");
x.style.display = "block";
}
else {
x = document.getElementById("LastRow");
x.style.display = "none";
}
TL;DR: a library
I advise using something like dom-helpers, written by the react team as a regular JS lib.
In their contains implementation you will see a Node#contains based implementation with a Node#compareDocumentPosition fallback.
Support for very old browsers e.g. IE <9 would not be given, which I find acceptable.
This answer incorporates the above ones, however I would advise against looping yourself.
I wrote a JavaScript function that will return a "list" of elements that has Id that start with some value:
function getElementsWithIdPrefix(prefix){
if (document.querySelectorAll){
return document.querySelectorAll('*[id^="' + prefix + '"]');
} else {
// none modern browsers support
var elements = document.getElementsByTagName('*');
var relevantElements = [];
for (var i = 0; i < elements.length; i++){
if (element.id && element.id.indexOf(prefix) !== -1){
relevantElements.push(element);
}
}
return relevantElements;
}
}
As you can see my function will return different types depends on browser support for document.querySelectorAll and I have two question regarding this:
Is this bad? I mean - In JavaScript typing system does it consider as a code smell or bad practice?
How can I create Node objects for each Element by my self and construct a new NodeList containing these elements to return?
Is this bad?
Probably, but it's probably a matter of opinion.
How can I create Node objects for each Element by my self and construct a new NodeList containing these elements to return?
You can turn your QSA result into a true array like this:
return Array.prototype.slice.call(document.querySelectorAll('*[id^="' + prefix + '"]'));
That way, you're returning an array in both cases.
Side note: You have to look pretty hard to find a browser that doesn't have QSA. It's on all modern browsers, and also IE8.
Side note 2: This line
if (element.id && element.id.indexOf(prefix) !== -1){
...doesn't look for a prefix. It looks for a substring match anywhere in the ID. For a prefix, use === 0 (or element.id.substring(0, prefix.length) === prefix).
Is there a way to select an element based on the data-event attribute?
Here is what I have:
<button data-event="5456293788" class="id-button-yes greenBtn list-buy-buttons">Buy now!</button>
Looking for something like:
document.getElementByDataEvent('5456293788')
Possible?
Something like this?
document.querySelector("button[data-event='5456293788']");
See in JSFiddle
It has quite a wide browser support (even IE 8 supports it)
Use the attribute selector
http://jsfiddle.net/QTNzf/
JS
document.querySelector('button[data-event="5456293788"]' );
CSS
button[data-devent="5456293788"] {
background-color: #eee;
}
The attribute selector does spring to mind, and you don't need jQuery to use that.
It probably won't work on older browsers, though.
If you really want, you can add a getElementsByDataEvent to the document object (not sure if all browsers allow this):
document.getElementsByDataEvent = function(dataEvent)
{
return document.querySelectorAll('[data-event="'+dataEvent+'"]');
};
But as you can see, this is a bit unnecessary, since it's just returning the return-value of a querySelectorAll call. What's more, if you use querySelectorAll directly, you can do something like this
document.querySelectorAll('a[data-event="foo"]');//only links will be retured
//or, with some other data-* thing, along the lines of:
document.querySelectorAll('[class="greenBtn"]');
Use the attribute equals selector in jQuery:
$('[data-event="5456293788"]')
or with document.querySelectorAll:
document.querySelectorAll('[data-event="5456293788"]');
For backwards compatibility, you'd have to iterate through all the elements checking the attribute values. This is a case where a library such as jQuery really helps.
backwards compatible raw js:
function getElementsByAttr(attr, val) {
var nodes,
node,
i,
ret;
val = '' + val;
nodes = document.getElementsByTagName('*');
ret = [];
for (i = 0; i < nodes.length; i += 1) {
node = nodes[i];
if (node.getAttribute(attr) === val) {
ret.push(node);
}
}
return ret;
}
de = getElementsByAttr('data-event', '5456293788');
2015 Note: Everyone should go look through their old questions and be encouraged by how far they've come. There are so many things wrong with this code – it's wonderful.
This code works PERFECTLY in Firefox, and in IE the line ending 'negative'); breaks. I have no idea how to fix it! Argghhh!
$(".topline strong").digits().each(function () {
s = $(this),
c = "change";
if ($(s).hasClass(c) & $(s).text().replace(/%/gi, "") > 0) {
$(s).addClass("positive");
}
if ($(s).hasClass(c) & $(s).text().trim().charAt(0) == "-") $(s).addClass("negative");
});
Please use && instead of & (unless it's meant for minimizing)
String.trim isn't widely implemented yet. Use
$(s).hasClass(c) && /^\s*-/.test($(s).text())
instead.
I'd rather rewrite the code as:
$(".topline strong").digits().each(function() {
var s = $(this);
if (s.hasClass("change")) { // no need to $() an already $()-ed object.
var value = parseFloat(s.text());
if (value != 0)
s.addClass(value > 0 ? "positive" : "negative");
}
});
Instead of .trim() you can use $.trim() like this:
$(".topline strong").digits().each(function () {
var s = $(this), c = "change";
if (s.hasClass(c) && s.text().replace(/%/gi, "") > 0) { s.addClass("positive"); }
if (s.hasClass(c) && $.trim(s.text()).charAt(0) == "-") s.addClass("negative");
});
Also note the s changes, there's no need to clone it as another jQuery object each time, it already is one so use it :) As for the error: .trim() isn't in all browsers, this is why jQuery includes the $.trim() function (same reason it has $.inArray(), IE doesn't have .indexOf()).
Also when declaring variable use var, otherwise you're creating global variables.
As an aside for future readers of this, jQuery 1.4.3+ will use the native String.prototype.trim if it's available when calling $.trim().
Say, I want to see if a DOM element is a block. I can write it in three ways, depending on my mood:
// first way
if (el.currentStyle.display == "block" || el.currentStyle.display == "inline-block" || el.currentStyle.display == "table-cell")
// second way
var blocks = {"block": 1, "inline-block": 1, "table-cell": 1};
if (el.currentStyle.display in blocks)//
// third way
if (el.currentStyle.display.match(/block|inline-block|table-cell/))
I have mixed feeling about all of them. First is too verbose once I have more than one option. Second contains those arbitrary values in the object (where I put 1s this time). Third looks like overkill. (What exactly is bad about overkilling?)
Do you know another, better way? If no, any cons I am missing about these three ways?
Javascript only, please.
I like the third way; I don't think it looks like overkill at all. If you need an even shorter way then this works too:
el.currentStyle.display.match(/(e-)?(block|cell)/)
But that's not very readable...
It might be worth abstracting it all away by extending the String prototype:
String.prototype.matches = function(what) {
return (',' + what + ',').indexOf(',' + this + ',') > -1;
};
// Using it:
el.currentStyle.display.matches('block,inline-block,table-cell');
If we're primarily aiming for readability, and if this is happening more than once -- perhaps even if it is just once -- I'd move the test to a function. Then define that function whichever way you like -- probably option 1, for max simplicity there.
Overkill? Possibly. But a gift to the programmer who wants to scan and understand the code 6 months from now. Probably you :-)
function isBlock(el) {
return (el.currentStyle.display == "block" ||
el.currentStyle.display == "inline-block" ||
el.currentStyle.display == "table-cell");
}
// ...
if (isBlock(el)) {
// do something
}
Can't you use the 2nd way but check if it's undefined and then skip the ": 1" part. I haven't tested though.
It looks like you need an inArray function, here is one from the top search result:
Array.prototype.inArray = function (value) {
var i;
for (i=0; i < this.length; i++) {
if (this[i] === value) {
return true;
}
}
return false;
};
Then the forth way would look like this:
if (['block','inline-block','table-cell'].inArray(el.currentStyle.display))
Or in a more readable manner:
var isBlock = ['block','inline-block','table-cell'].inArray(el.currentStyle.display);
My prefered solution for this is:
'block||inline-block||table-cell'.indexOf( el.currentStyle.display ) >= 0
I think that this will use native code of the string and be way more efficient than the array & iteration method.