JS: How do we work with classes in CSS? - javascript

How do we manipulate the class of DOM elements with javascript? Is there a getElementsByClassName function?

Standard way is
error_message.className = 'error-message';
But you'll find these functions can simplify things a lot:
function hasClass(ele,cls) {
return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
//chekcs if selected element has class "cls", works for elements with multiple classes
function addClass(ele,cls) {
if (!this.hasClass(ele,cls)) ele.className += " "+cls;
}
//adds new class to element
function removeClass(ele,cls) {
if (hasClass(ele,cls)) {
var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className=ele.className.replace(reg,' ');
}
}
//removes class from element
Usage in a stackoverflow greasemonkey script to show all questions on page, regardless if they're ignored or not:
var childNodes=document.getElementById("questions").childNodes; //array of all questions
for (var i=1; i<childNodes.length; i+=2) //iterates through all questions on page.
{
removeClass(childNodes[i],"tagged-ignored-hidden");
addClass(childNodes[i],"user_defined_class");
}
(Don't worry if the for loop looks weird in that it skips every other element; the specifics of Stackoverflow's DOM layout with extra nodes between questions aren't important here.)
As to document.getElementsByClassName, it returns an array of DOM elements with the specific class (as you would suspect). BUT:
Safari 3.1 has native
getElmentsByClassName support, and
upcoming Firefox 3 and Opera 9.5 will
have it too. It only leaves out,
you’ve guessed it, Internet Explorer.
source

You can change a class in plain-old JavaScript using something like:
document.getElementById('myElement').className = 'myClass';
Or, if you're using JQuery, you can just use the "Class" functions.
Addressing the added details to the question about 'getElementsByClassName' and your comment:
It would probably be safest (and easiest) to use your favourite JavaScript library for this.
JQuery example:
$(".myClassName").each(function() {
//do what you want with the current element $(this)
});
Hope that helps.

Many JavaScript implementations do have a getElementsByClassName method built in. But if they don’t, you can implement it for yourself:
if (typeof Element.prototype.getElementsByClassName == "undefined") {
Element.prototype.getElementsByClassName = function(className) {
var elems = document.getElementsByTagName("*"),
matches = [];
for (var i=0, n=elems.length; i<n; ++i) {
if (elems[i].hasAttribute("class")) {
var classNames = elems[i].getAttribute("class").split(/\s+/);
for (var j=0,m=classNames.length; j<m; ++j) {
if (classNames[j] == className) {
matches.push(elems[i]);
break;
}
}
}
}
return new NodeList(matches);
};
}

Related

Create a function to get selectors like jquery does with pure javascript

I am tired of including jquery in simple projects but I am so used to using it and I am trying to break free form my dependency on it. I am trying to create a function that will give the same feel of getting selectors like classes and tags. Example: $('selector').innerHTML = ".something";. I have just been looping through them one by one like so:
var classElements = document.querySelectorAll('.something');
for (var i = classElements.length - 1; i >= 0; i--) {
classElements[i].innerHTML = "This Is A Div";
}
But I wanted to create a function where I could just loop through a selector without having to write out a for loop for everything that I want to find. So I could just write it our like above $('.something').innerHTML = "something";
So far this is what I have but it will only get the first of each selector and won't get all of them. Needless to say I am very stuck and the more I read on the subject the more confused I get. I was wondering if someone could point me in the right direction where my thinking is flawed or explain how jquery goes about doing this. Here is my code:
window.getElements = function(selector) {
var selectors = document.querySelectorAll(selector);
for (var i = selectors.length - 1; i >= 0; i--) {
var elements = selectors[i];
}
return elements;
};
getElements(".something").innerHTML = "something";
Here is a fiddle Fiddle
Here is how you would do it. I have done what you have asked which is allow you to use all the native functionality rather than coin wrappers around it. jQuery returns its own api which acts on the selectors. What I have done is create a selector which allows you to act on each element it finds
window.getElements = function(selector,cb) {
var selectors = document.querySelectorAll(selector);
[].forEach.call(selectors, cb);
};
getElements(".something", function(el){el.innerHTML = "ha"});
getElements("#one", function(el){el.style.background = "red" });
It takes the dom list that is found, converts it into an array and then calls your passed function where you pass your native code
Here is a fiddle
https://jsfiddle.net/y52f4wh8/5/
Jquery works differently:
window.jquery = function(selector,cb) {
var selectors = document.querySelectorAll(selector);
function command(cb) {
[].forEach.call(selectors, cb);
};
// Here we return our own api that uses the command function to act on
// our selected list.
return {
html: function(str){
command(function(el){
el.innerHTML=str;
});
},
bg: function(color){
command(function(el){
el.style.background = color;
});
}
}
};
// The jquery way!
jquery(".something").html("ha");
getElements(".something").innerHTML = "something";
this does not work because that your function gets and returns all of the elements, which is a NodeList, but it does not automatically apply methods to every element in the collection as jQuery does. In order to do that, you would have to convert the elements to an array and actually call the function on each element using a loop or some other function.
EDIT: To be clear, you cannot just call NodeList.innerHTML = '' on a NodeList because innerHTML is applied to one element. jQuery internally takes care of BOTH of the collecting of elements, and the applying of methods for you.
EDIT #2: After examining your function more carefully, I have realized there are other issues, but what I wrote above is still the basis from which you want to spring.
You could use something like this for getting elements?:
function getElements(elements) {
return [...querySelectorAll(elements)]
}
But applying functions on nodes is going to be more selective on a case-by-case basis since many of them are applied differently.
The selector part of JQuery is called Sizzle. It has all the functionality that you need but does not come with the other parts of JQuery.
If you would like to find out more about the javascript behind it, I recommend to take a look at the sourcefiles of Sizzle.
jQuery is essentially a wrapper object for NodeList which adds more functionality to DOM operations. If you want to create your own wrapper object which defines functions for bulk versions of all the Element API, you are free to do so, but then you might as well use jQuery or some derivative.
If you want an extremely lightweight proxy object for doing bulk operations on DOM elements, there is the Proxy object in ES6 which can make this very easy to do, but has no IE support.
const $ = function(selector) {
const nodeList = document.querySelectorAll(selector);
return new Proxy(nodeList, {
set: function(target, property, value) {
for (let i = 0; i < target.length; i++) {
target[i][property] = value;
}
},
get: function(target, property) {
return target[0] && target[0][property];
}
});
};
console.log($('p').innerHTML);
$('p').innerHTML = 'Bulk assignement!';
<p>A B C</p>
<p>1 2 3</p>
<p>Do Re Mi</p>
An approach which would be best avoided is to define a setter for innerHTML on NodeList.
Object.defineProperty(NodeList.prototype, 'innerHTML', {
set(text) {
[...this].forEach(elt => elt.innerHTML = text);
}
});
const $ = selector => document.querySelectorAll(selector);
$('.foo').innerHTML = "it works";
<div class="foo"></div>
<div class="foo"></div>

Delete object by attribute in javascript

I have a few different tables on the same page but unfortunately they were not assigned any unique id's. I want to remove a table using a JS command, but since id cannot be used, is it possible to delete a table based on a certain attribute it has? For example, is there a command to delete all tables on the page that have the attribute: width="25%" ?
You can use querySelectorAll to do that.
var x = document.querySelectorAll("table[width='25%']");
for (var i=0; i<x.length; i++) { //returns array of elements that match the attribute selector
x[i].remove(); //call prototype method defined below
}
Removing is tricky, I found this code that makes a nice remove method
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = 0, len = this.length; i < len; i++) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
This creates a prototype remove() function that iterates the node and deletes the children.
Please note that querySelectorAll will not work in IE8 or below, but the poster of the prototype method said that it should work in IE8 but not 7.
I know this already has some solutions, but I'll offer up one more alternative.
var tables = document.getElementsByTagName('table');
for(var i = 0; i < tables.length; i++){
if(tables[i].getAttribute('width') == "25%"){
tables[i].parentNode.removeChild(tables[i]);
}
}
Demo at http://codepen.io/michaelehead/pen/HfdKx.
Yes you can. The easiest way is to use JQuery.
In your javascript code you would just write:
$("[attribute=value]").remove()
So in your case it could be something like $("table[width='25%']").remove()

DOM scripting getElementsByClassName (for links)

I am practicing DOM Scripting (no real-life problem but rather practice and theory, I know how to achieve this with jQuery of course)
So, I am trying to improve and understand the following:
I have some links with classes and I am attaching event on them:
click
click2
click3
click4
Javascript:
window.onload = prepareLinks;
function prepareLinks() {
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
if (links[i].getAttribute("class") == "popup") {
links[i].onclick = function () {
popUp(this.getAttribute("href"));
return false;
}
}
}
}
function popUp(winURL) {
window.open(winURL, "popup", "width=320,height=480");
}
That works fine. I got it from a book basically. Now I want to improve it by making use of getElementsByClassName. I went on to write:
window.onload = prepareLinks;
function prepareLinks() {
var links = document.getElementsByTagName("a");
var popups = links.getElementsByClassName("popup");
for (var i = 0; i < popups.length; i++) {
popups[i].onclick = function () {
popUp(this.getAttribute("href"));
return false;
}
}
}
function popUp(winURL) {
window.open(winURL, "popup", "width=320,height=480");
}
But I got error:
Uncaught TypeError: Object # has no method 'getElementsByClassName'
Apparently links is a NodeList so I can't use the getElementsByClassName on it. Which I don't really understand...
Can you help on how I could do this, and whether or not the first version of the script is good? (performance wise). Thanks.
You can't use getElement* functions to filter each other like that, because they don't operate on lists, and they don't return the original node in their results anyway. If you need to filter on more than one condition simultaneously, use querySelectorAll instead, e.g. document.querySelectorAll("a.popup").
The first version is fine, but you might see an improvement if you got the elements by class name first, then filtered them by tag name, if in fact you do even need them filtered by tag name. If the class popup will only be used on links, getElementsByTagName is unnecessary and your script will speed up if you remove it, of course.

