todomvc
Version:
> Helping you select an MV\* framework
189 lines (163 loc) • 4.81 kB
JavaScript
(function (define) {
define(function (require, exports) {
// TODO: use has() to select code to use node.classList / DOMSettableTokenList
var splitClassNameRx = /\s+/;
var classRx = '(\\s+|^)(classNames)(\\b(?![\\-_])|$)';
var trimLeadingRx = /^\s+/;
var splitClassNamesRx = /(\b\s+\b)|(\s+)/g;
/**
* Returns the list of class names on a node as an array.
* @param node {HTMLElement}
* @returns {Array}
*/
function getClassList (node) {
return node.className.split(splitClassNameRx);
}
/**
* Adds a list of class names on a node and optionally removes some.
* @param node {HTMLElement}
* @param list {Array|Object} a list of class names to add.
* @param [list.add] {Array} a list of class names to add.
* @param [list.remove] {Array} a list of class names to remove.
* @returns {Array} the resulting class names on the node
*
* @description The list param may be supplied with any of the following:
* simple array:
* setClassList(node, ['foo-box', 'bar-box']) (all added)
* simple array w/ remove property:
* list = ['foo-box', 'bar-box'];
* list.remove = ['baz-box'];
* setClassList(node, list);
* object with add and remove array properties:
* list = {
* add: ['foo-box', 'bar-box'],
* remove: ['baz-box']
* };
* setClassList(node, list);
*/
function setClassList (node, list) {
var adds, removes;
if (list) {
// figure out what to add and remove
adds = list.add || list || [];
removes = list.remove || [];
node.className = spliceClassNames(node.className, removes, adds);
}
return getClassList(node);
}
function getClassSet (node) {
var set, classNames, className;
set = {};
classNames = node.className.split(splitClassNameRx);
while ((className = classNames.pop())) set[className] = true;
return set;
}
/**
*
* @param node
* @param classSet {Object}
* @description
* Example bindings:
* stepsCompleted: {
* node: 'viewNode',
* prop: 'classList',
* enumSet: ['one', 'two', 'three']
* },
* permissions: {
* node: 'myview',
* prop: 'classList',
* enumSet: {
* modify: 'can-edit-data',
* create: 'can-add-data',
* remove: 'can-delete-data'
* }
* }
*/
function setClassSet (node, classSet) {
var removes, adds, p, newList;
removes = [];
adds = [];
for (p in classSet) {
if (p) {
if (classSet[p]) {
adds.push(p);
}
else {
removes.push(p);
}
}
}
return node.className = spliceClassNames(node.className, removes, adds);
}
// class parsing
var openRx, closeRx, innerRx, innerSpacesRx, outerSpacesRx;
openRx = '(?:\\b\\s+|^\\s*)(';
closeRx = ')(?:\\b(?!-))|(?:\\s*)$';
innerRx = '|';
innerSpacesRx = /\b\s+\b/;
outerSpacesRx = /^\s+|\s+$/;
/**
* Adds and removes class names to a string.
* @private
* @param className {String} current className
* @param removes {Array} class names to remove
* @param adds {Array} class names to add
* @returns {String} modified className
*/
function spliceClassNames (className, removes, adds) {
var rx, leftovers;
// create regex to find all removes *and adds* since we're going to
// remove them all to prevent duplicates.
removes = trim(removes.concat(adds).join(' '));
adds = trim(adds.join(' '));
rx = new RegExp(openRx
+ removes.replace(innerSpacesRx, innerRx)
+ closeRx, 'g');
// remove and add
return trim(className.replace(rx, function (m) {
// if nothing matched, we're at the end
return !m && adds ? ' ' + adds : '';
}));
}
function trim (str) {
// don't worry about high-unicode spaces. they should never be here.
return str.replace(outerSpacesRx, '');
}
function addClass (className, str) {
var newClass = removeClass(className, str);
if(newClass && className) {
newClass += ' ';
}
return newClass + className;
}
function removeClass (removes, tokens) {
var rx;
if (!removes) {
return tokens;
}
// convert space-delimited tokens with bar-delimited (regexp `or`)
removes = removes.replace(splitClassNamesRx, function (m, inner, edge) {
// only replace inner spaces with |
return edge ? '' : '|';
});
// create one-pass regexp
rx = new RegExp(classRx.replace('classNames', removes), 'g');
// remove all tokens in one pass (wish we could trim leading
// spaces in the same pass! at least the trim is not a full
// scan of the string)
return tokens.replace(rx, '').replace(trimLeadingRx, '');
}
return {
addClass: addClass,
removeClass: removeClass,
getClassList: getClassList,
setClassList: setClassList,
getClassSet: getClassSet,
setClassSet: setClassSet
};
});
}(
typeof define == 'function'
? define
: function (factory) { module.exports = factory(require); }
));