API Docs for: 3.13.0
Show:

File: datatable/js/head.js

  1. /**
  2. View class responsible for rendering the `<thead>` section of a table. Used as
  3. the default `headerView` for `Y.DataTable.Base` and `Y.DataTable` classes.
  4.  
  5. @module datatable
  6. @submodule datatable-head
  7. @since 3.5.0
  8. **/
  9. var Lang = Y.Lang,
  10. fromTemplate = Lang.sub,
  11. isArray = Lang.isArray,
  12. toArray = Y.Array;
  13.  
  14. /**
  15. View class responsible for rendering the `<thead>` section of a table. Used as
  16. the default `headerView` for `Y.DataTable.Base` and `Y.DataTable` classes.
  17.  
  18. Translates the provided array of column configuration objects into a rendered
  19. `<thead>` based on the data in those objects.
  20.  
  21.  
  22. The structure of the column data is expected to be a single array of objects,
  23. where each object corresponds to a `<th>`. Those objects may contain a
  24. `children` property containing a similarly structured array to indicate the
  25. nested cells should be grouped under the parent column's colspan in a separate
  26. row of header cells. E.g.
  27.  
  28. <pre><code>
  29. new Y.DataTable.HeaderView({
  30. container: tableNode,
  31. columns: [
  32. { key: 'id' }, // no nesting
  33. { key: 'name', children: [
  34. { key: 'firstName', label: 'First' },
  35. { key: 'lastName', label: 'Last' } ] }
  36. ]
  37. }).render();
  38. </code></pre>
  39.  
  40. This would translate to the following visualization:
  41.  
  42. <pre>
  43. ---------------------
  44. | | name |
  45. | |---------------
  46. | id | First | Last |
  47. ---------------------
  48. </pre>
  49.  
  50. Supported properties of the column objects include:
  51.  
  52. * `label` - The HTML content of the header cell.
  53. * `key` - If `label` is not specified, the `key` is used for content.
  54. * `children` - Array of columns to appear below this column in the next
  55. row.
  56. * `headerTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in this
  57. column only.
  58. * `abbr` - The content of the 'abbr' attribute of the `<th>`
  59. * `title` - The content of the 'title' attribute of the `<th>`
  60. * `className` - Adds this string of CSS classes to the column header
  61.  
  62. Through the life of instantiation and rendering, the column objects will have
  63. the following properties added to them:
  64.  
  65. * `id` - (Defaulted by DataTable) The id to assign the rendered column
  66. * `_colspan` - To supply the `<th>` attribute
  67. * `_rowspan` - To supply the `<th>` attribute
  68. * `_parent` - (Added by DataTable) If the column is a child of another
  69. column, this points to its parent column
  70.  
  71. The column object is also used to provide values for {placeholder} tokens in the
  72. instance's `CELL_TEMPLATE`, so you can modify the template and include other
  73. column object properties to populate them.
  74.  
  75. @class HeaderView
  76. @namespace DataTable
  77. @extends View
  78. @since 3.5.0
  79. **/
  80. Y.namespace('DataTable').HeaderView = Y.Base.create('tableHeader', Y.View, [], {
  81. // -- Instance properties -------------------------------------------------
  82.  
  83. /**
  84. Template used to create the table's header cell markup. Override this to
  85. customize how header cell markup is created.
  86.  
  87. @property CELL_TEMPLATE
  88. @type {HTML}
  89. @default '<th id="{id}" colspan="{_colspan}" rowspan="{_rowspan}" class="{className}" scope="col" {_id}{abbr}{title}>{content}</th>'
  90. @since 3.5.0
  91. **/
  92. CELL_TEMPLATE:
  93. '<th id="{id}" colspan="{_colspan}" rowspan="{_rowspan}" class="{className}" scope="col" {_id}{abbr}{title}>{content}</th>',
  94.  
  95. /**
  96. The data representation of the header rows to render. This is assigned by
  97. parsing the `columns` configuration array, and is used by the render()
  98. method.
  99.  
  100. @property columns
  101. @type {Array[]}
  102. @default (initially unset)
  103. @since 3.5.0
  104. **/
  105. //TODO: should this be protected?
  106. //columns: null,
  107.  
  108. /**
  109. Template used to create the table's header row markup. Override this to
  110. customize the row markup.
  111.  
  112. @property ROW_TEMPLATE
  113. @type {HTML}
  114. @default '<tr>{content}</tr>'
  115. @since 3.5.0
  116. **/
  117. ROW_TEMPLATE:
  118. '<tr>{content}</tr>',
  119.  
  120. /**
  121. The object that serves as the source of truth for column and row data.
  122. This property is assigned at instantiation from the `source` property of
  123. the configuration object passed to the constructor.
  124.  
  125. @property source
  126. @type {Object}
  127. @default (initially unset)
  128. @since 3.5.0
  129. **/
  130. //TODO: should this be protected?
  131. //source: null,
  132.  
  133. /**
  134. HTML templates used to create the `<thead>` containing the table headers.
  135.  
  136. @property THEAD_TEMPLATE
  137. @type {HTML}
  138. @default '<thead class="{className}">{content}</thead>'
  139. @since 3.6.0
  140. **/
  141. THEAD_TEMPLATE: '<thead class="{className}"></thead>',
  142.  
  143. // -- Public methods ------------------------------------------------------
  144.  
  145. /**
  146. Returns the generated CSS classname based on the input. If the `host`
  147. attribute is configured, it will attempt to relay to its `getClassName`
  148. or use its static `NAME` property as a string base.
  149.  
  150. If `host` is absent or has neither method nor `NAME`, a CSS classname
  151. will be generated using this class's `NAME`.
  152.  
  153. @method getClassName
  154. @param {String} token* Any number of token strings to assemble the
  155. classname from.
  156. @return {String}
  157. @protected
  158. **/
  159. getClassName: function () {
  160. // TODO: add attribute with setter? to host to use property this.host
  161. // for performance
  162. var host = this.host,
  163. NAME = (host && host.constructor.NAME) ||
  164. this.constructor.NAME;
  165.  
  166. if (host && host.getClassName) {
  167. return host.getClassName.apply(host, arguments);
  168. } else {
  169. return Y.ClassNameManager.getClassName
  170. .apply(Y.ClassNameManager,
  171. [NAME].concat(toArray(arguments, 0, true)));
  172. }
  173. },
  174.  
  175. /**
  176. Creates the `<thead>` Node content by assembling markup generated by
  177. populating the `ROW_TEMPLATE` and `CELL_TEMPLATE` templates with content
  178. from the `columns` property.
  179.  
  180. @method render
  181. @return {HeaderView} The instance
  182. @chainable
  183. @since 3.5.0
  184. **/
  185. render: function () {
  186. var table = this.get('container'),
  187. thead = this.theadNode ||
  188. (this.theadNode = this._createTHeadNode()),
  189. columns = this.columns,
  190. defaults = {
  191. _colspan: 1,
  192. _rowspan: 1,
  193. abbr: '',
  194. title: ''
  195. },
  196. i, len, j, jlen, col, html, content, values;
  197.  
  198. if (thead && columns) {
  199. html = '';
  200.  
  201. if (columns.length) {
  202. for (i = 0, len = columns.length; i < len; ++i) {
  203. content = '';
  204.  
  205. for (j = 0, jlen = columns[i].length; j < jlen; ++j) {
  206. col = columns[i][j];
  207. values = Y.merge(
  208. defaults,
  209. col, {
  210. className: this.getClassName('header'),
  211. content : col.label || col.key ||
  212. ("Column " + (j + 1))
  213. }
  214. );
  215.  
  216. values._id = col._id ?
  217. ' data-yui3-col-id="' + col._id + '"' : '';
  218.  
  219. if (col.abbr) {
  220. values.abbr = ' abbr="' + col.abbr + '"';
  221. }
  222.  
  223. if (col.title) {
  224. values.title = ' title="' + col.title + '"';
  225. }
  226.  
  227. if (col.className) {
  228. values.className += ' ' + col.className;
  229. }
  230.  
  231. if (col._first) {
  232. values.className += ' ' + this.getClassName('first', 'header');
  233. }
  234.  
  235. if (col._id) {
  236. values.className +=
  237. ' ' + this.getClassName('col', col._id);
  238. }
  239.  
  240. content += fromTemplate(
  241. col.headerTemplate || this.CELL_TEMPLATE, values);
  242. }
  243.  
  244. html += fromTemplate(this.ROW_TEMPLATE, {
  245. content: content
  246. });
  247. }
  248. }
  249.  
  250. thead.setHTML(html);
  251.  
  252. if (thead.get('parentNode') !== table) {
  253. table.insertBefore(thead, table.one('tfoot, tbody'));
  254. }
  255. }
  256.  
  257. this.bindUI();
  258.  
  259. return this;
  260. },
  261.  
  262. // -- Protected and private properties and methods ------------------------
  263.  
  264. /**
  265. Handles changes in the source's columns attribute. Redraws the headers.
  266.  
  267. @method _afterColumnsChange
  268. @param {EventFacade} e The `columnsChange` event object
  269. @protected
  270. @since 3.5.0
  271. **/
  272. _afterColumnsChange: function (e) {
  273. this.columns = this._parseColumns(e.newVal);
  274.  
  275. this.render();
  276. },
  277.  
  278. /**
  279. Binds event subscriptions from the UI and the source (if assigned).
  280.  
  281. @method bindUI
  282. @protected
  283. @since 3.5.0
  284. **/
  285. bindUI: function () {
  286. if (!this._eventHandles.columnsChange) {
  287. // TODO: How best to decouple this?
  288. this._eventHandles.columnsChange =
  289. this.after('columnsChange',
  290. Y.bind('_afterColumnsChange', this));
  291. }
  292. },
  293.  
  294. /**
  295. Creates the `<thead>` node that will store the header rows and cells.
  296.  
  297. @method _createTHeadNode
  298. @return {Node}
  299. @protected
  300. @since 3.6.0
  301. **/
  302. _createTHeadNode: function () {
  303. return Y.Node.create(fromTemplate(this.THEAD_TEMPLATE, {
  304. className: this.getClassName('columns')
  305. }));
  306. },
  307.  
  308. /**
  309. Destroys the instance.
  310.  
  311. @method destructor
  312. @protected
  313. @since 3.5.0
  314. **/
  315. destructor: function () {
  316. (new Y.EventHandle(Y.Object.values(this._eventHandles))).detach();
  317. },
  318.  
  319. /**
  320. Holds the event subscriptions needing to be detached when the instance is
  321. `destroy()`ed.
  322.  
  323. @property _eventHandles
  324. @type {Object}
  325. @default undefined (initially unset)
  326. @protected
  327. @since 3.5.0
  328. **/
  329. //_eventHandles: null,
  330.  
  331. /**
  332. Initializes the instance. Reads the following configuration properties:
  333.  
  334. * `columns` - (REQUIRED) The initial column information
  335. * `host` - The object to serve as source of truth for column info
  336.  
  337. @method initializer
  338. @param {Object} config Configuration data
  339. @protected
  340. @since 3.5.0
  341. **/
  342. initializer: function (config) {
  343. this.host = config.host;
  344. this.columns = this._parseColumns(config.columns);
  345.  
  346. this._eventHandles = [];
  347. },
  348.  
  349. /**
  350. Translate the input column format into a structure useful for rendering a
  351. `<thead>`, rows, and cells. The structure of the input is expected to be a
  352. single array of objects, where each object corresponds to a `<th>`. Those
  353. objects may contain a `children` property containing a similarly structured
  354. array to indicate the nested cells should be grouped under the parent
  355. column's colspan in a separate row of header cells. E.g.
  356.  
  357. <pre><code>
  358. [
  359. { key: 'id' }, // no nesting
  360. { key: 'name', children: [
  361. { key: 'firstName', label: 'First' },
  362. { key: 'lastName', label: 'Last' } ] }
  363. ]
  364. </code></pre>
  365.  
  366. would indicate two header rows with the first column 'id' being assigned a
  367. `rowspan` of `2`, the 'name' column appearing in the first row with a
  368. `colspan` of `2`, and the 'firstName' and 'lastName' columns appearing in
  369. the second row, below the 'name' column.
  370.  
  371. <pre>
  372. ---------------------
  373. | | name |
  374. | |---------------
  375. | id | First | Last |
  376. ---------------------
  377. </pre>
  378.  
  379. Supported properties of the column objects include:
  380.  
  381. * `label` - The HTML content of the header cell.
  382. * `key` - If `label` is not specified, the `key` is used for content.
  383. * `children` - Array of columns to appear below this column in the next
  384. row.
  385. * `abbr` - The content of the 'abbr' attribute of the `<th>`
  386. * `title` - The content of the 'title' attribute of the `<th>`
  387. * `headerTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells
  388. in this column only.
  389.  
  390. The output structure is basically a simulation of the `<thead>` structure
  391. with arrays for rows and objects for cells. Column objects have the
  392. following properties added to them:
  393.  
  394. * `id` - (Defaulted by DataTable) The id to assign the rendered
  395. column
  396. * `_colspan` - Per the `<th>` attribute
  397. * `_rowspan` - Per the `<th>` attribute
  398. * `_parent` - (Added by DataTable) If the column is a child of another
  399. column, this points to its parent column
  400.  
  401. The column object is also used to provide values for {placeholder}
  402. replacement in the `CELL_TEMPLATE`, so you can modify the template and
  403. include other column object properties to populate them.
  404.  
  405. @method _parseColumns
  406. @param {Object[]} data Array of column object data
  407. @return {Array[]} An array of arrays corresponding to the header row
  408. structure to render
  409. @protected
  410. @since 3.5.0
  411. **/
  412. _parseColumns: function (data) {
  413. var columns = [],
  414. stack = [],
  415. rowSpan = 1,
  416. entry, row, col, children, parent, i, len, j;
  417.  
  418. if (isArray(data) && data.length) {
  419. // don't modify the input array
  420. data = data.slice();
  421.  
  422. // First pass, assign colspans and calculate row count for
  423. // non-nested headers' rowspan
  424. stack.push([data, -1]);
  425.  
  426. while (stack.length) {
  427. entry = stack[stack.length - 1];
  428. row = entry[0];
  429. i = entry[1] + 1;
  430.  
  431. for (len = row.length; i < len; ++i) {
  432. row[i] = col = Y.merge(row[i]);
  433. children = col.children;
  434.  
  435. Y.stamp(col);
  436.  
  437. if (!col.id) {
  438. col.id = Y.guid();
  439. }
  440.  
  441. if (isArray(children) && children.length) {
  442. stack.push([children, -1]);
  443. entry[1] = i;
  444.  
  445. rowSpan = Math.max(rowSpan, stack.length);
  446.  
  447. // break to let the while loop process the children
  448. break;
  449. } else {
  450. col._colspan = 1;
  451. }
  452. }
  453.  
  454. if (i >= len) {
  455. // All columns in this row are processed
  456. if (stack.length > 1) {
  457. entry = stack[stack.length - 2];
  458. parent = entry[0][entry[1]];
  459.  
  460. parent._colspan = 0;
  461.  
  462. for (i = 0, len = row.length; i < len; ++i) {
  463. // Can't use .length because in 3+ rows, colspan
  464. // needs to aggregate the colspans of children
  465. row[i]._parent = parent;
  466. parent._colspan += row[i]._colspan;
  467. }
  468. }
  469. stack.pop();
  470. }
  471. }
  472.  
  473. // Second pass, build row arrays and assign rowspan
  474. for (i = 0; i < rowSpan; ++i) {
  475. columns.push([]);
  476. }
  477.  
  478. stack.push([data, -1]);
  479.  
  480. while (stack.length) {
  481. entry = stack[stack.length - 1];
  482. row = entry[0];
  483. i = entry[1] + 1;
  484.  
  485. for (len = row.length; i < len; ++i) {
  486. col = row[i];
  487. children = col.children;
  488.  
  489. columns[stack.length - 1].push(col);
  490.  
  491. entry[1] = i;
  492.  
  493. // collect the IDs of parent cols
  494. col._headers = [col.id];
  495.  
  496. for (j = stack.length - 2; j >= 0; --j) {
  497. parent = stack[j][0][stack[j][1]];
  498.  
  499. col._headers.unshift(parent.id);
  500. }
  501.  
  502. if (children && children.length) {
  503. // parent cells must assume rowspan 1 (long story)
  504.  
  505. // break to let the while loop process the children
  506. stack.push([children, -1]);
  507. break;
  508. } else {
  509. col._rowspan = rowSpan - stack.length + 1;
  510. }
  511. }
  512.  
  513. if (i >= len) {
  514. // All columns in this row are processed
  515. stack.pop();
  516. }
  517. }
  518. }
  519.  
  520. for (i = 0, len = columns.length; i < len; i += col._rowspan) {
  521. col = columns[i][0];
  522.  
  523. col._first = true;
  524. }
  525.  
  526. return columns;
  527. }
  528. });
  529.