/*
YUI 3.7.3 (build 5687)
Copyright 2012 Yahoo! Inc. All rights reserved.
Licensed under the BSD License.
http://yuilibrary.com/license/
*/
if (typeof _yuitest_coverage == "undefined"){
    _yuitest_coverage = {};
    _yuitest_coverline = function(src, line){
        var coverage = _yuitest_coverage[src];
        if (!coverage.lines[line]){
            coverage.calledLines++;
        }
        coverage.lines[line]++;
    };
    _yuitest_coverfunc = function(src, name, line){
        var coverage = _yuitest_coverage[src],
            funcId = name + ":" + line;
        if (!coverage.functions[funcId]){
            coverage.calledFunctions++;
        }
        coverage.functions[funcId]++;
    };
}
_yuitest_coverage["build/datatable-sort/datatable-sort.js"] = {
    lines: {},
    functions: {},
    coveredLines: 0,
    calledLines: 0,
    coveredFunctions: 0,
    calledFunctions: 0,
    path: "build/datatable-sort/datatable-sort.js",
    code: []
};
_yuitest_coverage["build/datatable-sort/datatable-sort.js"].code=["YUI.add('datatable-sort', function (Y, NAME) {","","/**","Adds support for sorting the table data by API methods `table.sort(...)` or","`table.toggleSort(...)` or by clicking on column headers in the rendered UI.","","@module datatable","@submodule datatable-sort","@since 3.5.0","**/","var YLang     = Y.Lang,","    isBoolean = YLang.isBoolean,","    isString  = YLang.isString,","    isArray   = YLang.isArray,","    isObject  = YLang.isObject,","","    toArray = Y.Array,","    sub     = YLang.sub,","","    dirMap = {","        asc : 1,","        desc: -1,","        \"1\" : 1,","        \"-1\": -1","    };","","","/**","_API docs for this extension are included in the DataTable class._","","This DataTable class extension adds support for sorting the table data by API","methods `table.sort(...)` or `table.toggleSort(...)` or by clicking on column","headers in the rendered UI.","","Sorting by the API is enabled automatically when this module is `use()`d.  To","enable UI triggered sorting, set the DataTable's `sortable` attribute to","`true`.","","<pre><code>","var table = new Y.DataTable({","    columns: [ 'id', 'username', 'name', 'birthdate' ],","    data: [ ... ],","    sortable: true","});","","table.render('#table');","</code></pre>","","Setting `sortable` to `true` will enable UI sorting for all columns.  To enable","UI sorting for certain columns only, set `sortable` to an array of column keys,","or just add `sortable: true` to the respective column configuration objects.","This uses the default setting of `sortable: auto` for the DataTable instance.","","<pre><code>","var table = new Y.DataTable({","    columns: [","        'id',","        { key: 'username',  sortable: true },","        { key: 'name',      sortable: true },","        { key: 'birthdate', sortable: true }","    ],","    data: [ ... ]","    // sortable: 'auto' is the default","});","","// OR","var table = new Y.DataTable({","    columns: [ 'id', 'username', 'name', 'birthdate' ],","    data: [ ... ],","    sortable: [ 'username', 'name', 'birthdate' ]","});","</code></pre>","","To disable UI sorting for all columns, set `sortable` to `false`.  This still","permits sorting via the API methods.","","As new records are inserted into the table's `data` ModelList, they will be inserted at the correct index to preserve the sort order.","","The current sort order is stored in the `sortBy` attribute.  Assigning this value at instantiation will automatically sort your data.","","Sorting is done by a simple value comparison using &lt; and &gt; on the field","value.  If you need custom sorting, add a sort function in the column's","`sortFn` property.  Columns whose content is generated by formatters, but don't","relate to a single `key`, require a `sortFn` to be sortable.","","<pre><code>","function nameSort(a, b, desc) {","    var aa = a.get('lastName') + a.get('firstName'),","        bb = a.get('lastName') + b.get('firstName'),","        order = (aa > bb) ? 1 : -(aa < bb);","        ","    return desc ? -order : order;","}","","var table = new Y.DataTable({","    columns: [ 'id', 'username', { key: name, sortFn: nameSort }, 'birthdate' ],","    data: [ ... ],","    sortable: [ 'username', 'name', 'birthdate' ]","});","</code></pre>","","See the user guide for more details.","","@class DataTable.Sortable","@for DataTable","@since 3.5.0","**/","function Sortable() {}","","Sortable.ATTRS = {","    // Which columns in the UI should suggest and respond to sorting interaction","    // pass an empty array if no UI columns should show sortable, but you want the","    // table.sort(...) API","    /**","    Controls which column headers can trigger sorting by user clicks.","","    Acceptable values are:","","     * \"auto\" - (default) looks for `sortable: true` in the column configurations","     * `true` - all columns are enabled","     * `false - no UI sortable is enabled","     * {String[]} - array of key names to give sortable headers","","    @attribute sortable","    @type {String|String[]|Boolean}","    @default \"auto\"","    @since 3.5.0","    **/","    sortable: {","        value: 'auto',","        validator: '_validateSortable'","    },","","    /**","    The current sort configuration to maintain in the data.","","    Accepts column `key` strings or objects with a single property, the column","    `key`, with a value of 1, -1, \"asc\", or \"desc\".  E.g. `{ username: 'asc'","    }`.  String values are assumed to be ascending.","","    Example values would be:","","     * `\"username\"` - sort by the data's `username` field or the `key`","       associated to a column with that `name`.","     * `{ username: \"desc\" }` - sort by `username` in descending order.","       Alternately, use values \"asc\", 1 (same as \"asc\"), or -1 (same as \"desc\").","     * `[\"lastName\", \"firstName\"]` - ascending sort by `lastName`, but for","       records with the same `lastName`, ascending subsort by `firstName`.","       Array can have as many items as you want.","     * `[{ lastName: -1 }, \"firstName\"]` - descending sort by `lastName`,","       ascending subsort by `firstName`. Mixed types are ok.","","    @attribute sortBy","    @type {String|String[]|Object|Object[]}","    @since 3.5.0","    **/","    sortBy: {","        validator: '_validateSortBy',","        getter: '_getSortBy'","    },","","    /**","    Strings containing language for sorting tooltips.","","    @attribute strings","    @type {Object}","    @default (strings for current lang configured in the YUI instance config)","    @since 3.5.0","    **/","    strings: {}","};","","Y.mix(Sortable.prototype, {","","    /**","    Sort the data in the `data` ModelList and refresh the table with the new","    order.","","    Acceptable values for `fields` are `key` strings or objects with a single","    property, the column `key`, with a value of 1, -1, \"asc\", or \"desc\".  E.g.","    `{ username: 'asc' }`.  String values are assumed to be ascending.","","    Example values would be:","","     * `\"username\"` - sort by the data's `username` field or the `key`","       associated to a column with that `name`.","     * `{ username: \"desc\" }` - sort by `username` in descending order.","       Alternately, use values \"asc\", 1 (same as \"asc\"), or -1 (same as \"desc\").","     * `[\"lastName\", \"firstName\"]` - ascending sort by `lastName`, but for","       records with the same `lastName`, ascending subsort by `firstName`.","       Array can have as many items as you want.","     * `[{ lastName: -1 }, \"firstName\"]` - descending sort by `lastName`,","       ascending subsort by `firstName`. Mixed types are ok.","","    @method sort","    @param {String|String[]|Object|Object[]} fields The field(s) to sort by","    @param {Object} [payload] Extra `sort` event payload you want to send along","    @return {DataTable}","    @chainable","    @since 3.5.0","    **/","    sort: function (fields, payload) {","        /**","        Notifies of an impending sort, either from clicking on a column","        header, or from a call to the `sort` or `toggleSort` method.","","        The requested sort is available in the `sortBy` property of the event.","","        The default behavior of this event sets the table's `sortBy` attribute.","","        @event sort","        @param {String|String[]|Object|Object[]} sortBy The requested sort","        @preventable _defSortFn","        **/","        return this.fire('sort', Y.merge((payload || {}), {","            sortBy: fields || this.get('sortBy')","        }));","    },","","    /**","    Template for the node that will wrap the header content for sortable","    columns.","","    @property SORTABLE_HEADER_TEMPLATE","    @type {HTML}","    @value '<div class=\"{className}\" tabindex=\"0\"><span class=\"{indicatorClass}\"></span></div>'","    @since 3.5.0","    **/","    SORTABLE_HEADER_TEMPLATE: '<div class=\"{className}\" tabindex=\"0\"><span class=\"{indicatorClass}\"></span></div>',","","    /**","    Reverse the current sort direction of one or more fields currently being","    sorted by.","","    Pass the `key` of the column or columns you want the sort order reversed","    for.","","    @method toggleSort","    @param {String|String[]} fields The field(s) to reverse sort order for","    @param {Object} [payload] Extra `sort` event payload you want to send along","    @return {DataTable}","    @chainable","    @since 3.5.0","    **/","    toggleSort: function (columns, payload) {","        var current = this._sortBy,","            sortBy = [],","            i, len, j, col, index;","","        // To avoid updating column configs or sortBy directly","        for (i = 0, len = current.length; i < len; ++i) {","            col = {};","            col[current[i]._id] = current[i].sortDir;","            sortBy.push(col);","        }","","        if (columns) {","            columns = toArray(columns);","","            for (i = 0, len = columns.length; i < len; ++i) {","                col = columns[i];","                index = -1;","","                for (j = sortBy.length - 1; i >= 0; --i) {","                    if (sortBy[j][col]) {","                        sortBy[j][col] *= -1;","                        break;","                    }","                }","            }","        } else {","            for (i = 0, len = sortBy.length; i < len; ++i) {","                for (col in sortBy[i]) {","                    if (sortBy[i].hasOwnProperty(col)) {","                        sortBy[i][col] *= -1;","                        break;","                    }","                }","            }","        }","","        return this.fire('sort', Y.merge((payload || {}), {","            sortBy: sortBy","        }));","    },","","    //--------------------------------------------------------------------------","    // Protected properties and methods","    //--------------------------------------------------------------------------","    /**","    Sorts the `data` ModelList based on the new `sortBy` configuration.","","    @method _afterSortByChange","    @param {EventFacade} e The `sortByChange` event","    @protected","    @since 3.5.0","    **/","    _afterSortByChange: function (e) {","        // Can't use a setter because it's a chicken and egg problem. The","        // columns need to be set up to translate, but columns are initialized","        // from Core's initializer.  So construction-time assignment would","        // fail.","        this._setSortBy();","","        // Don't sort unless sortBy has been set","        if (this._sortBy.length) {","            if (!this.data.comparator) {","                 this.data.comparator = this._sortComparator;","            }","","            this.data.sort();","        }","    },","","    /**","    Applies the sorting logic to the new ModelList if the `newVal` is a new","    ModelList.","","    @method _afterSortDataChange","    @param {EventFacade} e the `dataChange` event","    @protected","    @since 3.5.0","    **/","    _afterSortDataChange: function (e) {","        // object values always trigger a change event, but we only want to","        // call _initSortFn if the value passed to the `data` attribute was a","        // new ModelList, not a set of new data as an array, or even the same","        // ModelList.","        if (e.prevVal !== e.newVal || e.newVal.hasOwnProperty('_compare')) {","            this._initSortFn();","        }","    },","","    /**","    Checks if any of the fields in the modified record are fields that are","    currently being sorted by, and if so, resorts the `data` ModelList.","","    @method _afterSortRecordChange","    @param {EventFacade} e The Model's `change` event","    @protected","    @since 3.5.0","    **/","    _afterSortRecordChange: function (e) {","        var i, len;","","        for (i = 0, len = this._sortBy.length; i < len; ++i) {","            if (e.changed[this._sortBy[i].key]) {","                this.data.sort();","                break;","            }","        }","    },","","    /**","    Subscribes to state changes that warrant updating the UI, and adds the","    click handler for triggering the sort operation from the UI.","","    @method _bindSortUI","    @protected","    @since 3.5.0","    **/","    _bindSortUI: function () {","        var handles = this._eventHandles;","        ","        if (!handles.sortAttrs) {","            handles.sortAttrs = this.after(","                ['sortableChange', 'sortByChange', 'columnsChange'],","                Y.bind('_uiSetSortable', this));","        }","","        if (!handles.sortUITrigger && this._theadNode) {","            handles.sortUITrigger = this.delegate(['click','keydown'],","                Y.rbind('_onUITriggerSort', this),","                '.' + this.getClassName('sortable', 'column'));","        }","    },","","    /**","    Sets the `sortBy` attribute from the `sort` event's `e.sortBy` value.","","    @method _defSortFn","    @param {EventFacade} e The `sort` event","    @protected","    @since 3.5.0","    **/","    _defSortFn: function (e) {","        this.set.apply(this, ['sortBy', e.sortBy].concat(e.details));","    },","","    /**","    Getter for the `sortBy` attribute.","    ","    Supports the special subattribute \"sortBy.state\" to get a normalized JSON","    version of the current sort state.  Otherwise, returns the last assigned","    value.","","    For example:","","    <pre><code>var table = new Y.DataTable({","        columns: [ ... ],","        data: [ ... ],","        sortBy: 'username'","    });","","    table.get('sortBy'); // 'username'","    table.get('sortBy.state'); // { key: 'username', dir: 1 }","","    table.sort(['lastName', { firstName: \"desc\" }]);","    table.get('sortBy'); // ['lastName', { firstName: \"desc\" }]","    table.get('sortBy.state'); // [{ key: \"lastName\", dir: 1 }, { key: \"firstName\", dir: -1 }]","    </code></pre>","","    @method _getSortBy","    @param {String|String[]|Object|Object[]} val The current sortBy value","    @param {String} detail String passed to `get(HERE)`. to parse subattributes","    @protected","    @since 3.5.0","    **/","    _getSortBy: function (val, detail) {","        var state, i, len, col;","","        // \"sortBy.\" is 7 characters. Used to catch ","        detail = detail.slice(7);","","        // TODO: table.get('sortBy.asObject')? table.get('sortBy.json')?","        if (detail === 'state') {","            state = [];","","            for (i = 0, len = this._sortBy.length; i < len; ++i) {","                col = this._sortBy[i];","                state.push({","                    column: col._id,","                    dir: col.sortDir","                });","            }","","            // TODO: Always return an array?","            return { state: (state.length === 1) ? state[0] : state };","        } else {","            return val;","        }","    },","","    /**","    Sets up the initial sort state and instance properties.  Publishes events","    and subscribes to attribute change events to maintain internal state.","","    @method initializer","    @protected","    @since 3.5.0","    **/","    initializer: function () {","        var boundParseSortable = Y.bind('_parseSortable', this);","","        this._parseSortable();","","        this._setSortBy();","","        this._initSortFn();","","        this._initSortStrings();","","        this.after({","            'table:renderHeader': Y.bind('_renderSortable', this),","            dataChange          : Y.bind('_afterSortDataChange', this),","            sortByChange        : Y.bind('_afterSortByChange', this),","            sortableChange      : boundParseSortable,","            columnsChange       : boundParseSortable","        });","        this.data.after(this.data.model.NAME + \":change\",","            Y.bind('_afterSortRecordChange', this));","","        // TODO: this event needs magic, allowing async remote sorting","        this.publish('sort', {","            defaultFn: Y.bind('_defSortFn', this)","        });","    },","","    /**","    Creates a `_compare` function for the `data` ModelList to allow custom","    sorting by multiple fields.","","    @method _initSortFn","    @protected","    @since 3.5.0","    **/","    _initSortFn: function () {","        var self = this;","","        // TODO: This should be a ModelList extension.","        // FIXME: Modifying a component of the host seems a little smelly","        // FIXME: Declaring inline override to leverage closure vs","        // compiling a new function for each column/sortable change or","        // binding the _compare implementation to this, resulting in an","        // extra function hop during sorting. Lesser of three evils?","        this.data._compare = function (a, b) {","            var cmp = 0,","                i, len, col, dir, aa, bb;","","            for (i = 0, len = self._sortBy.length; !cmp && i < len; ++i) {","                col = self._sortBy[i];","                dir = col.sortDir;","","                if (col.sortFn) {","                    cmp = col.sortFn(a, b, (dir === -1));","                } else {","                    // FIXME? Requires columns without sortFns to have key","                    aa = a.get(col.key) || '';","                    bb = b.get(col.key) || '';","","                    cmp = (aa > bb) ? dir : ((aa < bb) ? -dir : 0);","                }","            }","","            return cmp;","        };","","        if (this._sortBy.length) {","            this.data.comparator = this._sortComparator;","","            // TODO: is this necessary? Should it be elsewhere?","            this.data.sort();","        } else {","            // Leave the _compare method in place to avoid having to set it","            // up again.  Mistake?","            delete this.data.comparator;","        }","    },","","    /**","    Add the sort related strings to the `strings` map.","    ","    @method _initSortStrings","    @protected","    @since 3.5.0","    **/","    _initSortStrings: function () {","        // Not a valueFn because other class extensions will want to add to it","        this.set('strings', Y.mix((this.get('strings') || {}), ","            Y.Intl.get('datatable-sort')));","    },","","    /**","    Fires the `sort` event in response to user clicks on sortable column","    headers.","","    @method _onUITriggerSort","    @param {DOMEventFacade} e The `click` event","    @protected","    @since 3.5.0","    **/","    _onUITriggerSort: function (e) {","        var id = e.currentTarget.getAttribute('data-yui3-col-id'),","            sortBy = e.shiftKey ? this.get('sortBy') : [{}],","            column = id && this.getColumn(id),","            i, len;","","        if (e.type === 'keydown' && e.keyCode !== 32) {","            return;","        }","","        // In case a headerTemplate injected a link","        // TODO: Is this overreaching?","        e.preventDefault();","","        if (column) {","            if (e.shiftKey) {","                for (i = 0, len = sortBy.length; i < len; ++i) {","                    if (id === sortBy[i] || Math.abs(sortBy[i][id] === 1)) {","                        if (!isObject(sortBy[i])) {","                            sortBy[i] = {};","                        }","","                        sortBy[i][id] = -(column.sortDir|0) || 1;","                        break;","                    }","                }","","                if (i >= len) {","                    sortBy.push(column._id);","                }","            } else {","                sortBy[0][id] = -(column.sortDir|0) || 1;","            }","","            this.fire('sort', {","                originEvent: e,","                sortBy: sortBy","            });","        }","    },","","    /**","    Normalizes the possible input values for the `sortable` attribute, storing","    the results in the `_sortable` property.","","    @method _parseSortable","    @protected","    @since 3.5.0","    **/","    _parseSortable: function () {","        var sortable = this.get('sortable'),","            columns  = [],","            i, len, col;","","        if (isArray(sortable)) {","            for (i = 0, len = sortable.length; i < len; ++i) {","                col = sortable[i];","","                // isArray is called because arrays are objects, but will rely","                // on getColumn to nullify them for the subsequent if (col)","                if (!isObject(col, true) || isArray(col)) {","                    col = this.getColumn(col);","                }","","                if (col) {","                    columns.push(col);","                }","            }","        } else if (sortable) {","            columns = this._displayColumns.slice();","","            if (sortable === 'auto') {","                for (i = columns.length - 1; i >= 0; --i) {","                    if (!columns[i].sortable) {","                        columns.splice(i, 1);","                    }","                }","            }","        }","","        this._sortable = columns;","    },","","    /**","    Initial application of the sortable UI.","","    @method _renderSortable","    @protected","    @since 3.5.0","    **/","    _renderSortable: function () {","        this._uiSetSortable();","","        this._bindSortUI();","    },","","    /**","    Parses the current `sortBy` attribute into a normalized structure for the","    `data` ModelList's `_compare` method.  Also updates the column","    configurations' `sortDir` properties.","","    @method _setSortBy","    @protected","    @since 3.5.0","    **/","    _setSortBy: function () {","        var columns     = this._displayColumns,","            sortBy      = this.get('sortBy') || [],","            sortedClass = ' ' + this.getClassName('sorted'),","            i, len, name, dir, field, column;","","        this._sortBy = [];","","        // Purge current sort state from column configs","        for (i = 0, len = columns.length; i < len; ++i) {","            column = columns[i];","","            delete column.sortDir;","","            if (column.className) {","                // TODO: be more thorough","                column.className = column.className.replace(sortedClass, '');","            }","        }","","        sortBy = toArray(sortBy);","","        for (i = 0, len = sortBy.length; i < len; ++i) {","            name = sortBy[i];","            dir  = 1;","","            if (isObject(name)) {","                field = name;","                // Have to use a for-in loop to process sort({ foo: -1 })","                for (name in field) {","                    if (field.hasOwnProperty(name)) {","                        dir = dirMap[field[name]];","                        break;","                    }","                }","            }","","            if (name) {","                // Allow sorting of any model field and any column","                // FIXME: this isn't limited to model attributes, but there's no","                // convenient way to get a list of the attributes for a Model","                // subclass *including* the attributes of its superclasses.","                column = this.getColumn(name) || { _id: name, key: name };","","                if (column) {","                    column.sortDir = dir;","","                    if (!column.className) {","                        column.className = '';","                    }","","                    column.className += sortedClass;","","                    this._sortBy.push(column);","                }","            }","        }","    },","","    /**","    Array of column configuration objects of those columns that need UI setup","    for user interaction.","","    @property _sortable","    @type {Object[]}","    @protected","    @since 3.5.0","    **/","    //_sortable: null,","","    /**","    Array of column configuration objects for those columns that are currently","    being used to sort the data.  Fake column objects are used for fields that","    are not rendered as columns.","","    @property _sortBy","    @type {Object[]}","    @protected","    @since 3.5.0","    **/","    //_sortBy: null,","","    /**","    Replacement `comparator` for the `data` ModelList that defers sorting logic","    to the `_compare` method.  The deferral is accomplished by returning `this`.","","    @method _sortComparator","    @param {Model} item The record being evaluated for sort position","    @return {Model} The record","    @protected","    @since 3.5.0","    **/","    _sortComparator: function (item) {","        // Defer sorting to ModelList's _compare","        return item;","    },","","    /**","    Applies the appropriate classes to the `boundingBox` and column headers to","    indicate sort state and sortability.","","    Also currently wraps the header content of sortable columns in a `<div>`","    liner to give a CSS anchor for sort indicators.","","    @method _uiSetSortable","    @protected","    @since 3.5.0","    **/","    _uiSetSortable: function () {","        var columns       = this._sortable || [],","            sortableClass = this.getClassName('sortable', 'column'),","            ascClass      = this.getClassName('sorted'),","            descClass     = this.getClassName('sorted', 'desc'),","            linerClass    = this.getClassName('sort', 'liner'),","            indicatorClass= this.getClassName('sort', 'indicator'),","            sortableCols  = {},","            i, len, col, node, liner, title, desc;","","        this.get('boundingBox').toggleClass(","            this.getClassName('sortable'),","            columns.length);","","        for (i = 0, len = columns.length; i < len; ++i) {","            sortableCols[columns[i].id] = columns[i];","        }","","        // TODO: this.head.render() + decorate cells?","        this._theadNode.all('.' + sortableClass).each(function (node) {","            var col       = sortableCols[node.get('id')],","                liner     = node.one('.' + linerClass),","                indicator;","","            if (col) {","                if (!col.sortDir) {","                    node.removeClass(ascClass)","                        .removeClass(descClass);","                }","            } else {","                node.removeClass(sortableClass)","                    .removeClass(ascClass)","                    .removeClass(descClass);","","                if (liner) {","                    liner.replace(liner.get('childNodes').toFrag());","                }","","                indicator = node.one('.' + indicatorClass);","","                if (indicator) {","                    indicator.remove().destroy(true);","                }","            }","        });","","        for (i = 0, len = columns.length; i < len; ++i) {","            col  = columns[i];","            node = this._theadNode.one('#' + col.id);","            desc = col.sortDir === -1;","","            if (node) {","                liner = node.one('.' + linerClass);","","                node.addClass(sortableClass);","","                if (col.sortDir) {","                    node.addClass(ascClass);","","                    node.toggleClass(descClass, desc);","","                    node.setAttribute('aria-sort', desc ?","                        'descending' : 'ascending');","                }","","                if (!liner) {","                    liner = Y.Node.create(Y.Lang.sub(","                        this.SORTABLE_HEADER_TEMPLATE, {","                            className: linerClass,","                            indicatorClass: indicatorClass","                        }));","","                    liner.prepend(node.get('childNodes').toFrag());","","                    node.append(liner);","                }","","                title = sub(this.getString(","                    (col.sortDir === 1) ? 'reverseSortBy' : 'sortBy'), {","                        column: col.abbr || col.label ||","                                col.key  || ('column ' + i)","                });","","                node.setAttribute('title', title);","                // To combat VoiceOver from reading the sort title as the","                // column header","                node.setAttribute('aria-labelledby', col.id);","            }","        }","    },","","    /**","    Allows values `true`, `false`, \"auto\", or arrays of column names through.","","    @method _validateSortable","    @param {Any} val The input value to `set(\"sortable\", VAL)`","    @return {Boolean}","    @protected","    @since 3.5.0","    **/","    _validateSortable: function (val) {","        return val === 'auto' || isBoolean(val) || isArray(val);","    },","","    /**","    Allows strings, arrays of strings, objects, or arrays of objects.","","    @method _validateSortBy","    @param {String|String[]|Object|Object[]} val The new `sortBy` value","    @return {Boolean}","    @protected","    @since 3.5.0","    **/","    _validateSortBy: function (val) {","        return val === null ||","               isString(val) ||","               isObject(val, true) ||","               (isArray(val) && (isString(val[0]) || isObject(val, true)));","    }","","}, true);","","Y.DataTable.Sortable = Sortable;","","Y.Base.mix(Y.DataTable, [Sortable]);","","","}, '3.7.3', {\"requires\": [\"datatable-base\"], \"lang\": [\"en\"], \"skinnable\": true});"];
_yuitest_coverage["build/datatable-sort/datatable-sort.js"].lines = {"1":0,"11":0,"108":0,"110":0,"173":0,"215":0,"246":0,"251":0,"252":0,"253":0,"254":0,"257":0,"258":0,"260":0,"261":0,"262":0,"264":0,"265":0,"266":0,"267":0,"272":0,"273":0,"274":0,"275":0,"276":0,"282":0,"303":0,"306":0,"307":0,"308":0,"311":0,"329":0,"330":0,"344":0,"346":0,"347":0,"348":0,"349":0,"363":0,"365":0,"366":0,"371":0,"372":0,"387":0,"420":0,"423":0,"426":0,"427":0,"429":0,"430":0,"431":0,"438":0,"440":0,"453":0,"455":0,"457":0,"459":0,"461":0,"463":0,"470":0,"474":0,"488":0,"496":0,"497":0,"500":0,"501":0,"502":0,"504":0,"505":0,"508":0,"509":0,"511":0,"515":0,"518":0,"519":0,"522":0,"526":0,"539":0,"553":0,"558":0,"559":0,"564":0,"566":0,"567":0,"568":0,"569":0,"570":0,"571":0,"574":0,"575":0,"579":0,"580":0,"583":0,"586":0,"602":0,"606":0,"607":0,"608":0,"612":0,"613":0,"616":0,"617":0,"620":0,"621":0,"623":0,"624":0,"625":0,"626":0,"632":0,"643":0,"645":0,"658":0,"663":0,"666":0,"667":0,"669":0,"671":0,"673":0,"677":0,"679":0,"680":0,"681":0,"683":0,"684":0,"686":0,"687":0,"688":0,"689":0,"694":0,"699":0,"701":0,"702":0,"704":0,"705":0,"708":0,"710":0,"751":0,"766":0,"775":0,"779":0,"780":0,"784":0,"785":0,"789":0,"790":0,"791":0,"795":0,"799":0,"800":0,"803":0,"805":0,"806":0,"811":0,"812":0,"813":0,"814":0,"816":0,"817":0,"819":0,"821":0,"822":0,"824":0,"826":0,"830":0,"831":0,"837":0,"839":0,"842":0,"848":0,"851":0,"866":0,"879":0,"887":0,"889":0};
_yuitest_coverage["build/datatable-sort/datatable-sort.js"].functions = {"Sortable:108":0,"sort:202":0,"toggleSort:245":0,"_afterSortByChange:298":0,"_afterSortDataChange:324":0,"_afterSortRecordChange:343":0,"_bindSortUI:362":0,"_defSortFn:386":0,"_getSortBy:419":0,"initializer:452":0,"_compare:496":0,"_initSortFn:487":0,"_initSortStrings:537":0,"_onUITriggerSort:552":0,"_parseSortable:601":0,"_renderSortable:642":0,"_setSortBy:657":0,"_sortComparator:749":0,"(anonymous 2):784":0,"_uiSetSortable:765":0,"_validateSortable:865":0,"_validateSortBy:878":0,"(anonymous 1):1":0};
_yuitest_coverage["build/datatable-sort/datatable-sort.js"].coveredLines = 174;
_yuitest_coverage["build/datatable-sort/datatable-sort.js"].coveredFunctions = 23;
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 1);
YUI.add('datatable-sort', function (Y, NAME) {

/**
Adds support for sorting the table data by API methods `table.sort(...)` or
`table.toggleSort(...)` or by clicking on column headers in the rendered UI.

@module datatable
@submodule datatable-sort
@since 3.5.0
**/
_yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "(anonymous 1)", 1);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 11);
var YLang     = Y.Lang,
    isBoolean = YLang.isBoolean,
    isString  = YLang.isString,
    isArray   = YLang.isArray,
    isObject  = YLang.isObject,

    toArray = Y.Array,
    sub     = YLang.sub,

    dirMap = {
        asc : 1,
        desc: -1,
        "1" : 1,
        "-1": -1
    };


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

