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);