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