K345 classNames source
source code
/** * @requires Function.prototype.bind() * @requires K345.isHostMethod() * @requires K345.isNodeElement() * @requires K345.toArray() * @requires K345.isObject() * @requires Object.forEach() */ (function (GNS /* the global namespace */) { var hasCList, funcs, pubApi; /** * tests if classList API is available * @private * @type Boolean */ hasCList = (function () { var te = document.createElement('p'); /* test element*/ return ('object|function'.indexOf(typeof te.classList) > -1 && K345.isHostMethod(te.classList, 'contains') && K345.isHostMethod(te.classList, 'add') && K345.isHostMethod(te.classList, 'remove') && K345.isHostMethod(te.classList, 'toggle')); })(); /* remove leading and trailing whitespace */ function trim(s) { if (typeof s === 'string') { s = s.replace(/(^\s+|\s+$)/g, ''); } return s; } /** * @namespace internal functions * @private */ funcs = { /**/ reg: undefined, /**#@+ * @private * @function * @inner * @memberOf funcs */ /** * $$DESCRIPTION$$ * * @param {Object|String} addto * $$DESCRIPTION$$ * @throws TypeError on illegal parameter type */ setnamespace: function (addto) { var pre; if (K345.isObject(addto) | addto === GNS) { pre = ''; } else if (typeof addto === 'string' && (/^\S+$/).test(addto)) { pre = addto; addto = GNS; } else { throw new TypeError( 'classAPI namespace must be either an object or a string' ); } Object.forEach(pubApi, function (item) { addto[pre + item] = pubApi[item]; }); }, /** * Checks if an element's className property contains cl_name * * @name has * @param {Node} el * element to check * @param {String} cl_name * class name to check for in el.className * @returns {Boolean} * true if element.className contains cl_name */ has: (hasCList) ? function (el, cl_name) { return el.classList.contains(cl_name); } : function (el, cl_name) { if ((/[\s\x00-\x1F\x7F\xA0]/).test(cl_name)) { throw new Error('INVALID_CHARACTER_ERR'); } funcs.reg = new RegExp('(^|\\s)' + cl_name + '(\\s|$)', 'g'); return funcs.reg.test(el.className); }, /** * Adds cl_name to element's className property * * @name add * @param {Node} el * element to add class name to * @param {String} cl_name * class name to add to el.className */ add: (hasCList) ? function (el, cl_name) { el.classList.add(cl_name); } : function (el, cl_name) { if (!funcs.has(el, cl_name)) { el.className += ' ' + cl_name; } }, /** * removes cl_name from element's className property * * @name remove * @param {Node} el * element to remove class name from * @param {String} cl_name * class name to remove from el.className */ remove: (hasCList) ? function (el, cl_name) { el.classList.remove(cl_name); } : function (el, cl_name) { if (funcs.has(el, cl_name)) { el.className = el.className.replace(funcs.reg, ' '); } }, /** * toggles cl_name on element's className property * * @name toggle * @param {Node} el * element to toggle class name on * @param {String} cl_name * class name to switch */ toggle: (hasCList) ? function (el, cl_name) { el.classList.toggle(cl_name); } : function (el, cl_name) { funcs.add_rem_if(el, cl_name, !funcs.has(el, cl_name)); }, /** * adds »class_name« to class name of »elRef« if »condition« is truthy * otherwise »class_name« will be removed * * @name add_rem_if * @param {Node} el * element to toggle class name on * @param {String} cl_name * class name to switch * @param {Mixed} condition * class name is altered only if condition is truthy */ add_rem_if: function (el, cl_name, cond) { var fname = (cond) ? 'add' : 'remove'; return funcs.multi.call(funcs[fname], el, cl_name); }, /** * check if a class name is a valid non empty string not containing any * whitespace characters * * @name isValid * @param {String} cl_name * class name to test * @returns {Boolean} * true, if cl_name is valid */ isValid: function (cl_name) { if (typeof cl_name === 'string' && cl_name.length > 0 && !((/\s/).test(cl_name))) { return true; } throw new Error('class names must be non empty strings not containing' + ' any whitespace.\nparameter was: "' + cl_name + '"'); }, /** * handle variable number of class names * * @name multi * @param {Node} el * element to apply multiple classname actions to * @param {String} cl_name * variable number of class name strings * @param {String...} [cl_name] * variable number of class name strings * @this {Function} * function reference to apply */ multi: function (el) { var l = arguments.length, clname, i; for (i = 1; i < l; i++) { clname = arguments[i]; if (funcs.isValid(clname)) { this(el, clname); } } /* if remaining className is empty or contains only whitespace, remove attribute className */ if ((/^\s*$/).test(el.className)) { try { el.removeAttribute('class'); delete el.className; } catch (ex) { //'NOP'; /* NOP, this branch prevents stop on exception */ } } } }; /**#@- */ /** * generic handler for api method calls * @private * * @this {Object} * internal data object, contains name of func to call, * func api name, number of args… * @param {Node} el * element to apply class name changes to * @param {String} cname... * class name(s) to work with * @returns {Mixed} * return value of called function */ function handler(el, cname) { var args = K345.toArray(arguments), fname = this.func, rv, cond, o, oldname; /* check number of passed arguments */ if (isNaN(this.minArgs)) { this.minArgs = 2; } if (args.length < this.minArgs) { throw new Error(this.name + ': method expects at least ' + this.minArgs + ' arguments; only ' + args.length + ' were passed'); } if (!K345.isNodeElement(el)) { if (Array.isArray(el)) { o = this; return el.map(function (elem) { args[0] = elem; /* replace element reference */ return handler.apply(o, args); }); } throw new Error(this.name + ': first argument is not an element' + ' reference: ' + el); } oldname = (el.className) ? trim(el.className) : ''; rv = oldname; switch (this.name) { case 'getClass': rv = oldname.split(/\s+/); break; case 'setClass': el.className = ''; /*no break statement here*/ case 'addClass': case 'removeClass': case 'toggleClass': funcs.multi.apply(funcs[fname], args); break; case 'addClassIf': case 'removeClassIf': cond = args.pop(); if (cond) { funcs.multi.apply(funcs[fname], args); } break; case 'replaceClass': if (funcs.isValid(cname) && funcs.isValid(args[2]) && (args[3] !== true || funcs.has(el, cname)) ) { funcs.add(el, args[2]); funcs.remove(el, cname); } break; default: if (fname in funcs && funcs.isValid(cname)) { rv = funcs[fname].apply(null, args); } break; } return rv; } /* * P U B L I C A P I * ==================== */ pubApi = { /**#@+ * @public * @function * @memberOf K345.DOM */ /** * check existance of »class_name« * * @name hasClass * @param {Node} elRef * reference of element to test * @param {String} class_name * @returns {Boolean} * »true« if the class name of »elRef« contains »class_name« */ hasClass: handler.bind({ func: 'has', name: 'hasClass' }), /** * adds all arguments »class_name« to the class name of »elRef« if not * already present. * * @name addClass * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @returns {String} * previous value of »elRef.className« */ addClass: handler.bind({ func: 'add', name: 'addClass' }), /** * Overwrites existing class name of »elRef« with all arguments »class_name« * * @name setClass * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @returns {String} * previous value of »elRef.className« */ setClass: handler.bind({ func: 'add', name: 'setClass' }), /** * gets class name(s) of »elRef« as Array * * @name getClass * @param {Node} elRef * reference of element to alter * @returns {Array} * Array containing value(s) of »elRef.className« */ getClass: handler.bind({ name: 'getClass', minArgs: 1 }), /** * adds »class_name« to the class name of »elRef« if condition is truthy * * @name addClassIf * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @param {Mixed} condition * class name is altered only if condition is truthy * (true, 1, non-empty string etc...) * @returns {String} * previous value of »elRef.className« */ addClassIf: handler.bind({ func: 'add', name: 'addClassIf', minArgs: 3 }), /** * removes all arguments »class_name« from the class name of »elRef« * * @name removeClass * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @returns {String} * previous value of »elRef.className« */ removeClass: handler.bind({ func: 'remove', name: 'removeClass' }), /** * removes »class_name« from the class name of »elRef« if condition is truthy * * @name removeClassIf * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @param {Mixed} condition * class name is altered only if condition is truthy * (true, 1, non-empty string etc...) * @returns {String} * previous value of »elRef.className« */ removeClassIf: handler.bind({ func: 'remove', name: 'removeClassIf', minArgs: 3 }), /** * removes »class_name1« and adds »class_name2« to the class name of »elRef«. * (like seperate calls of »addClass« and »removeClass«) * if »strict« is »true« class name will be replaced only if »class_name1« exists * * @name replaceClass * @param {Node} elRef * reference of element to alter * @param {String} class_name1 * class name to replace * @param {String} class_name2 * replacement class name * @param {Boolean} strict * if »true«, class name of elRef.« is altered only if »class_name1« is * present in elRef.classname * @returns {String} * previous value of »elRef.className« */ replaceClass: handler.bind({ func: 'repl', name: 'replaceClass', minArgs: 3 }), /** * for each argument »class_name«: * add »class_name« if not in class name of »elRef« and removes it * if »class_name« is set * * @name toggleClass * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names (optional) * @returns {String} * previous value of »elRef.className« */ toggleClass: handler.bind({ func: 'toggle', name: 'toggleClass' }), /** * adds »class_name« to class name of »elRef« if »condition« is truthy * otherwise »class_name« will be removed * * @name addOrRemoveClassIf * @param {Node} elRef * reference of element to alter * @param {String} class_name * @param {String...} [class_name] * additional class names * @param {Mixed} condition * class name is altered only if condition is truthy * (true, 1, non-empty string etc...) * @returns {String} * previous value of »elRef.className« */ addOrRemoveClassIf: handler.bind({ func: 'add_rem_if', name: 'addOrRemoveClassIf', minArgs: 3 }) /**#@+ */ }; /** * Add all methods to a custom namespace object or add them to global namespace * (with prefix) * * @param {Object | String} ns * object or prefix char(s). * All chars of prefix must be valid within variable names */ K345.DOM.classAPI_NS = funcs.setnamespace; /* add to namespace K345.DOM */ funcs.setnamespace(K345.DOM); })(this);