sort.js revision f5df3320c255ef9e7c007469613dc31b4f91a3d8
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke SmithAdds support for column sort.
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith@module datatable-sort
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith // Which columns in the UI should suggest and respond to sorting interaction
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith // pass an empty array if no UI columns should show sortable, but you want the
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith // table.sort(...) API
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Last sort params
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith valueFn: function () {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // To avoid updating column configs or sortBy directly
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith for (i = 0, len = current.length; i < len; ++i) {
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith for (i = 0, len = columns.length; i < len; ++i) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith //----------------------------------------------------------------------------
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // Protected properties and methods
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith //----------------------------------------------------------------------------
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _afterDataChange: function (e) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // object values always trigger a change event, but we only want to
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // call _initSortFn if the value passed to the `data` attribute was a
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // new ModelList, not a set of new data as an array, or even the same
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // ModelList.
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith if (e.prevVal !== e.newVal || e.newVal.hasOwnProperty('_compare')) {
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith _afterSortByChange: function (e) {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Can't use a setter because it's a chicken and egg problem. The columns
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // need to be set up to translate, but columns are initialized from
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Core's initializer. So construction-time assignment would fail.
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Don't sort unless sortBy has been set
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith _bindSortUI: function () {
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith this.after(['sortableChange', 'sortByChange', 'columnsChange'],
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith this._sortHandle = this._theadNode.delegate('click',
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _defSortFn: function (e) {
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith this.set.apply(this, ['sortBy', e.sortBy].concat(e.details));
8fff801a4a38e13d6ff8181ff097fb01d058d617Luke Smith destructor: function () {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // "sortBy." is 7 characters. Used to catch
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // TODO: table.get('sortBy.asObject')? table.get('sortBy.json')?
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith for (i = 0, len = this._sortBy.length; i < len; ++i) {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // TODO: Always return an array?
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith return { state: (state.length === 1) ? state[0] : state };
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith initializer: function () {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith var boundParseSortable = Y.bind('_parseSortable', this);
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith sortByChange : Y.bind('_afterSortByChange', this),
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _initSortFn: function () {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // TODO: This should be a ModelList extension.
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // FIXME: Modifying a component of the host seems a little smelly
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // FIXME: Declaring inline override to leverage closure vs
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // compiling a new function for each column/sortable change or
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // binding the _compare implementation to this, resulting in an
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // extra function hop during sorting. Lesser of three evils?
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith for (i = 0, len = self._sortBy.length; !cmp && i < len; ++i) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // FIXME? Requires columns without sortFns to have key
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith if (this.get('sortable') && this._sortBy.length) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // TODO: is this necessary? Should it be elsewhere?
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // Leave the _compare method in place to avoid having to set it
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // up again. Mistake?
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _onUITriggerSort: function (e) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // TODO: if (e.ctrlKey) { /* subsort */ }
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith Y.Array.each(this._displayColumns, function (col) {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Flip current sortDir or default to 1 (asc)
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _parseSortable: function () {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith for (i = 0, len = sortable.length; i < len; ++i) {
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // isArray is called because arrays are objects, but will rely
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // on getColumn to nullify them for the subsequent if (col)
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith } else if (sortable) {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith _setSortBy: function () {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Purge current sort state from column configs
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith for (i = 0, len = columns.length; i < len; ++i) {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // TODO: be more thorough
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith column.className = column.className.replace(sortedClass, '');
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Have to use a for-in loop to process sort({ foo: -1 })
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // Allow sorting of any model field and any column
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // FIXME: this isn't limited to model attributes, but there's no
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // convenient way to get a list of the attributes for a Model
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // subclass *including* the attributes of its superclasses.
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith column = this.getColumn(name) || { _id: name, key: name };
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith // Defer sorting to ModelList's _compare
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith return val === 'auto' || isBoolean(val) || isArray(val);
4eafc0c9b36f7e8efc8808ab27dbed7ff8dc87abLuke Smith _uiSetSortable: function () {
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith sortableClass = this.getClassName('sortable', 'column'),
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith // TODO: this.head.render() + decorate cells?
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith for (i = 0, len = columns.length; i < len; ++i) {
f5df3320c255ef9e7c007469613dc31b4f91a3d8Luke Smith Y.Node.create('<div class="' + linerClass + '"></div>')
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith return val === null ||
32624ce45f113c04dfa5469b3cc77df42c4c46f4Luke Smith (isArray(val) && (isString(val[0]) || isObject(val, true)));