API Docs for: 3.8.0
Show:

File: datatable/js/mutable.js

  1. /**
  2. Adds mutation convenience methods such as `table.addRow(data)` to `Y.DataTable`. (or other built class).

  3. @module datatable
  4. @submodule datatable-mutable
  5. @since 3.5.0
  6. **/
  7. var toArray = Y.Array,
  8.     YLang   = Y.Lang,
  9.     isString = YLang.isString,
  10.     isArray  = YLang.isArray,
  11.     isObject = YLang.isObject,
  12.     isNumber = YLang.isNumber,
  13.     arrayIndex = Y.Array.indexOf,
  14.     Mutable;

  15. /**
  16. _API docs for this extension are included in the DataTable class._

  17. Class extension to add mutation convenience methods to `Y.DataTable` (or other
  18. built class).

  19. Column mutation methods are paired with new custom events:

  20.  * addColumn
  21.  * removeColumn
  22.  * modifyColumn
  23.  * moveColumn

  24. Row mutation events are bubbled from the DataTable's `data` ModelList through
  25. the DataTable instance.

  26. @class DataTable.Mutable
  27. @for DataTable
  28. @since 3.5.0
  29. **/
  30. Y.namespace('DataTable').Mutable = Mutable = function () {};

  31. Mutable.ATTRS = {
  32.     /**
  33.     Controls whether `addRow`, `removeRow`, and `modifyRow` should trigger the
  34.     underlying Model's sync layer by default.

  35.     When `true`, it is unnecessary to pass the "sync" configuration property to
  36.     those methods to trigger per-operation sync.


  37.     @attribute autoSync
  38.     @type {Boolean}
  39.     @default `false`
  40.     @since 3.5.0
  41.     **/
  42.     autoSync: {
  43.         value: false,
  44.         validator: YLang.isBoolean
  45.     }
  46. };

  47. Y.mix(Mutable.prototype, {
  48.     /**
  49.     Adds the column configuration to the DataTable's `columns` configuration.
  50.     If the `index` parameter is supplied, it is injected at that index.  If the
  51.     table has nested headers, inject a subcolumn by passing an array of indexes
  52.     to identify the new column's final location.

  53.     The `index` parameter is required if adding a nested column.

  54.     This method is a convienience method for fetching the DataTable's `columns`
  55.     attribute, updating it, and calling
  56.     `table.set('columns', _updatedColumnsDefs_)`

  57.     For example:

  58.     <pre><code>// Becomes last column
  59.     table.addColumn('name');

  60.     // Inserted after the current second column, moving the current third column
  61.     // to index 4
  62.     table.addColumn({ key: 'price', formatter: currencyFormatter }, 2 );

  63.     // Insert a new column in a set of headers three rows deep.  The index array
  64.     // translates to
  65.     // [ 2, --  in the third column's children
  66.     //   1, --  in the second child's children
  67.     //   3 ] -- as the fourth child column
  68.     table.addColumn({ key: 'age', sortable: true }, [ 2, 1, 3 ]);
  69.     </code></pre>

  70.     @method addColumn
  71.     @param {Object|String} config The new column configuration object
  72.     @param {Number|Number[]} [index] the insertion index
  73.     @return {DataTable}
  74.     @chainable
  75.     @since 3.5.0
  76.     **/
  77.     addColumn: function (config, index) {
  78.         if (isString(config)) {
  79.             config = { key: config };
  80.         }

  81.         if (config) {
  82.             if (arguments.length < 2 || (!isNumber(index) && !isArray(index))) {
  83.                 index = this.get('columns').length;
  84.             }

  85.             this.fire('addColumn', {
  86.                 column: config,
  87.                 index: index
  88.             });
  89.         }
  90.         return this;
  91.     },

  92.     /**
  93.     Updates an existing column definition. Fires the `modifyColumn` event.

  94.     For example:

  95.     <pre><code>// Add a formatter to the existing 'price' column definition
  96.     table.modifyColumn('price', { formatter: currencyFormatter });

  97.     // Change the label on a header cell in a set of nested headers three rows
  98.     // deep.  The index array translates to
  99.     // [ 2,  -- in the third column's children
  100.     //   1,  -- the second child
  101.     //   3 ] -- the fourth child column
  102.     table.modifyColumn([2, 1, 3], { label: 'Experience' });
  103.     </code></pre>

  104.     @method modifyColumn
  105.     @param {String|Number|Number[]|Object} name The column key, name, index, or
  106.                 current configuration object
  107.     @param {Object} config The new column configuration properties
  108.     @return {DataTable}
  109.     @chainable
  110.     @since 3.5.0
  111.     **/
  112.     modifyColumn: function (name, config) {
  113.         if (isString(config)) {
  114.             config = { key: config };
  115.         }

  116.         if (isObject(config)) {
  117.             this.fire('modifyColumn', {
  118.                 column: name,
  119.                 newColumnDef: config
  120.             });
  121.         }

  122.         return this;
  123.     },

  124.     /**
  125.     Moves an existing column to a new location. Fires the `moveColumn` event.

  126.     The destination index can be a number or array of numbers to place a column
  127.     header in a nested header row.

  128.     @method moveColumn
  129.     @param {String|Number|Number[]|Object} name The column key, name, index, or
  130.                 current configuration object
  131.     @param {Number|Number[]} index The destination index of the column
  132.     @return {DataTable}
  133.     @chainable
  134.     @since 3.5.0
  135.     **/
  136.     moveColumn: function (name, index) {
  137.         if (name !== undefined && (isNumber(index) || isArray(index))) {
  138.             this.fire('moveColumn', {
  139.                 column: name,
  140.                 index: index
  141.             });
  142.         }

  143.         return this;
  144.     },

  145.     /**
  146.     Removes an existing column. Fires the `removeColumn` event.

  147.     @method removeColumn
  148.     @param {String|Number|Number[]|Object} name The column key, name, index, or
  149.                 current configuration object
  150.     @return {DataTable}
  151.     @chainable
  152.     @since 3.5.0
  153.     **/
  154.     removeColumn: function (name) {
  155.         if (name !== undefined) {
  156.             this.fire('removeColumn', {
  157.                 column: name
  158.             });
  159.         }

  160.         return this;
  161.     },

  162.     /**
  163.     Adds a new record to the DataTable's `data` ModelList.  Record data can be
  164.     an object of field values or an instance of the DataTable's configured
  165.     `recordType` class.

  166.     This relays all parameters to the `data` ModelList's `add` method.

  167.     If a configuration object is passed as a second argument, and that object
  168.     has `sync: true` set, the underlying Model will be `save()`d.

  169.     If the DataTable's `autoSync` attribute is set to `true`, the additional
  170.     argument is not needed.

  171.     If syncing and the last argument is a function, that function will be used
  172.     as a callback to the Model's `save()` method.

  173.     @method addRow
  174.     @param {Object} data The data or Model instance for the new record
  175.     @param {Object} [config] Configuration to pass along
  176.     @param {Function} [callback] Callback function for Model's `save()`
  177.       @param {Error|null} callback.err If an error occurred or validation
  178.         failed, this parameter will contain the error. If the sync operation
  179.         succeeded, _err_ will be `null`.
  180.       @param {Any} callback.response The server's response. This value will
  181.         be passed to the `parse()` method, which is expected to parse it and
  182.         return an attribute hash.
  183.     @return {DataTable}
  184.     @chainable
  185.     @since 3.5.0
  186.     **/
  187.     addRow: function (data, config) {
  188.         // Allow autoSync: true + addRow({ data }, { sync: false })
  189.         var sync = (config && ('sync' in config)) ?
  190.                 config.sync :
  191.                 this.get('autoSync'),
  192.             models, model, i, len, args;

  193.         if (data && this.data) {
  194.             models = this.data.add.apply(this.data, arguments);

  195.             if (sync) {
  196.                 models = toArray(models);
  197.                 args   = toArray(arguments, 1, true);

  198.                 for (i = 0, len = models.length; i < len; ++i) {
  199.                     model = models[i];

  200.                     if (model.isNew()) {
  201.                         models[i].save.apply(models[i], args);
  202.                     }
  203.                 }
  204.             }
  205.         }

  206.         return this;
  207.     },

  208.     /**
  209.     Removes a record from the DataTable's `data` ModelList.  The record can be
  210.     provided explicitly or targeted by it's `id` (see ModelList's `getById`
  211.     method), `clientId`, or index in the ModelList.

  212.     After locating the target Model, this relays the Model and all other passed
  213.     arguments to the `data` ModelList's `remove` method.

  214.     If a configuration object is passed as a second argument, and that object
  215.     has `sync: true` set, the underlying Model will be destroyed, passing
  216.     `{ delete: true }` to trigger calling the Model's sync layer.

  217.     If the DataTable's `autoSync` attribute is set to `true`, the additional
  218.     argument is not needed.

  219.     If syncing and the last argument is a function, that function will be used
  220.     as a callback to the Model's `destroy()` method.

  221.     @method removeRow
  222.     @param {Object|String|Number} id The Model instance or identifier
  223.     @param {Object} [config] Configuration to pass along
  224.     @param {Function} [callback] Callback function for Model's `save()`
  225.       @param {Error|null} callback.err If an error occurred or validation
  226.         failed, this parameter will contain the error. If the sync operation
  227.         succeeded, _err_ will be `null`.
  228.       @param {Any} callback.response The server's response. This value will
  229.         be passed to the `parse()` method, which is expected to parse it and
  230.         return an attribute hash.
  231.     @return {DataTable}
  232.     @chainable
  233.     @since 3.5.0
  234.     **/
  235.     removeRow: function (id, config) {
  236.         var modelList = this.data,
  237.             // Allow autoSync: true + addRow({ data }, { sync: false })
  238.             sync      = (config && ('sync' in config)) ?
  239.                             config.sync :
  240.                             this.get('autoSync'),
  241.             models, model, i, len, args;

  242.         // TODO: support removing via DOM element. This should be relayed to View
  243.         if (isObject(id) && id instanceof this.get('recordType')) {
  244.             model = id;
  245.         } else if (modelList && id !== undefined) {
  246.             model = modelList.getById(id) ||
  247.                     modelList.getByClientId(id) ||
  248.                     modelList.item(id);
  249.         }

  250.         if (model) {
  251.             args = toArray(arguments, 1, true);

  252.             models = modelList.remove.apply(modelList,
  253.                 [model].concat(args));

  254.             if (sync) {
  255.                 if (!isObject(args[0])) {
  256.                     args.unshift({});
  257.                 }

  258.                 args[0]['delete'] = true;

  259.                 models = toArray(models);

  260.                 for (i = 0, len = models.length; i < len; ++i) {
  261.                     model = models[i];
  262.                     model.destroy.apply(model, args);
  263.                 }
  264.             }
  265.         }

  266.         return this;
  267.     },

  268.     /**
  269.     Updates an existing record in the DataTable's `data` ModelList.  The record
  270.     can be provided explicitly or targeted by it's `id` (see ModelList's
  271.     `getById` method), `clientId`, or index in the ModelList.

  272.     After locating the target Model, this relays the all other passed
  273.     arguments to the Model's `setAttrs` method.

  274.     If a configuration object is passed as a second argument, and that object
  275.     has `sync: true` set, the underlying Model will be `save()`d.

  276.     If the DataTable's `autoSync` attribute is set to `true`, the additional
  277.     argument is not needed.

  278.     If syncing and the last argument is a function, that function will be used
  279.     as a callback to the Model's `save()` method.

  280.     @method modifyRow
  281.     @param {Object|String|Number} id The Model instance or identifier
  282.     @param {Object} data New data values for the Model
  283.     @param {Object} [config] Configuration to pass along to `setAttrs()`
  284.     @param {Function} [callback] Callback function for Model's `save()`
  285.       @param {Error|null} callback.err If an error occurred or validation
  286.         failed, this parameter will contain the error. If the sync operation
  287.         succeeded, _err_ will be `null`.
  288.       @param {Any} callback.response The server's response. This value will
  289.         be passed to the `parse()` method, which is expected to parse it and
  290.         return an attribute hash.
  291.     @return {DataTable}
  292.     @chainable
  293.     @since 3.5.0
  294.     **/
  295.     modifyRow: function (id, data, config) {
  296.         var modelList = this.data,
  297.             // Allow autoSync: true + addRow({ data }, { sync: false })
  298.             sync      = (config && ('sync' in config)) ?
  299.                             config.sync :
  300.                             this.get('autoSync'),
  301.             model, args;

  302.         if (isObject(id) && id instanceof this.get('recordType')) {
  303.             model = id;
  304.         } else if (modelList && id !== undefined) {
  305.             model = modelList.getById(id) ||
  306.                     modelList.getByClientId(id) ||
  307.                     modelList.item(id);
  308.         }

  309.         if (model && isObject(data)) {
  310.             args = toArray(arguments, 1, true);

  311.             model.setAttrs.apply(model, args);

  312.             if (sync && !model.isNew()) {
  313.                 model.save.apply(model, args);
  314.             }
  315.         }

  316.         return this;
  317.     },

  318.     // --------------------------------------------------------------------------
  319.     // Protected properties and methods
  320.     // --------------------------------------------------------------------------

  321.     /**
  322.     Default function for the `addColumn` event.

  323.     Inserts the specified column at the provided index.

  324.     @method _defAddColumnFn
  325.     @param {EventFacade} e The `addColumn` event
  326.         @param {Object} e.column The new column definition object
  327.         @param {Number|Number[]} e.index The array index to insert the new column
  328.     @protected
  329.     @since 3.5.0
  330.     **/
  331.     _defAddColumnFn: function (e) {
  332.         var index   = toArray(e.index),
  333.             columns = this.get('columns'),
  334.             cols    = columns,
  335.             i, len;

  336.         for (i = 0, len = index.length - 1; cols && i < len; ++i) {
  337.             cols = cols[index[i]] && cols[index[i]].children;
  338.         }

  339.         if (cols) {
  340.             cols.splice(index[i], 0, e.column);

  341.             this.set('columns', columns, { originEvent: e });
  342.         } else { Y.log('addColumn index not findable', 'warn', 'datatable');
  343.         }
  344.     },

  345.     /**
  346.     Default function for the `modifyColumn` event.

  347.     Mixes the new column properties into the specified column definition.

  348.     @method _defModifyColumnFn
  349.     @param {EventFacade} e The `modifyColumn` event
  350.         @param {Object|String|Number|Number[]} e.column The column definition object or identifier
  351.         @param {Object} e.newColumnDef The properties to assign to the column
  352.     @protected
  353.     @since 3.5.0
  354.     **/
  355.     _defModifyColumnFn: function (e) {
  356.         var columns = this.get('columns'),
  357.             column  = this.getColumn(e.column);

  358.         if (column) {
  359.             Y.mix(column, e.newColumnDef, true);
  360.            
  361.             this.set('columns', columns, { originEvent: e });
  362.         } else { Y.log('Could not locate column index to modify column', 'warn', 'datatable');
  363.         }
  364.     },

  365.     /**
  366.     Default function for the `moveColumn` event.

  367.     Removes the specified column from its current location and inserts it at the
  368.     specified array index (may be an array of indexes for nested headers).

  369.     @method _defMoveColumnFn
  370.     @param {EventFacade} e The `moveColumn` event
  371.         @param {Object|String|Number|Number[]} e.column The column definition object or identifier
  372.         @param {Object} e.index The destination index to move to
  373.     @protected
  374.     @since 3.5.0
  375.     **/
  376.     _defMoveColumnFn: function (e) {
  377.         var columns = this.get('columns'),
  378.             column  = this.getColumn(e.column),
  379.             toIndex = toArray(e.index),
  380.             fromCols, fromIndex, toCols, i, len;

  381.         if (column) {
  382.             fromCols  = column._parent ? column._parent.children : columns;
  383.             fromIndex = arrayIndex(fromCols, column);

  384.             if (fromIndex > -1) {
  385.                 toCols = columns;

  386.                 for (i = 0, len = toIndex.length - 1; toCols && i < len; ++i) {
  387.                     toCols = toCols[toIndex[i]] && toCols[toIndex[i]].children;
  388.                 }

  389.                 if (toCols) {
  390.                     len = toCols.length;
  391.                     fromCols.splice(fromIndex, 1);
  392.                     toIndex = toIndex[i];

  393.                     if (len > toCols.lenth) {
  394.                         // spliced off the same array, so adjust destination
  395.                         // index if necessary
  396.                         if (fromIndex < toIndex) {
  397.                             toIndex--;
  398.                         }
  399.                     }

  400.                     toCols.splice(toIndex, 0, column);

  401.                     this.set('columns', columns, { originEvent: e });
  402.                 } else { Y.log('Column [' + e.column + '] could not be moved. Destination index invalid for moveColumn', 'warn', 'datatable');
  403.                 }
  404.             }
  405.         } else { Y.log('Column [' + e.column + '] not found for moveColumn', 'warn', 'datatable');
  406.         }
  407.     },

  408.     /**
  409.     Default function for the `removeColumn` event.

  410.     Splices the specified column from its containing columns array.

  411.     @method _defRemoveColumnFn
  412.     @param {EventFacade} e The `removeColumn` event
  413.         @param {Object|String|Number|Number[]} e.column The column definition object or identifier
  414.     @protected
  415.     @since 3.5.0
  416.     **/
  417.     _defRemoveColumnFn: function (e) {
  418.         var columns = this.get('columns'),
  419.             column  = this.getColumn(e.column),
  420.             cols, index;

  421.         if (column) {
  422.             cols = column._parent ? column._parent.children : columns;
  423.             index = Y.Array.indexOf(cols, column);

  424.             if (index > -1) {
  425.                 cols.splice(index, 1);

  426.                 this.set('columns', columns, { originEvent: e });
  427.             }
  428.         } else { Y.log('Could not locate column [' + e.column + '] for removeColumn', 'warn', 'datatable');
  429.         }
  430.     },

  431.     /**
  432.     Publishes the events used by the mutation methods:

  433.      * addColumn
  434.      * removeColumn
  435.      * modifyColumn
  436.      * moveColumn

  437.     @method initializer
  438.     @protected
  439.     @since 3.5.0
  440.     **/
  441.     initializer: function () {
  442.         this.publish({
  443.             addColumn:    { defaultFn: Y.bind('_defAddColumnFn', this) },
  444.             removeColumn: { defaultFn: Y.bind('_defRemoveColumnFn', this) },
  445.             moveColumn:   { defaultFn: Y.bind('_defMoveColumnFn', this) },
  446.             modifyColumn: { defaultFn: Y.bind('_defModifyColumnFn', this) }
  447.         });
  448.     }
  449. });

  450. /**
  451. Adds an array of new records to the DataTable's `data` ModelList.  Record data
  452. can be an array of objects containing field values or an array of instance of
  453. the DataTable's configured `recordType` class.

  454. This relays all parameters to the `data` ModelList's `add` method.

  455. Technically, this is an alias to `addRow`, but please use the appropriately
  456. named method for readability.

  457. If a configuration object is passed as a second argument, and that object
  458. has `sync: true` set, the underlying Models will be `save()`d.

  459. If the DataTable's `autoSync` attribute is set to `true`, the additional
  460. argument is not needed.

  461. If syncing and the last argument is a function, that function will be used
  462. as a callback to each Model's `save()` method.

  463. @method addRows
  464. @param {Object[]} data The data or Model instances to add
  465. @param {Object} [config] Configuration to pass along
  466. @param {Function} [callback] Callback function for each Model's `save()`
  467.   @param {Error|null} callback.err If an error occurred or validation
  468.     failed, this parameter will contain the error. If the sync operation
  469.     succeeded, _err_ will be `null`.
  470.   @param {Any} callback.response The server's response. This value will
  471.     be passed to the `parse()` method, which is expected to parse it and
  472.     return an attribute hash.
  473. @return {DataTable}
  474. @chainable
  475. @since 3.5.0
  476. **/
  477. Mutable.prototype.addRows = Mutable.prototype.addRow;

  478. // Add feature APIs to public Y.DataTable class
  479. if (YLang.isFunction(Y.DataTable)) {
  480.     Y.Base.mix(Y.DataTable, [Mutable]);
  481. }

  482. /**
  483. Fired by the `addColumn` method.

  484. @event addColumn
  485. @preventable _defAddColumnFn
  486. @param {Object} column The new column definition object
  487. @param {Number|Number[]} index The array index to insert the new column
  488. @since 3.5.0
  489. **/

  490. /**
  491. Fired by the `removeColumn` method.

  492. @event removeColumn
  493. @preventable _defRemoveColumnFn
  494. @param {Object|String|Number|Number[]} column The column definition object or identifier
  495. @since 3.5.0
  496. **/

  497. /**
  498. Fired by the `modifyColumn` method.

  499. @event modifyColumn
  500. @preventable _defModifyColumnFn
  501. @param {Object|String|Number|Number[]} column The column definition object or identifier
  502. @param {Object} newColumnDef The properties to assign to the column
  503. @since 3.5.0
  504. **/

  505. /**
  506. Fired by the `moveColumn` method.

  507. @event moveColumn
  508. @preventable _defMoveColumnFn
  509. @param {Object|String|Number|Number[]} column The column definition object or identifier
  510. @param {Object} index The destination index to move to
  511. @since 3.5.0
  512. **/


  513.