This DataTable class extension adds support for sorting the table data by API
methods `table.sort(...)` or `table.toggleSort(...)` or by clicking on column
headers in the rendered UI.

Sorting by the API is enabled automatically when this module is `use()`d.  To
enable UI triggered sorting, set the DataTable's `sortable` attribute to
`true`.

<pre><code>
var table = new Y.DataTable({
    columns: [ 'id', 'username', 'name', 'birthdate' ],
    data: [ ... ],
    sortable: true
});

table.render('#table');
</code></pre>

Setting `sortable` to `true` will enable UI sorting for all columns.  To enable
UI sorting for certain columns only, set `sortable` to an array of column keys,
or just add `sortable: true` to the respective column configuration objects.
This uses the default setting of `sortable: auto` for the DataTable instance.

<pre><code>
var table = new Y.DataTable({
    columns: [
        'id',
        { key: 'username',  sortable: true },
        { key: 'name',      sortable: true },
        { key: 'birthdate', sortable: true }
    ],
    data: [ ... ]
    // sortable: 'auto' is the default
});

// OR
var table = new Y.DataTable({
    columns: [ 'id', 'username', 'name', 'birthdate' ],
    data: [ ... ],
    sortable: [ 'username', 'name', 'birthdate' ]
});
</code></pre>

