JAVASCRIPT   9

datatable-sort.js

Guest on 24th November 2021 08:02:25 PM

  1. /*
  2. YUI 3.15.0 (build 834026e)
  3. Copyright  Yahoo! Inc. All rights reserved.
  4. Licensed under the BSD License.
  5. http://yuilibrary.com/license/
  6. */
  7.  
  8. YUI.add('datatable-sort', function (Y, NAME) {
  9.  
  10. /**
  11. Adds support for sorting the table data by API methods `table.sort(...)` or
  12. `table.toggleSort(...)` or by clicking on column headers in the rendered UI.
  13.  
  14. @module datatable
  15. @submodule datatable-sort
  16. @since 3.5.0
  17. **/
  18. var YLang     = Y.Lang,
  19.     isBoolean = YLang.isBoolean,
  20.     isString  = YLang.isString,
  21.     isArray   = YLang.isArray,
  22.     isObject  = YLang.isObject,
  23.  
  24.     toArray = Y.Array,
  25.     sub     = YLang.sub,
  26.  
  27.     dirMap = {
  28.         asc : 1,
  29.         desc: -1,
  30.         "1" : 1,
  31.         "-1": -1
  32.     };
  33.  
  34.  
  35. /**
  36. _API docs for this extension are included in the DataTable class._
  37.  
  38. This DataTable class extension adds support for sorting the table data by API
  39. methods `table.sort(...)` or `table.toggleSort(...)` or by clicking on column
  40. headers in the rendered UI.
  41.  
  42. Sorting by the API is enabled automatically when this module is `use()`d.  To
  43. enable UI triggered sorting, set the DataTable's `sortable` attribute to
  44. `true`.
  45.  
  46. <pre><code>
  47. var table = new Y.DataTable({
  48.     columns: [ 'id', 'username', 'name', 'birthdate' ],
  49.     data: [ ... ],
  50.     sortable: true
  51. });
  52.  
  53. table.render('#table');
  54. </code></pre>
  55.  
  56. Setting `sortable` to `true` will enable UI sorting for all columns.  To enable
  57. UI sorting for certain columns only, set `sortable` to an array of column keys,
  58. or just add `sortable: true` to the respective column configuration objects.
  59. This uses the default setting of `sortable: auto` for the DataTable instance.
  60.  
  61. <pre><code>
  62. var table = new Y.DataTable({
  63.     columns: [
  64.         'id',
  65.         { key: 'username',  sortable: true },
  66.         { key: 'name',      sortable: true },
  67.         { key: 'birthdate', sortable: true }
  68.     ],
  69.     data: [ ... ]
  70.     // sortable: 'auto' is the default
  71. });
  72.  
  73. // OR
  74. var table = new Y.DataTable({
  75.     columns: [ 'id', 'username', 'name', 'birthdate' ],
  76.     data: [ ... ],
  77.     sortable: [ 'username', 'name', 'birthdate' ]
  78. });
  79. </code></pre>
  80.  
  81. To disable UI sorting for all columns, set `sortable` to `false`.  This still
  82. permits sorting via the API methods.
  83.  
  84. As new records are inserted into the table's `data` ModelList, they will be inserted at the correct index to preserve the sort order.
  85.  
  86. The current sort order is stored in the `sortBy` attribute.  Assigning this value at instantiation will automatically sort your data.
  87.  
  88. Sorting is done by a simple value comparison using &lt; and &gt; on the field
  89. value.  If you need custom sorting, add a sort function in the column's
  90. `sortFn` property.  Columns whose content is generated by formatters, but don't
  91. relate to a single `key`, require a `sortFn` to be sortable.
  92.  
  93. <pre><code>
  94. function nameSort(a, b, desc) {
  95.     var aa = a.get('lastName') + a.get('firstName'),
  96.         bb = a.get('lastName') + b.get('firstName'),
  97.         order = (aa > bb) ? 1 : -(aa < bb);
  98.  
  99.     return desc ? -order : order;
  100. }
  101.  
  102. var table = new Y.DataTable({
  103.     columns: [ 'id', 'username', { key: name, sortFn: nameSort }, 'birthdate' ],
  104.     data: [ ... ],
  105.     sortable: [ 'username', 'name', 'birthdate' ]
  106. });
  107. </code></pre>
  108.  
  109. See the user guide for more details.
  110.  
  111. @class DataTable.Sortable
  112. @for DataTable
  113. @since 3.5.0
  114. **/
  115. function Sortable() {}
  116.  
  117. Sortable.ATTRS = {
  118.     // Which columns in the UI should suggest and respond to sorting interaction
  119.     // pass an empty array if no UI columns should show sortable, but you want the
  120.     // table.sort(...) API
  121.     /**
  122.     Controls which column headers can trigger sorting by user clicks.
  123.  
  124.     Acceptable values are:
  125.  
  126.      * "auto" - (default) looks for `sortable: true` in the column configurations
  127.      * `true` - all columns are enabled
  128.      * `false - no UI sortable is enabled
  129.      * {String[]} - array of key names to give sortable headers
  130.  
  131.     @attribute sortable
  132.     @type {String|String[]|Boolean}
  133.     @default "auto"
  134.     @since 3.5.0
  135.     **/
  136.     sortable: {
  137.         value: 'auto',
  138.         validator: '_validateSortable'
  139.     },
  140.  
  141.     /**
  142.     The current sort configuration to maintain in the data.
  143.  
  144.     Accepts column `key` strings or objects with a single property, the column
  145.     `key`, with a value of 1, -1, "asc", or "desc".  E.g. `{ username: 'asc'
  146.     }`.  String values are assumed to be ascending.
  147.  
  148.     Example values would be:
  149.  
  150.      * `"username"` - sort by the data's `username` field or the `key`
  151.        associated to a column with that `name`.
  152.      * `{ username: "desc" }` - sort by `username` in descending order.
  153.        Alternately, use values "asc", 1 (same as "asc"), or -1 (same as "desc").
  154.      * `["lastName", "firstName"]` - ascending sort by `lastName`, but for
  155.        records with the same `lastName`, ascending subsort by `firstName`.
  156.        Array can have as many items as you want.
  157.      * `[{ lastName: -1 }, "firstName"]` - descending sort by `lastName`,
  158.        ascending subsort by `firstName`. Mixed types are ok.
  159.  
  160.     @attribute sortBy
  161.     @type {String|String[]|Object|Object[]}
  162.     @since 3.5.0
  163.     **/
  164.     sortBy: {
  165.         validator: '_validateSortBy',
  166.         getter: '_getSortBy'
  167.     },
  168.  
  169.     /**
  170.     Strings containing language for sorting tooltips.
  171.  
  172.     @attribute strings
  173.     @type {Object}
  174.     @default (strings for current lang configured in the YUI instance config)
  175.     @since 3.5.0
  176.     **/
  177.     strings: {}
  178. };
  179.  
  180. Y.mix(Sortable.prototype, {
  181.  
  182.     /**
  183.     Sort the data in the `data` ModelList and refresh the table with the new
  184.     order.
  185.  
  186.     Acceptable values for `fields` are `key` strings or objects with a single
  187.     property, the column `key`, with a value of 1, -1, "asc", or "desc".  E.g.
  188.     `{ username: 'asc' }`.  String values are assumed to be ascending.
  189.  
  190.     Example values would be:
  191.  
  192.      * `"username"` - sort by the data's `username` field or the `key`
  193.        associated to a column with that `name`.
  194.      * `{ username: "desc" }` - sort by `username` in descending order.
  195.        Alternately, use values "asc", 1 (same as "asc"), or -1 (same as "desc").
  196.      * `["lastName", "firstName"]` - ascending sort by `lastName`, but for
  197.        records with the same `lastName`, ascending subsort by `firstName`.
  198.        Array can have as many items as you want.
  199.      * `[{ lastName: -1 }, "firstName"]` - descending sort by `lastName`,
  200.        ascending subsort by `firstName`. Mixed types are ok.
  201.  
  202.     @method sort
  203.     @param {String|String[]|Object|Object[]} fields The field(s) to sort by
  204.     @param {Object} [payload] Extra `sort` event payload you want to send along
  205.     @return {DataTable}
  206.     @chainable
  207.     @since 3.5.0
  208.     **/
  209.     sort: function (fields, payload) {
  210.         /**
  211.         Notifies of an impending sort, either from clicking on a column
  212.         header, or from a call to the `sort` or `toggleSort` method.
  213.  
  214.         The requested sort is available in the `sortBy` property of the event.
  215.  
  216.         The default behavior of this event sets the table's `sortBy` attribute.
  217.  
  218.         @event sort
  219.         @param {String|String[]|Object|Object[]} sortBy The requested sort
  220.         @preventable _defSortFn
  221.         **/
  222.         return this.fire('sort', Y.merge((payload || {}), {
  223.             sortBy: fields || this.get('sortBy')
  224.         }));
  225.     },
  226.  
  227.     /**
  228.     Template for the node that will wrap the header content for sortable
  229.     columns.
  230.  
  231.     @property SORTABLE_HEADER_TEMPLATE
  232.     @type {String}
  233.     @value '<div class="{className}" tabindex="0"><span class="{indicatorClass}"></span></div>'
  234.     @since 3.5.0
  235.     **/
  236.     SORTABLE_HEADER_TEMPLATE: '<div class="{className}" tabindex="0" unselectable="on"><span class="{indicatorClass}"></span></div>',
  237.  
  238.     /**
  239.     Reverse the current sort direction of one or more fields currently being
  240.     sorted by.
  241.  
  242.     Pass the `key` of the column or columns you want the sort order reversed
  243.     for.
  244.  
  245.     @method toggleSort
  246.     @param {String|String[]} fields The field(s) to reverse sort order for
  247.     @param {Object} [payload] Extra `sort` event payload you want to send along
  248.     @return {DataTable}
  249.     @chainable
  250.     @since 3.5.0
  251.     **/
  252.     toggleSort: function (columns, payload) {
  253.         var current = this._sortBy,
  254.             sortBy = [],
  255.             i, len, j, col, index;
  256.  
  257.         // To avoid updating column configs or sortBy directly
  258.         for (i = 0, len = current.length; i < len; ++i) {
  259.             col = {};
  260.             col[current[i]._id] = current[i].sortDir;
  261.             sortBy.push(col);
  262.         }
  263.  
  264.         if (columns) {
  265.             columns = toArray(columns);
  266.  
  267.             for (i = 0, len = columns.length; i < len; ++i) {
  268.                 col = columns[i];
  269.                 index = -1;
  270.  
  271.                 for (j = sortBy.length - 1; i >= 0; --i) {
  272.                     if (sortBy[j][col]) {
  273.                         sortBy[j][col] *= -1;
  274.                         break;
  275.                     }
  276.                 }
  277.             }
  278.         } else {
  279.             for (i = 0, len = sortBy.length; i < len; ++i) {
  280.                 for (col in sortBy[i]) {
  281.                     if (sortBy[i].hasOwnProperty(col)) {
  282.                         sortBy[i][col] *= -1;
  283.                         break;
  284.                     }
  285.                 }
  286.             }
  287.         }
  288.  
  289.         return this.fire('sort', Y.merge((payload || {}), {
  290.             sortBy: sortBy
  291.         }));
  292.     },
  293.  
  294.     //--------------------------------------------------------------------------
  295.     // Protected properties and methods
  296.     //--------------------------------------------------------------------------
  297.     /**
  298.     Sorts the `data` ModelList based on the new `sortBy` configuration.
  299.  
  300.     @method _afterSortByChange
  301.     @param {EventFacade} e The `sortByChange` event
  302.     @protected
  303.     @since 3.5.0
  304.     **/
  305.     _afterSortByChange: function () {
  306.         // Can't use a setter because it's a chicken and egg problem. The
  307.         // columns need to be set up to translate, but columns are initialized
  308.         // from Core's initializer.  So construction-time assignment would
  309.         // fail.
  310.         this._setSortBy();
  311.  
  312.         // Don't sort unless sortBy has been set
  313.         if (this._sortBy.length) {
  314.             if (!this.data.comparator) {
  315.                  this.data.comparator = this._sortComparator;
  316.             }
  317.  
  318.             this.data.sort();
  319.         }
  320.     },
  321.  
  322.     /**
  323.     Applies the sorting logic to the new ModelList if the `newVal` is a new
  324.     ModelList.
  325.  
  326.     @method _afterSortDataChange
  327.     @param {EventFacade} e the `dataChange` event
  328.     @protected
  329.     @since 3.5.0
  330.     **/
  331.     _afterSortDataChange: function (e) {
  332.         // object values always trigger a change event, but we only want to
  333.         // call _initSortFn if the value passed to the `data` attribute was a
  334.         // new ModelList, not a set of new data as an array, or even the same
  335.         // ModelList.
  336.         if (e.prevVal !== e.newVal || e.newVal.hasOwnProperty('_compare')) {
  337.             this._initSortFn();
  338.         }
  339.     },
  340.  
  341.     /**
  342.     Checks if any of the fields in the modified record are fields that are
  343.     currently being sorted by, and if so, resorts the `data` ModelList.
  344.  
  345.     @method _afterSortRecordChange
  346.     @param {EventFacade} e The Model's `change` event
  347.     @protected
  348.     @since 3.5.0
  349.     **/
  350.     _afterSortRecordChange: function (e) {
  351.         var i, len;
  352.  
  353.         for (i = 0, len = this._sortBy.length; i < len; ++i) {
  354.             if (e.changed[this._sortBy[i].key]) {
  355.                 this.data.sort();
  356.                 break;
  357.             }
  358.         }
  359.     },
  360.  
  361.     /**
  362.     Subscribes to state changes that warrant updating the UI, and adds the
  363.     click handler for triggering the sort operation from the UI.
  364.  
  365.     @method _bindSortUI
  366.     @protected
  367.     @since 3.5.0
  368.     **/
  369.     _bindSortUI: function () {
  370.         var handles = this._eventHandles;
  371.  
  372.         if (!handles.sortAttrs) {
  373.             handles.sortAttrs = this.after(
  374.                 ['sortableChange', 'sortByChange', 'columnsChange'],
  375.                 Y.bind('_uiSetSortable', this));
  376.         }
  377.  
  378.         if (!handles.sortUITrigger && this._theadNode) {
  379.             handles.sortUITrigger = this.delegate(['click','keydown'],
  380.                 Y.rbind('_onUITriggerSort', this),
  381.                 '.' + this.getClassName('sortable', 'column'));
  382.         }
  383.     },
  384.  
  385.     /**
  386.     Sets the `sortBy` attribute from the `sort` event's `e.sortBy` value.
  387.  
  388.     @method _defSortFn
  389.     @param {EventFacade} e The `sort` event
  390.     @protected
  391.     @since 3.5.0
  392.     **/
  393.     _defSortFn: function (e) {
  394.         this.set.apply(this, ['sortBy', e.sortBy].concat(e.details));
  395.     },
  396.  
  397.     /**
  398.     Getter for the `sortBy` attribute.
  399.  
  400.     Supports the special subattribute "sortBy.state" to get a normalized JSON
  401.     version of the current sort state.  Otherwise, returns the last assigned
  402.     value.
  403.  
  404.     For example:
  405.  
  406.     <pre><code>var table = new Y.DataTable({
  407.         columns: [ ... ],
  408.         data: [ ... ],
  409.         sortBy: 'username'
  410.     });
  411.  
  412.     table.get('sortBy'); // 'username'
  413.     table.get('sortBy.state'); // { key: 'username', dir: 1 }
  414.  
  415.     table.sort(['lastName', { firstName: "desc" }]);
  416.     table.get('sortBy'); // ['lastName', { firstName: "desc" }]
  417.     table.get('sortBy.state'); // [{ key: "lastName", dir: 1 }, { key: "firstName", dir: -1 }]
  418.     </code></pre>
  419.  
  420.     @method _getSortBy
  421.     @param {String|String[]|Object|Object[]} val The current sortBy value
  422.     @param {String} detail String passed to `get(HERE)`. to parse subattributes
  423.     @protected
  424.     @since 3.5.0
  425.     **/
  426.     _getSortBy: function (val, detail) {
  427.         var state, i, len, col;
  428.  
  429.         // "sortBy." is 7 characters. Used to catch
  430.         detail = detail.slice(7);
  431.  
  432.         // TODO: table.get('sortBy.asObject')? table.get('sortBy.json')?
  433.         if (detail === 'state') {
  434.             state = [];
  435.  
  436.             for (i = 0, len = this._sortBy.length; i < len; ++i) {
  437.                 col = this._sortBy[i];
  438.                 state.push({
  439.                     column: col._id,
  440.                     dir: col.sortDir
  441.                 });
  442.             }
  443.  
  444.             // TODO: Always return an array?
  445.             return { state: (state.length === 1) ? state[0] : state };
  446.         } else {
  447.             return val;
  448.         }
  449.     },
  450.  
  451.     /**
  452.     Sets up the initial sort state and instance properties.  Publishes events
  453.     and subscribes to attribute change events to maintain internal state.
  454.  
  455.     @method initializer
  456.     @protected
  457.     @since 3.5.0
  458.     **/
  459.     initializer: function () {
  460.         var boundParseSortable = Y.bind('_parseSortable', this);
  461.  
  462.         this._parseSortable();
  463.  
  464.         this._setSortBy();
  465.  
  466.         this._initSortFn();
  467.  
  468.         this._initSortStrings();
  469.  
  470.         this.after({
  471.             'table:renderHeader': Y.bind('_renderSortable', this),
  472.             dataChange          : Y.bind('_afterSortDataChange', this),
  473.             sortByChange        : Y.bind('_afterSortByChange', this),
  474.             sortableChange      : boundParseSortable,
  475.             columnsChange       : boundParseSortable
  476.         });
  477.         this.data.after(this.data.model.NAME + ":change",
  478.             Y.bind('_afterSortRecordChange', this));
  479.  
  480.         // TODO: this event needs magic, allowing async remote sorting
  481.         this.publish('sort', {
  482.             defaultFn: Y.bind('_defSortFn', this)
  483.         });
  484.     },
  485.  
  486.     /**
  487.     Creates a `_compare` function for the `data` ModelList to allow custom
  488.     sorting by multiple fields.
  489.  
  490.     @method _initSortFn
  491.     @protected
  492.     @since 3.5.0
  493.     **/
  494.     _initSortFn: function () {
  495.         var self = this;
  496.  
  497.         // TODO: This should be a ModelList extension.
  498.         // FIXME: Modifying a component of the host seems a little smelly
  499.         // FIXME: Declaring inline override to leverage closure vs
  500.         // compiling a new function for each column/sortable change or
  501.         // binding the _compare implementation to this, resulting in an
  502.         // extra function hop during sorting. Lesser of three evils?
  503.         this.data._compare = function (a, b) {
  504.             var cmp = 0,
  505.                 i, len, col, dir, cs, aa, bb;
  506.  
  507.             for (i = 0, len = self._sortBy.length; !cmp && i < len; ++i) {
  508.                 col = self._sortBy[i];
  509.                 dir = col.sortDir,
  510.                 cs = col.caseSensitive;
  511.  
  512.                 if (col.sortFn) {
  513.                     cmp = col.sortFn(a, b, (dir === -1));
  514.                 } else {
  515.                     // FIXME? Requires columns without sortFns to have key
  516.                     aa = a.get(col.key) || '';
  517.                     bb = b.get(col.key) || '';
  518.                     if (!cs && typeof(aa) === "string" && typeof(bb) === "string"){// Not case sensitive
  519.                         aa = aa.toLowerCase();
  520.                         bb = bb.toLowerCase();
  521.                     }
  522.                     cmp = (aa > bb) ? dir : ((aa < bb) ? -dir : 0);
  523.                 }
  524.             }
  525.  
  526.             return cmp;
  527.         };
  528.  
  529.         if (this._sortBy.length) {
  530.             this.data.comparator = this._sortComparator;
  531.  
  532.             // TODO: is this necessary? Should it be elsewhere?
  533.             this.data.sort();
  534.         } else {
  535.             // Leave the _compare method in place to avoid having to set it
  536.             // up again.  Mistake?
  537.             delete this.data.comparator;
  538.         }
  539.     },
  540.  
  541.     /**
  542.     Add the sort related strings to the `strings` map.
  543.  
  544.     @method _initSortStrings
  545.     @protected
  546.     @since 3.5.0
  547.     **/
  548.     _initSortStrings: function () {
  549.         // Not a valueFn because other class extensions will want to add to it
  550.         this.set('strings', Y.mix((this.get('strings') || {}),
  551.             Y.Intl.get('datatable-sort')));
  552.     },
  553.  
  554.     /**
  555.     Fires the `sort` event in response to user clicks on sortable column
  556.     headers.
  557.  
  558.     @method _onUITriggerSort
  559.     @param {DOMEventFacade} e The `click` event
  560.     @protected
  561.     @since 3.5.0
  562.     **/
  563.     _onUITriggerSort: function (e) {
  564.         var id = e.currentTarget.getAttribute('data-yui3-col-id'),
  565.             column = id && this.getColumn(id),
  566.             sortBy, i, len;
  567.  
  568.         if (e.type === 'keydown' && e.keyCode !== 32) {
  569.             return;
  570.         }
  571.  
  572.         // In case a headerTemplate injected a link
  573.         // TODO: Is this overreaching?
  574.         e.preventDefault();
  575.  
  576.         if (column) {
  577.             if (e.shiftKey) {
  578.                 sortBy = this.get('sortBy') || [];
  579.  
  580.                 for (i = 0, len = sortBy.length; i < len; ++i) {
  581.                     if (id === sortBy[i]  || Math.abs(sortBy[i][id]) === 1) {
  582.                         if (!isObject(sortBy[i])) {
  583.                             sortBy[i] = {};
  584.                         }
  585.  
  586.                         sortBy[i][id] = -(column.sortDir||0) || 1;
  587.                         break;
  588.                     }
  589.                 }
  590.  
  591.                 if (i >= len) {
  592.                     sortBy.push(column._id);
  593.                 }
  594.             } else {
  595.                 sortBy = [{}];
  596.  
  597.                 sortBy[0][id] = -(column.sortDir||0) || 1;
  598.             }
  599.  
  600.             this.fire('sort', {
  601.                 originEvent: e,
  602.                 sortBy: sortBy
  603.             });
  604.         }
  605.     },
  606.  
  607.     /**
  608.     Normalizes the possible input values for the `sortable` attribute, storing
  609.     the results in the `_sortable` property.
  610.  
  611.     @method _parseSortable
  612.     @protected
  613.     @since 3.5.0
  614.     **/
  615.     _parseSortable: function () {
  616.         var sortable = this.get('sortable'),
  617.             columns  = [],
  618.             i, len, col;
  619.  
  620.         if (isArray(sortable)) {
  621.             for (i = 0, len = sortable.length; i < len; ++i) {
  622.                 col = sortable[i];
  623.  
  624.                 // isArray is called because arrays are objects, but will rely
  625.                 // on getColumn to nullify them for the subsequent if (col)
  626.                 if (!isObject(col, true) || isArray(col)) {
  627.                     col = this.getColumn(col);
  628.                 }
  629.  
  630.                 if (col) {
  631.                     columns.push(col);
  632.                 }
  633.             }
  634.         } else if (sortable) {
  635.             columns = this._displayColumns.slice();
  636.  
  637.             if (sortable === 'auto') {
  638.                 for (i = columns.length - 1; i >= 0; --i) {
  639.                     if (!columns[i].sortable) {
  640.                         columns.splice(i, 1);
  641.                     }
  642.                 }
  643.             }
  644.         }
  645.  
  646.         this._sortable = columns;
  647.     },
  648.  
  649.     /**
  650.     Initial application of the sortable UI.
  651.  
  652.     @method _renderSortable
  653.     @protected
  654.     @since 3.5.0
  655.     **/
  656.     _renderSortable: function () {
  657.         this._uiSetSortable();
  658.  
  659.         this._bindSortUI();
  660.     },
  661.  
  662.     /**
  663.     Parses the current `sortBy` attribute into a normalized structure for the
  664.     `data` ModelList's `_compare` method.  Also updates the column
  665.     configurations' `sortDir` properties.
  666.  
  667.     @method _setSortBy
  668.     @protected
  669.     @since 3.5.0
  670.     **/
  671.     _setSortBy: function () {
  672.         var columns     = this._displayColumns,
  673.             sortBy      = this.get('sortBy') || [],
  674.             sortedClass = ' ' + this.getClassName('sorted'),
  675.             i, len, name, dir, field, column;
  676.  
  677.         this._sortBy = [];
  678.  
  679.         // Purge current sort state from column configs
  680.         for (i = 0, len = columns.length; i < len; ++i) {
  681.             column = columns[i];
  682.  
  683.             delete column.sortDir;
  684.  
  685.             if (column.className) {
  686.                 // TODO: be more thorough
  687.                 column.className = column.className.replace(sortedClass, '');
  688.             }
  689.         }
  690.  
  691.         sortBy = toArray(sortBy);
  692.  
  693.         for (i = 0, len = sortBy.length; i < len; ++i) {
  694.             name = sortBy[i];
  695.             dir  = 1;
  696.  
  697.             if (isObject(name)) {
  698.                 field = name;
  699.                 // Have to use a for-in loop to process sort({ foo: -1 })
  700.                 for (name in field) {
  701.                     if (field.hasOwnProperty(name)) {
  702.                         dir = dirMap[field[name]];
  703.                         break;
  704.                     }
  705.                 }
  706.             }
  707.  
  708.             if (name) {
  709.                 // Allow sorting of any model field and any column
  710.                 // FIXME: this isn't limited to model attributes, but there's no
  711.                 // convenient way to get a list of the attributes for a Model
  712.                 // subclass *including* the attributes of its superclasses.
  713.                 column = this.getColumn(name) || { _id: name, key: name };
  714.  
  715.                 if (column) {
  716.                     column.sortDir = dir;
  717.  
  718.                     if (!column.className) {
  719.                         column.className = '';
  720.                     }
  721.  
  722.                     column.className += sortedClass;
  723.  
  724.                     this._sortBy.push(column);
  725.                 }
  726.             }
  727.         }
  728.     },
  729.  
  730.     /**
  731.     Array of column configuration objects of those columns that need UI setup
  732.     for user interaction.
  733.  
  734.     @property _sortable
  735.     @type {Object[]}
  736.     @protected
  737.     @since 3.5.0
  738.     **/
  739.     //_sortable: null,
  740.  
  741.     /**
  742.     Array of column configuration objects for those columns that are currently
  743.     being used to sort the data.  Fake column objects are used for fields that
  744.     are not rendered as columns.
  745.  
  746.     @property _sortBy
  747.     @type {Object[]}
  748.     @protected
  749.     @since 3.5.0
  750.     **/
  751.     //_sortBy: null,
  752.  
  753.     /**
  754.     Replacement `comparator` for the `data` ModelList that defers sorting logic
  755.     to the `_compare` method.  The deferral is accomplished by returning `this`.
  756.  
  757.     @method _sortComparator
  758.     @param {Model} item The record being evaluated for sort position
  759.     @return {Model} The record
  760.     @protected
  761.     @since 3.5.0
  762.     **/
  763.     _sortComparator: function (item) {
  764.         // Defer sorting to ModelList's _compare
  765.         return item;
  766.     },
  767.  
  768.     /**
  769.     Applies the appropriate classes to the `boundingBox` and column headers to
  770.     indicate sort state and sortability.
  771.  
  772.     Also currently wraps the header content of sortable columns in a `<div>`
  773.     liner to give a CSS anchor for sort indicators.
  774.  
  775.     @method _uiSetSortable
  776.     @protected
  777.     @since 3.5.0
  778.     **/
  779.     _uiSetSortable: function () {
  780.         var columns       = this._sortable || [],
  781.             sortableClass = this.getClassName('sortable', 'column'),
  782.             ascClass      = this.getClassName('sorted'),
  783.             descClass     = this.getClassName('sorted', 'desc'),
  784.             linerClass    = this.getClassName('sort', 'liner'),
  785.             indicatorClass= this.getClassName('sort', 'indicator'),
  786.             sortableCols  = {},
  787.             i, len, col, node, liner, title, desc;
  788.  
  789.         this.get('boundingBox').toggleClass(
  790.             this.getClassName('sortable'),
  791.             columns.length);
  792.  
  793.         for (i = 0, len = columns.length; i < len; ++i) {
  794.             sortableCols[columns[i].id] = columns[i];
  795.         }
  796.  
  797.         // TODO: this.head.render() + decorate cells?
  798.         this._theadNode.all('.' + sortableClass).each(function (node) {
  799.             var col       = sortableCols[node.get('id')],
  800.                 liner     = node.one('.' + linerClass),
  801.                 indicator;
  802.  
  803.             if (col) {
  804.                 if (!col.sortDir) {
  805.                     node.removeClass(ascClass)
  806.                         .removeClass(descClass);
  807.                 }
  808.             } else {
  809.                 node.removeClass(sortableClass)
  810.                     .removeClass(ascClass)
  811.                     .removeClass(descClass);
  812.  
  813.                 if (liner) {
  814.                     liner.replace(liner.get('childNodes').toFrag());
  815.                 }
  816.  
  817.                 indicator = node.one('.' + indicatorClass);
  818.  
  819.                 if (indicator) {
  820.                     indicator.remove().destroy(true);
  821.                 }
  822.             }
  823.         });
  824.  
  825.         for (i = 0, len = columns.length; i < len; ++i) {
  826.             col  = columns[i];
  827.             node = this._theadNode.one('#' + col.id);
  828.             desc = col.sortDir === -1;
  829.  
  830.             if (node) {
  831.                 liner = node.one('.' + linerClass);
  832.  
  833.                 node.addClass(sortableClass);
  834.  
  835.                 if (col.sortDir) {
  836.                     node.addClass(ascClass);
  837.  
  838.                     node.toggleClass(descClass, desc);
  839.  
  840.                     node.setAttribute('aria-sort', desc ?
  841.                         'descending' : 'ascending');
  842.                 }
  843.  
  844.                 if (!liner) {
  845.                     liner = Y.Node.create(Y.Lang.sub(
  846.                         this.SORTABLE_HEADER_TEMPLATE, {
  847.                             className: linerClass,
  848.                             indicatorClass: indicatorClass
  849.                         }));
  850.  
  851.                     liner.prepend(node.get('childNodes').toFrag());
  852.  
  853.                     node.append(liner);
  854.                 }
  855.  
  856.                 title = sub(this.getString(
  857.                     (col.sortDir === 1) ? 'reverseSortBy' : 'sortBy'), // get string
  858.                     {
  859.                         title:  col.title || '',
  860.                         key:    col.key || '',
  861.                         abbr:   col.abbr || '',
  862.                         label:  col.label || '',
  863.                         column: col.abbr || col.label ||
  864.                                 col.key  || ('column ' + i)
  865.                     }
  866.                 );
  867.  
  868.                 node.setAttribute('title', title);
  869.                 // To combat VoiceOver from reading the sort title as the
  870.                 // column header
  871.                 node.setAttribute('aria-labelledby', col.id);
  872.             }
  873.         }
  874.     },
  875.  
  876.     /**
  877.     Allows values `true`, `false`, "auto", or arrays of column names through.
  878.  
  879.     @method _validateSortable
  880.     @param {Any} val The input value to `set("sortable", VAL)`
  881.     @return {Boolean}
  882.     @protected
  883.     @since 3.5.0
  884.     **/
  885.     _validateSortable: function (val) {
  886.         return val === 'auto' || isBoolean(val) || isArray(val);
  887.     },
  888.  
  889.     /**
  890.     Allows strings, arrays of strings, objects, or arrays of objects.
  891.  
  892.     @method _validateSortBy
  893.     @param {String|String[]|Object|Object[]} val The new `sortBy` value
  894.     @return {Boolean}
  895.     @protected
  896.     @since 3.5.0
  897.     **/
  898.     _validateSortBy: function (val) {
  899.         return val === null ||
  900.                isString(val) ||
  901.                isObject(val, true) ||
  902.                (isArray(val) && (isString(val[0]) || isObject(val, true)));
  903.     }
  904.  
  905. }, true);
  906.  
  907. Y.DataTable.Sortable = Sortable;
  908. /**
  909. Used when the instance's `sortable` attribute is set to
  910. "auto" (the default) to determine which columns will support
  911. user sorting by clicking on the header.
  912.  
  913. If the instance's `key` attribute is not set, this
  914. configuration is ignored.
  915.  
  916.     { key: 'lastLogin', sortable: true }
  917.  
  918. @property sortable
  919. @type Boolean
  920. @for DataTable.Column
  921.  */
  922. /**
  923. When the instance's `caseSensitive` attribute is set to
  924. `true` the sort order is case sensitive (relevant to string columns only).
  925.  
  926. Case sensitive sort is marginally more efficient and should be considered
  927. for large data sets when case insensitive sort is not required.
  928.  
  929.     { key: 'lastLogin', sortable: true, caseSensitive: true }
  930.  
  931. @property caseSensitive
  932. @type Boolean
  933. @for DataTable.Column
  934.  */
  935. /**
  936. Allows a column to be sorted using a custom algorithm.  The
  937. function receives three parameters, the first two being the
  938. two record Models to compare, and the third being a boolean
  939. `true` if the sort order should be descending.
  940.  
  941. The function should return `1` to sort `a` above `b`, `-1`
  942. to sort `a` below `b`, and `0` if they are equal.  Keep in
  943. mind that the order should be reversed when `desc` is
  944. `true`.
  945.  
  946. The `desc` parameter is provided to allow `sortFn`s to
  947. always sort certain values above or below others, such as
  948. always sorting `null`s on top.
  949.  
  950.     {
  951.       label: 'Name',
  952.       sortFn: function (a, b, desc) {
  953.         var an = a.get('lname') + b.get('fname'),
  954.             bn = a.get('lname') + b.get('fname'),
  955.             order = (an > bn) ? 1 : -(an < bn);
  956.  
  957.         return desc ? -order : order;
  958.       },
  959.       formatter: function (o) {
  960.         return o.data.lname + ', ' + o.data.fname;
  961.       }
  962.     }
  963.  
  964. @property sortFn
  965. @type Function
  966. @for DataTable.Column
  967. */
  968. /**
  969. (__read-only__) If a column is sorted, this
  970. will be set to `1` for ascending order or `-1` for
  971. descending.  This configuration is public for inspection,
  972. but can't be used during DataTable instantiation to set the
  973. sort direction of the column.  Use the table's
  974. [sortBy](DataTable.html#attr_sortBy)
  975. attribute for that.
  976.  
  977. @property sortDir
  978. @type {Number}
  979. @readOnly
  980. @for DataTable.Column
  981. */
  982.  
  983. Y.Base.mix(Y.DataTable, [Sortable]);
  984.  
  985.  
  986. }, '3.15.0', {"requires": ["datatable-base"], "lang": ["en", "fr", "es", "hu"], "skinnable": true});

Raw Paste


Login or Register to edit or fork this paste. It's free.