Started work last night on writing some dom ‘class’ functions. ‘addClass’ and ‘removeClass’ so far. Got a little carried away and wrapped them up in a basic Jquery-esque constructor/selector.
The class methods utitilise ‘classlist’ if available IE10 etc. and fall back on hand rolled code if not.
It needs a polyfill for IE8’s querySelectAll and it’s lack of pseudo selectors like nth-child etc.
My particular issue is with the addClass method. I’m using a regex to test if the class already exists. Albeit I’ve never used them myself, as per what I have read, it test for hyphens in the name. As javascript’s flavour of regex doesn’t support look behinds, I’m having to bodge it by stripping out lookbehind matches first before testing for matches with a look ahead.
The code snipped is here.
// Strips lookbehind matches before testing. Needs work!!
preRx = new RegExp('[\\\\S-]' + clName,'g');
classRx = new RegExp(clName + '(?![-\\\\S])');
for (; i < len; i += 1) {
if (!(classRx.test(els[i].className.replace(preRx, '')))) {
els[i].className = els[i].className + ' ' + clName;
}
}
I know Jquery pads out the classname string first, left and right and then uses a while loop and index of ’ ’ + className + ’ ’ to find a match.
Wondered if the regex above could be improved on or combined into one. Just feels a bit clunky.
Here’s the complete code with example usage.
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="utf-8">
<title>Root QuerySelector</title>
<style>
li.odd { background-color: gray; }
li.even { background-color: teal!important; }
li {
background-color: darkslategray;
}
.whiteText{ color: white; }
</style>
</head>
<body>
<ul id = 'list1'>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
</ul>
<ul id = 'list2'>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
</ul>
<button type="button" onclick="Dom('li:nth-child(even)').addClass('even')">Set class name 'even'</button>
<br>
<button type="button" onclick="Dom('li').removeClass('even')">Remove class name 'even'</button>
<script>
// ------------------------------------ Dom Select -----------------------------------
(function(win, doc){
var docElem = doc.documentElement,
slice = [].slice,
push = [].push,
toString = {}.toString,
// native trim not supported in older versions of IE
trim = function(strg) { return strg.replace(/^\\s+|\\s+$/g, '') },
objType = function(obj){ return / ([^\\]]+)/.exec({}.toString.call(obj))[1]; },
isNodeList = function(obj) {
return /HTMLCollection|HTMLUListElement/.test(objType(obj));
},
//------------------- Shortcuts to default selectors. ------------------
getById = function getById(id) { return doc.getElementById(id); },
getByTag = function getByTag(name, root) { return (root||doc).getElementsByTagName(name); },
// Needs polyfill for IE8 and pseudo selectors
queryAll = function (name, root) { return (root||doc).querySelectorAll(name); },
// Fallback for IE 8 use querySelectorAll instead.
getByClass = (docElem.getElementsByClassName)
? function getByClass(name, root) { return (root||doc).getElementsByClassName(name); }
: queryAll,
//--------------------------- Class Methods -----------------------------
//----------------------------- Add Class -------------------------------
addClass = function (clName) {
var i = 0, len, classRx, preRx, els = this;
if(len = els.length) {
if (clName = (typeof clName === 'string' && trim(clName))) {
// Use native classList method if available. Not supported in IE8-9.
if(els[0].classList)
for (; i < len; i += 1) els[i].classList.add(clName);
else {
// Strips lookbehind matches before testing. Needs work!!
preRx = new RegExp('[\\\\S-]' + clName,'g');
classRx = new RegExp(clName + '(?![-\\\\S])');
for (; i < len; i += 1) {
if (!(classRx.test(els[i].className.replace(preRx, '')))) {
els[i].className = els[i].className + ' ' + clName;
}
}
}
}
}
return els;
},
//----------------------------- Remove Class -------------------------------
removeClass = function (clName) {
var i = 0, len, classRX, els = this;
if(len = els.length) {
if (clName = (typeof clName === 'string' && trim(clName))) {
// Use native classList method if available. Not supported in IE8-9.
if(els[0].classList)
for (; i < len; i += 1) els[i].classList.remove(clName);
else {
// ([\\\\S-])? and the replace function(a,b){..} provide a negative lookbehind
classRx = new RegExp('([\\\\S-])?'+ clName +'(?: |(?![-\\\\S]))','g');
for (; i < len; i += 1) {
els[i].className = trim(els[i].className.replace(classRx, function (a,b){return b ? a : '';}));
};
}
}
}
return els; // returns an array of the elements
};
// ----------------------- Dom Factory Constructor --------------------------------
var Dom = function Dom(selector, root) {
if ( this instanceof Dom ) {
// Simple Selector [1] #Id, [2] Tag, [3] .class
var basicSelect = /^#([\\w-]+)$|^([\\w-]+)$|^\\.([\\w-]+)$/,
mtch,
els = this;
// If selector is an HTMl Collection add to Dom Array Like Object
if (isNodeList(selector)) return push.apply(els, selector);
if (selector = (typeof selector === 'string' && trim(selector))){
mch = (selector.match(basicSelect) || '');
if (mch[1]) {
els['0'] = getById(mch[1]); els.length = 1;
}
else if (mch[2]) {
push.apply(els, getByTag(mch[2], root));
}
else if (mch[3]) {
push.apply(els, getByClass(mch[3], root));
}
// If selector string doesn't match basicSelect use querySelectorAll
else {
push.apply(els, queryAll(selector, root));
}
}
return els; // return Dom Array like object
}
else return new Dom(selector, root);
};
Dom.prototype = {
constructor : 'Dom',
addClass : addClass,
removeClass : removeClass,
get : function(){ return slice.call(this); }
};
// Add methods to Dom constructor.
Dom.getById = getById;
Dom.getByTag = getByTag;
Dom.getByClass = getByClass;
Dom.queryAll = queryAll;
win.Dom = Dom;
}(window, window.document));
//-------------------------------- End of Dom Select -------------------------------------------
// Examples of usage.
// Dom( selector, root ).method
// Using querySelectorAll, additional root selector and
// get() to return an array of html node items.
Dom('li:nth-child(even)', Dom.getById('list1')).get().
forEach(
function(i){
i.style.backgroundColor = 'darkkhaki';
}
);
// Select using querySelectorAll and Add a class
Dom('#list2 li:nth-child(odd)').addClass('odd');
// Using a standard HTML Collection as selector
var liItems = Dom.getByTag('li');
Dom(liItems).addClass('whiteText');
</script>
</body>
</html>
Cheers
RLM