Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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