PageRenderTime 43ms CodeModel.GetById 3ms RepoModel.GetById 0ms app.codeStats 1ms

/src/scripts/dom.js

https://bitbucket.org/scope/dragonfly-stp-1/
JavaScript | 994 lines | 756 code | 86 blank | 152 comment | 170 complexity | 7ef20a0947adf8328be4d23d36427651 MD5 | raw file
Possible License(s): Apache-2.0
  1. if (!window.opera)
  2. {
  3. window.opera =
  4. {
  5. postError: function(a){console.log(a);},
  6. stpVersion: true
  7. };
  8. }
  9. /*
  10. (function()
  11. {
  12. var div = document.createElement('div')
  13. var setter = div.__lookupSetter__("scrollTop");
  14. var getter = div.__lookupGetter__("scrollTop");
  15. Element.prototype.__defineSetter__('scrollTop', function(scroll_top)
  16. {
  17. opera.postError('setter: '+this.nodeName + ', '+scroll_top);
  18. setter.call(this, scroll_top);
  19. });
  20. Element.prototype.__defineGetter__('scrollTop', function()
  21. {
  22. var scroll_top = getter.call(this);
  23. return scroll_top;
  24. });
  25. })();
  26. */
  27. /* testing in Chrome or FF
  28. if (document.createElementNS &&
  29. document.createElement('div').namespaceURI != 'http://www.w3.org/1999/xhtml')
  30. {
  31. Document.prototype.createElement = document.createElement = function(name)
  32. {
  33. return this.createElementNS('http://www.w3.org/1999/xhtml', name);
  34. };
  35. }
  36. */
  37. if (!Element.prototype.contains)
  38. {
  39. Element.prototype.contains = function(ele)
  40. {
  41. while (ele && ele != this)
  42. ele = ele.parentNode;
  43. return Boolean(ele);
  44. }
  45. }
  46. if (!Element.prototype.insertAdjacentHTML)
  47. {
  48. Element.prototype.insertAdjacentHTML = function(position, markup)
  49. {
  50. if (position == 'beforeend')
  51. {
  52. var div = this.appendChild(document.createElement('div'));
  53. div.innerHTML = markup;
  54. var range = document.createRange();
  55. range.selectNodeContents(div);
  56. this.replaceChild(range.extractContents(), div);
  57. return this.firstElementChild;
  58. }
  59. }
  60. }
  61. if (typeof document.createElement('div').classList == 'undefined')
  62. {
  63. Element.prototype.__defineGetter__('classList', function()
  64. {
  65. return this.className.split(/\s+/);
  66. });
  67. Element.prototype.__defineSetter__('classList', function(){});
  68. }
  69. /**
  70. * @fileoverview
  71. * Helper function prototypes related to DOM objects and the DOM
  72. * <strong>fixme: Christian should document the template syntax</strong>
  73. *
  74. * TEMPLATE :: =
  75. * "[" [NODENAME | "null"]
  76. * {"," TEXT | "," TEMPLATE}
  77. * {"," KEY "," VALUE}
  78. * "]"
  79. *
  80. * where NODENAME, TEXT and KEY are DOM strings and VALUE can be everything except an array
  81. */
  82. Element.prototype.render = Document.prototype.render = function(args, namespace)
  83. {
  84. var args_is_string = typeof args == 'string';
  85. if (this.nodeType == 1 && args_is_string ||
  86. (args.length == 1 && typeof args[0] == 'string' && /</.test(args[0])))
  87. {
  88. this.insertAdjacentHTML('beforeend', args_is_string ? args : args[0]);
  89. return this.firstElementChild;
  90. }
  91. var
  92. doc = this.nodeType == 9 ? this : this.ownerDocument,
  93. i = 0,
  94. ele = this,
  95. first_arg = args[0],
  96. arg = null,
  97. prefix_pos = -1;
  98. if (args.length)
  99. {
  100. if (first_arg)
  101. {
  102. if (typeof first_arg == 'string')
  103. {
  104. if ((prefix_pos = first_arg.indexOf(':')) != -1)
  105. {
  106. namespace = doc.lookupNamespaceURI(first_arg.slice(0, prefix_pos));
  107. if (!namespace)
  108. {
  109. throw('namespace not defined in call Node.prototype.___add')
  110. }
  111. ele = doc.createElementNS(namespace, first_arg.slice(prefix_pos + 1));
  112. }
  113. else if (namespace)
  114. ele = doc.createElementNS(namespace, first_arg.slice(prefix_pos + 1));
  115. else
  116. {
  117. ele = first_arg in CustomElements ? CustomElements[first_arg].create() : doc.createElement(first_arg);
  118. }
  119. i++;
  120. }
  121. arg = args[i];
  122. while (true)
  123. {
  124. if (arg instanceof Array)
  125. {
  126. ele.render(arg, namespace);
  127. arg = args[++i];
  128. }
  129. else if (typeof arg == 'string' && ((args.length - i) % 2 || args[i + 1] instanceof Array))
  130. {
  131. ele.appendChild(doc.createTextNode(arg));
  132. arg = args[++i];
  133. }
  134. else
  135. {
  136. break;
  137. }
  138. }
  139. for ( ; args[i]; i += 2)
  140. {
  141. if (typeof args[i] != 'string')
  142. {
  143. throw "TemplateSyntaxError, expected 'string', got " +
  144. (typeof args[i]) + " for TEXT or KEY";
  145. }
  146. if (typeof args[i + 1] == 'string')
  147. {
  148. ele.setAttribute(args[i], args[i + 1]);
  149. }
  150. else
  151. {
  152. ele[args[i]] = args[i + 1];
  153. }
  154. }
  155. if (this.nodeType == 1 && (this != ele))
  156. {
  157. this.appendChild(ele);
  158. }
  159. return ele;
  160. }
  161. else
  162. {
  163. return this.appendChild(doc.createTextNode(args[1]));
  164. }
  165. }
  166. return null;
  167. };
  168. Element.prototype.re_render = function(args)
  169. {
  170. var parent = this.parentNode, ret = [];
  171. if (parent)
  172. {
  173. var div = document.createElement('div');
  174. var doc_frag = document.createDocumentFragment();
  175. div.render(args);
  176. while (div.firstChild)
  177. {
  178. ret.push(doc_frag.appendChild(div.firstChild));
  179. }
  180. parent.replaceChild(doc_frag, this);
  181. return ret;
  182. }
  183. return null;
  184. }
  185. /**
  186. * Clear the element and render the template into it
  187. */
  188. Element.prototype.clearAndRender = function(template)
  189. {
  190. this.innerHTML = '';
  191. return this.render(template);
  192. };
  193. /**
  194. * Add the css class "name" to the element's list of classes
  195. * fixme: Does not work with dashes in the name!
  196. * Note: Uses get/setAttribute instead of .className so it will
  197. * work on both html and svg elements
  198. */
  199. Element.prototype.addClass = function(name)
  200. {
  201. var c = this.getAttribute("class");
  202. if (!(new RegExp('\\b' + name + '\\b')).test(c))
  203. {
  204. this.setAttribute("class", (c ? c + ' ' : '') + name);
  205. }
  206. return this;
  207. };
  208. /**
  209. * Remove the css class "name" from the elements list of classes
  210. * Note: Uses get/setAttribute instead of .className so it will
  211. * work on both html and svg elements
  212. */
  213. Element.prototype.removeClass = function(name)
  214. {
  215. var c = this.getAttribute("class");
  216. var re = new RegExp(name + ' ?| ?' + name);
  217. if (re.test(c))
  218. {
  219. this.setAttribute("class", c.replace(re, ''));
  220. }
  221. return this;
  222. };
  223. /**
  224. * Check if the element has the class "name" set
  225. */
  226. Element.prototype.hasClass = function(name)
  227. {
  228. return (new RegExp('(?:^| +)' + name + '(?: +|$)')).test(this.className)
  229. };
  230. /**
  231. * Swap class "from" with class "to"
  232. */
  233. Element.prototype.swapClass = function(from, to)
  234. {
  235. if (this.hasClass(from))
  236. {
  237. this.removeClass(from);
  238. this.addClass(to);
  239. }
  240. };
  241. /**
  242. * Insert node after target in the tree.
  243. */
  244. Element.prototype.insertAfter = function(node, target)
  245. {
  246. var nextElement = target.nextSibling;
  247. while (nextElement && nextElement.nodeType != 1)
  248. {
  249. nextElement = nextElement.nextSibling;
  250. }
  251. if (nextElement)
  252. {
  253. this.insertBefore(node, nextElement);
  254. }
  255. else
  256. {
  257. this.appendChild(node);
  258. }
  259. return node;
  260. };
  261. /**
  262. * Dispatches an event on the element with the name "name" and and properties
  263. * that are set in the "custom_props" object
  264. */
  265. Element.prototype.releaseEvent = function(name, custom_props)
  266. {
  267. var event = document.createEvent('Events'), prop = '';
  268. event.initEvent(name, true, true);
  269. if (custom_props)
  270. {
  271. for (prop in custom_props)
  272. {
  273. event[prop] = custom_props[prop];
  274. }
  275. }
  276. this.dispatchEvent(event);
  277. };
  278. Element.prototype.dispatchMouseEvent = function(type, ctrl_key, alt_key, shift_key)
  279. {
  280. var event = document.createEvent('MouseEvents');
  281. var box = this.getBoundingClientRect();
  282. var client_x = box.left + box.width * .5;
  283. var client_y = box.top + box.height * .5;
  284. event.initMouseEvent(type, true, true, window, 1,
  285. window.screenLeft + client_x,
  286. window.screenTop + client_y,
  287. client_x, client_y,
  288. ctrl_key, alt_key, shift_key, false,
  289. 0, null);
  290. this.dispatchEvent(event);
  291. };
  292. Element.prototype.get_scroll_container = function()
  293. {
  294. var scroll_container = this;
  295. while (scroll_container &&
  296. scroll_container.scrollHeight <= scroll_container.offsetHeight)
  297. scroll_container = scroll_container.parentNode;
  298. return (scroll_container == document.documentElement ||
  299. scroll_container == document) ? null : scroll_container;
  300. }
  301. /**
  302. * A class to store a scroll position and reset it later in an asynchronous
  303. * environment.
  304. * The class takes a target as initialisation argument.
  305. * The scroll position is stored for the first scroll container
  306. * in the parent node chain of that target. The root element is
  307. * disregarded as scroll container (this is a bit too Dragonfly specific.
  308. * Better would be to check the overflow property of the computed style to
  309. * find a real scroll container).
  310. * Resetting the scroll position can be done with or without argument.
  311. * Without argument. it resets the scrollTop and scrollLeft properties
  312. * of the scroll container to the stored values. With a target argument,
  313. * it scroll the target in the exact same position as the target of
  314. * the initialisation.
  315. */
  316. Element.ScrollPosition = function(target)
  317. {
  318. this._scroll_container = target.get_scroll_container();
  319. this._scroll_top = 0;
  320. this._scroll_left = 0;
  321. this._delta_top = 0;
  322. this._delta_left = 0;
  323. if (this._scroll_container)
  324. {
  325. this._scroll_top = this._scroll_container.scrollTop;
  326. this._scroll_left = this._scroll_container.scrollLeft;
  327. var target_box = target.getBoundingClientRect();
  328. var scroll_box = this._scroll_container.getBoundingClientRect();
  329. this._container_top = scroll_box.top;
  330. this._container_left = scroll_box.left;
  331. this._delta_top = target_box.top - scroll_box.top;
  332. this._delta_left = target_box.left - scroll_box.left;
  333. }
  334. }
  335. /**
  336. * To reset the scroll position.
  337. * Without target, scrollTop and scrollleft are restored to
  338. * the initialisation values.
  339. * If target is set, the target is scrolled in the exact same position
  340. * as the target of the initialisation.
  341. * A secondary container can be specified. This will be used in case the
  342. * initial scroll container was not set in get_scroll_container().
  343. */
  344. Element.ScrollPosition.prototype.reset = function(target, sec_container)
  345. {
  346. if (this._scroll_container)
  347. {
  348. if (target)
  349. {
  350. var target_box = target.getBoundingClientRect();
  351. this._scroll_container.scrollTop -= this._delta_top -
  352. (target_box.top - this._container_top);
  353. this._scroll_container.scrollTop -= this._delta_left -
  354. (target_box.left - this._container_left);
  355. }
  356. else
  357. {
  358. this._scroll_container.scrollTop = this._scroll_top;
  359. this._scroll_container.scrollLeft = this._scroll_left;
  360. }
  361. }
  362. else if (sec_container)
  363. {
  364. var scroll_container = sec_container.get_scroll_container();
  365. if (scroll_container)
  366. {
  367. scroll_container.scrollTop = 0;
  368. scroll_container.scrollLeft = 0;
  369. }
  370. }
  371. }
  372. /**
  373. * Returns the next element for which the function "filter" returns true.
  374. * The filter functions is passed two arguments, the current candidate element
  375. * and the element on which the method was called
  376. */
  377. Element.prototype.getNextWithFilter = function(root_context, filter)
  378. {
  379. var cursor = this;
  380. while ((cursor = cursor.getNextInFlow(root_context)) && !filter(cursor, this));
  381. return cursor;
  382. };
  383. /**
  384. * Same as getNextWithFilter but finds previous element
  385. */
  386. Element.prototype.getPreviousWithFilter = function(root_context, filter)
  387. {
  388. var cursor = this;
  389. while ((cursor = cursor.getPreviousInFlow(root_context)) && !filter(cursor, this));
  390. return cursor;
  391. };
  392. Element.prototype.getNextInFlow = function(root_context)
  393. {
  394. var
  395. next = this.firstElementChild || this.nextElementSibling,
  396. cursor = this;
  397. while (!next && (cursor = cursor.parentNode) && cursor != root_context)
  398. {
  399. next = cursor.nextElementSibling;
  400. }
  401. return next;
  402. };
  403. Element.prototype.getPreviousInFlow = function(root_context)
  404. {
  405. var
  406. previous = this.previousElementSibling,
  407. parent = this.parentNode,
  408. cursor = previous;
  409. while (cursor && cursor.lastElementChild && (cursor = cursor.lastElementChild));
  410. return cursor || previous || parent != root_context && parent || null;
  411. };
  412. /* Check elements of a DOM traversal for an attribute. */
  413. Element.prototype.has_attr = function(traverse_type, name)
  414. {
  415. switch (traverse_type)
  416. {
  417. case "parent-node-chain":
  418. {
  419. var ele = this;
  420. while (ele && ele.nodeType == 1 && !ele.hasAttribute(name))
  421. ele = ele.parentNode;
  422. return ele && ele.nodeType == 1 && ele || null;
  423. break;
  424. }
  425. }
  426. return null;
  427. };
  428. /* Get an attribute of the first hit of a DOM traversal. */
  429. Element.prototype.get_attr = function(traverse_type, name)
  430. {
  431. switch (traverse_type)
  432. {
  433. case "parent-node-chain":
  434. {
  435. var ele = this;
  436. while (ele && ele.nodeType == 1 && !ele.hasAttribute(name))
  437. ele = ele.parentNode;
  438. return ele && ele.nodeType == 1 && ele.hasAttribute(name) ? ele.getAttribute(name) : null;
  439. break;
  440. }
  441. }
  442. return null;
  443. };
  444. if (!Element.prototype.matchesSelector)
  445. {
  446. Element.prototype.matchesSelector =
  447. Element.prototype.oMatchesSelector ?
  448. Element.prototype.oMatchesSelector :
  449. function(selector)
  450. {
  451. var sel = this.parentNode.querySelectorAll(selector);
  452. for (var i = 0; sel[i] && sel[i] != this; i++);
  453. return Boolean(sel[i]);
  454. }
  455. };
  456. /* The naming is not precise, it can return the element itself. */
  457. Element.prototype.get_ancestor = function(selector)
  458. {
  459. var ele = this;
  460. while (ele)
  461. {
  462. if (ele.nodeType == 1 && ele.matchesSelector(selector))
  463. {
  464. return ele;
  465. }
  466. ele = ele.parentNode;
  467. }
  468. return null;
  469. };
  470. /**
  471. * Make sure the element is visible in its scoll context.
  472. * @see Element.prototype.scrollSoftIntoContainerView
  473. */
  474. Element.prototype.scrollSoftIntoView = function()
  475. {
  476. // just checking the first offsetParent to keep it simple
  477. var scrollContainer = this.offsetParent;
  478. var min_top = 20;
  479. if (scrollContainer && scrollContainer.offsetHeight < scrollContainer.scrollHeight)
  480. {
  481. if (this.offsetTop < scrollContainer.scrollTop + min_top)
  482. {
  483. scrollContainer.scrollTop = this.offsetTop - min_top;
  484. }
  485. else if (this.offsetTop + this.offsetHeight > scrollContainer.scrollTop + scrollContainer.offsetHeight - min_top)
  486. {
  487. scrollContainer.scrollTop =
  488. this.offsetTop + this.offsetHeight - scrollContainer.offsetHeight + min_top;
  489. }
  490. }
  491. };
  492. /**
  493. * Make sure the element is visible in the container. The container is the
  494. * first <container> element found in the offsetParent chain, or body if no
  495. * container element is found.
  496. */
  497. Element.prototype.scrollSoftIntoContainerView = function()
  498. {
  499. var scrollContainer = this.offsetParent;
  500. while (scrollContainer && scrollContainer != document.body &&
  501. scrollContainer.nodeName.toLowerCase() != "container")
  502. {
  503. scrollContainer = scrollContainer.offsetParent;
  504. }
  505. var min_top = 20;
  506. if (scrollContainer && scrollContainer.offsetHeight < scrollContainer.scrollHeight)
  507. {
  508. if (this.offsetTop < scrollContainer.scrollTop + min_top)
  509. {
  510. scrollContainer.scrollTop = this.offsetTop - min_top;
  511. }
  512. else if (this.offsetTop + this.offsetHeight > scrollContainer.scrollTop + scrollContainer.offsetHeight - min_top)
  513. {
  514. scrollContainer.scrollTop =
  515. this.offsetTop + this.offsetHeight - scrollContainer.offsetHeight + min_top;
  516. }
  517. }
  518. };
  519. Element.prototype.hasTextNodeChild = function()
  520. {
  521. for (var i = 0, child; child = this.childNodes[i]; i++)
  522. {
  523. if (child.nodeType == document.TEXT_NODE)
  524. {
  525. return true;
  526. }
  527. }
  528. return false;
  529. }
  530. /**
  531. * Get the text content of the first node in Node with the name nodeName
  532. * Escapes opening angle brackets into less than entities. If node is not
  533. * found, returns null
  534. * @argument nodeName {string} node name
  535. * @returns {Element}
  536. */
  537. Node.prototype.getNodeData = function(nodeName)
  538. {
  539. var node = this.getElementsByTagName(nodeName)[0];
  540. if (node)
  541. {
  542. return node.textContent.replace(/</g, '&lt;');
  543. }
  544. return null;
  545. };
  546. /**
  547. * Returns the index of item in the nodelist
  548. * (The same behaviour as js1.6 array.indexOf)
  549. * @argument item {Element}
  550. */
  551. NodeList.prototype.indexOf = function(item)
  552. {
  553. for (var cursor = null, i = 0; cursor = this[i]; i++)
  554. {
  555. if (cursor == item)
  556. {
  557. return i;
  558. }
  559. }
  560. return -1;
  561. };
  562. /**
  563. * Return the sum of all the values in the array. If selectorfun is given,
  564. * it will be called to retrieve the relevant value for each item in the
  565. * array.
  566. */
  567. Array.prototype.sum = function(selectorfun)
  568. {
  569. if (selectorfun)
  570. {
  571. return this.map(selectorfun).sum();
  572. }
  573. else
  574. {
  575. var ret = 0;
  576. this.forEach(function(e) { ret += e });
  577. return ret
  578. }
  579. };
  580. Array.prototype.unique = function()
  581. {
  582. var ret = [];
  583. this.forEach(function(e) { if (ret.indexOf(e) == -1) {ret.push(e) }});
  584. return ret;
  585. }
  586. Array.prototype.__defineGetter__("last", function()
  587. {
  588. return this[this.length - 1];
  589. });
  590. Array.prototype.__defineSetter__("last", function() {});
  591. Array.prototype.extend = function(list)
  592. {
  593. this.push.apply(this, list);
  594. return this;
  595. };
  596. Array.prototype.insert = function(index, list, replace_count)
  597. {
  598. this.splice.apply(this, [index, replace_count || 0].extend(list));
  599. return this;
  600. };
  601. StyleSheetList.prototype.getDeclaration = function(selector)
  602. {
  603. var sheet = null, i = 0, j = 0, rules = null, rule = null;
  604. for ( ; sheet = this[i]; i++)
  605. {
  606. rules = sheet.cssRules;
  607. // does not take into account import rules
  608. for (j = 0; (rule = rules[j]) && !(rule.type == 1 && rule.selectorText == selector); j++);
  609. if (rule)
  610. {
  611. return rule.style;
  612. }
  613. }
  614. return null;
  615. };
  616. StyleSheetList.prototype.getPropertyValue = function(selector, property)
  617. {
  618. var style = this.getDeclaration(selector);
  619. return style && style.getPropertyValue(property) || '';
  620. };
  621. if (!(function(){}).bind)
  622. {
  623. Function.prototype.bind = function (context)
  624. {
  625. var method = this, args = Array.prototype.slice.call(arguments, 1);
  626. return function()
  627. {
  628. return method.apply(context, args.concat(Array.prototype.slice.call(arguments)));
  629. }
  630. };
  631. };
  632. if (!"".trim)
  633. {
  634. String.prototype.trim = function()
  635. {
  636. return this.replace(/^\s+/, '').replace(/\s+$/, '');
  637. }
  638. }
  639. /**
  640. * Check if a string appears to be a number, that is, all letters in the
  641. * string are numbers. Does not take in to account decimals. Clones the
  642. * behaviour of str.isdigit in python
  643. */
  644. String.prototype.isdigit = function()
  645. {
  646. return this.length && !(/\D/.test(this));
  647. };
  648. Array.prototype.contains = String.prototype.contains = function(str)
  649. {
  650. return this.indexOf(str) != -1;
  651. };
  652. String.prototype.startswith = function(str)
  653. {
  654. return this.slice(0, str.length) === str;
  655. };
  656. String.prototype.endswith = function(str)
  657. {
  658. return this.slice(this.length - str.length) === str;
  659. };
  660. String.prototype.zfill = function(width)
  661. {
  662. return this.replace(/(^[+-]?)(.+)/, function(str, sign, rest) {
  663. var fill = Array(Math.max(width - str.length + 1, 0)).join(0);
  664. return sign + fill + rest;
  665. });
  666. };
  667. String.prototype.ljust = function(width, char)
  668. {
  669. return this + Array(Math.max(width - this.length + 1, 0)).join(char || ' ');
  670. };
  671. /**
  672. * Capitalizes the first character of the string. Lowercases the rest of
  673. * the characters, unless `only_first` is true.
  674. */
  675. String.prototype.capitalize = function(only_first)
  676. {
  677. var rest = this.slice(1);
  678. if (!only_first)
  679. {
  680. rest = rest.toLowerCase();
  681. }
  682. return this[0].toUpperCase() + rest;
  683. };
  684. /**
  685. * Local ISO strings, currently needed as datetime-local input values
  686. * http://dev.w3.org/html5/markup/input.datetime-local.html#input.datetime-local.attrs.value
  687. */
  688. Date.prototype.toLocaleISOString = function()
  689. {
  690. return new Date(this.getTime() - this.getTimezoneOffset() * 1000 * 60).toISOString().replace('Z','');
  691. };
  692. /**
  693. * Convenience function for loading a resource with XHR using the get method.
  694. * Will automatically append a "time" guery argument to avoid caching.
  695. * When the load is finished, callback will be invoced with context as its
  696. * "this" value
  697. */
  698. XMLHttpRequest.prototype.loadResource = function(url, callback, context)
  699. {
  700. this.onload = function()
  701. {
  702. callback(this, context);
  703. }
  704. this.open('GET', url);
  705. this.send(null);
  706. };
  707. window.CustomElements = new function()
  708. {
  709. this._init_queue = [];
  710. this._init_listener = function(event)
  711. {
  712. var
  713. queue = CustomElements._init_queue,
  714. wait_list = [],
  715. item = null,
  716. i = 0,
  717. target = event.target;
  718. for ( ; item = queue[i]; i++)
  719. {
  720. if (target.contains(item.ele))
  721. {
  722. CustomElements[item.type].init(item.ele);
  723. }
  724. else
  725. {
  726. wait_list.push(item);
  727. }
  728. }
  729. CustomElements._init_queue = wait_list;
  730. if (!wait_list.length)
  731. {
  732. document.removeEventListener('DOMNodeInserted', CustomElements._init_listener, false);
  733. }
  734. }
  735. this.add = function(CustomElementClass)
  736. {
  737. CustomElementClass.prototype = this.Base;
  738. var custom_element = new CustomElementClass(), feature = '', i = 1;
  739. if (custom_element.type)
  740. {
  741. for ( ; feature = arguments[i]; i++)
  742. {
  743. if (feature in this)
  744. {
  745. this[feature].apply(custom_element);
  746. }
  747. }
  748. this[custom_element.type] = custom_element;
  749. }
  750. }
  751. };
  752. window.CustomElements.Base = new function()
  753. {
  754. this.create = function()
  755. {
  756. var ele = document.createElement(this.html_name);
  757. if (!CustomElements._init_queue.length)
  758. {
  759. document.addEventListener('DOMNodeInserted', CustomElements._init_listener, false);
  760. }
  761. CustomElements._init_queue.push(
  762. {
  763. ele: ele,
  764. type: this.type
  765. });
  766. return ele;
  767. }
  768. this.init = function(ele)
  769. {
  770. if (this._inits)
  771. {
  772. for (var init = null, i = 0; init = this._inits[i]; i++)
  773. {
  774. init.call(this, ele);
  775. }
  776. }
  777. }
  778. };
  779. window.CustomElements.PlaceholderFeature = function()
  780. {
  781. this.set_placeholder = function()
  782. {
  783. var placeholder = this.getAttribute('data-placeholder');
  784. if (!this.value && placeholder)
  785. {
  786. this.value = placeholder;
  787. this.addClass('placeholder');
  788. }
  789. }
  790. this.clear_placeholder = function()
  791. {
  792. if (this.hasClass('placeholder'))
  793. {
  794. this.removeClass('placeholder');
  795. this.value = '';
  796. }
  797. }
  798. this.get_value = function()
  799. {
  800. return this.hasClass('placeholder') ? '' : this._get_value();
  801. };
  802. (this._inits || (this._inits = [])).push(function(ele)
  803. {
  804. if (!ele._get_value)
  805. {
  806. var _interface = ele.toString().slice(8).replace(']', '');
  807. window[_interface].prototype._get_value = ele.__lookupGetter__('value');
  808. window[_interface].prototype._set_value = ele.__lookupSetter__('value');
  809. }
  810. ele.__defineSetter__('value', ele._set_value);
  811. ele.__defineGetter__('value', this.get_value);
  812. this.set_placeholder.call(ele);
  813. ele.addEventListener('focus', this.clear_placeholder, false);
  814. ele.addEventListener('blur', this.set_placeholder, false);
  815. });
  816. };
  817. window.CustomElements.AutoScrollHeightFeature = function()
  818. {
  819. this.adjust_height = function()
  820. {
  821. if (this.scrollHeight != this.offsetHeight)
  822. {
  823. // TODO values should not be hardcoded
  824. this.style.height = (4 + (this.scrollHeight > 16 ? this.scrollHeight : 16)) + 'px';
  825. }
  826. };
  827. this._get_adjust_height = function(count_lines, line_height, border_padding)
  828. {
  829. var lines = -1;
  830. return function()
  831. {
  832. var new_count = count_lines(this.value);
  833. if (new_count != lines)
  834. {
  835. lines = new_count;
  836. this.style.height = (border_padding + (lines) * line_height) + 'px';
  837. }
  838. }
  839. }
  840. this._count_lines = (function(re)
  841. {
  842. return function(str)
  843. {
  844. for (var count = 1; re.exec(str); count++);
  845. return count;
  846. };
  847. })(/\r\n/g);
  848. this._get_line_height = function(textarea)
  849. {
  850. // computed style returns by default just "normal"
  851. var
  852. CRLF = "\r\n",
  853. offset_height = textarea.offsetHeight,
  854. textarea_value = textarea._get_value(),
  855. line_height = 0,
  856. test_value = "\r\n\r\n\r\n\r\n\r\n\r\n";
  857. textarea.value = test_value;
  858. while (textarea.scrollHeight < offset_height)
  859. {
  860. textarea.value = (test_value += CRLF);
  861. }
  862. line_height = textarea.scrollHeight;
  863. textarea.value = (test_value += CRLF);
  864. line_height = textarea.scrollHeight - line_height;
  865. textarea.value = textarea_value;
  866. return line_height;
  867. };
  868. this._get_border_padding = function(ele)
  869. {
  870. var
  871. border_padding = 0,
  872. style_dec = window.getComputedStyle(ele, null);
  873. if (style_dec.getPropertyValue('box-sizing') == 'border-box')
  874. {
  875. ['padding-top', 'padding-bottom', 'border-top', 'border-bottom'].forEach(function(prop)
  876. {
  877. border_padding += parseInt(style_dec.getPropertyValue(prop)) || 0;
  878. })
  879. };
  880. return border_padding;
  881. };
  882. (this._inits || (this._inits = [])).push(function(ele)
  883. {
  884. var adjust_height = this._get_adjust_height(this._count_lines,
  885. this._get_line_height(ele), this._get_border_padding(ele));
  886. adjust_height.call(ele);
  887. ele.addEventListener('input', adjust_height, false);
  888. });
  889. };
  890. CustomElements.add(function()
  891. {
  892. this.type = '_html5_input';
  893. this.html_name = 'input';
  894. },
  895. 'PlaceholderFeature');
  896. CustomElements.add(function()
  897. {
  898. this.type = '_html5_textarea';
  899. this.html_name = 'textarea';
  900. },
  901. 'PlaceholderFeature');
  902. CustomElements.add(function()
  903. {
  904. this.type = '_auto_height_textarea';
  905. this.html_name = 'textarea';
  906. },
  907. 'PlaceholderFeature',
  908. 'AutoScrollHeightFeature');