PageRenderTime 78ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/files/yui/3.15.0/dom-base/dom-base.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 702 lines | 469 code | 103 blank | 130 comment | 132 complexity | 741976b535d9413da70514d3ae5dff34 MD5 | raw file
  1. /*
  2. YUI 3.15.0 (build 834026e)
  3. Copyright 2014 Yahoo! Inc. All rights reserved.
  4. Licensed under the BSD License.
  5. http://yuilibrary.com/license/
  6. */
  7. YUI.add('dom-base', function (Y, NAME) {
  8. /**
  9. * @for DOM
  10. * @module dom
  11. */
  12. var documentElement = Y.config.doc.documentElement,
  13. Y_DOM = Y.DOM,
  14. TAG_NAME = 'tagName',
  15. OWNER_DOCUMENT = 'ownerDocument',
  16. EMPTY_STRING = '',
  17. addFeature = Y.Features.add,
  18. testFeature = Y.Features.test;
  19. Y.mix(Y_DOM, {
  20. /**
  21. * Returns the text content of the HTMLElement.
  22. * @method getText
  23. * @param {HTMLElement} element The html element.
  24. * @return {String} The text content of the element (includes text of any descending elements).
  25. */
  26. getText: (documentElement.textContent !== undefined) ?
  27. function(element) {
  28. var ret = '';
  29. if (element) {
  30. ret = element.textContent;
  31. }
  32. return ret || '';
  33. } : function(element) {
  34. var ret = '';
  35. if (element) {
  36. ret = element.innerText || element.nodeValue; // might be a textNode
  37. }
  38. return ret || '';
  39. },
  40. /**
  41. * Sets the text content of the HTMLElement.
  42. * @method setText
  43. * @param {HTMLElement} element The html element.
  44. * @param {String} content The content to add.
  45. */
  46. setText: (documentElement.textContent !== undefined) ?
  47. function(element, content) {
  48. if (element) {
  49. element.textContent = content;
  50. }
  51. } : function(element, content) {
  52. if ('innerText' in element) {
  53. element.innerText = content;
  54. } else if ('nodeValue' in element) {
  55. element.nodeValue = content;
  56. }
  57. },
  58. CUSTOM_ATTRIBUTES: (!documentElement.hasAttribute) ? { // IE < 8
  59. 'for': 'htmlFor',
  60. 'class': 'className'
  61. } : { // w3c
  62. 'htmlFor': 'for',
  63. 'className': 'class'
  64. },
  65. /**
  66. * Provides a normalized attribute interface.
  67. * @method setAttribute
  68. * @param {HTMLElement} el The target element for the attribute.
  69. * @param {String} attr The attribute to set.
  70. * @param {String} val The value of the attribute.
  71. */
  72. setAttribute: function(el, attr, val, ieAttr) {
  73. if (el && attr && el.setAttribute) {
  74. attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
  75. el.setAttribute(attr, val, ieAttr);
  76. }
  77. },
  78. /**
  79. * Provides a normalized attribute interface.
  80. * @method getAttribute
  81. * @param {HTMLElement} el The target element for the attribute.
  82. * @param {String} attr The attribute to get.
  83. * @return {String} The current value of the attribute.
  84. */
  85. getAttribute: function(el, attr, ieAttr) {
  86. ieAttr = (ieAttr !== undefined) ? ieAttr : 2;
  87. var ret = '';
  88. if (el && attr && el.getAttribute) {
  89. attr = Y_DOM.CUSTOM_ATTRIBUTES[attr] || attr;
  90. // BUTTON value issue for IE < 8
  91. ret = (el.tagName === "BUTTON" && attr === 'value') ? Y_DOM.getValue(el) : el.getAttribute(attr, ieAttr);
  92. if (ret === null) {
  93. ret = ''; // per DOM spec
  94. }
  95. }
  96. return ret;
  97. },
  98. VALUE_SETTERS: {},
  99. VALUE_GETTERS: {},
  100. getValue: function(node) {
  101. var ret = '', // TODO: return null?
  102. getter;
  103. if (node && node[TAG_NAME]) {
  104. getter = Y_DOM.VALUE_GETTERS[node[TAG_NAME].toLowerCase()];
  105. if (getter) {
  106. ret = getter(node);
  107. } else {
  108. ret = node.value;
  109. }
  110. }
  111. // workaround for IE8 JSON stringify bug
  112. // which converts empty string values to null
  113. if (ret === EMPTY_STRING) {
  114. ret = EMPTY_STRING; // for real
  115. }
  116. return (typeof ret === 'string') ? ret : '';
  117. },
  118. setValue: function(node, val) {
  119. var setter;
  120. if (node && node[TAG_NAME]) {
  121. setter = Y_DOM.VALUE_SETTERS[node[TAG_NAME].toLowerCase()];
  122. val = (val === null) ? '' : val;
  123. if (setter) {
  124. setter(node, val);
  125. } else {
  126. node.value = val;
  127. }
  128. }
  129. },
  130. creators: {}
  131. });
  132. addFeature('value-set', 'select', {
  133. test: function() {
  134. var node = Y.config.doc.createElement('select');
  135. node.innerHTML = '<option>1</option><option>2</option>';
  136. node.value = '2';
  137. return (node.value && node.value === '2');
  138. }
  139. });
  140. if (!testFeature('value-set', 'select')) {
  141. Y_DOM.VALUE_SETTERS.select = function(node, val) {
  142. for (var i = 0, options = node.getElementsByTagName('option'), option;
  143. option = options[i++];) {
  144. if (Y_DOM.getValue(option) === val) {
  145. option.selected = true;
  146. //Y_DOM.setAttribute(option, 'selected', 'selected');
  147. break;
  148. }
  149. }
  150. };
  151. }
  152. Y.mix(Y_DOM.VALUE_GETTERS, {
  153. button: function(node) {
  154. return (node.attributes && node.attributes.value) ? node.attributes.value.value : '';
  155. }
  156. });
  157. Y.mix(Y_DOM.VALUE_SETTERS, {
  158. // IE: node.value changes the button text, which should be handled via innerHTML
  159. button: function(node, val) {
  160. var attr = node.attributes.value;
  161. if (!attr) {
  162. attr = node[OWNER_DOCUMENT].createAttribute('value');
  163. node.setAttributeNode(attr);
  164. }
  165. attr.value = val;
  166. }
  167. });
  168. Y.mix(Y_DOM.VALUE_GETTERS, {
  169. option: function(node) {
  170. var attrs = node.attributes;
  171. return (attrs.value && attrs.value.specified) ? node.value : node.text;
  172. },
  173. select: function(node) {
  174. var val = node.value,
  175. options = node.options;
  176. if (options && options.length) {
  177. // TODO: implement multipe select
  178. if (node.multiple) {
  179. } else if (node.selectedIndex > -1) {
  180. val = Y_DOM.getValue(options[node.selectedIndex]);
  181. }
  182. }
  183. return val;
  184. }
  185. });
  186. var addClass, hasClass, removeClass;
  187. Y.mix(Y.DOM, {
  188. /**
  189. * Determines whether a DOM element has the given className.
  190. * @method hasClass
  191. * @for DOM
  192. * @param {HTMLElement} element The DOM element.
  193. * @param {String} className the class name to search for
  194. * @return {Boolean} Whether or not the element has the given class.
  195. */
  196. hasClass: function(node, className) {
  197. var re = Y.DOM._getRegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
  198. return re.test(node.className);
  199. },
  200. /**
  201. * Adds a class name to a given DOM element.
  202. * @method addClass
  203. * @for DOM
  204. * @param {HTMLElement} element The DOM element.
  205. * @param {String} className the class name to add to the class attribute
  206. */
  207. addClass: function(node, className) {
  208. if (!Y.DOM.hasClass(node, className)) { // skip if already present
  209. node.className = Y.Lang.trim([node.className, className].join(' '));
  210. }
  211. },
  212. /**
  213. * Removes a class name from a given element.
  214. * @method removeClass
  215. * @for DOM
  216. * @param {HTMLElement} element The DOM element.
  217. * @param {String} className the class name to remove from the class attribute
  218. */
  219. removeClass: function(node, className) {
  220. if (className && hasClass(node, className)) {
  221. node.className = Y.Lang.trim(node.className.replace(Y.DOM._getRegExp('(?:^|\\s+)' +
  222. className + '(?:\\s+|$)'), ' '));
  223. if ( hasClass(node, className) ) { // in case of multiple adjacent
  224. removeClass(node, className);
  225. }
  226. }
  227. },
  228. /**
  229. * Replace a class with another class for a given element.
  230. * If no oldClassName is present, the newClassName is simply added.
  231. * @method replaceClass
  232. * @for DOM
  233. * @param {HTMLElement} element The DOM element
  234. * @param {String} oldClassName the class name to be replaced
  235. * @param {String} newClassName the class name that will be replacing the old class name
  236. */
  237. replaceClass: function(node, oldC, newC) {
  238. removeClass(node, oldC); // remove first in case oldC === newC
  239. addClass(node, newC);
  240. },
  241. /**
  242. * If the className exists on the node it is removed, if it doesn't exist it is added.
  243. * @method toggleClass
  244. * @for DOM
  245. * @param {HTMLElement} element The DOM element
  246. * @param {String} className the class name to be toggled
  247. * @param {Boolean} addClass optional boolean to indicate whether class
  248. * should be added or removed regardless of current state
  249. */
  250. toggleClass: function(node, className, force) {
  251. var add = (force !== undefined) ? force :
  252. !(hasClass(node, className));
  253. if (add) {
  254. addClass(node, className);
  255. } else {
  256. removeClass(node, className);
  257. }
  258. }
  259. });
  260. hasClass = Y.DOM.hasClass;
  261. removeClass = Y.DOM.removeClass;
  262. addClass = Y.DOM.addClass;
  263. var re_tag = /<([a-z]+)/i,
  264. Y_DOM = Y.DOM,
  265. addFeature = Y.Features.add,
  266. testFeature = Y.Features.test,
  267. creators = {},
  268. createFromDIV = function(html, tag) {
  269. var div = Y.config.doc.createElement('div'),
  270. ret = true;
  271. div.innerHTML = html;
  272. if (!div.firstChild || div.firstChild.tagName !== tag.toUpperCase()) {
  273. ret = false;
  274. }
  275. return ret;
  276. },
  277. re_tbody = /(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*<tbody/,
  278. TABLE_OPEN = '<table>',
  279. TABLE_CLOSE = '</table>',
  280. selectedIndex;
  281. Y.mix(Y.DOM, {
  282. _fragClones: {},
  283. _create: function(html, doc, tag) {
  284. tag = tag || 'div';
  285. var frag = Y_DOM._fragClones[tag];
  286. if (frag) {
  287. frag = frag.cloneNode(false);
  288. } else {
  289. frag = Y_DOM._fragClones[tag] = doc.createElement(tag);
  290. }
  291. frag.innerHTML = html;
  292. return frag;
  293. },
  294. _children: function(node, tag) {
  295. var i = 0,
  296. children = node.children,
  297. childNodes,
  298. hasComments,
  299. child;
  300. if (children && children.tags) { // use tags filter when possible
  301. if (tag) {
  302. children = node.children.tags(tag);
  303. } else { // IE leaks comments into children
  304. hasComments = children.tags('!').length;
  305. }
  306. }
  307. if (!children || (!children.tags && tag) || hasComments) {
  308. childNodes = children || node.childNodes;
  309. children = [];
  310. while ((child = childNodes[i++])) {
  311. if (child.nodeType === 1) {
  312. if (!tag || tag === child.tagName) {
  313. children.push(child);
  314. }
  315. }
  316. }
  317. }
  318. return children || [];
  319. },
  320. /**
  321. * Creates a new dom node using the provided markup string.
  322. * @method create
  323. * @param {String} html The markup used to create the element
  324. * @param {HTMLDocument} doc An optional document context
  325. * @return {HTMLElement|DocumentFragment} returns a single HTMLElement
  326. * when creating one node, and a documentFragment when creating
  327. * multiple nodes.
  328. */
  329. create: function(html, doc) {
  330. if (typeof html === 'string') {
  331. html = Y.Lang.trim(html); // match IE which trims whitespace from innerHTML
  332. }
  333. doc = doc || Y.config.doc;
  334. var m = re_tag.exec(html),
  335. create = Y_DOM._create,
  336. custom = creators,
  337. ret = null,
  338. creator, tag, node, nodes;
  339. if (html != undefined) { // not undefined or null
  340. if (m && m[1]) {
  341. creator = custom[m[1].toLowerCase()];
  342. if (typeof creator === 'function') {
  343. create = creator;
  344. } else {
  345. tag = creator;
  346. }
  347. }
  348. node = create(html, doc, tag);
  349. nodes = node.childNodes;
  350. if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment"
  351. ret = node.removeChild(nodes[0]);
  352. } else if (nodes[0] && nodes[0].className === 'yui3-big-dummy') { // using dummy node to preserve some attributes (e.g. OPTION not selected)
  353. selectedIndex = node.selectedIndex;
  354. if (nodes.length === 2) {
  355. ret = nodes[0].nextSibling;
  356. } else {
  357. node.removeChild(nodes[0]);
  358. ret = Y_DOM._nl2frag(nodes, doc);
  359. }
  360. } else { // return multiple nodes as a fragment
  361. ret = Y_DOM._nl2frag(nodes, doc);
  362. }
  363. }
  364. return ret;
  365. },
  366. _nl2frag: function(nodes, doc) {
  367. var ret = null,
  368. i, len;
  369. if (nodes && (nodes.push || nodes.item) && nodes[0]) {
  370. doc = doc || nodes[0].ownerDocument;
  371. ret = doc.createDocumentFragment();
  372. if (nodes.item) { // convert live list to static array
  373. nodes = Y.Array(nodes, 0, true);
  374. }
  375. for (i = 0, len = nodes.length; i < len; i++) {
  376. ret.appendChild(nodes[i]);
  377. }
  378. } // else inline with log for minification
  379. return ret;
  380. },
  381. /**
  382. * Inserts content in a node at the given location
  383. * @method addHTML
  384. * @param {HTMLElement} node The node to insert into
  385. * @param {HTMLElement | Array | HTMLCollection} content The content to be inserted
  386. * @param {HTMLElement} where Where to insert the content
  387. * If no "where" is given, content is appended to the node
  388. * Possible values for "where"
  389. * <dl>
  390. * <dt>HTMLElement</dt>
  391. * <dd>The element to insert before</dd>
  392. * <dt>"replace"</dt>
  393. * <dd>Replaces the existing HTML</dd>
  394. * <dt>"before"</dt>
  395. * <dd>Inserts before the existing HTML</dd>
  396. * <dt>"before"</dt>
  397. * <dd>Inserts content before the node</dd>
  398. * <dt>"after"</dt>
  399. * <dd>Inserts content after the node</dd>
  400. * </dl>
  401. */
  402. addHTML: function(node, content, where) {
  403. var nodeParent = node.parentNode,
  404. i = 0,
  405. item,
  406. ret = content,
  407. newNode;
  408. if (content != undefined) { // not null or undefined (maybe 0)
  409. if (content.nodeType) { // DOM node, just add it
  410. newNode = content;
  411. } else if (typeof content == 'string' || typeof content == 'number') {
  412. ret = newNode = Y_DOM.create(content);
  413. } else if (content[0] && content[0].nodeType) { // array or collection
  414. newNode = Y.config.doc.createDocumentFragment();
  415. while ((item = content[i++])) {
  416. newNode.appendChild(item); // append to fragment for insertion
  417. }
  418. }
  419. }
  420. if (where) {
  421. if (newNode && where.parentNode) { // insert regardless of relationship to node
  422. where.parentNode.insertBefore(newNode, where);
  423. } else {
  424. switch (where) {
  425. case 'replace':
  426. while (node.firstChild) {
  427. node.removeChild(node.firstChild);
  428. }
  429. if (newNode) { // allow empty content to clear node
  430. node.appendChild(newNode);
  431. }
  432. break;
  433. case 'before':
  434. if (newNode) {
  435. nodeParent.insertBefore(newNode, node);
  436. }
  437. break;
  438. case 'after':
  439. if (newNode) {
  440. if (node.nextSibling) { // IE errors if refNode is null
  441. nodeParent.insertBefore(newNode, node.nextSibling);
  442. } else {
  443. nodeParent.appendChild(newNode);
  444. }
  445. }
  446. break;
  447. default:
  448. if (newNode) {
  449. node.appendChild(newNode);
  450. }
  451. }
  452. }
  453. } else if (newNode) {
  454. node.appendChild(newNode);
  455. }
  456. // `select` elements are the only elements with `selectedIndex`.
  457. // Don't grab the dummy `option` element's `selectedIndex`.
  458. if (node.nodeName == "SELECT" && selectedIndex > 0) {
  459. node.selectedIndex = selectedIndex - 1;
  460. }
  461. return ret;
  462. },
  463. wrap: function(node, html) {
  464. var parent = (html && html.nodeType) ? html : Y.DOM.create(html),
  465. nodes = parent.getElementsByTagName('*');
  466. if (nodes.length) {
  467. parent = nodes[nodes.length - 1];
  468. }
  469. if (node.parentNode) {
  470. node.parentNode.replaceChild(parent, node);
  471. }
  472. parent.appendChild(node);
  473. },
  474. unwrap: function(node) {
  475. var parent = node.parentNode,
  476. lastChild = parent.lastChild,
  477. next = node,
  478. grandparent;
  479. if (parent) {
  480. grandparent = parent.parentNode;
  481. if (grandparent) {
  482. node = parent.firstChild;
  483. while (node !== lastChild) {
  484. next = node.nextSibling;
  485. grandparent.insertBefore(node, parent);
  486. node = next;
  487. }
  488. grandparent.replaceChild(lastChild, parent);
  489. } else {
  490. parent.removeChild(node);
  491. }
  492. }
  493. }
  494. });
  495. addFeature('innerhtml', 'table', {
  496. test: function() {
  497. var node = Y.config.doc.createElement('table');
  498. try {
  499. node.innerHTML = '<tbody></tbody>';
  500. } catch(e) {
  501. return false;
  502. }
  503. return (node.firstChild && node.firstChild.nodeName === 'TBODY');
  504. }
  505. });
  506. addFeature('innerhtml-div', 'tr', {
  507. test: function() {
  508. return createFromDIV('<tr></tr>', 'tr');
  509. }
  510. });
  511. addFeature('innerhtml-div', 'script', {
  512. test: function() {
  513. return createFromDIV('<script></script>', 'script');
  514. }
  515. });
  516. if (!testFeature('innerhtml', 'table')) {
  517. // TODO: thead/tfoot with nested tbody
  518. // IE adds TBODY when creating TABLE elements (which may share this impl)
  519. creators.tbody = function(html, doc) {
  520. var frag = Y_DOM.create(TABLE_OPEN + html + TABLE_CLOSE, doc),
  521. tb = Y.DOM._children(frag, 'tbody')[0];
  522. if (frag.children.length > 1 && tb && !re_tbody.test(html)) {
  523. tb.parentNode.removeChild(tb); // strip extraneous tbody
  524. }
  525. return frag;
  526. };
  527. }
  528. if (!testFeature('innerhtml-div', 'script')) {
  529. creators.script = function(html, doc) {
  530. var frag = doc.createElement('div');
  531. frag.innerHTML = '-' + html;
  532. frag.removeChild(frag.firstChild);
  533. return frag;
  534. };
  535. creators.link = creators.style = creators.script;
  536. }
  537. if (!testFeature('innerhtml-div', 'tr')) {
  538. Y.mix(creators, {
  539. option: function(html, doc) {
  540. return Y_DOM.create('<select><option class="yui3-big-dummy" selected></option>' + html + '</select>', doc);
  541. },
  542. tr: function(html, doc) {
  543. return Y_DOM.create('<tbody>' + html + '</tbody>', doc);
  544. },
  545. td: function(html, doc) {
  546. return Y_DOM.create('<tr>' + html + '</tr>', doc);
  547. },
  548. col: function(html, doc) {
  549. return Y_DOM.create('<colgroup>' + html + '</colgroup>', doc);
  550. },
  551. tbody: 'table'
  552. });
  553. Y.mix(creators, {
  554. legend: 'fieldset',
  555. th: creators.td,
  556. thead: creators.tbody,
  557. tfoot: creators.tbody,
  558. caption: creators.tbody,
  559. colgroup: creators.tbody,
  560. optgroup: creators.option
  561. });
  562. }
  563. Y_DOM.creators = creators;
  564. Y.mix(Y.DOM, {
  565. /**
  566. * Sets the width of the element to the given size, regardless
  567. * of box model, border, padding, etc.
  568. * @method setWidth
  569. * @param {HTMLElement} element The DOM element.
  570. * @param {String|Number} size The pixel height to size to
  571. */
  572. setWidth: function(node, size) {
  573. Y.DOM._setSize(node, 'width', size);
  574. },
  575. /**
  576. * Sets the height of the element to the given size, regardless
  577. * of box model, border, padding, etc.
  578. * @method setHeight
  579. * @param {HTMLElement} element The DOM element.
  580. * @param {String|Number} size The pixel height to size to
  581. */
  582. setHeight: function(node, size) {
  583. Y.DOM._setSize(node, 'height', size);
  584. },
  585. _setSize: function(node, prop, val) {
  586. val = (val > 0) ? val : 0;
  587. var size = 0;
  588. node.style[prop] = val + 'px';
  589. size = (prop === 'height') ? node.offsetHeight : node.offsetWidth;
  590. if (size > val) {
  591. val = val - (size - val);
  592. if (val < 0) {
  593. val = 0;
  594. }
  595. node.style[prop] = val + 'px';
  596. }
  597. }
  598. });
  599. }, '3.15.0', {"requires": ["dom-core"]});