├── .gitignore
├── examples
├── leaderboard
│ ├── .meteor
│ │ ├── cordova-plugins
│ │ ├── .gitignore
│ │ ├── release
│ │ ├── platforms
│ │ ├── identifier
│ │ ├── .id
│ │ ├── packages
│ │ ├── .finished-upgraders
│ │ └── versions
│ ├── leaderboard.html
│ ├── leaderboard.css
│ └── leaderboard.js
├── saved-state
│ ├── .meteor
│ │ ├── cordova-plugins
│ │ ├── .gitignore
│ │ ├── release
│ │ ├── platforms
│ │ ├── identifier
│ │ ├── .id
│ │ ├── packages
│ │ ├── .finished-upgraders
│ │ └── versions
│ ├── leaderboard.html
│ ├── leaderboard.css
│ └── leaderboard.js
├── server
│ ├── .meteor
│ │ ├── .gitignore
│ │ ├── release
│ │ ├── platforms
│ │ ├── .id
│ │ ├── .finished-upgraders
│ │ ├── packages
│ │ └── versions
│ ├── table.html
│ └── table.js
├── table-features
│ ├── .meteor
│ │ ├── cordova-plugins
│ │ ├── .gitignore
│ │ ├── release
│ │ ├── platforms
│ │ ├── packages
│ │ ├── .id
│ │ ├── .finished-upgraders
│ │ └── versions
│ ├── table-features.css
│ ├── table-features.html
│ └── table-features.js
└── advanced-filters
│ ├── .meteor
│ ├── .gitignore
│ ├── release
│ ├── platforms
│ ├── .id
│ ├── .finished-upgraders
│ ├── packages
│ └── versions
│ ├── clear_filters_button.html
│ ├── autocomplete_filter.html
│ ├── date_filter.html
│ ├── quote_filter.html
│ ├── clear_filters_button.js
│ ├── greater_than_filter.html
│ ├── checkbox_filter.html
│ ├── select_filter.js
│ ├── compound_filter.html
│ ├── select_filter.html
│ ├── quote_filter.js
│ ├── greater_than_filter.js
│ ├── date_filter.js
│ ├── checkbox_filter.js
│ ├── compound_filter.js
│ ├── autocomplete_filter.js
│ ├── leaderboard.css
│ ├── leaderboard.html
│ └── leaderboard.js
├── test
├── test_no_data_template.html
├── test_events_tmpl.html
├── README.md
├── test_reactivity.html
├── test_template.html
├── test_fields_tmpl.html
├── test_events.js
├── test_template.js
├── test_reactivity_server.js
├── test_i18n.js
├── test_filtering_server.js
├── helpers.js
├── test_compatibility.js
├── test_collection_argument.js
├── test_multiple_tables.js
├── test_pagination.js
├── test_column_toggles.js
├── test_reactivity.js
├── test_filtering.js
├── test_sorting.js
└── test_settings.js
├── LICENSE
├── lib
├── filter.html
├── reactive_table.css
├── server.js
├── sort.js
├── reactive_table.html
├── reactive_table_i18n.js
└── filter.js
├── versions.json
└── package.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .build*
2 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/cordova-plugins:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/cordova-plugins:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/server/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/server/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/cordova-plugins:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/examples/server/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.1
2 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/identifier:
--------------------------------------------------------------------------------
1 | 1aqojff1qds7wz1aca7ne
2 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/identifier:
--------------------------------------------------------------------------------
1 | 1aqojff1qds7wz1aca7ne
2 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/test/test_no_data_template.html:
--------------------------------------------------------------------------------
1 |
2 | No data found
3 |
--------------------------------------------------------------------------------
/test/test_events_tmpl.html:
--------------------------------------------------------------------------------
1 |
2 | {{> reactiveTable collection=collection}}
3 |
4 |
--------------------------------------------------------------------------------
/examples/table-features/table-features.css:
--------------------------------------------------------------------------------
1 | #content {
2 | margin: 100px;
3 | }
4 |
5 | #table {
6 | margin-top: 3em;
7 | }
8 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | Start the test app:
2 |
3 | meteor test-packages ./
4 |
5 | And open http://localhost:3000 to run the tests
6 |
--------------------------------------------------------------------------------
/test/test_reactivity.html:
--------------------------------------------------------------------------------
1 |
2 | {{> reactiveTable collection=collection settings=settings}}
3 |
--------------------------------------------------------------------------------
/test/test_template.html:
--------------------------------------------------------------------------------
1 |
2 | {{#each array}}
3 | {{> reactiveTable collection=../collection}}
4 | {{/each}}
5 |
6 |
--------------------------------------------------------------------------------
/examples/advanced-filters/clear_filters_button.html:
--------------------------------------------------------------------------------
1 |
2 | Clear Filters
3 |
--------------------------------------------------------------------------------
/test/test_fields_tmpl.html:
--------------------------------------------------------------------------------
1 |
2 | {{name}}{{score}}
3 |
4 |
5 |
6 | nodata
7 |
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/autocomplete_filter.html:
--------------------------------------------------------------------------------
1 |
2 | {{> inputAutocomplete settings=settings id="name-autocomplete-filter" class="input-xlarge" placeholder="Name..."}}
3 |
4 |
5 |
6 | {{name}}
7 |
--------------------------------------------------------------------------------
/examples/server/table.html:
--------------------------------------------------------------------------------
1 |
2 | Reactive Table
3 |
4 |
5 |
6 | Welcome to Reactive Table!
7 |
8 | {{> table}}
9 |
10 |
11 |
12 | {{> reactiveTable collection="rows" fields=fields}}
13 |
14 |
--------------------------------------------------------------------------------
/examples/advanced-filters/date_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Date
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/quote_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Match this quote
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/clear_filters_button.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 |
3 | Template.clearFiltersButton.events({
4 | "click #clear-filters-button" : function () {
5 | ReactiveTable.clearFilters(['filter1', 'filter2', 'filter3', 'date-filter', 'checkbox-filter']);
6 | }
7 | });
8 |
9 | }
--------------------------------------------------------------------------------
/examples/advanced-filters/greater_than_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Score Greater Than
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/server/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 134onf5cggsx7x68ee7
8 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | standard-app-packages
7 | autopublish
8 | twbs:bootstrap-noglyph
9 | aslagle:reactive-table
10 | standard-minifier-css
11 | standard-minifier-js
12 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | l394661aru71g1gfqsy5
8 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | l394661aru71g1gfqsy5
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | no5plc1767uzekceye8
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/checkbox_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Show Checked Only
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | l7em6qaz16yt17typqu
8 |
--------------------------------------------------------------------------------
/examples/advanced-filters/select_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.selectFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('select-filter', ['score']);
4 | };
5 |
6 | Template.selectFilter.events({
7 | "change .select-filter": function (event, template) {
8 | template.filter.set($(event.target).val());
9 | }
10 | });
11 | }
--------------------------------------------------------------------------------
/examples/advanced-filters/compound_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Show Score Greater Than 20 and Checked
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | standard-app-packages
7 | autopublish
8 | insecure
9 | aslagle:reactive-table
10 | anti:i18n
11 | twbs:bootstrap-noglyph
12 | fortawesome:fontawesome
13 | standard-minifier-css
14 | standard-minifier-js
15 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | standard-app-packages
7 | autopublish
8 | insecure
9 | aslagle:reactive-table
10 | anti:i18n
11 | twbs:bootstrap-noglyph
12 | fortawesome:fontawesome
13 | reactive-var
14 | standard-minifier-css
15 | standard-minifier-js
16 |
--------------------------------------------------------------------------------
/examples/server/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/advanced-filters/select_filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Select a score:
5 |
6 |
7 | Any
8 | 0
9 | 10
10 | 20
11 | 30
12 | 40
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/advanced-filters/quote_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.quoteFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('quote-filter');
4 | };
5 |
6 | Template.quoteFilter.events({
7 | "keyup .quote-filter-input, input .quote-filter-input": function (event, template) {
8 | var input = $(event.target).val();
9 | if (input) {
10 | template.filter.set('"' + input + '"');
11 | } else {
12 | template.filter.set("");
13 | }
14 | }
15 | });
16 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Amy Slagle
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/examples/server/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | insecure
8 | aslagle:reactive-table
9 | twbs:bootstrap
10 | audit-argument-checks
11 | meteor-base
12 | mobile-experience
13 | mongo
14 | blaze-html-templates
15 | session
16 | jquery
17 | tracker
18 | logging
19 | reload
20 | random
21 | ejson
22 | spacebars
23 | check
24 | standard-minifier-css
25 | standard-minifier-js
26 |
--------------------------------------------------------------------------------
/examples/advanced-filters/greater_than_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.greaterThanFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('greater-than-filter', ['score']);
4 | };
5 |
6 | Template.greaterThanFilter.events({
7 | "keyup .greater-than-filter-input, input .greater-than-filter-input": function (event, template) {
8 | var input = parseInt($(event.target).val(), 10);
9 | if (!_.isNaN(input)) {
10 | template.filter.set({'$gt': input});
11 | } else {
12 | template.filter.set("");
13 | }
14 | }
15 | });
16 | }
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | autopublish
8 | insecure
9 | aslagle:reactive-table
10 | twbs:bootstrap
11 | momentjs:moment
12 | linto:jquery-ui
13 | jquery
14 | mizzao:autocomplete
15 | meteor-base
16 | mobile-experience
17 | mongo
18 | blaze-html-templates
19 | session
20 | tracker
21 | logging
22 | reload
23 | random
24 | ejson
25 | spacebars
26 | check
27 | standard-minifier-css
28 | standard-minifier-js
29 |
--------------------------------------------------------------------------------
/examples/advanced-filters/date_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.dateFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('date-filter', ['date']);
4 | };
5 |
6 | Template.dateFilter.rendered = function () {
7 | $('.date-selector').datepicker();
8 | };
9 |
10 | Template.dateFilter.events({
11 | "change .date-selector": function (event, template) {
12 | if ($(event.target).val()) {
13 | var date = moment($(event.target).val());
14 | template.filter.set(date.format("YYYY-MM-DD"));
15 | } else {
16 | template.filter.set("");
17 | }
18 | }
19 | });
20 | }
--------------------------------------------------------------------------------
/examples/server/table.js:
--------------------------------------------------------------------------------
1 | var Rows = new Meteor.Collection('rows');
2 |
3 | if (Meteor.isClient) {
4 |
5 | Template.table.helpers({
6 | fields: function () {
7 | return ['string', 'number'];
8 | }
9 | });
10 | }
11 |
12 | if (Meteor.isServer) {
13 | Meteor.startup(function () {
14 | if (Rows.find().count() === 0) {
15 | for (var i = 0; i < 20000; i++) {
16 | var randomString = Math.random().toString(36).substr(7);
17 | var randomNumber = Math.floor(Math.random() * 100);
18 | Rows.insert({string: randomString, number: randomNumber});
19 | }
20 | }
21 | });
22 |
23 | ReactiveTable.publish('rows', function () { return Rows; }, {});
24 | }
25 |
--------------------------------------------------------------------------------
/test/test_events.js:
--------------------------------------------------------------------------------
1 | testAsyncMulti('Events - click event', [function (test, expect) {
2 | var expectClicked = expect(function () {
3 | Blaze.remove(table);
4 | });
5 |
6 | var clickHandler = function () {
7 | test.equal(this.name, "Ada Lovelace", "this should be the row object");
8 | test.equal(this.score, 5, "this should be the row object");
9 | expectClicked();
10 | };
11 |
12 | Template.testEventsTmpl.events({
13 | "click .reactive-table tbody tr": clickHandler
14 | });
15 |
16 | var table = Blaze.renderWithData(
17 | Template.testEventsTmpl,
18 | {collection: rows},
19 | document.body
20 | );
21 |
22 | $('.reactive-table tbody tr:first-child').click();
23 | }]);
24 |
--------------------------------------------------------------------------------
/examples/advanced-filters/checkbox_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.checkboxFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('checkbox-filter', ['checked']);
4 | };
5 |
6 | Template.checkboxFilter.helpers({
7 | checked: function () {
8 | if (Template.instance().filter.get() === "true") {
9 | return "checked";
10 | }
11 | return "";
12 | }
13 | });
14 |
15 | Template.checkboxFilter.events({
16 | "change .checkbox-filter": function (event, template) {
17 | if ($(event.target).is(":checked")) {
18 | template.filter.set("true");
19 | } else {
20 | template.filter.set("");
21 | }
22 | }
23 | });
24 | }
--------------------------------------------------------------------------------
/test/test_template.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Template - nested', function (test) {
2 | var view = Blaze.renderWithData(
3 | Template.testTmpl,
4 | {collection: rows, array: [1]},
5 | document.body
6 | );
7 | test.length($('.reactive-table th'), 2, "two columns should be rendered");
8 | test.length($('.reactive-table tbody tr td:first-child'), 6, "six rows should be rendered and have cells");
9 | Blaze.remove(view);
10 |
11 | var view2 = Blaze.renderWithData(
12 | Template.testTmpl,
13 | {collection: rows, array: [{fields: 'abc'}]},
14 | document.body
15 | );
16 | test.length($('.reactive-table th'), 2, "two columns should be rendered");
17 | test.length($('.reactive-table tbody tr td:first-child'), 6, "six rows should be rendered and have cells");
18 | Blaze.remove(view2);
19 | });
20 |
--------------------------------------------------------------------------------
/lib/filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{#if useFontAwesome}}
5 |
6 | {{else}}
7 | {{#if label}}
8 | {{label}}
9 | {{else}}
10 | {{i18n 'reactiveTable.filter'}}
11 | {{/if}}
12 | {{/if}}
13 |
14 | {{#if useFontAwesome}}
15 | {{#if label}}
16 |
17 | {{else}}
18 |
19 | {{/if}}
20 | {{else}}
21 |
22 | {{/if}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/leaderboard/leaderboard.html:
--------------------------------------------------------------------------------
1 |
2 | Leaderboard
3 |
4 |
5 |
6 |
7 | {{> leaderboard}}
8 |
9 |
10 |
11 |
12 |
13 | {{> reactiveTable collection=players settings=tableSettings}}
14 |
15 |
16 | {{#each players}}
17 | {{> player}}
18 | {{/each}}
19 |
20 |
21 | {{#if selected_name}}
22 |
23 |
{{selected_name}}
24 |
25 |
26 | {{else}}
27 | Click a player to select
28 | {{/if}}
29 |
30 |
31 |
32 |
33 | {{name}}
34 | {{score}}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/saved-state/leaderboard.html:
--------------------------------------------------------------------------------
1 |
2 | Leaderboard
3 |
4 |
5 |
6 |
7 | {{> leaderboard}}
8 |
9 |
10 |
11 |
12 |
13 | {{> reactiveTable collection=players settings=tableSettings}}
14 |
15 |
16 | {{#each players}}
17 | {{> player}}
18 | {{/each}}
19 |
20 |
21 | {{#if selected_name}}
22 |
23 |
{{selected_name}}
24 |
25 |
26 | {{else}}
27 | Click a player to select
28 | {{/if}}
29 |
30 |
31 |
32 |
33 | {{name}}
34 | {{score}}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/test/test_reactivity_server.js:
--------------------------------------------------------------------------------
1 | var collection = new Mongo.Collection('reactivity-test');
2 | collection.remove({});
3 | collection.insert({name: 'item 1', value: 1});
4 |
5 | ReactiveTable.publish('reactivity-test', collection);
6 |
7 | Meteor.methods({
8 | "testInsert": function () {
9 | collection.insert({name: 'item 2', value: 2});
10 | },
11 |
12 | "testRemove": function () {
13 | collection.remove({name: 'item 2'});
14 | },
15 |
16 | "testUpdate": function () {
17 | collection.update({name: 'item 1'}, {'$set': {value: 2}});
18 | }
19 | });
20 |
21 | var collection2 = new Mongo.Collection('reactivity-test-access');
22 | collection2.remove({});
23 | collection2.insert({name: 'item 1', value: 1});
24 |
25 | if (!Meteor.users.findOne()) {
26 | Accounts.createUser({username: 'abcd', password: 'abcd1234'});
27 | }
28 |
29 |
30 | ReactiveTable.publish('reactivity-test-access', function () {
31 | if (this.userId) {
32 | return collection2;
33 | } else {
34 | return [];
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/examples/advanced-filters/compound_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.compoundFilter.created = function () {
3 | this.checkFilter = new ReactiveTable.Filter('compound-check-filter', ['checked']);
4 | this.scoreFilter = new ReactiveTable.Filter('compound-score-filter', ['score']);
5 | };
6 |
7 | Template.compoundFilter.helpers({
8 | checked: function () {
9 | var checkTrue = Template.instance().checkFilter.get() === "true";
10 | var scoreTrue = _.isObject(Template.instance().scoreFilter.get());
11 | if (checkTrue && scoreTrue) {
12 | return "checked";
13 | }
14 | return "";
15 | }
16 | });
17 |
18 | Template.compoundFilter.events({
19 | "change .compound-checkbox": function (event, template) {
20 | if ($(event.target).is(":checked")) {
21 | template.checkFilter.set("true");
22 | template.scoreFilter.set({'$gt': 20});
23 | } else {
24 | template.checkFilter.set("");
25 | template.scoreFilter.set("");
26 | }
27 | }
28 | });
29 | }
--------------------------------------------------------------------------------
/test/test_i18n.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('i18n - default english', function (test) {
2 | testTable(
3 | rows,
4 | function () {
5 | test.equal($('.reactive-table-filter span').text().trim(), "Filter", "filter text");
6 | test.length($('.reactive-table-navigation .rows-per-page label span:first-of-type').text().trim().match(/^Show$/), 1, "show text")
7 | test.length($('.reactive-table-navigation .rows-per-page .rows-per-page-label').text().trim().match(/^rows\sper\spage$/), 1, "rows per page text");
8 | }
9 | );
10 | });
11 |
12 | Tinytest.add('i18n - french', function (test) {
13 | i18n.setLanguage('fr');
14 | testTable(
15 | rows,
16 | function () {
17 | test.equal($('.reactive-table-filter span').text().trim(), "Filtre", "filter text");
18 | test.length($('.reactive-table-navigation .rows-per-page label span:first-of-type').text().trim().match(/^Voir$/), 1, "show text")
19 | test.length($('.reactive-table-navigation .rows-per-page .rows-per-page-label').text().trim().match(/^lignes\spar\spage$/), 1, "rows per page text");
20 | }
21 | );
22 | i18n.setLanguage('en');
23 | });
24 |
--------------------------------------------------------------------------------
/test/test_filtering_server.js:
--------------------------------------------------------------------------------
1 | var collection = new Mongo.Collection('filter-regex-test');
2 |
3 | collection.remove({});
4 |
5 | collection.insert({name: 'item 1', value: '1+2'});
6 | collection.insert({name: 'item 2', value: 'abc'});
7 |
8 | ReactiveTable.publish('filter-regex-disabled', collection, {}, {enableRegex: false});
9 | ReactiveTable.publish('filter-regex-enabled', collection, {}, {enableRegex: true});
10 |
11 | ReactiveTable.publish('filter-inclusion', collection, {}, {fields: {name: 1}});
12 | ReactiveTable.publish('filter-exclusion', collection, {}, {fields: {value: 0}});
13 |
14 | var collectionWithNestedKeys = new Mongo.Collection('filter-nested-keys');
15 |
16 | collectionWithNestedKeys.remove({});
17 |
18 | collectionWithNestedKeys.insert({'name': 'item 1', 'nested': [{'value': 'value 1'}, {'value': 'other'}]});
19 | collectionWithNestedKeys.insert({'name': 'item 2', 'nested': [{'value': 'value 2'}]});
20 |
21 | ReactiveTable.publish('nested-filter-inclusion', collectionWithNestedKeys, {}, {fields: {nested: 1}});
22 | ReactiveTable.publish('nested-filter-exclusion', collectionWithNestedKeys, {}, {fields: {nested: 0}});
23 |
24 | ReactiveTable.publish('nested-filter-inclusion-with-array', collectionWithNestedKeys, {}, {fields: {"nested.value": 1}})
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | testTable = function(tableData, testFunction) {
2 | var table = Blaze.renderWithData(
3 | Template.reactiveTable,
4 | tableData,
5 | document.body
6 | );
7 | testFunction();
8 | Blaze.remove(table);
9 | };
10 |
11 | rows = [
12 | {name: 'Ada Lovelace', score: 5},
13 | {name: 'Grace Hopper', score: 10},
14 | {name: 'Marie Curie', score: 5},
15 | {name: 'Carl Friedrich Gauss', score: 0},
16 | {name: 'Nikola Tesla', score: 15},
17 | {name: 'Claude Shannon', score: 5}
18 | ];
19 |
20 | if (Meteor.isClient) {
21 | collection = new Mongo.Collection();
22 | _.each(rows, function (row) {
23 | collection.insert(row);
24 | });
25 | }
26 |
27 | if (Meteor.isServer) {
28 | collection = new Mongo.Collection('players');
29 | collection.remove({});
30 | _.each(rows, function (row) {
31 | collection.insert(row);
32 | });
33 |
34 | ReactiveTable.publish('collection', collection);
35 |
36 | ReactiveTable.publish('collection-and-selector', collection, {score: 5});
37 |
38 | ReactiveTable.publish('collection-function', function () {
39 | return collection;
40 | });
41 |
42 | ReactiveTable.publish('selector-function', collection, function () {
43 | return {score: 5};
44 | });
45 |
46 | ReactiveTable.publish('collection-or-filter', collection, {}, {filterOperator: "$or"});
47 | }
48 |
--------------------------------------------------------------------------------
/examples/advanced-filters/autocomplete_filter.js:
--------------------------------------------------------------------------------
1 | if (Meteor.isClient) {
2 | Template.autocompleteFilter.created = function () {
3 | this.filter = new ReactiveTable.Filter('autocomplete-filter', ['name']);
4 | };
5 |
6 | Template.autocompleteFilter.helpers({
7 | settings: function () {
8 | var filter = Template.instance().filter;
9 | return {
10 | position: "top",
11 | limit: 5,
12 | rules: [
13 | {
14 | collection: "Players",
15 | subscription: "players-autocomplete",
16 | field: "name",
17 | template: Template.namePill,
18 | callback: function(doc) {
19 | return filter.set(doc.name);
20 | }
21 | }
22 | ]
23 | };
24 | }
25 | });
26 |
27 | Template.autocompleteFilter.events({
28 | "input #name-autocomplete-filter": function (event, template) {
29 | var input = $(event.target).val();
30 | if (input) {
31 | template.filter.set(input);
32 | } else {
33 | template.filter.set("");
34 | }
35 | }
36 | });
37 | }
38 |
39 | if (Meteor.isServer) {
40 | Meteor.publish("players-autocomplete", function (selector, options) {
41 | // guard against client-side DOS: hard limit to 50
42 | if (options.limit) {
43 | options.limit = Math.min(50, Math.abs(options.limit));
44 | }
45 |
46 | Autocomplete.publishCursor(Players.find(selector, options), this);
47 | this.ready();
48 | })
49 | }
--------------------------------------------------------------------------------
/examples/leaderboard/leaderboard.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 200;
4 | margin: 50px 0;
5 | padding: 0;
6 | -webkit-user-select: none;
7 | -khtml-user-select: none;
8 | -moz-user-select: none;
9 | -o-user-select: none;
10 | user-select: none;
11 | }
12 |
13 | .leaderboard {
14 | margin-top: 150px;
15 | border-top: 1px solid black;
16 | }
17 |
18 | #table {
19 | -webkit-user-select: text;
20 | -khtml-user-select: text;
21 | -moz-user-select: text;
22 | -o-user-select: text;
23 | user-select: text;
24 | }
25 |
26 | #outer {
27 | width: 600px;
28 | margin: 0 auto;
29 | }
30 |
31 | .player {
32 | cursor: pointer;
33 | padding: 5px;
34 | }
35 |
36 | .player .name {
37 | display: inline-block;
38 | width: 300px;
39 | font-size: 1.75em;
40 | }
41 |
42 | .player .score {
43 | display: inline-block;
44 | width: 100px;
45 | text-align: right;
46 | font-size: 2em;
47 | font-weight: bold;
48 | color: #777;
49 | }
50 |
51 | .player.selected {
52 | background-color: yellow;
53 | }
54 |
55 | .player.selected .score {
56 | color: black;
57 | }
58 |
59 | .details, .none {
60 | font-weight: bold;
61 | font-size: 2em;
62 | border-style: dashed none none none;
63 | border-color: #ccc;
64 | border-width: 4px;
65 | margin: 50px 10px;
66 | padding: 10px 0px;
67 | }
68 |
69 | .none {
70 | color: #777;
71 | }
72 |
73 | .inc {
74 | cursor: pointer;
75 | }
76 |
--------------------------------------------------------------------------------
/examples/saved-state/leaderboard.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 200;
4 | margin: 50px 0;
5 | padding: 0;
6 | -webkit-user-select: none;
7 | -khtml-user-select: none;
8 | -moz-user-select: none;
9 | -o-user-select: none;
10 | user-select: none;
11 | }
12 |
13 | .leaderboard {
14 | margin-top: 150px;
15 | border-top: 1px solid black;
16 | }
17 |
18 | #table {
19 | -webkit-user-select: text;
20 | -khtml-user-select: text;
21 | -moz-user-select: text;
22 | -o-user-select: text;
23 | user-select: text;
24 | }
25 |
26 | #outer {
27 | width: 600px;
28 | margin: 0 auto;
29 | }
30 |
31 | .player {
32 | cursor: pointer;
33 | padding: 5px;
34 | }
35 |
36 | .player .name {
37 | display: inline-block;
38 | width: 300px;
39 | font-size: 1.75em;
40 | }
41 |
42 | .player .score {
43 | display: inline-block;
44 | width: 100px;
45 | text-align: right;
46 | font-size: 2em;
47 | font-weight: bold;
48 | color: #777;
49 | }
50 |
51 | .player.selected {
52 | background-color: yellow;
53 | }
54 |
55 | .player.selected .score {
56 | color: black;
57 | }
58 |
59 | .details, .none {
60 | font-weight: bold;
61 | font-size: 2em;
62 | border-style: dashed none none none;
63 | border-color: #ccc;
64 | border-width: 4px;
65 | margin: 50px 10px;
66 | padding: 10px 0px;
67 | }
68 |
69 | .none {
70 | color: #777;
71 | }
72 |
73 | .inc {
74 | cursor: pointer;
75 | }
76 |
--------------------------------------------------------------------------------
/examples/table-features/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | anti:i18n@0.4.3
3 | aslagle:reactive-table@0.8.29
4 | autopublish@1.0.6
5 | autoupdate@1.2.7
6 | babel-compiler@6.6.1
7 | babel-runtime@0.1.7
8 | base64@1.0.7
9 | binary-heap@1.0.7
10 | blaze@2.1.6
11 | blaze-tools@1.0.7
12 | boilerplate-generator@1.0.7
13 | caching-compiler@1.0.3
14 | caching-html-compiler@1.0.5
15 | callback-hook@1.0.7
16 | check@1.1.3
17 | ddp@1.2.4
18 | ddp-client@1.2.4
19 | ddp-common@1.2.4
20 | ddp-server@1.2.5
21 | deps@1.0.11
22 | diff-sequence@1.0.4
23 | ecmascript@0.4.2
24 | ecmascript-runtime@0.2.9
25 | ejson@1.0.10
26 | fastclick@1.0.10
27 | geojson-utils@1.0.7
28 | html-tools@1.0.8
29 | htmljs@1.0.8
30 | http@1.1.4
31 | id-map@1.0.6
32 | jquery@1.11.7
33 | launch-screen@1.0.10
34 | livedata@1.0.17
35 | logging@1.0.11
36 | meteor@1.1.13
37 | meteor-platform@1.2.5
38 | minifier-css@1.1.10
39 | minifier-js@1.1.10
40 | minimongo@1.0.13
41 | mobile-status-bar@1.0.11
42 | modules@0.5.2
43 | modules-runtime@0.6.2
44 | mongo@1.1.6
45 | mongo-id@1.0.3
46 | npm-mongo@1.4.42
47 | observe-sequence@1.0.10
48 | ordered-dict@1.0.6
49 | promise@0.6.6
50 | random@1.0.8
51 | reactive-dict@1.1.6
52 | reactive-var@1.0.8
53 | reload@1.1.7
54 | retry@1.0.6
55 | routepolicy@1.0.9
56 | session@1.1.4
57 | spacebars@1.0.10
58 | spacebars-compiler@1.0.10
59 | standard-app-packages@1.0.8
60 | standard-minifier-css@1.0.5
61 | standard-minifier-js@1.0.5
62 | templating@1.1.8
63 | templating-tools@1.0.3
64 | tracker@1.0.12
65 | twbs:bootstrap-noglyph@3.3.5
66 | ui@1.0.10
67 | underscore@1.0.7
68 | url@1.0.8
69 | webapp@1.2.7
70 | webapp-hashing@1.0.8
71 |
--------------------------------------------------------------------------------
/examples/advanced-filters/leaderboard.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 200;
4 | margin: 50px 0;
5 | padding: 0;
6 | -webkit-user-select: none;
7 | -khtml-user-select: none;
8 | -moz-user-select: none;
9 | -o-user-select: none;
10 | user-select: none;
11 | }
12 |
13 | .leaderboard {
14 | margin-top: 150px;
15 | border-top: 1px solid black;
16 | }
17 |
18 | #table {
19 | -webkit-user-select: text;
20 | -khtml-user-select: text;
21 | -moz-user-select: text;
22 | -o-user-select: text;
23 | user-select: text;
24 | }
25 |
26 | #outer {
27 | width: 600px;
28 | margin: 0 auto;
29 | }
30 |
31 | .player {
32 | cursor: pointer;
33 | padding: 5px;
34 | }
35 |
36 | .player .name {
37 | display: inline-block;
38 | width: 300px;
39 | font-size: 1.75em;
40 | }
41 |
42 | .player .score {
43 | display: inline-block;
44 | width: 100px;
45 | text-align: right;
46 | font-size: 2em;
47 | font-weight: bold;
48 | color: #777;
49 | }
50 |
51 | .player.selected {
52 | background-color: yellow;
53 | }
54 |
55 | .player.selected .score {
56 | color: black;
57 | }
58 |
59 | .details, .none {
60 | font-weight: bold;
61 | font-size: 2em;
62 | border-style: dashed none none none;
63 | border-color: #ccc;
64 | border-width: 4px;
65 | margin: 50px 10px;
66 | padding: 10px 0px;
67 | }
68 |
69 | .none {
70 | color: #777;
71 | }
72 |
73 | .inc {
74 | cursor: pointer;
75 | }
76 |
77 | #filter .input-group {
78 | margin: 5px 0px;
79 | }
80 |
--------------------------------------------------------------------------------
/lib/reactive_table.css:
--------------------------------------------------------------------------------
1 | .reactive-table-options {
2 | padding-right: 0px;
3 | margin-right: -5px;
4 | }
5 |
6 | .reactive-table-filter {
7 | float: right;
8 | }
9 |
10 | .reactive-table-columns-dropdown {
11 | float: right;
12 | }
13 |
14 | .reactive-table-columns-dropdown button {
15 | float: right;
16 | }
17 |
18 | .reactive-table .sortable, .reactive-table-add-column {
19 | cursor: pointer;
20 | -webkit-user-select: none;
21 | -moz-user-select: none;
22 | -ms-user-select: none;
23 | user-select: none;
24 | }
25 |
26 | .table > thead > tr > th.reactive-table-add-column {
27 | border-bottom: none;
28 | }
29 |
30 | .reactive-table-navigation {
31 | display: inline-block;
32 | width: 100%;
33 | }
34 |
35 | .reactive-table-navigation .form-inline input {
36 | width: 45px;
37 | }
38 |
39 | .reactive-table-navigation .rows-per-page {
40 | float: left;
41 | }
42 |
43 | .reactive-table-navigation .page-number {
44 | float: right;
45 | }
46 |
47 | .reactive-table-navigation .previous-page,
48 | .reactive-table-navigation .next-page {
49 | font-size: 130%;
50 | margin: 0 5px;
51 | padding: 0.5em;
52 | }
53 |
54 | .reactive-table-navigation .previous-page.fa,
55 | .reactive-table-navigation .next-page.fa {
56 | vertical-align: middle;
57 | }
58 |
59 | .reactive-table-navigation .previous-page:hover,
60 | .reactive-table-navigation .next-page:hover {
61 | cursor: pointer;
62 | }
63 |
64 | .reactive-table .fa-sort-asc {
65 | position: relative;
66 | top: -2px;
67 | }
68 |
69 | .reactive-table .fa-sort-desc {
70 | position: relative;
71 | top: 2px;
72 | }
73 |
--------------------------------------------------------------------------------
/examples/leaderboard/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | anti:i18n@0.4.3
3 | aslagle:reactive-table@0.8.29
4 | autopublish@1.0.6
5 | autoupdate@1.2.7
6 | babel-compiler@6.6.1
7 | babel-runtime@0.1.7
8 | base64@1.0.7
9 | binary-heap@1.0.7
10 | blaze@2.1.6
11 | blaze-tools@1.0.7
12 | boilerplate-generator@1.0.7
13 | caching-compiler@1.0.3
14 | caching-html-compiler@1.0.5
15 | callback-hook@1.0.7
16 | check@1.1.3
17 | ddp@1.2.4
18 | ddp-client@1.2.4
19 | ddp-common@1.2.4
20 | ddp-server@1.2.5
21 | deps@1.0.11
22 | diff-sequence@1.0.4
23 | ecmascript@0.4.2
24 | ecmascript-runtime@0.2.9
25 | ejson@1.0.10
26 | fastclick@1.0.10
27 | fortawesome:fontawesome@4.5.0
28 | geojson-utils@1.0.7
29 | html-tools@1.0.8
30 | htmljs@1.0.8
31 | http@1.1.4
32 | id-map@1.0.6
33 | insecure@1.0.6
34 | jquery@1.11.7
35 | launch-screen@1.0.10
36 | livedata@1.0.17
37 | logging@1.0.11
38 | meteor@1.1.13
39 | meteor-platform@1.2.5
40 | minifier-css@1.1.10
41 | minifier-js@1.1.10
42 | minimongo@1.0.13
43 | mobile-status-bar@1.0.11
44 | modules@0.5.2
45 | modules-runtime@0.6.2
46 | mongo@1.1.6
47 | mongo-id@1.0.3
48 | npm-mongo@1.4.42
49 | observe-sequence@1.0.10
50 | ordered-dict@1.0.6
51 | promise@0.6.6
52 | random@1.0.8
53 | reactive-dict@1.1.6
54 | reactive-var@1.0.8
55 | reload@1.1.7
56 | retry@1.0.6
57 | routepolicy@1.0.9
58 | session@1.1.4
59 | spacebars@1.0.10
60 | spacebars-compiler@1.0.10
61 | standard-app-packages@1.0.8
62 | standard-minifier-css@1.0.5
63 | standard-minifier-js@1.0.5
64 | templating@1.1.8
65 | templating-tools@1.0.3
66 | tracker@1.0.12
67 | twbs:bootstrap-noglyph@3.3.5
68 | ui@1.0.10
69 | underscore@1.0.7
70 | url@1.0.8
71 | webapp@1.2.7
72 | webapp-hashing@1.0.8
73 |
--------------------------------------------------------------------------------
/examples/saved-state/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | anti:i18n@0.4.3
3 | aslagle:reactive-table@0.8.29
4 | autopublish@1.0.6
5 | autoupdate@1.2.7
6 | babel-compiler@6.6.1
7 | babel-runtime@0.1.7
8 | base64@1.0.7
9 | binary-heap@1.0.7
10 | blaze@2.1.6
11 | blaze-tools@1.0.7
12 | boilerplate-generator@1.0.7
13 | caching-compiler@1.0.3
14 | caching-html-compiler@1.0.5
15 | callback-hook@1.0.7
16 | check@1.1.3
17 | ddp@1.2.4
18 | ddp-client@1.2.4
19 | ddp-common@1.2.4
20 | ddp-server@1.2.5
21 | deps@1.0.11
22 | diff-sequence@1.0.4
23 | ecmascript@0.4.2
24 | ecmascript-runtime@0.2.9
25 | ejson@1.0.10
26 | fastclick@1.0.10
27 | fortawesome:fontawesome@4.5.0
28 | geojson-utils@1.0.7
29 | html-tools@1.0.8
30 | htmljs@1.0.8
31 | http@1.1.4
32 | id-map@1.0.6
33 | insecure@1.0.6
34 | jquery@1.11.7
35 | launch-screen@1.0.10
36 | livedata@1.0.17
37 | logging@1.0.11
38 | meteor@1.1.13
39 | meteor-platform@1.2.5
40 | minifier-css@1.1.10
41 | minifier-js@1.1.10
42 | minimongo@1.0.13
43 | mobile-status-bar@1.0.11
44 | modules@0.5.2
45 | modules-runtime@0.6.2
46 | mongo@1.1.6
47 | mongo-id@1.0.3
48 | npm-mongo@1.4.42
49 | observe-sequence@1.0.10
50 | ordered-dict@1.0.6
51 | promise@0.6.6
52 | random@1.0.8
53 | reactive-dict@1.1.6
54 | reactive-var@1.0.8
55 | reload@1.1.7
56 | retry@1.0.6
57 | routepolicy@1.0.9
58 | session@1.1.4
59 | spacebars@1.0.10
60 | spacebars-compiler@1.0.10
61 | standard-app-packages@1.0.8
62 | standard-minifier-css@1.0.5
63 | standard-minifier-js@1.0.5
64 | templating@1.1.8
65 | templating-tools@1.0.3
66 | tracker@1.0.12
67 | twbs:bootstrap-noglyph@3.3.5
68 | ui@1.0.10
69 | underscore@1.0.7
70 | url@1.0.8
71 | webapp@1.2.7
72 | webapp-hashing@1.0.8
73 |
--------------------------------------------------------------------------------
/examples/advanced-filters/leaderboard.html:
--------------------------------------------------------------------------------
1 |
2 | Leaderboard
3 |
4 |
5 |
6 |
7 | {{> leaderboard}}
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{> reactiveTableFilter id="filter1" label="Name" fields=nameFields }}
15 | {{> reactiveTableFilter id="filter2" label="Score" fields=scoreFields }}
16 | {{> greaterThanFilter }}
17 | {{> dateFilter }}
18 | {{> checkboxFilter }}
19 | {{> compoundFilter }}
20 | {{> autocompleteFilter }}
21 | {{> selectFilter }}
22 |
23 |
24 | {{> quoteFilter }}
25 | {{> reactiveTableFilter id="filter3" label="All" }}
26 |
27 |
28 | {{> clearFiltersButton }}
29 |
30 |
31 |
32 |
33 | {{> reactiveTable collection=players settings=tableSettings}}
34 |
35 |
36 |
37 | {{#each players}}
38 | {{> player}}
39 | {{/each}}
40 |
41 |
42 | {{#if selected_name}}
43 |
44 |
{{selected_name}}
45 |
46 |
47 | {{else}}
48 | Click a player to select
49 | {{/if}}
50 |
51 |
52 |
53 |
54 | {{name}}
55 | {{score}}
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/server/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | anti:i18n@0.4.3
3 | aslagle:reactive-table@0.8.29
4 | audit-argument-checks@1.0.6
5 | autoupdate@1.2.7
6 | babel-compiler@6.6.1
7 | babel-runtime@0.1.7
8 | base64@1.0.7
9 | binary-heap@1.0.7
10 | blaze@2.1.6
11 | blaze-html-templates@1.0.3
12 | blaze-tools@1.0.7
13 | boilerplate-generator@1.0.7
14 | caching-compiler@1.0.3
15 | caching-html-compiler@1.0.5
16 | callback-hook@1.0.7
17 | check@1.1.3
18 | ddp@1.2.4
19 | ddp-client@1.2.4
20 | ddp-common@1.2.4
21 | ddp-server@1.2.5
22 | deps@1.0.11
23 | diff-sequence@1.0.4
24 | ecmascript@0.4.2
25 | ecmascript-runtime@0.2.9
26 | ejson@1.0.10
27 | fastclick@1.0.10
28 | geojson-utils@1.0.7
29 | hot-code-push@1.0.3
30 | html-tools@1.0.8
31 | htmljs@1.0.8
32 | http@1.1.4
33 | id-map@1.0.6
34 | insecure@1.0.6
35 | jquery@1.11.7
36 | launch-screen@1.0.10
37 | livedata@1.0.17
38 | logging@1.0.11
39 | meteor@1.1.13
40 | meteor-base@1.0.3
41 | minifier-css@1.1.10
42 | minifier-js@1.1.10
43 | minimongo@1.0.13
44 | mobile-experience@1.0.3
45 | mobile-status-bar@1.0.11
46 | modules@0.5.2
47 | modules-runtime@0.6.2
48 | mongo@1.1.6
49 | mongo-id@1.0.3
50 | npm-mongo@1.4.42
51 | observe-sequence@1.0.10
52 | ordered-dict@1.0.6
53 | promise@0.6.6
54 | random@1.0.8
55 | reactive-dict@1.1.6
56 | reactive-var@1.0.8
57 | reload@1.1.7
58 | retry@1.0.6
59 | routepolicy@1.0.9
60 | session@1.1.4
61 | spacebars@1.0.10
62 | spacebars-compiler@1.0.10
63 | standard-minifier-css@1.0.5
64 | standard-minifier-js@1.0.5
65 | templating@1.1.8
66 | templating-tools@1.0.3
67 | tracker@1.0.12
68 | twbs:bootstrap@3.3.6
69 | ui@1.0.10
70 | underscore@1.0.7
71 | url@1.0.8
72 | webapp@1.2.7
73 | webapp-hashing@1.0.8
74 |
--------------------------------------------------------------------------------
/examples/table-features/table-features.html:
--------------------------------------------------------------------------------
1 |
2 | JavaScript table components comparison
3 |
4 |
5 |
6 | {{> forkMe}}
7 |
8 |
JavaScript table/grid components comparison
9 | {{> about}}
10 | {{> featureComparison}}
11 | {{> footer}}
12 |
13 |
14 |
15 |
16 |
17 | {{> reactiveTable collection=tables settings=tableSettings}}
18 |
19 |
20 |
21 |
22 | All tables support sorting by one column, hence the first column is "multi-column sorting". Click the "Columns" button to display more features in the comparison table.
23 |
24 |
25 |
26 | This list of JS table libraries and their features was built as a demo of
reactive-table , a table package designed for
Meteor . Please report any accuracies or omissions in
the GitHub issues , and feel free to report bugs or suggest features as well.
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/advanced-filters/.meteor/versions:
--------------------------------------------------------------------------------
1 | allow-deny@1.0.3
2 | anti:i18n@0.4.3
3 | aslagle:reactive-table@0.8.29
4 | autopublish@1.0.6
5 | autoupdate@1.2.7
6 | babel-compiler@6.6.1
7 | babel-runtime@0.1.7
8 | base64@1.0.7
9 | binary-heap@1.0.7
10 | blaze@2.1.6
11 | blaze-html-templates@1.0.3
12 | blaze-tools@1.0.7
13 | boilerplate-generator@1.0.7
14 | caching-compiler@1.0.3
15 | caching-html-compiler@1.0.5
16 | callback-hook@1.0.7
17 | check@1.1.3
18 | coffeescript@1.0.16
19 | dandv:caret-position@2.1.1
20 | ddp@1.2.4
21 | ddp-client@1.2.4
22 | ddp-common@1.2.4
23 | ddp-server@1.2.5
24 | deps@1.0.11
25 | diff-sequence@1.0.4
26 | ecmascript@0.4.2
27 | ecmascript-runtime@0.2.9
28 | ejson@1.0.10
29 | fastclick@1.0.10
30 | geojson-utils@1.0.7
31 | hot-code-push@1.0.3
32 | html-tools@1.0.8
33 | htmljs@1.0.8
34 | http@1.1.4
35 | id-map@1.0.6
36 | insecure@1.0.6
37 | jquery@1.11.7
38 | launch-screen@1.0.10
39 | linto:jquery-ui@1.11.2
40 | livedata@1.0.17
41 | logging@1.0.11
42 | meteor@1.1.13
43 | meteor-base@1.0.3
44 | minifier-css@1.1.10
45 | minifier-js@1.1.10
46 | minimongo@1.0.13
47 | mizzao:autocomplete@0.5.1
48 | mobile-experience@1.0.3
49 | mobile-status-bar@1.0.11
50 | modules@0.5.2
51 | modules-runtime@0.6.2
52 | momentjs:moment@2.12.0
53 | mongo@1.1.6
54 | mongo-id@1.0.3
55 | npm-mongo@1.4.42
56 | observe-sequence@1.0.10
57 | ordered-dict@1.0.6
58 | promise@0.6.6
59 | random@1.0.8
60 | reactive-dict@1.1.6
61 | reactive-var@1.0.8
62 | reload@1.1.7
63 | retry@1.0.6
64 | routepolicy@1.0.9
65 | session@1.1.4
66 | spacebars@1.0.10
67 | spacebars-compiler@1.0.10
68 | standard-minifier-css@1.0.5
69 | standard-minifier-js@1.0.5
70 | templating@1.1.8
71 | templating-tools@1.0.3
72 | tracker@1.0.12
73 | twbs:bootstrap@3.3.6
74 | ui@1.0.10
75 | underscore@1.0.7
76 | url@1.0.8
77 | webapp@1.2.7
78 | webapp-hashing@1.0.8
79 |
--------------------------------------------------------------------------------
/examples/leaderboard/leaderboard.js:
--------------------------------------------------------------------------------
1 | // Set up a collection to contain player information. On the server,
2 | // it is backed by a MongoDB collection named "players".
3 |
4 | Players = new Meteor.Collection("players");
5 |
6 | if (Meteor.isClient) {
7 | Template.leaderboard.helpers({
8 | players : function () {
9 | return Players.find({}, {sort: {score: -1, name: 1}});
10 | },
11 |
12 | tableSettings : function () {
13 | return {
14 | fields: [
15 | { key: 'name', label: 'Full Name' },
16 | { key: 'name', label: 'First Name', fn: function (name) { return name ? name.split(' ')[0] : ''; } },
17 | { key: 'score', label: 'Score' }
18 | ]
19 | };
20 | },
21 |
22 | selected_name : function () {
23 | var player = Players.findOne(Session.get("selected_player"));
24 | return player && player.name;
25 | }
26 | });
27 |
28 | Template.player.helpers({
29 | selected : function () {
30 | return Session.equals("selected_player", this._id) ? "selected" : '';
31 | }
32 | });
33 |
34 | Template.leaderboard.events({
35 | 'click input.inc': function () {
36 | Players.update(Session.get("selected_player"), {$inc: {score: 5}});
37 | }
38 | });
39 |
40 | Template.player.events({
41 | 'click': function () {
42 | Session.set("selected_player", this._id);
43 | }
44 | });
45 | }
46 |
47 | // On server startup, create some players if the database is empty.
48 | if (Meteor.isServer) {
49 | Meteor.startup(function () {
50 | if (Players.find().count() === 0) {
51 | var names = ["Ada Lovelace",
52 | "Grace Hopper",
53 | "Marie Curie",
54 | "Carl Friedrich Gauss",
55 | "Nikola Tesla",
56 | "Claude Shannon"];
57 | for (var i = 0; i < names.length; i++)
58 | Players.insert({name: names[i], score: Math.floor(Random.fraction()*10)*5});
59 | }
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | [
4 | "anti:i18n",
5 | "0.4.3"
6 | ],
7 | [
8 | "application-configuration",
9 | "1.0.3"
10 | ],
11 | [
12 | "base64",
13 | "1.0.1"
14 | ],
15 | [
16 | "binary-heap",
17 | "1.0.1"
18 | ],
19 | [
20 | "blaze",
21 | "2.0.3"
22 | ],
23 | [
24 | "callback-hook",
25 | "1.0.1"
26 | ],
27 | [
28 | "check",
29 | "1.0.2"
30 | ],
31 | [
32 | "ddp",
33 | "1.0.11"
34 | ],
35 | [
36 | "deps",
37 | "1.0.5"
38 | ],
39 | [
40 | "ejson",
41 | "1.0.4"
42 | ],
43 | [
44 | "follower-livedata",
45 | "1.0.2"
46 | ],
47 | [
48 | "geojson-utils",
49 | "1.0.1"
50 | ],
51 | [
52 | "htmljs",
53 | "1.0.2"
54 | ],
55 | [
56 | "id-map",
57 | "1.0.1"
58 | ],
59 | [
60 | "jquery",
61 | "1.0.1"
62 | ],
63 | [
64 | "json",
65 | "1.0.1"
66 | ],
67 | [
68 | "logging",
69 | "1.0.5"
70 | ],
71 | [
72 | "meteor",
73 | "1.1.3"
74 | ],
75 | [
76 | "minimongo",
77 | "1.0.5"
78 | ],
79 | [
80 | "mongo",
81 | "1.0.8"
82 | ],
83 | [
84 | "observe-sequence",
85 | "1.0.3"
86 | ],
87 | [
88 | "ordered-dict",
89 | "1.0.1"
90 | ],
91 | [
92 | "random",
93 | "1.0.1"
94 | ],
95 | [
96 | "reactive-var",
97 | "1.0.3"
98 | ],
99 | [
100 | "retry",
101 | "1.0.1"
102 | ],
103 | [
104 | "templating",
105 | "1.0.9"
106 | ],
107 | [
108 | "tracker",
109 | "1.0.3"
110 | ],
111 | [
112 | "ui",
113 | "1.0.4"
114 | ],
115 | [
116 | "underscore",
117 | "1.0.1"
118 | ]
119 | ],
120 | "pluginDependencies": [],
121 | "toolVersion": "meteor-tool@1.0.35",
122 | "format": "1.0"
123 | }
--------------------------------------------------------------------------------
/examples/saved-state/leaderboard.js:
--------------------------------------------------------------------------------
1 | // Set up a collection to contain player information. On the server,
2 | // it is backed by a MongoDB collection named "players".
3 |
4 | Players = new Meteor.Collection("players");
5 |
6 | if (Meteor.isClient) {
7 | Template.leaderboard.onCreated(function () {
8 | var currentPage = new ReactiveVar(Session.get('current-page') || 0);
9 | this.currentPage = currentPage;
10 | this.autorun(function () {
11 | Session.set('current-page', currentPage.get());
12 | });
13 | });
14 |
15 | Template.leaderboard.helpers({
16 | players : function () {
17 | return Players.find({}, {sort: {score: -1, name: 1}});
18 | },
19 |
20 | tableSettings : function () {
21 | return {
22 | currentPage: Template.instance().currentPage,
23 | fields: [
24 | { key: 'name', label: 'Full Name' },
25 | { key: 'name', label: 'First Name', fn: function (name) { return name ? name.split(' ')[0] : ''; } },
26 | { key: 'score', label: 'Score' }
27 | ]
28 | };
29 | },
30 |
31 | selected_name : function () {
32 | var player = Players.findOne(Session.get("selected_player"));
33 | return player && player.name;
34 | }
35 | });
36 |
37 | Template.player.helpers({
38 | selected : function () {
39 | return Session.equals("selected_player", this._id) ? "selected" : '';
40 | }
41 | });
42 |
43 | Template.leaderboard.events({
44 | 'click input.inc': function () {
45 | Players.update(Session.get("selected_player"), {$inc: {score: 5}});
46 | }
47 | });
48 |
49 | Template.player.events({
50 | 'click': function () {
51 | Session.set("selected_player", this._id);
52 | }
53 | });
54 | }
55 |
56 | // On server startup, create some players if the database is empty.
57 | if (Meteor.isServer) {
58 | Meteor.startup(function () {
59 | if (Players.find().count() === 0) {
60 | var names = ["Ada Lovelace",
61 | "Grace Hopper",
62 | "Marie Curie",
63 | "Carl Friedrich Gauss",
64 | "Nikola Tesla",
65 | "Claude Shannon"];
66 | for (var i = 0; i < names.length; i++)
67 | Players.insert({name: names[i], score: Math.floor(Random.fraction()*10)*5});
68 | }
69 | });
70 | }
71 |
--------------------------------------------------------------------------------
/test/test_compatibility.js:
--------------------------------------------------------------------------------
1 | testAsyncMulti('Compatibility - collection with dburles:collection-helpers', [function (test, expect) {
2 | var collectionWithHelpers = new Mongo.Collection();
3 | _.each(rows, function (row) {
4 | collectionWithHelpers.insert(row);
5 | });
6 | collectionWithHelpers.helpers({
7 | "first": function () {
8 | return this.name.slice(0, 1);
9 | }
10 | });
11 |
12 | var table = Blaze.renderWithData(
13 | Template.reactiveTable,
14 | {
15 | collection: collectionWithHelpers,
16 | fields: [
17 | {key: "first", label: "first", fn: function (val, obj) { return obj.first(); } }
18 | ]
19 | },
20 | document.body
21 | );
22 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
23 | test.equal($('.reactive-table tbody tr:first-child td').text(), "A", "table should display output of helper");
24 |
25 | collectionWithHelpers.update({name: 'Ada Lovelace'}, {'$set': {name: 'Bbbbb'}});
26 | Meteor.setTimeout(expect(function () {
27 | test.equal($('.reactive-table tbody tr:first-child td').text(), "B", "table should display output of helper");
28 | Blaze.remove(table);
29 | }), 0);
30 | }]);
31 |
32 | testAsyncMulti('Compatibility - cursor with dburles:collection-helpers', [function (test, expect) {
33 | var collectionWithHelpers = new Mongo.Collection();
34 | _.each(rows, function (row) {
35 | collectionWithHelpers.insert(row);
36 | });
37 | collectionWithHelpers.helpers({
38 | "first": function () {
39 | return this.name.slice(0, 1);
40 | }
41 | });
42 |
43 | var table = Blaze.renderWithData(
44 | Template.reactiveTable,
45 | {
46 | collection: collectionWithHelpers.find(),
47 | fields: [
48 | {key: "first", label: "first", fn: function (val, obj) { return obj.first(); } }
49 | ]
50 | },
51 | document.body
52 | );
53 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
54 | test.equal($('.reactive-table tbody tr:first-child td').text(), "A", "table should display output of helper");
55 |
56 | collectionWithHelpers.update({name: 'Ada Lovelace'}, {'$set': {name: 'Bbbbb'}});
57 | Meteor.setTimeout(expect(function () {
58 | test.equal($('.reactive-table tbody tr:first-child td').text(), "B", "table should display output of helper");
59 | Blaze.remove(table);
60 | }), 0);
61 | }]);
62 |
--------------------------------------------------------------------------------
/examples/advanced-filters/leaderboard.js:
--------------------------------------------------------------------------------
1 | // Set up a collection to contain player information. On the server,
2 | // it is backed by a MongoDB collection named "players".
3 |
4 | Players = new Meteor.Collection("players");
5 |
6 | if (Meteor.isClient) {
7 | Template.leaderboard.helpers({
8 | players : function () {
9 | return Players.find({}, {sort: {score: -1, name: 1}});
10 | },
11 |
12 | tableSettings : function () {
13 | return {
14 | fields: [
15 | { key: 'name', label: 'Full Name' },
16 | { key: 'name', label: 'First Name', fn: function (name) { return name ? name.split(' ')[0] : ''; } },
17 | { key: 'score', label: 'Score' },
18 | { key: 'date', label: 'Date', sortByValue: true, fn: function (date) { return moment(date).format("dddd, MMMM Do YYYY"); }},
19 | {
20 | key: 'checked',
21 | label: 'Checked',
22 | fn: function (checked) {
23 | var html = '✔ '
24 | if (checked === false) {
25 | html = '✘ ';
26 | }
27 | return new Spacebars.SafeString(html);
28 | }
29 | }
30 | ],
31 | filters: [
32 | 'filter1',
33 | 'filter2',
34 | 'filter3',
35 | 'date-filter',
36 | 'checkbox-filter',
37 | 'greater-than-filter',
38 | 'compound-check-filter',
39 | 'compound-score-filter',
40 | 'quote-filter',
41 | 'autocomplete-filter',
42 | 'select-filter'
43 | ]
44 | };
45 | },
46 |
47 | nameFields: function () {
48 | return ['name'];
49 | },
50 |
51 | scoreFields: function () {
52 | return ['score'];
53 | },
54 |
55 | selected_name : function () {
56 | var player = Players.findOne(Session.get("selected_player"));
57 | return player && player.name;
58 | }
59 | });
60 |
61 | Template.player.helpers({
62 | selected : function () {
63 | return Session.equals("selected_player", this._id) ? "selected" : '';
64 | }
65 | });
66 |
67 | Template.leaderboard.events({
68 | 'click input.inc': function () {
69 | Players.update(Session.get("selected_player"), {$inc: {score: 5}});
70 | }
71 | });
72 |
73 | Template.player.events({
74 | 'click': function () {
75 | Session.set("selected_player", this._id);
76 | }
77 | });
78 | }
79 |
80 | // On server startup, create some players if the database is empty.
81 | if (Meteor.isServer) {
82 | Meteor.startup(function () {
83 | if (Players.find().count() === 0) {
84 | var names = ["Ada Lovelace",
85 | "Grace Hopper",
86 | "Marie Curie",
87 | "Carl Friedrich Gauss",
88 | "Nikola Tesla",
89 | "Claude Shannon"];
90 | var dates = [
91 | "2014-12-10",
92 | "2011-12-09",
93 | "2016-11-07",
94 | "2012-04-30",
95 | "2014-07-10",
96 | "2016-04-30"
97 | ];
98 | for (var i = 0; i < names.length; i++)
99 | Players.insert({name: names[i], score: Math.floor(Random.fraction()*10)*5, date: dates[i], checked: (Random.fraction()>0.5)});
100 | }
101 | });
102 |
103 | ReactiveTable.publish('players', Players);
104 | }
105 |
--------------------------------------------------------------------------------
/test/test_collection_argument.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Collection Argument - array named argument', function (test) {
2 | testTable(
3 | {collection: rows},
4 | function () {
5 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
6 | }
7 | );
8 | });
9 |
10 | Tinytest.add('Collection Argument - collection named argument', function (test) {
11 | testTable(
12 | {collection: collection},
13 | function () {
14 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
15 | }
16 | );
17 | });
18 |
19 | Tinytest.add('Collection Argument - cursor named argument', function (test) {
20 | testTable(
21 | {collection: collection.find({score: 5})},
22 | function () {
23 | test.length($('.reactive-table tbody tr'), 3, "rendered table should have 3 rows");
24 | }
25 | );
26 | });
27 |
28 | Tinytest.add('Collection Argument - collection as only argument', function (test) {
29 | testTable(
30 | collection,
31 | function () {
32 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
33 | }
34 | );
35 | });
36 |
37 | Tinytest.add('Collection Argument - in settings argument', function (test) {
38 | testTable(
39 | {settings: {collection: rows}},
40 | function () {
41 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
42 | }
43 | );
44 | });
45 |
46 | testAsyncMulti('Collection Argument - server-side collection', [function (test, expect) {
47 | var table = Blaze.renderWithData(
48 | Template.reactiveTable,
49 | {collection: 'collection', fields: ['name', 'score']},
50 | document.body
51 | );
52 |
53 | var expectSixRows = expect(function () {
54 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
55 | Blaze.remove(table);
56 | });
57 |
58 | Meteor.setTimeout(expectSixRows, 500);
59 | }]);
60 |
61 | testAsyncMulti('Collection Argument - server-side collection with selector', [function (test, expect) {
62 | var table = Blaze.renderWithData(
63 | Template.reactiveTable,
64 | {collection: 'collection-and-selector', fields: ['name', 'score']},
65 | document.body
66 | );
67 |
68 | var expectThreeRows = expect(function () {
69 | test.length($('.reactive-table tbody tr'), 3, "rendered table should have 3 rows");
70 | Blaze.remove(table);
71 | });
72 |
73 | Meteor.setTimeout(expectThreeRows, 500);
74 | }]);
75 |
76 | testAsyncMulti('Collection Argument - server-side collection function', [function (test, expect) {
77 | var table = Blaze.renderWithData(
78 | Template.reactiveTable,
79 | {collection: 'collection-function', fields: ['name', 'score']},
80 | document.body
81 | );
82 |
83 | var expectSixRows = expect(function () {
84 | test.length($('.reactive-table tbody tr'), 6, "rendered table should have 6 rows");
85 | Blaze.remove(table);
86 | });
87 |
88 | Meteor.setTimeout(expectSixRows, 500);
89 | }]);
90 |
91 | testAsyncMulti('Collection Argument - server-side collection with selector function', [function (test, expect) {
92 | var table = Blaze.renderWithData(
93 | Template.reactiveTable,
94 | {collection: 'selector-function', fields: ['name', 'score']},
95 | document.body
96 | );
97 |
98 | var expectThreeRows = expect(function () {
99 | test.length($('.reactive-table tbody tr'), 3, "rendered table should have 3 rows");
100 | Blaze.remove(table);
101 | });
102 |
103 | Meteor.setTimeout(expectThreeRows, 500);
104 | }]);
105 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | summary: "A reactive table designed for Meteor",
3 | version: "0.8.45",
4 | name: "aslagle:reactive-table",
5 | git: "https://github.com/aslagle/reactive-table.git"
6 | });
7 |
8 | Package.on_use(function (api) {
9 | api.versionsFrom("METEOR@0.9.0");
10 | api.use('templating', 'client');
11 | api.use('jquery', 'client');
12 | api.use('underscore', ['server', 'client']);
13 | api.use('tracker@1.0.9', 'client');
14 | api.use('reactive-var@1.0.3', 'client');
15 | api.use("anti:i18n@0.4.3", 'client');
16 | api.use("mongo@1.0.8", ["server", "client"]);
17 | api.use("check", "server");
18 |
19 | api.use("fortawesome:fontawesome@4.2.0", 'client', {weak: true});
20 |
21 | api.add_files('lib/reactive_table.html', 'client');
22 | api.add_files('lib/filter.html', 'client');
23 | api.add_files('lib/reactive_table_i18n.js', 'client');
24 | api.add_files('lib/reactive_table.js', 'client');
25 | api.add_files('lib/reactive_table.css', 'client');
26 | api.add_files('lib/sort.js', 'client');
27 | api.add_files('lib/filter.js', ['client', 'server']);
28 | api.add_files('lib/server.js', 'server');
29 |
30 | api.export("ReactiveTable", ["client", "server"]);
31 | });
32 |
33 | Package.on_test(function (api) {
34 | api.use('templating', 'client');
35 | api.use('jquery', 'client');
36 | api.use('underscore', ['client', 'server']);
37 | api.use('tracker@1.0.9', 'client');
38 | api.use('reactive-var@1.0.3', 'client');
39 | api.use("anti:i18n@0.4.3", 'client');
40 | api.use("mongo", ["server", "client"]);
41 | api.use("check", "server");
42 | api.use("audit-argument-checks", "server");
43 |
44 | api.add_files('lib/reactive_table.html', 'client');
45 | api.add_files('lib/filter.html', 'client');
46 | api.add_files('lib/reactive_table_i18n.js', 'client');
47 | api.add_files('lib/reactive_table.js', 'client');
48 | api.add_files('lib/reactive_table.css', 'client');
49 | api.add_files('lib/sort.js', 'client');
50 | api.add_files('lib/filter.js', ['client', 'server']);
51 | api.add_files('lib/server.js', 'server');
52 |
53 | api.export("ReactiveTable", ["client", "server"]);
54 |
55 | api.use(['tinytest', 'test-helpers'], 'client');
56 | api.add_files('test/helpers.js', ['client', 'server']);
57 | api.add_files('test/test_collection_argument.js', 'client');
58 | api.add_files('test/test_no_data_template.html', 'client');
59 | api.add_files('test/test_settings.js', 'client');
60 | api.add_files('test/test_fields_tmpl.html', 'client');
61 | api.add_files('test/test_fields.js', 'client');
62 |
63 | api.use('accounts-password@1.0.6', ['client', 'server']);
64 | api.add_files('test/test_reactivity_server.js', 'server');
65 | api.add_files('test/test_reactivity.html', 'client');
66 | api.add_files('test/test_reactivity.js', 'client');
67 |
68 | api.add_files('test/test_sorting.js', 'client');
69 | api.add_files('test/test_filtering_server.js', 'server');
70 | api.add_files('test/test_filtering.js', 'client');
71 | api.add_files('test/test_pagination.js', 'client');
72 | api.add_files('test/test_i18n.js', 'client');
73 | api.add_files('test/test_events_tmpl.html', 'client');
74 | api.add_files('test/test_events.js', 'client');
75 | api.add_files('test/test_column_toggles.js', 'client');
76 | api.add_files('test/test_multiple_tables.js', 'client');
77 | api.add_files('test/test_template.html', 'client');
78 | api.add_files('test/test_template.js', 'client');
79 | api.add_files('test/test_custom_filters.js', 'client');
80 |
81 | api.use("dburles:collection-helpers@1.0.1", "client");
82 | api.add_files("test/test_compatibility.js", "client");
83 | });
84 |
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | ReactiveTable = {};
2 |
3 | ReactiveTable.publish = function (name, collectionOrFunction, selectorOrFunction, settings) {
4 | Meteor.publish("reactive-table-" + name, function (publicationId, filters, fields, options, rowsPerPage) {
5 | check(publicationId, String);
6 | check(filters, [Match.OneOf(String, Object, Mongo.ObjectID)]);
7 | check(fields, [[String]]);
8 | check(options, {skip: Match.Integer, limit: Match.Integer, sort: Object});
9 | check(rowsPerPage, Match.Integer);
10 |
11 | var collection;
12 | var selector;
13 |
14 | if (_.isFunction(collectionOrFunction)) {
15 | collection = collectionOrFunction.call(this);
16 | } else {
17 | collection = collectionOrFunction;
18 | }
19 |
20 | if (!(collection instanceof Mongo.Collection)) {
21 | console.log("ReactiveTable.publish: no collection to publish");
22 | return [];
23 | }
24 |
25 | if (_.isFunction(selectorOrFunction)) {
26 | selector = selectorOrFunction.call(this);
27 | } else {
28 | selector = selectorOrFunction;
29 | }
30 | var self = this;
31 | var filterQuery = _.extend(getFilterQuery(filters, fields, settings), selector);
32 | if (settings && settings.fields) {
33 | options.fields = settings.fields;
34 | }
35 | var pageCursor = collection.find(filterQuery, options);
36 | var fullCursor = collection.find(filterQuery);
37 | var count = fullCursor.count();
38 |
39 | var getRow = function (row, index) {
40 | return _.extend({
41 | "reactive-table-id": publicationId,
42 | "reactive-table-sort": index
43 | }, row);
44 | };
45 |
46 | var getRows = function () {
47 | return _.map(pageCursor.fetch(), getRow);
48 | };
49 | var rows = {};
50 | _.each(getRows(), function (row) {
51 | rows[row._id] = row;
52 | });
53 |
54 | var updateRows = function () {
55 | var newRows = getRows();
56 | _.each(newRows, function (row, index) {
57 | var oldRow = rows[row._id];
58 | if (oldRow) {
59 | if (!_.isEqual(oldRow, row)) {
60 | self.changed("reactive-table-rows-" + publicationId, row._id, row);
61 | rows[row._id] = row;
62 | }
63 | } else {
64 | self.added("reactive-table-rows-" + publicationId, row._id, row);
65 | rows[row._id] = row;
66 | }
67 | });
68 | };
69 |
70 | self.added("reactive-table-counts", publicationId, {count: count});
71 | _.each(rows, function (row) {
72 | self.added("reactive-table-rows-" + publicationId, row._id, row);
73 | });
74 |
75 | if (!(settings || {}).disableRowReactivity) {
76 | var initializing = true;
77 |
78 | var handle = pageCursor.observeChanges({
79 | added: function (id, fields) {
80 | if (!initializing) {
81 | updateRows();
82 | }
83 | },
84 |
85 | removed: function (id, fields) {
86 | self.removed("reactive-table-rows-" + publicationId, id);
87 | delete rows[id];
88 | updateRows();
89 | },
90 |
91 | changed: function (id, fields) {
92 | updateRows();
93 | }
94 |
95 | });
96 | }
97 |
98 | if (!(settings || {}).disablePageCountReactivity) {
99 | var countHandle = fullCursor.observeChanges({
100 | added: function (id, fields) {
101 | if (!initializing) {
102 | self.changed("reactive-table-counts", publicationId, {count: fullCursor.count()});
103 | }
104 | },
105 |
106 | removed: function (id, fields) {
107 | self.changed("reactive-table-counts", publicationId, {count: fullCursor.count()});
108 | }
109 | });
110 | }
111 | initializing = false;
112 |
113 | self.ready();
114 |
115 | self.onStop(function () {
116 | if (handle) handle.stop();
117 | if (countHandle) countHandle.stop();
118 | });
119 | });
120 | };
121 |
--------------------------------------------------------------------------------
/lib/sort.js:
--------------------------------------------------------------------------------
1 | normalizeSort = function (field, oldField) {
2 | // preserve user sort settings
3 | if (oldField && _.has(oldField, 'sortOrder')) {
4 | field.sortOrder = oldField.sortOrder;
5 | }
6 | if (oldField && _.has(oldField, 'sortDirection')) {
7 | field.sortDirection = oldField.sortDirection;
8 | }
9 |
10 | // backwards-compatibility
11 | if (!_.has(field, 'sortOrder') && _.has(field, 'sort')) {
12 | console.warn('reactiveTable warning: The "sort" option for fields is deprecated');
13 | field.sortOrder = 0;
14 | field.sortDirection = field.sort;
15 | }
16 |
17 |
18 | var sortOrder;
19 |
20 | if (!_.has(field, 'sortOrder')) {
21 | sortOrder = Infinity;
22 | field.sortOrder = new ReactiveVar();
23 | } else if (field.sortOrder instanceof ReactiveVar) {
24 | sortOrder = field.sortOrder.get()
25 | } else {
26 | sortOrder = field.sortOrder;
27 | field.sortOrder = new ReactiveVar();
28 | }
29 |
30 | if (!_.isNumber(sortOrder) || sortOrder < 0) {
31 | console.error('reactiveTable error - sortOrder must be a postive number: ' + sortOrder);
32 | sortOrder = Infinity;
33 | }
34 | field.sortOrder.set(sortOrder);
35 |
36 | var sortDirection;
37 |
38 | if (!_.has(field, 'sortDirection')) {
39 | sortDirection = 1;
40 | field.sortDirection = new ReactiveVar()
41 | } else if (field.sortDirection instanceof ReactiveVar) {
42 | sortDirection = field.sortDirection.get();
43 | } else {
44 | sortDirection = field.sortDirection;
45 | field.sortDirection = new ReactiveVar();
46 | }
47 |
48 | if (sortDirection === 'desc' || sortDirection === 'descending' || sortDirection === -1) {
49 | sortDirection = -1;
50 | } else if (sortDirection) {
51 | sortDirection = 1;
52 | }
53 | field.sortDirection.set(sortDirection);
54 | };
55 |
56 | getSortedFields = function (fields, multiColumnSort) {
57 | var filteredFields = _.filter(fields, function (field) {
58 | return field.sortOrder.get() < Infinity;
59 | });
60 | if (!filteredFields.length) {
61 | var firstSortableField = _.find(fields, function (field) {
62 | return _.isUndefined(field.sortable) || field.sortable !== false;
63 | });
64 | if (firstSortableField) {
65 | filteredFields = [firstSortableField];
66 | }
67 | }
68 | var sortedFields = _.sortBy(filteredFields, function (field) {
69 | return field.sortOrder.get();
70 | });
71 | return multiColumnSort ? sortedFields : sortedFields.slice(0, 1);
72 | }
73 |
74 | getSortQuery = function (fields, multiColumnSort) {
75 | var sortedFields = getSortedFields(fields, multiColumnSort);
76 | var sortQuery = {};
77 | _.each(sortedFields, function (field) {
78 | sortQuery[field.key] = field.sortDirection.get();
79 | });
80 | return sortQuery;
81 | };
82 |
83 | sortWithFunctions = function (rows, fields, multiColumnSort) {
84 | var sortedFields = getSortedFields(fields, multiColumnSort);
85 | var sortedRows = rows;
86 |
87 | _.each(sortedFields.reverse(), function (field) {
88 | if (field.sortFn) {
89 | sortedRows = _.sortBy(sortedRows, function (row) {
90 | return field.sortFn( get( row, field.key ), row );
91 | });
92 | } else if (field.sortByValue || !field.fn) {
93 | sortedRows = _.sortBy(sortedRows, field.key);
94 | } else {
95 | sortedRows = _.sortBy(sortedRows, function (row) {
96 | return field.fn( get( row, field.key ), row );
97 | });
98 | }
99 | if (field.sortDirection.get() === -1) {
100 | sortedRows.reverse();
101 | }
102 | });
103 | return sortedRows;
104 | };
105 |
106 | getPrimarySortField = function (fields, multiColumnSort) {
107 | return getSortedFields(fields, multiColumnSort)[0];
108 | };
109 |
110 | changePrimarySort = function(fieldId, fields, multiColumnSort) {
111 | var primarySortField = getPrimarySortField(fields, multiColumnSort);
112 | if (primarySortField && primarySortField.fieldId === fieldId) {
113 | var sortDirection = -1 * primarySortField.sortDirection.get();
114 | primarySortField.sortDirection.set(sortDirection);
115 | primarySortField.sortOrder.set(0);
116 | } else {
117 | _.each(fields, function (field) {
118 | if (field.fieldId === fieldId) {
119 | field.sortOrder.set(0);
120 | if (primarySortField) {
121 | field.sortDirection.set(primarySortField.sortDirection.get());
122 | }
123 | } else {
124 | var sortOrder = 1 + field.sortOrder.get();
125 | field.sortOrder.set(sortOrder);
126 | }
127 | });
128 | }
129 | };
130 |
--------------------------------------------------------------------------------
/lib/reactive_table.html:
--------------------------------------------------------------------------------
1 |
2 | {{#with context}}
3 | {{#if ready}}
4 |
5 |
6 | {{#if showFilter}}
7 |
8 | {{> reactiveTableFilter id=getFilterId useFontAwesome=useFontAwesome}}
9 |
10 | {{/if}}
11 | {{#if showColumnToggles}}
12 |
13 |
14 | {{i18n 'reactiveTable.columns'}}
15 |
16 |
32 |
33 | {{/if}}
34 |
35 |
36 | {{#unless noData}}
37 |
38 |
39 |
40 | {{#each fields}}
41 | {{#if isVisible}}
42 | {{#if isPrimarySortField}}
43 |
59 | {{else}}
60 | {{#if isSortable}}
61 |
62 | {{else}}
63 |
64 | {{/if}}
65 | {{/if}}
66 | {{/if}}
67 | {{/each}}
68 |
69 |
70 |
71 | {{#each sortedRows}}
72 |
73 | {{#each ../fields}}
74 | {{#if isVisible}}
75 | {{#if tmpl}}{{#with ..}}{{> ../tmpl}}{{/with}}{{else}}{{getField ..}}{{/if}}
76 | {{/if}}
77 | {{/each}}
78 |
79 | {{/each}}
80 |
81 |
82 | {{#if showNavigation}}
83 |
120 | {{/if}}
121 | {{else}}
122 | {{> noDataTmpl}}
123 | {{/unless}}
124 | {{/if}}
125 | {{/with}}
126 |
127 |
--------------------------------------------------------------------------------
/test/test_multiple_tables.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Multiple tables - rendering', function (test) {
2 | testTable(
3 | {collection: rows},
4 | function () {
5 | testTable(
6 | {collection: collection},
7 | function () {
8 | test.length($('.reactive-table'), 2, "two tables should be rendered");
9 | test.length($('.reactive-table:nth-of-type(1) tbody tr'), 6, "the first table should have 6 rows");
10 | test.length($('.reactive-table:nth-of-type(2) tbody tr'), 6, "the second table should have 6 rows");
11 | }
12 | );
13 | }
14 | );
15 | });
16 |
17 | Tinytest.add('Multiple tables - settings', function (test) {
18 | testTable(
19 | {collection: rows, settings: {showNavigation: true}},
20 | function () {
21 | testTable(
22 | {collection: collection, settings: {showNavigation: false}},
23 | function () {
24 | test.length($('.reactive-table'), 2, "two tables should be rendered");
25 | test.length($('.reactive-table-navigation'), 1, "only one table should have navigation");
26 | }
27 | );
28 | }
29 | );
30 |
31 | testTable(
32 | {collection: rows, settings: {rowsPerPage: 5}},
33 | function () {
34 | testTable(
35 | {collection: collection, settings: {rowsPerPage: 2}},
36 | function () {
37 | test.length($('.reactive-table'), 2, "two tables should be rendered");
38 | test.length($('.reactive-table:nth-of-type(1) tbody tr'), 5, "the first table should have 5 rows");
39 | test.length($('.reactive-table:nth-of-type(2) tbody tr'), 2, "the second table should have 2 rows");
40 | }
41 | );
42 | }
43 | );
44 | });
45 |
46 | testAsyncMulti('Multiple tables - sorting', [function (test, expect) {
47 | var table1 = Blaze.renderWithData(
48 | Template.reactiveTable,
49 | {collection: rows},
50 | document.body
51 | );
52 | var table2 = Blaze.renderWithData(
53 | Template.reactiveTable,
54 | {collection: rows},
55 | document.body
56 | );
57 | test.equal($('.reactive-table:nth-of-type(1) tbody tr:first-child td:first-child').text(), "Ada Lovelace", "first table sorted ascending");
58 | test.equal($('.reactive-table:nth-of-type(2) tbody tr:first-child td:first-child').text(), "Ada Lovelace", "second table sorted ascending");
59 |
60 | var expectSecondTableDescending = expect(function () {
61 | test.equal($('.reactive-table:nth-of-type(1) tbody tr:first-child td:first-child').text(), "Ada Lovelace", "first table sorted ascending");
62 | test.equal($('.reactive-table:nth-of-type(2) tbody tr:first-child td:first-child').text(), "Nikola Tesla", "second table sorted descending");
63 | Blaze.remove(table1);
64 | Blaze.remove(table2);
65 | });
66 |
67 | $('.reactive-table:nth-of-type(2) th:first-child').click();
68 | Meteor.setTimeout(expectSecondTableDescending, 0);
69 | }]);
70 |
71 | testAsyncMulti('Multiple tables - filtering', [function (test, expect) {
72 | var table1 = Blaze.renderWithData(
73 | Template.reactiveTable,
74 | {collection: rows},
75 | document.body
76 | );
77 | var table2 = Blaze.renderWithData(
78 | Template.reactiveTable,
79 | {collection: rows},
80 | document.body
81 | );
82 | test.length($('.reactive-table:nth-of-type(1) tbody tr'), 6, "first table should have 6 rows");
83 | test.length($('.reactive-table:nth-of-type(2) tbody tr'), 6, "second table should have 6 rows");
84 |
85 | var expectFirstTableFiltered = expect(function () {
86 | test.length($('.reactive-table:nth-of-type(1) tbody tr'), 1, "first table should have 1 row");
87 | test.length($('.reactive-table:nth-of-type(2) tbody tr'), 6, "second table should have 6 rows");
88 | Blaze.remove(table1);
89 | Blaze.remove(table2);
90 | });
91 |
92 | $($('.reactive-table-filter input')[0]).val("carl");
93 | $($('.reactive-table-filter input')[0]).trigger('input');
94 | Meteor.setTimeout(expectFirstTableFiltered, 1000);
95 | }]);
96 |
97 | testAsyncMulti('Multiple tables - pagination', [function (test, expect) {
98 | var table1 = Blaze.renderWithData(
99 | Template.reactiveTable,
100 | {collection: rows, settings: {rowsPerPage: 2}},
101 | document.body
102 | );
103 | var table2 = Blaze.renderWithData(
104 | Template.reactiveTable,
105 | {collection: rows, settings: {rowsPerPage: 2}},
106 | document.body
107 | );
108 | test.equal($($('.reactive-table-navigation .page-number input')[0]).val(), "1", "first table on page 1");
109 | test.equal($($('.reactive-table-navigation .page-number input')[1]).val(), "1", "second table on page 1");
110 |
111 | var expectSecondTablePageTwo = expect(function () {
112 | test.equal($($('.reactive-table-navigation .page-number input')[0]).val(), "1", "first table on page 1");
113 | test.equal($($('.reactive-table-navigation .page-number input')[1]).val(), "2", "second table on page 2");
114 | Blaze.remove(table1);
115 | Blaze.remove(table2);
116 | });
117 |
118 | $($('.reactive-table-navigation .next-page')[1]).click();
119 | Meteor.setTimeout(expectSecondTablePageTwo, 0);
120 | }]);
121 |
122 | testAsyncMulti('Multiple tables - server-side collection', [function (test, expect) {
123 | var table1 = Blaze.renderWithData(
124 | Template.reactiveTable,
125 | {collection: 'collection', fields: ['name', 'score']},
126 | document.body
127 | );
128 |
129 | var table2 = Blaze.renderWithData(
130 | Template.reactiveTable,
131 | {collection: 'collection', fields: ['name', 'score']},
132 | document.body
133 | );
134 |
135 | var expectSixRowsEach = expect(function () {
136 | test.length($('.reactive-table tbody tr'), 12, " 2 rendered tables should have 6 rows each");
137 | Blaze.remove(table1);
138 | Blaze.remove(table2);
139 | });
140 |
141 | Meteor.setTimeout(expectSixRowsEach, 500);
142 | }]);
--------------------------------------------------------------------------------
/lib/reactive_table_i18n.js:
--------------------------------------------------------------------------------
1 | i18n.map('en', {
2 | reactiveTable: {
3 | filter: 'Filter',
4 | columns: 'Columns',
5 | show: 'Show',
6 | rowsPerPage: 'rows per page',
7 | page: 'Page',
8 | of: 'of'
9 | }
10 | });
11 |
12 | i18n.map('fr', {
13 | reactiveTable: {
14 | filter: 'Filtre',
15 | columns: 'Colonnes',
16 | show: 'Voir',
17 | rowsPerPage: 'lignes par page',
18 | page: 'page',
19 | of: 'sur'
20 | }
21 | });
22 |
23 | i18n.map('ru', {
24 | reactiveTable: {
25 | filter: 'Фильтр',
26 | columns: 'Колонки',
27 | show: 'Показать',
28 | rowsPerPage: 'строк на странице',
29 | page: 'Страница',
30 | of: 'из'
31 | }
32 | });
33 |
34 | i18n.map('es', {
35 | reactiveTable: {
36 | filter: 'Filtro',
37 | columns: 'Columnas',
38 | show: 'Mostrar',
39 | rowsPerPage: 'filas por página',
40 | page: 'Página',
41 | of: 'de'
42 | }
43 | });
44 |
45 | i18n.map('nl', {
46 | reactiveTable: {
47 | filter: 'Filter',
48 | show: 'Toon',
49 | rowsPerPage: 'regels per pagina',
50 | page: 'Pagina',
51 | of: 'van'
52 | }
53 | });
54 |
55 | i18n.map('pt-br', {
56 | reactiveTable: {
57 | filter: 'Filtro',
58 | show: 'Mostrar',
59 | rowsPerPage: 'linhas por página',
60 | page: 'Página',
61 | of: 'de'
62 | }
63 | });
64 |
65 | i18n.map('pt', {
66 | reactiveTable: {
67 | filter: 'Filtro',
68 | show: 'Mostrar',
69 | rowsPerPage: 'linhas por página',
70 | page: 'Página',
71 | of: 'de'
72 | }
73 | });
74 |
75 | i18n.map('it', {
76 | reactiveTable: {
77 | filter: 'Filtra',
78 | show: 'Mostra',
79 | rowsPerPage: 'righe per pagina',
80 | page: 'Pagina',
81 | of: 'di'
82 | }
83 | });
84 |
85 | i18n.map('sv', {
86 | reactiveTable: {
87 | filter: 'Filter',
88 | show: 'Visa',
89 | rowsPerPage: 'rader per sida',
90 | page: 'Sida',
91 | of: 'av'
92 | }
93 | });
94 |
95 | i18n.map('ua', {
96 | reactiveTable: {
97 | filter: 'Фільтр',
98 | show: 'Показати',
99 | rowsPerPage: 'рядків на сторінці',
100 | page: 'Сторінка',
101 | of: 'з'
102 | }
103 | });
104 |
105 | i18n.map('tr', {
106 | reactiveTable: {
107 | filter: 'Süz',
108 | columns: 'Sütunlar',
109 | show: 'Sayfa başına',
110 | rowsPerPage: 'satır göster',
111 | page: 'Sayfa',
112 | of: ' / '
113 | }
114 | });
115 |
116 | i18n.map('sk', {
117 | reactiveTable: {
118 | filter: 'Filter',
119 | show: 'Zobraz',
120 | rowsPerPage: 'riadkov na stranu',
121 | page: 'Strana',
122 | of: 'z'
123 | }
124 | });
125 |
126 | i18n.map('cs', {
127 | reactiveTable: {
128 | filter: 'Filter',
129 | show: 'Zobraz',
130 | rowsPerPage: 'řádků na stranu',
131 | page: 'Strana',
132 | of: 'z'
133 | }
134 | });
135 |
136 | i18n.map('he', {
137 | reactiveTable: {
138 | filter: 'פילטר',
139 | show: 'הצג',
140 | rowsPerPage: 'שורות לעמוד',
141 | page: 'עמוד',
142 | of: 'מתוך'
143 | }
144 | });
145 |
146 | i18n.map('da', {
147 | reactiveTable: {
148 | filter: 'Filter',
149 | columns: 'Kolonner',
150 | show: 'Vis',
151 | rowsPerPage: 'rækker per side',
152 | page: 'Side',
153 | of: 'af'
154 | }
155 | });
156 |
157 | i18n.map('de', {
158 | reactiveTable: {
159 | filter: 'Filter',
160 | columns: 'Spalten',
161 | show: 'Zeige',
162 | rowsPerPage: 'Zeilen pro Seite',
163 | page: 'Seite',
164 | of: 'von'
165 | }
166 | });
167 |
168 | i18n.map('fi', {
169 | reactiveTable: {
170 | filter: 'Suodata',
171 | show: 'Näytä',
172 | rowsPerPage: 'riviä sivulla',
173 | page: 'Sivu',
174 | of: ' / '
175 | }
176 | });
177 |
178 | i18n.map('no', {
179 | reactiveTable: {
180 | filter: 'Filter',
181 | columns: 'Kolonner',
182 | show: 'Vis',
183 | rowsPerPage: 'rader per side',
184 | page: 'Side',
185 | of: 'av'
186 | }
187 | });
188 |
189 | i18n.map('pl', {
190 | reactiveTable: {
191 | filter: 'Szukaj',
192 | columns: 'Kolumny',
193 | show: 'Pokaż',
194 | rowsPerPage: 'pozycji na stronie',
195 | page: 'Strona',
196 | of: 'z'
197 | }
198 | });
199 |
200 | i18n.map('hr', {
201 | reactiveTable: {
202 | filter: 'Filter',
203 | columns: 'Stupci',
204 | show: 'Prikaži',
205 | rowsPerPage: 'redova po stranici',
206 | page: 'Stranica',
207 | of: 'od'
208 | }
209 | });
210 |
211 | i18n.map('is', {
212 | reactiveTable: {
213 | filter: 'Sía',
214 | columns: 'Dálkar',
215 | show: 'Sýna',
216 | rowsPerPage: 'raðir á síðu',
217 | page: 'Síða',
218 | of: 'af'
219 | }
220 | });
221 |
222 | i18n.map('zh', {
223 | reactiveTable: {
224 | filter: '过滤',
225 | columns: '列',
226 | show: '显示',
227 | rowsPerPage: '每页行数',
228 | page: '页数',
229 | of: '之'
230 | }
231 | });
232 |
233 | i18n.map('zh-tw', {
234 | reactiveTable: {
235 | filter: '過濾',
236 | columns: '列',
237 | show: '顯示',
238 | rowsPerPage: '每頁行數',
239 | page: '頁數',
240 | of: '之'
241 | }
242 | });
243 |
244 | i18n.map('fa', {
245 | reactiveTable: {
246 | filter: 'تزکیه',
247 | columns: 'ستون',
248 | show: 'ارائه',
249 | rowsPerPage: 'ردیف در هر صفحه',
250 | page: 'صفحه',
251 | of: 'از'
252 | }
253 | });
254 |
255 | i18n.map('gr', {
256 | reactiveTable: {
257 | filter: 'Φίλτρα',
258 | columns: 'Στήλες',
259 | show: 'Προβολή',
260 | rowsPerPage: 'γραμμές ανά σελίδα',
261 | page: 'Σελίδα',
262 | of: 'από'
263 | }
264 | });
265 |
266 | i18n.map('bg', {
267 | reactiveTable: {
268 | filter: 'Филтър',
269 | columns: 'Колони',
270 | show: 'Покажи',
271 | rowsPerPage: 'реда на страница',
272 | page: 'Страница',
273 | of: 'от'
274 | }
275 | });
276 |
277 | i18n.map('mk', {
278 | reactiveTable: {
279 | filter: 'Филтер',
280 | columns: 'Колони',
281 | show: 'Покажи',
282 | rowsPerPage: 'Редови на страница',
283 | page: 'Страница',
284 | of: 'од'
285 | }
286 | });
287 |
288 | i18n.map('ro', {
289 | reactiveTable: {
290 | filter: 'Filtru',
291 | columns: 'Coloane',
292 | show: 'Arată',
293 | rowsPerPage: 'rânduri per pagină',
294 | page: 'Pagină',
295 | of: 'din'
296 | }
297 | });
298 |
299 | i18n.map('ar', {
300 | reactiveTable: {
301 | filter: 'رشح',
302 | columns: 'الأعمدة',
303 | show: 'اظهر',
304 | rowsPerPage: 'الصفوف بالصفحة',
305 | page: 'الصفحة',
306 | of: 'من'
307 | }
308 | });
--------------------------------------------------------------------------------
/lib/filter.js:
--------------------------------------------------------------------------------
1 | var parseFilterString = function (filterString) {
2 | var startQuoteRegExp = /^[\'\"]/;
3 | var endQuoteRegExp = /[\'\"]$/;
4 | var filters = [];
5 | var words = filterString.split(' ');
6 |
7 | var inQuote = false;
8 | var quotedWord = '';
9 | _.each(words, function (word) {
10 | if (inQuote) {
11 | if (endQuoteRegExp.test(word)) {
12 | filters.push(quotedWord + ' ' + word.slice(0, word.length - 1));
13 | inQuote = false;
14 | quotedWord = '';
15 | } else {
16 | quotedWord = quotedWord + ' ' + word;
17 | }
18 | } else if (startQuoteRegExp.test(word)) {
19 | if (endQuoteRegExp.test(word)) {
20 | filters.push(word.slice(1, word.length - 1));
21 | } else {
22 | inQuote = true;
23 | quotedWord = word.slice(1, word.length);
24 | }
25 | } else {
26 | filters.push(word);
27 | }
28 | });
29 | return filters;
30 | };
31 |
32 | var escapeRegex = function(text) {
33 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
34 | };
35 |
36 | var getFieldMatches = function (field) {
37 | var fieldMatches = [];
38 | var keys = field.split('.');
39 | var previousKeys = '';
40 | _.each(keys, function (key) {
41 | fieldMatches.push(previousKeys + key);
42 | previousKeys += key + '.';
43 | });
44 | var extraMatch = field.replace(/\.\d+\./g, ".");
45 | if (fieldMatches.indexOf(extraMatch) === -1) fieldMatches.push(extraMatch);
46 | return fieldMatches;
47 | };
48 |
49 | getFilterQuery = function (filterInputs, filterFields, settings) {
50 | settings = settings || {};
51 | if (settings.enableRegex === undefined) {
52 | settings.enableRegex = false;
53 | }
54 | if (settings.filterOperator === undefined) {
55 | settings.filterOperator = "$and";
56 | }
57 | if (settings.fields) {
58 | _.each(filterInputs, function (filter, index) {
59 | if (_.any(settings.fields, function (include) { return include; })) {
60 | filterFields[index] = _.filter(filterFields[index], function (field) {
61 | return _.any(getFieldMatches(field), function (fieldMatch) {
62 | // ensure that the _id field is filtered on, even if it is not explicitly mentioned
63 | if (fieldMatch === "_id") return true;
64 | return settings.fields[fieldMatch];
65 | });
66 | });
67 | } else {
68 | filterFields[index] = _.filter(filterFields[index], function (field) {
69 | return _.all(getFieldMatches(field), function (fieldMatch) {
70 | return _.isUndefined(settings.fields[fieldMatch]) || settings.fields[fieldMatch];
71 | });
72 | });
73 | }
74 | });
75 | }
76 | var numberRegExp = /^\d+$/;
77 | var queryList = [];
78 | _.each(filterInputs, function (filter, index) {
79 | if (filter) {
80 | if (_.isObject(filter)) {
81 | var fieldQueries = _.map(filterFields[index], function (field) {
82 | var query = {};
83 | query[field] = filter;
84 | return query;
85 | });
86 | if (fieldQueries.length) {
87 | queryList.push({'$or': fieldQueries});
88 | }
89 | } else {
90 | var filters = parseFilterString(filter);
91 | _.each(filters, function (filterWord) {
92 | if (settings.enableRegex === false) {
93 | filterWord = escapeRegex(filterWord);
94 | }
95 | var filterQueryList = [];
96 | _.each(filterFields[index], function (field) {
97 | var filterRegExp = new RegExp(filterWord, 'i');
98 | var query = {};
99 | query[field] = filterRegExp;
100 | filterQueryList.push(query);
101 |
102 | if (numberRegExp.test(filterWord)) {
103 | var numberQuery = {};
104 | numberQuery[field] = parseInt(filterWord, 10);
105 | filterQueryList.push(numberQuery);
106 | }
107 |
108 | if (filterWord === "true") {
109 | var boolQuery = {};
110 | boolQuery[field] = true;
111 | filterQueryList.push(boolQuery);
112 | } else if (filterWord === "false") {
113 | var boolQuery = {};
114 | boolQuery[field] = false;
115 | filterQueryList.push(boolQuery);
116 | }
117 | });
118 |
119 | if (filterQueryList.length) {
120 | var filterQuery = {'$or': filterQueryList};
121 | queryList.push(filterQuery);
122 | }
123 | });
124 | }
125 | }
126 | });
127 |
128 | var query = {};
129 |
130 | if(queryList.length) {
131 | query[settings.filterOperator] = queryList;
132 | }
133 |
134 | return query;
135 | };
136 |
137 | if (Meteor.isClient) {
138 | ReactiveTable = ReactiveTable || {};
139 |
140 | var reactiveTableFilters = {};
141 | var callbacks = {};
142 |
143 | ReactiveTable.Filter = function (id, fields) {
144 | if (reactiveTableFilters[id]) {
145 | reactiveTableFilters[id].fields = fields;
146 | return reactiveTableFilters[id];
147 | }
148 |
149 | var filter = new ReactiveVar();
150 |
151 | this.fields = fields;
152 |
153 | this.get = function () {
154 | return filter.get() || '';
155 | };
156 |
157 | this.set = function (filterString) {
158 | filter.set(filterString);
159 | _.each(callbacks[id], function (callback) {
160 | callback();
161 | });
162 | };
163 |
164 | reactiveTableFilters[id] = this;
165 | };
166 |
167 | ReactiveTable.clearFilters = function (filterIds) {
168 | _.each(filterIds, function (filterId) {
169 | if (reactiveTableFilters[filterId]) {
170 | reactiveTableFilters[filterId].set('');
171 | }
172 | });
173 | };
174 |
175 | dependOnFilters = function (filterIds, callback) {
176 | _.each(filterIds, function (filterId) {
177 | if (_.isUndefined(callbacks[filterId])) {
178 | callbacks[filterId] = [];
179 | }
180 | callbacks[filterId].push(callback);
181 | });
182 | };
183 |
184 | getFilterStrings = function (filterIds) {
185 | return _.map(filterIds, function (filterId) {
186 | if (_.isUndefined(reactiveTableFilters[filterId])) {
187 | reactiveTableFilters[filterId] = new ReactiveTable.Filter(filterId);
188 | }
189 | return reactiveTableFilters[filterId].get();
190 | });
191 | };
192 |
193 | getFilterFields = function (filterIds, allFields) {
194 | return _.map(filterIds, function (filterId) {
195 | if (_.isUndefined(reactiveTableFilters[filterId])) {
196 | return _.map(allFields, function (field) { return field.key; });
197 | } else if (_.isEmpty(reactiveTableFilters[filterId].fields)) {
198 | return _.map(allFields, function (field) { return field.key; });
199 | } else {
200 | return reactiveTableFilters[filterId].fields;
201 | }
202 | });
203 | };
204 |
205 | Template.reactiveTableFilter.helpers({
206 | 'class': function () {
207 | return this.class || 'input-group';
208 | },
209 |
210 | 'filter': function () {
211 | if (_.isUndefined(reactiveTableFilters[this.id])) {
212 | new ReactiveTable.Filter(this.id, this.fields);
213 | } else if (_.isUndefined(reactiveTableFilters[this.id].fields)) {
214 | reactiveTableFilters[this.id].fields = this.fields;
215 | }
216 | return reactiveTableFilters[this.id].get();
217 | }
218 | });
219 |
220 | var updateFilter = _.debounce(function (template, filterText) {
221 | reactiveTableFilters[template.data.id].set(filterText);
222 | }, 200);
223 |
224 | Template.reactiveTableFilter.events({
225 | 'keyup .reactive-table-input, input .reactive-table-input': function (event) {
226 | var template = Template.instance();
227 | var filterText = $(event.target).val();
228 | updateFilter(template, filterText);
229 | },
230 | });
231 | }
232 |
--------------------------------------------------------------------------------
/examples/table-features/table-features.js:
--------------------------------------------------------------------------------
1 | var Tables = new Meteor.Collection('features');
2 |
3 | if (Meteor.isClient) {
4 |
5 | var checkOrX = function (value) {
6 | var html;
7 | // first, normalize the value to a canonical interpretation
8 | if (typeof value === 'boolean')
9 | value = {
10 | support: value
11 | };
12 |
13 | if (value === null || value === undefined) {
14 | html = '? ';
15 | } else {
16 | if (value.support === true)
17 | html = '✔ '
18 | else if (value.support === false)
19 | html = '✘ ';
20 | else
21 | html = '' + value.support + ' ';
22 | if (value.link)
23 | html += ' (more )';
24 | }
25 | return new Spacebars.SafeString(html);
26 | };
27 |
28 | Template.featureComparison.helpers({
29 | tables : function () {
30 | return Tables;
31 | },
32 |
33 | tableSettings : function () {
34 | return {
35 | rowsPerPage: 5,
36 | showNavigation: 'auto',
37 | showColumnToggles: true,
38 | fields: [
39 | {
40 | key: 'name',
41 | label: 'Library',
42 | fn: function (name, object) {
43 | var html = '' + name + ' ';
44 | return new Spacebars.SafeString(html);
45 | }
46 | },
47 | { key: 'multisort', label: 'Multi-column sorting', fn: checkOrX },
48 | { key: 'pages', label: 'Pagination', fn: checkOrX },
49 | { key: 'filter', label: 'Filtering/Search', fn: checkOrX },
50 | { key: 'resize', label: 'Resizable Columns', fn: checkOrX },
51 | { key: 'edit', label: 'Inline Editing', fn: checkOrX },
52 | { key: 'responsive', label: 'Mobile/Responsive', fn: checkOrX },
53 | { key: 'i18n', label: 'Internationalization', fn: checkOrX, hidden: true },
54 | { key: 'keyboard', label: 'Keyboard navigation', fn: checkOrX, hidden: true },
55 | { key: 'plugins', label: 'Plugins', fn: checkOrX, hidden: true },
56 | { key: 'meteor', label: 'Meteor Integration', fn: checkOrX, hidden: true },
57 | { key: 'lastUpdate', label: 'Last update', fn: checkOrX }
58 | ]
59 | };
60 | }
61 | });
62 | }
63 |
64 | if (Meteor.isServer) {
65 | Meteor.startup(function () {
66 | Tables.remove({'name': {'$exists': true}});
67 |
68 | Tables.insert({
69 | name: 'reactive-table',
70 | url: 'https://github.com/ecohealthalliance/reactive-table',
71 | multisort: false,
72 | pages: true,
73 | filter: true,
74 | resize: false,
75 | edit: false,
76 | responsive: false,
77 | i18n: true,
78 | keyboard: false,
79 | plugins: undefined,
80 | meteor: true
81 | });
82 |
83 | Tables.insert({
84 | name: 'DataTables',
85 | url: 'https://datatables.net/',
86 | multisort: {support: true, link: 'http://www.datatables.net/examples/basic_init/multi_col_sort.html'},
87 | pages: true,
88 | filter: true,
89 | resize: true,
90 | edit: true,
91 | responsive: {support: true, link: 'https://datatables.net/release-datatables/examples/basic_init/flexible_width.html'},
92 | i18n: true,
93 | keyboard: {support: true, link: 'http://datatables.net/release-datatables/extras/KeyTable/'},
94 | plugins: {support: 'plugins & extensions', link: 'https://datatables.net/plug-ins/'},
95 | meteor: {support: 'partial', 'link': 'https://github.com/ecohealthalliance/reactive-table/issues/10#issuecomment-35941155'},
96 | lastUpdate: {support: 'probably today', link: 'https://github.com/DataTables/DataTables'}
97 | });
98 |
99 | Tables.insert({
100 | name: 'SlickGrid',
101 | url: 'https://github.com/mleibman/SlickGrid',
102 | multisort: {support: true, link: 'http://mleibman.github.io/SlickGrid/examples/example-multi-column-sort.html'},
103 | filter: true,
104 | resize: true,
105 | edit: true,
106 | responsive: undefined,
107 | i18n: undefined,
108 | keyboard: undefined,
109 | plugins: {support: true, link: 'https://github.com/mleibman/SlickGrid/wiki/Examples'},
110 | meteor: {support: 'partial', link: 'https://github.com/ecohealthalliance/reactive-table/issues/10#issuecomment-35941155'},
111 | lastUpdate: {support: 'stalled', link: 'https://github.com/mleibman/SlickGrid'}
112 | });
113 |
114 | Tables.insert({
115 | name: 'Dynatable',
116 | url: 'http://www.dynatable.com/',
117 | multisort: {support: true, link: 'http://www.dynatable.com/#sorting'},
118 | pages: true,
119 | filter: true,
120 | resize: false,
121 | edit: false,
122 | responsive: true,
123 | i18n: undefined,
124 | keyboard: false,
125 | plugins: undefined,
126 | meteor: {support: false, 'link': 'https://github.com/alfajango/jquery-dynatable/issues/59'}
127 | });
128 |
129 | Tables.insert({
130 | name: 'tablesorter',
131 | url: 'https://github.com/Mottie/tablesorter',
132 | multisort: {support: true, link: 'https://github.com/Mottie/tablesorter#features'},
133 | pages: {support: true, link: 'http://mottie.github.io/tablesorter/docs/example-pager.html'},
134 | filter: true,
135 | edit: true,
136 | responsive: undefined,
137 | i18n: undefined,
138 | keyboard: undefined,
139 | plugins: undefined,
140 | meteor: undefined
141 | });
142 |
143 | Tables.insert({
144 | name: 'Handsontable',
145 | url: 'http://handsontable.com/',
146 | multisort: {support: false, link: 'https://github.com/handsontable/jquery-handsontable/wiki/Understanding-column-sorting-plugin'},
147 | pages: {support: true, link: 'http://handsontable.com/demo/pagination.html'},
148 | filter: {support: true, link: 'http://handsontable.com/demo/search.html'},
149 | resize: {support: true, link: 'http://handsontable.com/demo/column_resize.html'},
150 | edit: true,
151 | responsive: undefined,
152 | i18n: undefined,
153 | keyboard: true,
154 | plugins: undefined,
155 | meteor: {support: true, link: 'https://github.com/olragon/meteor-handsontable/'}
156 | });
157 |
158 | Tables.insert({
159 | name: 'jqWidgets jqxGrid',
160 | url: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm',
161 | multisort: {support: false, link: 'http://www.jqwidgets.com/community/topic/sorting-by-multiple-columns/'},
162 | pages: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/paging.htm'},
163 | filter: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/filtering.htm'},
164 | resize: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/columnsresizing.htm'},
165 | edit: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/spreadsheet.htm'},
166 | responsive: false,
167 | i18n: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/localization.htm'},
168 | keyboard: {support: true, link: 'http://www.jqwidgets.com/jquery-widgets-demo/demos/jqxgrid/index.htm#demos/jqxgrid/keyboardsupport.htm'},
169 | plugins: undefined,
170 | meteor: undefined
171 | });
172 |
173 | Tables.insert({
174 | name: 'Backgrid.js - reactive, Backbone UI',
175 | url: 'http://backgridjs.com/',
176 | multisort: {support: false, link: 'https://github.com/wyuenho/backgrid/issues/453'},
177 | pages: {support: true, link: 'https://github.com/wyuenho/backgrid-paginator'},
178 | filter: {support: true, link: 'https://github.com/wyuenho/backgrid-filter'},
179 | resize: false,
180 | edit: {support: true, link: 'http://backgridjs.com/index.html#complete-example'},
181 | responsive: undefined,
182 | i18n: undefined,
183 | keyboard: false,
184 | plugins: undefined,
185 | meteor: {support: false, link: 'https://atmospherejs.com/?q=backgrid'}
186 | });
187 |
188 | Tables.insert({
189 | name: 'Tabular',
190 | url: 'https://github.com/aldeed/meteor-tabular',
191 | multisort: {support: false, link: 'https://github.com/aldeed/meteor-tabular/issues/144'},
192 | pages: true,
193 | filter: true,
194 | resize: {support: false, link: 'https://github.com/aldeed/meteor-tabular/issues/146'},
195 | edit: {support: false, link: 'https://github.com/aldeed/meteor-tabular/issues/145'},
196 | responsive: true,
197 | i18n: {support: undefined, link: 'https://github.com/aldeed/meteor-tabular/issues/147'},
198 | keyboard: {support: undefined, link: 'https://github.com/aldeed/meteor-tabular/issues/148'},
199 | plugins: {support: true, link: 'http://datatables.net/extensions/index'},
200 | meteor: true
201 | });
202 |
203 | });
204 | }
205 |
--------------------------------------------------------------------------------
/test/test_pagination.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Pagination - initial page', function (test) {
2 | testTable(
3 | {collection: rows, settings: {rowsPerPage: 2}},
4 | function () {
5 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
6 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on the first page");
7 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
8 | test.length($('.reactive-table-navigation .page-number label .page-number-count').text().match(/3/), 1, "displayed page count should be 3");
9 | }
10 | );
11 | });
12 |
13 | testAsyncMulti('Pagination - previous/next controls', [function (test, expect) {
14 | var table = Blaze.renderWithData(
15 | Template.reactiveTable,
16 | {collection: rows, settings: {rowsPerPage: 2}},
17 | document.body
18 | );
19 |
20 | test.length($('.reactive-table-navigation .previous-page'), 0, "first page shouldn't have previous button");
21 |
22 | var expectBackToFirstPage = expect(function () {
23 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
24 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
25 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
26 |
27 | Blaze.remove(table);
28 | });
29 |
30 | var expectBackToSecondPage = expect(function () {
31 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
32 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
33 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
34 |
35 | $('.reactive-table-navigation .previous-page').click();
36 | Meteor.setTimeout(expectBackToFirstPage, 0);
37 | });
38 |
39 | var expectLastPage = expect(function () {
40 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Marie Curie", "should be on last page");
41 | test.length($('.reactive-table tbody tr'), 2, "last page should have two rows");
42 | test.equal($('.reactive-table-navigation .page-number input').val(), "3", "displayed page number should be 3");
43 | test.length($('.reactive-table-navigation .next-page'), 0, "last page shouldn't have next button");
44 |
45 | $('.reactive-table-navigation .previous-page').click();
46 | Meteor.setTimeout(expectBackToSecondPage, 0);
47 | });
48 |
49 | var expectSecondPage = expect(function () {
50 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
51 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
52 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
53 |
54 | $('.reactive-table-navigation .next-page').click();
55 | Meteor.setTimeout(expectLastPage, 0);
56 | });
57 |
58 | $('.reactive-table-navigation .next-page').click();
59 | Meteor.setTimeout(expectSecondPage, 0);
60 | }]);
61 |
62 |
63 | Tinytest.add('Pagination - rows per page', function (test) {
64 | testTable(
65 | {collection: rows, settings: {showRowCount: true}},
66 | function() {
67 | test.equal($('.reactive-table-navigation .rows-per-page .rows-per-page-count').text(), "6");
68 | })
69 | });
70 |
71 | testAsyncMulti('Pagination - rows per page while filtering', [function (test, expect) {
72 | var table = Blaze.renderWithData(
73 | Template.reactiveTable,
74 | {collection: rows, settings: {showRowCount: true}},
75 | document.body
76 | );
77 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
78 |
79 | var expectTwoRows = expect(function () {
80 | test.length($('.reactive-table tbody tr'), 2, "filtered to two rows");
81 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "filtered first row");
82 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "filtered second row");
83 | test.equal($('.reactive-table-navigation .rows-per-page .rows-per-page-count').text(), "2", "Number of rows should be 2");
84 | Blaze.remove(table);
85 | });
86 |
87 | $('.reactive-table-filter input').val('g');
88 | $('.reactive-table-filter input').trigger('input');
89 | Meteor.setTimeout(expectTwoRows, 1000);
90 | }]);
91 |
92 | testAsyncMulti('Pagination - changing rows per page sets current page to last when past it', [function (test, expect) {
93 | var table = Blaze.renderWithData(
94 | Template.reactiveTable,
95 | {collection: rows, settings: {rowsPerPage: 2}},
96 | document.body
97 | );
98 |
99 | var expectFirstPage = expect(function () {
100 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
101 | test.length($('.reactive-table tbody tr'), 6, "first page should have six rows");
102 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
103 |
104 | Blaze.remove(table);
105 | });
106 |
107 | var expectSecondPage = expect(function () {
108 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
109 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
110 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
111 |
112 | $('.reactive-table-navigation .rows-per-page input').val("6");
113 | $('.reactive-table-navigation .rows-per-page input').trigger("change");
114 | Meteor.setTimeout(expectFirstPage, 0);
115 | });
116 |
117 | $('.reactive-table-navigation .next-page').click();
118 | Meteor.setTimeout(expectSecondPage, 0);
119 | }]);
120 |
121 | testAsyncMulti('Pagination - page input', [function (test, expect) {
122 | var table = Blaze.renderWithData(
123 | Template.reactiveTable,
124 | {collection: rows, settings: {rowsPerPage: 2}},
125 | document.body
126 | );
127 |
128 | var expectFirstPage = expect(function () {
129 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
130 | test.length($('.reactive-table tbody tr'), 2, "first page should have two rows");
131 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
132 |
133 | Blaze.remove(table);
134 | });
135 |
136 | var expectSecondPage = expect(function () {
137 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
138 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
139 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
140 |
141 | $('.reactive-table-navigation .page-number input').val("1");
142 | $('.reactive-table-navigation .page-number input').trigger("change");
143 | Meteor.setTimeout(expectFirstPage, 0);
144 | });
145 |
146 | var expectLastPage = expect(function () {
147 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Marie Curie", "should be on last page");
148 | test.length($('.reactive-table tbody tr'), 2, "last page should have two rows");
149 | test.equal($('.reactive-table-navigation .page-number input').val(), "3", "displayed page number should be 3");
150 |
151 | $('.reactive-table-navigation .page-number input').val('2');
152 | $('.reactive-table-navigation .page-number input').trigger("change");
153 | Meteor.setTimeout(expectSecondPage, 0);
154 | });
155 |
156 | $('.reactive-table-navigation .page-number input').val("3");
157 | $('.reactive-table-navigation .page-number input').trigger("change");
158 | Meteor.setTimeout(expectLastPage, 0);
159 | }]);
160 |
161 | testAsyncMulti('Pagination - server-side', [function (test, expect) {
162 | var table = Blaze.renderWithData(
163 | Template.reactiveTable,
164 | {collection: 'collection', fields: ['name', 'score'], rowsPerPage: 2},
165 | document.body
166 | );
167 |
168 | var expectBackToFirstPage = expect(function () {
169 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
170 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
171 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
172 |
173 | Blaze.remove(table);
174 | });
175 |
176 | var expectBackToSecondPage = expect(function () {
177 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
178 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
179 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
180 |
181 | $('.reactive-table-navigation .previous-page').click();
182 | Meteor.setTimeout(expectBackToFirstPage, 500);
183 | });
184 |
185 | var expectLastPage = expect(function () {
186 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Marie Curie", "should be on last page");
187 | test.length($('.reactive-table tbody tr'), 2, "last page should have two rows");
188 | test.equal($('.reactive-table-navigation .page-number input').val(), "3", "displayed page number should be 3");
189 | test.length($('.reactive-table-navigation .next-page'), 0, "last page shouldn't have next button");
190 |
191 | $('.reactive-table-navigation .previous-page').click();
192 | Meteor.setTimeout(expectBackToSecondPage, 500);
193 | });
194 |
195 | var expectSecondPage = expect(function () {
196 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
197 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
198 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
199 |
200 | $('.reactive-table-navigation .next-page').click();
201 | Meteor.setTimeout(expectLastPage, 500);
202 | });
203 |
204 | var expectFirstPage = expect(function () {
205 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
206 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on the first page");
207 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
208 | test.length($('.reactive-table-navigation .page-number label .page-number-count').text().match(/3/), 1, "displayed page count should be 3");
209 | test.length($('.reactive-table-navigation .page-number label .page-number-count').text().match(/3/), 1, "displayed page count should be 3");
210 | test.length($('.reactive-table-navigation .previous-page'), 0, "first page shouldn't have previous button");
211 |
212 | $('.reactive-table-navigation .next-page').click();
213 | Meteor.setTimeout(expectSecondPage, 500);
214 | });
215 |
216 | Meteor.setTimeout(expectFirstPage, 500);
217 | }]);
218 |
--------------------------------------------------------------------------------
/test/test_column_toggles.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Column Toggles - setting', function (test) {
2 | testTable(
3 | {
4 | collection: rows,
5 | settings: {
6 | showColumnToggles: false
7 | }
8 | },
9 | function () {
10 | test.length($('.reactive-table-columns-dropdown'), 0, "column toggle button should be hidden");
11 | }
12 | );
13 |
14 | testTable(
15 | {
16 | collection: rows,
17 | showColumnToggles: true
18 | },
19 | function () {
20 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
21 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
22 | test.length($('.reactive-table-columns-dropdown input:checked'), 2, "two checked boxes should be available");
23 | }
24 | );
25 | });
26 |
27 | Tinytest.add('Column Toggles - hidden columns', function (test) {
28 | testTable(
29 | {
30 | collection: rows,
31 | settings: {
32 | showColumnToggles: true,
33 | fields: [
34 | {key: 'name', label: 'Visible'},
35 | {key: 'score', label: 'Hidden', hidden: true}
36 | ]
37 | }
38 | },
39 | function () {
40 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
41 | test.length($('.reactive-table th'), 1, "one column should be displayed");
42 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
43 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "only one box should be checked");
44 | }
45 | );
46 | });
47 |
48 | Tinytest.add('Column Toggles - hidden columns with non-unique keys', function (test) {
49 | /**
50 | * Tests legacy behaviour of duplicate key values without specifying a fieldId
51 | */
52 | testTable(
53 | {
54 | collection: rows,
55 | settings: {
56 | showColumnToggles: true,
57 | fields: [
58 | {key: 'name', label: 'Visible'},
59 | {key: 'name', label: 'Hidden', hidden: true}
60 | ]
61 | }
62 | },
63 | function () {
64 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
65 | test.length($('.reactive-table th'), 1, "one columns should be displayed");
66 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
67 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "one box should be checked");
68 | }
69 | );
70 |
71 | testTable(
72 | {
73 | collection: rows,
74 | settings: {
75 | showColumnToggles: true,
76 | fields: [
77 | {key: 'name', label: 'Hidden', hidden: true},
78 | {key: 'name', label: 'Visible'}
79 | ]
80 | }
81 | },
82 | function () {
83 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
84 | test.length($('.reactive-table th'), 1, "one column should be displayed");
85 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
86 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "one box should be checked");
87 | }
88 | );
89 |
90 | });
91 |
92 | Tinytest.add('Column Toggles - hidden columns with fieldIds', function (test) {
93 | testTable(
94 | {
95 | collection: rows,
96 | settings: {
97 | showColumnToggles: true,
98 | fields: [
99 | {fieldId: 'one', key: 'name', label: 'Visible'},
100 | {fieldId: 'two', key: 'score', label: 'Hidden', hidden: true}
101 | ]
102 | }
103 | },
104 | function () {
105 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
106 | test.length($('.reactive-table th'), 1, "one column should be displayed");
107 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
108 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "only one box should be checked");
109 | }
110 | );
111 | });
112 |
113 | testAsyncMulti('Column Toggles - toggling', [function (test, expect) {
114 | var table = Blaze.renderWithData(
115 | Template.reactiveTable,
116 | {
117 | collection: rows,
118 | showColumnToggles: true,
119 | fields: [
120 | {key: 'name', label: 'Visible'},
121 | {key: 'score', label: 'Hidden', hidden: true}
122 | ]
123 | },
124 | document.body
125 | );
126 | test.length($('.reactive-table th'), 1, "one column should be displayed");
127 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
128 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "only one box should be checked");
129 |
130 | var expectSecondColumnOnly = expect(function () {
131 | test.length($('.reactive-table th'), 1, "one column should be displayed");
132 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
133 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "only one box should be checked");
134 | test.length($('.reactive-table th:first-child').text().trim().match(/Hidden/), 1, "initially hidden column should now be displayed");
135 |
136 | Blaze.remove(table);
137 | });
138 |
139 | var expectBothColumns = expect(function () {
140 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
141 | test.length($('.reactive-table-columns-dropdown input'), 2, "both boxes should be checked");
142 | test.length($('.reactive-table th:first-child').text().trim().match(/Visible/), 1, "visible column should still be displayed");
143 | test.length($('.reactive-table th:nth-child(2)').text().trim().match(/Hidden/), 1, "initially hidden column should now be displayed");
144 |
145 | $('.reactive-table-columns-dropdown input[data-fieldid="0"]').click();
146 | Meteor.setTimeout(expectSecondColumnOnly, 0);
147 | });
148 |
149 | $('.reactive-table-columns-dropdown input[data-fieldid="1"]').click();
150 | Meteor.setTimeout(expectBothColumns, 0);
151 | }]);
152 |
153 | testAsyncMulti('Column Toggles - toggling after adding new columns with fieldId', [function (test, expect) {
154 | var insert2ndField = new ReactiveVar(false);
155 | Template.testReactivity.helpers({
156 | collection: function () {
157 | return collection;
158 | },
159 | settings: function () {
160 | if (insert2ndField.get()) {
161 | return {
162 | showColumnToggles: true,
163 | fields: [
164 | {fieldId: 'one', key: 'name', label: 'Name'},
165 | {fieldId: 'two', key: 'score', label: 'Score'},
166 | {fieldId: 'three', key: 'average', label: 'Average'}
167 | ]
168 | }
169 | } else {
170 | return {
171 | showColumnToggles: true,
172 | fields: [
173 | {fieldId: 'one', key: 'name', label: 'Name'},
174 | {fieldId: 'three', key: 'average', label: 'Average'}
175 | ]
176 | }
177 | }
178 | }
179 | });
180 |
181 | var table = Blaze.renderWithData(
182 | Template.testReactivity,
183 | {},
184 | document.body
185 | );
186 |
187 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
188 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
189 | test.length($('.reactive-table-columns-dropdown input:checked'), 2, "both boxes should be checked");
190 |
191 | var expectColumnsOneAndTwoOnly = expect(function () {
192 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
193 | test.length($('.reactive-table-columns-dropdown input'), 3, "three checkboxes should be available");
194 | test.length($('.reactive-table-columns-dropdown input:checked'), 2, "two checkboxes box should be checked");
195 | test.length($('.reactive-table-columns-dropdown input:checked[data-fieldid="two"]'), 1, "newly inserted field should be checked");
196 | test.length($('.reactive-table-columns-dropdown input:checked[data-fieldid="three"]'), 0, "previously hidden field should still be unchecked");
197 | test.length($('.reactive-table th:first-child').text().trim().match(/Name/), 1, "Name column should still be visible");
198 | test.length($('.reactive-table th:nth-child(2)').text().trim().match(/Score/), 1, "Score column should now be visible");
199 |
200 | Blaze.remove(table);
201 | });
202 |
203 | var expectOneColumnOnly = expect(function () {
204 | test.length($('.reactive-table th'), 1, "one columns should be displayed");
205 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "one box should be checked");
206 | test.length($('.reactive-table th:first-child').text().trim().match(/Name/), 1, "visible column should still be displayed");
207 |
208 | insert2ndField.set(true);
209 | Meteor.setTimeout(expectColumnsOneAndTwoOnly, 0);
210 | });
211 |
212 | $('.reactive-table-columns-dropdown input[data-fieldid="three"]').click();
213 | Meteor.setTimeout(expectOneColumnOnly, 0);
214 | }]);
215 |
216 | testAsyncMulti('Column Toggles - toggling after adding new columns without fieldIds', [function (test, expect) {
217 | var insert2ndField = new ReactiveVar(false);
218 | Template.testReactivity.helpers({
219 | collection: function () {
220 | return collection;
221 | },
222 | settings: function () {
223 | if (insert2ndField.get()) {
224 | return {
225 | showColumnToggles: true,
226 | fields: [
227 | {key: 'name', label: 'Name'},
228 | {key: 'score', label: 'Score'},
229 | {key: 'average', label: 'Average'}
230 | ]
231 | }
232 | } else {
233 | return {
234 | showColumnToggles: true,
235 | fields: [
236 | {key: 'name', label: 'Name'},
237 | {key: 'average', label: 'Average'}
238 | ]
239 | }
240 | }
241 | }
242 | });
243 |
244 | var table = Blaze.renderWithData(
245 | Template.testReactivity,
246 | {},
247 | document.body
248 | );
249 |
250 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
251 | test.length($('.reactive-table-columns-dropdown input'), 2, "two checkboxes should be available");
252 | test.length($('.reactive-table-columns-dropdown input:checked'), 2, "both boxes should be checked");
253 |
254 | var expectColumnsOneAndTwoOnly = expect(function () {
255 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
256 | test.length($('.reactive-table-columns-dropdown input'), 3, "three checkboxes should be available");
257 | test.length($('.reactive-table-columns-dropdown input:checked'), 2, "two checkboxes box should be checked");
258 | test.length($('.reactive-table th:first-child').text().trim().match(/Name/), 1, "Name column should still be visible");
259 |
260 | // Tests for known erroneous behaviour in this case if fieldIds are not explicitly set:
261 | test.length($('.reactive-table-columns-dropdown input:checked[data-fieldid="1"]'), 0, "newly inserted field will be unchecked");
262 | test.length($('.reactive-table th:nth-child(2)').text().trim().match(/Average/), 1, "Average column will still be visible");
263 |
264 | Blaze.remove(table);
265 | });
266 |
267 | var expectOneColumnOnly = expect(function () {
268 | test.length($('.reactive-table th'), 1, "one columns should be displayed");
269 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "one box should be checked");
270 | test.length($('.reactive-table th:first-child').text().trim().match(/Name/), 1, "visible column should still be displayed");
271 |
272 | insert2ndField.set(true);
273 | Meteor.setTimeout(expectColumnsOneAndTwoOnly, 0);
274 | });
275 |
276 | $('.reactive-table-columns-dropdown input[data-fieldid="1"]').click();
277 | Meteor.setTimeout(expectOneColumnOnly, 0);
278 | }]);
279 |
280 | Tinytest.add('Column Toggles - hideToggle option', function (test) {
281 | testTable(
282 | {
283 | collection: rows,
284 | settings: {
285 | showColumnToggles: true,
286 | fields: [
287 | {key: 'name', hideToggle: false},
288 | {key: 'score', hideToggle: true}
289 | ]
290 | }
291 | },
292 | function () {
293 | test.length($('.reactive-table-columns-dropdown'), 1, "column toggle button should be visible");
294 | test.length($('.reactive-table th'), 2, "two columns should be displayed");
295 | test.length($('.reactive-table-columns-dropdown input'), 1, "one checkbox should be available");
296 | test.length($('.reactive-table-columns-dropdown input:checked'), 1, "one box should be checked");
297 | }
298 | );
299 | });
300 |
--------------------------------------------------------------------------------
/test/test_reactivity.js:
--------------------------------------------------------------------------------
1 | testAsyncMulti('Reactivity - collection', [function (test, expect) {
2 | var collection = new Mongo.Collection();
3 | collection.insert({name: 'item 1', value: 1});
4 | var table = Blaze.renderWithData(
5 | Template.reactiveTable,
6 | {collection: collection},
7 | document.body
8 | );
9 | test.length($('.reactive-table tbody tr'), 1, "table should initially have one row");
10 | collection.insert({name: 'item 2', value: 2});
11 | test.length($('.reactive-table tbody tr'), 2, "table should reactively add second row");
12 | collection.remove({name: 'item 2'});
13 | test.length($('.reactive-table tbody tr'), 1, "table should reactively remove a row");
14 |
15 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "1", "table row should have the initial value");
16 | collection.update({name: 'item 1'}, {'$set': {value: 2}});
17 | Meteor.setTimeout(expect(function () {
18 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "2", "table should reactively update with new value");
19 | Blaze.remove(table);
20 | }), 0);
21 | }]);
22 |
23 | testAsyncMulti('Reactivity - cursor', [function (test, expect) {
24 | var collection = new Meteor.Collection();
25 | collection.insert({name: 'item 1', value: 1});
26 | var table = Blaze.renderWithData(
27 | Template.reactiveTable,
28 | {collection: collection.find()},
29 | document.body
30 | );
31 | test.length($('.reactive-table tbody tr'), 1, "table should initially have one row");
32 | collection.insert({name: 'item 2', value: 2});
33 | test.length($('.reactive-table tbody tr'), 2, "table should reactively add second row");
34 | collection.remove({name: 'item 2'});
35 | test.length($('.reactive-table tbody tr'), 1, "table should reactively remove a row");
36 |
37 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "1", "table row should have the initial value");
38 | collection.update({name: 'item 1'}, {'$set': {value: 2}});
39 | Meteor.setTimeout(expect(function () {
40 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "2", "table should reactively update with new value");
41 | Blaze.remove(table);
42 | }), 0);
43 | }]);
44 |
45 | testAsyncMulti('Reactivity - collection changed by helper', [function (test, expect) {
46 | var collection = new Mongo.Collection();
47 | collection.insert({name: 'item 1', value: 1});
48 | var showCollection = new ReactiveVar(false);
49 |
50 | Template.testReactivity.helpers({
51 | collection: function () {
52 | if (showCollection.get()) {
53 | return collection;
54 | } else {
55 | return [];
56 | }
57 | }
58 | });
59 |
60 | var view = Blaze.renderWithData(
61 | Template.testReactivity,
62 | {},
63 | document.body
64 | );
65 |
66 | test.length($('.reactive-table tbody tr'), 0, 'table should be empty');
67 |
68 | showCollection.set(true);
69 | Meteor.setTimeout(expect(function () {
70 | test.length($('.reactive-table tbody tr'), 1, 'table should render collection');
71 | Blaze.remove(view);
72 | }), 0);
73 | }]);
74 |
75 | testAsyncMulti('Reactivity - setting changed by helper', [function (test, expect) {
76 | var useFontAwesome = new ReactiveVar(false);
77 | Template.testReactivity.helpers({
78 | collection: function () {
79 | return collection;
80 | },
81 | settings: function () {
82 | if (useFontAwesome.get()) {
83 | return {useFontAwesome: true}
84 | } else {
85 | return {useFontAwesome: false};
86 | }
87 | }
88 | });
89 |
90 | var view = Blaze.renderWithData(
91 | Template.testReactivity,
92 | {},
93 | document.body
94 | );
95 |
96 | test.length($('.reactive-table-filter .fa'), 0, "font awesome should be off");
97 |
98 | useFontAwesome.set(true);
99 | Meteor.setTimeout(expect(function () {
100 | test.length($('.reactive-table-filter .fa'), 1, "font awesome should be on");
101 | Blaze.remove(view);
102 | }), 0);
103 | }]);
104 |
105 | testAsyncMulti('Reactivity - user-editable setting persists', [function (test, expect) {
106 | var rowsPerPage = new ReactiveVar(2);
107 | Template.testReactivity.helpers({
108 | collection: function () {
109 | return collection;
110 | },
111 | settings: function () {
112 | return {rowsPerPage: rowsPerPage.get()};
113 | }
114 | });
115 |
116 | var view = Blaze.renderWithData(
117 | Template.testReactivity,
118 | {},
119 | document.body
120 | );
121 |
122 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
123 |
124 | var expectSameRowsPerPage = expect(function () {
125 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
126 | Blaze.remove(view);
127 | });
128 |
129 | rowsPerPage.set(5);
130 | Meteor.setTimeout(expectSameRowsPerPage, 0);
131 | }]);
132 |
133 | testAsyncMulti('Reactivity - user setting persists when other arguments change', [function (test, expect) {
134 | var showCollection = new ReactiveVar(true);
135 | Template.testReactivity.helpers({
136 | collection: function () {
137 | if (showCollection.get()) {
138 | return collection;
139 | } else {
140 | return rows;
141 | }
142 | },
143 | settings: function () {
144 | return {rowsPerPage: 2};
145 | }
146 | });
147 |
148 | var view = Blaze.renderWithData(
149 | Template.testReactivity,
150 | {},
151 | document.body
152 | );
153 |
154 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
155 | test.length($('.reactive-table tbody tr'), 2, "first page should have two rows");
156 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
157 |
158 | var testSecondPage = function () {
159 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
160 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
161 | };
162 |
163 | var expectStillSecondPage = expect(function () {
164 | testSecondPage();
165 | Blaze.remove(view);
166 | });
167 |
168 | var expectSecondPage = expect(function () {
169 | testSecondPage();
170 |
171 | showCollection.set(false);
172 | Meteor.setTimeout(expectStillSecondPage, 0);
173 | });
174 |
175 | $('.reactive-table-navigation .page-number input').val('2');
176 | $('.reactive-table-navigation .page-number input').trigger("change");
177 |
178 | Meteor.setTimeout(expectSecondPage, 0);
179 |
180 |
181 | showCollection.set(true);
182 | }]);
183 |
184 | testAsyncMulti('Reactivity - adding new visible field with fieldIds', [function (test, expect) {
185 | var add2ndField = new ReactiveVar(false);
186 | Template.testReactivity.helpers({
187 | collection: function () {
188 | return collection;
189 | },
190 | settings: function () {
191 | if (add2ndField.get()) {
192 | return {
193 | fields: [
194 | {fieldId: 'one', key: 'name', label: 'name', hidden: false},
195 | {fieldId: 'two', key: 'score', label: 'score', hidden: false}
196 | ]
197 | }
198 | } else {
199 | return {
200 | fields: [
201 | {fieldId: 'one', key: 'name', label: 'name', hidden: false}
202 | ]
203 | }
204 | }
205 | }
206 | });
207 |
208 | var view = Blaze.renderWithData(
209 | Template.testReactivity,
210 | {},
211 | document.body
212 | );
213 |
214 | test.length($('.reactive-table th'), 1, "one column should be rendered");
215 | test.length($('.reactive-table th:first-child').text().trim().match(/^name/), 1, "first column should be name");
216 |
217 | add2ndField.set(true);
218 | Meteor.setTimeout(expect(function () {
219 | test.length($('.reactive-table th'), 2, "two columns should be rendered");
220 | test.length($('.reactive-table th:first-child').text().trim().match(/^name/), 1, "first column should be name");
221 | test.length($('.reactive-table th:nth-child(2)').text().trim().match(/^score/), 1, "second column should be score");
222 | Blaze.remove(view);
223 | }), 0);
224 | }]);
225 |
226 | testAsyncMulti('Reactivity - adding new hidden field with fieldIds', [function (test, expect) {
227 | var add2ndField = new ReactiveVar(false);
228 | Template.testReactivity.helpers({
229 | collection: function () {
230 | return collection;
231 | },
232 | settings: function () {
233 | if (add2ndField.get()) {
234 | return {
235 | fields: [
236 | {fieldId: 'one', key: 'name', label: 'name', hidden: false},
237 | {fieldId: 'two', key: 'score', label: 'score', hidden: true}
238 | ]
239 | }
240 | } else {
241 | return {
242 | fields: [
243 | {fieldId: 'one', key: 'name', label: 'name', hidden: false}
244 | ]
245 | }
246 | }
247 | }
248 | });
249 |
250 | var view = Blaze.renderWithData(
251 | Template.testReactivity,
252 | {},
253 | document.body
254 | );
255 |
256 | test.length($('.reactive-table th'), 1, "one column should be rendered");
257 | test.length($('.reactive-table th:first-child').text().trim().match(/^name/), 1, "first column should be name");
258 |
259 | add2ndField.set(true);
260 | Meteor.setTimeout(expect(function () {
261 | test.length($('.reactive-table th'), 1, "one column should be rendered");
262 | test.length($('.reactive-table th:first-child').text().trim().match(/^name/), 1, "first column should be name");
263 | test.isNull($('.reactive-table th:nth-child(2)').text().trim().match(/^score/), 1, "second column should be hidden");
264 | Blaze.remove(view);
265 | }), 0);
266 | }]);
267 |
268 | testAsyncMulti('Reactivity - adding new visible field without fieldIds', [function (test, expect) {
269 | var add2ndField = new ReactiveVar(false);
270 | Template.testReactivity.helpers({
271 | collection: function () {
272 | return collection;
273 | },
274 | settings: function () {
275 | if (add2ndField.get()) {
276 | return {
277 | fields: [
278 | {key: 'score', label: 'score', hidden: false},
279 | {key: 'name', label: 'name', hidden: false}
280 | ]
281 | }
282 | } else {
283 | return {
284 | fields: [
285 | {key: 'name', label: 'name', hidden: false}
286 | ]
287 | }
288 | }
289 | }
290 | });
291 |
292 | var view = Blaze.renderWithData(
293 | Template.testReactivity,
294 | {},
295 | document.body
296 | );
297 |
298 | test.length($('.reactive-table th'), 1, "one column should be rendered");
299 | test.length($('.reactive-table th:first-child').text().trim().match(/^name/), 1, "first column should be name");
300 |
301 | add2ndField.set(true);
302 | Meteor.setTimeout(expect(function () {
303 | test.length($('.reactive-table th'), 2, "two columns should be rendered");
304 | test.length($('.reactive-table th:first-child').text().trim().match(/^score/), 1, "first column should be score");
305 | test.length($('.reactive-table th:nth-child(2)').text().trim().match(/^name/), 1, "second column should be name");
306 | Blaze.remove(view);
307 | }), 0);
308 | }]);
309 |
310 | testAsyncMulti('Reactivity - server-side collection', [function (test, expect) {
311 | var table = Blaze.renderWithData(
312 | Template.reactiveTable,
313 | {collection: 'reactivity-test', fields: ['name', 'value']},
314 | document.body
315 | );
316 |
317 | var expectUpdate = expect(function () {
318 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "2", "table should reactively update with new value");
319 | Blaze.remove(table);
320 | });
321 |
322 | var expectRemove = expect(function () {
323 | test.length($('.reactive-table tbody tr'), 1, "table should reactively remove a row");
324 |
325 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "1", "table row should have the initial value");
326 | Meteor.call('testUpdate', function () {
327 | Meteor.setTimeout(expectUpdate, 500);
328 | });
329 | });
330 |
331 | var expectInsert = expect(function () {
332 | test.length($('.reactive-table tbody tr'), 2, "table should reactively add second row");
333 | Meteor.call('testRemove', function () {
334 | Meteor.setTimeout(expectRemove, 500);
335 | });
336 | });
337 |
338 | var expectInitialRow = expect(function () {
339 | test.length($('.reactive-table tbody tr'), 1, "table should initially have one row");
340 | Meteor.call('testInsert', function () {
341 | Meteor.setTimeout(expectInsert, 500);
342 | });
343 | });
344 |
345 | Meteor.setTimeout(expectInitialRow, 500);
346 | }]);
347 |
348 | testAsyncMulti('Reactivity - server-side collection access', [function (test, expect) {
349 | var table = Blaze.renderWithData(
350 | Template.reactiveTable,
351 | {collection: 'reactivity-test-access', fields: ['name', 'value']},
352 | document.body
353 | );
354 |
355 | var expectDataHidden = expect(function () {
356 | test.length($('.reactive-table tbody tr'), 0, "table should remove row");
357 | Blaze.remove(table);
358 | });
359 |
360 | var expectData = expect(function () {
361 | test.length($('.reactive-table tbody tr'), 1, "table should show one row");
362 | Meteor.logout(function () {
363 | Meteor.setTimeout(expectDataHidden, 500);
364 | });
365 | });
366 |
367 | var expectNoData = expect(function () {
368 | test.length($('.reactive-table tbody tr'), 0, "table should initially have no rows");
369 | Meteor.loginWithPassword('abcd', 'abcd1234', function () {
370 | Meteor.setTimeout(expectData, 500);
371 | });
372 | });
373 |
374 | Meteor.logout(function () {
375 | Meteor.setTimeout(expectNoData, 500);
376 | });
377 | }]);
378 |
379 |
--------------------------------------------------------------------------------
/test/test_filtering.js:
--------------------------------------------------------------------------------
1 | testAsyncMulti('Filtering - one input', [function (test, expect) {
2 | var table = Blaze.renderWithData(
3 | Template.reactiveTable,
4 | {collection: rows},
5 | document.body
6 | );
7 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
8 |
9 | var expectTwoRows = expect(function () {
10 | test.length($('.reactive-table tbody tr'), 2, "filtered to two rows");
11 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "filtered first row");
12 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "filtered second row");
13 | Blaze.remove(table);
14 | });
15 |
16 | $('.reactive-table-filter input').val('g');
17 | $('.reactive-table-filter input').trigger('input');
18 | Meteor.setTimeout(expectTwoRows, 1000);
19 | }]);
20 |
21 | testAsyncMulti('Filtering - two inputs', [function (test, expect) {
22 | var table = Blaze.renderWithData(
23 | Template.reactiveTable,
24 | {collection: rows},
25 | document.body
26 | );
27 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
28 |
29 | var expectOneRow = expect(function () {
30 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
31 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Grace Hopper", "filtered row");
32 | Blaze.remove(table);
33 | });
34 |
35 | $('.reactive-table-filter input').val('g hopper');
36 | $('.reactive-table-filter input').trigger('input');
37 | Meteor.setTimeout(expectOneRow, 1000);
38 | }]);
39 |
40 | testAsyncMulti('Filtering - quotes', [function (test, expect) {
41 | var table = Blaze.renderWithData(
42 | Template.reactiveTable,
43 | {collection: rows},
44 | document.body
45 | );
46 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
47 |
48 | var expectZeroRows = expect(function () {
49 | test.length($('.reactive-table tbody tr'), 0, "filtered to zero rows");
50 | Blaze.remove(table);
51 | });
52 |
53 | var expectOneRow = expect(function () {
54 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
55 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Grace Hopper", "filtered first row");
56 |
57 | $('.reactive-table-filter input').val("\"hopper grace\"");
58 | $('.reactive-table-filter input').trigger('input');
59 | Meteor.setTimeout(expectZeroRows, 1000);
60 | });
61 |
62 | $('.reactive-table-filter input').val("\"grace hopper\"");
63 | $('.reactive-table-filter input').trigger('input');
64 | Meteor.setTimeout(expectOneRow, 1000);
65 | }]);
66 |
67 | testAsyncMulti('Filtering - numerical', [function (test, expect) {
68 | var table = Blaze.renderWithData(
69 | Template.reactiveTable,
70 | {collection: rows},
71 | document.body
72 | );
73 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
74 |
75 | var expectThreeRows = expect(function () {
76 | test.length($('.reactive-table tbody tr'), 3, "filtered to three rows");
77 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "filtered first row");
78 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Claude Shannon", "filtered second row");
79 | Blaze.remove(table);
80 | });
81 |
82 | $('.reactive-table-filter input').val("5");
83 | $('.reactive-table-filter input').trigger('input');
84 | Meteor.setTimeout(expectThreeRows, 1000);
85 | }]);
86 |
87 | testAsyncMulti('Filtering - numbers in strings', [function (test, expect) {
88 | var numberStringRows = _.map(rows, function (row) {
89 | var newRow = {name: row.name, score: row.score + "points"};
90 | return newRow;
91 | });
92 | var table = Blaze.renderWithData(
93 | Template.reactiveTable,
94 | {collection: numberStringRows},
95 | document.body
96 | );
97 | test.length($('.reactive-table tbody tr'), 6, "initial six rows");
98 |
99 | var expectThreeRows = expect(function () {
100 | test.length($('.reactive-table tbody tr'), 4, "filtered to four rows");
101 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "filtered first row");
102 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Claude Shannon", "filtered second row");
103 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Nikola Tesla", "filtered fourth row");
104 | Blaze.remove(table);
105 | });
106 |
107 | $('.reactive-table-filter input').val("5");
108 | $('.reactive-table-filter input').trigger('input');
109 | Meteor.setTimeout(expectThreeRows, 1000);
110 | }]);
111 |
112 | testAsyncMulti('Filtering - server-side', [function (test, expect) {
113 | var table = Blaze.renderWithData(
114 | Template.reactiveTable,
115 | {collection: 'collection', fields: ['name', 'score']},
116 | document.body
117 | );
118 |
119 | var expectTwoRows = expect(function () {
120 | test.length($('.reactive-table tbody tr'), 2, "filtered to two rows");
121 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "filtered first row");
122 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "filtered second row");
123 | Blaze.remove(table);
124 | });
125 |
126 | var expectSixRows = expect(function () {
127 | test.length($('.reactive-table tbody tr'), 6, "initial 6 rows");
128 |
129 | $('.reactive-table-filter input').val('g');
130 | $('.reactive-table-filter input').trigger('input');
131 | Meteor.setTimeout(expectTwoRows, 1000);
132 | });
133 |
134 | Meteor.setTimeout(expectSixRows, 500);
135 | }]);
136 |
137 | testAsyncMulti('Filtering - enableRegex false client side', [function (test, expect) {
138 | var rows = [
139 | {name: 'item 1', value: '1+2'},
140 | {name: 'item 2', value: 'abc'}
141 | ];
142 |
143 | var table = Blaze.renderWithData(
144 | Template.reactiveTable,
145 | {collection: rows},
146 | document.body
147 | );
148 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
149 |
150 | var expectOneRow = expect(function () {
151 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
152 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "filtered first row");
153 | Blaze.remove(table);
154 | });
155 |
156 | $('.reactive-table-filter input').val('+');
157 | $('.reactive-table-filter input').trigger('input');
158 | Meteor.setTimeout(expectOneRow, 1000);
159 | }]);
160 |
161 | testAsyncMulti('Filtering - enableRegex true client side', [function (test, expect) {
162 | var rows = [
163 | {name: 'item 1', value: '1+2'},
164 | {name: 'item 2', value: 'abc'}
165 | ];
166 |
167 | var table = Blaze.renderWithData(
168 | Template.reactiveTable,
169 | {collection: rows, enableRegex: true},
170 | document.body
171 | );
172 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
173 |
174 | var expectTwoRows = expect(function () {
175 | test.length($('.reactive-table tbody tr'), 2, "filtered to both rows");
176 | Blaze.remove(table);
177 | });
178 |
179 | var expectOneRow = expect(function () {
180 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
181 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "filtered first row");
182 |
183 | $('.reactive-table-filter input').val('a|1');
184 | $('.reactive-table-filter input').trigger('input');
185 | Meteor.setTimeout(expectTwoRows, 1000);
186 | });
187 |
188 | $('.reactive-table-filter input').val('\\+');
189 | $('.reactive-table-filter input').trigger('input');
190 | Meteor.setTimeout(expectOneRow, 1000);
191 | }]);
192 |
193 | testAsyncMulti('Filtering - enableRegex false server side', [function (test, expect) {
194 | var table = Blaze.renderWithData(
195 | Template.reactiveTable,
196 | {collection: 'filter-regex-disabled', fields: ['name', 'value']},
197 | document.body
198 | );
199 |
200 | var expectOneRow = expect(function () {
201 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
202 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "filtered first row");
203 | Blaze.remove(table);
204 | });
205 |
206 | var expectInitialRows = expect(function () {
207 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
208 |
209 | $('.reactive-table-filter input').val('+');
210 | $('.reactive-table-filter input').trigger('input');
211 | Meteor.setTimeout(expectOneRow, 1000);
212 | });
213 |
214 | Meteor.setTimeout(expectInitialRows, 500);
215 | }]);
216 |
217 | testAsyncMulti('Filtering - enableRegex true server side', [function (test, expect) {
218 | var table = Blaze.renderWithData(
219 | Template.reactiveTable,
220 | {collection: 'filter-regex-enabled', fields: ['name', 'value']},
221 | document.body
222 | );
223 |
224 | var expectTwoRows = expect(function () {
225 | test.length($('.reactive-table tbody tr'), 2, "filtered to both rows");
226 | Blaze.remove(table);
227 | });
228 |
229 | var expectOneRow = expect(function () {
230 | test.length($('.reactive-table tbody tr'), 1, "filtered to one row");
231 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "filtered first row");
232 |
233 | $('.reactive-table-filter input').val('a|1');
234 | $('.reactive-table-filter input').trigger('input');
235 | Meteor.setTimeout(expectTwoRows, 1000);
236 | });
237 |
238 | var expectInitialRows = expect(function () {
239 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
240 |
241 | $('.reactive-table-filter input').val('\\+');
242 | $('.reactive-table-filter input').trigger('input');
243 | Meteor.setTimeout(expectOneRow, 1000);
244 | });
245 |
246 | Meteor.setTimeout(expectInitialRows, 500);
247 | }]);
248 |
249 | testAsyncMulti('Filtering - server-side field inclusion', [function (test, expect) {
250 | var table = Blaze.renderWithData(
251 | Template.reactiveTable,
252 | {collection: 'filter-inclusion', fields: ['name', 'value']},
253 | document.body
254 | );
255 |
256 | var expectNoRows = expect(function () {
257 | test.length($('.reactive-table tbody tr'), 0, "no rows should match");
258 | Blaze.remove(table);
259 | });
260 |
261 | var expectTwoRowsNoValues = expect(function () {
262 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
263 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "first column content");
264 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "", "second column content");
265 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "item 2", "first column content");
266 | test.equal($('.reactive-table tbody tr:nth-child(2) td:nth-child(2)').text(), "", "second column content");
267 |
268 | $('.reactive-table-filter input').val('abc');
269 | $('.reactive-table-filter input').trigger('input');
270 | Meteor.setTimeout(expectNoRows, 1000);
271 | });
272 |
273 | Meteor.setTimeout(expectTwoRowsNoValues, 500);
274 | }]);
275 |
276 | testAsyncMulti('Filtering - server-side field exclusion', [function (test, expect) {
277 | var table = Blaze.renderWithData(
278 | Template.reactiveTable,
279 | {collection: 'filter-exclusion', fields: ['name', 'value']},
280 | document.body
281 | );
282 |
283 | var expectNoRows = expect(function () {
284 | test.length($('.reactive-table tbody tr'), 0, "no rows should match");
285 | Blaze.remove(table);
286 | });
287 |
288 | var expectTwoRowsNoValues = expect(function () {
289 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
290 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "first column content");
291 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "", "second column content");
292 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "item 2", "first column content");
293 | test.equal($('.reactive-table tbody tr:nth-child(2) td:nth-child(2)').text(), "", "second column content");
294 |
295 | $('.reactive-table-filter input').val('abc');
296 | $('.reactive-table-filter input').trigger('input');
297 | Meteor.setTimeout(expectNoRows, 1000);
298 | });
299 |
300 | Meteor.setTimeout(expectTwoRowsNoValues, 500);
301 | }]);
302 |
303 | testAsyncMulti('Filtering - server-side nested field inclusion', [function (test, expect) {
304 | var table = Blaze.renderWithData(
305 | Template.reactiveTable,
306 | {collection: 'nested-filter-inclusion', fields: ['name', 'nested.0.value']},
307 | document.body
308 | );
309 |
310 | var expectOneRow = expect(function () {
311 | test.length($('.reactive-table tbody tr'), 1, "one row should match");
312 | Blaze.remove(table);
313 | });
314 |
315 | var expectNoRows = expect(function () {
316 | test.length($('.reactive-table tbody tr'), 0, "no rows should match");
317 |
318 | $('.reactive-table-filter input').val('2');
319 | $('.reactive-table-filter input').trigger('input');
320 | Meteor.setTimeout(expectOneRow, 1000);
321 | });
322 |
323 | var expectTwoRowsNestedValues = expect(function () {
324 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
325 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "", "first column content");
326 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "value 1", "second column content");
327 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "", "first column content");
328 | test.equal($('.reactive-table tbody tr:nth-child(2) td:nth-child(2)').text(), "value 2", "second column content");
329 |
330 | $('.reactive-table-filter input').val('item');
331 | $('.reactive-table-filter input').trigger('input');
332 | Meteor.setTimeout(expectNoRows, 1000);
333 | });
334 |
335 | Meteor.setTimeout(expectTwoRowsNestedValues, 500);
336 | }]);
337 |
338 | testAsyncMulti('Filtering - server-side nested field exclusion', [function (test, expect) {
339 | var table = Blaze.renderWithData(
340 | Template.reactiveTable,
341 | {collection: 'nested-filter-exclusion', fields: ['name', 'nested.0.value']},
342 | document.body
343 | );
344 |
345 | var expectNoRows = expect(function () {
346 | test.length($('.reactive-table tbody tr'), 0, "no rows should match");
347 | Blaze.remove(table);
348 | });
349 |
350 | var expectTwoRowsNoValues = expect(function () {
351 | test.length($('.reactive-table tbody tr'), 2, "initial two rows");
352 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "item 1", "first column content");
353 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "", "second column content");
354 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "item 2", "first column content");
355 | test.equal($('.reactive-table tbody tr:nth-child(2) td:nth-child(2)').text(), "", "second column content");
356 |
357 | $('.reactive-table-filter input').val('value');
358 | $('.reactive-table-filter input').trigger('input');
359 | Meteor.setTimeout(expectNoRows, 1000);
360 | });
361 |
362 | Meteor.setTimeout(expectTwoRowsNoValues, 500);
363 | }]);
364 |
--------------------------------------------------------------------------------
/test/test_sorting.js:
--------------------------------------------------------------------------------
1 | testAsyncMulti('Sorting - direction', [function (test, expect) {
2 | var table = Blaze.renderWithData(
3 | Template.reactiveTable,
4 | {collection: rows},
5 | document.body
6 | );
7 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
8 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
9 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");
10 |
11 | var expectAscending = expect(function () {
12 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "ascending first row");
13 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "ascending second row");
14 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "ascending fourth row");
15 | Blaze.remove(table);
16 | });
17 |
18 | var expectDescending = expect(function () {
19 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "descending first row");
20 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "descending second row");
21 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "descending fourth row");
22 |
23 | $('.reactive-table th:first-child').click();
24 | Meteor.setTimeout(expectAscending, 0);
25 | });
26 |
27 | $('.reactive-table th:first-child').click();
28 | Meteor.setTimeout(expectDescending, 0);
29 | }]);
30 |
31 | testAsyncMulti('Sorting - column', [function (test, expect) {
32 | var table = Blaze.renderWithData(
33 | Template.reactiveTable,
34 | {collection: collection},
35 | document.body
36 | );
37 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
38 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
39 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");
40 |
41 | var expectSecondColumnDescending = expect(function () {
42 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
43 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
44 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column descending fourth row");
45 | Blaze.remove(table);
46 | });
47 |
48 | var expectSecondColumn = expect(function () {
49 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
50 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "2nd column second row");
51 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column fourth row");
52 |
53 | $('.reactive-table th:nth-child(2)').click();
54 | Meteor.setTimeout(expectSecondColumnDescending, 0);
55 | });
56 |
57 | $('.reactive-table th:nth-child(2)').click();
58 | Meteor.setTimeout(expectSecondColumn, 0);
59 | }]);
60 |
61 | testAsyncMulti('Sorting - after adding new column with fieldIds', [function (test, expect) {
62 | var insert1stField = new ReactiveVar(false);
63 | Template.testReactivity.helpers({
64 | collection: function () {
65 | return collection;
66 | },
67 | settings: function () {
68 | if (insert1stField.get()) {
69 | return {
70 | fields: [
71 | {fieldId: 'name', key: 'name', label: 'Name'},
72 | {fieldId: 'score', key: 'score', label: 'Score'}
73 | ]
74 | }
75 | } else {
76 | return {
77 | fields: [
78 | {fieldId: 'score', key: 'score', label: 'Score'},
79 | ]
80 | }
81 | }
82 | }
83 | });
84 |
85 | var table = Blaze.renderWithData(
86 | Template.testReactivity,
87 | {},
88 | document.body
89 | );
90 |
91 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "0", "initial first row");
92 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "5", "initial second row");
93 | test.equal($('.reactive-table tbody tr:nth-child(5) td:first-child').text(), "10", "initial fifth row");
94 |
95 | var expectSecondColumnStillDescending = expect(function () {
96 | test.equal($('.reactive-table tbody tr:first-child td:nth-child(2)').text(), "15", "2nd column descending first row");
97 | test.equal($('.reactive-table tbody tr:nth-child(2) td:nth-child(2)').text(), "10", "2nd column descending second row");
98 | test.equal($('.reactive-table tbody tr:nth-child(5) td:nth-child(2)').text(), "5", "2nd column descending fifth row");
99 | Blaze.remove(table);
100 | });
101 |
102 | var expectDescending = expect(function () {
103 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "15", "descending first row");
104 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "10", "descending second row");
105 | test.equal($('.reactive-table tbody tr:nth-child(5) td:first-child').text(), "5", "descending fifth row");
106 |
107 | insert1stField.set(true);
108 | Meteor.setTimeout(expectSecondColumnStillDescending, 0);
109 | });
110 |
111 | $('.reactive-table th:first-child').click();
112 | Meteor.setTimeout(expectDescending, 0);
113 | }]);
114 |
115 | testAsyncMulti('Sorting - server-side', [function (test, expect) {
116 | var table = Blaze.renderWithData(
117 | Template.reactiveTable,
118 | {collection: 'collection', fields: ['name', 'score']},
119 | document.body
120 | );
121 |
122 | var expectSecondColumn = expect(function () {
123 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
124 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "2nd column second row");
125 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column fourth row");
126 |
127 | Blaze.remove(table);
128 | });
129 |
130 | var expectAscending = expect(function () {
131 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "ascending first row");
132 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "ascending second row");
133 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "ascending fourth row");
134 |
135 | $('.reactive-table th:nth-child(2)').click();
136 | Meteor.setTimeout(expectSecondColumn, 500);
137 | });
138 |
139 | var expectDescending = expect(function () {
140 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "descending first row");
141 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "descending second row");
142 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "descending fourth row");
143 |
144 | $('.reactive-table th:first-child').click();
145 | Meteor.setTimeout(expectAscending, 500);
146 | });
147 |
148 | var expectDefaultAscending = expect(function () {
149 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
150 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
151 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");
152 |
153 | $('.reactive-table th:first-child').click();
154 | Meteor.setTimeout(expectDescending, 1000);
155 | });
156 |
157 | Meteor.setTimeout(expectDefaultAscending, 1000);
158 | }]);
159 |
160 | testAsyncMulti('Sorting - virtual columns', [function (test, expect) {
161 | var table = Blaze.renderWithData(
162 | Template.reactiveTable,
163 | {
164 | collection: collection,
165 | fields: [
166 | {key: 'name', label: 'First two letters', fn: function (name) { return name.slice(0, 2); }},
167 | {key: 'name', label: 'Second letter', sortByValue: true, fn: function (name) { return name.slice(1, 2); }}
168 | ]
169 | },
170 | document.body
171 | );
172 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ad", "initial first row");
173 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ca", "initial second row");
174 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Gr", "initial fourth row");
175 |
176 | var expectSecondColumnDescending = expect(function () {
177 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ni", "2nd column descending first row");
178 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ma", "2nd column descending second row");
179 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Cl", "2nd column descending fourth row");
180 | Blaze.remove(table);
181 | });
182 |
183 | var expectNoChange = expect(function () {
184 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ad", "2nd column first row");
185 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ca", "2nd column second row");
186 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Gr", "2nd column fourth row");
187 |
188 | $('.reactive-table th:nth-child(2)').click();
189 | Meteor.setTimeout(expectSecondColumnDescending, 0);
190 | });
191 |
192 | $('.reactive-table th:nth-child(2)').click();
193 | Meteor.setTimeout(expectNoChange, 0);
194 | }]);
195 |
196 | testAsyncMulti('Sorting - multi-column', [function (test, expect) {
197 | var table = Blaze.renderWithData(
198 | Template.reactiveTable,
199 | {collection: collection},
200 | document.body
201 | );
202 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
203 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
204 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");
205 |
206 | var expectSecondColumnAscending = expect(function () {
207 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
208 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "2nd column second row");
209 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Ada Lovelace", "2nd column fourth row");
210 | Blaze.remove(table);
211 | });
212 |
213 | var expectSecondColumnDescending = expect(function () {
214 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
215 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
216 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column descending fourth row");
217 |
218 | $('.reactive-table th:nth-child(2)').click();
219 | Meteor.setTimeout(expectSecondColumnAscending, 0);
220 | });
221 |
222 | var expectFirstColumnDescending = expect(function () {
223 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "first column descending first row");
224 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "first column descending second row");
225 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "first column descending fourth row");
226 |
227 | $('.reactive-table th:nth-child(2)').click();
228 | Meteor.setTimeout(expectSecondColumnDescending, 0);
229 | });
230 |
231 | $('.reactive-table th:first-child').click();
232 | Meteor.setTimeout(expectFirstColumnDescending, 0);
233 | }]);
234 |
235 | testAsyncMulti('Sorting - multi-column disabled', [function (test, expect) {
236 | var table = Blaze.renderWithData(
237 | Template.reactiveTable,
238 | {collection: collection, multiColumnSort: false},
239 | document.body
240 | );
241 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "initial first row");
242 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Carl Friedrich Gauss", "initial second row");
243 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Grace Hopper", "initial fourth row");
244 |
245 | var expectSecondColumnAscending = expect(function () {
246 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Carl Friedrich Gauss", "2nd column first row");
247 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Ada Lovelace", "2nd column second row");
248 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "2nd column fourth row");
249 | Blaze.remove(table);
250 | });
251 |
252 | var expectSecondColumnDescending = expect(function () {
253 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "2nd column descending first row");
254 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Grace Hopper", "2nd column descending second row");
255 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Marie Curie", "2nd column descending fourth row");
256 |
257 | $('.reactive-table th:nth-child(2)').click();
258 | Meteor.setTimeout(expectSecondColumnAscending, 0);
259 | });
260 |
261 | var expectFirstColumnDescending = expect(function () {
262 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Nikola Tesla", "first column descending first row");
263 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "Marie Curie", "first column descending second row");
264 | test.equal($('.reactive-table tbody tr:nth-child(4) td:first-child').text(), "Claude Shannon", "first column descending fourth row");
265 |
266 | $('.reactive-table th:nth-child(2)').click();
267 | Meteor.setTimeout(expectSecondColumnDescending, 0);
268 | });
269 |
270 | $('.reactive-table th:first-child').click();
271 | Meteor.setTimeout(expectFirstColumnDescending, 0);
272 | }]);
273 |
274 | testAsyncMulti('Sorting - nested field', [function (test, expect) {
275 | var nestedObjectRows = [
276 | {outerObject: {innerObject: 'a'}},
277 | {outerObject: {innerObject: 'c'}},
278 | {outerObject: {innerObject: 'b'}}
279 | ];
280 | var table = Blaze.renderWithData(
281 | Template.reactiveTable,
282 | {
283 | collection: nestedObjectRows,
284 | fields: [{key: 'outerObject.innerObject', label: 'Column', fn: function (val) { return val; }}]
285 | },
286 | document.body
287 | );
288 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "a", "initial first row");
289 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "b", "initial second row");
290 | test.equal($('.reactive-table tbody tr:nth-child(3) td:first-child').text(), "c", "initial third row");
291 |
292 | var expectDescending = expect(function () {
293 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "c", "descending first row");
294 | test.equal($('.reactive-table tbody tr:nth-child(2) td:first-child').text(), "b", "descending second row");
295 | test.equal($('.reactive-table tbody tr:nth-child(3) td:first-child').text(), "a", "descending fourth row");
296 |
297 | Blaze.remove(table);
298 | });
299 |
300 | $('.reactive-table th:first-child').click();
301 | Meteor.setTimeout(expectDescending, 0);
302 | }]);
--------------------------------------------------------------------------------
/test/test_settings.js:
--------------------------------------------------------------------------------
1 | Tinytest.add('Settings - showFilter', function (test) {
2 | testTable(
3 | {collection: rows},
4 | function () {
5 | test.length($('.reactive-table-filter'), 1, "filter should be visible by default");
6 | }
7 | );
8 |
9 | testTable(
10 | {collection: rows, settings: {showFilter: false}},
11 | function () {
12 | test.length($('.reactive-table-filter'), 0, "filter should be hidden");
13 | }
14 | );
15 |
16 | testTable(
17 | {collection: rows, settings: {showFilter: true}},
18 | function () {
19 | test.length($('.reactive-table-filter'), 1, "filter should be visible");
20 | }
21 | );
22 |
23 | testTable(
24 | {collection: rows, showFilter: false},
25 | function () {
26 | test.length($('.reactive-table-filter'), 0, "filter should be hidden");
27 | }
28 | );
29 | });
30 |
31 | Tinytest.add('Settings - rowsPerPage', function (test) {
32 | testTable(
33 | {collection: rows, settings: {rowsPerPage: 2}},
34 | function () {
35 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
36 | }
37 | );
38 |
39 | testTable(
40 | {collection: rows, settings: {rowsPerPage: 1}},
41 | function () {
42 | test.length($('.reactive-table tbody tr'), 1, "one row should be rendered");
43 | }
44 | );
45 |
46 | testTable(
47 | {collection: rows, rowsPerPage: 2},
48 | function () {
49 | test.length($('.reactive-table tbody tr'), 2, "two rows should be rendered");
50 | }
51 | );
52 | });
53 |
54 | Tinytest.add('Settings - showNavigation', function (test) {
55 | testTable(
56 | {collection: rows},
57 | function () {
58 | test.length($('.reactive-table-navigation'), 1, "navigation should be visible by default");
59 | }
60 | );
61 |
62 | testTable(
63 | {collection: rows, settings: {showNavigation: 'never'}},
64 | function () {
65 | test.length($('.reactive-table-navigation'), 0, "navigation should be hidden");
66 | }
67 | );
68 |
69 | testTable(
70 | {collection: rows, settings: {showNavigation: 'always'}},
71 | function () {
72 | test.length($('.reactive-table-navigation'), 1, "navigation should be visible");
73 | }
74 | );
75 |
76 | testTable(
77 | {collection: rows, settings: {showNavigation: 'auto'}},
78 | function () {
79 | test.length($('.reactive-table-navigation'), 0, "navigation should be hidden");
80 | }
81 | );
82 |
83 | testTable(
84 | {collection: rows, settings: {showNavigation: 'auto', rowsPerPage: 2}},
85 | function () {
86 | test.length($('.reactive-table-navigation'), 1, "navigation should be visible");
87 | }
88 | );
89 |
90 | testTable(
91 | {collection: rows, showNavigation: 'never'},
92 | function () {
93 | test.length($('.reactive-table-navigation'), 0, "navigation should be hidden");
94 | }
95 | );
96 | });
97 |
98 |
99 | Tinytest.add('Settings - showNavigationRowsPerPage', function (test) {
100 | testTable(
101 | {
102 | collection: rows,
103 | settings: {
104 | showNavigation: 'never',
105 | showNavigationRowsPerPage: true
106 | }
107 | },
108 | function () {
109 | test.length($('.reactive-table-navigation'), 0, "navigation should be hidden");
110 | }
111 | );
112 |
113 | testTable(
114 | {
115 | collection: rows,
116 | showNavigation: 'always',
117 | showNavigationRowsPerPage: true
118 | },
119 | function () {
120 | test.length($('.reactive-table-navigation .rows-per-page'), 1, "rows per page should be visible");
121 | }
122 | );
123 |
124 | testTable(
125 | {
126 | collection: rows,
127 | settings: {
128 | showNavigation: 'always',
129 | showNavigationRowsPerPage: false
130 | }
131 | },
132 | function () {
133 | test.length($('.reactive-table-navigation'), 1, "navigation should be visible");
134 | test.length($('.reactive-table-navigation .rows-per-page'), 0, "rows per page should be hidden");
135 | }
136 | );
137 | });
138 |
139 |
140 | Tinytest.add('Settings - showRowCount', function (test) {
141 | testTable(
142 | {
143 | collection: rows,
144 | settings: {
145 | showNavigation: 'always',
146 | showNavigationRowsPerPage: true,
147 | }
148 | },
149 | function () {
150 | test.length($('.reactive-table-navigation .rows-per-page .rows-per-page-count'), 0, "row count should be hidden");
151 | }
152 | );
153 |
154 | testTable(
155 | {
156 | collection: rows,
157 | settings: {
158 | showNavigation: 'always',
159 | showNavigationRowsPerPage: true,
160 | showRowCount: true
161 | }
162 | },
163 | function () {
164 | test.length($('.reactive-table-navigation .rows-per-page .rows-per-page-count'), 1, "row count should be visible");
165 | }
166 | );
167 | });
168 |
169 | Tinytest.add('Settings - useFontAwesome', function (test) {
170 | testTable(
171 | {collection: rows},
172 | function () {
173 | test.length($('.reactive-table-filter .fa'), 0, "font awesome should be off by default");
174 | }
175 | );
176 |
177 | testTable(
178 | {collection: rows, settings: {useFontAwesome: true}},
179 | function () {
180 | test.length($('.reactive-table-filter .fa'), 1, "font awesome should be on");
181 | }
182 | );
183 |
184 | testTable(
185 | {collection: rows, settings: {useFontAwesome: false}},
186 | function () {
187 | test.length($('.reactive-table-filter .fa'), 0, "font awesome should be off");
188 | }
189 | );
190 |
191 | testTable(
192 | {collection: rows, useFontAwesome: true},
193 | function () {
194 | test.length($('.reactive-table-filter .fa'), 1, "font awesome should be on");
195 | }
196 | );
197 |
198 | // simulate fontawesome installation
199 | Package['fortawesome:fontawesome'] = true;
200 |
201 | testTable(
202 | {collection: rows},
203 | function () {
204 | test.length($('.reactive-table-filter .fa'), 1, "font awesome should be on");
205 | }
206 | );
207 |
208 | testTable(
209 | {collection: rows, useFontAwesome: false},
210 | function () {
211 | test.length($('.reactive-table-filter .fa'), 0, "font awesome should be off");
212 | }
213 | );
214 |
215 | delete Package['fortawesome:fontawesome'];
216 | });
217 |
218 | Tinytest.add('Settings - rowClass', function (test) {
219 | testTable(
220 | {collection: rows, settings: {rowClass: 'row-class'}},
221 | function () {
222 | test.length($('.reactive-table tbody tr.row-class'), $('.reactive-table tbody tr').length, "all rows should have the class");
223 | }
224 | );
225 |
226 | testTable(
227 | {collection: rows, rowClass: 'row-class'},
228 | function () {
229 | test.length($('.reactive-table tbody tr.row-class'), $('.reactive-table tbody tr').length, "all rows should have the class");
230 | }
231 | );
232 |
233 | testTable(
234 | {
235 | collection: rows,
236 | settings: {
237 | rowClass: function (row) {
238 | return 'row-class-' + row.score;
239 | }
240 | }
241 | },
242 | function () {
243 | test.length($('.reactive-table tbody tr.row-class-10'), 1, "1 row should have this class");
244 | test.length($('.reactive-table tbody tr.row-class-5'), 3, "3 rows should have this class");
245 | }
246 | );
247 | });
248 |
249 | Tinytest.add('Settings - class', function (test) {
250 | testTable(
251 | {collection: rows},
252 | function () {
253 | test.length($('.reactive-table.table.table-striped.table-hover'), 1, "table should have default classes");
254 | }
255 | );
256 |
257 | testTable(
258 | {collection: rows, class: 'table-class'},
259 | function () {
260 | test.length($('.reactive-table.table-class'), 1, "table should have the class");
261 | }
262 | );
263 |
264 | testTable(
265 | {collection: rows, settings: {class: 'table-class'}},
266 | function () {
267 | test.length($('.reactive-table.table-class'), 1, "table should have the class");
268 | }
269 | );
270 | });
271 |
272 | Tinytest.add('Settings - id', function (test) {
273 | testTable(
274 | {collection: rows},
275 | function () {
276 | test.equal($('.reactive-table').attr('id').slice(0, 15), 'reactive-table-', "table should have default id");
277 | }
278 | );
279 |
280 | testTable(
281 | {collection: rows, id: 'unique-table'},
282 | function () {
283 | test.length($('#unique-table.reactive-table'), 1, "table should have the id");
284 | }
285 | );
286 |
287 | testTable(
288 | {collection: rows, settings: {id: 'unique-table-2'}},
289 | function () {
290 | test.length($('#unique-table-2.reactive-table'), 1, "table should have the id");
291 | }
292 | );
293 | });
294 |
295 | testAsyncMulti('Settings - noDataTmpl', [function (test, expect) {
296 | var collection = new Mongo.Collection();
297 | var id;
298 |
299 | var table = Blaze.renderWithData(
300 | Template.reactiveTable,
301 | {collection: collection, noDataTmpl: Template.noData, fields: ['name', 'score']},
302 | document.body
303 | );
304 |
305 | var expectNoData = expect(function () {
306 | test.length($('.no-data'), 1, "no data template should be rendered");
307 | test.length($('.reactive-table'), 0, "table should not be rendered");
308 |
309 | Blaze.remove(table);
310 | });
311 |
312 | var expectFilteredToNoData = expect(function () {
313 | test.length($('.no-data'), 1, "no data template should be rendered");
314 | test.length($('.reactive-table'), 0, "table should not be rendered");
315 |
316 | collection.remove(id);
317 | Meteor.setTimeout(expectNoData, 0);
318 | })
319 |
320 | var expectTable = expect(function () {
321 | test.length($('.no-data'), 0, "no data template should not be rendered");
322 | test.length($('.reactive-table tbody tr'), 1, "second page should have one rows");
323 |
324 | $('.reactive-table-filter input').val('g');
325 | $('.reactive-table-filter input').trigger('input');
326 | Meteor.setTimeout(expectFilteredToNoData, 1000);
327 | });
328 |
329 | test.length($('.no-data'), 1, "no data template should be rendered");
330 | test.length($('.reactive-table'), 0, "table should not be rendered");
331 |
332 | id = collection.insert({name: 'Ada Lovelace', score: 5});
333 | Meteor.setTimeout(expectTable, 0);
334 | }]);
335 |
336 | testAsyncMulti('Settings - currentPage var updates table', [function (test, expect) {
337 | var page = new ReactiveVar(0);
338 |
339 | var table = Blaze.renderWithData(
340 | Template.reactiveTable,
341 | {collection: rows, settings: {rowsPerPage: 2, currentPage: page}},
342 | document.body
343 | );
344 |
345 | var expectFirstPage = expect(function () {
346 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
347 | test.length($('.reactive-table tbody tr'), 2, "first page should have two rows");
348 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
349 |
350 | Blaze.remove(table);
351 | });
352 |
353 | var expectSecondPage = expect(function () {
354 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
355 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
356 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
357 |
358 | page.set(0);
359 | Meteor.setTimeout(expectFirstPage, 0);
360 | });
361 |
362 | var expectLastPage = expect(function () {
363 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Marie Curie", "should be on last page");
364 | test.length($('.reactive-table tbody tr'), 2, "last page should have two rows");
365 | test.equal($('.reactive-table-navigation .page-number input').val(), "3", "displayed page number should be 3");
366 |
367 | page.set(1);
368 | Meteor.setTimeout(expectSecondPage, 0);
369 | });
370 |
371 | page.set(2);
372 | Meteor.setTimeout(expectLastPage, 0);
373 | }]);
374 |
375 | testAsyncMulti('Settings - currentPage var updates table with server-side collection', [function (test, expect) {
376 | var page = new ReactiveVar(0);
377 |
378 | var table = Blaze.renderWithData(
379 | Template.reactiveTable,
380 | {collection: "collection", fields: ["name"], settings: {rowsPerPage: 2, currentPage: page}},
381 | document.body
382 | );
383 |
384 | var expectFirstPage = expect(function () {
385 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Ada Lovelace", "should be on first page");
386 | test.length($('.reactive-table tbody tr'), 2, "first page should have two rows");
387 | test.equal($('.reactive-table-navigation .page-number input').val(), "1", "displayed page number should be 1");
388 |
389 | Blaze.remove(table);
390 | });
391 |
392 | var expectSecondPage = expect(function () {
393 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Claude Shannon", "should be on the second page");
394 | test.length($('.reactive-table tbody tr'), 2, "second page should have two rows");
395 | test.equal($('.reactive-table-navigation .page-number input').val(), "2", "displayed page number should be 2");
396 |
397 | page.set(0);
398 | Meteor.setTimeout(expectFirstPage, 500);
399 | });
400 |
401 | var expectLastPage = expect(function () {
402 | test.equal($('.reactive-table tbody tr:first-child td:first-child').text(), "Marie Curie", "should be on last page");
403 | test.length($('.reactive-table tbody tr'), 2, "last page should have two rows");
404 | test.equal($('.reactive-table-navigation .page-number input').val(), "3", "displayed page number should be 3");
405 |
406 | page.set(1);
407 | Meteor.setTimeout(expectSecondPage, 500);
408 | });
409 |
410 | page.set(2);
411 | Meteor.setTimeout(expectLastPage, 500);
412 | }]);
413 |
414 | testAsyncMulti('Settings - currentPage var updated from table changes', [function (test, expect) {
415 | var page = new ReactiveVar(0);
416 |
417 | var table = Blaze.renderWithData(
418 | Template.reactiveTable,
419 | {collection: rows, settings: {rowsPerPage: 2, currentPage: page}},
420 | document.body
421 | );
422 |
423 | var expectFirstPage = expect(function () {
424 | test.equal(page.get(), 0, "should be on first page");
425 |
426 | Blaze.remove(table);
427 | });
428 |
429 | var expectSecondPage = expect(function () {
430 | test.equal(page.get(), 1, "should be on second page");
431 |
432 | $('.reactive-table-navigation .page-number input').val("1");
433 | $('.reactive-table-navigation .page-number input').trigger("change");
434 | Meteor.setTimeout(expectFirstPage, 0);
435 | });
436 |
437 | var expectLastPage = expect(function () {
438 | test.equal(page.get(), 2, "should be on last page");
439 |
440 | $('.reactive-table-navigation .page-number input').val('2');
441 | $('.reactive-table-navigation .page-number input').trigger("change");
442 | Meteor.setTimeout(expectSecondPage, 0);
443 | });
444 |
445 | $('.reactive-table-navigation .page-number input').val("3");
446 | $('.reactive-table-navigation .page-number input').trigger("change");
447 | Meteor.setTimeout(expectLastPage, 0);
448 | }]);
449 |
450 | testAsyncMulti('Settings - rowsPerPage var updates table', [function (test, expect) {
451 | var rowsPerPage = new ReactiveVar(5);
452 |
453 | var table = Blaze.renderWithData(
454 | Template.reactiveTable,
455 | {collection: rows, settings: {rowsPerPage: rowsPerPage}},
456 | document.body
457 | );
458 |
459 | var expectThreeRows = expect(function () {
460 | test.length($('.reactive-table tbody tr'), 3, "three rows should be rendered");
461 |
462 | Blaze.remove(table);
463 | });
464 |
465 | var expectFiveRows = expect(function () {
466 | test.length($('.reactive-table tbody tr'), 5, "five rows should be rendered");
467 |
468 | rowsPerPage.set(3);
469 | Meteor.setTimeout(expectThreeRows, 0);
470 | });
471 |
472 | Meteor.setTimeout(expectFiveRows, 0);
473 | }]);
474 |
475 | testAsyncMulti('Settings - rowsPerPage var updates table with server-side collection', [function (test, expect) {
476 | var rowsPerPage = new ReactiveVar(5);
477 |
478 | var table = Blaze.renderWithData(
479 | Template.reactiveTable,
480 | {collection: "collection", fields: ["name"], settings: {rowsPerPage: rowsPerPage}},
481 | document.body
482 | );
483 |
484 | var expectThreeRows = expect(function () {
485 | test.length($('.reactive-table tbody tr'), 3, "three rows should be rendered");
486 |
487 | Blaze.remove(table);
488 | });
489 |
490 | var expectFiveRows = expect(function () {
491 | test.length($('.reactive-table tbody tr'), 5, "five rows should be rendered");
492 |
493 | rowsPerPage.set(3);
494 | Meteor.setTimeout(expectThreeRows, 500);
495 | });
496 |
497 | Meteor.setTimeout(expectFiveRows, 500);
498 | }]);
499 |
500 | testAsyncMulti('Settings - rowsPerPage var updated from table changes', [function (test, expect) {
501 | var rowsPerPage = new ReactiveVar(5);
502 |
503 | var table = Blaze.renderWithData(
504 | Template.reactiveTable,
505 | {collection: rows, settings: {rowsPerPage: rowsPerPage}},
506 | document.body
507 | );
508 |
509 | var expectThreeRows = expect(function () {
510 | test.equal(rowsPerPage.get(), 3, "three rows should be rendered");
511 |
512 | Blaze.remove(table);
513 | });
514 |
515 | $('.reactive-table-navigation .rows-per-page input').val(3);
516 | $('.reactive-table-navigation .rows-per-page input').trigger('change');
517 | Meteor.setTimeout(expectThreeRows, 0);
518 | }]);
519 |
--------------------------------------------------------------------------------