To disable UI sorting for all columns, set `sortable` to `false`.  This still
permits sorting via the API methods.

As new records are inserted into the table's `data` ModelList, they will be inserted at the correct index to preserve the sort order.

The current sort order is stored in the `sortBy` attribute.  Assigning this value at instantiation will automatically sort your data.

Sorting is done by a simple value comparison using &lt; and &gt; on the field
value.  If you need custom sorting, add a sort function in the column's
`sortFn` property.  Columns whose content is generated by formatters, but don't
relate to a single `key`, require a `sortFn` to be sortable.

<pre><code>
function nameSort(a, b, desc) {
    var aa = a.get('lastName') + a.get('firstName'),
        bb = a.get('lastName') + b.get('firstName'),
        order = (aa > bb) ? 1 : -(aa < bb);
        
    return desc ? -order : order;
}

var table = new Y.DataTable({
    columns: [ 'id', 'username', { key: name, sortFn: nameSort }, 'birthdate' ],
    data: [ ... ],
    sortable: [ 'username', 'name', 'birthdate' ]
});
</code></pre>

See the user guide for more details.

@class DataTable.Sortable
@for DataTable
@since 3.5.0
**/
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 108);
function Sortable() {}

_yuitest_coverline("build/datatable-sort/datatable-sort.js", 110);
Sortable.ATTRS = {
    // Which columns in the UI should suggest and respond to sorting interaction
    // pass an empty array if no UI columns should show sortable, but you want the
    // table.sort(...) API
    /**
    Controls which column headers can trigger sorting by user clicks.

    Acceptable values are:

     * "auto" - (default) looks for `sortable: true` in the column configurations
     * `true` - all columns are enabled
     * `false - no UI sortable is enabled
     * {String[]} - array of key names to give sortable headers

    @attribute sortable
    @type {String|String[]|Boolean}
    @default "auto"
    @since 3.5.0
    **/
    sortable: {
        value: 'auto',
        validator: '_validateSortable'
    },

    /**
    The current sort configuration to maintain in the data.

    Accepts column `key` strings or objects with a single property, the column
    `key`, with a value of 1, -1, "asc", or "desc".  E.g. `{ username: 'asc'
    }`.  String values are assumed to be ascending.

    Example values would be:

     * `"username"` - sort by the data's `username` field or the `key`
       associated to a column with that `name`.
     * `{ username: "desc" }` - sort by `username` in descending order.
       Alternately, use values "asc", 1 (same as "asc"), or -1 (same as "desc").
     * `["lastName", "firstName"]` - ascending sort by `lastName`, but for
       records with the same `lastName`, ascending subsort by `firstName`.
       Array can have as many items as you want.
     * `[{ lastName: -1 }, "firstName"]` - descending sort by `lastName`,
       ascending subsort by `firstName`. Mixed types are ok.

    @attribute sortBy
    @type {String|String[]|Object|Object[]}
    @since 3.5.0
    **/
    sortBy: {
        validator: '_validateSortBy',
        getter: '_getSortBy'
    },

    /**
    Strings containing language for sorting tooltips.

    @attribute strings
    @type {Object}
    @default (strings for current lang configured in the YUI instance config)
    @since 3.5.0
    **/
    strings: {}
};

