Version 3.18.1
Show:

File: datatable/js/table.js

  1. /**
  2. View class responsible for rendering a `<table>` from provided data. Used as
  3. the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.
  4. @module datatable
  5. @submodule datatable-table
  6. @since 3.6.0
  7. **/
  8. var toArray = Y.Array,
  9. YLang = Y.Lang,
  10. fromTemplate = YLang.sub,
  11. isArray = YLang.isArray,
  12. isFunction = YLang.isFunction;
  13. /**
  14. View class responsible for rendering a `<table>` from provided data. Used as
  15. the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.
  16. @class TableView
  17. @namespace DataTable
  18. @extends View
  19. @since 3.6.0
  20. **/
  21. Y.namespace('DataTable').TableView = Y.Base.create('table', Y.View, [], {
  22. /**
  23. The HTML template used to create the caption Node if the `caption`
  24. attribute is set.
  25. @property CAPTION_TEMPLATE
  26. @type {String}
  27. @default '<caption class="{className}"></caption>'
  28. @since 3.6.0
  29. **/
  30. CAPTION_TEMPLATE: '<caption class="{className}"></caption>',
  31. /**
  32. The HTML template used to create the table Node.
  33. @property TABLE_TEMPLATE
  34. @type {String}
  35. @default '<table cellspacing="0" class="{className}"></table>'
  36. @since 3.6.0
  37. **/
  38. TABLE_TEMPLATE : '<table cellspacing="0" class="{className}"></table>',
  39. /**
  40. The object or instance of the class assigned to `bodyView` that is
  41. responsible for rendering and managing the table's `<tbody>`(s) and its
  42. content.
  43. @property body
  44. @type {Object}
  45. @default undefined (initially unset)
  46. @since 3.5.0
  47. **/
  48. //body: null,
  49. /**
  50. The object or instance of the class assigned to `footerView` that is
  51. responsible for rendering and managing the table's `<tfoot>` and its
  52. content.
  53. @property foot
  54. @type {Object}
  55. @default undefined (initially unset)
  56. @since 3.5.0
  57. **/
  58. //foot: null,
  59. /**
  60. The object or instance of the class assigned to `headerView` that is
  61. responsible for rendering and managing the table's `<thead>` and its
  62. content.
  63. @property head
  64. @type {Object}
  65. @default undefined (initially unset)
  66. @since 3.5.0
  67. **/
  68. //head: null,
  69. //-----------------------------------------------------------------------//
  70. // Public methods
  71. //-----------------------------------------------------------------------//
  72. /**
  73. Returns the `<td>` Node from the given row and column index. Alternately,
  74. the `seed` can be a Node. If so, the nearest ancestor cell is returned.
  75. If the `seed` is a cell, it is returned. If there is no cell at the given
  76. coordinates, `null` is returned.
  77. Optionally, include an offset array or string to return a cell near the
  78. cell identified by the `seed`. The offset can be an array containing the
  79. number of rows to shift followed by the number of columns to shift, or one
  80. of "above", "below", "next", or "previous".
  81. <pre><code>// Previous cell in the previous row
  82. var cell = table.getCell(e.target, [-1, -1]);
  83. // Next cell
  84. var cell = table.getCell(e.target, 'next');
  85. var cell = table.getCell(e.taregt, [0, 1];</pre></code>
  86. This is actually just a pass through to the `bodyView` instance's method
  87. by the same name.
  88. @method getCell
  89. @param {Number[]|Node} seed Array of row and column indexes, or a Node that
  90. is either the cell itself or a descendant of one.
  91. @param {Number[]|String} [shift] Offset by which to identify the returned
  92. cell Node
  93. @return {Node}
  94. @since 3.5.0
  95. **/
  96. getCell: function (/* seed, shift */) {
  97. return this.body && this.body.getCell &&
  98. this.body.getCell.apply(this.body, arguments);
  99. },
  100. /**
  101. Returns the generated CSS classname based on the input. If the `host`
  102. attribute is configured, it will attempt to relay to its `getClassName`
  103. or use its static `NAME` property as a string base.
  104. If `host` is absent or has neither method nor `NAME`, a CSS classname
  105. will be generated using this class's `NAME`.
  106. @method getClassName
  107. @param {String} token* Any number of token strings to assemble the
  108. classname from.
  109. @return {String}
  110. @protected
  111. **/
  112. getClassName: function () {
  113. // TODO: add attr with setter for host?
  114. var host = this.host,
  115. NAME = (host && host.constructor.NAME) ||
  116. this.constructor.NAME;
  117. if (host && host.getClassName) {
  118. return host.getClassName.apply(host, arguments);
  119. } else {
  120. return Y.ClassNameManager.getClassName
  121. .apply(Y.ClassNameManager,
  122. [NAME].concat(toArray(arguments, 0, true)));
  123. }
  124. },
  125. /**
  126. Relays call to the `bodyView`'s `getRecord` method if it has one.
  127. @method getRecord
  128. @param {String|Node} seed Node or identifier for a row or child element
  129. @return {Model}
  130. @since 3.6.0
  131. **/
  132. getRecord: function () {
  133. return this.body && this.body.getRecord &&
  134. this.body.getRecord.apply(this.body, arguments);
  135. },
  136. /**
  137. Returns the `<tr>` Node from the given row index, Model, or Model's
  138. `clientId`. If the rows haven't been rendered yet, or if the row can't be
  139. found by the input, `null` is returned.
  140. This is actually just a pass through to the `bodyView` instance's method
  141. by the same name.
  142. @method getRow
  143. @param {Number|String|Model} id Row index, Model instance, or clientId
  144. @return {Node}
  145. @since 3.5.0
  146. **/
  147. getRow: function (/* id */) {
  148. return this.body && this.body.getRow &&
  149. this.body.getRow.apply(this.body, arguments);
  150. },
  151. //-----------------------------------------------------------------------//
  152. // Protected and private methods
  153. //-----------------------------------------------------------------------//
  154. /**
  155. Updates the table's `summary` attribute.
  156. @method _afterSummaryChange
  157. @param {EventHandle} e The change event
  158. @protected
  159. @since 3.6.0
  160. **/
  161. _afterSummaryChange: function (e) {
  162. this._uiSetSummary(e.newVal);
  163. },
  164. /**
  165. Updates the table's `<caption>`.
  166. @method _afterCaptionChange
  167. @param {EventHandle} e The change event
  168. @protected
  169. @since 3.6.0
  170. **/
  171. _afterCaptionChange: function (e) {
  172. this._uiSetCaption(e.newVal);
  173. },
  174. /**
  175. Updates the table's width.
  176. @method _afterWidthChange
  177. @param {EventHandle} e The change event
  178. @protected
  179. @since 3.6.0
  180. **/
  181. _afterWidthChange: function (e) {
  182. this._uiSetWidth(e.newVal);
  183. },
  184. /**
  185. Attaches event subscriptions to relay attribute changes to the child Views.
  186. @method _bindUI
  187. @protected
  188. @since 3.6.0
  189. **/
  190. _bindUI: function () {
  191. var relay;
  192. if (!this._eventHandles) {
  193. relay = Y.bind('_relayAttrChange', this);
  194. this._eventHandles = this.after({
  195. columnsChange : relay,
  196. modelListChange: relay,
  197. summaryChange : Y.bind('_afterSummaryChange', this),
  198. captionChange : Y.bind('_afterCaptionChange', this),
  199. widthChange : Y.bind('_afterWidthChange', this)
  200. });
  201. }
  202. },
  203. /**
  204. Creates the `<table>`.
  205. @method _createTable
  206. @return {Node} The `<table>` node
  207. @protected
  208. @since 3.5.0
  209. **/
  210. _createTable: function () {
  211. return Y.Node.create(fromTemplate(this.TABLE_TEMPLATE, {
  212. className: this.getClassName('table')
  213. })).empty();
  214. },
  215. /**
  216. Calls `render()` on the `bodyView` class instance.
  217. @method _defRenderBodyFn
  218. @param {EventFacade} e The renderBody event
  219. @protected
  220. @since 3.5.0
  221. **/
  222. _defRenderBodyFn: function (e) {
  223. e.view.render();
  224. },
  225. /**
  226. Calls `render()` on the `footerView` class instance.
  227. @method _defRenderFooterFn
  228. @param {EventFacade} e The renderFooter event
  229. @protected
  230. @since 3.5.0
  231. **/
  232. _defRenderFooterFn: function (e) {
  233. e.view.render();
  234. },
  235. /**
  236. Calls `render()` on the `headerView` class instance.
  237. @method _defRenderHeaderFn
  238. @param {EventFacade} e The renderHeader event
  239. @protected
  240. @since 3.5.0
  241. **/
  242. _defRenderHeaderFn: function (e) {
  243. e.view.render();
  244. },
  245. /**
  246. Renders the `<table>` and, if there are associated Views, the `<thead>`,
  247. `<tfoot>`, and `<tbody>` (empty until `syncUI`).
  248. Assigns the generated table nodes to the `tableNode`, `_theadNode`,
  249. `_tfootNode`, and `_tbodyNode` properties. Assigns the instantiated Views
  250. to the `head`, `foot`, and `body` properties.
  251. @method _defRenderTableFn
  252. @param {EventFacade} e The renderTable event
  253. @protected
  254. @since 3.5.0
  255. **/
  256. _defRenderTableFn: function (e) {
  257. var container = this.get('container'),
  258. attrs = this.getAttrs();
  259. if (!this.tableNode) {
  260. this.tableNode = this._createTable();
  261. }
  262. attrs.host = this.get('host') || this;
  263. attrs.table = this;
  264. attrs.container = this.tableNode;
  265. this._uiSetCaption(this.get('caption'));
  266. this._uiSetSummary(this.get('summary'));
  267. this._uiSetWidth(this.get('width'));
  268. if (this.head || e.headerView) {
  269. if (!this.head) {
  270. this.head = new e.headerView(Y.merge(attrs, e.headerConfig));
  271. }
  272. this.fire('renderHeader', { view: this.head });
  273. }
  274. if (this.foot || e.footerView) {
  275. if (!this.foot) {
  276. this.foot = new e.footerView(Y.merge(attrs, e.footerConfig));
  277. }
  278. this.fire('renderFooter', { view: this.foot });
  279. }
  280. attrs.columns = this.displayColumns;
  281. if (this.body || e.bodyView) {
  282. if (!this.body) {
  283. this.body = new e.bodyView(Y.merge(attrs, e.bodyConfig));
  284. }
  285. this.fire('renderBody', { view: this.body });
  286. }
  287. if (!container.contains(this.tableNode)) {
  288. container.append(this.tableNode);
  289. }
  290. this._bindUI();
  291. },
  292. /**
  293. Cleans up state, destroys child views, etc.
  294. @method destructor
  295. @protected
  296. **/
  297. destructor: function () {
  298. if (this.head && this.head.destroy) {
  299. this.head.destroy();
  300. }
  301. delete this.head;
  302. if (this.foot && this.foot.destroy) {
  303. this.foot.destroy();
  304. }
  305. delete this.foot;
  306. if (this.body && this.body.destroy) {
  307. this.body.destroy();
  308. }
  309. delete this.body;
  310. if (this._eventHandles) {
  311. this._eventHandles.detach();
  312. delete this._eventHandles;
  313. }
  314. if (this.tableNode) {
  315. this.tableNode.remove().destroy(true);
  316. }
  317. },
  318. /**
  319. Processes the full column array, distilling the columns down to those that
  320. correspond to cell data columns.
  321. @method _extractDisplayColumns
  322. @protected
  323. **/
  324. _extractDisplayColumns: function () {
  325. var columns = this.get('columns'),
  326. displayColumns = [];
  327. function process(cols) {
  328. var i, len, col;
  329. for (i = 0, len = cols.length; i < len; ++i) {
  330. col = cols[i];
  331. if (isArray(col.children)) {
  332. process(col.children);
  333. } else {
  334. displayColumns.push(col);
  335. }
  336. }
  337. }
  338. if (columns) {
  339. process(columns);
  340. }
  341. /**
  342. Array of the columns that correspond to those with value cells in the
  343. data rows. Excludes colspan header columns (configured with `children`).
  344. @property displayColumns
  345. @type {Object[]}
  346. @since 3.6.0
  347. **/
  348. this.displayColumns = displayColumns;
  349. },
  350. /**
  351. Publishes core events.
  352. @method _initEvents
  353. @protected
  354. @since 3.5.0
  355. **/
  356. _initEvents: function () {
  357. this.publish({
  358. // Y.bind used to allow late binding for method override support
  359. renderTable : { defaultFn: Y.bind('_defRenderTableFn', this) },
  360. renderHeader: { defaultFn: Y.bind('_defRenderHeaderFn', this) },
  361. renderBody : { defaultFn: Y.bind('_defRenderBodyFn', this) },
  362. renderFooter: { defaultFn: Y.bind('_defRenderFooterFn', this) }
  363. });
  364. },
  365. /**
  366. Constructor logic.
  367. @method intializer
  368. @param {Object} config Configuration object passed to the constructor
  369. @protected
  370. @since 3.6.0
  371. **/
  372. initializer: function (config) {
  373. this.host = config.host;
  374. this._initEvents();
  375. this._extractDisplayColumns();
  376. this.after('columnsChange', this._extractDisplayColumns, this);
  377. },
  378. /**
  379. Relays attribute changes to the child Views.
  380. @method _relayAttrChange
  381. @param {EventHandle} e The change event
  382. @protected
  383. @since 3.6.0
  384. **/
  385. _relayAttrChange: function (e) {
  386. var attr = e.attrName,
  387. val = e.newVal;
  388. if (this.head) {
  389. this.head.set(attr, val);
  390. }
  391. if (this.foot) {
  392. this.foot.set(attr, val);
  393. }
  394. if (this.body) {
  395. if (attr === 'columns') {
  396. val = this.displayColumns;
  397. }
  398. this.body.set(attr, val);
  399. }
  400. },
  401. /**
  402. Creates the UI in the configured `container`.
  403. @method render
  404. @chainable
  405. **/
  406. render: function () {
  407. if (this.get('container')) {
  408. this.fire('renderTable', {
  409. headerView : this.get('headerView'),
  410. headerConfig: this.get('headerConfig'),
  411. bodyView : this.get('bodyView'),
  412. bodyConfig : this.get('bodyConfig'),
  413. footerView : this.get('footerView'),
  414. footerConfig: this.get('footerConfig')
  415. });
  416. }
  417. return this;
  418. },
  419. /**
  420. Creates, removes, or updates the table's `<caption>` element per the input
  421. value. Empty values result in the caption being removed.
  422. @method _uiSetCaption
  423. @param {String} htmlContent The content to populate the table caption
  424. @protected
  425. @since 3.5.0
  426. **/
  427. _uiSetCaption: function (htmlContent) {
  428. var table = this.tableNode,
  429. caption = this.captionNode;
  430. if (htmlContent) {
  431. if (!caption) {
  432. this.captionNode = caption = Y.Node.create(
  433. fromTemplate(this.CAPTION_TEMPLATE, {
  434. className: this.getClassName('caption')
  435. }));
  436. table.prepend(this.captionNode);
  437. }
  438. caption.setHTML(htmlContent);
  439. } else if (caption) {
  440. caption.remove(true);
  441. delete this.captionNode;
  442. }
  443. },
  444. /**
  445. Updates the table's `summary` attribute with the input value.
  446. @method _uiSetSummary
  447. @protected
  448. @since 3.5.0
  449. **/
  450. _uiSetSummary: function (summary) {
  451. if (summary) {
  452. this.tableNode.setAttribute('summary', summary);
  453. } else {
  454. this.tableNode.removeAttribute('summary');
  455. }
  456. },
  457. /**
  458. Sets the `boundingBox` and table width per the input value.
  459. @method _uiSetWidth
  460. @param {Number|String} width The width to make the table
  461. @protected
  462. @since 3.5.0
  463. **/
  464. _uiSetWidth: function (width) {
  465. var table = this.tableNode;
  466. // Table width needs to account for borders
  467. table.setStyle('width', !width ? '' :
  468. (this.get('container').get('offsetWidth') -
  469. (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0) -
  470. (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0)) +
  471. 'px');
  472. table.setStyle('width', width);
  473. },
  474. /**
  475. Ensures that the input is a View class or at least has a `render` method.
  476. @method _validateView
  477. @param {View|Function} val The View class
  478. @return {Boolean}
  479. @protected
  480. **/
  481. _validateView: function (val) {
  482. return isFunction(val) && val.prototype.render;
  483. }
  484. }, {
  485. ATTRS: {
  486. /**
  487. Content for the `<table summary="ATTRIBUTE VALUE HERE">`. Values
  488. assigned to this attribute will be HTML escaped for security.
  489. @attribute summary
  490. @type {String}
  491. @default '' (empty string)
  492. @since 3.5.0
  493. **/
  494. //summary: {},
  495. /**
  496. HTML content of an optional `<caption>` element to appear above the
  497. table. Leave this config unset or set to a falsy value to remove the
  498. caption.
  499. @attribute caption
  500. @type HTML
  501. @default undefined (initially unset)
  502. @since 3.6.0
  503. **/
  504. //caption: {},
  505. /**
  506. Columns to include in the rendered table.
  507. This attribute takes an array of objects. Each object is considered a
  508. data column or header cell to be rendered. How the objects are
  509. translated into markup is delegated to the `headerView`, `bodyView`,
  510. and `footerView`.
  511. The raw value is passed to the `headerView` and `footerView`. The
  512. `bodyView` receives the instance's `displayColumns` array, which is
  513. parsed from the columns array. If there are no nested columns (columns
  514. configured with a `children` array), the `displayColumns` is the same
  515. as the raw value.
  516. @attribute columns
  517. @type {Object[]}
  518. @since 3.6.0
  519. **/
  520. columns: {
  521. validator: isArray
  522. },
  523. /**
  524. Width of the table including borders. This value requires units, so
  525. `200` is invalid, but `'200px'` is valid. Setting the empty string
  526. (the default) will allow the browser to set the table width.
  527. @attribute width
  528. @type {String}
  529. @default ''
  530. @since 3.6.0
  531. **/
  532. width: {
  533. value: '',
  534. validator: YLang.isString
  535. },
  536. /**
  537. An instance of this class is used to render the contents of the
  538. `<thead>`&mdash;the column headers for the table.
  539. The instance of this View will be assigned to the instance's `head`
  540. property.
  541. It is not strictly necessary that the class function assigned here be
  542. a View subclass. It must however have a `render()` method.
  543. @attribute headerView
  544. @type {Function|Object}
  545. @default Y.DataTable.HeaderView
  546. @since 3.6.0
  547. **/
  548. headerView: {
  549. value: Y.DataTable.HeaderView,
  550. validator: '_validateView'
  551. },
  552. /**
  553. Configuration overrides used when instantiating the `headerView`
  554. instance.
  555. @attribute headerConfig
  556. @type {Object}
  557. @since 3.6.0
  558. **/
  559. //headerConfig: {},
  560. /**
  561. An instance of this class is used to render the contents of the
  562. `<tfoot>` (if appropriate).
  563. The instance of this View will be assigned to the instance's `foot`
  564. property.
  565. It is not strictly necessary that the class function assigned here be
  566. a View subclass. It must however have a `render()` method.
  567. @attribute footerView
  568. @type {Function|Object}
  569. @since 3.6.0
  570. **/
  571. footerView: {
  572. validator: '_validateView'
  573. },
  574. /**
  575. Configuration overrides used when instantiating the `footerView`
  576. instance.
  577. @attribute footerConfig
  578. @type {Object}
  579. @since 3.6.0
  580. **/
  581. //footerConfig: {},
  582. /**
  583. An instance of this class is used to render the contents of the table's
  584. `<tbody>`&mdash;the data cells in the table.
  585. The instance of this View will be assigned to the instance's `body`
  586. property.
  587. It is not strictly necessary that the class function assigned here be
  588. a View subclass. It must however have a `render()` method.
  589. @attribute bodyView
  590. @type {Function|Object}
  591. @default Y.DataTable.BodyView
  592. @since 3.6.0
  593. **/
  594. bodyView: {
  595. value: Y.DataTable.BodyView,
  596. validator: '_validateView'
  597. }
  598. /**
  599. Configuration overrides used when instantiating the `bodyView`
  600. instance.
  601. @attribute bodyConfig
  602. @type {Object}
  603. @since 3.6.0
  604. **/
  605. //bodyConfig: {}
  606. }
  607. });