element.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  1. import { isIE8, isIE9, isSafari } from '../browser';
  2. import { hasCaptionProblem } from '../feature';
  3. /**
  4. * Get the parent of the specified node in the DOM tree.
  5. *
  6. * @param {HTMLElement} element Element from which traversing is started.
  7. * @param {Number} [level=0] Traversing deep level.
  8. * @return {HTMLElement|null}
  9. */
  10. export function getParent(element) {
  11. var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  12. var iteration = -1;
  13. var parent = null;
  14. while (element != null) {
  15. if (iteration === level) {
  16. parent = element;
  17. break;
  18. }
  19. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  20. element = element.host;
  21. } else {
  22. iteration++;
  23. element = element.parentNode;
  24. }
  25. }
  26. return parent;
  27. }
  28. /**
  29. * Goes up the DOM tree (including given element) until it finds an element that matches the nodes or nodes name.
  30. * This method goes up through web components.
  31. *
  32. * @param {HTMLElement} element Element from which traversing is started
  33. * @param {Array} nodes Array of elements or Array of elements name
  34. * @param {HTMLElement} [until]
  35. * @returns {HTMLElement|null}
  36. */
  37. export function closest(element, nodes, until) {
  38. while (element != null && element !== until) {
  39. if (element.nodeType === Node.ELEMENT_NODE && (nodes.indexOf(element.nodeName) > -1 || nodes.indexOf(element) > -1)) {
  40. return element;
  41. }
  42. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  43. element = element.host;
  44. } else {
  45. element = element.parentNode;
  46. }
  47. }
  48. return null;
  49. }
  50. /**
  51. * Goes "down" the DOM tree (including given element) until it finds an element that matches the nodes or nodes name.
  52. *
  53. * @param {HTMLElement} element Element from which traversing is started
  54. * @param {Array} nodes Array of elements or Array of elements name
  55. * @param {HTMLElement} [until]
  56. * @returns {HTMLElement|null}
  57. */
  58. export function closestDown(element, nodes, until) {
  59. var matched = [];
  60. while (element) {
  61. element = closest(element, nodes, until);
  62. if (!element || until && !until.contains(element)) {
  63. break;
  64. }
  65. matched.push(element);
  66. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  67. element = element.host;
  68. } else {
  69. element = element.parentNode;
  70. }
  71. }
  72. var length = matched.length;
  73. return length ? matched[length - 1] : null;
  74. }
  75. /**
  76. * Goes up the DOM tree and checks if element is child of another element.
  77. *
  78. * @param child Child element
  79. * @param {Object|String} parent Parent element OR selector of the parent element.
  80. * If string provided, function returns `true` for the first occurrence of element with that class.
  81. * @returns {Boolean}
  82. */
  83. export function isChildOf(child, parent) {
  84. var node = child.parentNode;
  85. var queriedParents = [];
  86. if (typeof parent === 'string') {
  87. queriedParents = Array.prototype.slice.call(document.querySelectorAll(parent), 0);
  88. } else {
  89. queriedParents.push(parent);
  90. }
  91. while (node != null) {
  92. if (queriedParents.indexOf(node) > -1) {
  93. return true;
  94. }
  95. node = node.parentNode;
  96. }
  97. return false;
  98. }
  99. /**
  100. * Check if an element is part of `hot-table` web component.
  101. *
  102. * @param {Element} element
  103. * @returns {Boolean}
  104. */
  105. export function isChildOfWebComponentTable(element) {
  106. var hotTableName = 'hot-table',
  107. result = false,
  108. parentNode;
  109. parentNode = polymerWrap(element);
  110. function isHotTable(element) {
  111. return element.nodeType === Node.ELEMENT_NODE && element.nodeName === hotTableName.toUpperCase();
  112. }
  113. while (parentNode != null) {
  114. if (isHotTable(parentNode)) {
  115. result = true;
  116. break;
  117. } else if (parentNode.host && parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  118. result = isHotTable(parentNode.host);
  119. if (result) {
  120. break;
  121. }
  122. parentNode = parentNode.host;
  123. }
  124. parentNode = parentNode.parentNode;
  125. }
  126. return result;
  127. }
  128. /**
  129. * Wrap element into polymer/webcomponent container if exists
  130. *
  131. * @param element
  132. * @returns {*}
  133. */
  134. export function polymerWrap(element) {
  135. /* global Polymer */
  136. return typeof Polymer !== 'undefined' && typeof wrap === 'function' ? wrap(element) : element;
  137. }
  138. /**
  139. * Unwrap element from polymer/webcomponent container if exists
  140. *
  141. * @param element
  142. * @returns {*}
  143. */
  144. export function polymerUnwrap(element) {
  145. /* global Polymer */
  146. return typeof Polymer !== 'undefined' && typeof unwrap === 'function' ? unwrap(element) : element;
  147. }
  148. /**
  149. * Counts index of element within its parent
  150. * WARNING: for performance reasons, assumes there are only element nodes (no text nodes). This is true for Walkotnable
  151. * Otherwise would need to check for nodeType or use previousElementSibling
  152. *
  153. * @see http://jsperf.com/sibling-index/10
  154. * @param {Element} element
  155. * @return {Number}
  156. */
  157. export function index(element) {
  158. var i = 0;
  159. if (element.previousSibling) {
  160. /* eslint-disable no-cond-assign */
  161. while (element = element.previousSibling) {
  162. ++i;
  163. }
  164. }
  165. return i;
  166. }
  167. /**
  168. * Check if the provided overlay contains the provided element
  169. *
  170. * @param {String} overlay
  171. * @param {HTMLElement} element
  172. * @returns {boolean}
  173. */
  174. export function overlayContainsElement(overlayType, element) {
  175. var overlayElement = document.querySelector('.ht_clone_' + overlayType);
  176. return overlayElement ? overlayElement.contains(element) : null;
  177. }
  178. var classListSupport = !!document.documentElement.classList;
  179. var _hasClass, _addClass, _removeClass;
  180. function filterEmptyClassNames(classNames) {
  181. var len = 0,
  182. result = [];
  183. if (!classNames || !classNames.length) {
  184. return result;
  185. }
  186. while (classNames[len]) {
  187. result.push(classNames[len]);
  188. len++;
  189. }
  190. return result;
  191. }
  192. if (classListSupport) {
  193. var isSupportMultipleClassesArg = function () {
  194. var element = document.createElement('div');
  195. element.classList.add('test', 'test2');
  196. return element.classList.contains('test2');
  197. }();
  198. _hasClass = function _hasClass(element, className) {
  199. if (className === '') {
  200. return false;
  201. }
  202. return element.classList.contains(className);
  203. };
  204. _addClass = function _addClass(element, className) {
  205. var len = 0;
  206. if (typeof className === 'string') {
  207. className = className.split(' ');
  208. }
  209. className = filterEmptyClassNames(className);
  210. if (isSupportMultipleClassesArg) {
  211. element.classList.add.apply(element.classList, className);
  212. } else {
  213. while (className && className[len]) {
  214. element.classList.add(className[len]);
  215. len++;
  216. }
  217. }
  218. };
  219. _removeClass = function _removeClass(element, className) {
  220. var len = 0;
  221. if (typeof className === 'string') {
  222. className = className.split(' ');
  223. }
  224. className = filterEmptyClassNames(className);
  225. if (isSupportMultipleClassesArg) {
  226. element.classList.remove.apply(element.classList, className);
  227. } else {
  228. while (className && className[len]) {
  229. element.classList.remove(className[len]);
  230. len++;
  231. }
  232. }
  233. };
  234. } else {
  235. var createClassNameRegExp = function createClassNameRegExp(className) {
  236. return new RegExp('(\\s|^)' + className + '(\\s|$)');
  237. };
  238. _hasClass = function _hasClass(element, className) {
  239. // http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  240. return !!element.className.match(createClassNameRegExp(className));
  241. };
  242. _addClass = function _addClass(element, className) {
  243. var len = 0,
  244. _className = element.className;
  245. if (typeof className === 'string') {
  246. className = className.split(' ');
  247. }
  248. if (_className === '') {
  249. _className = className.join(' ');
  250. } else {
  251. while (className && className[len]) {
  252. if (!createClassNameRegExp(className[len]).test(_className)) {
  253. _className += ' ' + className[len];
  254. }
  255. len++;
  256. }
  257. }
  258. element.className = _className;
  259. };
  260. _removeClass = function _removeClass(element, className) {
  261. var len = 0,
  262. _className = element.className;
  263. if (typeof className === 'string') {
  264. className = className.split(' ');
  265. }
  266. while (className && className[len]) {
  267. // String.prototype.trim is defined in polyfill.js
  268. _className = _className.replace(createClassNameRegExp(className[len]), ' ').trim();
  269. len++;
  270. }
  271. if (element.className !== _className) {
  272. element.className = _className;
  273. }
  274. };
  275. }
  276. /**
  277. * Checks if element has class name
  278. *
  279. * @param {HTMLElement} element
  280. * @param {String} className Class name to check
  281. * @returns {Boolean}
  282. */
  283. export function hasClass(element, className) {
  284. return _hasClass(element, className);
  285. }
  286. /**
  287. * Add class name to an element
  288. *
  289. * @param {HTMLElement} element
  290. * @param {String|Array} className Class name as string or array of strings
  291. */
  292. export function addClass(element, className) {
  293. return _addClass(element, className);
  294. }
  295. /**
  296. * Remove class name from an element
  297. *
  298. * @param {HTMLElement} element
  299. * @param {String|Array} className Class name as string or array of strings
  300. */
  301. export function removeClass(element, className) {
  302. return _removeClass(element, className);
  303. }
  304. export function removeTextNodes(element, parent) {
  305. if (element.nodeType === 3) {
  306. parent.removeChild(element); // bye text nodes!
  307. } else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(element.nodeName) > -1) {
  308. var childs = element.childNodes;
  309. for (var i = childs.length - 1; i >= 0; i--) {
  310. removeTextNodes(childs[i], element);
  311. }
  312. }
  313. }
  314. /**
  315. * Remove childs function
  316. * WARNING - this doesn't unload events and data attached by jQuery
  317. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
  318. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/11 - no siginificant improvement with Chrome remove() method
  319. *
  320. * @param element
  321. * @returns {void}
  322. */
  323. //
  324. export function empty(element) {
  325. var child;
  326. /* eslint-disable no-cond-assign */
  327. while (child = element.lastChild) {
  328. element.removeChild(child);
  329. }
  330. }
  331. export var HTML_CHARACTERS = /(<(.*)>|&(.*);)/;
  332. /**
  333. * Insert content into element trying avoid innerHTML method.
  334. * @return {void}
  335. */
  336. export function fastInnerHTML(element, content) {
  337. if (HTML_CHARACTERS.test(content)) {
  338. element.innerHTML = content;
  339. } else {
  340. fastInnerText(element, content);
  341. }
  342. }
  343. /**
  344. * Insert text content into element
  345. * @return {void}
  346. */
  347. var textContextSupport = !!document.createTextNode('test').textContent;
  348. export function fastInnerText(element, content) {
  349. var child = element.firstChild;
  350. if (child && child.nodeType === 3 && child.nextSibling === null) {
  351. // fast lane - replace existing text node
  352. if (textContextSupport) {
  353. // http://jsperf.com/replace-text-vs-reuse
  354. child.textContent = content;
  355. } else {
  356. // http://jsperf.com/replace-text-vs-reuse
  357. child.data = content;
  358. }
  359. } else {
  360. // slow lane - empty element and insert a text node
  361. empty(element);
  362. element.appendChild(document.createTextNode(content));
  363. }
  364. }
  365. /**
  366. * Returns true if element is attached to the DOM and visible, false otherwise
  367. * @param elem
  368. * @returns {boolean}
  369. */
  370. export function isVisible(elem) {
  371. var next = elem;
  372. while (polymerUnwrap(next) !== document.documentElement) {
  373. // until <html> reached
  374. if (next === null) {
  375. // parent detached from DOM
  376. return false;
  377. } else if (next.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  378. if (next.host) {
  379. // this is Web Components Shadow DOM
  380. // see: http://w3c.github.io/webcomponents/spec/shadow/#encapsulation
  381. // according to spec, should be if (next.ownerDocument !== window.document), but that doesn't work yet
  382. if (next.host.impl) {
  383. // Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features disabled
  384. return isVisible(next.host.impl);
  385. } else if (next.host) {
  386. // Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features enabled
  387. return isVisible(next.host);
  388. }
  389. throw new Error('Lost in Web Components world');
  390. } else {
  391. return false; // this is a node detached from document in IE8
  392. }
  393. } else if (next.style.display === 'none') {
  394. return false;
  395. }
  396. next = next.parentNode;
  397. }
  398. return true;
  399. }
  400. /**
  401. * Returns elements top and left offset relative to the document. Function is not compatible with jQuery offset.
  402. *
  403. * @param {HTMLElement} elem
  404. * @return {Object} Returns object with `top` and `left` props
  405. */
  406. export function offset(elem) {
  407. var offsetLeft, offsetTop, lastElem, docElem, box;
  408. docElem = document.documentElement;
  409. if (hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  410. // fixes problem with Firefox ignoring <caption> in TABLE offset (see also export outerHeight)
  411. // http://jsperf.com/offset-vs-getboundingclientrect/8
  412. box = elem.getBoundingClientRect();
  413. return {
  414. top: box.top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
  415. left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
  416. };
  417. }
  418. offsetLeft = elem.offsetLeft;
  419. offsetTop = elem.offsetTop;
  420. lastElem = elem;
  421. /* eslint-disable no-cond-assign */
  422. while (elem = elem.offsetParent) {
  423. // from my observation, document.body always has scrollLeft/scrollTop == 0
  424. if (elem === document.body) {
  425. break;
  426. }
  427. offsetLeft += elem.offsetLeft;
  428. offsetTop += elem.offsetTop;
  429. lastElem = elem;
  430. }
  431. // slow - http://jsperf.com/offset-vs-getboundingclientrect/6
  432. if (lastElem && lastElem.style.position === 'fixed') {
  433. // if(lastElem !== document.body) { //faster but does gives false positive in Firefox
  434. offsetLeft += window.pageXOffset || docElem.scrollLeft;
  435. offsetTop += window.pageYOffset || docElem.scrollTop;
  436. }
  437. return {
  438. left: offsetLeft,
  439. top: offsetTop
  440. };
  441. }
  442. /**
  443. * Returns the document's scrollTop property.
  444. *
  445. * @returns {Number}
  446. */
  447. export function getWindowScrollTop() {
  448. var res = window.scrollY;
  449. if (res === void 0) {
  450. // IE8-11
  451. res = document.documentElement.scrollTop;
  452. }
  453. return res;
  454. }
  455. /**
  456. * Returns the document's scrollLeft property.
  457. *
  458. * @returns {Number}
  459. */
  460. export function getWindowScrollLeft() {
  461. var res = window.scrollX;
  462. if (res === void 0) {
  463. // IE8-11
  464. res = document.documentElement.scrollLeft;
  465. }
  466. return res;
  467. }
  468. /**
  469. * Returns the provided element's scrollTop property.
  470. *
  471. * @param element
  472. * @returns {Number}
  473. */
  474. export function getScrollTop(element) {
  475. if (element === window) {
  476. return getWindowScrollTop();
  477. }
  478. return element.scrollTop;
  479. }
  480. /**
  481. * Returns the provided element's scrollLeft property.
  482. *
  483. * @param element
  484. * @returns {Number}
  485. */
  486. export function getScrollLeft(element) {
  487. if (element === window) {
  488. return getWindowScrollLeft();
  489. }
  490. return element.scrollLeft;
  491. }
  492. /**
  493. * Returns a DOM element responsible for scrolling of the provided element.
  494. *
  495. * @param {HTMLElement} element
  496. * @returns {HTMLElement} Element's scrollable parent
  497. */
  498. export function getScrollableElement(element) {
  499. var el = element.parentNode,
  500. props = ['auto', 'scroll'],
  501. overflow,
  502. overflowX,
  503. overflowY,
  504. computedStyle = '',
  505. computedOverflow = '',
  506. computedOverflowY = '',
  507. computedOverflowX = '';
  508. while (el && el.style && document.body !== el) {
  509. overflow = el.style.overflow;
  510. overflowX = el.style.overflowX;
  511. overflowY = el.style.overflowY;
  512. if (overflow == 'scroll' || overflowX == 'scroll' || overflowY == 'scroll') {
  513. return el;
  514. } else if (window.getComputedStyle) {
  515. computedStyle = window.getComputedStyle(el);
  516. computedOverflow = computedStyle.getPropertyValue('overflow');
  517. computedOverflowY = computedStyle.getPropertyValue('overflow-y');
  518. computedOverflowX = computedStyle.getPropertyValue('overflow-x');
  519. if (computedOverflow === 'scroll' || computedOverflowX === 'scroll' || computedOverflowY === 'scroll') {
  520. return el;
  521. }
  522. }
  523. if (el.clientHeight <= el.scrollHeight && (props.indexOf(overflowY) !== -1 || props.indexOf(overflow) !== -1 || props.indexOf(computedOverflow) !== -1 || props.indexOf(computedOverflowY) !== -1)) {
  524. return el;
  525. }
  526. if (el.clientWidth <= el.scrollWidth && (props.indexOf(overflowX) !== -1 || props.indexOf(overflow) !== -1 || props.indexOf(computedOverflow) !== -1 || props.indexOf(computedOverflowX) !== -1)) {
  527. return el;
  528. }
  529. el = el.parentNode;
  530. }
  531. return window;
  532. }
  533. /**
  534. * Returns a DOM element responsible for trimming the provided element.
  535. *
  536. * @param {HTMLElement} base Base element
  537. * @returns {HTMLElement} Base element's trimming parent
  538. */
  539. export function getTrimmingContainer(base) {
  540. var el = base.parentNode;
  541. while (el && el.style && document.body !== el) {
  542. if (el.style.overflow !== 'visible' && el.style.overflow !== '') {
  543. return el;
  544. } else if (window.getComputedStyle) {
  545. var computedStyle = window.getComputedStyle(el);
  546. if (computedStyle.getPropertyValue('overflow') !== 'visible' && computedStyle.getPropertyValue('overflow') !== '') {
  547. return el;
  548. }
  549. }
  550. el = el.parentNode;
  551. }
  552. return window;
  553. }
  554. /**
  555. * Returns a style property for the provided element. (Be it an inline or external style).
  556. *
  557. * @param {HTMLElement} element
  558. * @param {String} prop Wanted property
  559. * @returns {String|undefined} Element's style property
  560. */
  561. export function getStyle(element, prop) {
  562. /* eslint-disable */
  563. if (!element) {
  564. return;
  565. } else if (element === window) {
  566. if (prop === 'width') {
  567. return window.innerWidth + 'px';
  568. } else if (prop === 'height') {
  569. return window.innerHeight + 'px';
  570. }
  571. return;
  572. }
  573. var styleProp = element.style[prop],
  574. computedStyle;
  575. if (styleProp !== '' && styleProp !== void 0) {
  576. return styleProp;
  577. } else {
  578. computedStyle = getComputedStyle(element);
  579. if (computedStyle[prop] !== '' && computedStyle[prop] !== void 0) {
  580. return computedStyle[prop];
  581. }
  582. }
  583. }
  584. /**
  585. * Returns a computed style object for the provided element. (Needed if style is declared in external stylesheet).
  586. *
  587. * @param element
  588. * @returns {IEElementStyle|CssStyle} Elements computed style object
  589. */
  590. export function getComputedStyle(element) {
  591. return element.currentStyle || document.defaultView.getComputedStyle(element);
  592. }
  593. /**
  594. * Returns the element's outer width.
  595. *
  596. * @param element
  597. * @returns {number} Element's outer width
  598. */
  599. export function outerWidth(element) {
  600. return element.offsetWidth;
  601. }
  602. /**
  603. * Returns the element's outer height
  604. *
  605. * @param elem
  606. * @returns {number} Element's outer height
  607. */
  608. export function outerHeight(elem) {
  609. if (hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  610. // fixes problem with Firefox ignoring <caption> in TABLE.offsetHeight
  611. // jQuery (1.10.1) still has this unsolved
  612. // may be better to just switch to getBoundingClientRect
  613. // http://bililite.com/blog/2009/03/27/finding-the-size-of-a-table/
  614. // http://lists.w3.org/Archives/Public/www-style/2009Oct/0089.html
  615. // http://bugs.jquery.com/ticket/2196
  616. // http://lists.w3.org/Archives/Public/www-style/2009Oct/0140.html#start140
  617. return elem.offsetHeight + elem.firstChild.offsetHeight;
  618. }
  619. return elem.offsetHeight;
  620. }
  621. /**
  622. * Returns the element's inner height.
  623. *
  624. * @param element
  625. * @returns {number} Element's inner height
  626. */
  627. export function innerHeight(element) {
  628. return element.clientHeight || element.innerHeight;
  629. }
  630. /**
  631. * Returns the element's inner width.
  632. *
  633. * @param element
  634. * @returns {number} Element's inner width
  635. */
  636. export function innerWidth(element) {
  637. return element.clientWidth || element.innerWidth;
  638. }
  639. export function addEvent(element, event, callback) {
  640. if (window.addEventListener) {
  641. element.addEventListener(event, callback, false);
  642. } else {
  643. element.attachEvent('on' + event, callback);
  644. }
  645. }
  646. export function removeEvent(element, event, callback) {
  647. if (window.removeEventListener) {
  648. element.removeEventListener(event, callback, false);
  649. } else {
  650. element.detachEvent('on' + event, callback);
  651. }
  652. }
  653. /**
  654. * Returns caret position in text input
  655. *
  656. * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
  657. * @return {Number}
  658. */
  659. export function getCaretPosition(el) {
  660. if (el.selectionStart) {
  661. return el.selectionStart;
  662. } else if (document.selection) {
  663. // IE8
  664. el.focus();
  665. var r = document.selection.createRange();
  666. if (r == null) {
  667. return 0;
  668. }
  669. var re = el.createTextRange();
  670. var rc = re.duplicate();
  671. re.moveToBookmark(r.getBookmark());
  672. rc.setEndPoint('EndToStart', re);
  673. return rc.text.length;
  674. }
  675. return 0;
  676. }
  677. /**
  678. * Returns end of the selection in text input
  679. *
  680. * @return {Number}
  681. */
  682. export function getSelectionEndPosition(el) {
  683. if (el.selectionEnd) {
  684. return el.selectionEnd;
  685. } else if (document.selection) {
  686. // IE8
  687. var r = document.selection.createRange();
  688. if (r == null) {
  689. return 0;
  690. }
  691. var re = el.createTextRange();
  692. return re.text.indexOf(r.text) + r.text.length;
  693. }
  694. return 0;
  695. }
  696. /**
  697. * Returns text under selection.
  698. *
  699. * @returns {String}
  700. */
  701. export function getSelectionText() {
  702. var text = '';
  703. if (window.getSelection) {
  704. text = window.getSelection().toString();
  705. } else if (document.selection && document.selection.type !== 'Control') {
  706. text = document.selection.createRange().text;
  707. }
  708. return text;
  709. }
  710. /**
  711. * Sets caret position in text input.
  712. *
  713. * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
  714. * @param {Element} element
  715. * @param {Number} pos
  716. * @param {Number} endPos
  717. */
  718. export function setCaretPosition(element, pos, endPos) {
  719. if (endPos === void 0) {
  720. endPos = pos;
  721. }
  722. if (element.setSelectionRange) {
  723. element.focus();
  724. try {
  725. element.setSelectionRange(pos, endPos);
  726. } catch (err) {
  727. var elementParent = element.parentNode;
  728. var parentDisplayValue = elementParent.style.display;
  729. elementParent.style.display = 'block';
  730. element.setSelectionRange(pos, endPos);
  731. elementParent.style.display = parentDisplayValue;
  732. }
  733. } else if (element.createTextRange) {
  734. // IE8
  735. var range = element.createTextRange();
  736. range.collapse(true);
  737. range.moveEnd('character', endPos);
  738. range.moveStart('character', pos);
  739. range.select();
  740. }
  741. }
  742. var cachedScrollbarWidth;
  743. // http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
  744. function walkontableCalculateScrollbarWidth() {
  745. var inner = document.createElement('div');
  746. inner.style.height = '200px';
  747. inner.style.width = '100%';
  748. var outer = document.createElement('div');
  749. outer.style.boxSizing = 'content-box';
  750. outer.style.height = '150px';
  751. outer.style.left = '0px';
  752. outer.style.overflow = 'hidden';
  753. outer.style.position = 'absolute';
  754. outer.style.top = '0px';
  755. outer.style.width = '200px';
  756. outer.style.visibility = 'hidden';
  757. outer.appendChild(inner);
  758. (document.body || document.documentElement).appendChild(outer);
  759. var w1 = inner.offsetWidth;
  760. outer.style.overflow = 'scroll';
  761. var w2 = inner.offsetWidth;
  762. if (w1 == w2) {
  763. w2 = outer.clientWidth;
  764. }
  765. (document.body || document.documentElement).removeChild(outer);
  766. return w1 - w2;
  767. }
  768. /**
  769. * Returns the computed width of the native browser scroll bar.
  770. *
  771. * @return {Number} width
  772. */
  773. export function getScrollbarWidth() {
  774. if (cachedScrollbarWidth === void 0) {
  775. cachedScrollbarWidth = walkontableCalculateScrollbarWidth();
  776. }
  777. return cachedScrollbarWidth;
  778. }
  779. /**
  780. * Checks if the provided element has a vertical scrollbar.
  781. *
  782. * @param {HTMLElement} element
  783. * @returns {Boolean}
  784. */
  785. export function hasVerticalScrollbar(element) {
  786. return element.offsetWidth !== element.clientWidth;
  787. }
  788. /**
  789. * Checks if the provided element has a vertical scrollbar.
  790. *
  791. * @param {HTMLElement} element
  792. * @returns {Boolean}
  793. */
  794. export function hasHorizontalScrollbar(element) {
  795. return element.offsetHeight !== element.clientHeight;
  796. }
  797. /**
  798. * Sets overlay position depending on it's type and used browser
  799. */
  800. export function setOverlayPosition(overlayElem, left, top) {
  801. if (isIE8() || isIE9()) {
  802. overlayElem.style.top = top;
  803. overlayElem.style.left = left;
  804. } else if (isSafari()) {
  805. overlayElem.style['-webkit-transform'] = 'translate3d(' + left + ',' + top + ',0)';
  806. } else {
  807. overlayElem.style.transform = 'translate3d(' + left + ',' + top + ',0)';
  808. }
  809. }
  810. export function getCssTransform(element) {
  811. var transform;
  812. if (element.style.transform && (transform = element.style.transform) !== '') {
  813. return ['transform', transform];
  814. } else if (element.style['-webkit-transform'] && (transform = element.style['-webkit-transform']) !== '') {
  815. return ['-webkit-transform', transform];
  816. }
  817. return -1;
  818. }
  819. export function resetCssTransform(element) {
  820. if (element.style.transform && element.style.transform !== '') {
  821. element.style.transform = '';
  822. } else if (element.style['-webkit-transform'] && element.style['-webkit-transform'] !== '') {
  823. element.style['-webkit-transform'] = '';
  824. }
  825. }
  826. /**
  827. * Determines if the given DOM element is an input field.
  828. * Notice: By 'input' we mean input, textarea and select nodes
  829. *
  830. * @param {HTMLElement} element - DOM element
  831. * @returns {Boolean}
  832. */
  833. export function isInput(element) {
  834. var inputs = ['INPUT', 'SELECT', 'TEXTAREA'];
  835. return element && (inputs.indexOf(element.nodeName) > -1 || element.contentEditable === 'true');
  836. }
  837. /**
  838. * Determines if the given DOM element is an input field placed OUTSIDE of HOT.
  839. * Notice: By 'input' we mean input, textarea and select nodes
  840. *
  841. * @param {HTMLElement} element - DOM element
  842. * @returns {Boolean}
  843. */
  844. export function isOutsideInput(element) {
  845. return isInput(element) && element.className.indexOf('handsontableInput') == -1 && element.className.indexOf('copyPaste') == -1;
  846. }