_yuitest_coverline("build/datatable-sort/datatable-sort.js", 173);
Y.mix(Sortable.prototype, {

    /**
    Sort the data in the `data` ModelList and refresh the table with the new
    order.

    Acceptable values for `fields` are `key` strings or objects with a single
    property, the column `key`, with a value of 1, -1, "asc", or "desc".  E.g.
    `{ username: 'asc' }`.  String values are assumed to be ascending.

    Example values would be:

     * `"username"` - sort by the data's `username` field or the `key`
       associated to a column with that `name`.
     * `{ username: "desc" }` - sort by `username` in descending order.
       Alternately, use values "asc", 1 (same as "asc"), or -1 (same as "desc").
     * `["lastName", "firstName"]` - ascending sort by `lastName`, but for
       records with the same `lastName`, ascending subsort by `firstName`.
       Array can have as many items as you want.
     * `[{ lastName: -1 }, "firstName"]` - descending sort by `lastName`,
       ascending subsort by `firstName`. Mixed types are ok.

    @method sort
    @param {String|String[]|Object|Object[]} fields The field(s) to sort by
    @param {Object} [payload] Extra `sort` event payload you want to send along
    @return {DataTable}
    @chainable
    @since 3.5.0
    **/
    sort: function (fields, payload) {
        /**
        Notifies of an impending sort, either from clicking on a column
        header, or from a call to the `sort` or `toggleSort` method.

        The requested sort is available in the `sortBy` property of the event.

        The default behavior of this event sets the table's `sortBy` attribute.

        @event sort
        @param {String|String[]|Object|Object[]} sortBy The requested sort
        @preventable _defSortFn
        **/
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "sort", 202);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 215);
return this.fire('sort', Y.merge((payload || {}), {
            sortBy: fields || this.get('sortBy')
        }));
    },

    /**
    Template for the node that will wrap the header content for sortable
    columns.

    @property SORTABLE_HEADER_TEMPLATE
    @type {HTML}
    @value '<div class="{className}" tabindex="0"><span class="{indicatorClass}"></span></div>'
    @since 3.5.0
    **/
    SORTABLE_HEADER_TEMPLATE: '<div class="{className}" tabindex="0"><span class="{indicatorClass}"></span></div>',

    /**
    Reverse the current sort direction of one or more fields currently being
    sorted by.

    Pass the `key` of the column or columns you want the sort order reversed
    for.

    @method toggleSort
    @param {String|String[]} fields The field(s) to reverse sort order for
    @param {Object} [payload] Extra `sort` event payload you want to send along
    @return {DataTable}
    @chainable
    @since 3.5.0
    **/
    toggleSort: function (columns, payload) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "toggleSort", 245);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 246);
