├── .bowerrc
├── .editorconfig
├── .ember-cli
├── .eslintignore
├── .eslintrc.js
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .ls-lint.yml
├── .npmignore
├── .npmrc
├── .prettierrc.js
├── .release-it.json
├── .sass-lint.yml
├── .vscode
└── settings.json
├── .watchmanconfig
├── .yarnrc
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── addon-test-support
├── helpers
│ ├── element.js
│ └── mouse.js
├── index.js
└── pages
│ ├── -private
│ ├── ember-table-body.js
│ ├── ember-table-footer.js
│ ├── ember-table-header.js
│ └── ember-table-loading-more.js
│ └── ember-table.js
├── addon
├── -private
│ ├── collapse-tree.js
│ ├── column-tree.js
│ ├── meta-cache.js
│ ├── sticky
│ │ └── table-sticky-polyfill.js
│ └── utils
│ │ ├── array.js
│ │ ├── default-to.js
│ │ ├── element.js
│ │ ├── ember.js
│ │ ├── observer.js
│ │ ├── reorder-indicators.js
│ │ └── sort.js
├── components
│ ├── -private
│ │ ├── base-table-cell.js
│ │ ├── row-wrapper.js
│ │ └── scroll-indicators
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ ├── ember-table-loading-more
│ │ └── component.js
│ ├── ember-table-simple-checkbox.js
│ ├── ember-table
│ │ ├── component.js
│ │ └── template.hbs
│ ├── ember-tbody
│ │ ├── component.js
│ │ └── template.hbs
│ ├── ember-td
│ │ ├── component.js
│ │ └── template.hbs
│ ├── ember-tfoot
│ │ ├── component.js
│ │ └── template.hbs
│ ├── ember-th
│ │ ├── component.js
│ │ ├── resize-handle
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ │ ├── sort-indicator
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ │ └── template.hbs
│ ├── ember-thead
│ │ ├── component.js
│ │ └── template.hbs
│ └── ember-tr
│ │ ├── component.js
│ │ └── template.hbs
└── styles
│ └── addon.css
├── app
├── components
│ ├── ember-table-loading-more.js
│ ├── ember-table-private
│ │ ├── row-wrapper.js
│ │ └── scroll-indicators.js
│ ├── ember-table-simple-checkbox.js
│ ├── ember-table.js
│ ├── ember-tbody.js
│ ├── ember-td.js
│ ├── ember-tfoot.js
│ ├── ember-th.js
│ ├── ember-th
│ │ ├── resize-handle.js
│ │ └── sort-indicator.js
│ ├── ember-thead.js
│ └── ember-tr.js
└── styles
│ └── ember-table
│ └── default.scss
├── config
├── addon-docs.js
└── deploy.js
├── ember-cli-build.js
├── index.js
├── jsconfig.json
├── package.json
├── testem.js
├── tests
├── acceptance
│ └── docs-test.js
├── dummy
│ ├── app
│ │ ├── app.js
│ │ ├── components
│ │ │ ├── custom-cell
│ │ │ │ ├── component.js
│ │ │ │ └── template.hbs
│ │ │ ├── custom-header
│ │ │ │ ├── component.js
│ │ │ │ └── template.hbs
│ │ │ ├── custom-row
│ │ │ │ └── component.js
│ │ │ ├── examples
│ │ │ │ ├── aborting-a-selection
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ ├── fixed-columns-dynamic
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ ├── infinite-scroll
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ ├── row-selection
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ ├── selected-rows
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ ├── selection-modes
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ │ └── sorting-empty-values
│ │ │ │ │ ├── component.js
│ │ │ │ │ └── template.hbs
│ │ │ ├── index-content
│ │ │ │ └── template.md
│ │ │ └── main
│ │ │ │ └── table-meta-data
│ │ │ │ └── cell-selection
│ │ │ │ ├── component.js
│ │ │ │ └── template.hbs
│ │ ├── controllers
│ │ │ ├── docs
│ │ │ │ └── guides
│ │ │ │ │ ├── body
│ │ │ │ │ ├── occlusion.js
│ │ │ │ │ ├── row-selection.js
│ │ │ │ │ └── rows-and-trees.js
│ │ │ │ │ ├── header
│ │ │ │ │ ├── columns.js
│ │ │ │ │ ├── fixed-columns.js
│ │ │ │ │ ├── scroll-indicators.js
│ │ │ │ │ ├── size-constraints.js
│ │ │ │ │ ├── sorting.js
│ │ │ │ │ └── subcolumns.js
│ │ │ │ │ └── main
│ │ │ │ │ ├── basic-table.js
│ │ │ │ │ ├── styling-the-table.js
│ │ │ │ │ ├── table-customization.js
│ │ │ │ │ └── table-meta-data.js
│ │ │ └── scenarios
│ │ │ │ ├── performance.js
│ │ │ │ └── simple.js
│ │ ├── index.html
│ │ ├── router.js
│ │ ├── routes
│ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── app.scss
│ │ │ ├── fixes.scss
│ │ │ └── tables.scss
│ │ ├── templates
│ │ │ ├── .gitkeep
│ │ │ ├── application.hbs
│ │ │ ├── docs.hbs
│ │ │ ├── docs
│ │ │ │ ├── guides
│ │ │ │ │ ├── body
│ │ │ │ │ │ ├── occlusion.md
│ │ │ │ │ │ ├── row-selection.md
│ │ │ │ │ │ └── rows-and-trees.md
│ │ │ │ │ ├── header
│ │ │ │ │ │ ├── column-keys.md
│ │ │ │ │ │ ├── columns.md
│ │ │ │ │ │ ├── fixed-columns.md
│ │ │ │ │ │ ├── scroll-indicators.md
│ │ │ │ │ │ ├── size-constraints.md
│ │ │ │ │ │ ├── sorting.md
│ │ │ │ │ │ └── subcolumns.md
│ │ │ │ │ └── main
│ │ │ │ │ │ ├── basic-table.md
│ │ │ │ │ │ ├── infinite-scroll.md
│ │ │ │ │ │ ├── styling-the-table.md
│ │ │ │ │ │ ├── table-customization.md
│ │ │ │ │ │ └── table-meta-data.md
│ │ │ │ ├── index.md
│ │ │ │ ├── quickstart.md
│ │ │ │ ├── testing
│ │ │ │ │ └── test-page.md
│ │ │ │ └── why-ember-table.md
│ │ │ ├── not-found.hbs
│ │ │ ├── scenarios.hbs
│ │ │ └── scenarios
│ │ │ │ ├── blank.hbs
│ │ │ │ ├── performance.hbs
│ │ │ │ └── simple.hbs
│ │ └── utils
│ │ │ ├── base-26.js
│ │ │ └── generators.js
│ ├── config
│ │ ├── ember-try.js
│ │ ├── environment.js
│ │ ├── optional-features.json
│ │ └── targets.js
│ └── public
│ │ ├── 404.html
│ │ ├── assets
│ │ └── images
│ │ │ ├── checkmark.svg
│ │ │ ├── favicon.ico
│ │ │ ├── logo.png
│ │ │ └── spinner.gif
│ │ └── robots.txt
├── helpers
│ ├── .gitkeep
│ ├── base-26.js
│ ├── generate-table.js
│ ├── module.js
│ └── warn-handlers.js
├── index.html
├── integration
│ └── components
│ │ ├── basic-test.js
│ │ ├── cell-test.js
│ │ ├── footer-test.js
│ │ ├── headers
│ │ ├── cell-test.js
│ │ ├── ember-th-test.js
│ │ ├── ember-thead-test.js
│ │ ├── main-test.js
│ │ ├── reorder-test.js
│ │ ├── resize-handle-test.js
│ │ ├── resize-test.js
│ │ └── sort-indicator-test.js
│ │ ├── loading-more-test.js
│ │ ├── meta-test.js
│ │ ├── row-test.js
│ │ ├── scroll-indicators-test.js
│ │ ├── selection-test.js
│ │ ├── sort-test.js
│ │ └── tree-test.js
├── test-helper.js
└── unit
│ ├── -private
│ ├── collapse-tree-test.js
│ ├── column-tree-test.js
│ ├── element-utils-test.js
│ ├── meta-cache-test.js
│ └── table-sticky-polyfill-test.js
│ └── .gitkeep
├── tsconfig.json
├── types
├── components
│ ├── ember-table-loading-more
│ │ └── component.d.ts
│ ├── ember-table
│ │ └── component.d.ts
│ ├── ember-tbody
│ │ └── component.d.ts
│ ├── ember-td
│ │ └── component.d.ts
│ ├── ember-tfoot
│ │ └── component.d.ts
│ ├── ember-th
│ │ ├── component.d.ts
│ │ ├── resize-handle
│ │ │ └── component.d.ts
│ │ └── sort-indicator
│ │ │ └── component.d.ts
│ ├── ember-thead
│ │ └── component.d.ts
│ └── ember-tr
│ │ └── component.d.ts
├── index.d.ts
└── template-registry.d.ts
├── 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 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 |
4 | # compiled output
5 | /declarations/
6 | /dist/
7 |
8 | # misc
9 | /coverage/
10 | !.*
11 | .*/
12 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@babel/eslint-parser',
4 | parserOptions: {
5 | ecmaVersion: 'latest',
6 | sourceType: 'module',
7 | requireConfigFile: false,
8 | babelOptions: {
9 | plugins: [
10 | [
11 | '@babel/plugin-proposal-decorators',
12 | {
13 | decoratorsBeforeExport: true,
14 | },
15 | ],
16 | ],
17 | },
18 | },
19 | plugins: ['ember'],
20 | extends: [
21 | 'eslint:recommended',
22 | 'plugin:ember/recommended',
23 | 'plugin:prettier/recommended',
24 | '@addepar',
25 | '@addepar/eslint-config/ember',
26 | ],
27 | env: {
28 | browser: true,
29 | },
30 | rules: {
31 | 'ember/no-classic-classes': 'off', // 41 errors
32 | 'ember/no-classic-components': 'off', // 20 errors
33 | 'ember/no-component-lifecycle-hooks': 'off', // 12 errors
34 | 'ember/no-get': 'off', // 406 errors
35 | 'ember/no-runloop': 'off', // 26 errors
36 | 'ember/require-computed-property-dependencies': 'off', // 23 errors
37 | 'ember/require-tagless-components': 'off', // 14 errors
38 | 'ember-best-practices/require-dependent-keys': 'off', // 56 errors
39 | 'no-restricted-imports': [
40 | 'error',
41 | {
42 | paths: [
43 | {
44 | name: '@ember/object',
45 | importNames: ['observer'],
46 | message: 'For compatibility, use `import { observer } from "-private/utils/observer"`',
47 | },
48 | {
49 | name: '@ember/object/observers',
50 | importNames: ['addObserver', 'removeObserver'],
51 | message:
52 | 'For compatibility, use `import { addObserver, removeObserver } from "-private/utils/observer"`',
53 | },
54 | ],
55 | },
56 | ],
57 | },
58 | overrides: [
59 | // node files
60 | {
61 | files: [
62 | './.eslintrc.js',
63 | './.prettierrc.js',
64 | './.stylelintrc.js',
65 | './.template-lintrc.js',
66 | './ember-cli-build.js',
67 | './index.js',
68 | './testem.js',
69 | './blueprints/*/index.js',
70 | './config/**/*.js',
71 | './tests/dummy/config/**/*.js',
72 | ],
73 | parserOptions: {
74 | sourceType: 'script',
75 | },
76 | env: {
77 | browser: false,
78 | node: true,
79 | },
80 | extends: ['plugin:n/recommended'],
81 | },
82 | {
83 | // test files
84 | files: ['tests/**/*-test.{js,ts}'],
85 | extends: ['plugin:qunit/recommended'],
86 | },
87 | ],
88 | };
89 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: "npm" # See documentation for possible values
6 | directory: "/" # Location of package manifests
7 | versioning-strategy: lockfile-only
8 | schedule:
9 | interval: "weekly"
10 | open-pull-requests-limit: 4
11 | labels:
12 | - "dependencies"
13 | groups:
14 | babel:
15 | applies-to: version-updates
16 | patterns:
17 | - "@babel/*"
18 | embroider:
19 | applies-to: version-updates
20 | patterns:
21 | - "@embroider/*"
22 | eslint:
23 | applies-to: version-updates
24 | patterns:
25 | - "@eslint/*"
26 | - eslint
27 | - eslint-*
28 | - package-ecosystem: "github-actions"
29 | # Workflow files stored in the
30 | # default location of `.github/workflows`
31 | directory: "/"
32 | schedule:
33 | interval: "weekly"
34 | open-pull-requests-limit: 2
35 | labels:
36 | - "dependencies"
37 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Ember-Table CI
2 | on:
3 | pull_request:
4 | types: [opened, synchronize, reopened, ready_for_review]
5 |
6 | jobs:
7 | test:
8 | name: Run Tests
9 | runs-on: ubuntu-latest
10 | timeout-minutes: 7
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Use Volta
17 | uses: volta-cli/action@v4
18 |
19 | - name: Node Modules Cache
20 | id: cache-npm
21 | uses: actions/cache@v4
22 | with:
23 | path: '**/node_modules'
24 | key: ci-yarn-${{ hashFiles('**/yarn.lock') }}
25 |
26 | - name: Install Dependencies
27 | if: steps.cache-npm.outputs.cache-hit != 'true'
28 | run: yarn install --frozen-lockfile
29 |
30 | - name: Lint
31 | run: yarn lint
32 |
33 | - name: Run Tests
34 | run: yarn test
35 | env:
36 | CI: true
37 |
38 | test-ember-try:
39 | name: Run Tests
40 | runs-on: ubuntu-latest
41 | timeout-minutes: 10
42 |
43 | strategy:
44 | fail-fast: false
45 | matrix:
46 | ember-version:
47 | [
48 | embroider-safe,
49 | # embroider-optimized,
50 | ember-lts-3.28,
51 | ember-lts-4.4,
52 | ember-lts-4.12,
53 | ember-lts-5.4,
54 | ember-lts-5.12,
55 | ember-release,
56 | # ember-production,
57 | ember-default-docs
58 | ]
59 |
60 | steps:
61 | - name: Checkout
62 | uses: actions/checkout@v4
63 |
64 | - name: Use Volta
65 | uses: volta-cli/action@v4
66 |
67 | - name: Stash yarn.lock for cache key
68 | run: cp yarn.lock __cache-key
69 |
70 | - name: Node Modules Cache
71 | id: cache-npm
72 | uses: actions/cache@v4
73 | with:
74 | path: |
75 | node_modules
76 | package.json
77 | yarn.lock
78 | __env
79 | key: ci-yarn-v3-${{ matrix.ember-version }}-${{ hashFiles('tests/dummy/config/ember-try.js', '__cache-key') }}
80 | restore-keys: |
81 | ci-yarn-${{ hashFiles('yarn.lock') }}
82 |
83 | - name: Install Dependencies
84 | if: steps.cache-npm.outputs.cache-hit != 'true'
85 | run: yarn install --frozen-lockfile
86 |
87 | - name: Run Tests
88 | run: yarn test:ember-compatibility ${{ matrix.ember-version }} --skip-cleanup
89 |
--------------------------------------------------------------------------------
/.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 | /.DS_Store
13 | /.eslintcache
14 | /.sass-cache
15 | /connect.lock
16 | /coverage/*
17 | /libpeerconnection.log
18 | npm-debug.log*
19 | yarn-error.log
20 | testem.log
21 |
--------------------------------------------------------------------------------
/.ls-lint.yml:
--------------------------------------------------------------------------------
1 | # Docs on ls-lint: https://ls-lint.org/
2 | # The linter can be run locally by `npx ls-lint [files]`
3 | # Pass `--debug` flag to see the list of all files/dirs that are linted
4 | ls:
5 | .js: kebab-case
6 | .d.ts: kebab-case
7 | .ts: kebab-case
8 | .html: kebab-case
9 | .md: kebab-case | screamingsnakecase
10 | .css: kebab-case
11 | .scss: kebab-case
12 | .dir: kebab-case
13 |
14 | ignore:
15 | - .git
16 | - node_modules
17 | - dist
18 | - ./.*
19 | - yarn.lock
20 | - yarn-error.log
21 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /dist
3 | /tests
4 | /tmp
5 | **/.gitkeep
6 | .bowerrc
7 | .editorconfig
8 | .ember-cli
9 | .eslintcache
10 | .eslintrc.js
11 | .gitignore
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | @addepar:registry = "https://registry.npmjs.org/"
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line n/no-unpublished-require
2 | module.exports = require('@addepar/prettier-config');
3 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "tagName": "v${version}"
4 | },
5 | "github": {
6 | "release": false
7 | },
8 | "hooks": {
9 | "after:release": "yarn run docs:deploy",
10 | "after:bump": "npx auto-changelog -p"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.sass-lint.yml:
--------------------------------------------------------------------------------
1 | options:
2 | config-file: node_modules/@addepar/sass-lint-config/config.yml
3 | rules:
4 | # Name Formats
5 | class-name-format:
6 | - 2
7 | -
8 | convention: strictbem
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.enable": true
3 | }
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | registry "https://registry.npmjs.org/"
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2017-2020 Addepar, Inc. All Rights Reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | 3. "Addepar" or "Addepar, Inc." may not be used to endorse or promote products
14 | derived from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY ADDEPAR, INC. "AS IS" AND ANY EXPRESS OR IMPLIED
17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 | EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25 | OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/addon-test-support/helpers/element.js:
--------------------------------------------------------------------------------
1 | import { getScale } from 'ember-table/-private/utils/element';
2 |
3 | export { getScale };
4 |
--------------------------------------------------------------------------------
/addon-test-support/helpers/mouse.js:
--------------------------------------------------------------------------------
1 | import { triggerEvent } from '@ember/test-helpers';
2 |
3 | export async function mouseDown(target, x, y) {
4 | await triggerEvent(target, 'pointerdown', {
5 | clientX: x,
6 | clientY: y,
7 | button: 0,
8 | });
9 | }
10 |
11 | export async function mouseMove(target, x, y) {
12 | await triggerEvent(target, 'pointermove', {
13 | clientX: x,
14 | clientY: y,
15 | button: 0,
16 | });
17 | }
18 |
19 | export async function mouseUp(target, x, y) {
20 | await triggerEvent(target, 'pointerup', {
21 | clientX: x,
22 | clientY: y,
23 | button: 0,
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/addon-test-support/index.js:
--------------------------------------------------------------------------------
1 | import TablePage from './pages/ember-table';
2 | import registerWaiter from 'ember-raf-scheduler/test-support/register-waiter';
3 | import { setSetupRowCountForTest } from 'ember-table/components/ember-tbody/component';
4 | import { setupTHeadForTest } from 'ember-table/components/ember-thead/component';
5 | import { setSimpleCheckboxForTest } from 'ember-table/components/ember-td/component';
6 |
7 | function setupForTest() {
8 | registerWaiter();
9 | setSetupRowCountForTest(true);
10 | setupTHeadForTest(true);
11 | setSimpleCheckboxForTest(true);
12 | }
13 |
14 | export { TablePage, setupForTest };
15 |
--------------------------------------------------------------------------------
/addon-test-support/pages/-private/ember-table-body.js:
--------------------------------------------------------------------------------
1 | import PageObject, {
2 | alias,
3 | triggerable,
4 | collection,
5 | hasClass,
6 | property,
7 | } from 'ember-classy-page-object';
8 | import { findElement } from 'ember-classy-page-object/extend';
9 |
10 | import { click } from '@ember/test-helpers';
11 |
12 | /**
13 | * Page object for single table `td` cell; also used by the footer page object
14 | * to represent footer cells.
15 | */
16 | export const BodyCell = PageObject.extend({
17 | scope: 'td',
18 |
19 | doubleClick: triggerable('dblclick'),
20 |
21 | isFirstColumn: hasClass('is-first-column'),
22 | isLastColumn: hasClass('is-last-column'),
23 | isSlack: hasClass('is-slack'),
24 | isResizing: hasClass('is-resizing'),
25 | });
26 |
27 | export default PageObject.extend({
28 | scope: 'tbody',
29 |
30 | /**
31 | Returns the height of the entire tbody element.
32 | */
33 | get height() {
34 | return findElement(this).offsetHeight;
35 | },
36 |
37 | /**
38 | Returns the number of rows in the body.
39 | */
40 | get rowCount() {
41 | let element = findElement(this);
42 | if (!element.hasAttribute('data-test-row-count')) {
43 | throw new Error(
44 | 'data-test-row-count attribute not found on the Ember Table tbody. Perhaps you need to run setupForTest? See https://github.com/Addepar/ember-table/blob/master/README.md'
45 | );
46 | }
47 | return Number(element.getAttribute('data-test-row-count'));
48 | },
49 |
50 | /**
51 | List of rows in table body. Each of property/function in this collections is the property/func
52 | of a single row selected by using calling rows.objectAt(index).
53 | */
54 | rows: collection({
55 | scope: 'tr',
56 |
57 | /**
58 | List of all cells for the selected row.
59 | */
60 | cells: collection('td:not([data-test-ember-table-slack])', BodyCell),
61 |
62 | /**
63 | Slack cell from this row, if present.
64 | */
65 | slackCell: BodyCell.extend({
66 | scope: 'td[data-test-ember-table-slack]',
67 | }),
68 |
69 | /**
70 | Returns the height of selected row.
71 | */
72 | get height() {
73 | return findElement(this).offsetHeight;
74 | },
75 |
76 | checkbox: {
77 | scope: '[data-test-select-row]',
78 | isChecked: property('checked'),
79 |
80 | async clickWith(options) {
81 | await click(findElement(this), options);
82 | },
83 | },
84 |
85 | checkboxContainer: {
86 | scope: '[data-test-select-row-container]',
87 |
88 | isHidden: hasClass('et-speech-only'),
89 | },
90 |
91 | toggleSelect: alias('checkbox.click'),
92 |
93 | collapse: {
94 | scope: '[data-test-collapse-row]',
95 | isCollapsed: property('checked'),
96 | },
97 |
98 | toggleCollapse: alias('collapse.click'),
99 |
100 | isSelected: hasClass('is-selected'),
101 |
102 | /**
103 | Helper function to click with options like the meta key and ctrl key set
104 |
105 | @param {Object} options - click event options
106 | */
107 | async clickWith(options) {
108 | await click(findElement(this), options);
109 | },
110 |
111 | doubleClick: triggerable('dblclick'),
112 | }),
113 |
114 | /**
115 | A shortcut to return cell page object specified by row & column indexes.
116 | */
117 | getCell(rowIndex, columnIndex) {
118 | return this.rows.objectAt(rowIndex).cells.objectAt(columnIndex);
119 | },
120 | });
121 |
--------------------------------------------------------------------------------
/addon-test-support/pages/-private/ember-table-footer.js:
--------------------------------------------------------------------------------
1 | import { collection } from 'ember-classy-page-object';
2 | import EmberTableBody, { BodyCell } from './ember-table-body';
3 |
4 | export default EmberTableBody.extend({
5 | scope: 'tfoot',
6 |
7 | footers: collection('td:not([data-test-ember-table-slack])', BodyCell),
8 | slackFooters: collection('td[data-test-ember-table-slack]', BodyCell),
9 | });
10 |
--------------------------------------------------------------------------------
/addon-test-support/pages/-private/ember-table-loading-more.js:
--------------------------------------------------------------------------------
1 | import PageObject from 'ember-classy-page-object';
2 | import { findElement } from 'ember-classy-page-object/extend';
3 |
4 | /**
5 | * Page object for "Loading More" component that renders beneath the body.
6 | */
7 | export default PageObject.extend({
8 | scope: '[data-test-ember-table-loading-more]',
9 |
10 | /**
11 | * Returns the pixel value of the `translateX` transform applied to center
12 | * the indicator in the scroll viewport.
13 | */
14 | get translateX() {
15 | let transform = findElement(this).style.transform;
16 | let result = transform.match(/translateX\((\d+)px\)/);
17 | return result ? parseInt(result[1]) : 0;
18 | },
19 |
20 | /**
21 | * Returns if the LoadingMore component is occupying space in the layout.
22 | */
23 | get isIncludedInLayout() {
24 | return findElement(this).style.display !== 'none';
25 | },
26 |
27 | /**
28 | * Returns if the LoadingMore component and user block is visible.
29 | */
30 | get isShown() {
31 | return findElement(this).style.visibility !== 'hidden';
32 | },
33 | });
34 |
--------------------------------------------------------------------------------
/addon/-private/meta-cache.js:
--------------------------------------------------------------------------------
1 | import { get } from '@ember/object';
2 |
3 | export function getOrCreate(obj, cache, Class) {
4 | if (cache.has(obj) === false) {
5 | cache.set(obj, Class.create ? Class.create() : new Class());
6 | }
7 |
8 | return cache.get(obj);
9 | }
10 |
11 | /**
12 | * Substitute for `Map` that allows non-identical object keys to share
13 | * identical values by specifying a key path for the associating keys.
14 | *
15 | * If no key path is specified, it behaves like a `Map`.
16 | *
17 | * @class MetaCache
18 | * @constructor
19 | * @param {Object} options
20 | */
21 | export default class MetaCache {
22 | constructor({ keyPath } = {}) {
23 | this.keyPath = keyPath;
24 |
25 | // in order to prevent memory leaks, we need to be able to clean the cache
26 | // manually when the table is destroyed or updated; this is why we use a
27 | // Map instead of WeakMap
28 | this._map = new Map();
29 | }
30 |
31 | get(obj) {
32 | let key = this._keyFor(obj);
33 | let entry = this._map.get(key);
34 | return entry ? entry[1] : entry;
35 | }
36 |
37 | getOrCreate(obj, Class) {
38 | return getOrCreate(obj, this, Class);
39 | }
40 |
41 | set(obj, meta) {
42 | let key = this._keyFor(obj);
43 | this._map.set(key, [obj, meta]);
44 | }
45 |
46 | has(obj) {
47 | let key = this._keyFor(obj);
48 | return this._map.has(key);
49 | }
50 |
51 | delete(obj) {
52 | let key = this._keyFor(obj);
53 | this._map.delete(key);
54 | }
55 |
56 | entries() {
57 | return this._map.values();
58 | }
59 |
60 | _keyFor(obj) {
61 | // falls back to `obj` as key if a legitimate key cannot be produced
62 | if (!obj || !this.keyPath) {
63 | return obj;
64 | }
65 |
66 | let key = get(obj, this.keyPath);
67 | return key ? key : obj;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/addon/-private/utils/array.js:
--------------------------------------------------------------------------------
1 | import { isArray } from '@ember/array';
2 | import { assert } from '@ember/debug';
3 |
4 | /**
5 | Genericizes `objectAt` so it can be run against a normal array or an Ember array
6 |
7 | @param {object|Array} arr
8 | @param {number} index
9 | @return {any}
10 | */
11 | export function objectAt(arr, index) {
12 | assert(
13 | 'arr must be an instance of a Javascript Array or implement `objectAt`',
14 | isArray(arr) || typeof arr.objectAt === 'function'
15 | );
16 |
17 | if (typeof arr.objectAt === 'function') {
18 | return arr.objectAt(index);
19 | }
20 |
21 | return arr[index];
22 | }
23 |
24 | export function splice(items, start, count, ...add) {
25 | if (typeof items.replace === 'function' && typeof items.objectAt === 'function') {
26 | return items.replace(start, count, add);
27 | }
28 |
29 | return items.splice(start, count, ...add);
30 | }
31 |
32 | /**
33 | * Cycle shift an internal [start..end] to [start + 1...end, start].
34 | */
35 | export function move(items, start, end) {
36 | let sourceItem = objectAt(items, start);
37 |
38 | splice(items, start, 1);
39 | splice(items, end, 0, sourceItem);
40 | }
41 |
--------------------------------------------------------------------------------
/addon/-private/utils/default-to.js:
--------------------------------------------------------------------------------
1 | import { computed } from '@ember/object';
2 |
3 | let VALUES = new WeakMap();
4 |
5 | function valuesFor(obj) {
6 | if (!VALUES.has(obj)) {
7 | VALUES.set(obj, Object.create(null));
8 | }
9 |
10 | return VALUES.get(obj);
11 | }
12 |
13 | export default function defaultTo(defaultValue) {
14 | return computed({
15 | get(key) {
16 | let values = valuesFor(this);
17 |
18 | if (!(key in values)) {
19 | values[key] = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
20 | }
21 |
22 | return values[key];
23 | },
24 |
25 | set(key, value) {
26 | let values = valuesFor(this);
27 |
28 | if (value === undefined) {
29 | values[key] = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
30 | } else {
31 | values[key] = value;
32 | }
33 |
34 | return values[key];
35 | },
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/addon/-private/utils/ember.js:
--------------------------------------------------------------------------------
1 | import { notifyPropertyChange as _notifyPropertyChange } from '@ember/object';
2 |
3 | export const notifyPropertyChange = _notifyPropertyChange;
4 |
--------------------------------------------------------------------------------
/addon/-private/utils/observer.js:
--------------------------------------------------------------------------------
1 | import { gte } from 'ember-compatibility-helpers';
2 | import { assert } from '@ember/debug';
3 |
4 | // eslint-disable-next-line no-restricted-imports
5 | import { observer as emberObserver } from '@ember/object';
6 |
7 | import {
8 | // eslint-disable-next-line no-restricted-imports
9 | addObserver as emberAddObserver,
10 | // eslint-disable-next-line no-restricted-imports
11 | removeObserver as emberRemoveObserver,
12 | } from '@ember/object/observers';
13 |
14 | const USE_ASYNC_OBSERVERS = gte('3.13.0');
15 |
16 | function asyncObserver(...args) {
17 | let fn = args.pop();
18 | let dependentKeys = args;
19 | let sync = false;
20 |
21 | // eslint-disable-next-line ember/no-observers
22 | return emberObserver({ dependentKeys, fn, sync });
23 | }
24 |
25 | function asyncAddObserver(...args) {
26 | let obj, path, target, method;
27 | let sync = false;
28 | obj = args[0];
29 | path = args[1];
30 | assert(
31 | `Expected 3 or 4 args for addObserver, got ${args.length}`,
32 | args.length === 3 || args.length === 4
33 | );
34 | if (args.length === 3) {
35 | target = null;
36 | method = args[2];
37 | } else if (args.length === 4) {
38 | target = args[2];
39 | method = args[3];
40 | }
41 |
42 | // eslint-disable-next-line ember/no-observers
43 | return emberAddObserver(obj, path, target, method, sync);
44 | }
45 |
46 | function asyncRemoveObserver(...args) {
47 | let obj, path, target, method;
48 | let sync = false;
49 | obj = args[0];
50 | path = args[1];
51 | assert(
52 | `Expected 3 or 4 args for addObserver, got ${args.length}`,
53 | args.length === 3 || args.length === 4
54 | );
55 | if (args.length === 3) {
56 | target = null;
57 | method = args[2];
58 | } else {
59 | target = args[2];
60 | method = args[3];
61 | }
62 | return emberRemoveObserver(obj, path, target, method, sync);
63 | }
64 |
65 | export const observer = USE_ASYNC_OBSERVERS ? asyncObserver : emberObserver;
66 | export const addObserver = USE_ASYNC_OBSERVERS ? asyncAddObserver : emberAddObserver;
67 | export const removeObserver = emberRemoveObserver ? asyncRemoveObserver : emberRemoveObserver;
68 |
--------------------------------------------------------------------------------
/addon/-private/utils/reorder-indicators.js:
--------------------------------------------------------------------------------
1 | import { getOuterClientRect, getInnerClientRect } from './element';
2 |
3 | function createElement(mainClass, dimensions) {
4 | let element = document.createElement('div');
5 |
6 | element.classList.add(mainClass);
7 |
8 | for (let key in dimensions) {
9 | element.style[key] = `${dimensions[key]}px`;
10 | }
11 |
12 | return element;
13 | }
14 |
15 | class ReorderIndicator {
16 | constructor(container, scale, element, bounds, mainClass, child) {
17 | this.container = container;
18 | this.element = element;
19 | this.bounds = bounds;
20 | this.child = child;
21 |
22 | let scrollTop = this.container.scrollTop;
23 | let scrollLeft = this.container.scrollLeft;
24 |
25 | let { top: containerTop, left: containerLeft } = getInnerClientRect(this.container, scale);
26 |
27 | let { top: elementTop, left: elementLeft, width: elementWidth } = getOuterClientRect(
28 | this.element
29 | );
30 |
31 | let top = (elementTop - containerTop) * scale + scrollTop;
32 | let left = (elementLeft - containerLeft) * scale + scrollLeft;
33 | let width = elementWidth * scale;
34 |
35 | this.originLeft = left;
36 | this.indicatorElement = createElement(mainClass, { top, left, width });
37 |
38 | if (child) {
39 | this.indicatorElement.appendChild(child);
40 | }
41 |
42 | this.container.appendChild(this.indicatorElement);
43 | this._left = left;
44 | }
45 |
46 | destroy() {
47 | this.container.removeChild(this.indicatorElement);
48 | }
49 |
50 | set width(newWidth) {
51 | this.indicatorElement.style.width = `${newWidth}px`;
52 | }
53 |
54 | get left() {
55 | return this._left;
56 | }
57 |
58 | set left(newLeft) {
59 | let { leftBound, rightBound } = this.bounds;
60 |
61 | let width = this.indicatorElement.offsetWidth;
62 |
63 | if (newLeft < leftBound) {
64 | newLeft = leftBound;
65 | } else if (newLeft + width > rightBound) {
66 | newLeft = rightBound - width;
67 | }
68 |
69 | if (newLeft < this.originLeft) {
70 | this.indicatorElement.classList.remove('et-reorder-direction-right');
71 | this.indicatorElement.classList.add('et-reorder-direction-left');
72 | } else {
73 | this.indicatorElement.classList.remove('et-reorder-direction-left');
74 | this.indicatorElement.classList.add('et-reorder-direction-right');
75 | }
76 |
77 | this.indicatorElement.style.left = `${newLeft}px`;
78 | this._left = newLeft;
79 | }
80 | }
81 |
82 | export class MainIndicator extends ReorderIndicator {
83 | constructor(container, scale, element, bounds) {
84 | let child = element.cloneNode(true);
85 |
86 | super(container, scale, element, bounds, 'et-reorder-main-indicator', child);
87 | }
88 | }
89 |
90 | export class DropIndicator extends ReorderIndicator {
91 | constructor(container, scale, element, bounds) {
92 | super(container, scale, element, bounds, 'et-reorder-drop-indicator');
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/addon/-private/utils/sort.js:
--------------------------------------------------------------------------------
1 | import { compare, isNone } from '@ember/utils';
2 | import { get } from '@ember/object';
3 |
4 | function merge(left, right, comparator) {
5 | let mergedArray = [];
6 | let leftIndex = 0;
7 | let rightIndex = 0;
8 |
9 | while (leftIndex < left.length && rightIndex < right.length) {
10 | let comparison = comparator(left[leftIndex], right[rightIndex]);
11 |
12 | if (comparison <= 0) {
13 | mergedArray.push(left[leftIndex]);
14 | leftIndex++;
15 | } else {
16 | mergedArray.push(right[rightIndex]);
17 | rightIndex++;
18 | }
19 | }
20 |
21 | if (leftIndex < left.length) {
22 | mergedArray.splice(mergedArray.length, 0, ...left.slice(leftIndex));
23 | }
24 |
25 | if (rightIndex < right.length) {
26 | mergedArray.splice(mergedArray.length, 0, ...right.slice(rightIndex));
27 | }
28 |
29 | return mergedArray;
30 | }
31 |
32 | /**
33 | * An implementation of the standard merge sort algorithm.
34 | *
35 | * This is necessary because we need a stable sorting algorithm that accepts
36 | * a general comparator. The built in sort function and Ember's sort functions
37 | * are not stable, and `_.sortBy` doesn't take a general comparator. Ideally
38 | * lodash would add a `_.sort` function whose API would mimic this function's.
39 | *
40 | * @param {Array} array The array to be sorted
41 | * @param {Comparator} comparator The comparator function to compare elements with.
42 | * @returns {Array} A sorted array
43 | */
44 | export function mergeSort(array, comparator = compare) {
45 | if (array.length <= 1) {
46 | return array;
47 | }
48 |
49 | let middleIndex = Math.floor(array.length / 2);
50 | let leftArray = mergeSort(array.slice(0, middleIndex), comparator);
51 | let rightArray = mergeSort(array.slice(middleIndex), comparator);
52 |
53 | return merge(leftArray, rightArray, comparator);
54 | }
55 |
56 | export function sortMultiple(itemA, itemB, sorts, compare, sortEmptyLast) {
57 | let compareValue;
58 |
59 | for (let { valuePath, isAscending } of sorts) {
60 | let valueA = get(itemA, valuePath);
61 | let valueB = get(itemB, valuePath);
62 |
63 | // The option only influences the outcome of an ascending sort.
64 | if (sortEmptyLast) {
65 | sortEmptyLast = isAscending;
66 | }
67 |
68 | compareValue = isAscending
69 | ? compare(valueA, valueB, sortEmptyLast)
70 | : -compare(valueA, valueB, sortEmptyLast);
71 |
72 | if (compareValue !== 0) {
73 | break;
74 | }
75 | }
76 |
77 | return compareValue;
78 | }
79 |
80 | function isExactlyNaN(value) {
81 | return typeof value === 'number' && isNaN(value);
82 | }
83 |
84 | function isEmptyString(value) {
85 | return typeof value === 'string' && value === '';
86 | }
87 |
88 | function isEmpty(value) {
89 | return isNone(value) || isExactlyNaN(value) || isEmptyString(value);
90 | }
91 |
92 | function orderEmptyValues(itemA, itemB, sortEmptyLast) {
93 | let aIsEmpty = isEmpty(itemA);
94 | let bIsEmpty = isEmpty(itemB);
95 | let less = -1;
96 | let more = 1;
97 |
98 | if (sortEmptyLast) {
99 | less = 1;
100 | more = -1;
101 | }
102 |
103 | if (aIsEmpty && !bIsEmpty) {
104 | return less;
105 | } else if (bIsEmpty && !aIsEmpty) {
106 | return more;
107 | } else if (isNone(itemA) && isExactlyNaN(itemB)) {
108 | return less;
109 | } else if (isExactlyNaN(itemA) && isNone(itemB)) {
110 | return more;
111 | } else {
112 | return 0;
113 | }
114 | }
115 |
116 | export function compareValues(itemA, itemB, sortEmptyLast) {
117 | if (isEmpty(itemA) || isEmpty(itemB)) {
118 | return orderEmptyValues(itemA, itemB, sortEmptyLast);
119 | }
120 |
121 | return compare(itemA, itemB);
122 | }
123 |
--------------------------------------------------------------------------------
/addon/components/-private/base-table-cell.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { equal, readOnly } from '@ember/object/computed';
3 | import { observer } from '../../-private/utils/observer';
4 | import { scheduleOnce } from '@ember/runloop';
5 | import { computed } from '@ember/object';
6 |
7 | export default Component.extend({
8 | // Provided by subclasses
9 | columnMeta: null,
10 | columnValue: null,
11 |
12 | attributeBindings: ['slackAttribute:data-test-ember-table-slack'],
13 | classNameBindings: [
14 | 'isFirstColumn',
15 | 'isLastColumn',
16 | 'isFixedLeft',
17 | 'isFixedRight',
18 | 'textAlign',
19 | 'isSlack',
20 | ],
21 |
22 | isFirstColumn: equal('columnMeta.index', 0),
23 | isLastColumn: readOnly('columnMeta.isLastRendered'),
24 | isFixedLeft: equal('columnMeta.isFixed', 'left'),
25 | isFixedRight: equal('columnMeta.isFixed', 'right'),
26 | isSlack: readOnly('columnMeta.isSlack'),
27 |
28 | // prevents `data-test-ember-table-slack="false"` on non-slack cells in Ember 2.4
29 | slackAttribute: computed('isSlack', function() {
30 | return this.get('isSlack') ? true : null;
31 | }),
32 |
33 | /**
34 | Indicates the text alignment of this cell
35 | */
36 | textAlign: computed('columnValue.textAlign', function() {
37 | let textAlign = this.get('columnValue.textAlign');
38 |
39 | if (['left', 'center', 'right'].includes(textAlign)) {
40 | return `ember-table__text-align-${textAlign}`;
41 | }
42 |
43 | return null;
44 | }),
45 |
46 | // eslint-disable-next-line
47 | scheduleUpdateStyles: observer(
48 | 'columnMeta.{width,offsetLeft,offsetRight}',
49 | 'isFixedLeft',
50 | 'isFixedRight',
51 |
52 | function() {
53 | scheduleOnce('actions', this, 'updateStyles');
54 | }
55 | ),
56 |
57 | updateStyles() {
58 | if (typeof FastBoot === 'undefined' && this.element) {
59 | let width = `${this.get('columnMeta.width')}px`;
60 |
61 | this.element.style.width = width;
62 | this.element.style.minWidth = width;
63 | this.element.style.maxWidth = width;
64 |
65 | this.element.style.left = this.get('isFixedLeft')
66 | ? `${Math.round(this.get('columnMeta.offsetLeft'))}px`
67 | : null;
68 | this.element.style.right = this.get('isFixedRight')
69 | ? `${Math.round(this.get('columnMeta.offsetRight'))}px`
70 | : null;
71 |
72 | if (this.get('isSlack')) {
73 | this.element.style.paddingLeft = 0;
74 | this.element.style.paddingRight = 0;
75 | this.element.style.display = width === '0px' ? 'none' : 'table-cell';
76 | }
77 | }
78 | },
79 |
80 | didInsertElement() {
81 | this._super(...arguments);
82 | this.updateStyles();
83 | },
84 | });
85 |
--------------------------------------------------------------------------------
/addon/components/-private/row-wrapper.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | import EmberObject, { get, setProperties, computed, defineProperty } from '@ember/object';
5 | import { alias } from '@ember/object/computed';
6 | import { A as emberA } from '@ember/array';
7 |
8 | import { notifyPropertyChange } from '../../-private/utils/ember';
9 | import { objectAt } from '../../-private/utils/array';
10 | import { observer } from '../../-private/utils/observer';
11 |
12 | const CellWrapper = EmberObject.extend({
13 | /* eslint-disable-next-line ember/no-observers, ember-best-practices/no-observers */
14 | columnValueValuePathDidChange: observer('columnValue.valuePath', function() {
15 | let columnValuePath = get(this, 'columnValue.valuePath');
16 | let cellValue = columnValuePath ? alias(`rowValue.${columnValuePath}`) : null;
17 |
18 | defineProperty(this, 'cellValue', cellValue);
19 | notifyPropertyChange(this, 'cellValue');
20 | }),
21 |
22 | cellMeta: computed('rowMeta', 'columnValue', function() {
23 | let rowMeta = get(this, 'rowMeta');
24 | let columnValue = get(this, 'columnValue');
25 |
26 | if (!rowMeta._cellMetaCache.has(columnValue)) {
27 | rowMeta._cellMetaCache.set(columnValue, EmberObject.create());
28 | }
29 |
30 | return rowMeta._cellMetaCache.get(columnValue);
31 | }),
32 | });
33 |
34 | const layout = hbs`{{yield this.api}}`;
35 |
36 | export default Component.extend({
37 | layout,
38 | tagName: '',
39 |
40 | canSelect: undefined,
41 | checkboxSelectionMode: undefined,
42 | columnMetaCache: undefined,
43 | columns: undefined,
44 | rowMetaCache: undefined,
45 | rowSelectionMode: undefined,
46 | rowToggleMode: undefined,
47 | rowValue: undefined,
48 | rowsCount: undefined,
49 |
50 | init() {
51 | this._super(...arguments);
52 |
53 | this._cells = emberA([]);
54 | },
55 |
56 | destroy() {
57 | this._cells.forEach(cell => cell.destroy());
58 |
59 | this._super(...arguments);
60 | },
61 |
62 | api: computed(
63 | 'rowValue',
64 | 'rowMeta',
65 | 'cells',
66 | 'canSelect',
67 | 'rowSelectionMode',
68 | 'rowToggleMode',
69 | 'rowsCount',
70 | function() {
71 | let rowValue = this.get('rowValue');
72 | let rowMeta = this.get('rowMeta');
73 | let cells = this.get('cells');
74 | let canSelect = this.get('canSelect');
75 | let rowSelectionMode = canSelect ? this.get('rowSelectionMode') : 'none';
76 | let rowToggleMode = this.get('rowToggleMode');
77 | let rowsCount = this.get('rowsCount');
78 |
79 | return { rowValue, rowMeta, cells, rowSelectionMode, rowToggleMode, rowsCount };
80 | }
81 | ),
82 |
83 | rowMeta: computed('rowValue', function() {
84 | let rowValue = this.get('rowValue');
85 | let rowMetaCache = this.get('rowMetaCache');
86 |
87 | return rowMetaCache.get(rowValue);
88 | }),
89 |
90 | cells: computed(
91 | 'rowValue',
92 | 'rowMeta',
93 | 'columns.[]',
94 | 'canSelect',
95 | 'checkboxSelectionMode',
96 | 'rowSelectionMode',
97 | function() {
98 | let columns = this.get('columns');
99 | let numColumns = get(columns, 'length');
100 |
101 | let rowValue = this.get('rowValue');
102 | let rowMeta = this.get('rowMeta');
103 | let rowsCount = this.get('rowsCount');
104 | let canSelect = this.get('canSelect');
105 | let checkboxSelectionMode = canSelect ? this.get('checkboxSelectionMode') : 'none';
106 | let rowSelectionMode = canSelect ? this.get('rowSelectionMode') : 'none';
107 |
108 | let { _cells } = this;
109 |
110 | if (numColumns !== _cells.length) {
111 | while (_cells.length < numColumns) {
112 | _cells.pushObject(CellWrapper.create());
113 | }
114 |
115 | while (_cells.length > numColumns) {
116 | _cells.popObject().destroy();
117 | }
118 | }
119 |
120 | _cells.forEach((cell, i) => {
121 | let columnValue = objectAt(columns, i);
122 | let columnMeta = this.get('columnMetaCache').get(columnValue);
123 |
124 | // eslint-disable-next-line ember/no-side-effects, ember-best-practices/no-side-effect-cp
125 | setProperties(cell, {
126 | checkboxSelectionMode,
127 | columnMeta,
128 | columnValue,
129 | rowMeta,
130 | rowSelectionMode,
131 | rowValue,
132 | rowsCount,
133 | });
134 | });
135 |
136 | return _cells;
137 | }
138 | ),
139 | });
140 |
--------------------------------------------------------------------------------
/addon/components/-private/scroll-indicators/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if this.showLeft}}
2 |
7 | {{/if}}
8 | {{#if this.showRight}}
9 |
14 | {{/if}}
15 | {{#if this.showTop}}
16 |
21 | {{/if}}
22 | {{#if this.showBottom}}
23 |
28 | {{/if}}
29 |
--------------------------------------------------------------------------------
/addon/components/ember-table-simple-checkbox.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import defaultTo from '../-private/utils/default-to';
3 |
4 | export default Component.extend({
5 | tagName: 'input',
6 |
7 | attributeBindings: [
8 | 'ariaLabel:aria-label',
9 | 'checked',
10 | 'disabled',
11 | 'indeterminate',
12 | 'type',
13 | 'value',
14 | 'dataTestSelectRow:data-test-select-row',
15 | 'dataTestCollapseRow:data-test-collapse-row',
16 | ],
17 |
18 | ariaLabel: undefined,
19 | checked: defaultTo(false),
20 | disabled: defaultTo(false),
21 | indeterminate: defaultTo(false),
22 | onChange: null,
23 | onClick: null,
24 | type: 'checkbox',
25 | value: null,
26 |
27 | click(event) {
28 | this.onClick?.(event);
29 | },
30 |
31 | change(event) {
32 | let checked = this.element.checked;
33 | let indeterminate = this.element.indeterminate;
34 | let value = this.get('value');
35 |
36 | // Checked and indeterminate state have been changed, but that's not DDAU!
37 | // Reset the change, send the action and wait for it to be changed manually
38 | this.element.checked = this.get('checked');
39 | this.element.indeterminate = this.get('indeterminate');
40 |
41 | this.onChange?.(checked, { value, indeterminate }, event);
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/addon/components/ember-table/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed } from '@ember/object';
3 | import { htmlSafe } from '@ember/template';
4 | import {
5 | setupTableStickyPolyfill,
6 | teardownTableStickyPolyfill,
7 | } from '../../-private/sticky/table-sticky-polyfill';
8 |
9 | import layout from './template';
10 |
11 | /**
12 | The primary Ember Table component. This component represents the root of the
13 | table, and manages high level state of all of its subcomponents. It does not
14 | have any arguments or actions itself - instead, all of those concerns are
15 | delegated to its children, who communicate to each other via the API.
16 |
17 | ```hbs
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | @yield {object} table - the API object yielded by the table
26 | @yield {Component} table.head - The table header component
27 | @yield {Component} table.body - The table body component
28 | @yield {Component} table.foot - The table footer component
29 | @class
30 | @public
31 | */
32 | export default Component.extend({
33 | layout,
34 | classNames: ['ember-table'],
35 | attributeBindings: ['dataTestEmberTable:data-test-ember-table'],
36 | dataTestEmberTable: true,
37 |
38 | didInsertElement() {
39 | this._super(...arguments);
40 |
41 | let thead = this.element.querySelector('thead');
42 | let tfoot = this.element.querySelector('tfoot');
43 |
44 | if (thead) {
45 | setupTableStickyPolyfill(thead);
46 | }
47 | if (tfoot) {
48 | setupTableStickyPolyfill(tfoot);
49 | }
50 | },
51 |
52 | willDestroyElement() {
53 | let thead = this.element.querySelector('thead');
54 | let tfoot = this.element.querySelector('tfoot');
55 |
56 | if (thead) {
57 | teardownTableStickyPolyfill(this.element.querySelector('thead'));
58 | }
59 |
60 | if (tfoot) {
61 | teardownTableStickyPolyfill(this.element.querySelector('tfoot'));
62 | }
63 |
64 | this._super(...arguments);
65 | },
66 |
67 | tableStyle: computed('tableWidth', function() {
68 | return htmlSafe(`width: ${this.get('tableWidth')}px;`);
69 | }),
70 |
71 | api: computed(function() {
72 | return {
73 | columns: null,
74 | registerColumnTree: this.registerColumnTree.bind(this),
75 | tableId: `${this.elementId}-overflow`,
76 | };
77 | }),
78 |
79 | registerColumnTree(columnTree) {
80 | this.set('api.columnTree', columnTree);
81 | },
82 | });
83 |
--------------------------------------------------------------------------------
/addon/components/ember-table/template.hbs:
--------------------------------------------------------------------------------
1 |
6 |
7 | {{yield (hash
8 | api=this.api
9 | head=(component "ember-thead" api=this.api)
10 | body=(component "ember-tbody" api=this.api)
11 | foot=(component "ember-tfoot" api=this.api)
12 | loadingMore=(component "ember-table-loading-more" api=this.api)
13 | )}}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/addon/components/ember-tbody/template.hbs:
--------------------------------------------------------------------------------
1 | {{#vertical-collection
2 | items=this.wrappedRows
3 | containerSelector=this._containerSelector
4 |
5 | estimateHeight=this.estimateRowHeight
6 | key=this.key
7 | staticHeight=this.staticHeight
8 | bufferSize=this.bufferSize
9 | renderAll=this.renderAll
10 |
11 | firstReached=this.firstReached
12 | lastReached=this.lastReached
13 | firstVisibleChanged=this.firstVisibleChanged
14 | lastVisibleChanged=this.lastVisibleChanged
15 | idForFirstItem=this.idForFirstItem
16 |
17 | as |rowValue|
18 | }}
19 |
30 | {{#if (has-block)}}
31 | {{yield (hash
32 | rowValue=api.rowValue
33 | rowMeta=api.rowMeta
34 | cells=api.cells
35 | rowSelectionMode=api.rowSelectionMode
36 | rowToggleMode=api.rowToggleMode
37 | rowsCount=api.rowsCount
38 |
39 | row=(component "ember-tr" api=api)
40 | )}}
41 | {{else}}
42 |
43 | {{/if}}
44 |
45 |
46 | {{else}}
47 | {{yield to='inverse'}}
48 | {{/vertical-collection}}
49 |
--------------------------------------------------------------------------------
/addon/components/ember-td/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if this.isFirstColumn}}
2 |
3 | {{#if this.canSelect}}
4 |
8 |
14 |
15 |
16 | {{/if}}
17 |
18 | {{#if this.canCollapse}}
19 |
20 |
26 |
27 |
28 | {{else}}
29 |
30 | {{/if}}
31 |
32 |
33 | {{#if (has-block)}}
34 | {{yield this.cellValue this.columnValue this.rowValue this.cellMeta this.columnMeta this.rowMeta this.rowsCount}}
35 | {{else}}
36 | {{this.cellValue}}
37 | {{/if}}
38 |
39 |
40 | {{else}}
41 | {{#if (has-block)}}
42 | {{yield this.cellValue this.columnValue this.rowValue this.cellMeta this.columnMeta this.rowMeta this.rowsCount}}
43 | {{else}}
44 | {{this.cellValue}}
45 | {{/if}}
46 | {{/if}}
47 |
--------------------------------------------------------------------------------
/addon/components/ember-tfoot/component.js:
--------------------------------------------------------------------------------
1 | import EmberTBody from '../ember-tbody/component';
2 | import { A as emberA } from '@ember/array';
3 |
4 | import { computed } from '@ember/object';
5 |
6 | import layout from './template';
7 |
8 | /**
9 | The table footer component. This component manages any footer rows which may
10 | be attached to the table, and has the same API as EmberTBody. It does not
11 | provide occlusion, because the number of footer rows is expected to be
12 | relatively small.
13 |
14 | ```hbs
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | @yield {object} foot - the API object yielded by the table footer
26 | @yield {Component} foot.row - The table row component
27 | @yield {object} foot.rowValue - The value for the currently yielded row
28 | @yield {object} foot.rowMeta - The meta for the currently yielded row
29 | @class
30 | @public
31 | */
32 | export default EmberTBody.extend({
33 | layout,
34 | tagName: 'tfoot',
35 |
36 | wrappedRowArray: computed('wrappedRows.[]', function() {
37 | let wrappedRows = this.get('wrappedRows');
38 | let wrappedRowsLength = wrappedRows.get('length');
39 |
40 | let arr = [];
41 |
42 | for (let i = 0; i < wrappedRowsLength; i++) {
43 | arr.push(wrappedRows.objectAt(i));
44 | }
45 |
46 | return emberA(arr);
47 | }),
48 | });
49 |
--------------------------------------------------------------------------------
/addon/components/ember-tfoot/template.hbs:
--------------------------------------------------------------------------------
1 | {{#each this.wrappedRowArray as |rowValue|}}
2 |
12 | {{#if (has-block)}}
13 | {{yield (hash
14 | rowValue=api.rowValue
15 | rowMeta=api.rowMeta
16 | cells=api.cells
17 | rowSelectionMode=api.rowSelectionMode
18 | rowsCount=api.rowsCount
19 |
20 | row=(component "ember-tr" api=api)
21 | )}}
22 | {{else}}
23 |
24 | {{/if}}
25 |
26 | {{/each}}
27 |
--------------------------------------------------------------------------------
/addon/components/ember-th/resize-handle/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import layout from './template';
3 |
4 | import { readOnly } from '@ember/object/computed';
5 |
6 | /**
7 | The table header cell resize handle component. This component renders an area to grab to resize a column.
8 |
9 | ```hbs
10 |
11 |
12 |
13 |
14 | {{columnValue.name}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 | @class
25 | @public
26 | */
27 | export default Component.extend({
28 | layout,
29 | tagName: '',
30 |
31 | /**
32 | The API object passed in by the table header cell
33 | @argument columnMeta
34 | @required
35 | @type object
36 | */
37 | columnMeta: null,
38 |
39 | isResizable: readOnly('columnMeta.isResizable'),
40 | });
41 |
--------------------------------------------------------------------------------
/addon/components/ember-th/resize-handle/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if this.isResizable}}
2 |
4 | {{/if}}
--------------------------------------------------------------------------------
/addon/components/ember-th/sort-indicator/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import layout from './template';
3 |
4 | import { readOnly } from '@ember/object/computed';
5 |
6 | /**
7 | The table header cell sort indicator component. This component renders the state of the sort on the column (ascending/descending/none).
8 |
9 | ```hbs
10 |
11 |
12 |
13 |
14 | {{columnValue.name}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ```
24 | @yield {object} columnMeta - The meta object associated with this column
25 | @class
26 | */
27 |
28 | export default Component.extend({
29 | layout,
30 | tagName: '',
31 |
32 | /**
33 | The API object passed in by the table header cell
34 | @argument columnMeta
35 | @required
36 | @type object
37 | */
38 | columnMeta: null,
39 |
40 | isSortable: readOnly('columnMeta.isSortable'),
41 |
42 | isSorted: readOnly('columnMeta.isSorted'),
43 |
44 | isSortedAsc: readOnly('columnMeta.isSortedAsc'),
45 |
46 | isMultiSorted: readOnly('columnMeta.isMultiSorted'),
47 |
48 | sortIndex: readOnly('columnMeta.sortIndex'),
49 | });
50 |
--------------------------------------------------------------------------------
/addon/components/ember-th/sort-indicator/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if this.isSorted}}
2 |
3 | {{#if (has-block)}}
4 | {{yield this.columnMeta}}
5 | {{else}}
6 | {{#if this.isMultiSorted}}
7 | {{this.sortIndex}}
8 | {{/if}}
9 | {{/if}}
10 |
11 | {{/if}}
12 |
13 | {{#if this.isSortable}}
14 | Toggle Sort
15 | {{/if}}
16 |
--------------------------------------------------------------------------------
/addon/components/ember-th/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if (has-block)}}
2 | {{yield this.columnValue this.columnMeta this.rowMeta}}
3 | {{else}}
4 | {{this.columnValue.name}}
5 |
6 |
7 |
8 |
9 | {{/if}}
10 |
--------------------------------------------------------------------------------
/addon/components/ember-thead/template.hbs:
--------------------------------------------------------------------------------
1 | {{#each this.wrappedRows as |api|}}
2 | {{#if (has-block)}}
3 | {{yield (hash
4 | cells=api.cells
5 | isHeader=api.isHeader
6 | rowsCount=api.rowsCount
7 |
8 | row=(component "ember-tr" api=api)
9 | )}}
10 | {{else}}
11 |
12 | {{/if}}
13 | {{/each}}
14 |
--------------------------------------------------------------------------------
/addon/components/ember-tr/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed } from '@ember/object';
3 | import { readOnly } from '@ember/object/computed';
4 |
5 | import { closest } from '../../-private/utils/element';
6 |
7 | import layout from './template';
8 | import { SELECT_MODE } from '../../-private/collapse-tree';
9 |
10 | /**
11 | The table row component. This component manages row level concerns, and yields
12 | an API object that contains the cell component, the cell/column/row values,
13 | and the cell/column/row meta objects. It is used in both the header and the
14 | body, mirroring the structure of native HTML tables.
15 |
16 | ```hbs
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ```
31 |
32 | @yield {object} row - the API object yielded by the table row
33 | @yield {Component} row.cell - The table cell contextual component
34 |
35 | @yield {any} row.cellValue - The value for the currently yielded cell
36 | @yield {object} row.cellMeta - The meta for the currently yielded cell
37 |
38 | @yield {object} row.columnValue - The value for the currently yielded column
39 | @yield {object} row.columnMeta - The meta for the currently yielded column
40 |
41 | @yield {object} row.rowValue - The value for the currently yielded row
42 | @yield {object} row.rowMeta - The meta for the currently yielded row
43 |
44 | @class
45 | @public
46 | */
47 | export default Component.extend({
48 | layout,
49 | tagName: 'tr',
50 | classNames: ['et-tr'],
51 | classNameBindings: ['isSelected', 'isGroupSelected', 'isSelectable'],
52 |
53 | /**
54 | The API object passed in by the table body, header, or footer
55 | @argument api
56 | @required
57 | @type object
58 | */
59 | api: null,
60 |
61 | /**
62 | Action sent when the user clicks this element
63 | @argument onClick
64 | @type Action?
65 | */
66 | onClick: null,
67 |
68 | /**
69 | Action sent when the user double clicks this element
70 | @argument onDoubleClick
71 | @type Action?
72 | */
73 | onDoubleClick: null,
74 |
75 | rowValue: readOnly('api.rowValue'),
76 |
77 | rowMeta: readOnly('api.rowMeta'),
78 |
79 | cells: readOnly('api.cells'),
80 |
81 | rowSelectionMode: readOnly('api.rowSelectionMode'),
82 |
83 | rowToggleMode: readOnly('api.rowToggleMode'),
84 |
85 | isHeader: readOnly('api.isHeader'),
86 |
87 | isSelected: readOnly('rowMeta.isSelected'),
88 |
89 | isGroupSelected: readOnly('rowMeta.isGroupSelected'),
90 |
91 | isSelectable: computed('rowSelectionMode', function() {
92 | let rowSelectionMode = this.get('rowSelectionMode');
93 |
94 | return rowSelectionMode === SELECT_MODE.MULTIPLE || rowSelectionMode === SELECT_MODE.SINGLE;
95 | }),
96 |
97 | click(event) {
98 | let rowSelectionMode = this.get('rowSelectionMode');
99 | let inputParent = closest(event.target, 'input, button, label, a, select');
100 |
101 | if (!inputParent) {
102 | let rowMeta = this.get('rowMeta');
103 |
104 | if (rowMeta && rowSelectionMode === SELECT_MODE.MULTIPLE) {
105 | let toggle = event.ctrlKey || event.metaKey || this.get('rowToggleMode');
106 | let range = event.shiftKey;
107 |
108 | rowMeta.select({ toggle, range });
109 | } else if (rowMeta && rowSelectionMode === SELECT_MODE.SINGLE) {
110 | rowMeta.select({ single: true });
111 | }
112 | }
113 |
114 | this.sendEventAction('onClick', event);
115 | },
116 |
117 | doubleClick(event) {
118 | this.sendEventAction('onDoubleClick', event);
119 | },
120 |
121 | sendEventAction(action, event) {
122 | let rowValue = this.get('rowValue');
123 | let rowMeta = this.get('rowMeta');
124 |
125 | let closureAction = this[action];
126 |
127 | closureAction?.({
128 | event,
129 | rowValue,
130 | rowMeta,
131 | });
132 | },
133 | });
134 |
--------------------------------------------------------------------------------
/addon/components/ember-tr/template.hbs:
--------------------------------------------------------------------------------
1 | {{#each this.cells as |api|}}
2 | {{#if (has-block)}}
3 | {{#if this.isHeader}}
4 | {{yield (hash
5 | columnValue=api.columnValue
6 | columnMeta=api.columnMeta
7 |
8 | sorts=api.sorts
9 | sendUpdateSort=api.sendUpdateSort
10 |
11 | rowMeta=api.rowMeta
12 | rowsCount=api.rowsCount
13 |
14 | cell=(component "ember-th" api=api)
15 | )}}
16 | {{else}}
17 | {{yield (hash
18 | api=api
19 |
20 | cellValue=api.cellValue
21 | cellMeta=api.cellMeta
22 |
23 | columnValue=api.columnValue
24 | columnMeta=api.columnMeta
25 |
26 | rowValue=api.rowValue
27 | rowMeta=api.rowMeta
28 |
29 | rowsCount=api.rowsCount
30 |
31 | cell=(component "ember-td" api=api)
32 | )}}
33 | {{/if}}
34 | {{else if this.isHeader}}
35 |
36 | {{else}}
37 |
38 | {{/if}}
39 | {{/each}}
40 |
--------------------------------------------------------------------------------
/addon/styles/addon.css:
--------------------------------------------------------------------------------
1 | .ember-table {
2 | position: relative;
3 | height: 100%;
4 | width: 100%;
5 | box-sizing: border-box;
6 | }
7 |
8 | .ember-table .ember-table-overflow {
9 | overflow: auto;
10 | max-height: 100%;
11 | max-width: 100%;
12 | }
13 |
14 | .ember-table table {
15 | border-spacing: 0;
16 | table-layout: fixed;
17 | box-sizing: border-box;
18 | }
19 |
20 | .ember-table td,
21 | .ember-table th {
22 | box-sizing: border-box;
23 | }
24 |
25 | .ember-table td.is-fixed-left,
26 | .ember-table td.is-fixed-right,
27 | .ember-table th.is-fixed-left,
28 | .ember-table th.is-fixed-right {
29 | position: -webkit-sticky;
30 | position: sticky;
31 | left: 0;
32 | }
33 |
34 | .ember-table td.ember-table__text-align-left,
35 | .ember-table th.ember-table__text-align-left {
36 | text-align: left;
37 | }
38 |
39 | .ember-table td.ember-table__text-align-center,
40 | .ember-table th.ember-table__text-align-center {
41 | text-align: center;
42 | }
43 |
44 | .ember-table td.ember-table__text-align-right,
45 | .ember-table th.ember-table__text-align-right {
46 | text-align: right;
47 | }
48 |
49 | .ember-table th {
50 | z-index: 2;
51 | }
52 |
53 | .ember-table th:not(.is-fixed-right) .et-header-resize-area {
54 | right: 0;
55 | }
56 |
57 | .ember-table th.is-fixed-right .et-header-resize-area {
58 | left: 0;
59 | }
60 |
61 | .ember-table td.is-fixed-left,
62 | .ember-table td.is-fixed-right {
63 | z-index: 3;
64 | }
65 |
66 | .ember-table th.is-fixed-left,
67 | .ember-table th.is-fixed-right {
68 | z-index: 4;
69 | }
70 |
71 | .ember-table th.is-sortable {
72 | cursor: pointer;
73 | }
74 |
75 | .ember-table thead {
76 | position: -webkit-sticky;
77 | position: sticky;
78 | top: 0;
79 | z-index: 2;
80 | box-sizing: border-box;
81 | }
82 |
83 | .ember-table tbody {
84 | box-sizing: border-box;
85 | }
86 |
87 | .ember-table tfoot {
88 | position: -webkit-sticky;
89 | position: sticky;
90 | bottom: 0;
91 | z-index: 2;
92 | box-sizing: border-box;
93 | }
94 |
95 | .ember-table .scroll-indicator {
96 | position: absolute;
97 | z-index: 5;
98 | }
99 |
100 | .ember-table .scroll-indicator__left,
101 | .ember-table .scroll-indicator__right {
102 | top: 0;
103 | width: 8px;
104 | height: 100%;
105 | }
106 |
107 | .ember-table .scroll-indicator__left {
108 | background: linear-gradient(to right, rgba(168, 168, 168, 0.4), rgba(168, 168, 168, 0));
109 | }
110 |
111 | .ember-table .scroll-indicator__right {
112 | right: 0;
113 | background: linear-gradient(to left, rgba(168, 168, 168, 0.4), rgba(168, 168, 168, 0));
114 | }
115 |
116 | .ember-table .scroll-indicator__top,
117 | .ember-table .scroll-indicator__bottom {
118 | left: 0;
119 | width: 100%;
120 | height: 8px;
121 | }
122 |
123 | .ember-table .scroll-indicator__top {
124 | top: 0;
125 | background: linear-gradient(to bottom, rgba(168, 168, 168, 0.4), rgba(168, 168, 168, 0));
126 | }
127 |
128 | .ember-table .scroll-indicator__bottom {
129 | bottom: 0;
130 | background: linear-gradient(to top, rgba(168, 168, 168, 0.4), rgba(168, 168, 168, 0));
131 | }
132 |
133 | .ember-table.et-unselectable {
134 | user-select: none;
135 | }
136 |
137 | .ember-table .et-header-resize-area {
138 | cursor: col-resize;
139 | width: 10px;
140 | height: 100%;
141 | position: absolute;
142 | top: 0;
143 | }
144 |
145 | .ember-table .et-speech-only {
146 | display: none !important;
147 | }
148 |
149 | @media speech {
150 | .ember-table .et-speech-only {
151 | display: block !important;
152 | }
153 | }
154 |
155 | .ember-table .ember-table-loading-more {
156 | display: inline-block;
157 | }
158 |
--------------------------------------------------------------------------------
/app/components/ember-table-loading-more.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-table-loading-more/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-table-private/row-wrapper.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/-private/row-wrapper';
2 |
--------------------------------------------------------------------------------
/app/components/ember-table-private/scroll-indicators.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/-private/scroll-indicators/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-table-simple-checkbox.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-table-simple-checkbox';
2 |
--------------------------------------------------------------------------------
/app/components/ember-table.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-table/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-tbody.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-tbody/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-td.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-td/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-tfoot.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-tfoot/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-th.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-th/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-th/resize-handle.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-th/resize-handle/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-th/sort-indicator.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-th/sort-indicator/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-thead.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-thead/component';
2 |
--------------------------------------------------------------------------------
/app/components/ember-tr.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-table/components/ember-tr/component';
2 |
--------------------------------------------------------------------------------
/app/styles/ember-table/default.scss:
--------------------------------------------------------------------------------
1 | /************************************************************************************************/
2 | /* Table default style */
3 | /************************************************************************************************/
4 | $table-header-background-color: #f8f8f8;
5 | $table-border-color: #dcdcdc;
6 | $table-hover-color: #e5edf8;
7 |
8 | .ember-table {
9 | border: solid 1px #dddddd;
10 |
11 | th,
12 | td {
13 | white-space: nowrap;
14 | text-overflow: ellipsis;
15 | overflow: hidden;
16 | font-size: 20px;
17 | padding: 4px 10px;
18 | }
19 |
20 | tbody {
21 | td {
22 | border-top: none;
23 | border-left: none;
24 | border-bottom: $table-border-color 1px dotted;
25 | border-right: $table-border-color 1px solid;
26 | background-color: #ffffff;
27 |
28 | &:last-child {
29 | border-right: none;
30 | }
31 |
32 | &.is-fixed-right {
33 | border-left: solid 1px $table-border-color;
34 | }
35 | }
36 | }
37 |
38 | th,
39 | tfoot td {
40 | padding: 5px 0 3px;
41 | background-color: $table-header-background-color;
42 | font-family: 'Univers LT W01 65 Bold';
43 | line-height: 1.4;
44 | font-weight: bold;
45 | text-align: center;
46 | box-sizing: border-box;
47 | }
48 |
49 | tfoot td {
50 | border-top: 1px solid $table-border-color;
51 | border-right: solid 1px $table-border-color;
52 |
53 | &:last-child {
54 | border-right: none;
55 | }
56 | }
57 |
58 | thead th {
59 | border-bottom: 1px solid $table-border-color;
60 | border-right: solid 1px $table-border-color;
61 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
62 | position: relative;
63 | overflow: hidden;
64 |
65 | &:last-child {
66 | border-right: none;
67 | }
68 |
69 | &.is-fixed-right {
70 | border-left: solid 1px $table-border-color;
71 |
72 | .et-header-resize-area {
73 | left: 0;
74 | }
75 | }
76 | }
77 |
78 | tr:hover {
79 | th {
80 | cursor: pointer;
81 | }
82 |
83 | td {
84 | background-color: $table-hover-color;
85 | }
86 | }
87 |
88 | tr.is-selected {
89 | td,
90 | th {
91 | background-color: #227ecb;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/config/addon-docs.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | // eslint-disable-next-line
5 | const AddonDocsConfig = require('ember-cli-addon-docs/lib/config');
6 |
7 | module.exports = class extends AddonDocsConfig {
8 | // See https://ember-learn.github.io/ember-cli-addon-docs/latest/docs/deploying
9 | // for details on configuration you can override here.
10 | };
11 |
--------------------------------------------------------------------------------
/config/deploy.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function(deployTarget) {
5 | let ENV = {
6 | build: {},
7 | // include other plugin configuration that applies to all deploy targets here
8 | };
9 |
10 | if (deployTarget === 'development') {
11 | ENV.build.environment = 'development';
12 | // configure other plugins for development deploy target here
13 | }
14 |
15 | if (deployTarget === 'staging') {
16 | ENV.build.environment = 'production';
17 | // configure other plugins for staging deploy target here
18 | }
19 |
20 | if (deployTarget === 'production') {
21 | ENV.build.environment = 'production';
22 | // configure other plugins for production deploy target here
23 | }
24 |
25 | // Note: if you need to build some configuration asynchronously, you can return
26 | // a promise that resolves with the ENV object instead of returning the
27 | // ENV object synchronously.
28 | return ENV;
29 | };
30 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | let app = new EmberAddon(defaults, {
7 | 'ember-cli-babel': {
8 | includePolyfill: false,
9 | },
10 | babel: {
11 | plugins: [
12 | '@babel/plugin-proposal-object-rest-spread',
13 | '@babel/plugin-proposal-optional-chaining',
14 | '@babel/plugin-proposal-nullish-coalescing-operator',
15 | '@babel/plugin-proposal-numeric-separator',
16 | '@babel/plugin-proposal-optional-catch-binding',
17 | ],
18 | },
19 | 'ember-faker': {
20 | /* Always enable for dummy app because the docs examples use faker */
21 | enabled: true,
22 | },
23 | });
24 |
25 | /*
26 | This build file specifies the options for the dummy test app of this
27 | addon, located in `/tests/dummy`
28 | This build file does *not* influence how the addon or the app using it
29 | behave. You most likely want to be modifying `./index.js` or app's build file
30 | */
31 |
32 | let { maybeEmbroider } = require('@embroider/test-setup');
33 | return maybeEmbroider(app);
34 | };
35 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const VersionChecker = require('ember-cli-version-checker');
3 |
4 | module.exports = {
5 | name: 'ember-table',
6 |
7 | options: {
8 | babel: {
9 | plugins: [
10 | '@babel/plugin-proposal-object-rest-spread',
11 | '@babel/plugin-proposal-optional-chaining',
12 | '@babel/plugin-proposal-nullish-coalescing-operator',
13 | '@babel/plugin-proposal-numeric-separator',
14 | '@babel/plugin-proposal-optional-catch-binding',
15 | ],
16 | },
17 | },
18 |
19 | included() {
20 | this._super.included.apply(this, arguments);
21 |
22 | this.checker = new VersionChecker(this.project);
23 |
24 | let importOptions;
25 |
26 | // If `ember-cli-fastboot` is being used in the root project, use the
27 | // fastboot transform when importing
28 | if (this.project.findAddonByName('ember-cli-fastboot')) {
29 | importOptions = {
30 | using: [
31 | {
32 | transformation: 'fastbootShim',
33 | },
34 | ],
35 | };
36 | }
37 |
38 | this.import('node_modules/css-element-queries/src/ResizeSensor.js', importOptions);
39 |
40 | this.import('node_modules/hammerjs/hammer.js', importOptions);
41 | },
42 |
43 | isDevelopingAddon() {
44 | // this prevents templates from being cached before we can strip them out
45 | // they get cached by jshintAddonTree before we can intervene
46 | if (this.checker.for('ember-source').gte('2.3.0')) {
47 | return this._super.isDevelopingAddon.apply(this, arguments);
48 | }
49 |
50 | return false;
51 | },
52 | };
53 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "experimentalDecorators": true
5 | },
6 | "exclude": ["node_modules", "tmp", "dist"]
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-table",
3 | "version": "6.0.0-9",
4 | "description": "An addon to support large data set and a number of features around table.",
5 | "keywords": [
6 | "ember-addon"
7 | ],
8 | "license": "BSD-3-Clause",
9 | "author": "",
10 | "directories": {
11 | "doc": "doc",
12 | "test": "tests"
13 | },
14 | "repository": "https://github.com/Addepar/ember-table",
15 | "scripts": {
16 | "build": "ember build",
17 | "docs:deploy": "ember try:one ember-default-docs --- ember deploy production",
18 | "lint": "concurrently \"yarn:lint:*(!fix)\" --names \"lint:\"",
19 | "lint:files": "ls-lint",
20 | "lint:js": "eslint . --cache",
21 | "lint:js:fix": "eslint . --fix",
22 | "lint:sass": "concurrently \"yarn:lint:sass:*\"",
23 | "lint:sass:sass-lint": "sass-lint -c .sass-lint.yml --verbose --no-exit",
24 | "lint:sass:prettier": "prettier --list-different '{addon,app}/styles/**/*.scss' 'tests/dummy/app/styles/**/*.scss'",
25 | "release": "release-it",
26 | "start": "ember serve",
27 | "test": "ember test",
28 | "test:ember-compatibility": "ember try:one"
29 | },
30 | "dependencies": {
31 | "@babel/core": "^7.0.0-0",
32 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13",
33 | "@babel/plugin-proposal-numeric-separator": "^7.12.13",
34 | "@babel/plugin-proposal-object-rest-spread": "^7.12.13",
35 | "@babel/plugin-proposal-optional-catch-binding": "^7.12.13",
36 | "@babel/plugin-proposal-optional-chaining": "^7.12.13",
37 | "@html-next/vertical-collection": "^4.0.0",
38 | "css-element-queries": "^0.4.0",
39 | "ember-classy-page-object": "^0.8.0",
40 | "ember-cli-babel": "^7.12.0",
41 | "ember-cli-htmlbars": "^6.0.0",
42 | "ember-cli-node-assets": "^0.2.2",
43 | "ember-cli-version-checker": "^5.1.2",
44 | "ember-compatibility-helpers": "^1.2.6",
45 | "ember-raf-scheduler": "^0.3.0",
46 | "ember-test-selectors": "^7.1.0",
47 | "hammerjs": "^2.0.8"
48 | },
49 | "devDependencies": {
50 | "@addepar/eslint-config": "^4.0.2",
51 | "@addepar/prettier-config": "^1.0.0",
52 | "@addepar/sass-lint-config": "^2.0.1",
53 | "@addepar/style-toolbox": "~0.8.1",
54 | "@babel/eslint-parser": "^7.27.1",
55 | "@babel/plugin-proposal-decorators": "^7.27.1",
56 | "@ember/optional-features": "^2.0.0",
57 | "@ember/string": "^3.0.0",
58 | "@ember/test-helpers": "^5.2.2",
59 | "@embroider/test-setup": "^4.0.0",
60 | "@glimmer/component": "^1.1.2",
61 | "@ls-lint/ls-lint": "^2.2.3",
62 | "@tsconfig/ember": "^1.0.1",
63 | "@types/ember__component": "^4.0.10",
64 | "broccoli-asset-rev": "^3.0.0",
65 | "concurrently": "^9.1.2",
66 | "ember-a11y-testing": "^7.1.2",
67 | "ember-auto-import": "^2.4.2",
68 | "ember-cli": "~3.28.0",
69 | "ember-cli-dependency-checker": "^3.2.0",
70 | "ember-cli-inject-live-reload": "^2.0.1",
71 | "ember-cli-sass": "^10.0.0",
72 | "ember-cli-sri": "^2.1.0",
73 | "ember-cli-terser": "^4.0.0",
74 | "ember-disable-prototype-extensions": "^1.1.2",
75 | "ember-faker": "^1.5.0",
76 | "ember-load-initializers": "^2.0.0",
77 | "ember-math-helpers": "~2.11.3",
78 | "ember-qunit": "^9.0.3",
79 | "ember-radio-button": "^2.0.0",
80 | "ember-resolver": "^8.0.2",
81 | "ember-source": "~3.28.0",
82 | "ember-truth-helpers": "^3.0.0",
83 | "ember-try": "^4.0.0",
84 | "eslint": "^8.57.1",
85 | "eslint-config-prettier": "^10.1.5",
86 | "eslint-plugin-ember": "^12.5.0",
87 | "eslint-plugin-n": "^17.18.0",
88 | "eslint-plugin-prettier": "^5.4.0",
89 | "eslint-plugin-qunit": "^8.1.2",
90 | "husky": "^1.3.1",
91 | "latest-version": "^9.0.0",
92 | "loader.js": "^4.2.3",
93 | "qunit": "^2.24.1",
94 | "qunit-dom": "^3.4.0",
95 | "release-it": "^15.5.0",
96 | "sass": "^1.26.10",
97 | "sass-lint": "^1.13.1",
98 | "typescript": "^4.8.4",
99 | "webpack": "^5.0.0"
100 | },
101 | "engines": {
102 | "node": ">= 14.*"
103 | },
104 | "ember": {
105 | "edition": "octane"
106 | },
107 | "ember-addon": {
108 | "configPath": "tests/dummy/config"
109 | },
110 | "homepage": "https://Addepar.github.io/ember-table",
111 | "resolutions": {
112 | "@ember/test-waiters": "^3.0.2",
113 | "prettier": "1.18.2"
114 | },
115 | "typesVersions": {
116 | "*": {
117 | "*": [
118 | "types/*"
119 | ]
120 | }
121 | },
122 | "volta": {
123 | "node": "18.20.5",
124 | "yarn": "1.22.19"
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/tests/acceptance/docs-test.js:
--------------------------------------------------------------------------------
1 | import { module, test as qunitTest, skip as qunitSkip } from 'qunit';
2 | import { visit, currentURL, click } from '@ember/test-helpers';
3 | import { setupApplicationTest } from 'ember-qunit';
4 | import config from 'dummy/config/environment';
5 | import TablePage from 'ember-table/test-support/pages/ember-table';
6 |
7 | let skip = (msg, ...args) =>
8 | qunitSkip(`Skip because ember-cli-addon-docs is not installed. ${msg}`, ...args);
9 | let test = config.ADDON_DOCS_INSTALLED ? qunitTest : skip;
10 |
11 | // The nav that contains buttons to show each snippet in a
12 | // `{{docs.demo}}`. See https://github.com/ember-learn/ember-cli-addon-docs/blob/a00a28e33acea463d82c64fa0a712913d70de3f1/addon/components/docs-demo/template.hbs#L11
13 | const DOCS_DEMO_SNIPPET_NAV_SELECTOR = '.docs-demo__snippets-nav';
14 |
15 | module('Acceptance | docs', function(hooks) {
16 | setupApplicationTest(hooks);
17 |
18 | test('visiting / redirects to /docs', async function(assert) {
19 | await visit('/');
20 |
21 | assert.strictEqual(currentURL(), '/docs');
22 | });
23 |
24 | test('pages linked to by /docs nav all render', async function(assert) {
25 | await visit('/docs');
26 |
27 | let nav = this.element.querySelector('nav');
28 | assert.true(!!nav, 'nav exists');
29 |
30 | let links = Array.from(nav.querySelectorAll('a')).filter(link =>
31 | link.getAttribute('href').startsWith('/docs')
32 | );
33 | assert.true(links.length > 0, `${links.length} nav links found`);
34 | for (let link of links) {
35 | let href = link.getAttribute('href');
36 | await visit(href);
37 | assert.true(true, `Visited ${href} successfully`);
38 |
39 | let buttonCount = 0;
40 | let docsNavs = Array.from(this.element.querySelectorAll(DOCS_DEMO_SNIPPET_NAV_SELECTOR));
41 | for (let nav of docsNavs) {
42 | let buttons = Array.from(nav.querySelectorAll('button'));
43 | for (let button of buttons) {
44 | await click(button);
45 | buttonCount++;
46 | }
47 | }
48 | assert.true(true, `Clicked ${buttonCount} snippet buttons on "${href}"`);
49 |
50 | await visit('/docs'); // start over
51 | }
52 | });
53 |
54 | test('subcolumns docs renders cell content', async function(assert) {
55 | let DemoTable = TablePage.extend({
56 | scope: '[data-test-demo="docs-example-subcolumns"] [data-test-ember-table]',
57 | });
58 |
59 | await visit('/docs/guides/header/subcolumns');
60 | let table = new DemoTable();
61 | assert.strictEqual(
62 | table.header.headers.objectAt(0).text,
63 | 'A',
64 | 'first header cell renders correctly'
65 | );
66 | assert.strictEqual(
67 | table.body.rows.objectAt(0).cells.objectAt(0).text,
68 | 'A A',
69 | 'first body cell renders correclty'
70 | );
71 | });
72 |
73 | test('autogenerated API docs are present', async function(assert) {
74 | await visit('/docs');
75 |
76 | let nav = this.element.querySelector('nav');
77 | assert.true(!!nav, 'nav exists');
78 |
79 | let navItems = Array.from(nav.querySelectorAll('li'));
80 |
81 | let expectedNavItems = ['API REFERENCE', ''];
82 |
83 | expectedNavItems.forEach(expectedText => {
84 | assert.true(
85 | navItems.some(li => li.innerText.includes(expectedText)),
86 | `"${expectedText}" nav item is exists`
87 | );
88 | });
89 | });
90 |
91 | test('sorting: 2-state sorting works as expected', async function(assert) {
92 | await visit('/docs/guides/header/sorting');
93 | let DemoTable = TablePage.extend({
94 | scope: '[data-test-demo="docs-example-2-state-sortings"] [data-test-ember-table]',
95 | });
96 |
97 | let table = new DemoTable();
98 | let header = table.headers.objectAt(0);
99 |
100 | assert.false(header.sortIndicator.isPresent, 'precond - sortIndicator is not present');
101 |
102 | await header.click();
103 | assert.true(
104 | // eslint-disable-next-line qunit/no-assert-logical-expression
105 | header.sortIndicator.isPresent && header.sortIndicator.isDescending,
106 | 'sort descending'
107 | );
108 |
109 | await header.click();
110 | assert.true(
111 | // eslint-disable-next-line qunit/no-assert-logical-expression
112 | header.sortIndicator.isPresent && header.sortIndicator.isAscending,
113 | 'sort ascending'
114 | );
115 |
116 | await header.click();
117 | assert.true(
118 | // eslint-disable-next-line qunit/no-assert-logical-expression
119 | header.sortIndicator.isPresent && header.sortIndicator.isDescending,
120 | 'sort cycles back to descending'
121 | );
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'dummy/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-cell/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend({
4 | tagName: '',
5 | color: null,
6 | });
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-cell/template.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET custom-cell.hbs }}
2 |
5 | {{! END-SNIPPET }}
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-header/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend({
4 | tagName: '',
5 | color: null,
6 | });
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-header/template.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET custom-header.hbs }}
2 |
5 | {{! END-SNIPPET }}
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-row/component.js:
--------------------------------------------------------------------------------
1 | import EmberTableRow from 'ember-table/components/ember-tr/component';
2 |
3 | export default EmberTableRow.extend({
4 | classNames: ['custom-row'],
5 | });
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/aborting-a-selection/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { action } from '@ember/object';
3 |
4 | let shouldAbortSelection;
5 |
6 | export default class AbortingASelectionExampleComponent extends Component {
7 | // BEGIN-SNIPPET docs-example-aborting-a-selection.js
8 | @action
9 | selectRows(selection, { abort }) {
10 | if (shouldAbortSelection(selection)) {
11 | abort();
12 | return;
13 | }
14 |
15 | this.selection = selection;
16 | }
17 | // END-SNIPPET
18 | }
19 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/aborting-a-selection/template.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET docs-example-aborting-a-selection.hbs }}
2 |
3 |
4 |
5 |
10 |
11 | {{! END-SNIPPET }}
12 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/fixed-columns-dynamic/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/fixed-columns-dynamic/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{! BEGIN-SNIPPET docs-example-dynamic-fixed-columns.hbs }}
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{column.name}}
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{! END-SNIPPET }}
17 |
18 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/infinite-scroll/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed } from '@ember/object';
3 | import { task, timeout } from 'ember-concurrency';
4 | import { A } from '@ember/array';
5 |
6 | export default Component.extend({
7 | // BEGIN-SNIPPET docs-example-infinite-scroll.js
8 | // count of records loaded so far
9 | offset: 0,
10 |
11 | // count of records per "page"
12 | limit: 20,
13 |
14 | // substitute total count of records from API response meta data
15 | maxRows: 100,
16 |
17 | // center spinner horizontally in scroll viewport
18 | centerSpinner: true,
19 |
20 | canLoadMore: computed('offset', 'maxRows', function() {
21 | return this.offset < this.maxRows;
22 | }),
23 |
24 | columns: computed(function() {
25 | return [
26 | { name: 'ID', valuePath: 'id', width: 180 },
27 | { name: 'A', valuePath: 'a', width: 180 },
28 | { name: 'B', valuePath: 'b', width: 180 },
29 | { name: 'C', valuePath: 'c', width: 180 },
30 | ];
31 | }),
32 |
33 | rows: computed(function() {
34 | return A();
35 | }),
36 |
37 | didInsertElement() {
38 | this._super(...arguments);
39 | this.loadMore.perform();
40 | },
41 |
42 | // ember-concurrency task
43 | loadMore: task(function*() {
44 | let offset = this.offset;
45 | let limit = this.limit;
46 |
47 | if (!this.canLoadMore) {
48 | return;
49 | }
50 |
51 | // substitute paginated API request
52 | yield timeout(1000);
53 |
54 | let newRows = [];
55 | for (let i = 0; i < limit; i++) {
56 | newRows.push({ id: offset + i + 1, a: 'A', b: 'B', c: 'C' });
57 | }
58 |
59 | this.rows.pushObjects(newRows);
60 | this.set('offset', offset + limit);
61 | }).drop(),
62 |
63 | // END-SNIPPET
64 | });
65 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/infinite-scroll/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Enable Centering
5 |
6 |
7 |
8 |
9 | {{! BEGIN-SNIPPET docs-example-infinite-scroll.hbs }}
10 |
11 |
12 |
13 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 | {{! END-SNIPPET }}
27 |
28 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/row-selection/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/row-selection/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{! BEGIN-SNIPPET docs-example-row-selection.hbs }}
3 |
4 |
5 |
6 |
11 |
12 | {{! END-SNIPPET }}
13 |
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/selected-rows/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/selected-rows/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{! BEGIN-SNIPPET docs-example-selected-rows.hbs }}
3 |
4 |
5 |
6 |
11 |
12 | {{! END-SNIPPET }}
13 |
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/selection-modes/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend();
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/selection-modes/template.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET docs-example-selection-modes.hbs }}
2 |
3 |
4 |
5 |
6 |
16 |
17 |
18 |
19 |
Current selection
20 |
{{currentSelection}}
21 |
22 |
23 |
rowSelectionMode
24 | multiple
25 | single
26 | none
27 |
28 |
29 |
checkboxSelectionMode
30 | multiple
31 | single
32 | none
33 |
34 |
35 |
selectingChildrenSelectsParent
36 |
37 |
38 |
39 | {{! END-SNIPPET }}
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/sorting-empty-values/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed } from '@ember/object';
3 | import faker from 'faker';
4 | import { getRandomInt } from 'dummy/utils/generators';
5 |
6 | export default Component.extend({
7 | // BEGIN-SNIPPET docs-example-sorting-empty-values.js
8 | columns: computed(function() {
9 | return [
10 | { name: 'Product', valuePath: 'name' },
11 | { name: 'Material', valuePath: 'material' },
12 | { name: 'Price', valuePath: 'price' },
13 | { name: 'Sold', valuePath: 'sold' },
14 | { name: 'Unsold', valuePath: 'unsold' },
15 | { name: 'Total Revenue', valuePath: 'totalRevenue' },
16 | ];
17 | }),
18 |
19 | sortEmptyLast: true,
20 | // END-SNIPPET
21 |
22 | rows: computed(function() {
23 | let rows = [];
24 |
25 | for (let k = 0; k < 10; k++) {
26 | let sold = getRandomInt(100, 10);
27 | let unsold = getRandomInt(100, 10);
28 | let price = getRandomInt(50, 10);
29 | let totalRevenue = price * sold;
30 |
31 | let product = {
32 | name: faker.commerce.productName(),
33 | material: faker.commerce.productMaterial(),
34 | price: `$${price}`,
35 | sold,
36 | unsold,
37 | totalRevenue: `$${totalRevenue}`,
38 | };
39 |
40 | rows.push(product);
41 | }
42 |
43 | for (let k = 0; k < 5; k++) {
44 | let sold = getRandomInt(100, 10);
45 | let unsold = getRandomInt(100, 10);
46 | let price = getRandomInt(50, 10);
47 | let totalRevenue = price * sold;
48 |
49 | let product = {
50 | name: faker.commerce.productName(),
51 | material: '',
52 | price: `$${price}`,
53 | sold,
54 | unsold,
55 | totalRevenue: `$${totalRevenue}`,
56 | };
57 |
58 | rows.push(product);
59 | }
60 |
61 | return rows;
62 | }),
63 | });
64 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/examples/sorting-empty-values/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{! BEGIN-SNIPPET docs-example-sorting-empty-values.hbs }}
4 |
5 |
6 |
7 | Sort Empty Last
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
25 |
26 | {{! END-SNIPPET }}
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/index-content/template.md:
--------------------------------------------------------------------------------
1 | Ember Table is a power table made for users who need a full-fledged,
2 | fully-customizable table component for their apps. It is built to be flexible,
3 | scalable, and ergonomic for day-to-day use.
4 |
5 | Unlike other table components, Ember Table seeks to provide as many common table
6 | features as it can _without_ sacrificing flexibility or composeability. You can
7 | still use standard component idioms to customize, extend, and build on top of
8 | Ember Table at every level, ensuring that it never gets in your way or prevents
9 | you from doing what you want.
10 |
11 | ## Features
12 |
13 | - Fixed headers and footers
14 | - Fixed columns
15 | - Row selection
16 | - Row sorting
17 | - Tree tables (with group collapsing)
18 | - Column resizing and reordering
19 | - Nested subcolumns (e.g. to create pivot tables)
20 | - Scalability - Can render thousands of rows performantly
21 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/main/table-meta-data/cell-selection/component.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { computed, action } from '@ember/object';
3 | import { generateRows } from 'dummy/utils/generators';
4 |
5 | export default Component.extend({
6 | rows: computed(function() {
7 | return generateRows(100);
8 | }),
9 |
10 | columns: computed(function() {
11 | return [
12 | { name: 'A', valuePath: 'A' },
13 | { name: 'B', valuePath: 'B' },
14 | { name: 'C', valuePath: 'C' },
15 | { name: 'D', valuePath: 'D' },
16 | { name: 'E', valuePath: 'E' },
17 | { name: 'F', valuePath: 'F' },
18 | { name: 'G', valuePath: 'G' },
19 | ];
20 | }),
21 |
22 | // BEGIN-SNIPPET table-meta-data-cell-selection.js
23 | setSelected: action(function(cellMeta, columnMeta, rowMeta) {
24 | // If we have selected before, unselect the previous selection
25 | if (this._hasSelection) {
26 | this._lastSelectedCellMeta.set('selected', false);
27 | this._lastSelectedColumnMeta.set('selected', false);
28 | this._lastSelectedRowMeta.set('selected', false);
29 | }
30 |
31 | // Set selection on the meta objects
32 | cellMeta.set('selected', true);
33 | columnMeta.set('selected', true);
34 | rowMeta.set('selected', true);
35 |
36 | // Store the meta objects to unset in the future
37 | this._lastSelectedCellMeta = cellMeta;
38 | this._lastSelectedColumnMeta = columnMeta;
39 | this._lastSelectedRowMeta = rowMeta;
40 | this._hasSelection = true;
41 | }),
42 | // END-SNIPPET
43 | });
44 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/main/table-meta-data/cell-selection/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{! BEGIN-SNIPPET table-meta-data-cell-selection.hbs }}
6 |
7 |
8 |
9 |
14 | {{column.name}}
15 |
16 |
17 |
18 |
19 |
20 |
25 |
30 |
31 | {{cell}}
32 |
33 |
34 |
35 |
36 |
37 | {{! END-SNIPPET }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/body/occlusion.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 |
4 | export default Controller.extend({
5 | // BEGIN-SNIPPET docs-example-occlusion.js
6 | columns: computed(function() {
7 | return [
8 | { name: 'A', valuePath: 'A', width: 180 },
9 | { name: 'B', valuePath: 'B', width: 180 },
10 | { name: 'C', valuePath: 'C', width: 180 },
11 | { name: 'D', valuePath: 'D', width: 180 },
12 | ];
13 | }),
14 |
15 | rows: computed(function() {
16 | return [
17 | { A: 'A', B: 'B', C: 'C', D: 'D' },
18 | { A: 'A', B: 'B', C: 'C', D: 'D' },
19 | { A: 'A', B: 'B', C: 'C', D: 'D' },
20 | { A: 'A', B: 'B', C: 'C', D: 'D' },
21 | { A: 'A', B: 'B', C: 'C', D: 'D' },
22 | { A: 'A', B: 'B', C: 'C', D: 'D' },
23 | { A: 'A', B: 'B', C: 'C', D: 'D' },
24 | { A: 'A', B: 'B', C: 'C', D: 'D' },
25 | { A: 'A', B: 'B', C: 'C', D: 'D' },
26 | { A: 'A', B: 'B', C: 'C', D: 'D' },
27 | { A: 'A', B: 'B', C: 'C', D: 'D' },
28 | ];
29 | }),
30 | // END-SNIPPET
31 | });
32 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/body/row-selection.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 |
4 | export default Controller.extend({
5 | // BEGIN-SNIPPET docs-example-row-selection.js
6 | columns: computed(function() {
7 | return [
8 | { name: 'A', valuePath: 'A', width: 180 },
9 | { name: 'B', valuePath: 'B', width: 180 },
10 | { name: 'C', valuePath: 'C', width: 180 },
11 | { name: 'D', valuePath: 'D', width: 180 },
12 | ];
13 | }),
14 |
15 | rows: computed(function() {
16 | return [
17 | { A: 'A', B: 'B', C: 'C', D: 'D' },
18 | { A: 'A', B: 'B', C: 'C', D: 'D' },
19 | { A: 'A', B: 'B', C: 'C', D: 'D' },
20 | { A: 'A', B: 'B', C: 'C', D: 'D' },
21 | { A: 'A', B: 'B', C: 'C', D: 'D' },
22 | { A: 'A', B: 'B', C: 'C', D: 'D' },
23 | { A: 'A', B: 'B', C: 'C', D: 'D' },
24 | { A: 'A', B: 'B', C: 'C', D: 'D' },
25 | { A: 'A', B: 'B', C: 'C', D: 'D' },
26 | { A: 'A', B: 'B', C: 'C', D: 'D' },
27 | { A: 'A', B: 'B', C: 'C', D: 'D' },
28 | ];
29 | }),
30 | // END-SNIPPET
31 |
32 | // BEGIN-SNIPPET docs-example-selected-rows.js
33 | init() {
34 | this._super(...arguments);
35 |
36 | let [rowWithChildren] = this.rowWithChildren;
37 |
38 | this.preselection = [rowWithChildren];
39 | },
40 |
41 | rowWithChildren: computed(function() {
42 | return [
43 | {
44 | A: 'A',
45 | B: 'B',
46 | C: 'C',
47 | D: 'D',
48 |
49 | children: [
50 | { A: 'A', B: 'B', C: 'C', D: 'D' },
51 | { A: 'A', B: 'B', C: 'C', D: 'D' },
52 | { A: 'A', B: 'B', C: 'C', D: 'D' },
53 | { A: 'A', B: 'B', C: 'C', D: 'D' },
54 | { A: 'A', B: 'B', C: 'C', D: 'D' },
55 | { A: 'A', B: 'B', C: 'C', D: 'D' },
56 | { A: 'A', B: 'B', C: 'C', D: 'D' },
57 | { A: 'A', B: 'B', C: 'C', D: 'D' },
58 | { A: 'A', B: 'B', C: 'C', D: 'D' },
59 | { A: 'A', B: 'B', C: 'C', D: 'D' },
60 | { A: 'A', B: 'B', C: 'C', D: 'D' },
61 | { A: 'A', B: 'B', C: 'C', D: 'D' },
62 | ],
63 | },
64 | ];
65 | }),
66 | // END-SNIPPET
67 |
68 | // BEGIN-SNIPPET docs-example-selection-modes.js
69 | rowSelectionMode: 'multiple',
70 | checkboxSelectionMode: 'multiple',
71 | selectingChildrenSelectsParent: true,
72 |
73 | rowsWithChildren: computed(function() {
74 | let makeRow = (id, { children } = { children: [] }) => {
75 | return {
76 | A: `A${id}`,
77 | B: 'B',
78 | C: 'C',
79 | D: 'D',
80 | children,
81 | };
82 | };
83 | return [
84 | makeRow(1, {
85 | children: [
86 | makeRow(2, {
87 | children: [makeRow(3), makeRow(4), makeRow(5)],
88 | }),
89 | makeRow(6),
90 | makeRow(7),
91 | makeRow(8, {
92 | children: [makeRow(9), makeRow(10), makeRow(11)],
93 | }),
94 | ],
95 | }),
96 | ];
97 | }),
98 |
99 | currentSelection: computed('demoSelection', function() {
100 | let selection = this.demoSelection;
101 | if (!selection || selection.length === 0) {
102 | return 'Nothing selected';
103 | } else {
104 | if (Array.isArray(selection)) {
105 | return `Array: [${selection.map(row => row.A).join(',')}]`;
106 | } else {
107 | let row = selection;
108 | return `Single: ${row.A}`;
109 | }
110 | }
111 | }),
112 | // END-SNIPPET
113 | });
114 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/body/rows-and-trees.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 |
4 | export default Controller.extend({
5 | // BEGIN-SNIPPET docs-example-rows.js
6 | columns: computed(function() {
7 | return [
8 | { name: 'A', valuePath: 'A', width: 180 },
9 | { name: 'B', valuePath: 'B', width: 180 },
10 | { name: 'C', valuePath: 'C', width: 180 },
11 | { name: 'D', valuePath: 'D', width: 180 },
12 | ];
13 | }),
14 |
15 | rows: computed(function() {
16 | return [
17 | { A: 'A', B: 'B', C: 'C', D: 'D' },
18 | { A: 'A', B: 'B', C: 'C', D: 'D' },
19 | { A: 'A', B: 'B', C: 'C', D: 'D' },
20 | { A: 'A', B: 'B', C: 'C', D: 'D' },
21 | { A: 'A', B: 'B', C: 'C', D: 'D' },
22 | { A: 'A', B: 'B', C: 'C', D: 'D' },
23 | { A: 'A', B: 'B', C: 'C', D: 'D' },
24 | { A: 'A', B: 'B', C: 'C', D: 'D' },
25 | { A: 'A', B: 'B', C: 'C', D: 'D' },
26 | { A: 'A', B: 'B', C: 'C', D: 'D' },
27 | { A: 'A', B: 'B', C: 'C', D: 'D' },
28 | ];
29 | }),
30 | // END-SNIPPET
31 |
32 | // BEGIN-SNIPPET docs-example-tree-rows.js
33 | treeEnabled: true,
34 |
35 | rowsWithChildren: computed(function() {
36 | return [
37 | {
38 | A: 'A',
39 | B: 'B',
40 | C: 'C',
41 | D: 'D',
42 |
43 | children: [
44 | { A: 'A', B: 'B', C: 'C', D: 'D' },
45 | { A: 'A', B: 'B', C: 'C', D: 'D' },
46 | { A: 'A', B: 'B', C: 'C', D: 'D' },
47 | ],
48 | },
49 | {
50 | A: 'A',
51 | B: 'B',
52 | C: 'C',
53 | D: 'D',
54 |
55 | children: [
56 | { A: 'A', B: 'B', C: 'C', D: 'D' },
57 | { A: 'A', B: 'B', C: 'C', D: 'D' },
58 | { A: 'A', B: 'B', C: 'C', D: 'D' },
59 | ],
60 | },
61 | {
62 | A: 'A',
63 | B: 'B',
64 | C: 'C',
65 | D: 'D',
66 |
67 | children: [
68 | { A: 'A', B: 'B', C: 'C', D: 'D' },
69 | { A: 'A', B: 'B', C: 'C', D: 'D' },
70 | { A: 'A', B: 'B', C: 'C', D: 'D' },
71 | ],
72 | },
73 | ];
74 | }),
75 | // END-SNIPPET
76 |
77 | // BEGIN-SNIPPET docs-example-rows-with-collapse.js
78 | collapseEnabled: true,
79 |
80 | rowsWithCollapse: computed(function() {
81 | return [
82 | {
83 | A: 'A',
84 | B: 'B',
85 | C: 'C',
86 | D: 'D',
87 |
88 | isCollapsed: true,
89 | children: [
90 | { A: 'A', B: 'B', C: 'C', D: 'D' },
91 | { A: 'A', B: 'B', C: 'C', D: 'D' },
92 | { A: 'A', B: 'B', C: 'C', D: 'D' },
93 | ],
94 | },
95 | {
96 | A: 'A',
97 | B: 'B',
98 | C: 'C',
99 | D: 'D',
100 |
101 | isCollapsed: true,
102 | children: [
103 | { A: 'A', B: 'B', C: 'C', D: 'D' },
104 | { A: 'A', B: 'B', C: 'C', D: 'D' },
105 | { A: 'A', B: 'B', C: 'C', D: 'D' },
106 | ],
107 | },
108 | {
109 | A: 'A',
110 | B: 'B',
111 | C: 'C',
112 | D: 'D',
113 |
114 | isCollapsed: true,
115 | children: [
116 | { A: 'A', B: 'B', C: 'C', D: 'D' },
117 | { A: 'A', B: 'B', C: 'C', D: 'D' },
118 | { A: 'A', B: 'B', C: 'C', D: 'D' },
119 | ],
120 | },
121 | ];
122 | }),
123 | // END-SNIPPET
124 | });
125 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/columns.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 | import { generateRows } from 'dummy/utils/generators';
4 |
5 | export default Controller.extend({
6 | rows: computed(function() {
7 | return generateRows(100);
8 | }),
9 |
10 | // BEGIN-SNIPPET docs-example-columns.js
11 | columns: computed(function() {
12 | return [
13 | { name: 'A', valuePath: 'A' },
14 | { name: 'B', valuePath: 'B' },
15 | { name: 'C', valuePath: 'C' },
16 | { name: 'D', valuePath: 'D' },
17 | { name: 'E', valuePath: 'E' },
18 | { name: 'F', valuePath: 'F' },
19 | { name: 'G', valuePath: 'G' },
20 | ];
21 | }),
22 | // END-SNIPPET
23 |
24 | // BEGIN-SNIPPET docs-example-columns-with-components.js
25 | columnsWithComponents: computed(function() {
26 | return [
27 | { heading: 'A', valuePath: 'A', component: 'custom-header', color: 'navy' },
28 | { heading: 'B', valuePath: 'B', component: 'custom-header', color: 'blue' },
29 | { heading: 'C', valuePath: 'C', component: 'custom-header', color: 'aqua' },
30 | { heading: 'D', valuePath: 'D', component: 'custom-header', color: 'teal' },
31 | { heading: 'E', valuePath: 'E', component: 'custom-header', color: 'orange' },
32 | { heading: 'F', valuePath: 'F', component: 'custom-header', color: 'red' },
33 | { heading: 'G', valuePath: 'G', component: 'custom-header', color: 'maroon' },
34 | ];
35 | }),
36 | // END-SNIPPET
37 |
38 | // BEGIN-SNIPPET docs-example-columns-with-widths.js
39 | columnsWithWidths: computed(function() {
40 | return [
41 | { name: 'A', valuePath: 'A', width: 100 },
42 | { name: 'B', valuePath: 'B', width: 100 },
43 | { name: 'C', valuePath: 'C', width: 100 },
44 | { name: 'D', valuePath: 'D', width: 100 },
45 | { name: 'E', valuePath: 'E', width: 100 },
46 | { name: 'F', valuePath: 'F', width: 100 },
47 | { name: 'G', valuePath: 'G', width: 100 },
48 | ];
49 | }),
50 | // END-SNIPPET
51 |
52 | // BEGIN-SNIPPET docs-example-column-resize-reorder.js
53 | resizeEnabled: false,
54 | reorderEnabled: false,
55 | resizeModeFluid: false,
56 | // END-SNIPPET
57 |
58 | resizeCount: 0,
59 | reorderCount: 0,
60 |
61 | // BEGIN-SNIPPET docs-example-text-align.js
62 | columnsWithTextAlign: computed(function() {
63 | return [
64 | { name: 'No alignment', valuePath: 'A' },
65 | { name: 'Left alignment', valuePath: 'B', textAlign: 'left' },
66 | { name: 'Center alignment', valuePath: 'C', textAlign: 'center' },
67 | { name: 'Right alignment', valuePath: 'D', textAlign: 'right' },
68 | ];
69 | }),
70 | // END-SNIPPET
71 | });
72 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/fixed-columns.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { A as emberA } from '@ember/array';
3 | import { set, action } from '@ember/object';
4 |
5 | import { generateRows } from 'dummy/utils/generators';
6 | import { computed } from '@ember/object';
7 |
8 | export default Controller.extend({
9 | rows: computed(function() {
10 | return generateRows(100);
11 | }),
12 |
13 | // BEGIN-SNIPPET docs-example-fixed-columns.js
14 | columns: computed(function() {
15 | return [
16 | { name: 'A', valuePath: 'A', isFixed: 'left' },
17 | { name: 'B', valuePath: 'B' },
18 | { name: 'C', valuePath: 'C' },
19 | { name: 'D', valuePath: 'D' },
20 | { name: 'E', valuePath: 'E' },
21 | { name: 'F', valuePath: 'F' },
22 | { name: 'G', valuePath: 'G' },
23 | { name: 'H', valuePath: 'H' },
24 | { name: 'I', valuePath: 'I' },
25 | { name: 'J', valuePath: 'J' },
26 | { name: 'K', valuePath: 'K', isFixed: 'right' },
27 | ];
28 | }),
29 | // END-SNIPPET
30 |
31 | // BEGIN-SNIPPET docs-example-out-of-order-fixed-columns.js
32 | outOfOrderFixedColumns: computed(function() {
33 | return [
34 | { name: 'A', valuePath: 'A', isFixed: 'right' },
35 | { name: 'B', valuePath: 'B' },
36 | { name: 'C', valuePath: 'C' },
37 | { name: 'D', valuePath: 'D', isFixed: 'left' },
38 | { name: 'E', valuePath: 'E' },
39 | { name: 'F', valuePath: 'F' },
40 | { name: 'G', valuePath: 'G' },
41 | { name: 'H', valuePath: 'H', isFixed: 'right' },
42 | { name: 'I', valuePath: 'I' },
43 | { name: 'J', valuePath: 'J' },
44 | { name: 'K', valuePath: 'K', isFixed: 'left' },
45 | ];
46 | }),
47 | // END-SNIPPET
48 |
49 | // BEGIN-SNIPPET docs-example-dynamic-fixed-columns.js
50 | dynamicFixedColumns: computed(function() {
51 | return emberA([
52 | { name: 'A', valuePath: 'A' },
53 | { name: 'B', valuePath: 'B' },
54 | { name: 'C', valuePath: 'C' },
55 | { name: 'D', valuePath: 'D' },
56 | { name: 'E', valuePath: 'E' },
57 | { name: 'F', valuePath: 'F' },
58 | { name: 'G', valuePath: 'G' },
59 | { name: 'H', valuePath: 'H' },
60 | { name: 'I', valuePath: 'I' },
61 | { name: 'J', valuePath: 'J' },
62 | { name: 'K', valuePath: 'K' },
63 | ]);
64 | }),
65 | // END-SNIPPET
66 |
67 | toggleFixed: action(function(column) {
68 | if (column.isFixed) {
69 | set(column, 'isFixed', false);
70 | } else {
71 | set(column, 'isFixed', 'left');
72 | }
73 | }),
74 | });
75 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/scroll-indicators.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 |
4 | import { generateRows } from 'dummy/utils/generators';
5 |
6 | export default Controller.extend({
7 | // BEGIN-SNIPPET docs-example-scroll-indicators.js
8 | columns: computed(function() {
9 | return [
10 | { name: 'A', valuePath: 'A' },
11 | { name: 'B', valuePath: 'B' },
12 | { name: 'C', valuePath: 'C' },
13 | { name: 'D', valuePath: 'D' },
14 | { name: 'E', valuePath: 'E' },
15 | { name: 'F', valuePath: 'F' },
16 | { name: 'G', valuePath: 'G' },
17 | { name: 'H', valuePath: 'H' },
18 | { name: 'I', valuePath: 'I' },
19 | { name: 'J', valuePath: 'J' },
20 | { name: 'K', valuePath: 'K' },
21 | ];
22 | }),
23 | // END-SNIPPET
24 |
25 | // BEGIN-SNIPPET docs-example-scroll-indicators-with-fixed.js
26 | columnsWithFixed: computed(function() {
27 | return [
28 | { name: 'A', valuePath: 'A', isFixed: 'left' },
29 | { name: 'B', valuePath: 'B' },
30 | { name: 'C', valuePath: 'C' },
31 | { name: 'D', valuePath: 'D' },
32 | { name: 'E', valuePath: 'E' },
33 | { name: 'F', valuePath: 'F' },
34 | { name: 'G', valuePath: 'G' },
35 | { name: 'H', valuePath: 'H' },
36 | { name: 'I', valuePath: 'I' },
37 | { name: 'J', valuePath: 'J' },
38 | { name: 'K', valuePath: 'K', isFixed: 'right' },
39 | ];
40 | }),
41 | // END-SNIPPET
42 |
43 | // BEGIN-SNIPPET docs-example-scroll-indicators-with-footer.js
44 | columnsWithFooter: computed(function() {
45 | return [
46 | { name: 'A', valuePath: 'A' },
47 | { name: 'B', valuePath: 'B' },
48 | { name: 'C', valuePath: 'C' },
49 | { name: 'D', valuePath: 'D' },
50 | { name: 'E', valuePath: 'E' },
51 | { name: 'F', valuePath: 'F' },
52 | { name: 'G', valuePath: 'G' },
53 | { name: 'H', valuePath: 'H' },
54 | { name: 'I', valuePath: 'I' },
55 | { name: 'J', valuePath: 'J' },
56 | { name: 'K', valuePath: 'K' },
57 | ];
58 | }),
59 | // END-SNIPPET
60 |
61 | rows: computed(function() {
62 | return generateRows(100);
63 | }),
64 |
65 | footerRows: computed(function() {
66 | return generateRows(100, 1, (row, key) => {
67 | return String.fromCharCode(key.charCodeAt(0) + 7);
68 | });
69 | }),
70 | });
71 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/size-constraints.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 | import { addObserver } from '@ember/object/observers'; // eslint-disable-line no-restricted-imports
4 | import { generateRows } from 'dummy/utils/generators';
5 |
6 | const defaultResizeMode = {
7 | 'eq-container': 'fluid',
8 | 'eq-container-slack': 'standard',
9 | 'gte-container': 'standard',
10 | 'gte-container-slack': 'standard',
11 | 'lte-container': 'standard',
12 | };
13 |
14 | export default Controller.extend({
15 | rows: computed(function() {
16 | return generateRows(100);
17 | }),
18 |
19 | widthConstraint: 'eq-container',
20 | fillMode: 'equal-column',
21 | resizeMode: 'fluid',
22 |
23 | init() {
24 | this._super(...arguments);
25 |
26 | // eslint-disable-next-line ember/no-observers
27 | addObserver(this, 'widthConstraint', this.setDefaultResizeMode);
28 | },
29 |
30 | setDefaultResizeMode() {
31 | let widthConstraint = this.widthConstraint;
32 | let resizeMode = defaultResizeMode[widthConstraint];
33 |
34 | if (resizeMode) {
35 | this.set('resizeMode', resizeMode);
36 | }
37 | },
38 |
39 | // BEGIN-SNIPPET docs-example-header-size-constraints.js
40 | columns: computed(function() {
41 | return [
42 | { name: 'A', valuePath: 'A' },
43 | { name: 'B', valuePath: 'B' },
44 | { name: 'C', valuePath: 'C' },
45 | { name: 'D', valuePath: 'D' },
46 | ];
47 | }),
48 | // END-SNIPPET
49 | });
50 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/sorting.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed, action } from '@ember/object';
3 | import faker from 'faker';
4 | import { getRandomInt } from 'dummy/utils/generators';
5 |
6 | export default Controller.extend({
7 | // BEGIN-SNIPPET docs-example-sortings.js
8 | columns: computed(function() {
9 | return [
10 | { name: 'Company ▸ Department ▸ Product', valuePath: 'name' },
11 | { name: 'Price', valuePath: 'price' },
12 | { name: 'Sold', valuePath: 'sold' },
13 | { name: 'Unsold', valuePath: 'unsold' },
14 | { name: 'Total Revenue', valuePath: 'totalRevenue' },
15 | ];
16 | }),
17 | // END-SNIPPET
18 |
19 | rows: computed(function() {
20 | let rows = [];
21 |
22 | for (let i = 0; i < getRandomInt(5, 2); i++) {
23 | let companyRow = {
24 | name: faker.company.companyName(),
25 | price: 'N/A',
26 | sold: 0,
27 | unsold: 0,
28 | totalRevenue: 0,
29 | children: [],
30 | };
31 |
32 | for (let j = 0; j < getRandomInt(5, 2); j++) {
33 | let departmentRow = {
34 | name: faker.commerce.department(),
35 | price: 'N/A',
36 | sold: 0,
37 | unsold: 0,
38 | totalRevenue: 0,
39 | children: [],
40 | };
41 |
42 | for (let k = 0; k < getRandomInt(100, 10); k++) {
43 | let sold = getRandomInt(100, 10);
44 | let unsold = getRandomInt(100, 10);
45 | let price = getRandomInt(50, 10);
46 | let totalRevenue = price * sold;
47 |
48 | let product = {
49 | name: faker.commerce.productName(),
50 | price: `$${price}`,
51 | sold,
52 | unsold,
53 | totalRevenue: `$${totalRevenue}`,
54 | };
55 |
56 | departmentRow.sold += sold;
57 | departmentRow.unsold += unsold;
58 | departmentRow.totalRevenue += totalRevenue;
59 | departmentRow.children.push(product);
60 | }
61 |
62 | companyRow.sold += departmentRow.sold;
63 | companyRow.unsold += departmentRow.unsold;
64 | companyRow.totalRevenue += departmentRow.totalRevenue;
65 |
66 | departmentRow.totalRevenue = `$${departmentRow.totalRevenue}`;
67 |
68 | companyRow.children.push(departmentRow);
69 | }
70 |
71 | companyRow.totalRevenue = `$${companyRow.totalRevenue}`;
72 |
73 | rows.push(companyRow);
74 | }
75 |
76 | return rows;
77 | }),
78 |
79 | // BEGIN-SNIPPET docs-example-2-state-sortings.js
80 | twoStateSorting: action(function(sorts) {
81 | if (sorts.length > 1) {
82 | // multi-column sort, default behavior
83 | this.set('sorts', sorts);
84 | return;
85 | }
86 |
87 | let hasExistingSort = this.sorts && this.sorts.length;
88 | let isDefaultSort = !sorts.length;
89 |
90 | if (hasExistingSort && isDefaultSort) {
91 | // override empty sorts with reversed previous sort
92 | let newSorts = [
93 | {
94 | valuePath: this.sorts[0].valuePath,
95 | isAscending: !this.sorts[0].isAscending,
96 | },
97 | ];
98 | this.set('sorts', newSorts);
99 | return;
100 | }
101 |
102 | this.set('sorts', sorts);
103 | }),
104 | // END-SNIPPET
105 | });
106 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/header/subcolumns.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { A as emberA } from '@ember/array';
3 | import { computed } from '@ember/object';
4 | import { generateRows, generateColumn } from 'dummy/utils/generators';
5 |
6 | const COLUMN_COUNT = 4;
7 |
8 | export default Controller.extend({
9 | rows: computed(function() {
10 | return generateRows(100);
11 | }),
12 |
13 | // BEGIN-SNIPPET docs-example-subcolumns.js
14 | simpleColumns: computed(function() {
15 | return [
16 | {
17 | name: 'A',
18 | subcolumns: [
19 | { name: 'A A', valuePath: 'A A' },
20 | { name: 'A B', valuePath: 'A B' },
21 | { name: 'A C', valuePath: 'A C' },
22 | ],
23 | },
24 | {
25 | name: 'B',
26 | subcolumns: [
27 | { name: 'B A', valuePath: 'B A' },
28 | { name: 'B B', valuePath: 'B B' },
29 | { name: 'B C', valuePath: 'B C' },
30 | ],
31 | },
32 | {
33 | name: 'C',
34 | subcolumns: [
35 | { name: 'C A', valuePath: 'C A' },
36 | { name: 'C B', valuePath: 'C B' },
37 | { name: 'C C', valuePath: 'C C' },
38 | ],
39 | },
40 | ];
41 | }),
42 | // END-SNIPPET
43 |
44 | complexColumns: computed(function() {
45 | let columns = emberA();
46 |
47 | for (let i = 0; i < COLUMN_COUNT; i++) {
48 | let column = generateColumn(i, { subcolumns: [] });
49 |
50 | if (Math.random() > 0.5) {
51 | for (let j = 0; j < COLUMN_COUNT - 1; j++) {
52 | let subcolumn = generateColumn([i, j], { subcolumns: [] });
53 |
54 | if (Math.random() > 0.5) {
55 | for (let k = 0; k < COLUMN_COUNT - 2; k++) {
56 | subcolumn.subcolumns.push(generateColumn([i, j, k]));
57 | }
58 | }
59 |
60 | column.subcolumns.push(subcolumn);
61 | }
62 | }
63 |
64 | columns.pushObject(column);
65 | }
66 |
67 | return columns;
68 | }),
69 | });
70 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/main/basic-table.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 |
4 | export default Controller.extend({
5 | // BEGIN-SNIPPET docs-example-basic-table.js
6 | columns: computed(function() {
7 | return [
8 | { name: 'A', valuePath: 'A', width: 180 },
9 | { name: 'B', valuePath: 'B', width: 180 },
10 | { name: 'C', valuePath: 'C', width: 180 },
11 | { name: 'D', valuePath: 'D', width: 180 },
12 | ];
13 | }),
14 |
15 | rows: computed(function() {
16 | return [
17 | { A: 'A', B: 'B', C: 'C', D: 'D' },
18 | { A: 'A', B: 'B', C: 'C', D: 'D' },
19 | { A: 'A', B: 'B', C: 'C', D: 'D' },
20 | { A: 'A', B: 'B', C: 'C', D: 'D' },
21 | { A: 'A', B: 'B', C: 'C', D: 'D' },
22 | { A: 'A', B: 'B', C: 'C', D: 'D' },
23 | { A: 'A', B: 'B', C: 'C', D: 'D' },
24 | { A: 'A', B: 'B', C: 'C', D: 'D' },
25 | { A: 'A', B: 'B', C: 'C', D: 'D' },
26 | { A: 'A', B: 'B', C: 'C', D: 'D' },
27 | { A: 'A', B: 'B', C: 'C', D: 'D' },
28 | ];
29 | }),
30 | // END-SNIPPET
31 | });
32 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/main/styling-the-table.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 |
3 | export default class SimpleController extends Controller {}
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/docs/guides/main/table-meta-data.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 | import { generateRows } from 'dummy/utils/generators';
4 |
5 | export default Controller.extend({
6 | rows: computed(function() {
7 | return generateRows(100);
8 | }),
9 |
10 | columns: computed(function() {
11 | return [
12 | { name: 'A', valuePath: 'A' },
13 | { name: 'B', valuePath: 'B' },
14 | { name: 'C', valuePath: 'C' },
15 | { name: 'D', valuePath: 'D' },
16 | { name: 'E', valuePath: 'E' },
17 | { name: 'F', valuePath: 'F' },
18 | { name: 'G', valuePath: 'G' },
19 | ];
20 | }),
21 | });
22 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/scenarios/performance.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed, action } from '@ember/object';
3 | import { generateRows, generateColumns } from 'dummy/utils/generators';
4 |
5 | export default Controller.extend({
6 | rows: computed(function() {
7 | let rows = generateRows(10, 3, (row, key) => `${row.id}${key}`);
8 |
9 | rows[0].children[0].children[0].children = generateRows(10, 1, (row, key) => `${row.id}${key}`);
10 |
11 | return rows;
12 | }),
13 |
14 | columns: computed(function() {
15 | let columns = generateColumns(20);
16 |
17 | columns[0].width = 300;
18 | columns[0].isResizable = false;
19 | columns[0].isReorderable = false;
20 |
21 | columns[1].subcolumns = generateColumns(3);
22 | columns[1].subcolumns[0].isReorderable = false;
23 | columns[1].subcolumns[1].isResizable = false;
24 | columns[1].subcolumns[2].isSortable = false;
25 |
26 | return columns;
27 | }),
28 |
29 | onSelect: action(function(selection) {
30 | this.set('selection', selection);
31 | }),
32 | onUpdateSorts: action(function(sorts) {
33 | this.set('sorts', sorts);
34 | }),
35 | });
36 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/scenarios/simple.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { computed } from '@ember/object';
3 | import { generateRows, generateColumns } from 'dummy/utils/generators';
4 |
5 | export default Controller.extend({
6 | rows: computed(function() {
7 | return generateRows(100);
8 | }),
9 |
10 | columns: computed(function() {
11 | return generateColumns(7);
12 | }),
13 | });
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ember Table
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
39 |
40 |
41 | {{content-for "head-footer"}}
42 |
43 |
44 | {{content-for "body"}}
45 |
46 |
47 |
48 |
49 | {{content-for "body-footer"}}
50 |
51 |
52 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import AddonDocsRouter, { docsRoute } from 'ember-cli-addon-docs/router';
2 | import config from './config/environment';
3 |
4 | const Router = AddonDocsRouter.extend({
5 | location: config.locationType,
6 | rootURL: config.rootURL,
7 | });
8 |
9 | Router.map(function() {
10 | docsRoute(this, function() {
11 | this.route('quickstart');
12 | this.route('why-ember-table');
13 |
14 | this.route('guides', function() {
15 | this.route('main', function() {
16 | this.route('basic-table');
17 | this.route('table-customization');
18 | this.route('table-meta-data');
19 | this.route('styling-the-table');
20 | this.route('infinite-scroll');
21 | });
22 |
23 | this.route('header', function() {
24 | this.route('columns');
25 | this.route('subcolumns');
26 | this.route('fixed-columns');
27 | this.route('size-constraints');
28 | this.route('sorting');
29 | this.route('scroll-indicators');
30 | this.route('column-keys');
31 | });
32 |
33 | this.route('body', function() {
34 | this.route('rows-and-trees');
35 | this.route('row-selection');
36 | this.route('occlusion');
37 | });
38 | });
39 |
40 | this.route('testing', function() {
41 | this.route('test-page');
42 | });
43 |
44 | this.route('api', function() {
45 | this.route('class', { path: '/:class_id' });
46 | });
47 | });
48 |
49 | this.route('scenarios', function() {
50 | this.route('simple');
51 | this.route('performance');
52 | this.route('blank');
53 | });
54 |
55 | this.route('not-found', { path: '/*path' });
56 | });
57 |
58 | export default Router;
59 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/index.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from '@ember/service';
2 | import Route from '@ember/routing/route';
3 |
4 | export default class IndexRoute extends Route {
5 | @service router;
6 |
7 | redirect() {
8 | this.router.transitionTo('docs');
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | @import './tables';
2 | @import './fixes';
3 | @import '@addepar/style-toolbox/onyx/index';
4 |
5 | // Override the url for the checkmark that indicates row selection.
6 | // This overrides a style in style-toolbox that uses the absolute url
7 | // '/@addepar/style-toolbox/assets/images/checkmark.svg'. When addon docs
8 | // are deployed, the root URL is '/ember-table/', and thus the checkmark asset
9 | // is not able to be loaded.
10 | // So instead we override the style and use a checkmark svg that has been added to this project:
11 | // See https://github.com/Addepar/ember-table/issues/593
12 | // The style-toolbox style is here: https://github.com/Addepar/addepar-style-toolbox/blob/640825d7722571c223f4b93e78a5c528018521e5/styles/onyx/elements/inputs/checkbox/_checkbox-core.scss#L68
13 | .ember-table .et-toggle-select input[type='checkbox'] + *:after {
14 | background-image: url('images/checkmark.svg');
15 | }
16 |
17 | $colors: (
18 | navy: #001f3f,
19 | blue: #0074d9,
20 | aqua: #7fdbff,
21 | teal: #39cccc,
22 | olive: #3d9970,
23 | green: #2ecc40,
24 | lime: #01ff70,
25 | yellow: #ffdc00,
26 | orange: #ff851b,
27 | red: #ff4136,
28 | maroon: #85144b,
29 | fuchsia: #f012be,
30 | purple: #b10dc9,
31 | black: #111111,
32 | grey: #aaaaaa,
33 | silver: #dddddd,
34 | );
35 |
36 | @each $color-name, $color-value in $colors {
37 | .bg-#{'' + $color-name} {
38 | background-color: #{$color-value};
39 | color: white;
40 | }
41 | .text-#{'' + $color-name} {
42 | color: #{$color-value};
43 | }
44 | }
45 |
46 | .resize-container {
47 | resize: both;
48 | overflow: scroll;
49 | border: solid 1px #dae1e7;
50 | width: 400px;
51 | height: 200px;
52 | padding: 10px;
53 |
54 | &.w-100 {
55 | width: 100%;
56 | }
57 | }
58 |
59 | .docs-hero {
60 | > div {
61 | padding-top: 2rem;
62 | padding-bottom: 2rem;
63 | }
64 |
65 | h1 {
66 | display: flex;
67 | justify-content: center;
68 | line-height: 1.5;
69 |
70 | > div {
71 | margin: 0;
72 |
73 | > span {
74 | height: 4rem;
75 | }
76 | }
77 | }
78 |
79 | p {
80 | margin-top: 0;
81 | }
82 | }
83 |
84 | tr {
85 | &.has-children {
86 | td {
87 | background-color: #f7f7f7;
88 | }
89 | }
90 |
91 | &.is-row-selected {
92 | td {
93 | background-color: #e5f5ff;
94 | }
95 | }
96 |
97 | td.is-column-selected,
98 | th.is-column-selected {
99 | background-color: #e5f5ff;
100 | border-bottom-color: #d2e2ec;
101 | }
102 |
103 | td.is-cell-selected {
104 | background-color: #b8ddf5;
105 | }
106 | }
107 |
108 | .cell-content {
109 | margin: -4px;
110 | padding: 4px;
111 | height: 30px;
112 | }
113 |
114 | .small {
115 | font-size: small;
116 | }
117 |
118 | .custom-header {
119 | padding: 0.25em;
120 | }
121 |
122 | .spinner {
123 | margin-top: 7px;
124 | width: 50%;
125 | height: 50%;
126 | }
127 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/fixes.scss:
--------------------------------------------------------------------------------
1 | /* CSS fixes for Chrome table rendering issues */
2 | .table-fixes {
3 | table {
4 | /* fix for vanishing borders */
5 | border-spacing: 0;
6 | border-collapse: separate;
7 | }
8 |
9 | thead {
10 | /* fix for buggy Ember Table headers, especially with `isFixed` */
11 | position: sticky !important;
12 | z-index: 5;
13 |
14 | th {
15 | /* paint over Ember Table header bleed */
16 | box-shadow: 0 -1px 0 0 white;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/tables.scss:
--------------------------------------------------------------------------------
1 | .demo-options {
2 | & > * + * {
3 | padding-left: 2em;
4 | }
5 | padding-top: 0.5em;
6 | padding-bottom: 0.5em;
7 |
8 | &:not(:last-child) {
9 | padding-bottom: 1em;
10 | }
11 |
12 | label {
13 | white-space: nowrap;
14 | }
15 | }
16 |
17 | .demo-options-heading {
18 | text-transform: uppercase;
19 | }
20 |
21 | .demo-current-selection {
22 | font-weight: bold;
23 | }
24 |
25 | .demo-options-group {
26 | h4 {
27 | font-weight: normal;
28 | font-size: larger;
29 | }
30 |
31 | display: flex;
32 |
33 | & *:first-child {
34 | flex-basis: 40%;
35 | }
36 |
37 | & > * + * {
38 | padding-left: 2em;
39 | }
40 | }
41 |
42 | .demo-container {
43 | position: relative;
44 | width: 100%;
45 | height: 400px;
46 |
47 | &.small {
48 | height: 200px;
49 | }
50 |
51 | &.fixed-width {
52 | width: 800px;
53 | }
54 | }
55 |
56 | .vertical-borders {
57 | td,
58 | th {
59 | &:not(:first-child) {
60 | box-sizing: border-box;
61 | border-left: 1px solid #ddd;
62 | }
63 | }
64 | }
65 |
66 | .et-table {
67 | td,
68 | th {
69 | background: #ffffff;
70 | }
71 | }
72 |
73 | /* Custom component classes */
74 | .custom-component-container {
75 | position: relative;
76 | width: 360px;
77 | height: 300px;
78 | }
79 |
80 | .cell-image {
81 | height: 40px;
82 | width: 40px;
83 | }
84 |
85 | .custom-row {
86 | background: #00ff00;
87 | }
88 |
89 | .custom-footer {
90 | background-color: #00ff00;
91 | }
92 |
93 | .info-table {
94 | margin: 2em auto;
95 |
96 | th {
97 | background-color: #f1f5f8;
98 | }
99 |
100 | th,
101 | td {
102 | padding: 0.5em;
103 | border: 1px solid #555;
104 |
105 | &.center {
106 | text-align: center;
107 | }
108 |
109 | &.highlight {
110 | background-color: #96c178;
111 | }
112 | }
113 |
114 | td:not(:first-child) {
115 | font-weight: bold;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/dummy/app/templates/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{outlet}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{outlet}}
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/body/occlusion.md:
--------------------------------------------------------------------------------
1 | # Occlusion
2 |
3 | Rendering lots and lots of HTML is really expensive for the browser, much more
4 | expensive than tracking those things in Javascript. Occlusion is a technique
5 | where we only render the HTML that is visible to the user at a given time. This
6 | allows us to load and present much more data than would otherwise be possible.
7 |
8 | Ember Table uses [vertical-collection](https://github.com/html-next/vertical-collection)
9 | by default to occlude rows of the table. This allows the table to render tens of
10 | thousands of rows without any performance hiccups.
11 |
12 | The occlusion also accounts for variable row heights automatically - no need to
13 | have static row heights, or to know the row heights in advance.
14 |
15 | ## Configuring Occlusion
16 |
17 | You can pass some parameters to the table body to fine tune the occlusion
18 | settings. The current options are:
19 |
20 | * `estimateRowHeight`: Vertical-collection figures out what your row heights
21 | are by measuring them after they have rendered. The first time each row is
22 | rendered, it assumes the row's height will be whatever value is provided by
23 | the `estimateRowHeight` in pixels (defaults to `30`). A more accurate estimate
24 | is always better, as it means vertical-collection will have less work to do
25 | if the "guess" was incorrect.
26 |
27 | * `staticHeight`: This field is a boolean flag that defaults to `false`. If you
28 | enable this field, vertical-collection will assume that all of the rows'
29 | heights are _exactly_ the value of `estimateRowHeight`. This will mean less
30 | work for vertical-collection and will be slightly more performant.
31 |
32 | Vertical-collection will **not** apply style changes to your rows if you
33 | pass `staticHeight=true`. You are responsible for ensuring that your rows are
34 | styled to always be the same as `estimateRowHeight`.
35 |
36 | * `key`: This key is the property used by the vertical-collection to determine
37 | whether an array mutation is an append, prepend, or complete replacement. It
38 | defaults to the object identity `"@identity"`.
39 |
40 | * `containerSelector`: A selector string used by the vertical-collection to select
41 | the element from which to calculate the viewable height. It defaults to the
42 | table id `"#{tableId}"`.
43 |
44 | {{#docs-demo as |demo|}}
45 | {{#demo.example}}
46 | {{! BEGIN-SNIPPET docs-example-occlusion.hbs }}
47 |
48 |
49 |
50 |
51 |
57 |
58 |
59 | {{! END-SNIPPET }}
60 | {{/demo.example}}
61 |
62 | {{demo.snippet name='docs-example-occlusion.hbs'}}
63 | {{demo.snippet label='component.js' name='docs-example-occlusion.js'}}
64 | {{/docs-demo}}
65 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/body/rows-and-trees.md:
--------------------------------------------------------------------------------
1 | # Rows and Trees
2 |
3 | Table body components must receive an `rows` array. The items in this array must
4 | all be objects, but beyond that there are no specific requirements for the
5 | objects themselves - they can be anything.
6 |
7 | {{#docs-demo as |demo|}}
8 | {{#demo.example}}
9 |
10 | {{! BEGIN-SNIPPET docs-example-rows.hbs }}
11 |
12 |
13 |
14 |
15 | {{! END-SNIPPET }}
16 |
17 | {{/demo.example}}
18 |
19 | {{demo.snippet name='docs-example-rows.hbs'}}
20 | {{demo.snippet label='component.js' name='docs-example-rows.js'}}
21 | {{/docs-demo}}
22 |
23 | The value passed to each cell in the table is determined by the `valuePath` of
24 | the `column` object. A simplified version of this in handlebars would look like
25 | this:
26 |
27 | ```hbs
28 | {{#each this.rows as |row|}}
29 |
30 | {{#each columns as |column|}}
31 |
32 | {{yield (get row column.valuePath)}}
33 |
34 | {{/each}}
35 |
36 | {{/each}}
37 | ```
38 |
39 | ## Trees and Children
40 |
41 | By default, Ember Table handles trees of rows. Each row can have a `children`
42 | property which is another array of rows. Children are treated the same way as
43 | parents - cells will attempt to find a value by getting the value at the value
44 | path on the child.
45 |
46 | If you want to disable the tree behavior, you can pass `enableTree=false` to
47 | the table body.
48 |
49 | {{#docs-demo as |demo|}}
50 | {{#demo.example name="trees"}}
51 | {{! BEGIN-SNIPPET docs-example-tree-rows.hbs }}
52 |
53 |
54 | {{input type="checkbox" checked=this.treeEnabled}}
55 | Enable Tree
56 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 | {{! END-SNIPPET }}
69 | {{/demo.example}}
70 |
71 | {{demo.snippet label='component.js' name='docs-example-tree-rows.js'}}
72 | {{demo.snippet name='docs-example-tree-rows.hbs'}}
73 | {{/docs-demo}}
74 |
75 | ## Collapsing Rows
76 |
77 | Trees with children are collapsible by default. You can set the `isCollapsed`
78 | property directly on rows to control the collapse state of rows externally. If
79 | you set `isCollapsed`, the table will update it when the user collapses or
80 | uncollapses a row. Otherwise, it will keep the state internally only.
81 |
82 | If you want to disable collapsing, you can pass `enableCollapse=false` to the
83 | table body.
84 |
85 | If you want to disable collapsing at a row level, you can pass
86 | `disableCollapse=true` to the row.
87 |
88 | {{#docs-demo as |demo|}}
89 | {{#demo.example name="collapse"}}
90 | {{! BEGIN-SNIPPET docs-example-rows-with-collapse.hbs }}
91 |
92 |
93 | {{input type="checkbox" checked=this.collapseEnabled}}
94 | Enable Collapse
95 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
107 | {{! END-SNIPPET }}
108 | {{/demo.example}}
109 |
110 | {{demo.snippet label='component.js' name='docs-example-rows-with-collapse.js'}}
111 | {{demo.snippet name='docs-example-rows-with-collapse.hbs'}}
112 | {{/docs-demo}}
113 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/header/column-keys.md:
--------------------------------------------------------------------------------
1 | ## Column Keys
2 |
3 | Ember Table will update the table layout as the `columns` array changes. When
4 | objects are removed and re-added to the array, meta data about the state of
5 | the column is preserved and recovered. By default, Ember Table uses the objects
6 | themselves as keys to save and retrieve this data.
7 |
8 | If your application is written in a functional style, this can pose a problem.
9 | For example, if your columns are generated by a getter that depends on tracked
10 | properties, you may be frequently passing new columns to Ember Table that aren't
11 | really new at all.
12 |
13 | ```js
14 | get columns() {
15 | // `data` could be a tracked property that updates frequently
16 | return this.data.headers.map((header, index) => {
17 | heading: header.name,
18 | valuePath: `${index}`,
19 | // ...
20 | });
21 | }
22 | ```
23 |
24 | This can have unexpected consequences, as meta data about your columns
25 | is being lost each time `columns` is re-computed.
26 |
27 | To handle this scenario, you can specify a `columnKeyPath` property that
28 | identifies a "key" property on each column object. This acts like a primary key
29 | in a database, identifying when two objects represent the same underlying
30 | entity. When `columns` is updated, these keys are used to preserve the state
31 | of your table, even if the replacement column objects are not identical to the
32 | originals.
33 |
34 | ```js
35 | get columns() {
36 | return this.data.headers.map((header, index) => {
37 | heading: header.name,
38 | valuePath: `${index}`,
39 |
40 | // assumes headers have distinct names
41 | key: header.name,
42 |
43 | // ...
44 | });
45 | }
46 | ```
47 |
48 | ```hbs
49 |
50 |
54 |
55 |
56 |
57 | ```
58 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/header/fixed-columns.md:
--------------------------------------------------------------------------------
1 | # Fixed Columns
2 |
3 | Columns can be fixed to the left or the right side of a table by setting the
4 | `isFixed` property on the column to `'left'` or `'right'`. Only root columns
5 | may be fixed, subcolumns will ignore their own `isFixed` property and use their
6 | parent's value instead.
7 |
8 | {{#docs-demo as |demo|}}
9 | {{#demo.example name='fixed-columns'}}
10 |
11 | {{! BEGIN-SNIPPET docs-example-fixed-columns.hbs }}
12 |
13 |
14 |
15 |
16 |
17 | {{! END-SNIPPET }}
18 |
19 | {{/demo.example}}
20 |
21 | {{demo.snippet name='docs-example-fixed-columns.hbs'}}
22 | {{demo.snippet name='docs-example-fixed-columns.js' label='component.js'}}
23 | {{/docs-demo}}
24 |
25 | ## Multiple Fixed Columns and Ordering
26 |
27 | Multiple columns may be fixed to either side of the table. Fixed columns _must_
28 | be placed contiguously at the start or end of the `columns` array. If columns
29 | are marked as fixed and are out of order, Ember Table will sort the columns
30 | array directly to fix the ordering.
31 |
32 | {{#docs-demo as |demo|}}
33 | {{#demo.example name='out-of-order-fixed-columns'}}
34 |
35 | {{! BEGIN-SNIPPET docs-example-out-of-order-fixed-columns.hbs }}
36 |
37 |
38 |
39 |
40 |
41 | {{! END-SNIPPET }}
42 |
43 | {{/demo.example}}
44 |
45 | {{demo.snippet name='docs-example-out-of-order-fixed-columns.hbs'}}
46 | {{demo.snippet name='docs-example-out-of-order-fixed-columns.js' label='component.js'}}
47 | {{/docs-demo}}
48 |
49 | ## Dynamic Fixed Columns
50 |
51 | Fixed positioning can be changed at any time, the below example demonstrates how
52 | you can make fixed columns toggleable for your users.
53 |
54 | {{#docs-demo as |demo|}}
55 | {{#demo.example name='dynamic-fixed-columns'}}
56 | {{examples/fixed-columns-dynamic
57 | rows=this.rows
58 | columns=this.dynamicFixedColumns
59 | toggleFixed=this.toggleFixed
60 | }}
61 | {{/demo.example}}
62 |
63 | {{demo.snippet name='docs-example-dynamic-fixed-columns.hbs'}}
64 | {{demo.snippet name='docs-example-dynamic-fixed-columns.js' label='component.js'}}
65 | {{/docs-demo}}
66 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/header/scroll-indicators.md:
--------------------------------------------------------------------------------
1 | # Scroll Indicators
2 |
3 | Set the `scrollIndicators` argument to `all`, `horizontal`, `vertical`, or `none` to
4 | shade the edges of the table where the user can scroll to see more data.
5 | These indicators will show/hide when there is content overflowing in their
6 | respective direction.
7 |
8 | {{#docs-demo as |demo|}}
9 | {{#demo.example name='scroll-indicators'}}
10 |
11 | {{! BEGIN-SNIPPET docs-example-scroll-indicators.hbs }}
12 |
13 |
14 |
18 |
19 |
20 | {{! END-SNIPPET }}
21 |
22 | {{/demo.example}}
23 |
24 | {{demo.snippet name='docs-example-scroll-indicators.hbs'}}
25 | {{demo.snippet name='docs-example-scroll-indicators.js' label='component.js'}}
26 | {{/docs-demo}}
27 |
28 | ## Horizontal Scroll Indicators with Fixed Columns
29 |
30 | Horizontal indicators will respect fixed columns, appearing inside of
31 | them when they are present, or at the edges of the table when they are not.
32 |
33 | {{#docs-demo as |demo|}}
34 | {{#demo.example name='scroll-indicators-with-fixed'}}
35 |
36 | {{! BEGIN-SNIPPET docs-example-scroll-indicators-with-fixed.hbs }}
37 |
38 |
39 |
43 |
44 |
45 | {{! END-SNIPPET }}
46 |
47 | {{/demo.example}}
48 |
49 | {{demo.snippet name='docs-example-scroll-indicators-with-fixed.hbs'}}
50 | {{demo.snippet name='docs-example-scroll-indicators-with-fixed.js' label='component.js'}}
51 | {{/docs-demo}}
52 |
53 | ## Vertical Scroll Indicators with a Header & Footer
54 |
55 | Vertical scroll indicators respect both headers and footers, appearing just
56 | inside any sticky rows at the top or bottom of the table.
57 |
58 | {{#docs-demo as |demo|}}
59 | {{#demo.example name='scroll-indicators-with-footer'}}
60 |
61 | {{! BEGIN-SNIPPET docs-example-scroll-indicators-with-footer.hbs }}
62 |
63 |
64 |
68 |
69 |
70 |
71 |
72 |
73 | {{! END-SNIPPET }}
74 |
75 | {{/demo.example}}
76 |
77 | {{demo.snippet name='docs-example-scroll-indicators-with-footer.hbs'}}
78 | {{demo.snippet name='docs-example-scroll-indicators-with-footer.js' label='component.js'}}
79 | {{/docs-demo}}
80 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/header/subcolumns.md:
--------------------------------------------------------------------------------
1 | # Subcolumns
2 |
3 | Columns can have an array of `subcolumns`, which should be objects that are
4 | exactly the same as any other column. Subcolumns can have their own subcolumns,
5 | and when rendering the rows of the table, cells will be matched up with the
6 | lowest level of subcolumns (the leaves of the column tree). This means that
7 | `valuePath` is optional for columns that have subcolumns.
8 |
9 | {{#docs-demo as |demo|}}
10 | {{#demo.example}}
11 |
12 | {{! BEGIN-SNIPPET docs-example-subcolumns.hbs }}
13 |
14 |
15 |
16 |
17 |
18 | {{! END-SNIPPET }}
19 |
20 | {{/demo.example}}
21 |
22 | {{demo.snippet name='docs-example-subcolumns.hbs'}}
23 | {{demo.snippet name='docs-example-subcolumns.js' label='component.js'}}
24 | {{/docs-demo}}
25 |
26 | ## Resizing and Reordering
27 |
28 | Subcolumns can be resized like any other column. When resizing a column with
29 | subcolumns, changes in width will be spread throughout the subcolumns.
30 | Subcolumns can only be reordered within their group - it is not currently
31 | possible to reorder move columns around arbitrarily.
32 |
33 | Columns do not need to have the same numbers of subcolumns, they can mix and
34 | match as much as you would like. This table's columns have generated completely
35 | randomly, demonstrating the flexibility of subcolumns.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/main/basic-table.md:
--------------------------------------------------------------------------------
1 | # A Basic Table
2 |
3 | Tables can get very complicated, and it's not easy to have a table API that is
4 | powerful, flexible, and succinct. Ember Table makes this tradeoff by providing a
5 | very flexible API, but at the cost of being fairly verbose. Because this table
6 | is meant for power users who need a lot of functionality and flexibility, this
7 | tradeoff generally makes sense.
8 |
9 | The table has a set of sane defaults. If you don't need much customization,
10 | setting up a minimal instance of Ember Table will only require you to define a
11 | header and a body, with columns and rows passed to it.
12 |
13 | {{#docs-demo as |demo|}}
14 | {{#demo.example}}
15 |
16 | {{! BEGIN-SNIPPET docs-example-basic-table.hbs }}
17 |
18 |
19 |
20 |
21 |
22 | {{! END-SNIPPET }}
23 |
24 | {{/demo.example}}
25 |
26 | {{demo.snippet name='docs-example-basic-table.hbs'}}
27 | {{demo.snippet label='component.js' name='docs-example-basic-table.js'}}
28 | {{/docs-demo}}
29 |
30 | ## High Level Structure
31 |
32 | At a high level, the structure of Ember Table is meant to mimic the structure of
33 | HTML tables directly. This allows you to customize each element in the table;
34 | you can add class names, setup actions, and handle events anywhere.
35 |
36 | This example demonstrates the same table as above, but with each level yielded.
37 |
38 | {{#docs-demo as |demo|}}
39 | {{#demo.example name='expanded'}}
40 |
41 | {{! BEGIN-SNIPPET docs-example-basic-expanded-table.hbs }}
42 |
43 |
44 |
45 |
46 | {{column.name}}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {{value}}
55 |
56 |
57 |
58 |
59 | {{! END-SNIPPET }}
60 |
61 | {{/demo.example}}
62 |
63 | {{demo.snippet name='docs-example-basic-expanded-table.hbs'}}
64 | {{/docs-demo}}
65 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/main/infinite-scroll.md:
--------------------------------------------------------------------------------
1 | # Infinite Scroll
2 |
3 | Infinite scroll is a common UI pattern where additional table rows are loaded as the user scrolls toward the bottom of the table. Ember Table does not provide infinite scrolling out of the box, but it has all the parts required to build an infinite scrolling table.
4 |
5 | {{#docs-demo as |demo|}}
6 | {{#demo.example}}
7 | {{examples/infinite-scroll}}
8 | {{/demo.example}}
9 |
10 | {{demo.snippet name='docs-example-infinite-scroll.hbs'}}
11 | {{demo.snippet label='component.js' name='docs-example-infinite-scroll.js'}}
12 | {{/docs-demo}}
13 |
14 | Ember Table does not provide a built-in spinner. You must specify your own by passing a block to `` like in the example above. See the {{#link-to route='docs.api.item' model='components/ember-table-loading-more'}}`EmberTableLoadingMore`{{/link-to}} component docs for more information.
15 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/main/styling-the-table.md:
--------------------------------------------------------------------------------
1 | # Styling the Table [WIP]
2 |
3 | Ember Table by default only ships with the minimum CSS necessary for it to
4 | function. This includes the `sticky` positioning of the header, footer, and
5 | fixed columns, along with a few other things.
6 |
7 | You can style the table by directly styling the `.ember-table` class, and the
8 | HTML structure beneath it. The examples in this documentation app use Addepar's
9 | own [CSS framework](https://github.com/Addepar/addepar-style-toolbox), which is
10 | tailored to our specific needs. You can check out the [table styles](https://github.com/Addepar/addepar-style-toolbox/blob/master/styles/onyx/components/table/_core.scss)
11 | applied there for inspiration.
12 |
13 | In the future, this page will contain a class list and outline of the base HTML
14 | for Ember Table.
15 |
16 | ## Reorder indicators
17 |
18 | When reordering the columns, two elements are created to be customized and allow the user to understand what he is doing.
19 |
20 | A first `.et-reorder-main-indicator` element is created, which basically is a ghost copy of the header cell currently grabbed. By default, it has no CSS property, giving a `position: absolute;` so this element is positioned on the `` is recommended.
21 |
22 | A second `.et-reorder-drop-indicator` element is created, which is the target header cell. It has two CSS classes `.et-reorder-direction-left` and `.et-reorder-direction-right` depending on the current drop position to show whether the drop will be directed on the left or on the right. Styling these will give the user a more intuitive experience of how they is reordering their table.
23 |
24 | ## Styling Examples
25 |
26 | Here is an example of using CSS Flex properties to create a layout that has a fixed page header and footer and a content area that is split for a search input area on the left and a data table on the right: [CSS Flex](https://ember-twiddle.com/4cb616452e3316ddcec242192fc4a96c?openFiles=templates.application.hbs%2C). The same layout using Bootstrap classes: [Bootstrap Flex](https://ember-twiddle.com/d27c9f154050688518a7ca9a0b055a26?openFiles=templates.application.hbs%2C). This version is also somewhat responsive, so change your window sizes to see it respond.
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/guides/main/table-meta-data.md:
--------------------------------------------------------------------------------
1 | # Table Meta Data
2 |
3 | So far you've seen how Ember Table revolves around three central concepts:
4 |
5 | 1. The `rowValue`, which is one of the rows that are provided to the body
6 | 2. The `columnValue`, which is one of the columns that are provided to the
7 | header
8 | 3. The `cellValue`, which is produced by using the `columnValue` to lookup a
9 | value on the `rowValue`
10 |
11 | These are the fundamental building blocks of any table, so it makes sense that
12 | they would be what is given to you when using the table API.
13 |
14 | You'll also find that Ember Table provides a meta object that is associated
15 | with each of these. These meta objects are yielded after the main
16 | cell/column/row values at the cell level, and are generally accessible wherever
17 | their corresponding values are:
18 |
19 | ```hbs
20 |
21 | ```
22 |
23 | ## What are meta objects?
24 |
25 | The meta objects are unique POJOs that are associated with a corresponding
26 | value. That is to say, for every `cell`, `column`, and `row` in the table, there
27 | are corresponding `cellMeta`, `columnMeta`, and `rowMeta` objects.
28 |
29 | `columnMeta` and `rowMeta` objects are used by the table to accomplish some
30 | internal bookkeeping such as collapse and selection state, but you are free to
31 | use these objects to store whatever meta information you would like in the
32 | table.
33 |
34 | `rowsCount` is also yielded by the cell component. This count is a reflection
35 | of how many rows the user can currently see by scrolling through the table. It
36 | is typically smaller than the total number of rows passed into, say, the
37 | `ember-tbody` component, because it excludes rows that have been hidden by
38 | collapsing a parent.
39 |
40 | ## What are they used for?
41 |
42 | Complex data tables have lots of functionality that requires some amount of
43 | state to be tracked. This state is generally unique to the table, and oftentimes
44 | related to a particular cell, column, or row. A good example of this is cell
45 | selection, like in Excel.
46 |
47 | When you click a cell in Excel, the row, column, and cell are all marked as
48 | active to show the user where they are in the table. Ember Table does _not_ have
49 | this functionality out of the box - let's see how we would implement it with
50 | meta objects:
51 |
52 |
53 | {{main/table-meta-data/cell-selection}}
54 |
55 | ## Accessing row indices in templates
56 |
57 | Meta objects can be used in templates to render conditional markdown based on
58 | the index of the current row.
59 |
60 | {{#docs-demo as |demo|}}
61 | {{#demo.example name="row-indices"}}
62 |
63 |
74 |
75 | {{! BEGIN-SNIPPET table-meta-data-row-indices.hbs }}
76 |
77 |
78 |
79 |
80 |
81 | {{#if (eq rowMeta.index 0)}}
82 | {{cell}}
83 | {{!-- `dec` helper is part of `ember-composable-helpers` --}}
84 | {{else if (lt rowMeta.index (dec rowsCount))}}
85 | {{cell}}
86 | {{else}}
87 | {{cell}}
88 | {{/if}}
89 |
90 |
91 |
92 |
93 | {{! END-SNIPPET }}
94 |
95 | {{/demo.example}}
96 |
97 | {{demo.snippet name='table-meta-data-row-indices.hbs'}}
98 | {{demo.snippet name='table-meta-data-row-indices.css'}}
99 | {{/docs-demo}}
100 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/index.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/quickstart.md:
--------------------------------------------------------------------------------
1 | # Quickstart.
2 |
3 | ## Installation
4 |
5 | ```sh
6 | ember install ember-table
7 | ```
8 |
9 | ## Basic Usage
10 |
11 | To use `Ember Table`, you'll need to create column definitions, and you'll need
12 | to pass in an array of rows. Here is a component definition with some basic
13 | column definitions and a rows array:
14 |
15 | ```javascript
16 | import Component from '@ember/component';
17 |
18 | class DemoComponent extends Component {
19 | columns = [
20 | {
21 | name: `First Name`,
22 | valuePath: `firstName`
23 | },
24 | {
25 | name: `Last Name`,
26 | valuePath: `lastName`
27 | }
28 | ];
29 |
30 | rows = [
31 | {
32 | firstName: 'Tony',
33 | lastName: 'Stark',
34 | },
35 | {
36 | firstName: 'Tom',
37 | lastName: 'Dale',
38 | }
39 | ];
40 | }
41 | ```
42 |
43 | Note how each item in the rows array has `firstName` and `lastName` properties,
44 | which matches up with the `valuePath` properties from the column definitions.
45 | This is how the table will get the values for each column.
46 |
47 | Now let's setup the template for this component:
48 |
49 | ```hbs
50 |
51 |
52 |
53 |
54 | ```
55 |
56 | And voila! You should have a basic table up and running!
57 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/testing/test-page.md:
--------------------------------------------------------------------------------
1 | # Testing Ember Table
2 |
3 | ## Table Page Object
4 |
5 | Ember Table includes a test helper that you can import in your app's acceptance tests and use to interact with a table on the page. The Page helper is based on [`ember-classy-page-object`](https://github.com/pzuraq/ember-classy-page-object).
6 |
7 | To import it:
8 |
9 | ```js
10 | import { TablePage } from 'ember-table/test-support';
11 | ```
12 |
13 | Usage:
14 |
15 | ```js
16 | // ... in an acceptance test:
17 | const table = new TablePage();
18 |
19 | assert.strictEqual(table.body.rowCount, 5, 'the table has 5 body rows');
20 | assert.strictEqual(table.header.rows.length, 1, 'the table has 1 row of headers');
21 | assert.strictEqual(table.footer.rows.length, 1, 'the table has 1 row of footers');
22 |
23 | await table.selectRow(0); // The first body row is selected
24 | assert.true(table.body.rows.objectAt(0).isSelected, 'first row is selected');
25 | assert.false(table.body.rows.objectAt(1).isSelected, 'second row is not selected');
26 | ```
27 |
28 | To learn more about the properties that are present on the table page object, refer to [its source](https://github.com/Addepar/ember-table/blob/master/addon-test-support/pages/ember-table.js) or
29 | to [its usage in the ember-table tests](https://github.com/Addepar/ember-table/blob/master/tests/integration/components/basic-test.js).
30 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/docs/why-ember-table.md:
--------------------------------------------------------------------------------
1 | # Why Ember Table?
2 |
3 | ## Tables are Hard
4 |
5 | They've been with us, for better or for worse, since the early
6 | days of computing, helping us sort through mounds of data and generate TPS
7 | reports. Excel macros are practically Turing complete at this point, and just
8 | about every UX pattern imaginable has been explored and implemented in a table
9 | in some application somewhere.
10 |
11 | Building that functionality into your Single Page App is a long, tiresome
12 | process, and as the datasets scale it just gets harder. There are a lot of
13 | different off the shelf tables out there, but customizing them is difficult and
14 | time consuming, and if they don't implement the feature you need there's no easy
15 | way to add it.
16 |
17 | ## What makes Ember Table different?
18 |
19 | Most table components try to abstract the complexity of the table away from
20 | users. They provide a few top components that are meant to make declaring a
21 | table simple, avoiding the nested HTML structure of traditional tables. After
22 | all, who wants to deal with figuring out the right ordering of `tbody`, `th`,
23 | and `td` tags to get everything working?
24 |
25 | The truth, however, is that tables _are_ complicated, and most attempts to
26 | reduce that complexity through abstraction actually make the problem worse. You
27 | end up having to pass tens or hundreds of configuration options to your table
28 | component to get it to work the way you want.
29 |
30 | Ember Table has decided to do the opposite. The structure of a table component
31 | mirrors the structure of an actual HTML table, giving you the ability to
32 | customize every level of your table using standard Ember templates, components,
33 | and actions:
34 |
35 | ```hbs
36 |
37 |
38 |
39 |
40 |
41 |
42 | Header
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Cell
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Footer
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Header
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Cell
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Footer
87 |
88 |
89 |
90 |
91 | ```
92 |
93 | Our stance is that rather than attempting to hide this complexity from you, we
94 | would rather give you full flexibility. It's very likely that you will want to
95 | customize the behavior of your table, and conversely, it's very _unlikely_ that
96 | you will have hundreds or thousands of data tables sprinkled throughout your
97 | app. Data tables tend to be used in a few central locations, so having a
98 | flexible API that allows you to easily flesh out those (relatively) few use
99 | cases is very valuable.
100 |
101 | ## Feature Complete OOTB
102 |
103 | Ember Table doesn't stop at giving you a solid API to build on, it also provides
104 | many features that you would expect a modern data table to have, including:
105 |
106 | - Fixed headers and footers
107 | - Fixed columns
108 | - Row selection
109 | - Row sorting
110 | - Tree tables (with group collapsing)
111 | - Column resizing and reordering
112 | - Nested subcolumns (e.g. to create pivot tables)
113 | - Scalability - Can render thousands of rows performantly
114 |
115 | By default, Ember Table is a feature complete data table solution that you can
116 | drop in to your app. We aim to support most standard data table features, and
117 | provide escape hatches where possible.
118 |
119 | ## Lightweight
120 |
121 | Ember Table is also a relatively lightweight table solution, currently weighing
122 | in at `22kb` (minified and gzipped) with plenty of extra weight to shed. Compare
123 | that to standalone table solutions like ag-grid (`152kb`) or HandsOnTable
124 | (`196kb`) and you can see how a table solution that integrates with the
125 | framework directly can save quite a few bytes.
126 |
127 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/not-found.hbs:
--------------------------------------------------------------------------------
1 |
2 |
Not found
3 |
This page doesn't exist. Head home?
4 |
5 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/scenarios.hbs:
--------------------------------------------------------------------------------
1 |
2 | Simple
3 | scenarios.performance
4 | scenarios.blank
5 |
6 |
7 | {{outlet}}
8 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/scenarios/blank.hbs:
--------------------------------------------------------------------------------
1 | blank
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/scenarios/performance.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{value}}
13 |
14 | {{#if (eq columnMeta.index 0)}}lorem ipsum dolor{{/if}}
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/scenarios/simple.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/dummy/app/utils/base-26.js:
--------------------------------------------------------------------------------
1 | import { assert } from '@ember/debug';
2 |
3 | const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
4 | const BASE = 26;
5 |
6 | /**
7 | * Convert an integer to bijective hexavigesimal notation (alphabetic base-26).
8 | *
9 | * @param {number} int - A positive integer above zero
10 | */
11 | export function toBase26(int) {
12 | assert('base 26 conversion must receive an integer', Number.isInteger(int));
13 | assert('base 26 conversion must receive a positive value', int >= 0);
14 |
15 | if (int < BASE) {
16 | return ALPHA[int];
17 | }
18 |
19 | let result = '';
20 | int += 1;
21 |
22 | while (int > 0) {
23 | let index = int % BASE || BASE;
24 | result = ALPHA[index - 1] + result;
25 | int = Math.floor((int - 1) / BASE);
26 | }
27 |
28 | return result;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/dummy/app/utils/generators.js:
--------------------------------------------------------------------------------
1 | import { A as emberA } from '@ember/array';
2 | import { toBase26 } from './base-26';
3 | import faker from 'faker';
4 |
5 | const DEFAULT_USE_EMBER_ARRAY = true;
6 | let useEmberArray = DEFAULT_USE_EMBER_ARRAY;
7 |
8 | export function configureTableGeneration({ useEmberArray: _useEmberArray }) {
9 | useEmberArray = _useEmberArray;
10 | }
11 |
12 | export function resetTableGenerationConfig() {
13 | useEmberArray = DEFAULT_USE_EMBER_ARRAY;
14 | }
15 |
16 | export function getRandomInt(max, min) {
17 | return faker.random.number({ min, max });
18 | }
19 |
20 | function identity(row, key) {
21 | return key;
22 | }
23 |
24 | export class DummyRow {
25 | constructor(id, format = identity) {
26 | this.id = id;
27 | this.format = format;
28 |
29 | // Set these so that they are not picked up by `unknownProperty` below
30 | this.disableCollapse = null;
31 | this.children = null;
32 | }
33 |
34 | unknownProperty(key) {
35 | return this.format(this, key);
36 | }
37 | }
38 |
39 | export function generateRow(id, format) {
40 | return new DummyRow(id, format);
41 | }
42 |
43 | export function generateRows(rowCount, depth, format, idPrefix = '') {
44 | let arr = [];
45 |
46 | for (let i = 0; i < rowCount; i++) {
47 | let id = idPrefix + i;
48 | let row = generateRow(id, format);
49 |
50 | if (depth > 1) {
51 | row.children = generateRows(rowCount, depth - 1, format, id);
52 | }
53 |
54 | arr.push(row);
55 | }
56 |
57 | return useEmberArray ? emberA(arr) : arr;
58 | }
59 |
60 | export function generateColumn(id, options) {
61 | let formattedId = Array.isArray(id) ? id.map(toBase26).join(' ') : toBase26(id);
62 |
63 | return {
64 | name: formattedId,
65 | valuePath: formattedId,
66 |
67 | ...options,
68 | };
69 | }
70 |
71 | export function generateColumns(
72 | columnCount,
73 | {
74 | id = [],
75 | subcolumnCount = 0,
76 | fixedLeftCount = 0,
77 | fixedRightCount = 0,
78 |
79 | ...columnOptions
80 | } = {}
81 | ) {
82 | let columns = [];
83 |
84 | for (let i = 0; i < columnCount; i++) {
85 | let columnId = id.slice();
86 |
87 | columnId.push(i);
88 |
89 | let columnDefinition = generateColumn(columnId, columnOptions);
90 |
91 | if (subcolumnCount) {
92 | columnDefinition.subcolumns = generateColumns(subcolumnCount, {
93 | id: columnId,
94 | ...columnOptions,
95 | });
96 | }
97 |
98 | columns.push(columnDefinition);
99 | }
100 |
101 | for (let i = 0; i < fixedLeftCount; i++) {
102 | columns[i].isFixed = 'left';
103 | }
104 |
105 | for (let i = 0; i < fixedRightCount; i++) {
106 | columns[columnCount - i - 1].isFixed = 'right';
107 | }
108 |
109 | return useEmberArray ? emberA(columns) : columns;
110 | }
111 |
--------------------------------------------------------------------------------
/tests/dummy/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
4 |
5 | module.exports = async function() {
6 | let { default: latestVersion } = await import('latest-version');
7 |
8 | return {
9 | packageManager: 'yarn',
10 | scenarios: [
11 | embroiderSafe(),
12 | embroiderOptimized(),
13 | {
14 | name: 'ember-lts-3.28',
15 | npm: {
16 | devDependencies: {
17 | '@ember/test-helpers': '2.9.4',
18 | 'ember-a11y-testing': '5.2.1',
19 | 'ember-qunit': '6.0.0',
20 | 'ember-source': '~3.28.0',
21 | },
22 | },
23 | },
24 | {
25 | name: 'ember-lts-4.4',
26 | npm: {
27 | devDependencies: {
28 | '@ember/test-helpers': '2.9.4',
29 | 'ember-a11y-testing': '5.2.1',
30 | 'ember-qunit': '6.0.0',
31 | 'ember-source': '~4.4.0',
32 | },
33 | },
34 | },
35 | {
36 | name: 'ember-lts-4.12',
37 | npm: {
38 | devDependencies: {
39 | 'ember-source': '~4.12.0',
40 | },
41 | },
42 | },
43 | {
44 | name: 'ember-lts-5.4',
45 | npm: {
46 | devDependencies: {
47 | 'ember-source': '~5.4.0',
48 | },
49 | },
50 | },
51 | {
52 | name: 'ember-lts-5.12',
53 | npm: {
54 | devDependencies: {
55 | 'ember-source': '~5.12.0',
56 | },
57 | },
58 | },
59 | {
60 | name: 'ember-release',
61 | npm: {
62 | devDependencies: {
63 | 'ember-source': await latestVersion('ember-source'),
64 | },
65 | },
66 | },
67 | {
68 | name: 'ember-beta',
69 | npm: {
70 | devDependencies: {
71 | 'ember-source': await latestVersion('ember-source', {
72 | version: 'beta',
73 | }),
74 | },
75 | },
76 | },
77 | {
78 | name: 'ember-canary',
79 | npm: {
80 | devDependencies: {
81 | 'ember-source': await latestVersion('ember-source', {
82 | version: 'alpha',
83 | }),
84 | },
85 | },
86 | },
87 | {
88 | name: 'ember-default',
89 | npm: {
90 | devDependencies: {},
91 | },
92 | },
93 | {
94 | name: 'ember-production',
95 | command: 'ember test -e production',
96 | npm: {
97 | devDependencies: {},
98 | },
99 | },
100 | {
101 | name: 'ember-default-docs',
102 | command: 'ember test --filter="Acceptance | docs"',
103 | npm: {
104 | devDependencies: {
105 | 'ember-data': '~3.24.0',
106 | 'ember-cli-addon-docs': '^5.2.0',
107 | 'ember-cli-addon-docs-yuidoc': '^1.0.0',
108 | 'ember-cli-deploy': '^1.0.2',
109 | 'ember-cli-deploy-build': '^1.1.1',
110 | 'ember-cli-deploy-git': '^1.3.3',
111 | 'ember-cli-deploy-git-ci': '^1.0.1',
112 | },
113 | resolutions: {
114 | '@handlebars/parser': '~2.1.0',
115 | },
116 | },
117 | },
118 | ],
119 | };
120 | };
121 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | const ADDON_DOCS_INSTALLED = Object.keys(require('../../../package.json').devDependencies).includes(
2 | 'ember-cli-addon-docs'
3 | );
4 |
5 | module.exports = function(environment) {
6 | let ENV = {
7 | modulePrefix: 'dummy',
8 | environment,
9 | rootURL: '/',
10 | locationType: 'history',
11 | ADDON_DOCS_INSTALLED,
12 | EmberENV: {
13 | EXTEND_PROTOTYPES: false,
14 | FEATURES: {
15 | // Here you can enable experimental features on an ember canary build
16 | // e.g. 'with-controller': true
17 | },
18 | },
19 |
20 | APP: {
21 | // Here you can pass flags/options to your application instance
22 | // when it is created
23 | },
24 | };
25 |
26 | if (environment === 'development') {
27 | // ENV.APP.LOG_RESOLVER = true;
28 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
29 | // ENV.APP.LOG_TRANSITIONS = true;
30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
31 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
32 | }
33 |
34 | if (environment === 'test') {
35 | // Testem prefers this...
36 | ENV.locationType = 'none';
37 |
38 | // keep test console output quieter
39 | ENV.APP.LOG_ACTIVE_GENERATION = false;
40 | ENV.APP.LOG_VIEW_LOOKUPS = false;
41 |
42 | ENV.APP.rootElement = '#ember-testing';
43 | ENV.APP.autoboot = false;
44 | }
45 |
46 | if (environment === 'production') {
47 | // Allow ember-cli-addon-docs to update the rootURL in compiled assets
48 | ENV.rootURL = 'ADDON_DOCS_ROOT_URL';
49 | }
50 |
51 | return ENV;
52 | };
53 |
--------------------------------------------------------------------------------
/tests/dummy/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "jquery-integration": false,
4 | "template-only-glimmer-components": true
5 | }
6 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'];
2 |
3 | module.exports = {
4 | browsers,
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Page Apps for GitHub Pages
6 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/dummy/public/assets/images/checkmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/dummy/public/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/dummy/public/assets/images/favicon.ico
--------------------------------------------------------------------------------
/tests/dummy/public/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/dummy/public/assets/images/logo.png
--------------------------------------------------------------------------------
/tests/dummy/public/assets/images/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/dummy/public/assets/images/spinner.gif
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/helpers/.gitkeep
--------------------------------------------------------------------------------
/tests/helpers/base-26.js:
--------------------------------------------------------------------------------
1 | import { assert } from '@ember/debug';
2 |
3 | const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
4 | const BASE = 26;
5 |
6 | /**
7 | * Convert an integer to bijective hexavigesimal notation (alphabetic base-26).
8 | *
9 | * @param {number} int - A positive integer above zero
10 | */
11 | export function toBase26(int) {
12 | assert('base 26 conversion must receive an integer', Number.isInteger(int));
13 | assert('base 26 conversion must receive a positive value', int >= 0);
14 |
15 | if (int < BASE) {
16 | return ALPHA[int];
17 | }
18 |
19 | let result = '';
20 | int += 1;
21 |
22 | while (int > 0) {
23 | let index = int % BASE || BASE;
24 | result = ALPHA[index - 1] + result;
25 | int = Math.floor((int - 1) / BASE);
26 | }
27 |
28 | return result;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/helpers/module.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 |
4 | export function scenarioModule(scenarios, callback) {
5 | for (let scenario in scenarios) {
6 | module(scenario, function(...moduleArgs) {
7 | callback(scenarios[scenario], ...moduleArgs);
8 | });
9 | }
10 | }
11 |
12 | export function componentModule(moduleName, callback) {
13 | module(moduleName, function(hooks) {
14 | setupRenderingTest(hooks);
15 |
16 | callback();
17 | });
18 | }
19 |
20 | export function parameterizedComponentModule(moduleName, parameters, callback) {
21 | Object.keys(parameters).forEach(key => {
22 | let { values, hooks } = parameters[key];
23 |
24 | for (let value of values) {
25 | module(`${moduleName} > params {${key}: ${value}}`, function(qunitHooks) {
26 | setupRenderingTest(qunitHooks);
27 | qunitHooks.beforeEach(function() {
28 | hooks.beforeEach && hooks.beforeEach(value);
29 | });
30 | qunitHooks.afterEach(function() {
31 | hooks.afterEach && hooks.afterEach(value);
32 | });
33 | callback(qunitHooks);
34 | });
35 | }
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/tests/helpers/warn-handlers.js:
--------------------------------------------------------------------------------
1 | import { registerWarnHandler } from '@ember/debug';
2 |
3 | let _registeredHandler = null;
4 | let _didRegisterWarnHandler = false;
5 |
6 | export function setup() {
7 | if (_didRegisterWarnHandler) {
8 | // We cannot unregister a warn handler, so make sure to only register the
9 | // handler one time during all tests.
10 | return;
11 | }
12 | _didRegisterWarnHandler = true;
13 | registerWarnHandler((message, options, next) => {
14 | if (_registeredHandler) {
15 | _registeredHandler(message, options);
16 | } else {
17 | next(message, options);
18 | }
19 | });
20 | }
21 |
22 | export function registerTestWarnHandler(callback) {
23 | _registeredHandler = callback;
24 | }
25 |
26 | export function teardown() {
27 | _registeredHandler = null;
28 | }
29 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}} {{content-for "test-head"}}
11 |
12 |
13 |
14 |
15 |
16 | {{content-for "head-footer"}} {{content-for "test-head-footer"}}
17 |
18 |
19 | {{content-for "body"}} {{content-for "test-body"}}
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{content-for "body-footer"}} {{content-for "test-body-footer"}}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/integration/components/footer-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 |
3 | import { generateTable } from '../../helpers/generate-table';
4 | import { componentModule } from '../../helpers/module';
5 |
6 | import TablePage from 'ember-table/test-support/pages/ember-table';
7 |
8 | const table = new TablePage();
9 |
10 | module('Integration | footer', function() {
11 | componentModule('basic', function() {
12 | test('renders if footerRows are set', async function(assert) {
13 | await generateTable(this, { footerRowCount: 3 });
14 |
15 | assert.true(table.footer.isPresent, 'Footer is present in the table');
16 | assert.strictEqual(table.footer.rows.length, 3, 'correct number of footer rows rendered');
17 | assert.strictEqual(
18 | table.footer.rowCount,
19 | 3,
20 | 'The total number of rows in the footer is available through the page object'
21 | );
22 |
23 | this.set('footerRows', []);
24 |
25 | assert.true(table.footer.isPresent, 'Footer is present in the table');
26 | assert.strictEqual(table.footer.rows.length, 0, 'Footer rows are removed');
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/integration/components/headers/cell-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { componentModule } from '../../../helpers/module';
3 |
4 | import { generateTable } from '../../../helpers/generate-table';
5 | import TablePage from 'ember-table/test-support/pages/ember-table';
6 |
7 | let table = new TablePage();
8 |
9 | module('Integration | header | th', function() {
10 | componentModule('basic', function() {
11 | test('sends onContextMenu action', async function(assert) {
12 | this.set('onHeaderCellContextMenu', event => {
13 | assert.ok(event, 'event sent');
14 | });
15 |
16 | await generateTable(this);
17 | await table.headers.objectAt(0).contextMenu();
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/tests/integration/components/headers/resize-handle-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { render } from '@ember/test-helpers';
3 | import hbs from 'htmlbars-inline-precompile';
4 |
5 | import { ResizePage } from 'ember-table/test-support/pages/-private/ember-table-header';
6 |
7 | import { componentModule } from '../../../helpers/module';
8 |
9 | let resize = new ResizePage();
10 |
11 | module('Integration | Component | ember-th/resize-handle', function() {
12 | componentModule('basic', function() {
13 | test('it renders', async function(assert) {
14 | this.set('columnMeta', {
15 | isResizable: true,
16 | });
17 |
18 | await render(hbs` `);
19 |
20 | assert.true(resize.isPresent);
21 |
22 | this.set('columnMeta.isResizable', false);
23 | assert.false(resize.isPresent);
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/integration/components/headers/sort-indicator-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { render } from '@ember/test-helpers';
3 | import hbs from 'htmlbars-inline-precompile';
4 |
5 | import { SortPage } from 'ember-table/test-support/pages/-private/ember-table-header';
6 |
7 | import { componentModule } from '../../../helpers/module';
8 |
9 | let sort = new SortPage();
10 |
11 | module('Integration | Component | ember-th/sort-indicator', function() {
12 | componentModule('basic', function() {
13 | test('it renders', async function(assert) {
14 | this.set('columnMeta', {
15 | isSorted: true,
16 | });
17 |
18 | await render(hbs` `);
19 |
20 | assert.true(sort.indicator.isPresent);
21 |
22 | // Template block usage:
23 | await render(hbs`
24 |
25 | template block text
26 |
27 | `);
28 |
29 | assert.strictEqual(sort.indicator.text, 'template block text');
30 | });
31 |
32 | test('it is hidden when not sorted', async function(assert) {
33 | this.set('columnMeta', {
34 | isSorted: false,
35 | });
36 |
37 | await render(hbs` `);
38 |
39 | assert.false(sort.indicator.isPresent);
40 |
41 | // Template block usage:
42 | await render(hbs`
43 |
44 | template block text
45 |
46 | `);
47 |
48 | assert.false(sort.indicator.isPresent);
49 | });
50 |
51 | test('it displays the sort order', async function(assert) {
52 | this.set('columnMeta', {
53 | isSorted: true,
54 | isSortedAsc: true,
55 | });
56 |
57 | await render(hbs` `);
58 | // asc sort
59 | assert.true(sort.indicator.isAscending);
60 | assert.false(sort.indicator.isDescending);
61 |
62 | // desc sort
63 | this.set('columnMeta.isSortedAsc', false);
64 | assert.false(sort.indicator.isAscending);
65 | assert.true(sort.indicator.isDescending);
66 | });
67 |
68 | test('it displays the sort index when using multiple sorts', async function(assert) {
69 | this.set('columnMeta', {
70 | isSorted: true,
71 | isMultiSorted: true,
72 | sortIndex: 2,
73 | });
74 |
75 | await render(hbs` `);
76 |
77 | assert.strictEqual(sort.indicator.text, '2');
78 |
79 | // desc sort
80 | this.set('columnMeta.isMultiSorted', false);
81 | assert.strictEqual(sort.indicator.text, '');
82 | });
83 |
84 | test('the sort option supports accessibility', async function(assert) {
85 | this.set('columnMeta', {
86 | isSortable: true,
87 | });
88 |
89 | await render(hbs` `);
90 |
91 | assert.true(sort.toggle.isPresent);
92 | assert.strictEqual(sort.toggle.text, 'Toggle Sort');
93 | });
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/tests/integration/components/row-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 |
3 | import {
4 | generateTable,
5 | configureTableGeneration,
6 | resetTableGenerationConfig,
7 | } from '../../helpers/generate-table';
8 | import { parameterizedComponentModule } from '../../helpers/module';
9 |
10 | import TablePage from 'ember-table/test-support/pages/ember-table';
11 | import { collection, hasClass } from 'ember-classy-page-object';
12 |
13 | let table = new TablePage({
14 | body: {
15 | rows: collection({
16 | isCustomRow: hasClass('custom-row'),
17 | }),
18 | },
19 | });
20 |
21 | const USE_EMBER_ARRAY_PARAMETERS = {
22 | useEmberArray: {
23 | values: [true, false],
24 | hooks: {
25 | beforeEach(value) {
26 | configureTableGeneration({ useEmberArray: value });
27 | },
28 | afterEach() {
29 | resetTableGenerationConfig();
30 | },
31 | },
32 | },
33 | };
34 |
35 | module('Integration | row', function() {
36 | parameterizedComponentModule('basic', USE_EMBER_ARRAY_PARAMETERS, function() {
37 | test('can use a custom row component', async function(assert) {
38 | await generateTable(this, {
39 | rowComponent: 'custom-row',
40 | });
41 |
42 | assert.true(table.rows.objectAt(0).isCustomRow, 'Table has custom row');
43 | });
44 |
45 | test('sends onClick action', async function(assert) {
46 | this.set('onRowClick', ({ event, rowValue, rowMeta }) => {
47 | assert.ok(event, 'event sent');
48 | assert.ok(rowValue, 'rowValue sent');
49 | assert.ok(rowMeta, 'rowMeta sent');
50 | });
51 |
52 | await generateTable(this);
53 | await table.rows.objectAt(0).click();
54 | });
55 |
56 | test('sends onDoubleClick action', async function(assert) {
57 | this.set('onRowDoubleClick', ({ event, rowValue, rowMeta }) => {
58 | assert.ok(event, 'event sent');
59 | assert.ok(rowValue, 'rowValue sent');
60 | assert.ok(rowMeta, 'rowMeta sent');
61 | });
62 |
63 | await generateTable(this);
64 | await table.rows.objectAt(0).doubleClick();
65 | });
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from '../app';
2 | import config from '../config/environment';
3 | import registerRAFWaiter from 'ember-raf-scheduler/test-support/register-waiter';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setupEmberOnerrorValidation, start } from 'ember-qunit';
6 | import { loadTests } from 'ember-qunit/test-loader';
7 | import QUnit from 'qunit';
8 | import { setup } from 'qunit-dom';
9 | import {
10 | setup as setupWarnHandlers,
11 | teardown as teardownWarnHandlers,
12 | } from './helpers/warn-handlers';
13 | import { setupForTest as setupEmberTableForTest } from 'ember-table/test-support';
14 |
15 | registerRAFWaiter();
16 | setApplication(Application.create(config.APP));
17 |
18 | setup(QUnit.assert);
19 | setupEmberOnerrorValidation();
20 | loadTests();
21 |
22 | QUnit.testStart(() => {
23 | setupEmberTableForTest();
24 | setupWarnHandlers();
25 | });
26 |
27 | QUnit.testDone(() => {
28 | teardownWarnHandlers();
29 | });
30 |
31 | start();
32 |
--------------------------------------------------------------------------------
/tests/unit/-private/column-tree-test.js:
--------------------------------------------------------------------------------
1 | import { set } from '@ember/object';
2 | import { run } from '@ember/runloop';
3 | import ColumnTree from 'ember-table/-private/column-tree';
4 | import MetaCache from 'ember-table/-private/meta-cache';
5 | import { module, test } from 'qunit';
6 |
7 | let columnMetaCache, tree;
8 |
9 | module('Unit | Private | ColumnTree', function(hooks) {
10 | hooks.beforeEach(function() {
11 | columnMetaCache = new MetaCache();
12 | });
13 |
14 | hooks.afterEach(function() {
15 | // Clean up so we can look for memory leaks more easily
16 | run(() => {
17 | for (let [key, value] of columnMetaCache.entries()) {
18 | value.destroy();
19 | columnMetaCache.delete(key);
20 | }
21 |
22 | tree.destroy();
23 | });
24 | });
25 |
26 | test('Setting the column width to its current value works', function(assert) {
27 | let columns = [
28 | { name: 'A', valuePath: 'A', width: 180 },
29 | { name: 'B', valuePath: 'B', width: 200 },
30 | { name: 'C', valuePath: 'C', width: 100 },
31 | { name: 'D', valuePath: 'D', width: 150 },
32 | ];
33 | tree = ColumnTree.create({
34 | columns,
35 | columnMetaCache,
36 | enableTree: true,
37 | });
38 |
39 | let root = tree.root;
40 | let subcolumns = root.subcolumnNodes;
41 | let firstSubcolumns = subcolumns[0];
42 | let initialWidth = firstSubcolumns.width;
43 | set(firstSubcolumns, 'width', initialWidth);
44 | let width = firstSubcolumns.width;
45 |
46 | assert.strictEqual(width, 180, 'The width is unchanged');
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/unit/-private/meta-cache-test.js:
--------------------------------------------------------------------------------
1 | import MetaCache from 'ember-table/-private/meta-cache';
2 | import { module, test } from 'qunit';
3 |
4 | let metaCache;
5 |
6 | module('Unit | Private | MetaCache', function(hooks) {
7 | hooks.beforeEach(function() {
8 | metaCache = new MetaCache();
9 | });
10 |
11 | test('it behaves like Map with no keyPath set', function(assert) {
12 | let a = { id: 1 };
13 | let b = { id: 1 };
14 |
15 | metaCache.set(a, 1);
16 | assert.strictEqual(metaCache.get(a), 1, 'gets value by added object key');
17 | assert.true(metaCache.has(a), 'contains added object key');
18 | assert.strictEqual(metaCache.get(b), undefined, 'gets undefined value when object key unknown');
19 | assert.false(metaCache.has(b), 'does not contain unknown object key');
20 |
21 | metaCache.delete(a);
22 | assert.strictEqual(metaCache.get(a), undefined, 'gets undefined value when object key removed');
23 | assert.false(metaCache.has(a), 'does not contain removed object key');
24 | });
25 |
26 | test('it uses keyPath for cache lookup', function(assert) {
27 | let a = { id: 1 };
28 | let b = { id: 1 };
29 |
30 | metaCache.keyPath = 'id';
31 |
32 | metaCache.set(a, 1);
33 | assert.strictEqual(metaCache.get(a), 1, 'gets value by added object key');
34 | assert.true(metaCache.has(a), 'contains added object key');
35 | assert.strictEqual(metaCache.get(b), 1, 'gets same value with equivalent object key');
36 | assert.true(metaCache.has(b), 'contains equivalent object key');
37 |
38 | metaCache.delete(a);
39 | assert.strictEqual(metaCache.get(a), undefined, 'gets undefined value when object key removed');
40 | assert.false(metaCache.has(a), 'does not contain removed object key');
41 | assert.strictEqual(
42 | metaCache.get(b),
43 | undefined,
44 | 'gets undefined value when equivalent key removed'
45 | );
46 | assert.false(metaCache.has(b), 'does not contain equivalent object key');
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/tests/unit/.gitkeep
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/ember/tsconfig.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "ember-table": [
7 | "types"
8 | ],
9 | "ember-table/*": [
10 | "types/*"
11 | ],
12 | }
13 | },
14 | "include": [
15 | "types/**/*"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/types/components/ember-table-loading-more/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export interface EmberTableLoadingMoreSignature {
4 | Element: HTMLDivElement;
5 | Args: {
6 | canLoadMore: boolean;
7 | center?: boolean;
8 | isLoading: boolean;
9 | };
10 | }
11 |
12 | export default class EmberTableLoadingMoreComponent extends Component {}
13 |
--------------------------------------------------------------------------------
/types/components/ember-table/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { EmberTableColumn, EmberTableRow } from 'ember-table';
3 | import EmberTbodyComponent from 'ember-table/components/ember-tbody/component';
4 | import EmberTfootComponent from 'ember-table/components/ember-tfoot/component';
5 | import EmberTheadComponent from 'ember-table/components/ember-thead/component';
6 | import EmberTableLoadingMoreComponent from 'ember-table/components/ember-table-loading-more/component';
7 |
8 | export interface EmberTableSignature<
9 | RowType extends EmberTableRow,
10 | ColumnType extends EmberTableColumn
11 | > {
12 | Element: HTMLDivElement;
13 | Blocks: {
14 | default: [
15 | {
16 | body: typeof EmberTbodyComponent;
17 | foot: typeof EmberTfootComponent;
18 | head: typeof EmberTheadComponent;
19 | loadingMore: typeof EmberTableLoadingMoreComponent;
20 | }
21 | ];
22 | };
23 | }
24 |
25 | export default class EmberTableComponent<
26 | RowType extends EmberTableRow = EmberTableRow,
27 | ColumnType extends EmberTableColumn = EmberTableColumn
28 | > extends Component> {}
29 |
--------------------------------------------------------------------------------
/types/components/ember-td/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { EmberTableColumn, EmberTableRow, TableColumnMeta, TableRowMeta } from 'ember-table';
3 |
4 | export interface EmberTdSignature {
5 | Element: HTMLTableCellElement;
6 | Args: {
7 | /**
8 | * Action sent when the user clicks this element.
9 | */
10 | onClick?: ((row: RowType, event: Event) => void) | undefined;
11 |
12 | /**
13 | * Action sent when the user double clicks this element.
14 | */
15 | onDoubleClick?: ((row: RowType, event: Event) => void) | undefined;
16 | };
17 | Blocks: {
18 | default: [
19 | cellValue: RowType[keyof RowType],
20 | columnValue: ColumnType,
21 | rowValue: RowType,
22 | cellMeta: unknown,
23 | columnMeta: TableColumnMeta,
24 | rowMeta: TableRowMeta,
25 | ];
26 | };
27 | }
28 |
29 | export default class EmberTdComponent<
30 | RowType extends EmberTableRow = EmberTableRow,
31 | ColumnType extends EmberTableColumn = EmberTableColumn
32 | > extends Component> {}
33 |
--------------------------------------------------------------------------------
/types/components/ember-tfoot/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { EmberTableColumn, EmberTableRow, TableRowMeta } from 'ember-table';
3 | import EmberTrComponent from 'ember-table/components/ember-tr/component';
4 |
5 | export interface EmberTfootSignature<
6 | RowType extends EmberTableRow,
7 | ColumnType extends EmberTableColumn
8 | > {
9 | Element: HTMLDivElement;
10 | Args: {
11 | rows: RowType[];
12 | };
13 | Blocks: {
14 | default: [
15 | {
16 | row: typeof EmberTrComponent;
17 | rowMeta: TableRowMeta;
18 | rowValue: RowType;
19 | }
20 | ];
21 | };
22 | }
23 |
24 | export default class EmberTfootComponent<
25 | RowType extends EmberTableRow = EmberTableRow,
26 | ColumnType extends EmberTableColumn = EmberTableColumn
27 | > extends Component> {}
28 |
--------------------------------------------------------------------------------
/types/components/ember-th/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { TableColumnMeta } from 'ember-table';
3 |
4 | export interface EmberThSignature {
5 | Element: HTMLTableCellElement;
6 | Args: {
7 | /**
8 | * Action sent when the user clicks right this element.
9 | */
10 | onContextMenu?: ((event: Event) => void);
11 | };
12 | Blocks: {
13 | default: [
14 | columnValue: ColumnType,
15 | columnMeta: TableColumnMeta,
16 | ];
17 | };
18 | }
19 |
20 | export default class EmberThComponent extends Component> {}
21 |
--------------------------------------------------------------------------------
/types/components/ember-th/resize-handle/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { TableColumnMeta } from 'ember-table';
3 |
4 | export interface ResizeHandleSignature {
5 | Element: HTMLTableCellElement;
6 | Args: {
7 | columnMeta: TableColumnMeta;
8 | };
9 | }
10 |
11 | export default class ResizeHandleComponent extends Component {}
12 |
--------------------------------------------------------------------------------
/types/components/ember-th/sort-indicator/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { TableColumnMeta } from 'ember-table';
3 |
4 | export interface SortIndicatorSignature {
5 | Element: HTMLTableCellElement;
6 | Args: {
7 | columnMeta: TableColumnMeta;
8 | };
9 | }
10 |
11 | export default class SortIndicatorComponent extends Component {}
12 |
--------------------------------------------------------------------------------
/types/components/ember-tr/component.d.ts:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { EmberTableColumn, EmberTableRow, TableColumnMeta, TableRowMeta } from 'ember-table';
3 | import EmberTdComponent from 'ember-table/components/ember-td/component';
4 |
5 | export interface EmberTrSignature<
6 | RowType extends EmberTableRow,
7 | ColumnType extends EmberTableColumn,
8 | CellComponentType
9 | > {
10 | Element: HTMLTableRowElement;
11 | Args: {
12 | /**
13 | * An action that is called when a row is clicked. Will be called with the row and the event.
14 | */
15 | onClick?: (event: { event: Event, rowValue: RowType, rowMeta: TableRowMeta }) => void;
16 |
17 | /**
18 | * An action that is called when a row is double clicked. Will be called with the row and the event.
19 | */
20 | onDoubleClick?: (event: { event: Event, rowValue: RowType, rowMeta: TableRowMeta }) => void;
21 | };
22 | Blocks: {
23 | default: [
24 | {
25 | cell: CellComponentType;
26 | cellMeta: unknown;
27 | cellValue: RowType[keyof RowType];
28 | columnMeta: TableColumnMeta;
29 | columnValue: ColumnType;
30 | rowMeta: TableRowMeta;
31 | rowValue: RowType;
32 | }
33 | ];
34 | };
35 | }
36 |
37 | export default class EmberTrComponent<
38 | RowType extends EmberTableRow = EmberTableRow,
39 | ColumnType extends EmberTableColumn = EmberTableColumn,
40 | CellComponentType = typeof EmberTdComponent
41 | > extends Component> {}
42 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface EmberTableColumn {
2 | name?: string;
3 | valuePath?: string;
4 |
5 | isFixed?: 'left' | 'right';
6 | isResizable?: boolean;
7 | isSortable?: boolean;
8 | maxWidth?: number;
9 | minWidth?: number;
10 | subcolumns?: EmberTableColumn[];
11 | textAlign?: 'left' | 'center' | 'right';
12 | width?: number;
13 | }
14 |
15 | export interface EmberTableRow {}
16 |
17 | export interface EmberTableSort {
18 | isAscending: boolean;
19 | valuePath: string;
20 | }
21 |
22 | export interface TableRowMeta {
23 | isCollapsed: boolean;
24 | isSelected: boolean;
25 | isGroupSelected: boolean;
26 | canCollapse: boolean;
27 | depth: number;
28 | first: unknown | null;
29 | last: unknown | null;
30 | next: unknown | null;
31 | prev: unknown | null;
32 | }
33 |
34 | export interface TableColumnMeta {
35 | isLeaf: boolean;
36 | isFixed: 'left' | 'right' | undefined;
37 | isSortable: boolean;
38 | isResizable: boolean;
39 | isReorderable: boolean;
40 | isSlack: boolean;
41 | width: number;
42 | offsetLeft: number;
43 | offsetRight: number;
44 | rowSpan: number;
45 | columnSpan: number;
46 | index: number | undefined;
47 | isLastRendered: boolean;
48 | sortIndex: number;
49 | isSorted: boolean;
50 | isMultiSorted: boolean;
51 | isSortedAsc: boolean;
52 | }
53 |
--------------------------------------------------------------------------------
/types/template-registry.d.ts:
--------------------------------------------------------------------------------
1 | import EmberTableComponent from 'ember-table/components/ember-table/component';
2 | import ResizeHandleComponent from 'ember-table/components/ember-th/resize-handle/component';
3 | import SortIndicatorComponent from 'ember-table/components/ember-th/sort-indicator/component';
4 |
5 | export default interface Registry {
6 | EmberTable: typeof EmberTableComponent;
7 | 'ember-table': typeof EmberTableComponent;
8 | 'EmberTh::ResizeHandle': typeof ResizeHandleComponent;
9 | 'ember-th/resize-handle': typeof ResizeHandleComponent;
10 | 'EmberTh::SortIndicator': typeof SortIndicatorComponent;
11 | 'ember-th/sort-indicator': typeof SortIndicatorComponent;
12 | }
13 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Addepar/ember-table/5d70fc82dfe50c238aabb4995a0ea62dadb3a944/vendor/.gitkeep
--------------------------------------------------------------------------------