classlist.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * classList.js: Cross-browser full element.classList implementation.
  3. * 1.1.20170427
  4. *
  5. * By Eli Grey, http://eligrey.com
  6. * License: Dedicated to the public domain.
  7. * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
  8. */
  9. /*global self, document, DOMException */
  10. /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
  11. if ("document" in self) {
  12. // Full polyfill for browsers with no classList support
  13. // Including IE < Edge missing SVGElement.classList
  14. if (!("classList" in document.createElement("_"))
  15. || document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) {
  16. (function (view) {
  17. "use strict";
  18. if (!('Element' in view)) return;
  19. var
  20. classListProp = "classList"
  21. , protoProp = "prototype"
  22. , elemCtrProto = view.Element[protoProp]
  23. , objCtr = Object
  24. , strTrim = String[protoProp].trim || function () {
  25. return this.replace(/^\s+|\s+$/g, "");
  26. }
  27. , arrIndexOf = Array[protoProp].indexOf || function (item) {
  28. var
  29. i = 0
  30. , len = this.length
  31. ;
  32. for (; i < len; i++) {
  33. if (i in this && this[i] === item) {
  34. return i;
  35. }
  36. }
  37. return -1;
  38. }
  39. // Vendors: please allow content code to instantiate DOMExceptions
  40. , DOMEx = function (type, message) {
  41. this.name = type;
  42. this.code = DOMException[type];
  43. this.message = message;
  44. }
  45. , checkTokenAndGetIndex = function (classList, token) {
  46. if (token === "") {
  47. throw new DOMEx(
  48. "SYNTAX_ERR"
  49. , "An invalid or illegal string was specified"
  50. );
  51. }
  52. if (/\s/.test(token)) {
  53. throw new DOMEx(
  54. "INVALID_CHARACTER_ERR"
  55. , "String contains an invalid character"
  56. );
  57. }
  58. return arrIndexOf.call(classList, token);
  59. }
  60. , ClassList = function (elem) {
  61. var
  62. trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
  63. , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
  64. , i = 0
  65. , len = classes.length
  66. ;
  67. for (; i < len; i++) {
  68. this.push(classes[i]);
  69. }
  70. this._updateClassName = function () {
  71. elem.setAttribute("class", this.toString());
  72. };
  73. }
  74. , classListProto = ClassList[protoProp] = []
  75. , classListGetter = function () {
  76. return new ClassList(this);
  77. }
  78. ;
  79. // Most DOMException implementations don't allow calling DOMException's toString()
  80. // on non-DOMExceptions. Error's toString() is sufficient here.
  81. DOMEx[protoProp] = Error[protoProp];
  82. classListProto.item = function (i) {
  83. return this[i] || null;
  84. };
  85. classListProto.contains = function (token) {
  86. token += "";
  87. return checkTokenAndGetIndex(this, token) !== -1;
  88. };
  89. classListProto.add = function () {
  90. var
  91. tokens = arguments
  92. , i = 0
  93. , l = tokens.length
  94. , token
  95. , updated = false
  96. ;
  97. do {
  98. token = tokens[i] + "";
  99. if (checkTokenAndGetIndex(this, token) === -1) {
  100. this.push(token);
  101. updated = true;
  102. }
  103. }
  104. while (++i < l);
  105. if (updated) {
  106. this._updateClassName();
  107. }
  108. };
  109. classListProto.remove = function () {
  110. var
  111. tokens = arguments
  112. , i = 0
  113. , l = tokens.length
  114. , token
  115. , updated = false
  116. , index
  117. ;
  118. do {
  119. token = tokens[i] + "";
  120. index = checkTokenAndGetIndex(this, token);
  121. while (index !== -1) {
  122. this.splice(index, 1);
  123. updated = true;
  124. index = checkTokenAndGetIndex(this, token);
  125. }
  126. }
  127. while (++i < l);
  128. if (updated) {
  129. this._updateClassName();
  130. }
  131. };
  132. classListProto.toggle = function (token, force) {
  133. token += "";
  134. var
  135. result = this.contains(token)
  136. , method = result ?
  137. force !== true && "remove"
  138. :
  139. force !== false && "add"
  140. ;
  141. if (method) {
  142. this[method](token);
  143. }
  144. if (force === true || force === false) {
  145. return force;
  146. } else {
  147. return !result;
  148. }
  149. };
  150. classListProto.toString = function () {
  151. return this.join(" ");
  152. };
  153. if (objCtr.defineProperty) {
  154. var classListPropDesc = {
  155. get: classListGetter
  156. , enumerable: true
  157. , configurable: true
  158. };
  159. try {
  160. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  161. } catch (ex) { // IE 8 doesn't support enumerable:true
  162. // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
  163. // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
  164. if (ex.number === undefined || ex.number === -0x7FF5EC54) {
  165. classListPropDesc.enumerable = false;
  166. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  167. }
  168. }
  169. } else if (objCtr[protoProp].__defineGetter__) {
  170. elemCtrProto.__defineGetter__(classListProp, classListGetter);
  171. }
  172. }(self));
  173. }
  174. // There is full or partial native classList support, so just check if we need
  175. // to normalize the add/remove and toggle APIs.
  176. (function () {
  177. "use strict";
  178. var testElement = document.createElement("_");
  179. testElement.classList.add("c1", "c2");
  180. // Polyfill for IE 10/11 and Firefox <26, where classList.add and
  181. // classList.remove exist but support only one argument at a time.
  182. if (!testElement.classList.contains("c2")) {
  183. var createMethod = function(method) {
  184. var original = DOMTokenList.prototype[method];
  185. DOMTokenList.prototype[method] = function(token) {
  186. var i, len = arguments.length;
  187. for (i = 0; i < len; i++) {
  188. token = arguments[i];
  189. original.call(this, token);
  190. }
  191. };
  192. };
  193. createMethod('add');
  194. createMethod('remove');
  195. }
  196. testElement.classList.toggle("c3", false);
  197. // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
  198. // support the second argument.
  199. if (testElement.classList.contains("c3")) {
  200. var _toggle = DOMTokenList.prototype.toggle;
  201. DOMTokenList.prototype.toggle = function(token, force) {
  202. if (1 in arguments && !this.contains(token) === !force) {
  203. return force;
  204. } else {
  205. return _toggle.call(this, token);
  206. }
  207. };
  208. }
  209. testElement = null;
  210. }());
  211. }