1 /**
  2  * @fileOverview dom-class
  3  * @author lifesinger@gmail.com,yiminghe@gmail.com
  4  */
  5 KISSY.add('dom/class', function (S, DOM, undefined) {
  6 
  7     var SPACE = ' ',
  8         REG_SPLIT = /[\.\s]\s*\.?/,
  9         REG_CLASS = /[\n\t]/g;
 10 
 11     function norm(elemClass) {
 12         return (SPACE + elemClass + SPACE).replace(REG_CLASS, SPACE);
 13     }
 14 
 15     S.mix(DOM,
 16 
 17         /**
 18          * @lends DOM
 19          */
 20         {
 21             /**
 22              * Determine whether any of the matched elements are assigned the given classes.
 23              * @param {HTMLElement|String|HTMLElement[]} [selector] matched elements
 24              * @param {String} className One or more class names to search for.
 25              * multiple class names is separated by space
 26              * @return {Boolean}
 27              */
 28             hasClass:function (selector, className) {
 29                 return batch(selector, className, function (elem, classNames, cl) {
 30                     var elemClass = elem.className;
 31                     if (elemClass) {
 32                         var className = norm(elemClass),
 33                             j = 0,
 34                             ret = true;
 35                         for (; j < cl; j++) {
 36                             if (className.indexOf(SPACE + classNames[j] + SPACE) < 0) {
 37                                 ret = false;
 38                                 break;
 39                             }
 40                         }
 41                         if (ret) {
 42                             return true;
 43                         }
 44                     }
 45                 }, true);
 46             },
 47 
 48             /**
 49              * Adds the specified class(es) to each of the set of matched elements.
 50              * @param {HTMLElement|String|HTMLElement[]} [selector] matched elements
 51              * @param {String} className One or more class names to be added to the class attribute of each matched element.
 52              * multiple class names is separated by space
 53              */
 54             addClass:function (selector, className) {
 55                 batch(selector, className, function (elem, classNames, cl) {
 56                     var elemClass = elem.className;
 57                     if (!elemClass) {
 58                         elem.className = className;
 59                     } else {
 60                         var normClassName = norm(elemClass),
 61                             setClass = elemClass,
 62                             j = 0;
 63                         for (; j < cl; j++) {
 64                             if (normClassName.indexOf(SPACE + classNames[j] + SPACE) < 0) {
 65                                 setClass += SPACE + classNames[j];
 66                             }
 67                         }
 68                         elem.className = S.trim(setClass);
 69                     }
 70                 }, undefined);
 71             },
 72 
 73             /**
 74              * Remove a single class, multiple classes, or all classes from each element in the set of matched elements.
 75              * @param {HTMLElement|String|HTMLElement[]} [selector] matched elements
 76              * @param {String} className One or more class names to be removed from the class attribute of each matched element.
 77              * multiple class names is separated by space
 78              */
 79             removeClass:function (selector, className) {
 80                 batch(selector, className, function (elem, classNames, cl) {
 81                     var elemClass = elem.className;
 82                     if (elemClass) {
 83                         if (!cl) {
 84                             elem.className = '';
 85                         } else {
 86                             var className = norm(elemClass),
 87                                 j = 0,
 88                                 needle;
 89                             for (; j < cl; j++) {
 90                                 needle = SPACE + classNames[j] + SPACE;
 91                                 // 一个 cls 有可能多次出现:'link link2 link link3 link'
 92                                 while (className.indexOf(needle) >= 0) {
 93                                     className = className.replace(needle, SPACE);
 94                                 }
 95                             }
 96                             elem.className = S.trim(className);
 97                         }
 98                     }
 99                 }, undefined);
100             },
101 
102             /**
103              * Replace a class with another class for matched elements.
104              * If no oldClassName is present, the newClassName is simply added.
105              * @param {HTMLElement|String|HTMLElement[]} [selector] matched elements
106              * @param {String} oldClassName One or more class names to be removed from the class attribute of each matched element.
107              * multiple class names is separated by space
108              * @param {String} newClassName One or more class names to be added to the class attribute of each matched element.
109              * multiple class names is separated by space
110              */
111             replaceClass:function (selector, oldClassName, newClassName) {
112                 DOM.removeClass(selector, oldClassName);
113                 DOM.addClass(selector, newClassName);
114             },
115 
116             /**
117              * Add or remove one or more classes from each element in the set of
118              * matched elements, depending on either the class's presence or the
119              * value of the switch argument.
120              * @param {HTMLElement|String|HTMLElement[]} [selector] matched elements
121              * @param {String} className One or more class names to be added to the class attribute of each matched element.
122              * multiple class names is separated by space
123              * @param [state] {Boolean} optional boolean to indicate whether class
124              *        should be added or removed regardless of current state.
125              */
126             toggleClass:function (selector, className, state) {
127                 var isBool = S.isBoolean(state), has;
128 
129                 batch(selector, className, function (elem, classNames, cl) {
130                     var j = 0, className;
131                     for (; j < cl; j++) {
132                         className = classNames[j];
133                         has = isBool ? !state : DOM.hasClass(elem, className);
134                         DOM[has ? 'removeClass' : 'addClass'](elem, className);
135                     }
136                 }, undefined);
137             }
138         });
139 
140     function batch(selector, value, fn, resultIsBool) {
141         if (!(value = S.trim(value))) {
142             return resultIsBool ? false : undefined;
143         }
144 
145         var elems = DOM.query(selector),
146             len = elems.length,
147             tmp = value.split(REG_SPLIT),
148             elem,
149             ret;
150 
151         var classNames = [];
152         for (var i = 0; i < tmp.length; i++) {
153             var t = S.trim(tmp[i]);
154             if (t) {
155                 classNames.push(t);
156             }
157         }
158         for (i = 0; i < len; i++) {
159             elem = elems[i];
160             if (elem.nodeType==DOM.ELEMENT_NODE) {
161                 ret = fn(elem, classNames, classNames.length);
162                 if (ret !== undefined) {
163                     return ret;
164                 }
165             }
166         }
167 
168         if (resultIsBool) {
169             return false;
170         }
171         return undefined;
172     }
173 
174     return DOM;
175 }, {
176     requires:["dom/base"]
177 });
178 
179 /**
180  * NOTES:
181  *   - hasClass/addClass/removeClass 的逻辑和 jQuery 保持一致
182  *   - toggleClass 不支持 value 为 undefined 的情形(jQuery 支持)
183  */
184