toolbar.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /**
  6. * @api
  7. */
  8. define([
  9. 'underscore',
  10. 'Magento_Ui/js/lib/view/utils/async',
  11. 'Magento_Ui/js/lib/view/utils/raf',
  12. 'rjsResolver',
  13. 'uiCollection'
  14. ], function (_, $, raf, resolver, Collection) {
  15. 'use strict';
  16. var transformProp;
  17. /**
  18. * Defines supported css 'transform' property.
  19. *
  20. * @returns {String|Undefined}
  21. */
  22. transformProp = (function () {
  23. var style = document.documentElement.style,
  24. base = 'Transform',
  25. vendors = ['webkit', 'moz', 'ms', 'o'],
  26. vi = vendors.length,
  27. property;
  28. if (typeof style.transform != 'undefined') {
  29. return 'transform';
  30. }
  31. while (vi--) {
  32. property = vendors[vi] + base;
  33. if (typeof style[property] != 'undefined') {
  34. return property;
  35. }
  36. }
  37. })();
  38. /**
  39. * Moves specified DOM element to the x and y coordinates.
  40. *
  41. * @param {HTMLElement} elem - Element to be relocated.
  42. * @param {Number} x - X coordinate.
  43. * @param {Number} y - Y coordinate.
  44. */
  45. function locate(elem, x, y) {
  46. var value = 'translate(' + x + 'px,' + y + 'px)';
  47. elem.style[transformProp] = value;
  48. }
  49. return Collection.extend({
  50. defaults: {
  51. template: 'ui/grid/toolbar',
  52. stickyTmpl: 'ui/grid/sticky/sticky',
  53. tableSelector: 'table',
  54. columnsProvider: 'ns = ${ $.ns }, componentType = columns',
  55. refreshFPS: 15,
  56. sticky: false,
  57. visible: false,
  58. _resized: true,
  59. _scrolled: true,
  60. _tableScrolled: true,
  61. _requiredNodes: {
  62. '$stickyToolbar': true,
  63. '$stickyTable': true,
  64. '$table': true,
  65. '$sticky': true
  66. },
  67. stickyClass: {
  68. 'sticky-header': true
  69. }
  70. },
  71. /**
  72. * Initializes sticky toolbar component.
  73. *
  74. * @returns {Sticky} Chainable.
  75. */
  76. initialize: function () {
  77. this._super();
  78. if (this.sticky) {
  79. this.waitDOMElements()
  80. .then(this.run.bind(this));
  81. }
  82. return this;
  83. },
  84. /**
  85. * Establishes DOM elements wait process.
  86. *
  87. * @returns {jQueryPromise} Promise which will be resolved
  88. * when all of the required DOM elements are defined.
  89. */
  90. waitDOMElements: function () {
  91. var _domPromise = $.Deferred();
  92. _.bindAll(this, 'setStickyTable', 'setTableNode');
  93. $.async({
  94. ctx: ':not([data-role="sticky-el-root"])',
  95. component: this.columnsProvider,
  96. selector: this.tableSelector
  97. }, this.setTableNode);
  98. $.async({
  99. ctx: '[data-role="sticky-el-root"]',
  100. component: this.columnsProvider,
  101. selector: this.tableSelector
  102. }, this.setStickyTable);
  103. this._domPromise = _domPromise;
  104. return _domPromise.promise();
  105. },
  106. /**
  107. * Defines left caption element.
  108. *
  109. * @param {HTMLElement} node
  110. */
  111. setLeftCap: function (node) {
  112. this.$leftCap = node;
  113. },
  114. /**
  115. * Defines right caption element.
  116. *
  117. * @param {HTMLElement} node
  118. */
  119. setRightCap: function (node) {
  120. this.$rightCap = node;
  121. },
  122. /**
  123. * Defines original table element.
  124. *
  125. * @param {HTMLTableElement} node
  126. */
  127. setTableNode: function (node) {
  128. this.$cols = node.tHead.children[0].cells;
  129. this.$tableContainer = node.parentNode;
  130. this.setNode('$table', node);
  131. },
  132. /**
  133. * Defines sticky table element.
  134. *
  135. * @param {HTMLTableElement} node
  136. */
  137. setStickyTable: function (node) {
  138. this.$stickyCols = node.tHead.children[0].cells;
  139. this.setNode('$stickyTable', node);
  140. },
  141. /**
  142. * Defines sticky toolbar node.
  143. *
  144. * @param {HTMLElement} node
  145. */
  146. setStickyToolbarNode: function (node) {
  147. this.setNode('$stickyToolbar', node);
  148. },
  149. /**
  150. * Defines sticky element container.
  151. *
  152. * @param {HTMLElement} node
  153. */
  154. setStickyNode: function (node) {
  155. this.setNode('$sticky', node);
  156. },
  157. /**
  158. * Defines toolbar element container.
  159. *
  160. * @param {HTMLElement} node
  161. */
  162. setToolbarNode: function (node) {
  163. this.$toolbar = node;
  164. },
  165. /**
  166. * Sets provided node as a value of 'key' property and
  167. * performs check for required DOM elements.
  168. *
  169. * @param {String} key - Properties key.
  170. * @param {HTMLElement} node - DOM element.
  171. */
  172. setNode: function (key, node) {
  173. var nodes = this._requiredNodes,
  174. promise = this._domPromise,
  175. defined;
  176. this[key] = node;
  177. defined = _.every(nodes, function (enabled, name) {
  178. return enabled ? this[name] : true;
  179. }, this);
  180. if (defined) {
  181. resolver(promise.resolve, promise);
  182. }
  183. },
  184. /**
  185. * Starts refresh process of the sticky element
  186. * and assigns DOM elements events handlers.
  187. */
  188. run: function () {
  189. _.bindAll(
  190. this,
  191. 'refresh',
  192. '_onWindowResize',
  193. '_onWindowScroll',
  194. '_onTableScroll'
  195. );
  196. $(window).on({
  197. scroll: this._onWindowScroll,
  198. resize: this._onWindowResize
  199. });
  200. $(this.$tableContainer).on('scroll', this._onTableScroll);
  201. this.refresh();
  202. this.checkTableWidth();
  203. },
  204. /**
  205. * Refreshes state of the sticky element and
  206. * invokes DOM elements events handlers
  207. * if corresponding event has been triggered.
  208. */
  209. refresh: function () {
  210. if (!raf(this.refresh, this.refreshFPS)) {
  211. return;
  212. }
  213. if (this._scrolled) {
  214. this.onWindowScroll();
  215. }
  216. if (this._tableScrolled) {
  217. this.onTableScroll();
  218. }
  219. if (this._resized) {
  220. this.onWindowResize();
  221. }
  222. if (this.visible) {
  223. this.checkTableWidth();
  224. }
  225. },
  226. /**
  227. * Shows sticky toolbar.
  228. *
  229. * @returns {Sticky} Chainable.
  230. */
  231. show: function () {
  232. this.visible = true;
  233. this.$sticky.style.display = '';
  234. this.$toolbar.style.visibility = 'hidden';
  235. return this;
  236. },
  237. /**
  238. * Hides sticky toolbar.
  239. *
  240. * @returns {Sticky} Chainable.
  241. */
  242. hide: function () {
  243. this.visible = false;
  244. this.$sticky.style.display = 'none';
  245. this.$toolbar.style.visibility = '';
  246. return this;
  247. },
  248. /**
  249. * Checks if sticky toolbar covers original elements.
  250. *
  251. * @returns {Boolean}
  252. */
  253. isCovered: function () {
  254. var stickyTop = this._stickyTableTop + this._wScrollTop;
  255. return stickyTop > this._tableTop;
  256. },
  257. /**
  258. * Updates offset of the sticky table element.
  259. *
  260. * @returns {Sticky} Chainable.
  261. */
  262. updateStickyTableOffset: function () {
  263. var style,
  264. top;
  265. if (this.visible) {
  266. top = this.$stickyTable.getBoundingClientRect().top;
  267. } else {
  268. style = this.$sticky.style;
  269. style.visibility = 'hidden';
  270. style.display = '';
  271. top = this.$stickyTable.getBoundingClientRect().top;
  272. style.display = 'none';
  273. style.visibility = '';
  274. }
  275. this._stickyTableTop = top;
  276. return this;
  277. },
  278. /**
  279. * Updates offset of the original table element.
  280. *
  281. * @returns {Sticky} Chainable.
  282. */
  283. updateTableOffset: function () {
  284. var box = this.$table.getBoundingClientRect(),
  285. top = box.top + this._wScrollTop;
  286. if (this._tableTop !== top) {
  287. this._tableTop = top;
  288. this.onTableTopChange(top);
  289. }
  290. return this;
  291. },
  292. /**
  293. * Checks if width of the table or it's columns has changed.
  294. *
  295. * @returns {Sticky} Chainable.
  296. */
  297. checkTableWidth: function () {
  298. var cols = this.$cols,
  299. total = cols.length,
  300. rightBorder = cols[total - 2].offsetLeft,
  301. tableWidth = this.$table.offsetWidth;
  302. if (this._tableWidth !== tableWidth) {
  303. this._tableWidth = tableWidth;
  304. this.onTableWidthChange(tableWidth);
  305. }
  306. if (this._rightBorder !== rightBorder) {
  307. this._rightBorder = rightBorder;
  308. this.onColumnsWidthChange();
  309. }
  310. return this;
  311. },
  312. /**
  313. * Updates width of the sticky table.
  314. *
  315. * @returns {Sticky} Chainable.
  316. */
  317. updateTableWidth: function () {
  318. this.$stickyTable.style.width = this._tableWidth + 'px';
  319. if (this._tableWidth < this._toolbarWidth) {
  320. this.checkToolbarSize();
  321. }
  322. return this;
  323. },
  324. /**
  325. * Updates width of the sticky columns.
  326. *
  327. * @returns {Sticky} Chainable.
  328. */
  329. updateColumnsWidth: function () {
  330. var cols = this.$cols,
  331. index = cols.length,
  332. stickyCols = this.$stickyCols;
  333. while (index--) {
  334. stickyCols[index].width = cols[index].offsetWidth;
  335. }
  336. return this;
  337. },
  338. /**
  339. * Upadates size of the sticky toolbar element
  340. * and invokes corresponding 'change' event handlers.
  341. *
  342. * @returns {Sticky} Chainable.
  343. */
  344. checkToolbarSize: function () {
  345. var width = this.$tableContainer.offsetWidth;
  346. if (this._toolbarWidth !== width) {
  347. this._toolbarWidth = width;
  348. this.onToolbarWidthChange(width);
  349. }
  350. return this;
  351. },
  352. /**
  353. * Toggles sticky toolbar visibility if it's necessary.
  354. *
  355. * @returns {Sticky} Chainable.
  356. */
  357. updateVisibility: function () {
  358. if (this.visible !== this.isCovered()) {
  359. this.visible ? this.hide() : this.show();
  360. }
  361. return this;
  362. },
  363. /**
  364. * Updates position of the left cover area.
  365. *
  366. * @returns {Sticky} Chainable.
  367. */
  368. updateLeftCap: function () {
  369. locate(this.$leftCap, -this._wScrollLeft, 0);
  370. return this;
  371. },
  372. /**
  373. * Updates position of the right cover area.
  374. *
  375. * @returns {Sticky} Chainable.
  376. */
  377. updateRightCap: function () {
  378. var left = this._toolbarWidth - this._wScrollLeft;
  379. locate(this.$rightCap, left, 0);
  380. return this;
  381. },
  382. /**
  383. * Updates position of the sticky table.
  384. *
  385. * @returns {Sticky} Chainable.
  386. */
  387. updateTableScroll: function () {
  388. var container = this.$tableContainer,
  389. left = container.scrollLeft + this._wScrollLeft;
  390. locate(this.$stickyTable, -left, 0);
  391. return this;
  392. },
  393. /**
  394. * Updates width of the toolbar element.
  395. *
  396. * @returns {Sticky} Chainable.
  397. */
  398. updateToolbarWidth: function () {
  399. this.$stickyToolbar.style.width = this._toolbarWidth + 'px';
  400. return this;
  401. },
  402. /**
  403. * Handles changes of the toolbar element's width.
  404. */
  405. onToolbarWidthChange: function () {
  406. this.updateToolbarWidth()
  407. .updateRightCap();
  408. },
  409. /**
  410. * Handles changes of the table top position.
  411. */
  412. onTableTopChange: function () {
  413. this.updateStickyTableOffset();
  414. },
  415. /**
  416. * Handles change of the table width.
  417. */
  418. onTableWidthChange: function () {
  419. this.updateTableWidth();
  420. },
  421. /**
  422. * Handles change of the table columns width.
  423. */
  424. onColumnsWidthChange: function () {
  425. this.updateColumnsWidth();
  426. },
  427. /**
  428. * Handles changes of the window's size.
  429. */
  430. onWindowResize: function () {
  431. this.checkToolbarSize();
  432. this._resized = false;
  433. },
  434. /**
  435. * Handles changes of the original table scroll position.
  436. */
  437. onTableScroll: function () {
  438. this.updateTableScroll();
  439. this._tableScrolled = false;
  440. },
  441. /**
  442. * Handles changes of window's scroll position.
  443. */
  444. onWindowScroll: function () {
  445. var scrollTop = window.pageYOffset,
  446. scrollLeft = window.pageXOffset;
  447. if (this._wScrollTop !== scrollTop) {
  448. this._wScrollTop = scrollTop;
  449. this.onWindowScrollTop(scrollTop);
  450. }
  451. if (this._wScrollLeft !== scrollLeft) {
  452. this._wScrollLeft = scrollLeft;
  453. this.onWindowScrollLeft(scrollLeft);
  454. }
  455. this._scrolled = false;
  456. },
  457. /**
  458. * Handles changes of windows' top scroll position.
  459. */
  460. onWindowScrollTop: function () {
  461. this.updateTableOffset()
  462. .updateVisibility();
  463. },
  464. /**
  465. * Handles changes of windows' left scroll position.
  466. */
  467. onWindowScrollLeft: function () {
  468. this.updateRightCap()
  469. .updateLeftCap()
  470. .updateTableScroll();
  471. },
  472. /**
  473. * Original window 'scroll' event handler.
  474. * Sets 'scrolled' flag to 'true'.
  475. *
  476. * @private
  477. */
  478. _onWindowScroll: function () {
  479. this._scrolled = true;
  480. },
  481. /**
  482. * Original window 'resize' event handler.
  483. * Sets 'resized' flag to 'true'.
  484. *
  485. * @private
  486. */
  487. _onWindowResize: function () {
  488. this._resized = true;
  489. },
  490. /**
  491. * Original table 'scroll' event handler.
  492. * Sets '_tableScrolled' flag to 'true'.
  493. *
  494. * @private
  495. */
  496. _onTableScroll: function () {
  497. this._tableScrolled = true;
  498. }
  499. });
  500. });