Skip to content

Commit

Permalink
Add option to disable multi-column sort and limit fields used for it.
Browse files Browse the repository at this point in the history
  • Loading branch information
aslagle committed Jun 22, 2015
1 parent cccb203 commit a05060e
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 33 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The reactiveTable helper accepts additional arguments that can be used to config
* `useFontAwesome`: Boolean. Whether to use [Font Awesome](http://fortawesome.github.io/Font-Awesome/) for icons. Requires the `fortawesome:fontawesome` package to be installed. Default `true` if `fortawesome:fontawesome` is installed, else `false`.
* `enableRegex`: Boolean. Whether to use filter text as a regular expression instead of a regular search term. When true, users won't be able to filter by special characters without escaping them. Default `false`. (Note: Setting this option on the client won't affect server-side filtering - see [Server-side pagination and filtering](#server-side-pagination-and-filtering-beta))
* `noDataTmpl`: Template. Template to render in place of the table when the collection is empty or filtered to 0 rows. Default none (renders table header with no rows).
* `multiColumnSort`: Boolean. Whether to enable sorting with multiple columns based on the order the user clicks them. Default: `true`.
* `class`: String. Classes to add to the table element in addition to 'reactive-table'. Default: 'table table-striped table-hover col-sm-12'.
* `id`: String. Unique id to add to the table element. Default: generated with [_.uniqueId](http://underscorejs.org/#uniqueId).
* `rowClass`: String or function returning a class name. The row element will be passed as first parameter.
Expand Down
22 changes: 12 additions & 10 deletions lib/reactive_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ var updateHandle = function (set_context) {

var options = {
skip: currentIndex,
limit: rowsPerPage
limit: rowsPerPage,
sort: getSortQuery(context.fields, context.multiColumnSort)
};
var sortQuery = {};

options.sort = getSortQuery(context.fields);

var filters = context.filters.get();

var onReady = function () {
Expand Down Expand Up @@ -151,6 +150,8 @@ var setup = function () {
}
context.collection = collection;

context.multiColumnSort = getDefaultTrueSetting('multiColumnSort', this.data);

var fields = this.data.fields || this.data.settings.fields || {};
if (_.keys(fields).length < 1 ||
(_.keys(fields).length === 1 &&
Expand Down Expand Up @@ -196,6 +197,7 @@ var setup = function () {
};

fields = _.map(fields, normalizeField);

context.fields = fields;

var visibleFields = [];
Expand Down Expand Up @@ -389,8 +391,8 @@ Template.reactiveTable.helpers({

'isPrimarySortField': function () {
var parentData = Template.parentData(1);
var primarySortField = getPrimarySortField(parentData.fields);
return primarySortField.fieldId === this.fieldId;
var primarySortField = getPrimarySortField(parentData.fields, parentData.multiColumnSort);
return primarySortField && primarySortField.fieldId === this.fieldId;
},

'isSortable': function () {
Expand Down Expand Up @@ -435,7 +437,7 @@ Template.reactiveTable.helpers({
}
});
} else {
var sortByValue = _.all(this.fields, function (field) {
var sortByValue = _.all(getSortedFields(this.fields, this.multiColumnSort), function (field) {
return field.sortByValue || !field.fn;
});
var filterQuery = getFilterQuery(getFilterStrings(this.filters.get()), getFilterFields(this.filters.get(), this.fields), {enableRegex: this.enableRegex});
Expand All @@ -446,7 +448,7 @@ Template.reactiveTable.helpers({

if (sortByValue) {

var sortQuery = getSortQuery(this.fields);
var sortQuery = getSortQuery(this.fields, this.multiColumnSort);
return this.collection.find(filterQuery, {
sort: sortQuery,
skip: skip,
Expand All @@ -456,7 +458,7 @@ Template.reactiveTable.helpers({
} else {

var rows = this.collection.find(filterQuery).fetch();
sortedRows = sortWithFunctions(rows, this.fields);
sortedRows = sortWithFunctions(rows, this.fields, this.multiColumnSort);
return sortedRows.slice(skip, skip + limit);

}
Expand Down Expand Up @@ -500,7 +502,7 @@ Template.reactiveTable.events({
var template = Template.instance();
var target = $(event.target).is('i') ? $(event.target).parent() : $(event.target);
var sortFieldId = target.attr('fieldid');
changePrimarySort(sortFieldId, template.context.fields);
changePrimarySort(sortFieldId, template.context.fields, template.multiColumnSort);
getUpdateHandleForTemplate(template)(template.context);
},

Expand Down
44 changes: 26 additions & 18 deletions lib/sort.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,35 @@ normalizeSort = function (field, oldField) {
field.sortDirection.set(sortDirection);
};

var getSortedFields = function (fields) {
return _.sortBy(fields, function (field) {
getSortedFields = function (fields, multiColumnSort) {
var filteredFields = _.filter(fields, function (field) {
return field.sortOrder.get() < Infinity;
});
if (!filteredFields.length) {
var firstSortableField = _.find(fields, function (field) {
return _.isUndefined(field.sortable) || field.sortable !== false;
});
if (firstSortableField) {
filteredFields = [firstSortableField];
}
}
var sortedFields = _.sortBy(filteredFields, function (field) {
return field.sortOrder.get();
});
return multiColumnSort ? sortedFields : sortedFields.slice(0, 1);
}

getSortQuery = function (fields) {
var sortedFields = getSortedFields(fields);
getSortQuery = function (fields, multiColumnSort) {
var sortedFields = getSortedFields(fields, multiColumnSort);
var sortQuery = {};
_.each(sortedFields, function (field) {
sortQuery[field.key] = field.sortDirection.get();
});
return sortQuery;
};

sortWithFunctions = function (rows, fields) {
var sortedFields = getSortedFields(fields);
sortWithFunctions = function (rows, fields, multiColumnSort) {
var sortedFields = getSortedFields(fields, multiColumnSort);
var sortedRows = rows;

_.each(sortedFields.reverse(), function (field) {
Expand All @@ -87,27 +99,23 @@ sortWithFunctions = function (rows, fields) {
return sortedRows;
};

getPrimarySortField = function (fields) {
var minSortField = _.min(fields, function (field) {
return field.sortOrder.get();
});
// if all values are Infinity _.min returns Infinity instead of the object
if (minSortField === Infinity) {
minSortField = fields[0];
}
return minSortField;
getPrimarySortField = function (fields, multiColumnSort) {
return getSortedFields(fields, multiColumnSort)[0];
};

changePrimarySort = function(fieldId, fields) {
var primarySortField = getPrimarySortField(fields);
if (primarySortField.fieldId === fieldId) {
changePrimarySort = function(fieldId, fields, multiColumnSort) {
var primarySortField = getPrimarySortField(fields, multiColumnSort);
if (primarySortField && primarySortField.fieldId === fieldId) {
var sortDirection = -1 * primarySortField.sortDirection.get();
primarySortField.sortDirection.set(sortDirection);
primarySortField.sortOrder.set(0);
} else {
_.each(fields, function (field) {
if (field.fieldId === fieldId) {
field.sortOrder.set(0);
if (primarySortField) {
field.sortDirection.set(primarySortField.sortDirection.get());
}
} else {
var sortOrder = 1 + field.sortOrder.get();
field.sortOrder.set(sortOrder);
Expand Down
2 changes: 1 addition & 1 deletion package.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package.describe({
summary: "A reactive table designed for Meteor",
version: "0.8.0",
version: "0.8.1",
name: "aslagle:reactive-table",
git: "https://github.com/aslagle/reactive-table.git"
});
Expand Down
46 changes: 46 additions & 0 deletions test/test_fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,52 @@ testAsyncMulti('Fields - sortDirection ReactiveVar', [function (test, expect) {
nameDirection.set(-1);
Meteor.setTimeout(expectDescending, 0);
}]);

Tinytest.add('Fields - default sort', function (test) {
testTable(
{
collection: rows,
fields: [
{key: 'name', label: 'Name'},
{key: 'score', label: 'Score'}
]
},
function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "sort should be ascending by first column");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "sort should be ascending by first column");
test.equal($('.reactive-table tbody tr:nth-child(6) td:first-child').text(), "Nikola Tesla", "sort should be ascending by first column");
}
);

testTable(
{
collection: rows,
fields: [
{key: 'name', label: 'Name', sortable: false},
{key: 'score', label: 'Score'}
]
},
function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "sort should be ascending by second column");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "sort should be ascending by second column");
test.equal($('.reactive-table tbody tr:nth-child(6) td:first-child').text(), "Nikola Tesla", "sort should be ascending by second column");
}
);

testTable(
{
collection: rows,
fields: [
{key: 'name', label: 'Name', sortable: false},
{key: 'score', label: 'Score', sortable: false}
]
},
function () {
test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
}
);
});

Tinytest.add('Fields - default sort DEPRECATED', function (test) {
_.each(['descending', 'desc', -1], function (sort) {
testTable(
Expand Down
86 changes: 82 additions & 4 deletions test/test_sorting.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ testAsyncMulti('Sorting - column', [function (test, expect) {
var expectSecondColumnDescending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column descending fourth row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column descending fourth row");
Blaze.remove(table);
});

var expectSecondColumn = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "2nd column second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column fourth row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column fourth row");

$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectSecondColumnDescending, 0);
Expand Down Expand Up @@ -151,10 +151,10 @@ testAsyncMulti('Sorting - server-side', [function (test, expect) {
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");

$('.reactive-table th:first-child').click();
Meteor.setTimeout(expectDescending, 500);
Meteor.setTimeout(expectDescending, 1000);
});

Meteor.setTimeout(expectDefaultAscending, 500);
Meteor.setTimeout(expectDefaultAscending, 1000);
}]);

testAsyncMulti('Sorting - virtual columns', [function (test, expect) {
Expand Down Expand Up @@ -192,3 +192,81 @@ testAsyncMulti('Sorting - virtual columns', [function (test, expect) {
$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectNoChange, 0);
}]);

testAsyncMulti('Sorting - multi-column', [function (test, expect) {
var table = Blaze.renderWithData(
Template.reactiveTable,
{collection: collection},
document.body
);
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");

var expectSecondColumnAscending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "2nd column second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Ada Lovelace", "2nd column fourth row");
Blaze.remove(table);
});

var expectSecondColumnDescending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column descending fourth row");

$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectSecondColumnAscending, 0);
});

var expectFirstColumnDescending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "first column descending first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "first column descending second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "first column descending fourth row");

$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectSecondColumnDescending, 0);
});

$('.reactive-table th:first-child').click();
Meteor.setTimeout(expectFirstColumnDescending, 0);
}]);

testAsyncMulti('Sorting - multi-column disabled', [function (test, expect) {
var table = Blaze.renderWithData(
Template.reactiveTable,
{collection: collection, multiColumnSort: false},
document.body
);
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");

var expectSecondColumnAscending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "2nd column second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column fourth row");
Blaze.remove(table);
});

var expectSecondColumnDescending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column descending fourth row");

$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectSecondColumnAscending, 0);
});

var expectFirstColumnDescending = expect(function () {
test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "first column descending first row");
test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "first column descending second row");
test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "first column descending fourth row");

$('.reactive-table th:nth-child(2)').click();
Meteor.setTimeout(expectSecondColumnDescending, 0);
});

$('.reactive-table th:first-child').click();
Meteor.setTimeout(expectFirstColumnDescending, 0);
}]);

0 comments on commit a05060e

Please sign in to comment.