var current = this._sortBy,
            sortBy = [],
            i, len, j, col, index;

        // To avoid updating column configs or sortBy directly
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 251);
for (i = 0, len = current.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 252);
col = {};
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 253);
col[current[i]._id] = current[i].sortDir;
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 254);
sortBy.push(col);
        }

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 257);
if (columns) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 258);
columns = toArray(columns);

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 260);
for (i = 0, len = columns.length; i < len; ++i) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 261);
col = columns[i];
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 262);
index = -1;

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 264);
for (j = sortBy.length - 1; i >= 0; --i) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 265);
if (sortBy[j][col]) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 266);
sortBy[j][col] *= -1;
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 267);
break;
                    }
                }
            }
        } else {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 272);
for (i = 0, len = sortBy.length; i < len; ++i) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 273);
for (col in sortBy[i]) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 274);
if (sortBy[i].hasOwnProperty(col)) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 275);
sortBy[i][col] *= -1;
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 276);
break;
                    }
                }
            }
        }

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 282);
return this.fire('sort', Y.merge((payload || {}), {
            sortBy: sortBy
        }));
    },

    //--------------------------------------------------------------------------
    // Protected properties and methods
    //--------------------------------------------------------------------------
    /**
    Sorts the `data` ModelList based on the new `sortBy` configuration.

    @method _afterSortByChange
    @param {EventFacade} e The `sortByChange` event
    @protected
    @since 3.5.0
    **/
    _afterSortByChange: function (e) {
        // Can't use a setter because it's a chicken and egg problem. The
        // columns need to be set up to translate, but columns are initialized
        // from Core's initializer.  So construction-time assignment would
        // fail.
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_afterSortByChange", 298);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 303);
this._setSortBy();

        // Don't sort unless sortBy has been set
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 306);
if (this._sortBy.length) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 307);
if (!this.data.comparator) {
                 _yuitest_coverline("build/datatable-sort/datatable-sort.js", 308);
this.data.comparator = this._sortComparator;
            }

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 311);
this.data.sort();
        }
    },

    /**
    Applies the sorting logic to the new ModelList if the `newVal` is a new
    ModelList.

    @method _afterSortDataChange
    @param {EventFacade} e the `dataChange` event
    @protected
    @since 3.5.0
    **/
    _afterSortDataChange: function (e) {
        // object values always trigger a change event, but we only want to
        // call _initSortFn if the value passed to the `data` attribute was a
        // new ModelList, not a set of new data as an array, or even the same
        // ModelList.
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_afterSortDataChange", 324);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 329);
if (e.prevVal !== e.newVal || e.newVal.hasOwnProperty('_compare')) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 330);
this._initSortFn();
        }
    },

    /**
    Checks if any of the fields in the modified record are fields that are
    currently being sorted by, and if so, resorts the `data` ModelList.

    @method _afterSortRecordChange
    @param {EventFacade} e The Model's `change` event
    @protected
    @since 3.5.0
    **/
    _afterSortRecordChange: function (e) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_afterSortRecordChange", 343);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 344);
