diff --git a/src/datatable/docs/index.mustache b/src/datatable/docs/index.mustache index 1914e4e49a5..f56bfa43628 100644 --- a/src/datatable/docs/index.mustache +++ b/src/datatable/docs/index.mustache @@ -1253,7 +1253,7 @@ YUI({ filter: 'raw' }).use('datatable-column-widths', function (Y) {
- By default, when `datatable-sort` is included, DataTables will inspects + By default, when `datatable-sort` is included, DataTables will inspect the `columns` objects, looking for `sortable: true` to enable table sorting by those columns, triggered by clicking on their respective headers.
diff --git a/src/datatable/js/sort.js b/src/datatable/js/sort.js index b73438d1aad..bc96e7563b1 100644 --- a/src/datatable/js/sort.js +++ b/src/datatable/js/sort.js @@ -77,7 +77,8 @@ As new records are inserted into the table's `data` ModelList, they will be inse 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 < and > on the field -value. If you need custom sorting, add a sort function in the column's +value. In the case where values are the same, the order of insertion is used +as a tiebreaker. 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. @@ -493,7 +494,7 @@ Y.mix(Sortable.prototype, { // extra function hop during sorting. Lesser of three evils? this.data._compare = function (a, b) { var cmp = 0, - i, len, col, dir, cs, aa, bb; + i, len, col, dir, cs, aa, bb, aId, bId; for (i = 0, len = self._sortBy.length; !cmp && i < len; ++i) { col = self._sortBy[i]; @@ -511,6 +512,19 @@ Y.mix(Sortable.prototype, { bb = bb.toLowerCase(); } cmp = (aa > bb) ? dir : ((aa < bb) ? -dir : 0); + + if (cmp === 0) { + /** + * In the case where values are the same, the model's clientId is used + * as a tiebreaker, effectively sorting by order of insertion. Can't + * do a straight parseInt on the clientId because the `_` is invalid + * and it will not return the number. So, `model_` is stripped + * and the `+` operator is used as a short way to cast to a Number. + */ + aId = +(a.get('clientId').replace(a.constructor.NAME + '_', '')); + bId = +(b.get('clientId').replace(b.constructor.NAME + '_', '')); + cmp = (aId > bId) ? dir : ((aId < bId) ? -dir : 0); + } } } diff --git a/src/datatable/tests/unit/assets/datatable-sort-tests.js b/src/datatable/tests/unit/assets/datatable-sort-tests.js index 03c1e41ec63..5b2b495b360 100644 --- a/src/datatable/tests/unit/assets/datatable-sort-tests.js +++ b/src/datatable/tests/unit/assets/datatable-sort-tests.js @@ -294,6 +294,72 @@ suite.add(new Y.Test.Case({ table.destroy(); }, + "sort should order based on insertion order when sorting against same values": function () { + var table = new Y.DataTable({ + columns: ['a', 'b'], + data: [ + { a: 'a1', b: 1 }, + { a: 'a1', b: 2 }, + { a: 'a1', b: 3 }, + { a: 'a1', b: 4 }, + { a: 'a1', b: 5 }, + { a: 'a1', b: 6 }, + { a: 'a2', b: 7 }, + { a: 'a3', b: 8 }, + { a: 'a4', b: 9 }, + { a: 'a5', b: 10 }, + { a: 'a6', b: 11 }, + ], + sortable: true + }).render(), + size, + idx; + + // Test initial sort order + table.data.each(function (item, index) { + Y.Assert.areEqual(++index, item.get('b'), 'item ' + item.get('a') + ' should be ' + index + ' in default order'); + }); + + // Test sort directions multiple times because they could change order each time + + table.sort({a:-1}); + size = table.data.size(); + table.data.each(function (item, index) { + idx = size - index; + Y.Assert.areEqual(idx, item.get('b'), 'item ' + item.get('a') + ' should be ' + idx + ' when desc sort'); + }); + + table.sort('a'); + table.data.each(function (item, index) { + Y.Assert.areEqual(++index, item.get('b'), 'item ' + item.get('a') + ' should be ' + index + ' in asc order'); + }); + + table.sort({a:-1}); + size = table.data.size(); + table.data.each(function (item, index) { + idx = size - index; + Y.Assert.areEqual(idx, item.get('b'), 'item ' + item.get('a') + ' should be ' + idx + ' when desc sort'); + }); + + table.sort('a'); + table.data.each(function (item, index) { + Y.Assert.areEqual(++index, item.get('b'), 'item ' + item.get('a') + ' should be ' + index + ' in asc order'); + }); + + table.sort({a:-1}); + size = table.data.size(); + table.data.each(function (item, index) { + idx = size - index; + Y.Assert.areEqual(idx, item.get('b'), 'item ' + item.get('a') + ' should be ' + idx + ' when desc sort'); + }); + + table.sort('a'); + table.data.each(function (item, index) { + Y.Assert.areEqual(++index, item.get('b'), 'item ' + item.get('a') + ' should be ' + index + ' in asc order'); + }); + + table.destroy(); + }, // reverse sorting a column "reverse sorting a column": function () { @@ -432,4 +498,4 @@ suite.add(new Y.Test.Case({ Y.Test.Runner.add(suite); -}, '@VERSION@' ,{requires:['datatable-sort', 'test', 'node-event-simulate']}); +}, '@VERSION@' ,{requires:['datatable', 'test', 'node-event-simulate']});