API Docs for: 3.10.3
Show:

File: datatable/js/core.js

  1. /**
  2. The core implementation of the `DataTable` and `DataTable.Base` Widgets.
  3.  
  4. @module datatable
  5. @submodule datatable-core
  6. @since 3.5.0
  7. **/
  8.  
  9. var INVALID = Y.Attribute.INVALID_VALUE,
  10.  
  11. Lang = Y.Lang,
  12. isFunction = Lang.isFunction,
  13. isObject = Lang.isObject,
  14. isArray = Lang.isArray,
  15. isString = Lang.isString,
  16. isNumber = Lang.isNumber,
  17.  
  18. toArray = Y.Array,
  19.  
  20. keys = Y.Object.keys,
  21.  
  22. Table;
  23. /**
  24. _API docs for this extension are included in the DataTable class._
  25.  
  26. Class extension providing the core API and structure for the DataTable Widget.
  27.  
  28. Use this class extension with Widget or another Base-based superclass to create
  29. the basic DataTable model API and composing class structure.
  30.  
  31. @class DataTable.Core
  32. @for DataTable
  33. @since 3.5.0
  34. **/
  35. Table = Y.namespace('DataTable').Core = function () {};
  36.  
  37. Table.ATTRS = {
  38. /**
  39. Columns to include in the rendered table.
  40. If omitted, the attributes on the configured `recordType` or the first item
  41. in the `data` collection will be used as a source.
  42.  
  43. This attribute takes an array of strings or objects (mixing the two is
  44. fine). Each string or object is considered a column to be rendered.
  45. Strings are converted to objects, so `columns: ['first', 'last']` becomes
  46. `columns: [{ key: 'first' }, { key: 'last' }]`.
  47.  
  48. DataTable.Core only concerns itself with a few properties of columns.
  49. These properties are:
  50.  
  51. * `key` - Used to identify the record field/attribute containing content for
  52. this column. Also used to create a default Model if no `recordType` or
  53. `data` are provided during construction. If `name` is not specified, this
  54. is assigned to the `_id` property (with added incrementer if the key is
  55. used by multiple columns).
  56. * `children` - Traversed to initialize nested column objects
  57. * `name` - Used in place of, or in addition to, the `key`. Useful for
  58. columns that aren't bound to a field/attribute in the record data. This
  59. is assigned to the `_id` property.
  60. * `id` - For backward compatibility. Implementers can specify the id of
  61. the header cell. This should be avoided, if possible, to avoid the
  62. potential for creating DOM elements with duplicate IDs.
  63. * `field` - For backward compatibility. Implementers should use `name`.
  64. * `_id` - Assigned unique-within-this-instance id for a column. By order
  65. of preference, assumes the value of `name`, `key`, `id`, or `_yuid`.
  66. This is used by the rendering views as well as feature module
  67. as a means to identify a specific column without ambiguity (such as
  68. multiple columns using the same `key`.
  69. * `_yuid` - Guid stamp assigned to the column object.
  70. * `_parent` - Assigned to all child columns, referencing their parent
  71. column.
  72.  
  73. @attribute columns
  74. @type {Object[]|String[]}
  75. @default (from `recordType` ATTRS or first item in the `data`)
  76. @since 3.5.0
  77. **/
  78. columns: {
  79. // TODO: change to setter to clone input array/objects
  80. validator: isArray,
  81. setter: '_setColumns',
  82. getter: '_getColumns'
  83. },
  84.  
  85. /**
  86. Model subclass to use as the `model` for the ModelList stored in the `data`
  87. attribute.
  88.  
  89. If not provided, it will try really hard to figure out what to use. The
  90. following attempts will be made to set a default value:
  91. 1. If the `data` attribute is set with a ModelList instance and its `model`
  92. property is set, that will be used.
  93. 2. If the `data` attribute is set with a ModelList instance, and its
  94. `model` property is unset, but it is populated, the `ATTRS` of the
  95. `constructor of the first item will be used.
  96. 3. If the `data` attribute is set with a non-empty array, a Model subclass
  97. will be generated using the keys of the first item as its `ATTRS` (see
  98. the `_createRecordClass` method).
  99. 4. If the `columns` attribute is set, a Model subclass will be generated
  100. using the columns defined with a `key`. This is least desirable because
  101. columns can be duplicated or nested in a way that's not parsable.
  102. 5. If neither `data` nor `columns` is set or populated, a change event
  103. subscriber will listen for the first to be changed and try all over
  104. again.
  105.  
  106. @attribute recordType
  107. @type {Function}
  108. @default (see description)
  109. @since 3.5.0
  110. **/
  111. recordType: {
  112. getter: '_getRecordType',
  113. setter: '_setRecordType'
  114. },
  115.  
  116. /**
  117. The collection of data records to display. This attribute is a pass
  118. through to a `data` property, which is a ModelList instance.
  119.  
  120. If this attribute is passed a ModelList or subclass, it will be assigned to
  121. the property directly. If an array of objects is passed, a new ModelList
  122. will be created using the configured `recordType` as its `model` property
  123. and seeded with the array.
  124.  
  125. Retrieving this attribute will return the ModelList stored in the `data`
  126. property.
  127.  
  128. @attribute data
  129. @type {ModelList|Object[]}
  130. @default `new ModelList()`
  131. @since 3.5.0
  132. **/
  133. data: {
  134. valueFn: '_initData',
  135. setter : '_setData',
  136. lazyAdd: false
  137. },
  138.  
  139. /**
  140. Content for the `<table summary="ATTRIBUTE VALUE HERE">`. Values assigned
  141. to this attribute will be HTML escaped for security.
  142.  
  143. @attribute summary
  144. @type {String}
  145. @default '' (empty string)
  146. @since 3.5.0
  147. **/
  148. //summary: {},
  149.  
  150. /**
  151. HTML content of an optional `<caption>` element to appear above the table.
  152. Leave this config unset or set to a falsy value to remove the caption.
  153.  
  154. @attribute caption
  155. @type HTML
  156. @default '' (empty string)
  157. @since 3.5.0
  158. **/
  159. //caption: {},
  160.  
  161. /**
  162. Deprecated as of 3.5.0. Passes through to the `data` attribute.
  163.  
  164. WARNING: `get('recordset')` will NOT return a Recordset instance as of
  165. 3.5.0. This is a break in backward compatibility.
  166.  
  167. @attribute recordset
  168. @type {Object[]|Recordset}
  169. @deprecated Use the `data` attribute
  170. @since 3.5.0
  171. **/
  172. recordset: {
  173. setter: '_setRecordset',
  174. getter: '_getRecordset',
  175. lazyAdd: false
  176. },
  177.  
  178. /**
  179. Deprecated as of 3.5.0. Passes through to the `columns` attribute.
  180.  
  181. WARNING: `get('columnset')` will NOT return a Columnset instance as of
  182. 3.5.0. This is a break in backward compatibility.
  183.  
  184. @attribute columnset
  185. @type {Object[]}
  186. @deprecated Use the `columns` attribute
  187. @since 3.5.0
  188. **/
  189. columnset: {
  190. setter: '_setColumnset',
  191. getter: '_getColumnset',
  192. lazyAdd: false
  193. }
  194. };
  195.  
  196. Y.mix(Table.prototype, {
  197. // -- Instance properties -------------------------------------------------
  198. /**
  199. The ModelList that manages the table's data.
  200.  
  201. @property data
  202. @type {ModelList}
  203. @default undefined (initially unset)
  204. @since 3.5.0
  205. **/
  206. //data: null,
  207.  
  208. // -- Public methods ------------------------------------------------------
  209.  
  210. /**
  211. Gets the column configuration object for the given key, name, or index. For
  212. nested columns, `name` can be an array of indexes, each identifying the index
  213. of that column in the respective parent's "children" array.
  214.  
  215. If you pass a column object, it will be returned.
  216.  
  217. For columns with keys, you can also fetch the column with
  218. `instance.get('columns.foo')`.
  219.  
  220. @method getColumn
  221. @param {String|Number|Number[]} name Key, "name", index, or index array to
  222. identify the column
  223. @return {Object} the column configuration object
  224. @since 3.5.0
  225. **/
  226. getColumn: function (name) {
  227. var col, columns, i, len, cols;
  228.  
  229. if (isObject(name) && !isArray(name)) {
  230. // TODO: support getting a column from a DOM node - this will cross
  231. // the line into the View logic, so it should be relayed
  232.  
  233. // Assume an object passed in is already a column def
  234. col = name;
  235. } else {
  236. col = this.get('columns.' + name);
  237. }
  238.  
  239. if (col) {
  240. return col;
  241. }
  242.  
  243. columns = this.get('columns');
  244.  
  245. if (isNumber(name) || isArray(name)) {
  246. name = toArray(name);
  247. cols = columns;
  248.  
  249. for (i = 0, len = name.length - 1; cols && i < len; ++i) {
  250. cols = cols[name[i]] && cols[name[i]].children;
  251. }
  252.  
  253. return (cols && cols[name[i]]) || null;
  254. }
  255.  
  256. return null;
  257. },
  258.  
  259. /**
  260. Returns the Model associated to the record `id`, `clientId`, or index (not
  261. row index). If none of those yield a Model from the `data` ModelList, the
  262. arguments will be passed to the `view` instance's `getRecord` method
  263. if it has one.
  264.  
  265. If no Model can be found, `null` is returned.
  266.  
  267. @method getRecord
  268. @param {Number|String|Node} seed Record `id`, `clientId`, index, Node, or
  269. identifier for a row or child element
  270. @return {Model}
  271. @since 3.5.0
  272. **/
  273. getRecord: function (seed) {
  274. var record = this.data.getById(seed) || this.data.getByClientId(seed);
  275.  
  276. if (!record) {
  277. if (isNumber(seed)) {
  278. record = this.data.item(seed);
  279. }
  280. // TODO: this should be split out to base somehow
  281. if (!record && this.view && this.view.getRecord) {
  282. record = this.view.getRecord.apply(this.view, arguments);
  283. }
  284. }
  285.  
  286. return record || null;
  287. },
  288.  
  289. // -- Protected and private properties and methods ------------------------
  290.  
  291. /**
  292. This tells `Y.Base` that it should create ad-hoc attributes for config
  293. properties passed to DataTable's constructor. This is useful for setting
  294. configurations on the DataTable that are intended for the rendering View(s).
  295.  
  296. @property _allowAdHocAttrs
  297. @type Boolean
  298. @default true
  299. @protected
  300. @since 3.6.0
  301. **/
  302. _allowAdHocAttrs: true,
  303.  
  304. /**
  305. A map of column key to column configuration objects parsed from the
  306. `columns` attribute.
  307.  
  308. @property _columnMap
  309. @type {Object}
  310. @default undefined (initially unset)
  311. @protected
  312. @since 3.5.0
  313. **/
  314. //_columnMap: null,
  315.  
  316. /**
  317. The Node instance of the table containing the data rows. This is set when
  318. the table is rendered. It may also be set by progressive enhancement,
  319. though this extension does not provide the logic to parse from source.
  320.  
  321. @property _tableNode
  322. @type {Node}
  323. @default undefined (initially unset)
  324. @protected
  325. @since 3.5.0
  326. **/
  327. //_tableNode: null,
  328.  
  329. /**
  330. Updates the `_columnMap` property in response to changes in the `columns`
  331. attribute.
  332.  
  333. @method _afterColumnsChange
  334. @param {EventFacade} e The `columnsChange` event object
  335. @protected
  336. @since 3.5.0
  337. **/
  338. _afterColumnsChange: function (e) {
  339. this._setColumnMap(e.newVal);
  340. },
  341.  
  342. /**
  343. Updates the `modelList` attributes of the rendered views in response to the
  344. `data` attribute being assigned a new ModelList.
  345.  
  346. @method _afterDataChange
  347. @param {EventFacade} e the `dataChange` event
  348. @protected
  349. @since 3.5.0
  350. **/
  351. _afterDataChange: function (e) {
  352. var modelList = e.newVal;
  353.  
  354. this.data = e.newVal;
  355.  
  356. if (!this.get('columns') && modelList.size()) {
  357. // TODO: this will cause a re-render twice because the Views are
  358. // subscribed to columnsChange
  359. this._initColumns();
  360. }
  361. },
  362.  
  363. /**
  364. Assigns to the new recordType as the model for the data ModelList
  365.  
  366. @method _afterRecordTypeChange
  367. @param {EventFacade} e recordTypeChange event
  368. @protected
  369. @since 3.6.0
  370. **/
  371. _afterRecordTypeChange: function (e) {
  372. var data = this.data.toJSON();
  373.  
  374. this.data.model = e.newVal;
  375.  
  376. this.data.reset(data);
  377.  
  378. if (!this.get('columns') && data) {
  379. if (data.length) {
  380. this._initColumns();
  381. } else {
  382. this.set('columns', keys(e.newVal.ATTRS));
  383. }
  384. }
  385. },
  386.  
  387. /**
  388. Creates a Model subclass from an array of attribute names or an object of
  389. attribute definitions. This is used to generate a class suitable to
  390. represent the data passed to the `data` attribute if no `recordType` is
  391. set.
  392.  
  393. @method _createRecordClass
  394. @param {String[]|Object} attrs Names assigned to the Model subclass's
  395. `ATTRS` or its entire `ATTRS` definition object
  396. @return {Model}
  397. @protected
  398. @since 3.5.0
  399. **/
  400. _createRecordClass: function (attrs) {
  401. var ATTRS, i, len;
  402.  
  403. if (isArray(attrs)) {
  404. ATTRS = {};
  405.  
  406. for (i = 0, len = attrs.length; i < len; ++i) {
  407. ATTRS[attrs[i]] = {};
  408. }
  409. } else if (isObject(attrs)) {
  410. ATTRS = attrs;
  411. }
  412.  
  413. return Y.Base.create('record', Y.Model, [], null, { ATTRS: ATTRS });
  414. },
  415.  
  416. /**
  417. Tears down the instance.
  418.  
  419. @method destructor
  420. @protected
  421. @since 3.6.0
  422. **/
  423. destructor: function () {
  424. new Y.EventHandle(Y.Object.values(this._eventHandles)).detach();
  425. },
  426.  
  427. /**
  428. The getter for the `columns` attribute. Returns the array of column
  429. configuration objects if `instance.get('columns')` is called, or the
  430. specific column object if `instance.get('columns.columnKey')` is called.
  431.  
  432. @method _getColumns
  433. @param {Object[]} columns The full array of column objects
  434. @param {String} name The attribute name requested
  435. (e.g. 'columns' or 'columns.foo');
  436. @protected
  437. @since 3.5.0
  438. **/
  439. _getColumns: function (columns, name) {
  440. // Workaround for an attribute oddity (ticket #2529254)
  441. // getter is expected to return an object if get('columns.foo') is called.
  442. // Note 'columns.' is 8 characters
  443. return name.length > 8 ? this._columnMap : columns;
  444. },
  445.  
  446. /**
  447. Relays the `get()` request for the deprecated `columnset` attribute to the
  448. `columns` attribute.
  449.  
  450. THIS BREAKS BACKWARD COMPATIBILITY. 3.4.1 and prior implementations will
  451. expect a Columnset instance returned from `get('columnset')`.
  452.  
  453. @method _getColumnset
  454. @param {Object} ignored The current value stored in the `columnset` state
  455. @param {String} name The attribute name requested
  456. (e.g. 'columnset' or 'columnset.foo');
  457. @deprecated This will be removed with the `columnset` attribute in a future
  458. version.
  459. @protected
  460. @since 3.5.0
  461. **/
  462. _getColumnset: function (_, name) {
  463. return this.get(name.replace(/^columnset/, 'columns'));
  464. },
  465.  
  466. /**
  467. Returns the Model class of the instance's `data` attribute ModelList. If
  468. not set, returns the explicitly configured value.
  469.  
  470. @method _getRecordType
  471. @param {Model} val The currently configured value
  472. @return {Model}
  473. **/
  474. _getRecordType: function (val) {
  475. // Prefer the value stored in the attribute because the attribute
  476. // change event defaultFn sets e.newVal = this.get('recordType')
  477. // before notifying the after() subs. But if this getter returns
  478. // this.data.model, then after() subs would get e.newVal === previous
  479. // model before _afterRecordTypeChange can set
  480. // this.data.model = e.newVal
  481. return val || (this.data && this.data.model);
  482. },
  483.  
  484. /**
  485. Initializes the `_columnMap` property from the configured `columns`
  486. attribute. If `columns` is not set, but there are records in the `data`
  487. ModelList, use
  488. `ATTRS` of that class.
  489.  
  490. @method _initColumns
  491. @protected
  492. @since 3.5.0
  493. **/
  494. _initColumns: function () {
  495. var columns = this.get('columns') || [],
  496. item;
  497. // Default column definition from the configured recordType
  498. if (!columns.length && this.data.size()) {
  499. // TODO: merge superclass attributes up to Model?
  500. item = this.data.item(0);
  501.  
  502. if (item.toJSON) {
  503. item = item.toJSON();
  504. }
  505.  
  506. this.set('columns', keys(item));
  507. }
  508.  
  509. this._setColumnMap(columns);
  510. },
  511.  
  512. /**
  513. Sets up the change event subscriptions to maintain internal state.
  514.  
  515. @method _initCoreEvents
  516. @protected
  517. @since 3.6.0
  518. **/
  519. _initCoreEvents: function () {
  520. this._eventHandles.coreAttrChanges = this.after({
  521. columnsChange : Y.bind('_afterColumnsChange', this),
  522. recordTypeChange: Y.bind('_afterRecordTypeChange', this),
  523. dataChange : Y.bind('_afterDataChange', this)
  524. });
  525. },
  526.  
  527. /**
  528. Defaults the `data` attribute to an empty ModelList if not set during
  529. construction. Uses the configured `recordType` for the ModelList's `model`
  530. proeprty if set.
  531.  
  532. @method _initData
  533. @protected
  534. @return {ModelList}
  535. @since 3.6.0
  536. **/
  537. _initData: function () {
  538. var recordType = this.get('recordType'),
  539. // TODO: LazyModelList if recordType doesn't have complex ATTRS
  540. modelList = new Y.ModelList();
  541.  
  542. if (recordType) {
  543. modelList.model = recordType;
  544. }
  545.  
  546. return modelList;
  547. },
  548.  
  549. /**
  550. Initializes the instance's `data` property from the value of the `data`
  551. attribute. If the attribute value is a ModelList, it is assigned directly
  552. to `this.data`. If it is an array, a ModelList is created, its `model`
  553. property is set to the configured `recordType` class, and it is seeded with
  554. the array data. This ModelList is then assigned to `this.data`.
  555.  
  556. @method _initDataProperty
  557. @param {Array|ModelList|ArrayList} data Collection of data to populate the
  558. DataTable
  559. @protected
  560. @since 3.6.0
  561. **/
  562. _initDataProperty: function (data) {
  563. var recordType;
  564.  
  565. if (!this.data) {
  566. recordType = this.get('recordType');
  567.  
  568. if (data && data.each && data.toJSON) {
  569. this.data = data;
  570.  
  571. if (recordType) {
  572. this.data.model = recordType;
  573. }
  574. } else {
  575. // TODO: customize the ModelList or read the ModelList class
  576. // from a configuration option?
  577. this.data = new Y.ModelList();
  578. if (recordType) {
  579. this.data.model = recordType;
  580. }
  581. }
  582.  
  583. // TODO: Replace this with an event relay for specific events.
  584. // Using bubbling causes subscription conflicts with the models'
  585. // aggregated change event and 'change' events from DOM elements
  586. // inside the table (via Widget UI event).
  587. this.data.addTarget(this);
  588. }
  589. },
  590.  
  591. /**
  592. Initializes the columns, `recordType` and data ModelList.
  593.  
  594. @method initializer
  595. @param {Object} config Configuration object passed to constructor
  596. @protected
  597. @since 3.5.0
  598. **/
  599. initializer: function (config) {
  600. var data = config.data,
  601. columns = config.columns,
  602. recordType;
  603.  
  604. // Referencing config.data to allow _setData to be more stringent
  605. // about its behavior
  606. this._initDataProperty(data);
  607.  
  608. // Default columns from recordType ATTRS if recordType is supplied at
  609. // construction. If no recordType is supplied, but the data is
  610. // supplied as a non-empty array, use the keys of the first item
  611. // as the columns.
  612. if (!columns) {
  613. recordType = (config.recordType || config.data === this.data) &&
  614. this.get('recordType');
  615.  
  616. if (recordType) {
  617. columns = keys(recordType.ATTRS);
  618. } else if (isArray(data) && data.length) {
  619. columns = keys(data[0]);
  620. }
  621.  
  622. if (columns) {
  623. this.set('columns', columns);
  624. }
  625. }
  626.  
  627. this._initColumns();
  628.  
  629. this._eventHandles = {};
  630.  
  631. this._initCoreEvents();
  632. },
  633.  
  634. /**
  635. Iterates the array of column configurations to capture all columns with a
  636. `key` property. An map is built with column keys as the property name and
  637. the corresponding column object as the associated value. This map is then
  638. assigned to the instance's `_columnMap` property.
  639.  
  640. @method _setColumnMap
  641. @param {Object[]|String[]} columns The array of column config objects
  642. @protected
  643. @since 3.6.0
  644. **/
  645. _setColumnMap: function (columns) {
  646. var map = {};
  647. function process(cols) {
  648. var i, len, col, key;
  649.  
  650. for (i = 0, len = cols.length; i < len; ++i) {
  651. col = cols[i];
  652. key = col.key;
  653.  
  654. // First in wins for multiple columns with the same key
  655. // because the first call to genId (in _setColumns) will
  656. // return the same key, which will then be overwritten by the
  657. // subsequent same-keyed column. So table.getColumn(key) would
  658. // return the last same-keyed column.
  659. if (key && !map[key]) {
  660. map[key] = col;
  661. }
  662.  
  663. //TODO: named columns can conflict with keyed columns
  664. map[col._id] = col;
  665.  
  666. if (col.children) {
  667. process(col.children);
  668. }
  669. }
  670. }
  671.  
  672. process(columns);
  673.  
  674. this._columnMap = map;
  675. },
  676.  
  677. /**
  678. Translates string columns into objects with that string as the value of its
  679. `key` property.
  680.  
  681. All columns are assigned a `_yuid` stamp and `_id` property corresponding
  682. to the column's configured `name` or `key` property with any spaces
  683. replaced with dashes. If the same `name` or `key` appears in multiple
  684. columns, subsequent appearances will have their `_id` appended with an
  685. incrementing number (e.g. if column "foo" is included in the `columns`
  686. attribute twice, the first will get `_id` of "foo", and the second an `_id`
  687. of "foo1"). Columns that are children of other columns will have the
  688. `_parent` property added, assigned the column object to which they belong.
  689.  
  690. @method _setColumns
  691. @param {null|Object[]|String[]} val Array of config objects or strings
  692. @return {null|Object[]}
  693. @protected
  694. **/
  695. _setColumns: function (val) {
  696. var keys = {},
  697. known = [],
  698. knownCopies = [],
  699. arrayIndex = Y.Array.indexOf;
  700. function copyObj(o) {
  701. var copy = {},
  702. key, val, i;
  703.  
  704. known.push(o);
  705. knownCopies.push(copy);
  706.  
  707. for (key in o) {
  708. if (o.hasOwnProperty(key)) {
  709. val = o[key];
  710.  
  711. if (isArray(val)) {
  712. copy[key] = val.slice();
  713. } else if (isObject(val, true)) {
  714. i = arrayIndex(val, known);
  715.  
  716. copy[key] = i === -1 ? copyObj(val) : knownCopies[i];
  717. } else {
  718. copy[key] = o[key];
  719. }
  720. }
  721. }
  722.  
  723. return copy;
  724. }
  725.  
  726. function genId(name) {
  727. // Sanitize the name for use in generated CSS classes.
  728. // TODO: is there more to do for other uses of _id?
  729. name = name.replace(/\s+/, '-');
  730.  
  731. if (keys[name]) {
  732. name += (keys[name]++);
  733. } else {
  734. keys[name] = 1;
  735. }
  736.  
  737. return name;
  738. }
  739.  
  740. function process(cols, parent) {
  741. var columns = [],
  742. i, len, col, yuid;
  743.  
  744. for (i = 0, len = cols.length; i < len; ++i) {
  745. columns[i] = // chained assignment
  746. col = isString(cols[i]) ? { key: cols[i] } : copyObj(cols[i]);
  747.  
  748. yuid = Y.stamp(col);
  749.  
  750. // For backward compatibility
  751. if (!col.id) {
  752. // Implementers can shoot themselves in the foot by setting
  753. // this config property to a non-unique value
  754. col.id = yuid;
  755. }
  756. if (col.field) {
  757. // Field is now known as "name" to avoid confusion with data
  758. // fields or schema.resultFields
  759. col.name = col.field;
  760. }
  761.  
  762. if (parent) {
  763. col._parent = parent;
  764. } else {
  765. delete col._parent;
  766. }
  767.  
  768. // Unique id based on the column's configured name or key,
  769. // falling back to the yuid. Duplicates will have a counter
  770. // added to the end.
  771. col._id = genId(col.name || col.key || col.id);
  772.  
  773. if (isArray(col.children)) {
  774. col.children = process(col.children, col);
  775. }
  776. }
  777.  
  778. return columns;
  779. }
  780.  
  781. return val && process(val);
  782. },
  783.  
  784. /**
  785. Relays attribute assignments of the deprecated `columnset` attribute to the
  786. `columns` attribute. If a Columnset is object is passed, its basic object
  787. structure is mined.
  788.  
  789. @method _setColumnset
  790. @param {Array|Columnset} val The columnset value to relay
  791. @deprecated This will be removed with the deprecated `columnset` attribute
  792. in a later version.
  793. @protected
  794. @since 3.5.0
  795. **/
  796. _setColumnset: function (val) {
  797. this.set('columns', val);
  798.  
  799. return isArray(val) ? val : INVALID;
  800. },
  801.  
  802. /**
  803. Accepts an object with `each` and `getAttrs` (preferably a ModelList or
  804. subclass) or an array of data objects. If an array is passes, it will
  805. create a ModelList to wrap the data. In doing so, it will set the created
  806. ModelList's `model` property to the class in the `recordType` attribute,
  807. which will be defaulted if not yet set.
  808.  
  809. If the `data` property is already set with a ModelList, passing an array as
  810. the value will call the ModelList's `reset()` method with that array rather
  811. than replacing the stored ModelList wholesale.
  812.  
  813. Any non-ModelList-ish and non-array value is invalid.
  814.  
  815. @method _setData
  816. @protected
  817. @since 3.5.0
  818. **/
  819. _setData: function (val) {
  820. if (val === null) {
  821. val = [];
  822. }
  823.  
  824. if (isArray(val)) {
  825. this._initDataProperty();
  826.  
  827. // silent to prevent subscribers to both reset and dataChange
  828. // from reacting to the change twice.
  829. // TODO: would it be better to return INVALID to silence the
  830. // dataChange event, or even allow both events?
  831. this.data.reset(val, { silent: true });
  832.  
  833. // Return the instance ModelList to avoid storing unprocessed
  834. // data in the state and their vivified Model representations in
  835. // the instance's data property. Decreases memory consumption.
  836. val = this.data;
  837. } else if (!val || !val.each || !val.toJSON) {
  838. // ModelList/ArrayList duck typing
  839. val = INVALID;
  840. }
  841.  
  842. return val;
  843. },
  844.  
  845. /**
  846. Relays the value assigned to the deprecated `recordset` attribute to the
  847. `data` attribute. If a Recordset instance is passed, the raw object data
  848. will be culled from it.
  849.  
  850. @method _setRecordset
  851. @param {Object[]|Recordset} val The recordset value to relay
  852. @deprecated This will be removed with the deprecated `recordset` attribute
  853. in a later version.
  854. @protected
  855. @since 3.5.0
  856. **/
  857. _setRecordset: function (val) {
  858. var data;
  859.  
  860. if (val && Y.Recordset && val instanceof Y.Recordset) {
  861. data = [];
  862. val.each(function (record) {
  863. data.push(record.get('data'));
  864. });
  865. val = data;
  866. }
  867.  
  868. this.set('data', val);
  869.  
  870. return val;
  871. },
  872.  
  873. /**
  874. Accepts a Base subclass (preferably a Model subclass). Alternately, it will
  875. generate a custom Model subclass from an array of attribute names or an
  876. object defining attributes and their respective configurations (it is
  877. assigned as the `ATTRS` of the new class).
  878.  
  879. Any other value is invalid.
  880.  
  881. @method _setRecordType
  882. @param {Function|String[]|Object} val The Model subclass, array of
  883. attribute names, or the `ATTRS` definition for a custom model
  884. subclass
  885. @return {Function} A Base/Model subclass
  886. @protected
  887. @since 3.5.0
  888. **/
  889. _setRecordType: function (val) {
  890. var modelClass;
  891.  
  892. // Duck type based on known/likely consumed APIs
  893. if (isFunction(val) && val.prototype.toJSON && val.prototype.setAttrs) {
  894. modelClass = val;
  895. } else if (isObject(val)) {
  896. modelClass = this._createRecordClass(val);
  897. }
  898.  
  899. return modelClass || INVALID;
  900. }
  901.  
  902. });
  903.