var i, len;

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 346);
for (i = 0, len = this._sortBy.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 347);
if (e.changed[this._sortBy[i].key]) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 348);
this.data.sort();
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 349);
break;
            }
        }
    },

    /**
    Subscribes to state changes that warrant updating the UI, and adds the
    click handler for triggering the sort operation from the UI.

    @method _bindSortUI
    @protected
    @since 3.5.0
    **/
    _bindSortUI: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_bindSortUI", 362);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 363);
var handles = this._eventHandles;
        
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 365);
if (!handles.sortAttrs) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 366);
handles.sortAttrs = this.after(
                ['sortableChange', 'sortByChange', 'columnsChange'],
                Y.bind('_uiSetSortable', this));
        }

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 371);
if (!handles.sortUITrigger && this._theadNode) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 372);
handles.sortUITrigger = this.delegate(['click','keydown'],
                Y.rbind('_onUITriggerSort', this),
                '.' + this.getClassName('sortable', 'column'));
        }
    },

    /**
    Sets the `sortBy` attribute from the `sort` event's `e.sortBy` value.

    @method _defSortFn
    @param {EventFacade} e The `sort` event
    @protected
    @since 3.5.0
    **/
    _defSortFn: function (e) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_defSortFn", 386);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 387);
this.set.apply(this, ['sortBy', e.sortBy].concat(e.details));
    },

    /**
    Getter for the `sortBy` attribute.
    
    Supports the special subattribute "sortBy.state" to get a normalized JSON
    version of the current sort state.  Otherwise, returns the last assigned
    value.

    For example:

    <pre><code>var table = new Y.DataTable({
        columns: [ ... ],
        data: [ ... ],
        sortBy: 'username'
    });

    table.get('sortBy'); // 'username'
    table.get('sortBy.state'); // { key: 'username', dir: 1 }

    table.sort(['lastName', { firstName: "desc" }]);
    table.get('sortBy'); // ['lastName', { firstName: "desc" }]
    table.get('sortBy.state'); // [{ key: "lastName", dir: 1 }, { key: "firstName", dir: -1 }]
    </code></pre>

    @method _getSortBy
    @param {String|String[]|Object|Object[]} val The current sortBy value
    @param {String} detail String passed to `get(HERE)`. to parse subattributes
    @protected
    @since 3.5.0
    **/
    _getSortBy: function (val, detail) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_getSortBy", 419);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 420);