Javascript find child class

I have a table. When the item is dropped I need to apply padding to a single table cell. I have flagged that cell with a class. How do I select it?
droppedRow contains the table row that is has just been dropped.
If it was an id I would do droppedRow.getElementById('..'); Is there something similar for class names. Needs to support >= IE7
Thanks
Using vanilla JavaScript, you'll probably need to load up all of the element's by tag name and then locate it by evaluating each element's classname.
For example (the styles are just for example)...
var tableCells = document.getElementsByTagName('td');
for(var i = 0, l = tableCells.length; i < l; i++) {
if(tableCells[i].className === 'droppedRow') {
tableCells[i].style.padding = '1em';
}
}
If, on the other hand, you're using jQuery, then you should be able to use:
$('.droppedRow').css('padding', '1em');
Note however that in both of these examples, all cells that have the droppedRow class name will receive this styling (rather than just a single element).
If you're not using a library, I'd say stick with the vanilla variant of this functionality - libraries would be too much overhead just to condense this to a single line.
Maxym's answer also provides a solid implementation of getElementsByClassName for older browsers.
There exists getElementsByClassName but it is not supported in IE. Here is what you can do:
var element;
// for modern browsers
if(document.querySelector) {
element = droppedRow.querySelector('.yourClass');
}
else if(document.getElementsByClassName) { // for all others
element = droppedRow.getElementsByClassName('yourClass')[0];
}
else { // for IE7 and below
var tds = droppedRow.getElementsByTagName('td');
for(var i = tds.length; i--; ) {
if((" " + tds[i].className + " ").indexOf(" yourClass ") > -1) {
element = tds[i];
break;
}
}
}
Reference: querySelector, getElementsByClassName, getElementsByTagName
Clientside getElementsByClassName cross-browser implementation:
var getElementsByClassName = function(className, root, tagName) {
root = root || document.body;
if (Swell.Core.isString(root)) {
root = this.get(root);
}
// for native implementations
if (document.getElementsByClassName) {
return root.getElementsByClassName(className);
}
// at least try with querySelector (IE8 standards mode)
// about 5x quicker than below
if (root.querySelectorAll) {
tagName = tagName || '';
return root.querySelectorAll(tagName + '.' + className);
}
// and for others... IE7-, IE8 (quirks mode), Firefox 2-, Safari 3.1-, Opera 9-
var tagName = tagName || '*', _tags = root.getElementsByTagName(tagName), _nodeList = [];
for (var i = 0, _tag; _tag = _tags[i++];) {
if (hasClass(_tag, className)) {
_nodeList.push(_tag);
}
}
return _nodeList;
}
Some browsers support it natively (like FireFox), for other you need provide your own implementation to use; that function could help you; its performance should be good enough cause it relies on native functions, and only if there is no native implementation it will take all tags, iterate and select needed...
UPDATE: script relies on hasClass function, which can be implemented this way:
function hasClass(_tag,_clsName) {
return _tag.className.match(new RegExp('(\\s|^)'+ _clsName +'(\\s|$)'));
}
It sounds like your project is in need of some JQuery goodness or some Dojo if you need a more robust and full-fledged javascript framework. JQuery will easily allow you to run the scenario you have described using its selector engine.
If you are using a library, why not use:
JQuery - $("#droppedRow > .paddedCell")
Thats the dropped row by ID and the cell by class
Prototype - $$("#droppedRow > .paddedCell")

