API Docs for: 3.10.3
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 {HTML}
  35. @default '<caption class="{className}"/>'
  36. @since 3.6.0
  37. **/
  38. CAPTION_TEMPLATE: '<caption class="{className}"/>',
  39.  
  40. /**
  41. The HTML template used to create the table Node.
  42.  
  43. @property TABLE_TEMPLATE
  44. @type {HTML}
  45. @default '<table cellspacing="0" class="{className}"/>'
  46. @since 3.6.0
  47. **/
  48. TABLE_TEMPLATE : '<table cellspacing="0" class="{className}"/>',
  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. @return {TableView}
  501. @chainable
  502. **/
  503. render: function () {
  504. if (this.get('container')) {
  505. this.fire('renderTable', {
  506. headerView : this.get('headerView'),
  507. headerConfig: this.get('headerConfig'),
  508.  
  509. bodyView : this.get('bodyView'),
  510. bodyConfig : this.get('bodyConfig'),
  511.  
  512. footerView : this.get('footerView'),
  513. footerConfig: this.get('footerConfig')
  514. });
  515. }
  516.  
  517. return this;
  518. },
  519.  
  520. /**
  521. Creates, removes, or updates the table's `<caption>` element per the input
  522. value. Empty values result in the caption being removed.
  523.  
  524. @method _uiSetCaption
  525. @param {HTML} htmlContent The content to populate the table caption
  526. @protected
  527. @since 3.5.0
  528. **/
  529. _uiSetCaption: function (htmlContent) {
  530. var table = this.tableNode,
  531. caption = this.captionNode;
  532.  
  533. if (htmlContent) {
  534. if (!caption) {
  535. this.captionNode = caption = Y.Node.create(
  536. fromTemplate(this.CAPTION_TEMPLATE, {
  537. className: this.getClassName('caption')
  538. }));
  539.  
  540. table.prepend(this.captionNode);
  541. }
  542.  
  543. caption.setHTML(htmlContent);
  544.  
  545. } else if (caption) {
  546. caption.remove(true);
  547.  
  548. delete this.captionNode;
  549. }
  550. },
  551.  
  552. /**
  553. Updates the table's `summary` attribute with the input value.
  554.  
  555. @method _uiSetSummary
  556. @protected
  557. @since 3.5.0
  558. **/
  559. _uiSetSummary: function (summary) {
  560. if (summary) {
  561. this.tableNode.setAttribute('summary', summary);
  562. } else {
  563. this.tableNode.removeAttribute('summary');
  564. }
  565. },
  566.  
  567. /**
  568. Sets the `boundingBox` and table width per the input value.
  569.  
  570. @method _uiSetWidth
  571. @param {Number|String} width The width to make the table
  572. @protected
  573. @since 3.5.0
  574. **/
  575. _uiSetWidth: function (width) {
  576. var table = this.tableNode;
  577.  
  578. // Table width needs to account for borders
  579. table.setStyle('width', !width ? '' :
  580. (this.get('container').get('offsetWidth') -
  581. (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0) -
  582. (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0)) +
  583. 'px');
  584.  
  585. table.setStyle('width', width);
  586. },
  587.  
  588. /**
  589. Ensures that the input is a View class or at least has a `render` method.
  590.  
  591. @method _validateView
  592. @param {View|Function} val The View class
  593. @return {Boolean}
  594. @protected
  595. **/
  596. _validateView: function (val) {
  597. return isFunction(val) && val.prototype.render;
  598. }
  599. }, {
  600. ATTRS: {
  601. /**
  602. Content for the `<table summary="ATTRIBUTE VALUE HERE">`. Values
  603. assigned to this attribute will be HTML escaped for security.
  604.  
  605. @attribute summary
  606. @type {String}
  607. @default '' (empty string)
  608. @since 3.5.0
  609. **/
  610. //summary: {},
  611.  
  612. /**
  613. HTML content of an optional `<caption>` element to appear above the
  614. table. Leave this config unset or set to a falsy value to remove the
  615. caption.
  616.  
  617. @attribute caption
  618. @type HTML
  619. @default undefined (initially unset)
  620. @since 3.6.0
  621. **/
  622. //caption: {},
  623.  
  624. /**
  625. Columns to include in the rendered table.
  626.  
  627. This attribute takes an array of objects. Each object is considered a
  628. data column or header cell to be rendered. How the objects are
  629. translated into markup is delegated to the `headerView`, `bodyView`,
  630. and `footerView`.
  631.  
  632. The raw value is passed to the `headerView` and `footerView`. The
  633. `bodyView` receives the instance's `displayColumns` array, which is
  634. parsed from the columns array. If there are no nested columns (columns
  635. configured with a `children` array), the `displayColumns` is the same
  636. as the raw value.
  637.  
  638. @attribute columns
  639. @type {Object[]}
  640. @since 3.6.0
  641. **/
  642. columns: {
  643. validator: isArray
  644. },
  645.  
  646. /**
  647. Width of the table including borders. This value requires units, so
  648. `200` is invalid, but `'200px'` is valid. Setting the empty string
  649. (the default) will allow the browser to set the table width.
  650.  
  651. @attribute width
  652. @type {String}
  653. @default ''
  654. @since 3.6.0
  655. **/
  656. width: {
  657. value: '',
  658. validator: YLang.isString
  659. },
  660.  
  661. /**
  662. An instance of this class is used to render the contents of the
  663. `<thead>`&mdash;the column headers for the table.
  664.  
  665. The instance of this View will be assigned to the instance's `head`
  666. property.
  667.  
  668. It is not strictly necessary that the class function assigned here be
  669. a View subclass. It must however have a `render()` method.
  670.  
  671. @attribute headerView
  672. @type {Function|Object}
  673. @default Y.DataTable.HeaderView
  674. @since 3.6.0
  675. **/
  676. headerView: {
  677. value: Y.DataTable.HeaderView,
  678. validator: '_validateView'
  679. },
  680.  
  681. /**
  682. Configuration overrides used when instantiating the `headerView`
  683. instance.
  684.  
  685. @attribute headerConfig
  686. @type {Object}
  687. @since 3.6.0
  688. **/
  689. //headerConfig: {},
  690.  
  691. /**
  692. An instance of this class is used to render the contents of the
  693. `<tfoot>` (if appropriate).
  694.  
  695. The instance of this View will be assigned to the instance's `foot`
  696. property.
  697.  
  698. It is not strictly necessary that the class function assigned here be
  699. a View subclass. It must however have a `render()` method.
  700.  
  701. @attribute footerView
  702. @type {Function|Object}
  703. @since 3.6.0
  704. **/
  705. footerView: {
  706. validator: '_validateView'
  707. },
  708.  
  709. /**
  710. Configuration overrides used when instantiating the `footerView`
  711. instance.
  712.  
  713. @attribute footerConfig
  714. @type {Object}
  715. @since 3.6.0
  716. **/
  717. //footerConfig: {},
  718.  
  719. /**
  720. An instance of this class is used to render the contents of the table's
  721. `<tbody>`&mdash;the data cells in the table.
  722.  
  723. The instance of this View will be assigned to the instance's `body`
  724. property.
  725.  
  726. It is not strictly necessary that the class function assigned here be
  727. a View subclass. It must however have a `render()` method.
  728.  
  729. @attribute bodyView
  730. @type {Function|Object}
  731. @default Y.DataTable.BodyView
  732. @since 3.6.0
  733. **/
  734. bodyView: {
  735. value: Y.DataTable.BodyView,
  736. validator: '_validateView'
  737. }
  738.  
  739. /**
  740. Configuration overrides used when instantiating the `bodyView`
  741. instance.
  742.  
  743. @attribute bodyConfig
  744. @type {Object}
  745. @since 3.6.0
  746. **/
  747. //bodyConfig: {}
  748. }
  749. });
  750.  
  751.  
  752.