var state, i, len, col;

        // "sortBy." is 7 characters. Used to catch 
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 423);
detail = detail.slice(7);

        // TODO: table.get('sortBy.asObject')? table.get('sortBy.json')?
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 426);
if (detail === 'state') {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 427);
state = [];

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 429);
for (i = 0, len = this._sortBy.length; i < len; ++i) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 430);
col = this._sortBy[i];
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 431);
state.push({
                    column: col._id,
                    dir: col.sortDir
                });
            }

            // TODO: Always return an array?
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 438);
return { state: (state.length === 1) ? state[0] : state };
        } else {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 440);
return val;
        }
    },

    /**
    Sets up the initial sort state and instance properties.  Publishes events
    and subscribes to attribute change events to maintain internal state.

    @method initializer
    @protected
    @since 3.5.0
    **/
    initializer: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "initializer", 452);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 453);
var boundParseSortable = Y.bind('_parseSortable', this);

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 455);
this._parseSortable();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 457);
this._setSortBy();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 459);
this._initSortFn();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 461);
this._initSortStrings();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 463);
this.after({
            'table:renderHeader': Y.bind('_renderSortable', this),
            dataChange          : Y.bind('_afterSortDataChange', this),
            sortByChange        : Y.bind('_afterSortByChange', this),
            sortableChange      : boundParseSortable,
            columnsChange       : boundParseSortable
        });
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 470);
this.data.after(this.data.model.NAME + ":change",
            Y.bind('_afterSortRecordChange', this));

        // TODO: this event needs magic, allowing async remote sorting
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 474);
this.publish('sort', {
            defaultFn: Y.bind('_defSortFn', this)
        });
    },

    /**
    Creates a `_compare` function for the `data` ModelList to allow custom
    sorting by multiple fields.

    @method _initSortFn
    @protected
    @since 3.5.0
    **/
    _initSortFn: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_initSortFn", 487);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 488);
var self = this;

        // TODO: This should be a ModelList extension.
        // FIXME: Modifying a component of the host seems a little smelly
        // FIXME: Declaring inline override to leverage closure vs
        // compiling a new function for each column/sortable change or
        // binding the _compare implementation to this, resulting in an
        // extra function hop during sorting. Lesser of three evils?
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 496);
this.data._compare = function (a, b) {
            _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_compare", 496);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 497);
var cmp = 0,
                i, len, col, dir, aa, bb;

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 500);
for (i = 0, len = self._sortBy.length; !cmp && i < len; ++i) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 501);
col = self._sortBy[i];
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 502);
dir = col.sortDir;

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 504);
if (col.sortFn) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 505);
cmp = col.sortFn(a, b, (dir === -1));
                } else {
                    // FIXME? Requires columns without sortFns to have key
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 508);
aa = a.get(col.key) || '';
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 509);
bb = b.get(col.key) || '';

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 511);
cmp = (aa > bb) ? dir : ((aa < bb) ? -dir : 0);
                }
            }

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 515);
return cmp;
        };

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 518);
if (this._sortBy.length) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 519);
this.data.comparator = this._sortComparator;

            // TODO: is this necessary? Should it be elsewhere?
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 522);
this.data.sort();
        } else {
            // Leave the _compare method in place to avoid having to set it
            // up again.  Mistake?
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 526);
delete this.data.comparator;
        }
    },

    /**
    Add the sort related strings to the `strings` map.
    
    @method _initSortStrings
    @protected
    @since 3.5.0
    **/
    _initSortStrings: function () {
        // Not a valueFn because other class extensions will want to add to it
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_initSortStrings", 537);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 539);
this.set('strings', Y.mix((this.get('strings') || {}), 
            Y.Intl.get('datatable-sort')));
    },

    /**
    Fires the `sort` event in response to user clicks on sortable column
    headers.

    @method _onUITriggerSort
    @param {DOMEventFacade} e The `click` event
    @protected
    @since 3.5.0
    **/
    _onUITriggerSort: function (e) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_onUITriggerSort", 552);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 553);
var id = e.currentTarget.getAttribute('data-yui3-col-id'),
            sortBy = e.shiftKey ? this.get('sortBy') : [{}],
            column = id && this.getColumn(id),
            i, len;

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 558);
if (e.type === 'keydown' && e.keyCode !== 32) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 559);
return;
        }

        // In case a headerTemplate injected a link
        // TODO: Is this overreaching?
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 564);
e.preventDefault();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 566);
if (column) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 567);
if (e.shiftKey) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 568);
for (i = 0, len = sortBy.length; i < len; ++i) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 569);
if (id === sortBy[i] || Math.abs(sortBy[i][id] === 1)) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 570);
if (!isObject(sortBy[i])) {
                            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 571);
sortBy[i] = {};
                        }

                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 574);
sortBy[i][id] = -(column.sortDir|0) || 1;
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 575);
break;
                    }
                }

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 579);
if (i >= len) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 580);
sortBy.push(column._id);
                }
            } else {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 583);
sortBy[0][id] = -(column.sortDir|0) || 1;
            }

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 586);
this.fire('sort', {
                originEvent: e,
                sortBy: sortBy
            });
        }
    },

    /**
    Normalizes the possible input values for the `sortable` attribute, storing
    the results in the `_sortable` property.

    @method _parseSortable
    @protected
    @since 3.5.0
    **/
    _parseSortable: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_parseSortable", 601);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 602);
var sortable = this.get('sortable'),
            columns  = [],
            i, len, col;

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 606);
if (isArray(sortable)) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 607);
for (i = 0, len = sortable.length; i < len; ++i) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 608);
col = sortable[i];

                // isArray is called because arrays are objects, but will rely
                // on getColumn to nullify them for the subsequent if (col)
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 612);
if (!isObject(col, true) || isArray(col)) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 613);
col = this.getColumn(col);
                }

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 616);
if (col) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 617);
columns.push(col);
                }
            }
        } else {_yuitest_coverline("build/datatable-sort/datatable-sort.js", 620);
if (sortable) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 621);
columns = this._displayColumns.slice();

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 623);
if (sortable === 'auto') {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 624);
for (i = columns.length - 1; i >= 0; --i) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 625);
if (!columns[i].sortable) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 626);
columns.splice(i, 1);
                    }
                }
            }
        }}

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 632);
this._sortable = columns;
    },

    /**
    Initial application of the sortable UI.

    @method _renderSortable
    @protected
    @since 3.5.0
    **/
    _renderSortable: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_renderSortable", 642);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 643);
this._uiSetSortable();

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 645);
this._bindSortUI();
    },

    /**
    Parses the current `sortBy` attribute into a normalized structure for the
    `data` ModelList's `_compare` method.  Also updates the column
    configurations' `sortDir` properties.

    @method _setSortBy
    @protected
    @since 3.5.0
    **/
    _setSortBy: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_setSortBy", 657);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 658);
