jsAPI.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. 'use strict';
  2. var EXTEND = require('whet.extend');
  3. var JSAPI = module.exports = function(data, parentNode) {
  4. EXTEND(this, data);
  5. if (parentNode) {
  6. Object.defineProperty(this, 'parentNode', {
  7. writable: true,
  8. value: parentNode
  9. });
  10. }
  11. };
  12. /**
  13. * Perform a deep clone of this node.
  14. *
  15. * @return {Object} element
  16. */
  17. JSAPI.prototype.clone = function() {
  18. var node = this;
  19. var nodeData = {};
  20. Object.keys(node).forEach(function(key) {
  21. if (key !== 'content') {
  22. nodeData[key] = node[key];
  23. }
  24. });
  25. // Deep-clone node data
  26. // This is still faster than using EXTEND(true…)
  27. nodeData = JSON.parse(JSON.stringify(nodeData));
  28. // parentNode gets set to a proper object by the parent clone,
  29. // but it needs to be true/false now to do the right thing
  30. // in the constructor.
  31. var clonedNode = new JSAPI(nodeData, !!node.parentNode);
  32. if (node.content) {
  33. clonedNode.content = node.content.map(function(childNode) {
  34. var clonedChild = childNode.clone();
  35. clonedChild.parentNode = clonedNode;
  36. return clonedChild;
  37. });
  38. }
  39. return clonedNode;
  40. };
  41. /**
  42. * Determine if item is an element
  43. * (any, with a specific name or in a names array).
  44. *
  45. * @param {String|Array} [param] element name or names arrays
  46. * @return {Boolean}
  47. */
  48. JSAPI.prototype.isElem = function(param) {
  49. if (!param) return !!this.elem;
  50. if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1);
  51. return !!this.elem && this.elem === param;
  52. };
  53. /**
  54. * Renames an element
  55. *
  56. * @param {String} name new element name
  57. * @return {Object} element
  58. */
  59. JSAPI.prototype.renameElem = function(name) {
  60. if (name && typeof name === 'string')
  61. this.elem = this.local = name;
  62. return this;
  63. };
  64. /**
  65. * Determine if element is empty.
  66. *
  67. * @return {Boolean}
  68. */
  69. JSAPI.prototype.isEmpty = function() {
  70. return !this.content || !this.content.length;
  71. };
  72. /**
  73. * Changes content by removing elements and/or adding new elements.
  74. *
  75. * @param {Number} start Index at which to start changing the content.
  76. * @param {Number} n Number of elements to remove.
  77. * @param {Array|Object} [insertion] Elements to add to the content.
  78. * @return {Array} Removed elements.
  79. */
  80. JSAPI.prototype.spliceContent = function(start, n, insertion) {
  81. if (arguments.length < 2) return [];
  82. if (!Array.isArray(insertion))
  83. insertion = Array.apply(null, arguments).slice(2);
  84. insertion.forEach(function(inner) { inner.parentNode = this }, this);
  85. return this.content.splice.apply(this.content, [start, n].concat(insertion));
  86. };
  87. /**
  88. * Determine if element has an attribute
  89. * (any, or by name or by name + value).
  90. *
  91. * @param {String} [name] attribute name
  92. * @param {String} [val] attribute value (will be toString()'ed)
  93. * @return {Boolean}
  94. */
  95. JSAPI.prototype.hasAttr = function(name, val) {
  96. if (!this.attrs || !Object.keys(this.attrs).length) return false;
  97. if (!arguments.length) return !!this.attrs;
  98. if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString();
  99. return !!this.attrs[name];
  100. };
  101. /**
  102. * Determine if element has an attribute by local name
  103. * (any, or by name or by name + value).
  104. *
  105. * @param {String} [localName] local attribute name
  106. * @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
  107. * @return {Boolean}
  108. */
  109. JSAPI.prototype.hasAttrLocal = function(localName, val) {
  110. if (!this.attrs || !Object.keys(this.attrs).length) return false;
  111. if (!arguments.length) return !!this.attrs;
  112. var callback;
  113. switch (val != null && val.constructor && val.constructor.name) {
  114. case 'Number': // same as String
  115. case 'String': callback = stringValueTest; break;
  116. case 'RegExp': callback = regexpValueTest; break;
  117. case 'Function': callback = funcValueTest; break;
  118. default: callback = nameTest;
  119. }
  120. return this.someAttr(callback);
  121. function nameTest(attr) {
  122. return attr.local === localName;
  123. }
  124. function stringValueTest(attr) {
  125. return attr.local === localName && val == attr.value;
  126. }
  127. function regexpValueTest(attr) {
  128. return attr.local === localName && val.test(attr.value);
  129. }
  130. function funcValueTest(attr) {
  131. return attr.local === localName && val(attr.value);
  132. }
  133. };
  134. /**
  135. * Get a specific attribute from an element
  136. * (by name or name + value).
  137. *
  138. * @param {String} name attribute name
  139. * @param {String} [val] attribute value (will be toString()'ed)
  140. * @return {Object|Undefined}
  141. */
  142. JSAPI.prototype.attr = function(name, val) {
  143. if (!this.hasAttr() || !arguments.length) return undefined;
  144. if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined;
  145. return this.attrs[name];
  146. };
  147. /**
  148. * Get computed attribute value from an element
  149. *
  150. * @param {String} name attribute name
  151. * @return {Object|Undefined}
  152. */
  153. JSAPI.prototype.computedAttr = function(name, val) {
  154. /* jshint eqnull: true */
  155. if (!arguments.length) return;
  156. for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode);
  157. if (val != null) {
  158. return elem ? elem.hasAttr(name, val) : false;
  159. } else if (elem && elem.hasAttr(name)) {
  160. return elem.attrs[name].value;
  161. }
  162. };
  163. /**
  164. * Remove a specific attribute.
  165. *
  166. * @param {String|Array} name attribute name
  167. * @param {String} [val] attribute value
  168. * @return {Boolean}
  169. */
  170. JSAPI.prototype.removeAttr = function(name, val, recursive) {
  171. if (!arguments.length) return false;
  172. if (Array.isArray(name)) name.forEach(this.removeAttr, this);
  173. if (!this.hasAttr(name)) return false;
  174. if (!recursive && val && this.attrs[name].value !== val) return false;
  175. delete this.attrs[name];
  176. if (!Object.keys(this.attrs).length) delete this.attrs;
  177. return true;
  178. };
  179. /**
  180. * Add attribute.
  181. *
  182. * @param {Object} [attr={}] attribute object
  183. * @return {Object|Boolean} created attribute or false if no attr was passed in
  184. */
  185. JSAPI.prototype.addAttr = function(attr) {
  186. attr = attr || {};
  187. if (attr.name === undefined ||
  188. attr.prefix === undefined ||
  189. attr.local === undefined
  190. ) return false;
  191. this.attrs = this.attrs || {};
  192. this.attrs[attr.name] = attr;
  193. return this.attrs[attr.name];
  194. };
  195. /**
  196. * Iterates over all attributes.
  197. *
  198. * @param {Function} callback callback
  199. * @param {Object} [context] callback context
  200. * @return {Boolean} false if there are no any attributes
  201. */
  202. JSAPI.prototype.eachAttr = function(callback, context) {
  203. if (!this.hasAttr()) return false;
  204. for (var name in this.attrs) {
  205. callback.call(context, this.attrs[name]);
  206. }
  207. return true;
  208. };
  209. /**
  210. * Tests whether some attribute passes the test.
  211. *
  212. * @param {Function} callback callback
  213. * @param {Object} [context] callback context
  214. * @return {Boolean} false if there are no any attributes
  215. */
  216. JSAPI.prototype.someAttr = function(callback, context) {
  217. if (!this.hasAttr()) return false;
  218. for (var name in this.attrs) {
  219. if (callback.call(context, this.attrs[name])) return true;
  220. }
  221. return false;
  222. };