ID Ends With in pure Javascript

I am working in a Javascript library that brings in jQuery for one thing: an "ends with" selector. It looks like this:
$('[id$=foo]')
It will find the elements in which the id ends with "foo".
I am looking to do this without jQuery (straight JavaScript). How might you go about this? I'd also like it to be as efficient as reasonably possible.
Use querySelectorAll, not available in all browsers (like IE 5/6/7/8) though. It basically works like jQuery:
http://jsfiddle.net/BBaFa/2/
console.log(document.querySelectorAll("[id$=foo]"));
You will need to iterate over all elements on the page and then use string functions to test it. The only optimizations I can think of is changing the starting point - i.e. not document.body but some other element where you know your element will be a child of - or you could use document.getElementsByTagName() to get an element list if you know the tag name of the elements.
However, your task would be much easier if you could use some 3rd-party-javascript, e.g. Sizzle (4k minified, the same selector engine jQuery uses).
So, using everything that was said, I put together this code. Assuming my elements are all inputs, then the following code is probably the best I am going to get?
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
function getInputsThatEndWith(text) {
var result = new Array();
var inputs = document.getElementsByTagName("input");
for(var i=0; i < inputs.length; i++) {
if(inputs[i].id.endsWith(text))
result.push(inputs[i]);
}
return result;
}
I put it on JsFiddle: http://jsfiddle.net/MF29n/1/
#ThiefMaster touched on how you can do the check, but here's the actual code:
function idEndsWith(str)
{
if (document.querySelectorAll)
{
return document.querySelectorAll('[id$="'+str+'"]');
}
else
{
var all,
elements = [],
i,
len,
regex;
all = document.getElementsByTagName('*');
len = all.length;
regex = new RegExp(str+'$');
for (i = 0; i < len; i++)
{
if (regex.test(all[i].id))
{
elements.push(all[i]);
}
}
return elements;
}
}
This can be enhanced in a number of ways. It currently iterates through the entire dom, but would be more efficient if it had a context:
function idEndsWith(str, context)
{
if (!context)
{
context = document;
}
...CODE... //replace all occurrences of "document" with "context"
}
There is no validation/escaping on the str variable in this function, the assumption is that it'll only receive a string of chars.
Suggested changes to your answer:
RegExp.quote = function(str) {
return str.replace(/([.?*+^$[\]\\(){}-])/g, "\\$1");
}; // from https://stackoverflow.com/questions/494035/#494122
String.prototype.endsWith = function(suffix) {
return !!this.match(new RegExp(RegExp.quote(suffix) + '$'));
};
function getInputsThatEndWith(text) {
var results = [],
inputs = document.getElementsByTagName("input"),
numInputs = inputs.length,
input;
for(var i=0; i < numInputs; i++) {
var input = inputs[i];
if(input.id.endsWith(text)) results.push(input);
}
return results;
}
http://jsfiddle.net/mattball/yJjDV/
Implementing String.endsWith using a regex instead of indexOf() is mostly a matter of preference, but I figured it was worth including for variety. If you aren't concerned about escaping special characters in the suffix, you can remove the RegExp.quote() bit, and just use
new RegExp(suffix + '$').
If you know the type of DOM elements you are targeting,
then get a list of references to them using getElementsByTagName , and then iterate over them.
You can use this optimization to fasten the iterations:
ignore the elements not having id.
target the nearest known parent of elements you want to seek, lets say your element is inside a div with id='myContainer', then you can get a restricted subset using
document.getElementById('myContainer').getElementsByTagName('*') , and then iterate over them.

Categories