var columns     = this._displayColumns,
            sortBy      = this.get('sortBy') || [],
            sortedClass = ' ' + this.getClassName('sorted'),
            i, len, name, dir, field, column;

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 663);
this._sortBy = [];

        // Purge current sort state from column configs
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 666);
for (i = 0, len = columns.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 667);
column = columns[i];

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 669);
delete column.sortDir;

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 671);
if (column.className) {
                // TODO: be more thorough
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 673);
column.className = column.className.replace(sortedClass, '');
            }
        }

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 677);
sortBy = toArray(sortBy);

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 679);
for (i = 0, len = sortBy.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 680);
name = sortBy[i];
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 681);
dir  = 1;

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 683);
if (isObject(name)) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 684);
field = name;
                // Have to use a for-in loop to process sort({ foo: -1 })
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 686);
for (name in field) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 687);
if (field.hasOwnProperty(name)) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 688);
dir = dirMap[field[name]];
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 689);
break;
                    }
                }
            }

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 694);
if (name) {
                // Allow sorting of any model field and any column
                // FIXME: this isn't limited to model attributes, but there's no
                // convenient way to get a list of the attributes for a Model
                // subclass *including* the attributes of its superclasses.
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 699);
column = this.getColumn(name) || { _id: name, key: name };

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 701);
if (column) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 702);
column.sortDir = dir;

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 704);
if (!column.className) {
                        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 705);
column.className = '';
                    }

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 708);
column.className += sortedClass;

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 710);
this._sortBy.push(column);
                }
            }
        }
    },

    /**
    Array of column configuration objects of those columns that need UI setup
    for user interaction.

    @property _sortable
    @type {Object[]}
    @protected
    @since 3.5.0
    **/
    //_sortable: null,

    /**
    Array of column configuration objects for those columns that are currently
    being used to sort the data.  Fake column objects are used for fields that
    are not rendered as columns.

    @property _sortBy
    @type {Object[]}
    @protected
    @since 3.5.0
    **/
    //_sortBy: null,

    /**
    Replacement `comparator` for the `data` ModelList that defers sorting logic
    to the `_compare` method.  The deferral is accomplished by returning `this`.

    @method _sortComparator
    @param {Model} item The record being evaluated for sort position
    @return {Model} The record
    @protected
    @since 3.5.0
    **/
    _sortComparator: function (item) {
        // Defer sorting to ModelList's _compare
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_sortComparator", 749);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 751);
return item;
    },

    /**
    Applies the appropriate classes to the `boundingBox` and column headers to
    indicate sort state and sortability.

    Also currently wraps the header content of sortable columns in a `<div>`
    liner to give a CSS anchor for sort indicators.

    @method _uiSetSortable
    @protected
    @since 3.5.0
    **/
    _uiSetSortable: function () {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_uiSetSortable", 765);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 766);
var columns       = this._sortable || [],
            sortableClass = this.getClassName('sortable', 'column'),
            ascClass      = this.getClassName('sorted'),
            descClass     = this.getClassName('sorted', 'desc'),
            linerClass    = this.getClassName('sort', 'liner'),
            indicatorClass= this.getClassName('sort', 'indicator'),
            sortableCols  = {},
            i, len, col, node, liner, title, desc;

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 775);
this.get('boundingBox').toggleClass(
            this.getClassName('sortable'),
            columns.length);

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 779);
for (i = 0, len = columns.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 780);
sortableCols[columns[i].id] = columns[i];
        }

        // TODO: this.head.render() + decorate cells?
        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 784);
this._theadNode.all('.' + sortableClass).each(function (node) {
            _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "(anonymous 2)", 784);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 785);
var col       = sortableCols[node.get('id')],
                liner     = node.one('.' + linerClass),
                indicator;

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 789);
if (col) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 790);
if (!col.sortDir) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 791);
node.removeClass(ascClass)
                        .removeClass(descClass);
                }
            } else {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 795);
node.removeClass(sortableClass)
                    .removeClass(ascClass)
                    .removeClass(descClass);

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 799);
if (liner) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 800);
liner.replace(liner.get('childNodes').toFrag());
                }

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 803);
indicator = node.one('.' + indicatorClass);

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 805);
if (indicator) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 806);
indicator.remove().destroy(true);
                }
            }
        });

        _yuitest_coverline("build/datatable-sort/datatable-sort.js", 811);
for (i = 0, len = columns.length; i < len; ++i) {
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 812);
col  = columns[i];
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 813);
node = this._theadNode.one('#' + col.id);
            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 814);
desc = col.sortDir === -1;

            _yuitest_coverline("build/datatable-sort/datatable-sort.js", 816);
if (node) {
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 817);
liner = node.one('.' + linerClass);

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 819);
node.addClass(sortableClass);

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 821);
if (col.sortDir) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 822);
node.addClass(ascClass);

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 824);
node.toggleClass(descClass, desc);

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 826);
node.setAttribute('aria-sort', desc ?
                        'descending' : 'ascending');
                }

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 830);
if (!liner) {
                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 831);
liner = Y.Node.create(Y.Lang.sub(
                        this.SORTABLE_HEADER_TEMPLATE, {
                            className: linerClass,
                            indicatorClass: indicatorClass
                        }));

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 837);
liner.prepend(node.get('childNodes').toFrag());

                    _yuitest_coverline("build/datatable-sort/datatable-sort.js", 839);
node.append(liner);
                }

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 842);
title = sub(this.getString(
                    (col.sortDir === 1) ? 'reverseSortBy' : 'sortBy'), {
                        column: col.abbr || col.label ||
                                col.key  || ('column ' + i)
                });

                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 848);
node.setAttribute('title', title);
                // To combat VoiceOver from reading the sort title as the
                // column header
                _yuitest_coverline("build/datatable-sort/datatable-sort.js", 851);
node.setAttribute('aria-labelledby', col.id);
            }
        }
    },

    /**
    Allows values `true`, `false`, "auto", or arrays of column names through.

    @method _validateSortable
    @param {Any} val The input value to `set("sortable", VAL)`
    @return {Boolean}
    @protected
    @since 3.5.0
    **/
    _validateSortable: function (val) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_validateSortable", 865);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 866);
return val === 'auto' || isBoolean(val) || isArray(val);
    },

    /**
    Allows strings, arrays of strings, objects, or arrays of objects.

    @method _validateSortBy
    @param {String|String[]|Object|Object[]} val The new `sortBy` value
    @return {Boolean}
    @protected
    @since 3.5.0
    **/
    _validateSortBy: function (val) {
        _yuitest_coverfunc("build/datatable-sort/datatable-sort.js", "_validateSortBy", 878);
_yuitest_coverline("build/datatable-sort/datatable-sort.js", 879);
return val === null ||
               isString(val) ||
               isObject(val, true) ||
               (isArray(val) && (isString(val[0]) || isObject(val, true)));
    }

}, true);

_yuitest_coverline("build/datatable-sort/datatable-sort.js", 887);
Y.DataTable.Sortable = Sortable;

_yuitest_coverline("build/datatable-sort/datatable-sort.js", 889);
Y.Base.mix(Y.DataTable, [Sortable]);


}, '3.7.3', {"requires": ["datatable-base"], "lang": ["en"], "skinnable": true});
