├── .bowerrc
├── .editorconfig
├── .ember-cli
├── .eslintrc.js
├── .gitignore
├── .jscsrc
├── .npmignore
├── .travis.yml
├── .watchmanconfig
├── LICENSE
├── LICENSE.md
├── README.md
├── addon
├── .gitkeep
├── components
│ ├── each-keys.js
│ ├── em-data-value.js
│ ├── sf-em-data.js
│ ├── sf-filter.js
│ ├── sf-headings.js
│ ├── sf-rows.js
│ ├── sf-table.js
│ └── sort-filter-table.js
├── helpers
│ ├── hash-contains.js
│ ├── is-image.js
│ ├── is-number.js
│ ├── is-object.js
│ ├── is-primitive.js
│ └── is-private-key.js
├── templates
│ └── components
│ │ ├── each-keys.hbs
│ │ ├── em-data-value.hbs
│ │ ├── sf-em-data.hbs
│ │ ├── sf-filter.hbs
│ │ ├── sf-headings.hbs
│ │ ├── sf-rows.hbs
│ │ ├── sf-table.hbs
│ │ └── sort-filter-table.hbs
└── utils.js
├── app
├── .gitkeep
├── components
│ ├── each-keys.js
│ ├── em-data-value.js
│ ├── sf-em-data.js
│ ├── sf-filter.js
│ ├── sf-headings.js
│ ├── sf-rows.js
│ ├── sf-table.js
│ └── sort-filter-table.js
└── helpers
│ ├── hash-contains.js
│ ├── is-image.js
│ ├── is-number.js
│ ├── is-object.js
│ ├── is-primitive.js
│ └── is-private-key.js
├── blueprints
└── ember-sort-filter-table
│ └── index.js
├── bower.json
├── config
├── deploy.js
├── ember-try.js
└── environment.js
├── ember-cli-build.js
├── index.js
├── package.json
├── testem.js
├── tests
├── .eslintrc.js
├── acceptance
│ └── general-test.js
├── dummy
│ ├── app
│ │ ├── adapters
│ │ │ └── application.js
│ │ ├── app.js
│ │ ├── components
│ │ │ ├── .gitkeep
│ │ │ ├── demo-block.js
│ │ │ ├── demo-ember-data.js
│ │ │ ├── demo-inline.js
│ │ │ ├── demo-nav.js
│ │ │ ├── ui-hero.js
│ │ │ └── ui-tabs.js
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ └── application.js
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── index.html
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ └── demo.js
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ └── application.js
│ │ ├── serializers
│ │ │ └── application.js
│ │ ├── styles
│ │ │ └── app.scss
│ │ └── templates
│ │ │ ├── application.hbs
│ │ │ └── components
│ │ │ ├── .gitkeep
│ │ │ ├── demo-block.hbs
│ │ │ ├── demo-ember-data.hbs
│ │ │ ├── demo-inline.hbs
│ │ │ ├── demo-nav.hbs
│ │ │ ├── ui-hero.hbs
│ │ │ └── ui-tabs.hbs
│ ├── config
│ │ ├── environment.js
│ │ └── targets.js
│ └── public
│ │ ├── crossdomain.xml
│ │ ├── json
│ │ └── demo.json
│ │ └── robots.txt
├── helpers
│ ├── destroy-app.js
│ ├── module-for-acceptance.js
│ ├── owner.js
│ ├── resolver.js
│ ├── start-app.js
│ └── store.js
├── index.html
├── integration
│ ├── .gitkeep
│ └── components
│ │ ├── each-keys-test.js
│ │ ├── em-data-value-test.js
│ │ ├── sf-em-data-test.js
│ │ ├── sf-filter-test.js
│ │ ├── sf-headings-test.js
│ │ ├── sf-rows-test.js
│ │ ├── sf-table-test.js
│ │ └── sort-filter-table-test.js
├── pages
│ ├── application.js
│ └── components
│ │ └── demo-nav.js
├── test-helper.js
└── unit
│ ├── .gitkeep
│ └── helpers
│ ├── hash-contains-test.js
│ ├── is-image-test.js
│ ├── is-number-test.js
│ ├── is-object-test.js
│ ├── is-primitive-test.js
│ └── is-private-key-test.js
├── vendor
└── .gitkeep
└── yarn.lock
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.hbs]
17 | insert_final_newline = false
18 |
19 | [*.{diff,md}]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: 'babel-eslint',
4 | parserOptions: {
5 | ecmaVersion: 2017,
6 | sourceType: 'module'
7 | },
8 | extends: 'eslint:recommended',
9 | env: {
10 | browser: true
11 | },
12 | rules: {
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log*
17 | yarn-error.log
18 | testem.log
19 |
20 | # ember-try
21 | .node_modules.ember-try/
22 | bower.json.ember-try
23 | package.json.ember-try
24 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "ember-suave"
3 | }
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .eslintrc.js
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | # we recommend testing addons with the same minimum supported node version as Ember CLI
5 | # so that your addon works for all apps
6 | - "4"
7 |
8 | sudo: false
9 |
10 | cache:
11 | yarn: true
12 |
13 | env:
14 | # we recommend new addons test the current and previous LTS
15 | # as well as latest stable release (bonus points to beta/canary)
16 | - EMBER_TRY_SCENARIO=ember-lts-2.8
17 | - EMBER_TRY_SCENARIO=ember-lts-2.12
18 | - EMBER_TRY_SCENARIO=ember-release
19 | - EMBER_TRY_SCENARIO=ember-beta
20 | - EMBER_TRY_SCENARIO=ember-canary
21 | - EMBER_TRY_SCENARIO=ember-default
22 |
23 | matrix:
24 | fast_finish: true
25 | allow_failures:
26 | - env: EMBER_TRY_SCENARIO=ember-canary
27 |
28 | before_install:
29 | - curl -o- -L https://yarnpkg.com/install.sh | bash
30 | - export PATH=$HOME/.yarn/bin:$PATH
31 | - yarn global add phantomjs-prebuilt
32 | - phantomjs --version
33 |
34 | install:
35 | - yarn install --no-lockfile --non-interactive
36 |
37 | script:
38 | # Usually, it's ok to finish the test scenario without reverting
39 | # to the addon's original dependency state, skipping "cleanup".
40 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup
41 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/ember-sort-filter-table)
2 | [](https://travis-ci.org/crodriguez1a/ember-sort-filter-table)
3 | [](https://emberobserver.com/addons/ember-sort-filter-table)
4 |
5 | # ember-sort-filter-table
6 |
7 | A table add-on for Ember-CLI with sorting and filtering. [Full Documentation & Demo](http://crodriguez1a.github.io/ember-sort-filter-table)
8 |
9 | ## Installation
10 | ember install ember-sort-filter-table
11 |
12 | ## Usage
13 |
14 | Simply pass an array of objects as the **table** parameter to the component. Use the key **rows** to define your array as follows:
15 |
16 | /**
17 | .js
18 | Defining a model that my table will display
19 | */
20 | let model = {
21 | rows: [
22 | {
23 | Tables: 'zebra stripes',
24 | Are: 'are neat',
25 | Cool: '$1'
26 | }
27 | ]
28 | };
29 |
30 | ...
31 |
32 | {{! some-template.hbs }}
33 | {{component "sort-filter-table" table=model}}
34 |
35 | The addon will assemble the table headers from the object keys and display a table like this:
36 |
37 | | [Tables](#) | [Are](#) | [Cool](#) |
38 | | ------------- |:-------------:| -----:|
39 | | zebra stripes | are neat | $1 |
40 |
41 | If your model has properties that should not be displayed in the table, use an underscore to mark that property as private.
42 |
43 | let model = {
44 | rows:[
45 | {
46 | name: 'Carlos Rodriguez',
47 | github_id: 'crodriguez1a',
48 | _writesCode: true
49 | }
50 | ]
51 | };
52 |
53 | The model above would display like this:
54 |
55 | | [name](#) | [github id](#) |
56 | | ------------- |:-------------:|
57 | | Carlos Rodriguez | crodriguez1a |
58 |
59 | ## Contribute
60 | Collaboration is welcome and greatly appreciated. To collaborate on this project, follow the instructions that follow.
61 |
62 | ## Installation
63 |
64 | * `git clone` this repository
65 | * `npm install`
66 | * `bower install`
67 |
68 | ## Running
69 |
70 | * `ember server`
71 | * Visit your app at http://localhost:4200.
72 |
73 | ## Running Tests
74 |
75 | * `ember test`
76 | * `ember test --server`
77 |
78 | ## Building
79 |
80 | * `ember build`
81 |
82 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).
83 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/addon/.gitkeep
--------------------------------------------------------------------------------
/addon/components/each-keys.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import layout from '../templates/components/each-keys';
3 | import { computed } from 'ember-decorators/object';
4 |
5 | /**
6 | Credit: rwjblue
7 | */
8 |
9 | const { keys } = Object;
10 |
11 | /**
12 | Each Keys component
13 |
14 | @class EachKeys
15 | @extends Ember.Component
16 | */
17 | export default Component.extend({
18 | layout,
19 | tagName: '',
20 | @computed('object')
21 | items(object) {
22 | if (object) {
23 | let keysArr = keys(object);
24 | return keysArr.map((key) => {
25 | return {
26 | key,
27 | value: object[key]
28 | };
29 | });
30 | }
31 | }
32 | });
33 |
--------------------------------------------------------------------------------
/addon/components/em-data-value.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import layout from '../templates/components/em-data-value';
3 |
4 | export default Component.extend({
5 | layout
6 | });
7 |
--------------------------------------------------------------------------------
/addon/components/sf-em-data.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { get } from '@ember/object';
3 | import layout from '../templates/components/sf-em-data';
4 | import { computed } from 'ember-decorators/object';
5 | import { assign } from '@ember/polyfills';
6 |
7 | const { keys } = Object;
8 |
9 | export default Component.extend({
10 | layout,
11 | tagName: '',
12 |
13 | /**
14 | Signal if nested arrays or objects should be displayed as a list
15 |
16 | @property shouldListNested
17 | @default true
18 | @type {Bool}
19 | @public
20 | */
21 | shouldListNested: true,
22 |
23 | /**
24 | Extract plain object from ember data internal model
25 |
26 | @method _extractPojo
27 | @param store {Service}
28 | @private
29 | */
30 | _extractPojo(store) {
31 | return store.toArray().map((item) => get(item, '_internalModel.__data'));
32 | },
33 |
34 | /**
35 | Filter out nodes when a corresponding header was not provided
36 | headings ['name', 'address']
37 | data [{ name, address, phone }, {...]
38 | result [ {name, address }, {... ]
39 |
40 | @method _filterByHeadings
41 | @param arr {Array}
42 | @param headings {Array}
43 | @returns {Array}
44 | @private
45 | */
46 | _filterByHeadings(arr, headings) {
47 | return arr.map((obj) => {
48 | // prevent any mutation on the original object
49 | let _obj = assign({}, obj);
50 | // when headings and keys match, return early
51 | if (headings.join() === keys(_obj).join()) {
52 | return _obj
53 | }
54 | // otherwise, remove nodes where headings array does not include key
55 | for (let key in _obj) {
56 | if (!headings.includes(key)) {
57 | delete _obj[key]
58 | }
59 | }
60 | return _obj;
61 | });
62 | },
63 | /**
64 | Re-arranges records in the same order the headings suggest
65 | headings suggest ...["foo", "baz"]
66 | re-arrange records from... [{baz:"value", foo:"value"}] to [{foo:"value", baz:"value"}]
67 |
68 | @method _arrangeByHeadings
69 | @param arr {Array} POJA of ember data records
70 | @param headings {Array} List of headings
71 | @returns {Array}
72 | @private
73 | */
74 | _arrangeByHeadings(arr, headings) {
75 | return arr.map((obj) => {
76 | return headings.reduce((hash, heading) => {
77 | hash[heading] = obj[heading];
78 | return hash;
79 | }, {});
80 | });
81 | },
82 |
83 | /**
84 | Convert ember data internal model into plain array
85 |
86 | @property listNested
87 | @default true
88 | @type {Bool}
89 | @public
90 | */
91 | @computed('store')
92 | poja(store) {
93 | let objArr = [];
94 | let headings = get(this, 'group.groupHeadings');
95 |
96 | if (store) {
97 | objArr = this._extractPojo(store);
98 | if (headings && headings.length) {
99 | objArr = this._filterByHeadings(objArr, headings);
100 | objArr = this._arrangeByHeadings(objArr, headings);
101 | }
102 | }
103 | return objArr.map((item) => assign({}, item));
104 | }
105 | });
106 |
--------------------------------------------------------------------------------
/addon/components/sf-filter.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { set } from '@ember/object';
3 | import layout from '../templates/components/sf-filter';
4 |
5 | export default Component.extend({
6 | layout,
7 | tagName: 'span',
8 | classNames: ['sf-table--filter'],
9 | placeholder: 'Filter',
10 | actions: {
11 | /**
12 | * Clear the current filter query
13 | *
14 | * @method clearField
15 | * @private
16 | */
17 | clearField() {
18 | set(this, 'group.groupQuery', null);
19 | }
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/addon/components/sf-headings.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { get, set } from '@ember/object';
3 | import layout from '../templates/components/sf-headings';
4 |
5 | export default Component.extend({
6 | layout,
7 | tagName: '',
8 | init() {
9 | this._super(...arguments);
10 | this._syncWithGroup();
11 | },
12 |
13 | actions: {
14 | /**
15 | * Updates sort key and direction
16 | *
17 | * @method sort
18 | * @param key {String}
19 | * @public
20 | */
21 | sort(key) {
22 | // Reference current direction
23 | let dir = get(this, 'group.groupSortDirection');
24 | // Toggle direction
25 | set(this, 'group.groupSortDirection', dir === 'asc' ? 'desc' : 'asc');
26 | // Update sort key
27 | set(this, 'group.groupSortKey', key);
28 | }
29 | },
30 |
31 | /**
32 | Provide headings to group
33 |
34 | @method _syncWithGroup
35 | @private
36 | */
37 | _syncWithGroup() {
38 | if (get(this, 'headings')) {
39 | set(this, 'group.groupHeadings', get(this, 'headings').getEach('key'));
40 | }
41 | }
42 | });
43 |
--------------------------------------------------------------------------------
/addon/components/sf-rows.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import layout from '../templates/components/sf-rows';
3 | import { computed } from 'ember-decorators/object';
4 | import { assign } from '@ember/polyfills';
5 |
6 | export default Component.extend({
7 | layout,
8 | tagName: '',
9 |
10 | /**
11 | Create a safe copy of the hash provided
12 |
13 | @property _rows
14 | @private
15 | @returns {Array}
16 | */
17 | @computed('rows')
18 | _rows(rows) {
19 | if (rows && rows.length) {
20 | return rows.map((item) => assign({}, item));
21 | } else {
22 | return [];
23 | }
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/addon/components/sf-table.js:
--------------------------------------------------------------------------------
1 | import SortFilterTable from 'ember-sort-filter-table/components/sort-filter-table';
2 |
3 | /**
4 | * Less verbose alias for 'sort-filter-table' component
5 | *
6 | * @class sfTable
7 | * @extends SortFilterTable
8 | */
9 |
10 | export default SortFilterTable.extend({
11 | layoutName: 'components/sort-filter-table'
12 | });
13 |
--------------------------------------------------------------------------------
/addon/components/sort-filter-table.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import EmberObject, { get, set } from '@ember/object';
3 | import { A } from '@ember/array'
4 | import layout from '../templates/components/sort-filter-table';
5 | import { computed } from 'ember-decorators/object';
6 | import { alias } from 'ember-decorators/object/computed';
7 | import { isPrivateKey, primitiveKeys } from '../utils';
8 |
9 | const { values } = Object;
10 |
11 | /**
12 | Sortable Filterable Table Component
13 |
14 | @class SortFilterTable
15 | @extends Ember.Component
16 | */
17 | export default Component.extend({
18 | layout,
19 | tagName: 'table',
20 | classNames: ['sort-filter-table table'],
21 |
22 | /**
23 | Config: Signal if filter input field should be included
24 |
25 | @property filterable
26 | @type Bool
27 | @public
28 | */
29 | filterable: true,
30 |
31 | /**
32 | A placeholder to display in the filter input field
33 |
34 | @property filterPlaceholder
35 | @public
36 | @type String
37 | */
38 | filterPlaceholder: 'filter',
39 |
40 | /**
41 | A query or filter provided by input
42 |
43 | @property filter
44 | @public
45 | @type String
46 | */
47 | filter: '',
48 |
49 | /**
50 | Only display rows with primitive values and public keys
51 |
52 | @property rows
53 | @private
54 | @type Array
55 | */
56 | @computed('table.rows', 'filter')
57 | rows(rows, filterQuery) {
58 | return A(rows.filter((row) => {
59 | let rowValues = values(row);
60 | let filterExp = new RegExp(filterQuery, 'ig');
61 | set(row, '_filtered', !(filterExp).test((rowValues.join(',')).toLowerCase()));
62 | return row;
63 | }));
64 | },
65 |
66 | /**
67 | Extract column headings from keys
68 |
69 | @property headings
70 | @type Array
71 | @public
72 | */
73 | @computed('rows')
74 | headings(rows) {
75 | return A(primitiveKeys(rows[0]));
76 | },
77 |
78 | /**
79 | Assemble pretty labels from column headings/keys
80 |
81 | @property labels
82 | @type Array
83 | @public
84 |
85 | */
86 | @computed('headings')
87 | labels(headings) {
88 | return A(headings.map((item) => {
89 | return EmberObject.create({
90 | _key: item,
91 | name: this._handleSeparators(item),
92 | _sortClass: 'asc',
93 | _isPrivate: isPrivateKey(item)
94 | });
95 | }));
96 | },
97 |
98 | /**
99 | Store separator for headings
100 |
101 | @property _separator
102 | @type String
103 | @private
104 | */
105 | _separator: null,
106 |
107 | /**
108 | Replaces hyphens or underscores with spaces (used to prettify headings)
109 |
110 | @method _handleSeparators
111 | @private
112 | */
113 | _handleSeparators(str) {
114 | let isPrivate = str[0] === '_';
115 |
116 | if (!isPrivate) {
117 | let separator = str.match(/[-\s_]/g);
118 | let camelCase = str.match(/[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*/);
119 | separator = camelCase || separator;
120 |
121 | if (separator && separator.length) {
122 | // save current separator
123 | set(this, '_separator', separator[0]);
124 |
125 | // return label without separator for display
126 | return str.replace(new RegExp(separator[0], 'g'), ' ');
127 | } else {
128 | // pass through
129 | return str;
130 | }
131 | }
132 |
133 | },
134 |
135 | /**
136 | Label to determine sort order
137 |
138 | @property sortLabel
139 | @type String
140 | @private
141 | */
142 | @computed('headings')
143 | sortLabel(headings) {
144 | return headings[0];
145 | },
146 |
147 | /**
148 | Signal wether current sort param is asc or desc
149 |
150 | @property isAscending
151 | @type Bool
152 | @private
153 | */
154 | isAscending: true,
155 |
156 | /**
157 | Compute when sort should reverse order
158 |
159 | @property _direction
160 | @private
161 | @type String
162 | */
163 | @computed('isAscending')
164 | _direction(isAscending) {
165 | return isAscending ? '' : ':desc';
166 | },
167 |
168 | /**
169 | Calculated total number of columns (colspan)
170 |
171 | @property totalColumns
172 | @type Number
173 | @public
174 | */
175 | @alias('labels.length') totalColumns: 0,
176 |
177 | /**
178 | * Key to sorty by
179 | *
180 | * @property groupSortKey
181 | * @type String
182 | * @public
183 | */
184 | groupSortKey: 'none',
185 |
186 | /**
187 | * Sort direction (asc/desc)
188 | *
189 | * @property groupSortDirection
190 | * @type String
191 | * @public
192 | */
193 | groupSortDirection: 'desc',
194 |
195 | actions: {
196 |
197 | /**
198 | Apply label as sort
199 |
200 | @method sortByLabel
201 | @private
202 | */
203 | sortByLabel(label) {
204 | let sortName = get(label, 'name').replace(/ /g, get(this, '_separator'));
205 |
206 | // toggle sort direction
207 | this.toggleProperty('isAscending');
208 |
209 | // provide direction class
210 | set(label, '_sortClass', get(this, 'isAscending') ? 'asc' : 'desc');
211 |
212 | // update sort label prop
213 | set(this, 'sortLabel', sortName);
214 | },
215 |
216 | /**
217 | Send values up to actions
218 |
219 | @method sendEditAction
220 | @private
221 | */
222 | sendEditAction(row, key, value, actionType) {
223 | let action = get(this, actionType);
224 |
225 | set(row, '_editingRow', null);
226 | if (action) {
227 | action({
228 | row,
229 | key,
230 | value
231 | });
232 | }
233 | },
234 |
235 | /**
236 | Display input field for editing
237 |
238 | @method
239 | @private
240 | */
241 | editValue(row, key) {
242 | get(this, 'rows').setEach('_editingRow', null);
243 | set(row, '_editingRow', key);
244 | },
245 | }
246 | });
247 |
--------------------------------------------------------------------------------
/addon/helpers/hash-contains.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 |
3 | const {
4 | stringify
5 | } = JSON;
6 |
7 | export function hashContains(params) {
8 | // Stringify the hash
9 | let _hash = params[0];
10 | let str = stringify(_hash);
11 |
12 | // Convert the query to a regex
13 | let query = params[1] || '';
14 | let rgx = new RegExp(query, 'i');
15 |
16 | // Returns truthy when str was found in hash
17 | return (rgx).test(str);
18 | }
19 |
20 | export default helper(hashContains);
21 |
--------------------------------------------------------------------------------
/addon/helpers/is-image.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 |
3 | export function isImage(params/*, hash*/) {
4 | let img = params[0];
5 | return img && (/([^\s]+(\.(jpg|png|gif|bmp))$)/gm).test(img);
6 | }
7 |
8 | export default helper(isImage);
9 |
--------------------------------------------------------------------------------
/addon/helpers/is-number.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 |
3 | export function isNumber(params/*, hash*/) {
4 | let val = params[0];
5 | // See if this value only contains numbers (e.g., "12" but not "12 things")
6 | return val && (/^[0-9]*$/).test(val);
7 | }
8 |
9 | export default helper(isNumber);
10 |
--------------------------------------------------------------------------------
/addon/helpers/is-object.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 |
3 | export function isObject(params/*, hash*/) {
4 | let obj = params[0];
5 | return obj && obj.toString() === '[object Object]';
6 | }
7 |
8 | export default helper(isObject);
9 |
--------------------------------------------------------------------------------
/addon/helpers/is-primitive.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 |
3 | /**
4 | Signal if a value is primitive type (exluding null and undefined)
5 |
6 | @helper isPrimitive
7 | @private
8 | @returns Bool
9 | */
10 | export function isPrimitive(params/*, hash*/) {
11 | let val = params[0];
12 | return typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number';
13 | }
14 |
15 | export default helper(isPrimitive);
16 |
--------------------------------------------------------------------------------
/addon/helpers/is-private-key.js:
--------------------------------------------------------------------------------
1 | import { helper } from "@ember/component/helper";
2 | import { isPrivateKey as _isPrivate } from '../utils';
3 |
4 | /**
5 | Object keys with a leading underscore should be designated as private
6 |
7 | @helper isPrivateKey
8 | @private
9 | @returns Bool
10 | */
11 | export function isPrivateKey(params/*, hash*/) {
12 | let key = params[0];
13 | return key ? _isPrivate(key) : false ;
14 | }
15 |
16 | export default helper(isPrivateKey);
17 |
--------------------------------------------------------------------------------
/addon/templates/components/each-keys.hbs:
--------------------------------------------------------------------------------
1 | {{#if items}}
2 | {{#each items as |item|}}
3 | {{yield item.key item.value}}
4 | {{/each}}
5 | {{else}}
6 | {{yield}}
7 | {{/if}}
8 |
--------------------------------------------------------------------------------
/addon/templates/components/em-data-value.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{yield}}
3 | {{else}}
4 | {{#if (is-image value)}}
5 |
6 | {{else}}
7 | {{html-safe value}}
8 | {{/if}}
9 | {{/if}}
10 |
--------------------------------------------------------------------------------
/addon/templates/components/sf-em-data.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{yield this}}
3 | {{else}}
4 | {{#each (sort-by (concat group.groupSortKey ':' group.groupSortDirection) poja) as |record|}}
5 | {{#if (hash-contains record group.groupQuery)}}
6 |
7 | {{#each-keys object=record as | key value|}}
8 | {{#unless (is-private-key key)}}
9 |
10 | {{#if shouldListNested}}
11 | {{#if (is-array value)}}
12 |
13 | {{#each value as |v|}}
14 | {{em-data-value value=v}}
15 | {{/each}}
16 |
17 | {{else if (is-object value)}}
18 |
19 | {{#each-keys object=value as | k v |}}
20 | {{em-data-value value=v}}
21 | {{/each-keys}}
22 |
23 | {{else}}
24 | {{em-data-value value=value}}
25 | {{/if}}
26 | {{else}}
27 | {{em-data-value value=value}}
28 | {{/if}}
29 |
30 | {{/unless}}
31 | {{/each-keys}}
32 |
33 | {{/if}}
34 | {{/each}}
35 | {{/if}}
36 |
--------------------------------------------------------------------------------
/addon/templates/components/sf-filter.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{yield}}
3 | {{else}}
4 |
5 | {{#if (and withClearField group.groupQuery)}}
6 |
7 | {{/if}}
8 | {{/if}}
9 |
--------------------------------------------------------------------------------
/addon/templates/components/sf-headings.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{yield}}
3 | {{else}}
4 | {{#each headings as |heading|}}
5 |
10 | {{/each}}
11 | {{/if}}
12 |
--------------------------------------------------------------------------------
/addon/templates/components/sf-rows.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{yield}}
3 | {{else}}
4 | {{#each (sort-by (concat group.groupSortKey ':' group.groupSortDirection) _rows) as |item|}}
5 | {{#if (hash-contains item group.groupQuery)}}
6 |
7 | {{#each-keys object=item as | key value|}}
8 | {{#unless (is-private-key key)}}
9 | {{html-safe value}}
10 | {{/unless}}
11 | {{/each-keys}}
12 |
13 | {{/if}}
14 | {{/each}}
15 | {{/if}}
16 |
--------------------------------------------------------------------------------
/addon/templates/components/sf-table.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
2 |
--------------------------------------------------------------------------------
/addon/templates/components/sort-filter-table.hbs:
--------------------------------------------------------------------------------
1 | {{#if hasBlock}}
2 | {{!Block syntax}}
3 | {{yield (hash
4 | headings=(component 'sf-headings' group=this)
5 | filter=(component 'sf-filter' group=this)
6 | rows=(component 'sf-rows' group=this)
7 | data=(component 'sf-em-data' group=this)
8 | )
9 | }}
10 | {{else}}
11 | {{!ember data}}
12 | {{#if store}}
13 | {{!TODO Inline ember data support}}
14 | {{else if table}}
15 |
16 | {{!Labels}}
17 |
18 | {{#each (filter-by "_isPrivate" false labels) as |label|}}
19 |
22 | {{/each}}
23 |
24 |
25 | {{!Filterable}}
26 | {{#if filterable}}
27 |
28 |
29 | {{input class="input" value=filter placeholder=filterPlaceholder}}
30 |
31 |
32 | {{/if}}
33 |
34 |
35 |
36 | {{!Sort by label }}
37 | {{#each (sort-by (concat sortLabel _direction) rows) as |row|}}
38 | {{#unless row._filtered}}
39 |
40 | {{#each-keys object=row as |key value|}}
41 | {{#unless (is-private-key key)}}
42 | {{#if (is-primitive value)}}
43 |
44 | {{#if editable}}
45 | {{!Can edit}}
46 | {{#if (eq row._editingRow key)}}
47 |
48 |
49 | {{input class="input" placeholder=value value=value}}
50 |
51 |
52 | Cancel
53 | Edit
54 |
55 |
56 | {{else}}
57 | {{html-safe value}}
58 | {{/if}}
59 | {{else}}
60 | {{!Cannot edit}}
61 | {{html-safe value}}
62 | {{/if}}
63 |
64 | {{/if}}
65 | {{/unless}}
66 | {{/each-keys}}
67 |
68 | {{/unless}}
69 | {{/each}}
70 |
71 | {{else}}
72 | {{!No model was provided}}
73 |
74 |
75 | Empty
76 |
77 |
78 |
79 |
80 | n/a
81 |
82 |
83 | {{/if}}
84 | {{/if}}
85 |
--------------------------------------------------------------------------------
/addon/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | Object keys with a leading underscore should be designated as private
3 |
4 | @method isPrivateKey
5 | @private
6 | @returns Bool
7 | */
8 | export const isPrivateKey = (key) => {
9 | return (/_/).test(key[0]);
10 | };
11 |
12 | /**
13 | Extract only keys that point to primitive types
14 | (exluding null and undefined)
15 |
16 | @method primitiveKeys
17 | @private
18 | @returns Array
19 | */
20 | export const primitiveKeys = (obj) => {
21 | let arr = [];
22 | for (let i in obj) {
23 | if (obj.hasOwnProperty(i)) {
24 | let val = obj[i];
25 | if (typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number') {
26 | arr.push(i);
27 | }
28 | }
29 | }
30 | return arr;
31 | };
32 |
33 | /**
34 | Object values polyfill
35 | https://github.com/tc39/proposal-object-values-entries/blob/master/polyfill.js
36 | */
37 | const reduce = Function.bind.call(Function.call, Array.prototype.reduce);
38 | const isEnumerable = Function.bind.call(Function.call, Object.prototype.propertyIsEnumerable);
39 | const concat = Function.bind.call(Function.call, Array.prototype.concat);
40 | const keys = Object.keys;
41 |
42 | if (!Object.values) {
43 | Object.values = function values(O) {
44 | return reduce(keys(O), (v, k) => concat(v, typeof k === 'string' && isEnumerable(O, k) ? [O[k]] : []), []);
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/app/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/app/.gitkeep
--------------------------------------------------------------------------------
/app/components/each-keys.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/each-keys';
2 |
--------------------------------------------------------------------------------
/app/components/em-data-value.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/em-data-value';
--------------------------------------------------------------------------------
/app/components/sf-em-data.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sf-em-data';
--------------------------------------------------------------------------------
/app/components/sf-filter.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sf-filter';
--------------------------------------------------------------------------------
/app/components/sf-headings.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sf-headings';
2 |
--------------------------------------------------------------------------------
/app/components/sf-rows.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sf-rows';
--------------------------------------------------------------------------------
/app/components/sf-table.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sf-table';
--------------------------------------------------------------------------------
/app/components/sort-filter-table.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-sort-filter-table/components/sort-filter-table';
2 |
--------------------------------------------------------------------------------
/app/helpers/hash-contains.js:
--------------------------------------------------------------------------------
1 | export { default, hashContains } from 'ember-sort-filter-table/helpers/hash-contains';
2 |
--------------------------------------------------------------------------------
/app/helpers/is-image.js:
--------------------------------------------------------------------------------
1 | export { default, isImage } from 'ember-sort-filter-table/helpers/is-image';
2 |
--------------------------------------------------------------------------------
/app/helpers/is-number.js:
--------------------------------------------------------------------------------
1 | export { default, isNumber } from 'ember-sort-filter-table/helpers/is-number';
2 |
--------------------------------------------------------------------------------
/app/helpers/is-object.js:
--------------------------------------------------------------------------------
1 | export { default, isObject } from 'ember-sort-filter-table/helpers/is-object';
2 |
--------------------------------------------------------------------------------
/app/helpers/is-primitive.js:
--------------------------------------------------------------------------------
1 | export { default, isPrimitive } from 'ember-sort-filter-table/helpers/is-primitive';
2 |
--------------------------------------------------------------------------------
/app/helpers/is-private-key.js:
--------------------------------------------------------------------------------
1 | export { default, isPrivateKey } from 'ember-sort-filter-table/helpers/is-private-key';
2 |
--------------------------------------------------------------------------------
/blueprints/ember-sort-filter-table/index.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | description: 'Install dependencies',
4 |
5 | normalizeEntityName: function(entityName) {
6 | return entityName;
7 | },
8 |
9 | /*
10 | afterInstall: function(options) {
11 |
12 | }
13 | */
14 | };
15 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-sort-filter-table"
3 | }
4 |
--------------------------------------------------------------------------------
/config/deploy.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(deployTarget) {
4 | var ENV = {
5 | build: {}
6 | // include other plugin configuration that applies to all deploy targets here
7 | };
8 |
9 | if (deployTarget === 'development') {
10 | ENV.build.environment = 'development';
11 | // configure other plugins for development deploy target here
12 | }
13 |
14 | if (deployTarget === 'staging') {
15 | ENV.build.environment = 'production';
16 | // configure other plugins for staging deploy target here
17 | }
18 |
19 | if (deployTarget === 'production') {
20 | ENV.build.environment = 'production';
21 | // configure other plugins for production deploy target here
22 | }
23 |
24 | // Note: if you need to build some configuration asynchronously, you can return
25 | // a promise that resolves with the ENV object instead of returning the
26 | // ENV object synchronously.
27 | return ENV;
28 | };
29 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | useYarn: true,
4 | scenarios: [
5 | {
6 | name: 'ember-lts-2.8',
7 | bower: {
8 | dependencies: {
9 | 'ember': 'components/ember#lts-2-8'
10 | },
11 | resolutions: {
12 | 'ember': 'lts-2-8'
13 | }
14 | },
15 | npm: {
16 | devDependencies: {
17 | 'ember-source': null
18 | }
19 | }
20 | },
21 | {
22 | name: 'ember-lts-2.12',
23 | npm: {
24 | devDependencies: {
25 | 'ember-source': '~2.12.0'
26 | }
27 | }
28 | },
29 | {
30 | name: 'ember-release',
31 | bower: {
32 | dependencies: {
33 | 'ember': 'components/ember#release'
34 | },
35 | resolutions: {
36 | 'ember': 'release'
37 | }
38 | },
39 | npm: {
40 | devDependencies: {
41 | 'ember-source': null
42 | }
43 | }
44 | },
45 | {
46 | name: 'ember-beta',
47 | bower: {
48 | dependencies: {
49 | 'ember': 'components/ember#beta'
50 | },
51 | resolutions: {
52 | 'ember': 'beta'
53 | }
54 | },
55 | npm: {
56 | devDependencies: {
57 | 'ember-source': null
58 | }
59 | }
60 | },
61 | {
62 | name: 'ember-canary',
63 | bower: {
64 | dependencies: {
65 | 'ember': 'components/ember#canary'
66 | },
67 | resolutions: {
68 | 'ember': 'canary'
69 | }
70 | },
71 | npm: {
72 | devDependencies: {
73 | 'ember-source': null
74 | }
75 | }
76 | },
77 | {
78 | name: 'ember-default',
79 | npm: {
80 | devDependencies: {}
81 | }
82 | }
83 | ]
84 | };
85 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
5 |
6 | module.exports = function(defaults) {
7 | let app = new EmberAddon(defaults, {
8 | 'ember-cli-babel': {
9 | includePolyfill: true
10 | },
11 | sassOptions: {
12 | includePaths: [
13 | 'node_modules/bulma'
14 | ]
15 | }
16 | });
17 |
18 | /*
19 | This build file specifies the options for the dummy test app of this
20 | addon, located in `/tests/dummy`
21 | This build file does *not* influence how the addon or the app using it
22 | behave. You most likely want to be modifying `./index.js` or app's build file
23 | */
24 |
25 | return app.toTree();
26 | };
27 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'ember-sort-filter-table',
6 | init: function(/*(app*/) {
7 | this._super.init && this._super.init.apply(this, arguments);
8 |
9 | this.options = this.options || {};
10 | this.options.babel = this.options.babel || {};
11 | this.options.babel.plugins = this.options.babel.plugins || [];
12 |
13 | if (this.options.babel.plugins.indexOf('transform-decorators-legacy') === -1) {
14 | this.options.babel.plugins.push('transform-decorators-legacy');
15 | }
16 |
17 | if (this.options.babel.plugins.indexOf('transform-class-properties') === -1) {
18 | this.options.babel.plugins.push('transform-class-properties');
19 | }
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-sort-filter-table",
3 | "version": "0.3.4",
4 | "description": "A sortable/filterable table add-on for your ember application",
5 | "keywords": [
6 | "ember-addon"
7 | ],
8 | "license": "MIT",
9 | "author": "@crodriguez1a",
10 | "directories": {
11 | "doc": "doc",
12 | "test": "tests"
13 | },
14 | "repository": "https://github.com/crodriguez1a/ember-sort-filter-table",
15 | "scripts": {
16 | "build": "ember build",
17 | "start": "ember server",
18 | "test": "ember try:each"
19 | },
20 | "dependencies": {
21 | "babel-eslint": "^7.2.3",
22 | "babel-plugin-transform-class-properties": "^6.24.1",
23 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
24 | "ember-cli-babel": "^6.3.0",
25 | "ember-cli-htmlbars": "^2.0.1",
26 | "ember-cli-page-object": "^1.11.0",
27 | "ember-cli-string-helpers": "^1.4.0",
28 | "ember-composable-helpers": "^2.0.3",
29 | "ember-decorators": "^1.2.1",
30 | "ember-truth-helpers": "^1.3.0"
31 | },
32 | "devDependencies": {
33 | "broccoli-asset-rev": "^2.4.5",
34 | "bulma": "^0.5.1",
35 | "code-highlight-linenums": "^0.2.1",
36 | "ember-ajax": "^3.0.0",
37 | "ember-cli": "~2.14.1",
38 | "ember-cli-dependency-checker": "^1.3.0",
39 | "ember-cli-deploy": "^1.0.1",
40 | "ember-cli-deploy-build": "^1.1.1",
41 | "ember-cli-deploy-git": "^1.3.0",
42 | "ember-cli-eslint": "^3.0.0",
43 | "ember-cli-htmlbars-inline-precompile": "^0.4.3",
44 | "ember-cli-inject-live-reload": "^1.4.1",
45 | "ember-cli-qunit": "^4.0.0",
46 | "ember-cli-sass": "~6.1.2",
47 | "ember-cli-shims": "^1.1.0",
48 | "ember-cli-sri": "^2.1.0",
49 | "ember-cli-uglify": "^1.2.0",
50 | "ember-computed-decorators": "^0.3.0",
51 | "ember-data": "^2.14.9",
52 | "ember-disable-prototype-extensions": "^1.1.2",
53 | "ember-export-application-global": "^2.0.0",
54 | "ember-font-awesome": "^3.0.5",
55 | "ember-load-initializers": "^1.0.0",
56 | "ember-macro-helpers": "^0.16.0",
57 | "ember-resolver": "^4.0.0",
58 | "ember-source": "~2.14.1",
59 | "ember-themed-syntax": "0.1.1",
60 | "eslint-plugin-ember-suave": "~1.0.0",
61 | "highlightjs": "^9.10.0",
62 | "hljs-themes": "^1.0.0",
63 | "loader.js": "^4.2.3"
64 | },
65 | "engines": {
66 | "node": "^4.5 || 6.* || >= 7.*"
67 | },
68 | "ember-addon": {
69 | "configPath": "tests/dummy/config"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | test_page: 'tests/index.html?hidepassed',
4 | disable_watching: true,
5 | launch_in_ci: [
6 | 'Chrome'
7 | ],
8 | launch_in_dev: [
9 | 'Chrome'
10 | ],
11 | browser_args: {
12 | Chrome: [
13 | '--disable-gpu',
14 | '--headless',
15 | '--remote-debugging-port=9222',
16 | '--window-size=1440,900'
17 | ]
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | embertest: true
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/tests/acceptance/general-test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'qunit';
2 | import moduleForAcceptance from '../../tests/helpers/module-for-acceptance';
3 | import page from '../../tests/pages/application';
4 |
5 | moduleForAcceptance('Acceptance | general');
6 |
7 | test('visiting various tabs', async function(assert) {
8 | await page.visit('/');
9 | await page.demoNav.tabInline();
10 | assert.equal((/(block|emberData)/).test(currentURL()), false, `navigated to inline (default) tab ${currentURL()}`);
11 |
12 | await page.demoNav.tabBlock();
13 | assert.equal(currentURL(), '/?tab=block', 'navigated to block tab');
14 |
15 | await page.demoNav.tabData();
16 | assert.equal(currentURL(), '/?tab=emberData', 'navigated to emberData tab');
17 | });
18 |
--------------------------------------------------------------------------------
/tests/dummy/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import config from '../config/environment';
3 |
4 | const PREFIX = config.APP.NAMESPACE || '';
5 |
6 | const {
7 | JSONAPIAdapter
8 | } = DS;
9 |
10 | export default JSONAPIAdapter.extend({
11 | namespace: `${PREFIX}/json`,
12 | pathForType (modelName) {
13 | return `${modelName}.json`;
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | const App = Ember.Application.extend({
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix,
9 | Resolver
10 | });
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/components/demo-block.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/demo-ember-data.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/demo-inline.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed } from 'ember-decorators/object';
3 |
4 | export default Component.extend({
5 | /**
6 | * @private
7 | */
8 | @computed()
9 | devs() {
10 | return {
11 | rows: [
12 | {
13 | name: 'Carlos Rodriguez',
14 | 'github_ID': 'crodriguez1a',
15 | _tags: 'developer javascript ember ios'
16 | },
17 | {
18 | name: 'Alex DiLiberto',
19 | 'github_ID': 'alexdiliberto',
20 | _tags: 'developer javascript ember android'
21 | }
22 | ]
23 | };
24 | },
25 |
26 | /**
27 | * @private
28 | */
29 | @computed()
30 | small() {
31 | return {
32 | rows: [
33 | {
34 | 'browser': ' Chrome',
35 | 'november': '48.15%',
36 | 'december': '46.22%',
37 | 'change': '-1.93%',
38 | 'relative': '-4.00%',
39 | '_private': 'foo',
40 | hello() {
41 | return 'foo';
42 | }
43 | },
44 | {
45 | 'browser': ' Firefox',
46 | 'november': '16.76%',
47 | 'december': '16.34%',
48 | 'change': '-0.42%',
49 | 'relative': '-2.50%',
50 | '_private': 'bar',
51 | hello() {
52 | return 'foo';
53 | }
54 | },
55 | {
56 | 'browser': ' Safari',
57 | 'november': '4.45%',
58 | 'december': '4.24%',
59 | 'change': '-0.21%',
60 | 'relative': '-4.70%',
61 | '_private': 'foo',
62 | hello() {
63 | return 'foo';
64 | }
65 | }
66 | ]
67 | };
68 | },
69 |
70 | /**
71 | * @private
72 | */
73 | @computed()
74 | big() {
75 | return {
76 | rows: [
77 | {
78 | 'No.': 9,
79 | 'Player': 'Ron Harper',
80 | 'Pos': 'PG',
81 | 'Ht': '6-6',
82 | 'Wt': 185,
83 | 'Birth-Date': 'January 20 1964',
84 | 'Exp': 9,
85 | 'College': 'Miami University'
86 | },
87 | {
88 | 'No.': 23,
89 | 'Player': 'Michael Jordan',
90 | 'Pos': 'SG',
91 | 'Ht': '6-6',
92 | 'Wt': 195,
93 | 'Birth-Date': 'February 17 1963',
94 | 'Exp': 10,
95 | 'College': 'University of North Carolina'
96 | },
97 |
98 | {
99 | 'No.': 13,
100 | 'Player': 'Luc Longley',
101 | 'Pos': 'C',
102 | 'Ht': '7-2',
103 | 'Wt': 265,
104 | 'Birth-Date': 'January 19 1969',
105 | 'Exp': 4,
106 | 'College': 'University of New Mexico'
107 | },
108 | {
109 | 'No.': 33,
110 | 'Player': 'Scottie Pippen',
111 | 'Pos': 'SF',
112 | 'Ht': '6-8',
113 | 'Wt': 210,
114 | 'Birth-Date': 'September 25 1965',
115 | 'Exp': 8,
116 | 'College': 'University of Central Arkansas'
117 | },
118 | {
119 | 'No.': 91,
120 | 'Player': 'Dennis Rodman',
121 | 'Pos': 'PF',
122 | 'Ht': '6-7',
123 | 'Wt': 210,
124 | 'Birth-Date': 'May 13 1961',
125 | 'Exp': 9,
126 | 'College': 'Southeastern Oklahoma State University'
127 | }
128 | ]
129 | };
130 | },
131 | actions: {
132 | /**
133 | @public
134 | */
135 | editField(row, key, value) {
136 | if (row && key && value) {
137 | return;
138 | }
139 | },
140 |
141 | /**
142 | @public
143 | */
144 | cancelEditField(row, key, value) {
145 | if (row && key && value) {
146 | return;
147 | }
148 | },
149 | }
150 | });
151 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/demo-nav.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { get } from '@ember/object';
3 |
4 | export default Component.extend({
5 | /**
6 | @property Name of currently active section
7 | @type Sting
8 | */
9 | tab: null,
10 |
11 | /**
12 | @property Action to perform to show active section
13 | @type Function
14 | */
15 | show: null,
16 |
17 | actions: {
18 | show() {
19 | if (get(this, 'show')) {
20 | get(this, 'show')(...arguments);
21 | }
22 | }
23 | }
24 | });
25 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/ui-hero.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend({
4 | classNames: ['hero'],
5 | classNameBindings: ['isWarning:is-warning']
6 | });
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/ui-tabs.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend({
4 | classNames: ['tabs'],
5 | classNameBindings: ['isBoxed:is-boxed', 'isRight:is-right']
6 | });
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/controllers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/application.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { set } from '@ember/object';
3 |
4 | export default Controller.extend({
5 | /**
6 | Active tab rendered
7 |
8 | @property tab
9 | @default inline
10 | @type String
11 | */
12 | tab: 'inline',
13 |
14 | actions: {
15 | show(section) {
16 | set(this, 'tab', section);
17 | }
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/helpers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ember Sort Filter Table
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/models/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/models/demo.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 |
3 | const {
4 | Model,
5 | attr
6 | } = DS;
7 |
8 | export default Model.extend({
9 | title: attr('string'),
10 | score: attr('string'),
11 | publisher: attr('string'),
12 | short_description: attr('string'),
13 | platforms: attr(),
14 | thumb: attr('string')
15 | });
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | const Router = Ember.Router.extend({
5 | location: config.locationType,
6 | rootURL: config.rootURL
7 | });
8 |
9 | Router.map(function() {
10 | this.route("/", { path: "*path"});
11 | });
12 |
13 | export default Router;
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/routes/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/routes/application.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 | import { get } from '@ember/object';
3 | import { inject as service } from '@ember/service';
4 |
5 | export default Route.extend({
6 | store: service(),
7 | queryParams: {
8 | tab: {
9 | replace: true
10 | }
11 | },
12 |
13 | model() {
14 | return get(this, 'store').findAll('demo');
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/tests/dummy/app/serializers/application.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 |
3 | const {
4 | JSONAPISerializer
5 | } = DS;
6 |
7 | export default JSONAPISerializer.extend({
8 | normalizeResponse(status, headers, payload, /*requestData*/) {
9 | return {
10 | data: payload.map((item, index) => {
11 | return {
12 | type: 'demo',
13 | id: index,
14 | attributes: item
15 | };
16 | })
17 | }
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | $fontFamily: monospace;
2 |
3 | @import 'bulma';
4 |
5 | * {
6 | .tab.is-active {
7 | i.fa {
8 | border-color: $primary;
9 | color: $primary;
10 | }
11 | }
12 | i.fa {
13 | display: inline-block;
14 | vertical-align: middle;
15 | position: relative;
16 | margin-top: -0.5%;
17 |
18 | &.expiremental {
19 | font-size: 95%;
20 | padding: 0.25rem;
21 | border-radius: 100%;
22 | border: 1px dotted $grey;
23 | margin: 0.2rem 0 0 0.5rem;
24 | vertical-align: bottom;
25 | color: $grey-dark;
26 | }
27 | }
28 |
29 | .button.cancel {
30 | @extend .is-danger;
31 | }
32 |
33 | button.edit {
34 | @extend .is-primary;
35 | }
36 |
37 | .edit-field {
38 | li {
39 | list-style: none;
40 | }
41 | }
42 |
43 | section.section {
44 | padding: 1.5rem;
45 | }
46 |
47 | // ember data
48 | .sftable-em-img {
49 | width: 100%;
50 | height: auto;
51 | }
52 |
53 | .sf-table--filter {
54 | position: relative;
55 | }
56 |
57 | a.sf-table--clear-field {
58 | &:after {
59 | display: block;
60 | vertical-align: middle;
61 | position: absolute;
62 | content: '\f00d';
63 | font-family: 'FontAwesome', sans-serif;
64 | top: 3px;
65 | right: 0.5rem;
66 | width: 1rem;
67 | height: 100%;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#ui-hero isWarning=true}}
3 |
4 |
5 | Ember Sort Filter Table
6 |
7 |
8 |
20 | {{/ui-hero}}
21 |
22 |
23 |
Version Notes
24 |
25 |
26 | 0.3.3
27 |
28 | Fixes #39
29 | Fixes #37
30 | Fixes #35
31 |
32 |
33 |
34 | 0.3.0
35 |
36 | Fixes #29
37 | Updates to Ember 2.14.x
38 | Contextual component approach (backwards compatible)
39 |
40 |
41 | {{#if older}}
42 |
43 | 0.2.6
44 |
45 | Block syntax support
46 | Ember Data support
47 |
48 |
49 |
50 | 0.2.5
51 |
52 | Fixes issue #27
53 |
54 |
55 |
56 | 0.2.4
57 |
58 | Update to Ember 2.6 - issue #25
59 | Replace lodash dependency with Object.values polyfill
60 | Fixes issue #26
61 |
62 |
63 |
64 | 0.2.1
65 |
66 |
67 | Remove observer - issue #21
68 |
69 |
70 | Replace sendAction with closure actions - issue #22
71 |
72 |
73 |
74 |
75 | 0.2.0
76 |
77 | Ember 2.x optimization
78 | Template update to remove outer div. Base of component is now a table
tag.
79 | Addition of some semantic class names
80 | Prevent non-primitives in model from rendering (e.g., functions, objects)
81 | Fix issue #16
82 | Fix issue #17
83 |
84 |
85 |
86 | 0.1.9
87 |
90 |
91 |
92 | 0.1.8
93 |
94 | HTML markup support in model
95 | Component short name alias sf-table
96 |
97 |
98 |
99 | 0.1.7
100 |
101 | Camel case row header support
102 | Private keys bugfix
103 |
104 |
105 | {{/if}}
106 |
107 | {{#unless older}}
108 |
109 |
110 | Older
111 |
112 |
113 | {{/unless}}
114 |
115 |
116 |
117 |
118 |
119 |
Installation
120 | {{#themed-syntax lang="bash" theme="dark"}}
121 | ember install ember-sort-filter-table
122 | {{/themed-syntax}}
123 |
124 |
125 |
126 |
127 | Usage
128 | {{demo-nav tab=tab show=(action 'show')}}
129 |
130 |
131 | {{component (concat 'demo-' tab) model=model}}
132 |
133 |
150 |
151 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/dummy/app/templates/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/demo-block.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A table can be composed using the following components:
5 |
6 |
7 | sf-table
8 |
9 |
10 | sf-headings
11 |
12 | headings Array - a list of (key) names and their corresponding display names.
13 |
14 |
15 |
16 | sf-filter
17 |
18 | placeholder String - Placeholder text for filter input. Default is "Filter"
19 | withClearField Bool - appends a link to clear the input field. Default is false
20 |
21 |
22 |
23 | sf-rows
24 |
25 | rows Array - a list hashes to display as table rows
26 |
27 |
28 |
29 |
30 |
31 | Components within a table communicate via the yielded group
parameter.
32 |
33 |
34 | {{#themed-syntax lang="handlebars" theme="dark"}}
35 | {{#sf-table as | sf |}}
36 | <thead>
37 | <tr>
38 | {{sf.headings
39 | headings=(array
40 | (hash key="firstName" display="First Name")
41 | (hash key="lastName" display="Last Name")
42 | (hash key="number" display="No.")
43 | (hash key="position" display="Pos.")
44 | )}}
45 | </tr>
46 | </thead>
47 | <tr><td colspan="99">{{sf.filter placeholder="Search"}}</td></tr>
48 | <tbody>
49 | {{sf.rows rows=(array
50 | (hash firstName='Michael' lastName='Jordan' number='23' position='G' _tags='guard north carolina')
51 | (hash firstName='Scottie' lastName='Pippen' number='33' position='SF' _tags='small forward central arkansas')
52 | (hash firstName='Horace' lastName='Grant' number='54' position='F' _tags='power forward clemson')
53 | )}}
54 | </tbody>
55 | {{/sf-table}}
56 | {{/themed-syntax}}
57 |
58 |
59 |
60 | As with inline usage, you can still designate properties that will not be displayed , using the an underscore (_tags="small forward marquette"
).
61 |
62 |
63 | {{#sf-table as | sf |}}
64 |
65 |
66 | {{sf.headings
67 | headings=(array
68 | (hash key="firstName" display="First Name")
69 | (hash key="lastName" display="Last Name")
70 | (hash key="number" display="No.")
71 | (hash key="position" display="Pos.")
72 | )}}
73 |
74 |
75 |
76 | {{sf.filter placeholder="Search"}}
77 |
78 |
79 | 1990s
80 |
81 |
82 | {{sf.rows rows=(array
83 | (hash firstName='Michael' lastName='Jordan' number='23' position='G' _tags='guard north carolina')
84 | (hash firstName='Scottie' lastName='Pippen' number='33' position='SF' _tags='small forward central arkansas')
85 | (hash firstName='Horace' lastName='Grant' number='54' position='F' _tags='power forward clemson')
86 | )}}
87 |
88 |
89 | Today
90 |
91 |
92 | {{sf.rows rows=(array
93 | (hash firstName='Dwyane' lastName='Wade' number='3' position='G' _tags='guard marquette')
94 | (hash firstName='Jimmy' lastName='Butler' number='21' position='SF' _tags='small forward marquette')
95 | (hash firstName='Rajan' lastName='Rondo' number='9' position='G' _tags='guard kentucky')
96 | )}}
97 |
98 | {{/sf-table}}
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/demo-ember-data.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A table that relies on Ember Data can be composed using the following components:
5 |
6 | sf-table
7 |
8 | sf.headings
9 |
10 | headings Array - a list of (key) names and their corresponding display names.
11 |
12 |
13 |
14 | sf.filter
15 |
16 | placeholder String - Placeholder text for filter input. Default is "Filter"
17 | withClearField Bool - appends a link to clear the input field. Default is false
18 |
19 |
20 |
21 | sf.data
22 |
23 | store Class - An ember data store
24 |
25 |
26 |
27 |
28 |
29 | Components within a table communicate via the yielded group
parameter.
30 |
31 |
32 | {{#themed-syntax lang="handlebars" theme="dark"}}
33 | {{#sf-table as | sf |}}
34 | <thead>
35 | <tr>
36 | {{sf.headings
37 | headings=(array
38 | (hash key="score" display="Score")
39 | (hash key="title" display="Title")
40 | (hash key="publisher" display="Publisher")
41 | (hash key="short_description" display="Description")
42 | (hash key="platforms" display="Platforms")
43 | )}}
44 | </tr>
45 | </thead>
46 | <tr><td colspan="99">{{sf.filter placeholder="Search" withClearField=true}}</td></tr>
47 | <tbody>
48 | {{sf.data store=model}}
49 | </tbody>
50 | {{/sf-table}}
51 | {{/themed-syntax}}
52 |
53 |
54 | {{#sf-table class="is-striped" as | sf |}}
55 |
56 |
57 | {{sf.headings
58 | headings=(array
59 | (hash key="score" display="Score")
60 | (hash key="title" display="Title")
61 | (hash key="publisher" display="Publisher")
62 | (hash key="short_description" display="Description")
63 | (hash key="platforms" display="Platforms")
64 | )}}
65 |
66 |
67 |
68 |
69 | {{sf.filter placeholder="Search" withClearField=true}}
70 |
71 |
72 |
73 | {{sf.data store=model}}
74 |
75 | {{/sf-table}}
76 |
77 |
78 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/demo-inline.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{#themed-syntax lang="handlebars" theme="dark"}}
5 | {{sf-table table=myTableModel}}
6 | {{/themed-syntax}}
7 |
8 |
9 | {{sf-table table=small isBordered=true isStriped=true}}
10 |
11 |
12 | Provide an object that contains an array called rows :
13 |
14 |
15 | {{#themed-syntax lang="javascript" theme="dark" withBuffers=false}}
16 | /* Using font awesome icons (not included) */
17 | myTableModel: {
18 | rows: [
19 | {
20 | 'Browser': '<i class="fa fa-chrome"></i> Chrome',
21 | 'November': '48.15%',
22 | 'December': '46.22%',
23 | 'change': '-1.93%',
24 | 'relative': '-4.00%'
25 | },
26 | {
27 | 'Browser': '<i class="fa fa-firefox"></i> Firefox',
28 | 'November': '16.76%',
29 | 'December': '16.34%',
30 | 'change': '-0.42%',
31 | 'relative': '-2.50%'
32 | },
33 | {
34 | 'Browser': '<i class="fa fa-safari"></i> Safari',
35 | 'November': '4.45%',
36 | 'December': '4.24%',
37 | 'change': '-0.21%',
38 | 'relative': '-4.70%'
39 | }
40 | ]
41 | };
42 | {{/themed-syntax}}
43 |
44 |
45 |
46 | The addon will assemble the table headers from the object keys. If your model has properties that should not be displayed in the table, use an underscore to designate that property as private .
47 |
48 |
49 |
50 | {{#themed-syntax lang="javascript" theme="dark" withBuffers=false}}
51 | let devs = {
52 | rows:[
53 | {
54 | name: 'Carlos Rodriguez',
55 | github_ID: 'crodriguez1a',
56 | _tags: 'developer javascript ember ios'
57 | },
58 | {
59 | name: 'Alex DiLiberto',
60 | github_ID: 'alexdiliberto',
61 | _tags: 'developer javascript ember android'
62 | }
63 | ]
64 | };
65 | {{/themed-syntax}}
66 |
67 |
68 |
69 | The model above would display as below. Although the _tags
key is designated private and does not display, its value is still considered.
70 |
71 |
72 | {{sf-table isBordered=true table=devs filterPlaceholder="Type android or ios"}}
73 |
74 |
75 |
76 |
77 |
78 |
Configuration
79 |
80 |
81 | filterable Bool - Signal if filter input should display. Default is true
82 |
83 |
84 | filterPlaceholder String - Placeholder text for filter input. Default is "Filter"
85 |
86 |
87 | editable Bool - Signal if editing dialogue should display. Default is false
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
Editable Example
96 | {{sf-table isStriped=true isBordered=true table=big editable=true edit=(action "editField") cancel=(action "cancelEditField")}}
97 |
98 | {{#themed-syntax lang="htmlbars" theme="dark"}}
99 | {{sf-table table=model editable=true edit=(action "myEditAction") cancel=(action "myCancelAction") }}
100 | {{/themed-syntax}}
101 |
102 |
103 |
104 | The edit and cancel actions will return the following hash:
105 |
106 | row - parent relationship
107 | key - property name
108 | value - edited value
109 |
110 |
111 |
112 | {{#themed-syntax lang="javascript" theme="dark" withBuffers=false}}
113 | myEditAction(hash) {
114 | //=> { row: 'Class', key: 'String', value: 'String' }
115 | },
116 |
117 | myCancelAction(hash) {
118 | //=> { row: 'Class', key: 'String', value: 'String' }
119 | }
120 | {{/themed-syntax}}
121 |
122 |
123 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/demo-nav.hbs:
--------------------------------------------------------------------------------
1 | {{#ui-tabs isBoxed=true}}
2 |
13 | {{/ui-tabs}}
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/ui-hero.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/ui-tabs.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(environment) {
5 | let ENV = {
6 | modulePrefix: 'dummy',
7 | environment,
8 | rootURL: '/',
9 | locationType: 'auto',
10 | EmberENV: {
11 | FEATURES: {
12 | // Here you can enable experimental features on an ember canary build
13 | // e.g. 'with-controller': true
14 | },
15 | EXTEND_PROTOTYPES: {
16 | // Prevent Ember Data from overriding Date.parse.
17 | Date: false
18 | }
19 | },
20 |
21 | APP: {
22 | // Here you can pass flags/options to your application instance
23 | // when it is created
24 | },
25 |
26 | // 'ember-bulma': {
27 | // except: ['ui-container'] // excludes `ui-container` https://github.com/open-tux/ember-bulma/issues/78
28 | // }
29 | };
30 |
31 | if (environment === 'development') {
32 | // ENV.APP.LOG_RESOLVER = true;
33 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
34 | // ENV.APP.LOG_TRANSITIONS = true;
35 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
36 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
37 | }
38 |
39 | if (environment === 'test') {
40 | // Testem prefers this...
41 | ENV.locationType = 'none';
42 |
43 | // keep test console output quieter
44 | ENV.APP.LOG_ACTIVE_GENERATION = false;
45 | ENV.APP.LOG_VIEW_LOOKUPS = false;
46 |
47 | ENV.APP.rootElement = '#ember-testing';
48 | }
49 |
50 | if (environment === 'production') {
51 | ENV.rootURL = '/ember-sort-filter-table';
52 | ENV.APP.NAMESPACE = 'ember-sort-filter-table';
53 | }
54 |
55 | return ENV;
56 | };
57 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | browsers: [
4 | 'ie 9',
5 | 'last 1 Chrome versions',
6 | 'last 1 Firefox versions',
7 | 'last 1 Safari versions'
8 | ]
9 | };
10 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/dummy/public/json/demo.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title":"Metal Gear Solid V: The Definitive Experience",
4 | "score":"10",
5 | "publisher":"Konami",
6 | "short_description":"October 11, 2016 - Metal Gear Solid V: The Definitive Experience is the ultimate collection of the MGS 5 story which includes the critically acclaimed Metal Gear Solid V:...",
7 | "platforms":{
8 | "1":"PS4",
9 | "2":"PC",
10 | "3":"Xbox One"
11 | },
12 | "thumb":"http:\/\/assets2.ignimgs.com\/2016\/08\/30\/metal-gear-solid-definitive-button-00jpg-75869c_160w.jpg"
13 | },
14 | {
15 | "title":"Metal Gear",
16 | "score":"",
17 | "publisher":"Ultra Games",
18 | "short_description":"June 01, 1988 - Outer Heaven leader Ca Taffy has activated the ultimate weapon: Metal Gear. It is up to Solid Snake to find and destroy the weapon and end Ca Taffy's reign of terror.",
19 | "platforms":{
20 | "1":"NES"
21 | },
22 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/object\/007\/007055\/MetalGeaR_f_NESBOXboxart_160w.jpg"
23 | },
24 | {
25 | "title":"Metal Gear Survive",
26 | "score":"",
27 | "publisher":"Konami",
28 | "short_description":"TBA 2017",
29 | "platforms":{
30 | "1":"Xbox One",
31 | "2":"PC",
32 | "3":"PS4"
33 | },
34 | "thumb":"http:\/\/assets1.ignimgs.com\/2016\/08\/17\/metal-gear-survive-buttonjpg-a7680a_160w.jpg"
35 | },
36 | {
37 | "title":"Metal Gear Rising: Revengeance",
38 | "score":"8.5",
39 | "publisher":"Konami",
40 | "short_description":"February 19, 2013 - Productions, Metal Gear Rising: Revengeance pits cyborg against cyborg in battle that blazes past every human limit! The main character, Raiden, was once feared...",
41 | "platforms":{
42 | "1":"PS3",
43 | "2":"Xbox 360",
44 | "3":"PC",
45 | "4":"Android"
46 | },
47 | "thumb":"http:\/\/media.ign.com\/games\/image\/object\/142\/14276686\/metalgearrisingrev1boxart_160w.jpg"
48 | },
49 | {
50 | "title":"Metal Gear Online [2008]",
51 | "score":"",
52 | "publisher":"Konami",
53 | "short_description":"June 12, 2008 - ...in the Metal Gear franchise, following up on the success of the online portion of MGS3 Subsistence on PS2 and MGS Portable Ops on PSP. Metal Gear Online...",
54 | "platforms":{
55 | "1":"PS3",
56 | "2":"Arcade"
57 | },
58 | "thumb":""
59 | },
60 | {
61 | "title":"Metal Gear Acid",
62 | "score":"6.5",
63 | "publisher":"Konami",
64 | "short_description":"PSP Launch - 03\/24\/2005 - ...blockbuster Metal Gear franchise gives players a new approach to their espionage. Metal Gear Acid (also lovingly known as Acid Metal by its creators for the...",
65 | "platforms":{
66 | "1":"PSP"
67 | },
68 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/object\/664\/664947\/MGSAcid_PSP_BoxFront_ESRBed02202005boxart_160w.jpg"
69 | },
70 | {
71 | "title":"Metal Gear Acid 2",
72 | "score":"7.7",
73 | "publisher":"Konami",
74 | "short_description":"March 21, 2006 - ...previous Metal Gear Acid game, Metal Gear Acid 2 features an improved card-based strategic gameplay system with card weapon upgrades and over 500 cards to...",
75 | "platforms":{
76 | "1":"PSP"
77 | },
78 | "thumb":"http:\/\/pspmedia.ign.com\/psp\/image\/object\/748\/748621\/metalgearacid2_pspboxboxart_160w.jpg"
79 | },
80 | {
81 | "title":"Metal Gear Solid [2000]",
82 | "score":"10",
83 | "publisher":"Konami",
84 | "short_description":"May 05, 2000 - ...bipedal tank Metal Gear has fallen into the hands of terrorists. Seven years after the fiasco at Outer Heaven involving the original Metal Gear, the US government...",
85 | "platforms":{
86 | "1":"GBC"
87 | },
88 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/object\/013\/013458\/metalgear_ghostbabel_GBCBOXboxart_160w.jpg"
89 | },
90 | {
91 | "title":"Metal Gear Online [MGS V]",
92 | "score":"",
93 | "publisher":"Konami",
94 | "short_description":"October 06, 2015 - Metal Gear Online is the multiplayer component for Metal Gear Solid V: The Phantom Pain.",
95 | "platforms":{
96 | "1":"Xbox 360",
97 | "2":"PS3",
98 | "3":"PS4",
99 | "4":"Xbox One",
100 | "5":"PC"
101 | },
102 | "thumb":"http:\/\/assets1.ignimgs.com\/2014\/12\/06\/metal-gear-online-placeholderjpg-d18fc9_160w.jpg"
103 | },
104 | {
105 | "title":"Metal Gear Solid 2: Substance",
106 | "score":"9",
107 | "publisher":"Konami",
108 | "short_description":"November 05, 2002 - ...popular Metal Gear Solid 2 title, on PlayStation 2, Xbox, and PC, expands gameplay with added options, and features. The largest addition to gameplay are...",
109 | "platforms":{
110 | "1":"Xbox",
111 | "2":"PS2",
112 | "3":"PC"
113 | },
114 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/mgs2substance_xboxbox_orgboxart_160w.jpg"
115 | },
116 | {
117 | "title":"Metal Gear Solid Touch",
118 | "score":"6",
119 | "publisher":"Konami",
120 | "short_description":"March 19, 2009 - ...in the Metal Gear Solid franchise, Metal Gear Solid Touch is a shooting game that makes use of the iPad's touchscreen control mechanisms to retell gamers...",
121 | "platforms":{
122 | "1":"iPhone",
123 | "2":"iPad"
124 | },
125 | "thumb":"http:\/\/wiimedia.ign.com\/wii\/image\/object\/143\/14306701\/METAL-GEAR-SOLID-TOUCH_dl10_iPhoneboxart_160w.jpg"
126 | },
127 | {
128 | "title":"Metal Gear Solid HD Collection",
129 | "score":"9",
130 | "publisher":"Konami",
131 | "short_description":"November 08, 2011 - ...in the Metal Gear franchise with the Metal Gear Solid HD Collection. Included are Metal Gear Solid 2: Sons of Liberty, Metal Gear Solid 3: Snake Eater and...",
132 | "platforms":{
133 | "1":"PS3",
134 | "2":"Xbox 360",
135 | "3":"Vita"
136 | },
137 | "thumb":"http:\/\/media.ign.com\/games\/image\/object\/110\/110044\/Metal-Gear-Solid-HD_STANDARD_US_ESRB_PS3boxart_160w.jpg"
138 | },
139 | {
140 | "title":"Metal Gear Solid: The Essential Collection",
141 | "score":"8.8",
142 | "publisher":"Konami",
143 | "short_description":"March 18, 2008 - Metal Gear Solid: The Essential Collection includes three classic games in the groundbreaking Metal gear franchise. Included are the original Metal Gear...",
144 | "platforms":{
145 | "1":"PS2"
146 | },
147 | "thumb":"http:\/\/media.ign.com\/games\/image\/object\/142\/14233286\/metalgearsolidecollectionpsboxart_160w.jpg"
148 | },
149 | {
150 | "title":"Metal Gear Solid 3: Subsistence",
151 | "score":"9.8",
152 | "publisher":"Konami",
153 | "short_description":"March 14, 2006 - ...dazzling Metal Gear Solid 3: Snake Eater . For Subsistence , a new 3rd Person View Camera\" gives a controllable, low-angle view of the action for a more intense...",
154 | "platforms":{
155 | "1":"PS2"
156 | },
157 | "thumb":"http:\/\/ps2media.ign.com\/ps2\/image\/object\/748\/748590\/mgs3subsistence_ps2box_usa_org_000boxart_160w.jpg"
158 | },
159 | {
160 | "title":"Metal Gear Solid: The Twin Snakes",
161 | "score":"8.5",
162 | "publisher":"Konami",
163 | "short_description":"March 09, 2004 - PlayStation hit Metal Gear Solid is recreated with updated graphics and an expanded story on the Nintendo GameCube. This overhauled version of the PS One best-seller...",
164 | "platforms":{
165 | "1":"GCN"
166 | },
167 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/mgstwin_gcn_boxboxart_160w.jpg"
168 | },
169 | {
170 | "title":"Metal Gear Solid: Portable Ops",
171 | "score":"9",
172 | "publisher":"Konami",
173 | "short_description":"December 05, 2006 - ...ongoing Metal Gear Solid narrative, as players learn more about returning characters such as Para-Medic, Major Zero and Sigint and also witness Big Boss'...",
174 | "platforms":{
175 | "1":"PSP"
176 | },
177 | "thumb":"http:\/\/pocketmedia.ign.com\/pocket\/image\/object\/783\/783630\/metalgearsolidportops_pspboxboxart_160w.jpg"
178 | },
179 | {
180 | "title":"Metal Gear 2: Solid Snake",
181 | "score":"",
182 | "publisher":"Konami",
183 | "short_description":"TBD",
184 | "platforms":{
185 | "1":"MSX2",
186 | "2":"Wii"
187 | },
188 | "thumb":"http:\/\/media.gamestats.com\/gg\/image\/object\/498\/498104\/mgs2solidsnake_msxboxboxart_160w.jpg"
189 | },
190 | {
191 | "title":"Metal Gear Solid V: The Phantom Pain",
192 | "score":"10",
193 | "publisher":"Konami",
194 | "short_description":"September 01, 2015 - ...in the Metal Gear franchise, Metal Gear Solid V: The Phantom Pain continues the complex saga of the Snake soldier line, connecting storylines from Metal...",
195 | "platforms":{
196 | "1":"Xbox One",
197 | "2":"PS4",
198 | "3":"PS3",
199 | "4":"Xbox 360",
200 | "5":"PC"
201 | },
202 | "thumb":"http:\/\/assets2.ignimgs.com\/2015\/07\/16\/mgs-5-us-esrb-std-xonejpg-1783a7_160w.jpg"
203 | },
204 | {
205 | "title":"Metal Gear Solid V: Ground Zeroes",
206 | "score":"8",
207 | "publisher":"Konami",
208 | "short_description":"March 18, 2014 - Metal Gear Solid V: Ground Zeroes acts as a prologue to Metal Gear Solid V: The Phantom Pain and takes place one year after the events of Metal Gear Solid:...",
209 | "platforms":{
210 | "1":"Xbox 360",
211 | "2":"Xbox One",
212 | "3":"PS3",
213 | "4":"PS4",
214 | "5":"PC"
215 | },
216 | "thumb":"http:\/\/assets2.ignimgs.com\/2014\/03\/18\/metal-gear-solid-v-ground-zeroes-us-esrb-x360jpg-e31ae2_160w.jpg"
217 | },
218 | {
219 | "title":"Metal Gear Solid 3D Snake Eater",
220 | "score":"8.5",
221 | "publisher":"Konami",
222 | "short_description":"February 21, 2012 - Metal Gear Solid 3D Snake Eater is set during the Cold War of the 60s, and sees series hero Snake infiltrating the Soviet jungle to bring back a scientist...",
223 | "platforms":{
224 | "1":"3DS"
225 | },
226 | "thumb":"http:\/\/xbox360media.ign.com\/xbox360\/image\/object\/077\/077733\/MGS_3DS_2D_Final1boxart_160w.jpg"
227 | }
228 | ]
229 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default function destroyApp(application) {
4 | Ember.run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import Ember from 'ember';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | const { RSVP: { resolve } } = Ember;
7 |
8 | export default function(name, options = {}) {
9 | module(name, {
10 | beforeEach() {
11 | this.application = startApp();
12 |
13 | if (options.beforeEach) {
14 | return options.beforeEach.apply(this, arguments);
15 | }
16 | },
17 |
18 | afterEach() {
19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
20 | return resolve(afterEach).then(() => destroyApp(this.application));
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/tests/helpers/owner.js:
--------------------------------------------------------------------------------
1 | // https://github.com/emberjs/data/blob/master/tests/helpers/owner.js
2 |
3 | import Ember from 'ember';
4 |
5 | let Owner;
6 |
7 | if (Ember._RegistryProxyMixin && Ember._ContainerProxyMixin) {
8 | Owner = Ember.Object.extend(Ember._RegistryProxyMixin, Ember._ContainerProxyMixin);
9 | } else {
10 | Owner = Ember.Object.extend();
11 | }
12 |
13 | export default Owner;
14 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import config from '../../config/environment';
4 |
5 | export default function startApp(attrs) {
6 | let attributes = Ember.merge({}, config.APP);
7 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
8 |
9 | return Ember.run(() => {
10 | let application = Application.create(attributes);
11 | application.setupForTesting();
12 | application.injectTestHelpers();
13 | return application;
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/tests/helpers/store.js:
--------------------------------------------------------------------------------
1 | // https://raw.githubusercontent.com/emberjs/data/master/tests/helpers/store.js
2 |
3 | import Ember from 'ember';
4 | import DS from 'ember-data';
5 | import Owner from './owner';
6 |
7 | export default function setupStore(options) {
8 | let container, registry, owner;
9 | let env = {};
10 | options = options || {};
11 |
12 | if (Ember.Registry) {
13 | registry = env.registry = new Ember.Registry();
14 | owner = Owner.create({
15 | __registry__: registry
16 | });
17 | container = env.container = registry.container({
18 | owner: owner
19 | });
20 | owner.__container__ = container;
21 | } else {
22 | container = env.container = new Ember.Container();
23 | registry = env.registry = container;
24 | }
25 |
26 | env.replaceContainerNormalize = function replaceContainerNormalize(fn) {
27 | if (env.registry) {
28 | env.registry.normalize = fn;
29 | } else {
30 | env.container.normalize = fn;
31 | }
32 | };
33 |
34 | let adapter = env.adapter = (options.adapter || '-default');
35 | delete options.adapter;
36 |
37 | if (typeof adapter !== 'string') {
38 | env.registry.register('adapter:-ember-data-test-custom', adapter);
39 | adapter = '-ember-data-test-custom';
40 | }
41 |
42 | for (let prop in options) {
43 | registry.register('model:' + Ember.String.dasherize(prop), options[prop]);
44 | }
45 |
46 | registry.register('service:store', DS.Store.extend({
47 | adapter: adapter
48 | }));
49 |
50 | registry.optionsForType('serializer', { singleton: false });
51 | registry.optionsForType('adapter', { singleton: false });
52 | registry.register('adapter:-default', DS.Adapter);
53 |
54 | registry.register('serializer:-default', DS.JSONAPISerializer);
55 | registry.register('serializer:-json', DS.JSONSerializer);
56 | registry.register('serializer:-rest', DS.RESTSerializer);
57 |
58 | registry.register('adapter:-rest', DS.RESTAdapter);
59 | registry.register('adapter:-json-api', DS.JSONAPIAdapter);
60 |
61 | registry.injection('serializer', 'store', 'service:store');
62 |
63 | env.store = container.lookup('service:store');
64 | env.restSerializer = container.lookup('serializer:-rest');
65 | env.restSerializer.store = env.store;
66 | env.serializer = env.store.serializerFor('-default');
67 | env.serializer.store = env.store;
68 | env.adapter = env.store.get('defaultAdapter');
69 |
70 | return env;
71 | }
72 |
73 | export { setupStore };
74 |
75 | export function createStore(options) {
76 | return setupStore(options).store;
77 | }
78 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{content-for "body-footer"}}
31 | {{content-for "test-body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/integration/.gitkeep
--------------------------------------------------------------------------------
/tests/integration/components/each-keys-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('each-keys', 'Integration | Component | each keys', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{each-keys}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#each-keys}}
19 | template block text
20 | {{/each-keys}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/em-data-value-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('em-data-value', 'Integration | Component | em data value', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{em-data-value}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#em-data-value}}
19 | template block text
20 | {{/em-data-value}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sf-em-data-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test/*, skip*/ } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 | import { create, text, count } from 'ember-cli-page-object';
4 | import RSVP from 'rsvp';
5 | import {/*setupStore,*/ createStore} from 'dummy/tests/helpers/store';
6 | import DS from 'ember-data';
7 | import { run } from '@ember/runloop';
8 |
9 | let store;
10 |
11 | const person = DS.Model.extend({
12 | name: DS.attr('string'),
13 | nickName: DS.attr('string')
14 | });
15 |
16 | const adapter = DS.Adapter.extend({
17 | deleteRecord() {
18 | return RSVP.Promise.resolve();
19 | }
20 | });
21 |
22 | const component = create({
23 | table: text('.table'),
24 | headings: { count: count(), scope: '.table th', text: text('', {multiple: true }) },
25 | rows: { count: count(), scope: '.table td', text: text('', {multiple: true }) }
26 | })
27 |
28 | moduleForComponent('sf-em-data', 'Integration | Component | sf em data', {
29 | integration: true,
30 | beforeEach() {
31 | component.setContext(this);
32 | store = createStore({ adapter, person });
33 | },
34 | afterEach() {
35 | component.removeContext();
36 | }
37 | });
38 |
39 | test('it renders', function(assert) {
40 | this.render(hbs`{{sf-em-data}}`);
41 | assert.equal(this.$().text().trim(), '');
42 |
43 | // Template block usage:
44 | this.render(hbs`
45 | {{#sf-em-data}}
46 | template block text
47 | {{/sf-em-data}}
48 | `);
49 |
50 | assert.equal(this.$().text().trim(), 'template block text');
51 | });
52 |
53 | test('it renders only the number of rows and columns corresponding with the headings hash: Single', async function(assert) {
54 | let recordArray = store.recordArrayManager
55 | .createAdapterPopulatedRecordArray('person', null);
56 |
57 | let payload = {
58 | data: [
59 | {
60 | type: 'person',
61 | id: '1',
62 | attributes: {
63 | name: 'Someone MaJoe',
64 | nickName: 'Super MaJoe'
65 | }
66 | }
67 | ]
68 | };
69 |
70 | await run(() => {
71 | recordArray._setInternalModels(store._push(payload), payload);
72 | });
73 |
74 | this.model = recordArray;
75 |
76 | this.render(hbs`
77 | {{#sf-table class="is-striped" as | sf |}}
78 | {{sf.headings
79 | headings=(array
80 | (hash key="name" display="Name")
81 | )}}
82 | {{sf.data store=model}}
83 | {{/sf-table}}
84 | `);
85 | assert.equal(component.headings.count, 1, 'A single heading was rendered');
86 | assert.equal(component.rows.count, 1, 'A single row rendered');
87 | });
88 |
89 | test('it renders data in the same order the headings suggest', async function(assert) {
90 | let recordArray = store.recordArrayManager
91 | .createAdapterPopulatedRecordArray('person', null);
92 |
93 | let payload = {
94 | data: [
95 | {
96 | type: 'person',
97 | id: '1',
98 | attributes: {
99 | name: 'Someone MaJoe',
100 | address: '123 hero lane',
101 | nickName: 'Super MaJoe'
102 | }
103 | }
104 | ]
105 | };
106 |
107 | await run(() => {
108 | recordArray._setInternalModels(store._push(payload), payload);
109 | });
110 |
111 | this.model = recordArray;
112 |
113 | this.render(hbs`
114 | {{#sf-table class="is-striped" as | sf |}}
115 | {{sf.headings
116 | headings=(array
117 | (hash key="address" display="Address")
118 | (hash key="nickName" display="Nickety Name")
119 | (hash key="name" display="Name")
120 | )}}
121 | {{sf.data store=model}}
122 | {{/sf-table}}
123 | `);
124 | assert.equal(component.rows.text.join(), "123 hero lane,Super MaJoe,Someone MaJoe", 'It rendered the records arranged in the correct order');
125 |
126 | this.render(hbs`
127 | {{#sf-table class="is-striped" as | sf |}}
128 | {{sf.headings
129 | headings=(array
130 | (hash key="nickName" display="Nickety Name")
131 | (hash key="name" display="Name")
132 | (hash key="address" display="Address")
133 | )}}
134 | {{sf.data store=model}}
135 | {{/sf-table}}
136 | `);
137 | assert.equal(component.rows.text.join(), "Super MaJoe,Someone MaJoe,123 hero lane", 'It rendered the records arranged in the correct order');
138 | });
139 |
140 | test('it renders only the number of rows and columns corresponding with the headings hash: Multiple', async function(assert) {
141 | let recordArray = store.recordArrayManager
142 | .createAdapterPopulatedRecordArray('person', null);
143 |
144 | let payload = {
145 | data: [
146 | {
147 | type: 'person',
148 | id: '1',
149 | attributes: {
150 | name: 'Someone MaJoe',
151 | nickName: 'Super MaJoe',
152 | occupation: 'super hero',
153 | address: '123 hero lane'
154 | }
155 | }
156 | ]
157 | };
158 |
159 | await run(() => {
160 | recordArray._setInternalModels(store._push(payload), payload);
161 | });
162 |
163 | this.model = recordArray;
164 |
165 | this.render(hbs`
166 | {{#sf-table class="is-striped" as | sf |}}
167 | {{sf.headings
168 | headings=(array
169 | (hash key="name" display="Name")
170 | (hash key="nickName" display="Nick Name")
171 | )}}
172 | {{sf.data store=model}}
173 | {{/sf-table}}
174 | `);
175 | // assert.async();
176 | assert.equal(component.headings.count, 2, 'Multiple headings were rendered');
177 | assert.equal(component.rows.count, 2, 'Multiple rows were rendered');
178 | });
179 |
180 | // TODO unit test POJO extractions
181 |
--------------------------------------------------------------------------------
/tests/integration/components/sf-filter-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('sf-filter', 'Integration | Component | sf filter', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{sf-filter}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#sf-filter}}
19 | template block text
20 | {{/sf-filter}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sf-headings-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('sf-headings', 'Integration | Component | sf headings', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{sf-headings}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#sf-headings}}
19 | template block text
20 | {{/sf-headings}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sf-rows-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('sf-rows', 'Integration | Component | sf rows', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{sf-rows}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#sf-rows}}
19 | template block text
20 | {{/sf-rows}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sf-table-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('sf-table', 'Integration | Component | sf table', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{sf-table}}`);
13 |
14 | assert.equal(this.$('.no-data-provided').length, 1);
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#sf-table}}
19 | template block text
20 | {{/sf-table}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sort-filter-table-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { moduleForComponent, test, skip } from 'ember-qunit';
3 | import hbs from 'htmlbars-inline-precompile';
4 |
5 | // since run-loop is disabled, wrap any code with asynchronous side-effects in a run
6 | const {
7 | run,
8 | set
9 | } = Ember;
10 |
11 | let sample = {
12 | rows: [
13 | {
14 | 'last_name': 'Billups',
15 | 'first_name': 'Chauncey',
16 | 'display_name': 'Chauncey Billups',
17 | 'birthdate': '1976-09-25',
18 | 'age': 37,
19 | 'birthplace': 'Denver, Colorado, USA',
20 | 'height_in': 75,
21 | 'height_cm': 190.5,
22 | 'height_m': 1.9,
23 | 'height_formatted': '6\'3\'',
24 | 'weight_lb': 202,
25 | 'weight_kg': 91.8,
26 | 'position': 'PG',
27 | 'uniform_number': 1
28 | },
29 | {
30 | 'last_name': 'Bynum',
31 | 'first_name': 'William',
32 | 'display_name': 'Will Bynum',
33 | 'birthdate': '1983-01-04',
34 | 'age': 30,
35 | 'birthplace': 'Chicago, Illinois, USA',
36 | 'height_in': 72,
37 | 'height_cm': 182.9,
38 | 'height_m': 1.8,
39 | 'height_formatted': '6\'0\'',
40 | 'weight_lb': 185,
41 | 'weight_kg': 84.1,
42 | 'position': 'PG',
43 | 'uniform_number': 12
44 | }
45 | ]
46 | };
47 |
48 | let hyphen = {
49 | rows: [
50 | {
51 | 'hyphen-ated': true
52 | }
53 | ]
54 | };
55 |
56 | let underscore = {
57 | rows: [
58 | {
59 | 'under_scored_underscored': true
60 | }
61 | ]
62 | };
63 |
64 | let alphaSort = {
65 | rows: [
66 | {
67 | name: 'alpha'
68 | },
69 | {
70 | name: 'zeta'
71 | }
72 | ]
73 | };
74 |
75 | let alphaSortHypen = {
76 | rows: [
77 | {
78 | 'the-name': 'alpha'
79 | },
80 | {
81 | 'the-name': 'zeta'
82 | }
83 | ]
84 | };
85 |
86 | let alphaSortUnderscore = {
87 | rows: [
88 | {
89 | 'the_name': 'alpha'
90 | },
91 | {
92 | 'the_name': 'zeta'
93 | }
94 | ]
95 | };
96 |
97 | let alphaSortCamelCase = {
98 | rows: [
99 | {
100 | 'theName': 'alpha'
101 | },
102 | {
103 | 'theName': 'zeta'
104 | }
105 | ]
106 | };
107 |
108 | let numericSort = {
109 | rows: [
110 | {
111 | number: "0"
112 | },
113 | {
114 | number: "1"
115 | }
116 | ]
117 | };
118 |
119 | let camelCase = {
120 | rows: [
121 | {
122 | camelCase: true,
123 | caseCamel: true
124 | }
125 | ]
126 | };
127 |
128 | let emberObject = Ember.Object.create({
129 | rows: Ember.A([
130 | Ember.Object.create({
131 | foo: 'bar'
132 | })
133 | ])
134 | });
135 |
136 | let privateLabel = {
137 | rows: [
138 | {
139 | _private: 'foo'
140 | }
141 | ]
142 | };
143 |
144 | let notAllPrimitive = {
145 | rows: [
146 | {
147 | hello: 'world',
148 | yo: true,
149 | mtvraps: 1
150 | }
151 | ]
152 | };
153 |
154 | moduleForComponent('sort-filter-table', 'Integration | Component | sort filter table', {
155 | integration: true
156 | });
157 |
158 | test('it renders', function(assert) {
159 | // Set any properties with set(this, 'myProperty', 'value');
160 | // Handle any actions with this.on('myAction', function(val) { ... });
161 |
162 | this.render(hbs`{{sort-filter-table table=table}}`);
163 |
164 | assert.equal(this.$('.no-data-provided').length, 1);
165 |
166 | // Template block usage:
167 | this.render(hbs`
168 | {{#sort-filter-table}}
169 | template block text
170 | {{/sort-filter-table}}
171 | `);
172 |
173 | assert.equal(this.$().text().trim(), 'template block text');
174 | });
175 |
176 | skip('contextual component implementation is backwards compatible', function(assert) {
177 | assert.ok(false);
178 | });
179 |
180 | test('it assembles header labels', function(assert) {
181 | set(this, 'table', sample);
182 | this.render(hbs`{{sort-filter-table table=table}}`);
183 |
184 | assert.equal(this.$('.table-header').length, 14, 'Correct number of labels are in DOM and in sync with model');
185 | });
186 |
187 | test('it excludes headers marked as private with a leading underscore', function(assert) {
188 | set(this, 'table', privateLabel);
189 | this.render(hbs`{{sort-filter-table table=table}}`);
190 |
191 | assert.equal(this.$('.table-header').length, 0, 'When an object key has a leading underscore (private), exclude from DOM');
192 | });
193 |
194 | test('it handles headers with underscores, hyphens, spaces, or camel case', function(assert) {
195 | // hyphenated keys
196 | set(this, 'table', hyphen);
197 | this.render(hbs`{{sort-filter-table table=table}}`);
198 |
199 | assert.equal((/-/g).test(this.$('.table-header').text().trim()), false, 'When object keys use hyphens, labels are displayed DOM without hyphens');
200 |
201 | // keys with underscores
202 | run(() => {
203 | set(this, 'table', underscore);
204 | this.render(hbs`{{sort-filter-table table=table}}`);
205 |
206 | assert.equal((/_/g).test(this.$('.table-header').text().trim()), false, 'When object keys use underscores, labels are displayed DOM without underscores');
207 | });
208 |
209 | // keys with camel case
210 | run(() => {
211 | set(this, 'table', camelCase);
212 | this.render(hbs`{{sort-filter-table table=table}}`);
213 |
214 | assert.equal((/[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*/).test(this.$('.table-header').text().trim()), false, 'When object keys use camel case, labels are displayed DOM with no camel casing');
215 | });
216 |
217 | });
218 |
219 | test('it sorts alphabetically', function(assert) {
220 | set(this, 'table', alphaSort);
221 | this.render(hbs`{{sort-filter-table table=table}}`);
222 |
223 | let $sortLabel = this.$('.sort-labels');
224 |
225 | $sortLabel.click();
226 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'zeta', 'Table was sorted alphabetically');
227 |
228 | $sortLabel.click();
229 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'alpha', 'Table was sorted again in the reverse');
230 |
231 | });
232 |
233 | test('it sorts alphabetically with hyphen separator', function(assert) {
234 | set(this, 'table', alphaSortHypen);
235 | this.render(hbs`{{sort-filter-table table=table}}`);
236 |
237 | let $sortLabel = this.$('.sort-labels');
238 |
239 | $sortLabel.click();
240 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'zeta', 'Table was sorted alphabetically');
241 |
242 | $sortLabel.click();
243 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'alpha', 'Table was sorted again in the reverse');
244 |
245 | });
246 |
247 | test('it sorts alphabetically with underscore separator', function(assert) {
248 | set(this, 'table', alphaSortUnderscore);
249 | this.render(hbs`{{sort-filter-table table=table}}`);
250 |
251 | let $sortLabel = this.$('.sort-labels');
252 |
253 | $sortLabel.click();
254 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'zeta', 'Table was sorted alphabetically');
255 |
256 | $sortLabel.click();
257 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'alpha', 'Table was sorted again in the reverse');
258 |
259 | });
260 |
261 | test('it sorts alphabetically with camelCase separator', function(assert) {
262 | set(this, 'table', alphaSortCamelCase);
263 | this.render(hbs`{{sort-filter-table table=table}}`);
264 |
265 | let $sortLabel = this.$('.sort-labels');
266 |
267 | $sortLabel.click();
268 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'zeta', 'Table was sorted alphabetically');
269 |
270 | $sortLabel.click();
271 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), 'alpha', 'Table was sorted again in the reverse');
272 |
273 | });
274 |
275 | test('it sorts numerically', function(assert) {
276 | set(this, 'table', numericSort);
277 | this.render(hbs`{{sort-filter-table table=table}}`);
278 |
279 | let $sortLabel = this.$('.sort-labels');
280 |
281 | $sortLabel.click();
282 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), '1', 'Table was sorted numerically');
283 | $sortLabel.click();
284 | assert.equal(this.$().find('tbody td').first().text().replace(/\n/g, '').replace(/ /g, ''), '0', 'Table was sorted again in the reverse');
285 | });
286 |
287 | test('it filters appropriately with multiple filter tems', function(assert) {
288 | set(this, 'table', sample);
289 | this.render(hbs`{{sort-filter-table filter="Chauncey Billups" table=table}}`);
290 |
291 | assert.ok(this.$().find('tbody tr').length > 0, 'When a filter using two query terms (eg., John Doe) is applied, a match is found');
292 | });
293 |
294 | test('it handles both POJOs and Ember Objects in the model', function(assert) {
295 | set(this, 'table', emberObject);
296 | this.render(hbs`{{sort-filter-table table=table}}`);
297 |
298 | assert.equal(this.$().find('tbody tr').length, 1, 'When an Ember Object is passed, DOM is populated accordingly');
299 | });
300 |
301 | test('it toggles to edit mode', function(assert) {
302 | this.setProperties({
303 | table: sample,
304 | editable: true,
305 | edit: 'myEditAction',
306 | cancel: 'myCancelAction'
307 | });
308 | this.render(hbs`{{sort-filter-table editable=true edit=edit cancel=cancel table=table}}`);
309 |
310 | let $editValue = this.$('.edit-value:first');
311 | $editValue.click();
312 |
313 | assert.ok(this.$().find('.edit-field:first').length > 0, 'An editable value was clicked, its corresponding form is displayed');
314 | assert.ok(this.$('.send-edit:first').length > 0, 'An edit button is available');
315 | assert.ok(this.$('.cancel-edit:first').length > 0, 'A cancel button is available');
316 |
317 | });
318 |
319 | test('it sends up the params up to the controller', function(assert) {
320 | let editValues;
321 | let cancelValues;
322 |
323 | // Simulate controller
324 | let myEditAction = (params) => {
325 | editValues = params;
326 | };
327 |
328 | let myCancelAction = (params) => {
329 | cancelValues = params;
330 | };
331 |
332 | this.setProperties({
333 | table: sample,
334 | editable: true,
335 | edit: myEditAction,
336 | cancel: myCancelAction
337 | });
338 | this.render(hbs`{{sort-filter-table editable=true edit=edit cancel=cancel table=table}}`);
339 |
340 | this.$('.edit-value:first').click();
341 | this.$('.send-edit:first').click();
342 | assert.ok(!!editValues, 'The controller\'s edit action received parameters');
343 |
344 | run(() => {
345 | this.$('.edit-value:first').click();
346 | this.$('.cancel-edit:first').click();
347 | assert.ok(!!cancelValues, 'The controller\'s cancel action received parameters');
348 | });
349 |
350 | });
351 |
352 | test('it only allows primitive types in its row headers', function(assert) {
353 | set(this, 'table', notAllPrimitive);
354 | this.render(hbs`{{sort-filter-table table=table}}`);
355 |
356 | assert.equal(this.$('.table-header').length, 3, 'Keys pointing to non primitive types were not included');
357 | });
358 |
--------------------------------------------------------------------------------
/tests/pages/application.js:
--------------------------------------------------------------------------------
1 | import {
2 | create,
3 | visitable
4 | } from 'ember-cli-page-object';
5 | import demoNav from '../../tests/pages/components/demo-nav';
6 |
7 | export default create({
8 | visit: visitable('/'),
9 | demoNav
10 | });
11 |
--------------------------------------------------------------------------------
/tests/pages/components/demo-nav.js:
--------------------------------------------------------------------------------
1 | import {
2 | clickable,
3 | /* text */
4 | } from 'ember-cli-page-object';
5 |
6 | export default {
7 | tabInline: clickable('.t-tab-inline'),
8 | tabBlock: clickable('.t-tab-block'),
9 | tabData: clickable('.t-tab-data')
10 | };
11 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 | import { start } from 'ember-cli-qunit';
6 |
7 | setResolver(resolver);
8 | start();
9 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/tests/unit/.gitkeep
--------------------------------------------------------------------------------
/tests/unit/helpers/hash-contains-test.js:
--------------------------------------------------------------------------------
1 | import { hashContains } from 'dummy/helpers/hash-contains';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | hash contains');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = hashContains([{foo:'bax'}, 'bax']);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/is-image-test.js:
--------------------------------------------------------------------------------
1 | import { isImage } from 'dummy/helpers/is-image';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | is image');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = isImage(['foo.jpg']);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/is-number-test.js:
--------------------------------------------------------------------------------
1 | import { isNumber } from 'dummy/helpers/is-number';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | is number');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = isNumber([42]);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/is-object-test.js:
--------------------------------------------------------------------------------
1 | import { isObject } from 'dummy/helpers/is-object';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | is object');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = isObject([{'blue':42}]);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/is-primitive-test.js:
--------------------------------------------------------------------------------
1 | import { isPrimitive } from 'dummy/helpers/is-primitive';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | is primitive');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = isPrimitive([42]);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/is-private-key-test.js:
--------------------------------------------------------------------------------
1 | import { isPrivateKey } from 'dummy/helpers/is-private-key';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | is private key');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = isPrivateKey(['_foo']);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crodriguez1a/ember-sort-filter-table/21ab87c5e67d6fd4dd2ef55ac9b0bf9edc1fa18b/vendor/.gitkeep
--------------------------------------------------------------------------------