Repository: offirgolan/ember-light-table Branch: master Commit: c27cd9fa6686 Files: 216 Total size: 289.4 KB Directory structure: gitextract_3izqe97e/ ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .tool-versions ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── addon/ │ ├── -private/ │ │ └── global-options.js │ ├── .gitkeep │ ├── classes/ │ │ ├── Column.js │ │ ├── Row.js │ │ └── Table.js │ ├── components/ │ │ ├── cells/ │ │ │ ├── base.hbs │ │ │ └── base.js │ │ ├── columns/ │ │ │ ├── base.hbs │ │ │ └── base.js │ │ ├── light-table.hbs │ │ ├── light-table.js │ │ ├── lt-body.hbs │ │ ├── lt-body.js │ │ ├── lt-column-resizer.hbs │ │ ├── lt-column-resizer.js │ │ ├── lt-foot.hbs │ │ ├── lt-foot.js │ │ ├── lt-head.hbs │ │ ├── lt-head.js │ │ ├── lt-infinity.hbs │ │ ├── lt-infinity.js │ │ ├── lt-row.hbs │ │ ├── lt-row.js │ │ ├── lt-scaffolding-row.hbs │ │ ├── lt-scaffolding-row.js │ │ ├── lt-scrollable.hbs │ │ ├── lt-scrollable.js │ │ ├── lt-spanned-row.hbs │ │ └── lt-spanned-row.js │ ├── helpers/ │ │ ├── compute.js │ │ └── html-safe.js │ ├── index.js │ ├── mixins/ │ │ ├── draggable-column.js │ │ └── table-header.js │ ├── styles/ │ │ └── addon.css │ └── utils/ │ ├── closest.js │ └── css-styleify.js ├── app/ │ ├── .gitkeep │ ├── components/ │ │ ├── light-table/ │ │ │ ├── cells/ │ │ │ │ └── base.js │ │ │ └── columns/ │ │ │ └── base.js │ │ ├── light-table.js │ │ ├── lt-body.js │ │ ├── lt-column-resizer.js │ │ ├── lt-foot.js │ │ ├── lt-head.js │ │ ├── lt-infinity.js │ │ ├── lt-row.js │ │ ├── lt-scaffolding-row.js │ │ ├── lt-scrollable.js │ │ └── lt-spanned-row.js │ └── helpers/ │ ├── compute.js │ └── html-safe.js ├── blueprints/ │ ├── cell-type/ │ │ ├── files/ │ │ │ └── app/ │ │ │ └── components/ │ │ │ └── light-table/ │ │ │ └── cells/ │ │ │ └── __name__.js │ │ └── index.js │ ├── column-type/ │ │ ├── files/ │ │ │ └── app/ │ │ │ └── components/ │ │ │ └── light-table/ │ │ │ └── columns/ │ │ │ └── __name__.js │ │ └── index.js │ └── ember-light-table/ │ └── index.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests/ │ ├── dummy/ │ │ ├── app/ │ │ │ ├── adapters/ │ │ │ │ └── application.js │ │ │ ├── app.js │ │ │ ├── breakpoints.js │ │ │ ├── components/ │ │ │ │ ├── .gitkeep │ │ │ │ ├── base-table.js │ │ │ │ ├── code-panel.hbs │ │ │ │ ├── code-panel.js │ │ │ │ ├── code-snippet.hbs │ │ │ │ ├── colored-row.js │ │ │ │ ├── columns/ │ │ │ │ │ ├── draggable-table.hbs │ │ │ │ │ ├── draggable-table.js │ │ │ │ │ ├── grouped-table.hbs │ │ │ │ │ ├── grouped-table.js │ │ │ │ │ ├── resizable-table.hbs │ │ │ │ │ └── resizable-table.js │ │ │ │ ├── cookbook/ │ │ │ │ │ ├── client-side-table.hbs │ │ │ │ │ ├── client-side-table.js │ │ │ │ │ ├── custom-row-table.hbs │ │ │ │ │ ├── custom-row-table.js │ │ │ │ │ ├── custom-sort-icon-table.hbs │ │ │ │ │ ├── custom-sort-icon-table.js │ │ │ │ │ ├── horizontal-scrolling-table.hbs │ │ │ │ │ ├── horizontal-scrolling-table.js │ │ │ │ │ ├── index-list.hbs │ │ │ │ │ ├── occluded-table.hbs │ │ │ │ │ ├── occluded-table.js │ │ │ │ │ ├── paginated-table.hbs │ │ │ │ │ ├── paginated-table.js │ │ │ │ │ ├── table-actions-table.hbs │ │ │ │ │ └── table-actions-table.js │ │ │ │ ├── expanded-row.hbs │ │ │ │ ├── fa-icon-wrapper.hbs │ │ │ │ ├── materialize-icon.hbs │ │ │ │ ├── no-data.hbs │ │ │ │ ├── responsive-expanded-row.hbs │ │ │ │ ├── responsive-table.hbs │ │ │ │ ├── responsive-table.js │ │ │ │ ├── row-toggle.hbs │ │ │ │ ├── rows/ │ │ │ │ │ ├── expandable-table.hbs │ │ │ │ │ ├── expandable-table.js │ │ │ │ │ ├── selectable-table.hbs │ │ │ │ │ └── selectable-table.js │ │ │ │ ├── scrolling-table.hbs │ │ │ │ ├── scrolling-table.js │ │ │ │ ├── simple-table.hbs │ │ │ │ ├── simple-table.js │ │ │ │ ├── table-loader.hbs │ │ │ │ ├── user-actions.hbs │ │ │ │ └── user-avatar.hbs │ │ │ ├── controllers/ │ │ │ │ ├── .gitkeep │ │ │ │ └── application.js │ │ │ ├── helpers/ │ │ │ │ ├── .gitkeep │ │ │ │ └── classify.js │ │ │ ├── index.html │ │ │ ├── models/ │ │ │ │ ├── .gitkeep │ │ │ │ └── user.js │ │ │ ├── router.js │ │ │ ├── routes/ │ │ │ │ ├── .gitkeep │ │ │ │ ├── columns/ │ │ │ │ │ ├── draggable.js │ │ │ │ │ ├── grouped.js │ │ │ │ │ └── resizable.js │ │ │ │ ├── cookbook/ │ │ │ │ │ ├── custom-row.js │ │ │ │ │ ├── custom-sort-icon.js │ │ │ │ │ ├── horizontal-scrolling.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── occlusion-rendering.js │ │ │ │ │ ├── pagination.js │ │ │ │ │ ├── sorting.js │ │ │ │ │ └── table-actions.js │ │ │ │ ├── cookbook.js │ │ │ │ ├── index.js │ │ │ │ ├── responsive.js │ │ │ │ ├── rows/ │ │ │ │ │ ├── expandable.js │ │ │ │ │ └── selectable.js │ │ │ │ ├── scrolling.js │ │ │ │ └── table-route.js │ │ │ ├── serializers/ │ │ │ │ └── application.js │ │ │ ├── styles/ │ │ │ │ ├── app.scss │ │ │ │ ├── loader.scss │ │ │ │ └── table.scss │ │ │ └── templates/ │ │ │ ├── application.hbs │ │ │ ├── columns/ │ │ │ │ ├── draggable.hbs │ │ │ │ ├── grouped.hbs │ │ │ │ └── resizable.hbs │ │ │ ├── cookbook/ │ │ │ │ ├── client-side.hbs │ │ │ │ ├── custom-row.hbs │ │ │ │ ├── custom-sort-icon.hbs │ │ │ │ ├── horizontal-scrolling.hbs │ │ │ │ ├── index.hbs │ │ │ │ ├── occlusion-rendering.hbs │ │ │ │ ├── pagination.hbs │ │ │ │ └── table-actions.hbs │ │ │ ├── index.hbs │ │ │ ├── responsive.hbs │ │ │ ├── rows/ │ │ │ │ ├── expandable.hbs │ │ │ │ └── selectable.hbs │ │ │ └── scrolling.hbs │ │ ├── config/ │ │ │ ├── ember-cli-update.json │ │ │ ├── ember-try.js │ │ │ ├── environment.js │ │ │ ├── icons.js │ │ │ ├── optional-features.json │ │ │ └── targets.js │ │ ├── mirage/ │ │ │ ├── config.js │ │ │ ├── factories/ │ │ │ │ └── user.js │ │ │ ├── models/ │ │ │ │ └── user.js │ │ │ ├── scenarios/ │ │ │ │ └── default.js │ │ │ └── serializers/ │ │ │ └── application.js │ │ └── public/ │ │ └── robots.txt │ ├── helpers/ │ │ ├── has-class.js │ │ ├── index.js │ │ ├── responsive.js │ │ └── table-columns.js │ ├── index.html │ ├── integration/ │ │ ├── .gitkeep │ │ ├── components/ │ │ │ ├── light-table/ │ │ │ │ ├── cells/ │ │ │ │ │ └── base-test.js │ │ │ │ └── columns/ │ │ │ │ └── base-test.js │ │ │ ├── light-table-occlusion-test.js │ │ │ ├── light-table-test.js │ │ │ ├── lt-body-occlusion-test.js │ │ │ ├── lt-body-test.js │ │ │ ├── lt-column-resizer-test.js │ │ │ ├── lt-foot-test.js │ │ │ ├── lt-head-test.js │ │ │ ├── lt-infinity-test.js │ │ │ ├── lt-row-test.js │ │ │ ├── lt-scaffolding-row-test.js │ │ │ ├── lt-scrollable-test.js │ │ │ └── lt-spanned-row-test.js │ │ └── helpers/ │ │ ├── compute-test.js │ │ └── html-safe-test.js │ ├── test-helper.js │ └── unit/ │ ├── .gitkeep │ ├── classes/ │ │ ├── column-test.js │ │ ├── row-test.js │ │ └── table-test.js │ └── mixins/ │ └── table-header-test.js └── yuidoc.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.hbs] insert_final_newline = false [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: .ember-cli ================================================ { /** Ember CLI sends analytics information by default. The data is completely anonymous, but there are times when you might want to disable this behavior. Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false, /** Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript rather than JavaScript by default, when a TypeScript version of a given blueprint is available. */ "isTypeScriptProject": false } ================================================ FILE: .eslintignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* .*/ .eslintcache # ember-try /.node_modules.ember-try/ /bower.json.ember-try /npm-shrinkwrap.json.ember-try /package.json.ember-try /package-lock.json.ember-try /yarn.lock.ember-try ================================================ FILE: .eslintrc.js ================================================ 'use strict'; module.exports = { root: true, parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { legacyDecorators: true, }, }, plugins: ['ember'], extends: [ 'eslint:recommended', 'plugin:ember/recommended', 'plugin:prettier/recommended', ], env: { browser: true, }, rules: { // TODO: enable all these rules and fix the violations 'ember/classic-decorator-no-classic-methods': 'off', 'ember/classic-decorator-hooks': 'off', 'ember/no-actions-hash': 'off', 'ember/no-classic-classes': 'off', 'ember/no-classic-components': 'off', 'ember/no-component-lifecycle-hooks': 'off', 'ember/no-computed-properties-in-native-classes': 'off', 'ember/no-observers': 'off', 'ember/no-jquery': 'error', 'ember/no-get': 'warn', 'ember/no-mixins': 'off', 'ember/no-new-mixins': 'off', 'ember/require-tagless-components': 'off', }, overrides: [ // node files { files: [ './.eslintrc.js', './.prettierrc.js', './.template-lintrc.js', './ember-cli-build.js', './index.js', './testem.js', './blueprints/*/index.js', './config/**/*.js', './tests/dummy/config/**/*.js', ], parserOptions: { sourceType: 'script', }, env: { browser: false, node: true, }, plugins: ['node'], extends: ['plugin:node/recommended'], }, { // test files files: ['tests/**/*-test.{js,ts}'], extends: ['plugin:qunit/recommended'], rules: { 'qunit/require-expect': 'off', }, }, ], }; ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - main - master pull_request: {} concurrency: group: ci-${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: test: name: "Tests" runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - name: Install Node uses: actions/setup-node@v3 with: node-version: 14.x cache: yarn - name: Install Dependencies run: yarn install --frozen-lockfile - name: Lint run: yarn lint - name: Run Tests run: yarn test:ember floating: name: "Floating Dependencies" runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 14.x cache: yarn - name: Install Dependencies run: yarn install --no-lockfile - name: Run Tests run: yarn test:ember try-scenarios: name: ${{ matrix.try-scenario }} runs-on: ubuntu-latest needs: "test" timeout-minutes: 10 strategy: fail-fast: false matrix: try-scenario: - ember-lts-3.24 - ember-lts-3.28 - ember-lts-4.4 - ember-release - ember-beta - ember-canary - ember-default-with-jquery - ember-classic - embroider-safe # - embroider-optimized steps: - uses: actions/checkout@v3 - name: Install Node uses: actions/setup-node@v3 with: node-version: 14.x cache: yarn - name: Install Dependencies run: yarn install --frozen-lockfile - name: Run Tests run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} ================================================ FILE: .gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /.env* /.pnp* /.sass-cache /.eslintcache /connect.lock /coverage/ /libpeerconnection.log /npm-debug.log* /testem.log /yarn-error.log # ember-try /.node_modules.ember-try/ /bower.json.ember-try /npm-shrinkwrap.json.ember-try /package.json.ember-try /package-lock.json.ember-try /yarn.lock.ember-try # broccoli-debug /DEBUG/ # VS Code .vscodeignore jsconfig.json /typings/* /.vscode/* # other stuff lcov.dat ================================================ FILE: .npmignore ================================================ # compiled output /dist/ /tmp/ # dependencies /bower_components/ # misc /.bowerrc /.editorconfig /.ember-cli /.env* /.eslintcache /.eslintignore /.eslintrc.js /.git/ /.github/ /.gitignore /.prettierignore /.prettierrc.js /.template-lintrc.js /.travis.yml /.watchmanconfig /bower.json /CONTRIBUTING.md /ember-cli-build.js /testem.js /tests/ /yarn-error.log /yarn.lock .gitkeep # ember-try /.node_modules.ember-try/ /bower.json.ember-try /npm-shrinkwrap.json.ember-try /package.json.ember-try /package-lock.json.ember-try /yarn.lock.ember-try ================================================ FILE: .prettierignore ================================================ # unconventional js /blueprints/*/files/ /vendor/ # compiled output /dist/ /tmp/ # dependencies /bower_components/ /node_modules/ # misc /coverage/ !.* .eslintcache .lint-todo/ # ember-try /.node_modules.ember-try/ /bower.json.ember-try /npm-shrinkwrap.json.ember-try /package.json.ember-try /package-lock.json.ember-try /yarn.lock.ember-try ================================================ FILE: .prettierrc.js ================================================ 'use strict'; module.exports = { singleQuote: true, overrides: [ { files: '**/*.hbs', options: { parser: 'glimmer', singleQuote: false, }, }, ], }; ================================================ FILE: .template-lintrc.js ================================================ 'use strict'; module.exports = { extends: 'recommended', rules: { 'no-action': false, 'no-curly-component-invocation': false, 'no-down-event-binding': false, 'no-duplicate-landmark-elements': false, 'no-heading-inside-button': false, 'no-inline-styles': false, 'no-nested-landmark': false, 'no-passed-in-event-handlers': false, 'no-yield-only': false, 'require-context-role': false, 'require-input-label': false, 'require-presentational-children': false, }, }; ================================================ FILE: .tool-versions ================================================ nodejs 14.20.0 ================================================ FILE: .watchmanconfig ================================================ { "ignore_dirs": ["tmp", "dist"] } ================================================ FILE: CHANGELOG.md ================================================ Changelog ========= ## v3.0.0-beta.2 (2023-01-17) #### :boom: Breaking Change * [#809](https://github.com/adopted-ember-addons/ember-light-table/pull/809) Convert positional params to named params ([@maxwondercorn](https://github.com/maxwondercorn)) * [#807](https://github.com/adopted-ember-addons/ember-light-table/pull/807) Create local html-safe helper/remove ember-cli-string-helpers ([@maxwondercorn](https://github.com/maxwondercorn)) #### :bug: Bug Fix * [#825](https://github.com/adopted-ember-addons/ember-light-table/pull/825) Fix: Add ...attributes to lt-body ([@IgnaceMaes](https://github.com/IgnaceMaes)) * [#824](https://github.com/adopted-ember-addons/ember-light-table/pull/824) FIx deprecation for using this.attrs for arguments in template ([@vstefanovic97](https://github.com/vstefanovic97)) #### :house: Internal * [#815](https://github.com/adopted-ember-addons/ember-light-table/pull/815) Update examples ([@rwwagner90](https://github.com/rwwagner90)) #### Committers: 4 - Gregg Martell ([@maxwondercorn](https://github.com/maxwondercorn)) - Ignace Maes ([@IgnaceMaes](https://github.com/IgnaceMaes)) - Robert Wagner ([@rwwagner90](https://github.com/rwwagner90)) - Vuk ([@vstefanovic97](https://github.com/vstefanovic97)) ## v3.0.0-beta.1 (2022-07-21) #### :boom: Breaking Change * [#805](https://github.com/adopted-ember-addons/ember-light-table/pull/805) Make light-table tagless ([@rwwagner90](https://github.com/rwwagner90)) * [#804](https://github.com/adopted-ember-addons/ember-light-table/pull/804) Remove enableSync option, require updating table rows manually ([@rwwagner90](https://github.com/rwwagner90)) * [#757](https://github.com/adopted-ember-addons/ember-light-table/pull/757) Bump deps, update GitHub actions ([@rwwagner90](https://github.com/rwwagner90)) * [#784](https://github.com/adopted-ember-addons/ember-light-table/pull/784) Remove fix-proto - Drop support for IE < 10 ([@maxwondercorn](https://github.com/maxwondercorn)) * [#780](https://github.com/adopted-ember-addons/ember-light-table/pull/780) Drop node 10 ([@maxwondercorn](https://github.com/maxwondercorn)) * [#775](https://github.com/adopted-ember-addons/ember-light-table/pull/775) Merge post 2.0.0-beta.5 branches ([@maxwondercorn](https://github.com/maxwondercorn)) * [#755](https://github.com/adopted-ember-addons/ember-light-table/pull/755) Merge 3-x into master ([@rwwagner90](https://github.com/rwwagner90)) #### :rocket: Enhancement * [#601](https://github.com/adopted-ember-addons/ember-light-table/pull/601) Avoid breaking line with sort icon in narrow columns ([@Gorzas](https://github.com/Gorzas)) * [#648](https://github.com/adopted-ember-addons/ember-light-table/pull/648) Expose shouldRecycle property of vertical-collection ([@Gaurav0](https://github.com/Gaurav0)) * [#584](https://github.com/adopted-ember-addons/ember-light-table/pull/584) Improve cells performance ([@mostafa-sakhiri](https://github.com/mostafa-sakhiri)) #### :bug: Bug Fix * [#803](https://github.com/adopted-ember-addons/ember-light-table/pull/803) Fix scroll to row ([@rwwagner90](https://github.com/rwwagner90)) * [#800](https://github.com/adopted-ember-addons/ember-light-table/pull/800) Fix responsive functionality ([@rwwagner90](https://github.com/rwwagner90)) * [#798](https://github.com/adopted-ember-addons/ember-light-table/pull/798) Fix onScrolledToBottom / use render modifiers ([@rwwagner90](https://github.com/rwwagner90)) * [#790](https://github.com/adopted-ember-addons/ember-light-table/pull/790) Import computed macros directly ([@rwwagner90](https://github.com/rwwagner90)) * [#681](https://github.com/adopted-ember-addons/ember-light-table/pull/681) use `assign` instead of `merge` ([@bekzod](https://github.com/bekzod)) * [#664](https://github.com/adopted-ember-addons/ember-light-table/pull/664) Fixing issue with multiple tables and onScrolledToBottom. ([@gmurphey](https://github.com/gmurphey)) * [#666](https://github.com/adopted-ember-addons/ember-light-table/pull/666) Pinning ip-regex to fix build issue. ([@gmurphey](https://github.com/gmurphey)) * [#596](https://github.com/adopted-ember-addons/ember-light-table/pull/596) [Draggable Column] Error when dragging column; "removeObject is not a function" ([@msenevir](https://github.com/msenevir)) * [#586](https://github.com/adopted-ember-addons/ember-light-table/pull/586) FIX - Incomplete use of htmlSafe() on Cell.style ([@ghost](https://github.com/ghost)) #### :house: Internal * [#806](https://github.com/adopted-ember-addons/ember-light-table/pull/806) Convert dummy app to glimmer components ([@rwwagner90](https://github.com/rwwagner90)) * [#801](https://github.com/adopted-ember-addons/ember-light-table/pull/801) Make embroider-safe ([@rwwagner90](https://github.com/rwwagner90)) * [#799](https://github.com/adopted-ember-addons/ember-light-table/pull/799) Tweak some icons ([@rwwagner90](https://github.com/rwwagner90)) * [#797](https://github.com/adopted-ember-addons/ember-light-table/pull/797) Minor icon fixes ([@maxwondercorn](https://github.com/maxwondercorn)) * [#796](https://github.com/adopted-ember-addons/ember-light-table/pull/796) Fix font-awesome icons, fix some build issues ([@rwwagner90](https://github.com/rwwagner90)) * [#793](https://github.com/adopted-ember-addons/ember-light-table/pull/793) Convert dummy app ([@maxwondercorn](https://github.com/maxwondercorn)) * [#792](https://github.com/adopted-ember-addons/ember-light-table/pull/792) Update ember-scrollable ([@rwwagner90](https://github.com/rwwagner90)) * [#791](https://github.com/adopted-ember-addons/ember-light-table/pull/791) Fix implicit `this`, runloop, and assign issues ([@rwwagner90](https://github.com/rwwagner90)) * [#779](https://github.com/adopted-ember-addons/ember-light-table/pull/779) Remove Bower configuration ([@maxwondercorn](https://github.com/maxwondercorn)) * [#653](https://github.com/adopted-ember-addons/ember-light-table/pull/653) Fix scroll to bottom test ([@Gaurav0](https://github.com/Gaurav0)) * [#649](https://github.com/adopted-ember-addons/ember-light-table/pull/649) Write integration tests for occlusion rendering ([@Gaurav0](https://github.com/Gaurav0)) * [#598](https://github.com/adopted-ember-addons/ember-light-table/pull/598) Stop using nativeDomClick which is deprecated. ([@plcarmel](https://github.com/plcarmel)) #### Committers: 11 - Chris Thoburn ([@runspired](https://github.com/runspired)) - Deleted user ([@ghost](https://github.com/ghost)) - Garrett Murphey ([@gmurphey](https://github.com/gmurphey)) - Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0)) - José David Cano Pérez ([@Gorzas](https://github.com/Gorzas)) - Mahen Seneviratne ([@msenevir](https://github.com/msenevir)) - Pierre-Luc Carmel Biron ([@plcarmel](https://github.com/plcarmel)) - Robert Wagner ([@rwwagner90](https://github.com/rwwagner90)) - [@bekzod](https://github.com/bekzod) - [@mostafa-sakhiri](https://github.com/mostafa-sakhiri) - maxwondercorn ([@maxwondercorn](https://github.com/maxwondercorn)) ## UNRELEASED 2.X #### Bug Fixes * [#734](https://github.com/offirgolan/ember-light-table/pull/734) bump dependency addons ([@fran-worley](https://github.com/fran-worley)) #### Internal * [#733](https://github.com/offirgolan/ember-light-table/pull/733) Bump Ember CLI to v3.16 (LTS) + bump core addons to latest ([@fran-worley](https://github.com/fran-worley)) #### Committers: 1 - Fran Worley ([@fran-worley](https://github.com/fran-worley)) ## v2.0.0-beta.5 (2020-01-16) #### :boom: Breaking Change * [#718](https://github.com/offirgolan/ember-light-table/pull/718) Replace volatile computed properties ([@fran-worley](https://github.com/fran-worley)) #### Bug Fixes * [#722](https://github.com/offirgolan/ember-light-table/pull/722) Fix resizing columns issues(2.x) ([@TomaszWegrzyn](https://github.com/TomaszWegrzyn)) * [#718](https://github.com/offirgolan/ember-light-table/pull/718) Replace volatile computed properties ([@fran-worley](https://github.com/fran-worley)) #### Enhancements * [8d0b592](https://github.com/offirgolan/ember-light-table/commit/8d0b592938ddaecc6d7353eaefab749bcc77175f) Update ember-scrollable version to jquery-less ([@alexander-alvarez](https://github.com/alexander-alvarez)) #### Committers: 3 - Alexander Alvarez ([@alexander-alvarez](https://github.com/alexander-alvarez)) - Tomasz Wegrzyn [@TomaszWegrzyn](https://github.com/TomaszWegrzyn) - Fran Worley ([@fran-worley](https://github.com/fran-worley)) ## v2.0.0-beta.4 (2019-08-19) #### :boom: Breaking Change * [#701](https://github.com/offirgolan/ember-light-table/pull/701) Convert ES6 native classes to ember objects ([@fran-worley](https://github.com/fran-worley)) * [#713](https://github.com/offirgolan/ember-light-table/pull/713) Set minimum supported ember version at 3.4 ([@fran-worley](https://github.com/fran-worley)) * [#698](https://github.com/offirgolan/ember-light-table/pull/698) Drop support for Node 6 as end of life 30 April 2019 ([@fran-worley](https://github.com/fran-worley)) #### Bug Fixes * [#701](https://github.com/offirgolan/ember-light-table/pull/701) Convert ES6 native classes to ember objects ([@fran-worley](https://github.com/fran-worley)) * [#693](https://github.com/offirgolan/ember-light-table/pull/693) Update ember-in-viewport, ember-wormhole ([@fran-worley](https://github.com/fran-worley)) * [#692](https://github.com/offirgolan/ember-light-table/pull/692) Replace propertyWillChange/propertyDidChange with notifyPropertyChange ([@mmadsen2](https://github.com/mmadsen2)) * [#681](https://github.com/offirgolan/ember-light-table/pull/681) use `assign` instead of `merge` ([@bekzod](https://github.com/bekzod)) * [#664](https://github.com/offirgolan/ember-light-table/pull/664) Fixing issue with multiple tables and onScrolledToBottom. ([@gmurphey](https://github.com/gmurphey)) * [#666](https://github.com/offirgolan/ember-light-table/pull/666) Pinning ip-regex to fix build issue. ([@gmurphey](https://github.com/gmurphey)) * [#596](https://github.com/offirgolan/ember-light-table/pull/596) [Draggable Column] Error when dragging column; "removeObject is not a function" ([@msenevir](https://github.com/msenevir)) #### Internal * [#716](https://github.com/offirgolan/ember-light-table/pull/716) Bump to ember cli 3.12 and update dependencies ([@fran-worley](https://github.com/fran-worley)) * [#697](https://github.com/offirgolan/ember-light-table/pull/697) Migrate from ember-cli-changelog to lerna-changelog ([@fran-worley](https://github.com/fran-worley)) * [#696](https://github.com/offirgolan/ember-light-table/pull/696) Bump Ember CLI to 3.8 and update other dependencies ([@fran-worley](https://github.com/fran-worley)) * [#693](https://github.com/offirgolan/ember-light-table/pull/693) Update ember-in-viewport, ember-wormhole ([@fran-worley](https://github.com/fran-worley)) #### Committers: 5 - mmadsen2 [@mmadsen2](https://github.com/mmadsen2) - bek ([@bekzod](https://github.com/bekzod)) - Garrett Murphey ([@gmurphey](https://github.com/gmurphey)) - Mahen Seneviratne ([@msenevir](https://github.com/msenevir)) - Fran Worley ([@fran-worley](https://github.com/fran-worley)) ## v2.0.0-beta.3 (2019-05-9) #### Breaking - [#657](https://github.com/offirgolan/ember-light-table/pull/657) Officially drop support for node 4 ([@Gaurav0](https://github.com/Gaurav0)) #### Enhancements * [#601](https://github.com/offirgolan/ember-light-table/pull/601) Avoid breaking line with sort icon in narrow columns ([@Goras](https://github.com/Gorzas)) * [#648](https://github.com/offirgolan/ember-light-table/pull/648) Expose shouldRecycle property of vertical-collection ([@Gaurav0](https://github.com/Gaurav0)) #### Bug Fixes * [#672](https://github.com/offirgolan/ember-light-table/pull/672) bump vertical-collection to v1.0.0-beta.13 ([@fran-worley](https://github.com/fran-worley)) * [#686](https://github.com/offirgolan/ember-light-table/pull/686) refactor: Remove sendAction() calls ([@MichalBryxi](https://github.com/MichalBryxi)) * [#673](https://github.com/offirgolan/ember-light-table/pull/673) Replace merge with assign ([@fran-worley](https://github.com/fran-worley)) * [#677](https://github.com/offirgolan/ember-light-table/pull/677) ensure ember-scrollable updates when rows are updated ([@fran-worley](https://github.com/fran-worley)) #### Internal * [#598](https://github.com/offirgolan/ember-light-table/pull/598) Stop using nativeDomClick which is deprecated. ([@plcarmel](https://github.com/plcarmel)) * [#649](https://github.com/offirgolan/ember-light-table/pull/649) Write integration tests for occlusion rendering ([@Gaurav0](https://github.com/Gaurav0)) * [#651](https://github.com/offirgolan/ember-light-table/pull/651) Update ember-cli-changelog ([@Gaurav0](https://github.com/Gaurav0)) * [#653](https://github.com/offirgolan/ember-light-table/pull/653) Fix scroll to bottom test ([@Gaurav0](https://github.com/Gaurav0)) * [#655](https://github.com/offirgolan/ember-light-table/pull/655) Update ember-scrollable ([@Gaurav0](https://github.com/Gaurav0)) * [#656](https://github.com/offirgolan/ember-light-table/pull/656) Assert and Test compatibility with LTS 3.4 ([@Gaurav0](https://github.com/Gaurav0)) #### Committers: 4 - Pierre-Luc Carmel Biron ([@plcarmel](https://github.com/plcarmel)) - Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0)) - Fran Worley ([@fran-worley](https://github.com/fran-worley)) - Michal Bryxi ([@MichalBryxi](https://github.com/MichalBryxi)) ## v2.0.0-beta.2 (2018-10-29) #### Enhancements *[#593](https://github.com/offirgolan/ember-light-table/pull/593) Remove jQuery usage ((@donaldwasserman)[https://github.com/donaldwasserman]) #### Committers: 1 - Donald Wasserman ((@donaldwasserman)[https://github.com/donaldwasserman]) ## v2.0.0-beta.1 (2018-10-26) #### Bug Fixes *[#590](https://github.com/offirgolan/ember-light-table/pull/590) replace `sendAction` with modern callable methods ((@donaldwasserman)[https://github.com/donaldwasserman]) #### Committers: 1 - Donald Wasserman ((@donaldwasserman)[https://github.com/donaldwasserman]) ## v2.0.0-beta.0 (2018-10-25) #### Enhancements * [#584](https://github.com/offirgolan/ember-light-table/pull/584) Improve cells performance ([@mostafa-sakhiri](https://github.com/mostafa-sakhiri)) #### Bug Fixes * [#586](https://github.com/offirgolan/ember-light-table/pull/586) Incomplete use of htmlSafe() on Cell.style. ([@richard-viney](https://github.com/richard-viney)) #### Committers: 2 - mostafa-sakhiri ([@mostafa-sakhiri](https://github.com/mostafa-sakhiri)) - Richard Viney ([@richard-viney](https://github.com/richard-viney)) ## UNRELEASED MASTER #### Enhancements * [#584](https://github.com/offirgolan/ember-light-table/pull/584) Improve cells performance ([@mostafa-sakhiri](https://github.com/mostafa-sakhiri)) * [#601](https://github.com/offirgolan/ember-light-table/pull/601) Avoid breaking line with sort icon in narrow columns ([@Goras](https://github.com/Gorzas)) * [#648](https://github.com/offirgolan/ember-light-table/pull/648) Expose shouldRecycle property of vertical-collection ([@Gaurav0](https://github.com/Gaurav0)) #### Bug Fixes * [#692](https://github.com/offirgolan/ember-light-table/pull/692) Replace propertyWillChange/propertyDidChange with notifyPropertyChange ([@mmadsen2](https://github.com/mmadsen2)) * [#681](https://github.com/offirgolan/ember-light-table/pull/681) use `assign` instead of `merge` ([@bekzod](https://github.com/bekzod)) * [#664](https://github.com/offirgolan/ember-light-table/pull/664) Fixing issue with multiple tables and onScrolledToBottom. ([@gmurphey](https://github.com/gmurphey)) * [#666](https://github.com/offirgolan/ember-light-table/pull/666) Pinning ip-regex to fix build issue. ([@gmurphey](https://github.com/gmurphey)) * [#596](https://github.com/offirgolan/ember-light-table/pull/596) [Draggable Column] Error when dragging column; "removeObject is not a function" ([@msenevir](https://github.com/msenevir)) * [#586](https://github.com/offirgolan/ember-light-table/pull/586) Incomplete use of htmlSafe() on Cell.style. ([@richard-viney](https://github.com/richard-viney)) #### Internal * [#598](https://github.com/offirgolan/ember-light-table/pull/598) Stop using nativeDomClick which is deprecated. ([@plcarmel](https://github.com/plcarmel)) - [#649](https://github.com/offirgolan/ember-light-table/pull/649) Write integration tests for occlusion rendering ([@Gaurav0](https://github.com/Gaurav0)) - [#653](https://github.com/offirgolan/ember-light-table/pull/653) Fix scroll to bottom test ([@Gaurav0](https://github.com/Gaurav0)) - [#651](https://github.com/offirgolan/ember-light-table/pull/651) Update ember-cli-changelog ([@Gaurav0](https://github.com/Gaurav0)) #### Committers: 8 - Garrett Murphey ([@gmurphey](https://github.com/gmurphey)) - Mahen Seneviratne ([@msenevir](https://github.com/msenevir)) - mmadsen2 [@mmadsen2](https://github.com/mmadsen2) - bek ([@bekzod](https://github.com/bekzod)) - José David Cano Pérez ([@Goras](https://github.com/Gorzas)) - Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0)) - mostafa-sakhiri ([@mostafa-sakhiri](https://github.com/mostafa-sakhiri)) - Richard Viney ([@richard-viney](https://github.com/richard-viney)) - Pierre-Luc Carmel Biron ([@plcarmel](https://github.com/plcarmel)) ## v1.13.2 (2018-08-26) #### Bug Fixes - [e345fec](https://github.com/offirgolan/ember-light-table/commit/e345fec67916fc18ced40cd161dbf38de934e894) Use isArray to check columns, rows type ([@quaertym](https://github.com/quaertym)) #### Internal - [7b50190](https://github.com/offirgolan/ember-light-table/commit/7b5019003b01ad4bb646d1f142ab63059bf4efd4) Update other dependencies ([@alexander-alvarez](https://github.com/alexander-alvarez)) - [715d94b](https://github.com/offirgolan/ember-light-table/commit/715d94b47cd5cbe31af99db8d6faf8aa7c00f124) Update Ember scrollable ([@Gaurav0](https://github.com/Gaurav0)) #### Committers: 3 - Emre Unal ([@quaertym](https://github.com/quaertym) - Alex Alvarez ([@alexander-alvarez](https://github.com/alexander-alvarez)) - Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0)) ## v1.13.1 (2018-06-22) #### Internal - [ace7f4c](https://github.com/offirgolan/ember-light-table/commit/ace7f4cc3535853fb07c406d5e3a06467d6a7f0d) Update ember-cli to 3.1.4 ([@jrjohnson](https://github.com/jrjohnson)) - [bf6edb8](https://github.com/offirgolan/ember-light-table/commit/bf6edb83fdc7fa195c786b7b1aa1826edde4518a) Update all dependencies ([@jrjohnson](https://github.com/jrjohnson)) #### Committers: 1 - Jonathan Johnson ([@jrjohnson](https://github.com/jrjohnson)) ## v1.13.0 (2018-06-21) #### Commits - [5dccab6a](https://github.com/offirgolan/ember-light-table/commit/5dccab6a47644eef0381b30004802aa88958f176) **test(light-table/onScrolledToBottom)**: skip *by [Jan Buschtöns](https://github.com/buschtoens)* - [a3af0b48](https://github.com/offirgolan/ember-light-table/commit/a3af0b483e1bdf354031832faf72acbf1cbbdb31) **test(lt-body/scaffolding)**: use querySelectorAll for subquery *by [Jan Buschtöns](https://github.com/buschtoens)* - [07972532](https://github.com/offirgolan/ember-light-table/commit/079725321d7a540c58e970815a757fe4298f4cd2) **fix(lt-infinity)**: disable intersection observer *by [Jan Buschtöns](https://github.com/buschtoens)* - [59054009](https://github.com/offirgolan/ember-light-table/commit/59054009cce1eed818926eb34c1447a4fd0cdd82) **fix(classes/{Column,Row})**: assign properties in the Ember init hook *by [Jan Buschtöns](https://github.com/buschtoens)* - [2cc88bac](https://github.com/offirgolan/ember-light-table/commit/2cc88bacf8389ff59b2680205f9bff50fd0ab15c) **fix(lt-infinity)**: set default scroll buffer to 0 *by [Jan Buschtöns](https://github.com/buschtoens)* - [35c89933](https://github.com/offirgolan/ember-light-table/commit/35c8993315aba2730c3cbeaa6b3e3b3a2ad445f0) **refactor(mixins/table-header)**: set sharedOptions.fixed(Header|Footer) once in init *by [Jan Buschtöns](https://github.com/buschtoens)* - [5a4fd499](https://github.com/offirgolan/ember-light-table/commit/5a4fd499195884235cde9924715ad59c0f74ffea) **docs(README)**: fix Ember.js versions badge *by [Jan Buschtöns](https://github.com/buschtoens)* - [fa9e50b8](https://github.com/offirgolan/ember-light-table/commit/fa9e50b89ff00f652d77177e254326f1fce496e4) **docs(README)**: add logo *by [Jan Buschtöns](https://github.com/buschtoens)* - [0d99b0f4](https://github.com/offirgolan/ember-light-table/commit/0d99b0f4655d6a02d0ee1d3c1801db822922269d) **fix(Table)**: constructor asserts param types (#522) *by [Redmond Tran](https://github.com/RedTn)* - [f5b56c97](https://github.com/offirgolan/ember-light-table/commit/f5b56c9710d6947e66f396ff511a67d57a1edf95) **fix(draggable-column)**: guard against undefined sourceColumn (#521) *by [Craig MacKenzie](https://github.com/cmackenz)* ## v1.12.2 #### Commits - [6d71609d](https://github.com/offirgolan/ember-light-table/commit/6d71609da875453ff9792f636b88c5c2e7e7f385) **fix(occlusion)**: wire up lastVisibleChanged *by [Jan Buschtöns](https://github.com/buschtoens)* ## v1.12.1 This patch release primarily re-enabled dynamic column sizing for occlusion tables and wires up the `onScrolledToBottom` and `onScroll` actions. However, we have to (temporarily) remove expanded rows. See [this comment in #514]( https://github.com/offirgolan/ember-light-table/pull/514#issuecomment-346613745) for more details. #### Commits - [55f5962e](https://github.com/offirgolan/ember-light-table/commit/55f5962e934b2c003e29812bc9ec76be18bbce7e) **fix(occlusion)**: remove colspan from loader and no-data spanned rows *by [Jan Buschtöns](https://github.com/buschtoens)* - [5f6637e3](https://github.com/offirgolan/ember-light-table/commit/5f6637e33445b0b73cc572f902661d5c98559757) **fix(occlusion)**: temporarily remove yield inside vertical-collection *by [Jan Buschtöns](https://github.com/buschtoens)* - [ee30bb83](https://github.com/offirgolan/ember-light-table/commit/ee30bb83ddad3f7e4c62dba60f1f33605f494199) **fix(occlusion)**: wire up onScroll and onScrolledToBottom *by [Jan Buschtöns](https://github.com/buschtoens)* - [b93f0671](https://github.com/offirgolan/ember-light-table/commit/b93f0671ad635bf4024aaab1f7773db52df45cc7) **fix(occlusion)**: pass bufferSize based on scrollBuffer *by [Jan Buschtöns](https://github.com/buschtoens)* - [c240a23c](https://github.com/offirgolan/ember-light-table/commit/c240a23c837d750c41511f93c190976e1d40d588) **fix(occlusion)**: tbody sizing via flexbox, dynamic column widths *by [Jan Buschtöns](https://github.com/buschtoens)* - [140ea0d6](https://github.com/offirgolan/ember-light-table/commit/140ea0d60058136de6ad632aaf8b38b2c292b1c7) **docs(README)**: update "help wanted" label link *by [Jan Buschtöns](https://github.com/buschtoens)* ## v1.12.0 #### Commits - [7feb748c](https://github.com/offirgolan/ember-light-table/commit/7feb748c670f31975156de2abca59958266daddf) **feat(lt-body)**: Preliminary Vertical collection integration (#483) *by [Alex Alvarez](https://github.com/alexander-alvarez)* ## v1.11.0 #### Commits - [e16af170](https://github.com/offirgolan/ember-light-table/commit/e16af170a67492d4644ff113836706f0595387cc) **feat(table-header)**: custom icon components (#489) *by [RedTn](https://github.com/RedTn)* - [daa657ee](https://github.com/offirgolan/ember-light-table/commit/daa657eea93414bbe2f9c34dfb99cd741a0d30f3) **fix(draggable-column)**: ensure the drop target remains properly identified (#496) *by [akshay-kr](https://github.com/akshay-kr)* - [2e2faf93](https://github.com/offirgolan/ember-light-table/commit/2e2faf93a0d7979fa8e74de85c0eea18addc9564) **fix(draggable-column)**: ensure the drop target remains properly identified (#418) *by [RustyToms](https://github.com/RustyToms)* ## v1.10.0 ### Pull Requests - [#445](https://github.com/offirgolan/ember-light-table/pull/445) **readme**: add link to #e-light-table Slack channel *by [Jan Buschtöns](https://github.com/buschtoens)* - [#449](https://github.com/offirgolan/ember-light-table/pull/449) **head & foot**: make `table.height` optional, warn in `table-header` mixin *by [Jan Buschtöns](https://github.com/buschtoens)* - [#451](https://github.com/offirgolan/ember-light-table/pull/451) **table**: add `setRowsSynced` method *by [Jan Buschtöns](https://github.com/buschtoens)* - [#457](https://github.com/offirgolan/ember-light-table/pull/457) **light-table**: add `extra` property *by [Jan Buschtöns](https://github.com/buschtoens)* - [#464](https://github.com/offirgolan/ember-light-table/pull/464) **light-table**: add `iconSortable` property *by [Vince Eberle](https://github.com/ignatius-j)* - [#473](https://github.com/offirgolan/ember-light-table/pull/473) **refactor**: migrate to RFC 176 style ES6 module imports *by [Robert Wagner](https://github.com/rwwagner90)* - [#462](https://github.com/offirgolan/ember-light-table/pull/462) **ci/travis**: use headless Chrome *by [Jan Buschtöns](https://github.com/buschtoens)* - [#466](https://github.com/offirgolan/ember-light-table/pull/466) **ci**: align Chrome headless usage with ember-cli 2.15 *by [Jan Buschtöns](https://github.com/buschtoens)* #### Commits - [a60647ab](https://github.com/offirgolan/ember-light-table/commit/a60647abb87904e031afc90d5a083a5496da53fa) **test(light-table)**: add case for `extra` and `tableActions` *by [Jan Buschtöns](https://github.com/buschtoens)* ## v1.9.0 ### Pull Requests - [#390](https://github.com/offirgolan/ember-light-table/pull/390) Move ember-truth-helpers to dependencies *by [fsmanuel/chore](https://github.com/fsmanuel/chore)* - [#422](https://github.com/offirgolan/ember-light-table/pull/422) Fix missing 'as body' on example code *by [Ahmad Suhendri](https://github.com/ahmadsoe)* - [#438](https://github.com/offirgolan/ember-light-table/pull/438) Modernize ELT, kill bower and enable yarn *by [Jan Buschtöns](https://github.com/buschtoens)* - [#440](https://github.com/offirgolan/ember-light-table/pull/440) **tests/table**: isEmpty & isEmpty (enableSync = true) *by [Jan Buschtöns](https://github.com/buschtoens)* - [#441](https://github.com/offirgolan/ember-light-table/pull/441) **readme**: add more information on collaborating *by [Jan Buschtöns](https://github.com/buschtoens)* - [#439](https://github.com/offirgolan/ember-light-table/pull/439) Polyfill support for __proto__ in IE <= 10 *by [Jan Buschtöns](https://github.com/buschtoens)* - [#421](https://github.com/offirgolan/ember-light-table/pull/421) **lt-body**: add enableScaffolding option *by [Jan Buschtöns](https://github.com/buschtoens)* - [#445](https://github.com/offirgolan/ember-light-table/pull/445) **readme**: add link to #e-light-table Slack channel *by [Jan Buschtöns](https://github.com/buschtoens)* - [#449](https://github.com/offirgolan/ember-light-table/pull/449) **head & foot**: make `table.height` optional, warn in `table-header` mixin *by [Jan Buschtöns](https://github.com/buschtoens)* - [#451](https://github.com/offirgolan/ember-light-table/pull/451) **table**: add `setRowsSynced` method *by [Jan Buschtöns](https://github.com/buschtoens)* #### Commits - [b0db5b15](https://github.com/offirgolan/ember-light-table/commit/b0db5b15d96e1b7bfd8f96a3001bfac6338ec0b3) **update ember-scrollable version (#408)**: //github.com/offirgolan/ember-light-table/issues/396 *by [Rusty Toms](https://github.com/RustyToms)* ## v1.8.6 ### Pull Requests - [#385](https://github.com/offirgolan/ember-light-table/pull/385) Fixes 'onScroll' deprecation *by [Alex Alvarez](https://github.com/alexander-alvarez)* - [#386](https://github.com/offirgolan/ember-light-table/pull/386) Move ember-cli-string-helpers to dependencies *by [Jonathan Steele](https://github.com/ynnoj)* ## v1.8.5 ### Pull Requests - [#383](https://github.com/offirgolan/ember-light-table/pull/383) Fix for ember-composable-helpers addon incompatibility *by [Nicholas McClay](https://github.com/nmcclay)* ## v1.8.4 ### Pull Requests - [#348](https://github.com/offirgolan/ember-light-table/pull/348) Update Column.js to support parent *by [Alex Alvarez](https://github.com/alexander-alvarez)* ## v1.8.3 ### Pull Requests - [#322](https://github.com/offirgolan/ember-light-table/pull/322) Fix typo for default colspan *by [Ilya Radchenko](https://github.com/knownasilya)* - [#318](https://github.com/offirgolan/ember-light-table/pull/318) Change to tag for sorting icons *by [Julie Graceffa](https://github.com/jewls618)* - [#332](https://github.com/offirgolan/ember-light-table/pull/332) Bump ember-scrollable *by [Offir Golan](https://github.com/offirgolan)* #### Commits - [70320d05](https://github.com/offirgolan/ember-light-table/commit/70320d05a99021e35d5c0878dccf6499ce88216c) **fix(package)**: update ember-get-config to version 0.2.1 *by [greenkeeper[bot]](https://github.com/greenkeeper[bot])* ## v1.8.2 ### Pull Requests - [#321](https://github.com/offirgolan/ember-light-table/pull/321) Update ember-scrollable to the latest version 🚀 *by [Offir Golan](https://github.com/offirgolan/greenkeeper)* #### Commits - [e2438a50](https://github.com/offirgolan/ember-light-table/commit/e2438a508e890ee7ccce7a02fde9654c047ebee8) **fix(package)**: update ember-scrollable to version 0.4.0 *by [greenkeeper[bot]](https://github.com/greenkeeper[bot])* ## v1.8.1 ### Pull Requests - [#303](https://github.com/offirgolan/ember-light-table/pull/303) Remove deprecated Ember.K *by [cibernox](https://github.com/cibernox)* - [#308](https://github.com/offirgolan/ember-light-table/pull/308) Update ember-in-viewport to the latest version 🚀 *by [Offir Golan](https://github.com/offirgolan/greenkeeper)* ## v1.8.0 ### Pull Requests - [#290](https://github.com/offirgolan/ember-light-table/pull/290) [FEATURE] Add selectOnClick option *by [Offir Golan](https://github.com/offirgolan)* ## v1.7.1 ### Pull Requests - [#286](https://github.com/offirgolan/ember-light-table/pull/286) [BUGFIX] In viewport left/right tolerance adjustment *by [Offir Golan](https://github.com/offirgolan)* ## v1.7.0 ### Pull Requests - [#222](https://github.com/offirgolan/ember-light-table/pull/222) [FEATURE] Support horizontal scrolling *by [Offir Golan](https://github.com/offirgolan)* - [#281](https://github.com/offirgolan/ember-light-table/pull/281) [BUGFIX] Resolve IE drag and drop crashes *by [Offir Golan](https://github.com/offirgolan)* ## v1.6.1 ### Pull Requests - [#266](https://github.com/offirgolan/ember-light-table/pull/266) [BUGFIX] Require ember-scrollable@^0.3.5 *by [Jan Buschtöns](https://github.com/buschtoens)* ## v1.6.0 ### Pull Requests - [#252](https://github.com/offirgolan/ember-light-table/pull/252) [BUGFIX] Resizable column improvements *by [Offir Golan](https://github.com/offirgolan)* - [#254](https://github.com/offirgolan/ember-light-table/pull/254) [BUGFIX] repeated scrollToRow *by [Jan Buschtöns](https://github.com/buschtoens)* - [#258](https://github.com/offirgolan/ember-light-table/pull/258) [FEATURE] Draggable Columns *by [Offir Golan](https://github.com/offirgolan)* ## v1.5.2 ### Pull Requests - [#244](https://github.com/offirgolan/ember-light-table/pull/244) [FEATURE] minResizeWidth + Event bubbling fix *by [Offir Golan](https://github.com/offirgolan)* ## v1.5.1 ### Pull Requests - [#241](https://github.com/offirgolan/ember-light-table/pull/241) [BUGFIX] Add safe checks to scroll logic *by [Offir Golan](https://github.com/offirgolan)* ## v1.5.0 ### Pull Requests - [#221](https://github.com/offirgolan/ember-light-table/pull/221) [FEATURE] Add label to base column to be used in column types *by [Offir Golan](https://github.com/offirgolan)* - [#228](https://github.com/offirgolan/ember-light-table/pull/228) [FEATURE] ScrollTo and ScrollToRow *by [Jan Buschtöns](https://github.com/buschtoens)* - [#235](https://github.com/offirgolan/ember-light-table/pull/235) [FEATURE] Responsive Columns *by [Offir Golan](https://github.com/offirgolan)* ## v1.4.4 ### Pull Requests - [#220](https://github.com/offirgolan/ember-light-table/pull/220) [BUGFIX] CPs using filterBy should also be dependent on the array size *by [Offir Golan](https://github.com/offirgolan)* ## v1.4.3 ### Pull Requests - [#214](https://github.com/offirgolan/ember-light-table/pull/214) [BUGFIX] enableSync sorting duplicates records *by [Offir Golan](https://github.com/offirgolan)* ## v1.4.2 ### Pull Requests - [#211](https://github.com/offirgolan/ember-light-table/pull/211) Upgrade dependencies to support Ember 2.9.0 *by [Offir Golan](https://github.com/offirgolan)* ## v1.4.1 ### Pull Requests - [#204](https://github.com/offirgolan/ember-light-table/pull/204) Update ember-scrollable *by [Taras Mankovski](https://github.com/taras)* ## v1.4.0 ### Pull Requests - [#167](https://github.com/offirgolan/ember-light-table/pull/167) [FEATURE] Two-way sync between rows and model *by [Offir Golan](https://github.com/offirgolan)* - [#177](https://github.com/offirgolan/ember-light-table/pull/177) [FEATURE] Customizable components *by [Taras Mankovski](https://github.com/taras)* - [#183](https://github.com/offirgolan/ember-light-table/pull/183) [BUGFIX] Add footer scaffolding and move width into style attr *by [Offir Golan](https://github.com/offirgolan)* ## v1.3.1 ### Pull Requests - [#166](https://github.com/offirgolan/ember-light-table/pull/166) [FEATURE] Introduce `resizeOnDrag` for column resizing *by [Offir Golan](https://github.com/offirgolan)* ## v1.3.0 ### Pull Requests - [#164](https://github.com/offirgolan/ember-light-table/pull/164) [FEATURE] Rename flattenedColumns to allColumns *by [Offir Golan](https://github.com/offirgolan)* ## v1.2.0 ### Pull Requests - [#160](https://github.com/offirgolan/ember-light-table/pull/160) [FEATURE] `multiSelectRequiresKeyboard` option for toggling row selection without ctrl/cmd *by [Jeremy Bargar](https://github.com/bargar)* - [#163](https://github.com/offirgolan/ember-light-table/pull/163) [BUGFIX] Autoprefix addon.css (until PostCSS is up and running) + install ember-cli-autoprefixer to prefix demo page CSS *by [Offir Golan](https://github.com/offirgolan)* - [#163](https://github.com/offirgolan/ember-light-table/pull/163) [BUGFIX] Pass table instance + rawValue to custom cell component *by [Offir Golan](https://github.com/offirgolan)* - [#163](https://github.com/offirgolan/ember-light-table/pull/163) [BUGFIX] Use style instead of deprecated width attribute *by [Offir Golan](https://github.com/offirgolan)* - [#163](https://github.com/offirgolan/ember-light-table/pull/163) [BUGFIX] Remove readOnly from value in base cell so it can be modified *by [Offir Golan](https://github.com/offirgolan)* - [#163](https://github.com/offirgolan/ember-light-table/pull/163) [BUGFIX] Column resizer now applies width to table rows on `mouseUp` instead of on `mouseMove` *by [Offir Golan](https://github.com/offirgolan)* ## v1.1.1 ### Pull Requests - [#133](https://github.com/offirgolan/ember-light-table/pull/133) [BUGFIX] onScrolledToBottom doesnt get re-triggered after removing table rows *by [Offir Golan](https://github.com/offirgolan)* ## v1.1.0 ### Pull Requests - [#98](https://github.com/offirgolan/ember-light-table/pull/98) [FEATURE] Resizable Columns *by [Offir Golan](https://github.com/offirgolan)* - [#115](https://github.com/offirgolan/ember-light-table/pull/115) [FEATURE] Style Table Element *by [Offir Golan](https://github.com/offirgolan)* - [#117](https://github.com/offirgolan/ember-light-table/pull/117) [BUGFIX] onScrolledToBottom doesnt get re-triggered when there arent enough rows in the table *by [Offir Golan](https://github.com/offirgolan)* - [#122](https://github.com/offirgolan/ember-light-table/pull/122) [BUGFIX] Remove deprecations *by [Offir Golan](https://github.com/offirgolan)* #### Commits - [8b80d645](https://github.com/offirgolan/ember-light-table/commit/8b80d645c59efbb37d2b92e9e839ec2bbcd29ae2) **make text unselectable if column is sortable** *by [Ben Limmer](https://github.com/blimmer)* ## v1.0.1 - [#101](https://github.com/offirgolan/ember-light-table/pull/101) Always use Ember object `get` by [@blimmer](https://github.com/blimmer) - [#102](https://github.com/offirgolan/ember-light-table/pull/102) Use ember-font-awesome vs. ember-cli-font-awesome by [@blimmer](https://github.com/blimmer) ## v1.0.0 - [#30](https://github.com/offirgolan/ember-light-table/pull/30) Support custom cell and column types - [#38](https://github.com/offirgolan/ember-light-table/pull/38) Add table reference to custom column components - [#40](https://github.com/offirgolan/ember-light-table/pull/40) Use native Ember trackpad scroll emulator via ember-scrollable - [@taras](https://github.com/taras) - [#42](https://github.com/offirgolan/ember-light-table/pull/42) Add hideable option to columns __BREAKING CHANGES__ 1. `headerComponent` in column definition has been renamed to `component` 2. `onScrolledToBottom` action has been moved from `{{light-table}}` to `{{t.body}}` component 3. `height` has been moved from `{{t.body}}` to `{{light-table}}` component ## v0.1.9 - Remove tag-less cell component since it was restricting a bunch of Ember.Component features such as class name bindings, events, etc. ## v0.1.8 - [#14](https://github.com/offirgolan/ember-light-table/pull/14) Table cell performance to decrease render time by almost half ## v0.1.7 - Setup scroll event binding only if action is present - Add is-expanded css class to row ## v0.1.6 - Rename `formatter` to `format` ## v0.1.5 - Ability to provide a formatter function to column definition that will be used to computed the cell value ## v0.1.4 - [#4](https://github.com/offirgolan/ember-light-table/pull/4) Apply width on cell component based on it's columns width [@steffenbrem](https://github.com/steffenbrem) - [#5](https://github.com/offirgolan/ember-light-table/issues/5) Add insertRowAt & insertColumnAt to public Table API - Return pushed/inserted rows & columns from public APIs ## v0.1.3 - Fixed an issue where cell value was not bound ## v0.1.2 - Ability to push rows using ArrayProxy instances ## v0.1.1 - [#1](https://github.com/offirgolan/ember-light-table/issues/1) Add default CSS ## v0.1.0 - Initial Release ================================================ FILE: CONTRIBUTING.md ================================================ # How To Contribute ## Installation * `git clone ` * `cd ember-light-table` * `yarn install` ## Linting * `yarn lint` * `yarn lint:fix` ## Running tests * `ember test` – Runs the test suite on the current Ember version * `ember test --server` – Runs the test suite in "watch mode" * `ember try:each` – Runs the test suite against multiple Ember versions ## Running the dummy application * `ember serve` * Visit the dummy application at [http://localhost:4200](http://localhost:4200). For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2016 - 2019 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================
Ember Light Table
[![Ember Versions](https://img.shields.io/badge/Ember.js%20Versions-%5E3.4%20and%20%5E4.0-brightgreen.svg)](https://travis-ci.org/offirgolan/ember-light-table) [![Build Status](https://travis-ci.org/offirgolan/ember-light-table.svg)](https://travis-ci.org/offirgolan/ember-light-table) [![npm version](https://badge.fury.io/js/ember-light-table.svg)](http://badge.fury.io/js/ember-light-table) [![Download Total](https://img.shields.io/npm/dt/ember-light-table.svg)](http://badge.fury.io/js/ember-light-table) [![Ember Observer Score](https://emberobserver.com/badges/ember-light-table.svg)](https://emberobserver.com/addons/ember-light-table) [![Code Climate](https://codeclimate.com/github/offirgolan/ember-light-table/badges/gpa.svg)](https://codeclimate.com/github/offirgolan/ember-light-table) **Ember Light Table** is a lightweight contextual component based table addon that follows Ember's actions up, data down ideology. # WARNING _The API for initializing Ember Light Table (v2.x) has recently changed. Please review the [Pull Request](https://github.com/adopted-ember-addons/ember-light-table/pull/701) for more information._ ## Features - Custom component based column headers and cells - Infinite scroll support - Select & Multi-select with keyboard support (CMD/CTRL, SHIFT) - Fixed header and footer - Grouped columns - Resizable columns - Expandable rows - Responsive - Scroll Tracking - Easy table manipulation - Easy override to table header, body, and footer - Contextual component for header, body, and footer, as well as loading, no data, and expanded row - **EXPERIMENTAL** Occlusion rendering leveraging [vertical-collection](https://github.com/html-next/vertical-collection). See [Demo](http://adopted-ember-addons.github.io/ember-light-table/#/cookbook/occlusion-rendering). Compatibility ------------------------------------------------------------------------------ * Ember.js v3.24 or above * Ember CLI v3.24 or above * Node.js v14 or above Installation ------------------------------------------------------------------------------ ```shell ember install ember-light-table ``` ## :link: Helpful Links - :rocket: [Live Demo][demo] - :books: [API Documentation][docs] - :pencil: [Changelog](CHANGELOG.md) ## :sos: Looking for Help? - :warning: **Bug reports**: If your bug hasn't been reported yet, please [**open an issue**][new-issue]. Try to pick a short but descriptive title. Make sure you're using the latest version of *ember-light-table*. In the issue body, try to provide exact steps for reproduction, ideally with example code. If you can't, please include any and all error messages, as many details as possible and exact information on which Ember.js / ember-cli version and browser / OS you're using. - :question: **Questions**: As with bugs, please make sure the question wasn't asked before. Also, see if the [Live Demo][demo], [Cookbook][cookbook] or [API docs][docs] already cover your problem. If not, please do [**open an issue**][new-issue]. - ![Discord](https://img.shields.io/discord/480462759797063690.svg?logo=discord) Join Ember on [Discord](https://discord.gg/zT3asNS). We're happy to help you there! ## :metal: Getting Involved We're glad you love *ember-light-table* just as much as we do! If you want to help us making it even better, we would be delighted to have you on board, even if you've just started using Ember. ### :bulb: Submitting Ideas If you've got a great idea in store, but don't feel up for the task to implement it yourself, just [**open an issue**][new-issue]. That way you can put your thoughts out there for discussion and we can evolve it further. We'll see, whether this feature is a good fit for *ember-light-table* itself or could better be implemented in a third-party addon. You're also always invited to chime in on ongoing issues, *especially* for issues marked with [**ideas-wanted**][ideas-wanted]. ### :keyboard: Contributing Code Contributing to an Ember addon is a great opportunity to get in touch with advanced concepts. You're also getting free peer review for your code as a bonus! And most importantly, you're doing something good for the community! #### :sparkles: New Features If you want to make a bigger change, we recommend [**opening an issue**][new-issue] first, so we can agree on the best possible implementation first and none of your work goes to waste. #### :eyes: Don't know where to start? You don't have a specific feature in mind but want to help out anyways? Awesome! Issues marked with [**help wanted**][help-wanted] are generally agreed upon and ready to get implemented. Oftentimes we have clearly outlined how these issues should get resolved. #### :handshake: Got Stuck? We're here to help! It's a good idea to submit you're pull request (PR) right away. Just prefix the title with `[WIP]` (work in progress) so we know that you're not done yet. This way, you can get feedback early on or ask others for help. Your commits are also automatically tested by Travis CI. :robot: Pull requests marked with [**ideas-wanted**][pr-ideas-wanted] are stuck and we would like to hear your thought. If a pull request is marked with [**help wanted**][pr-help-wanted] we just don't have the time and resources to work on it right now. You're invited to continue working on it instead! [new-issue]: https://github.com/offirgolan/ember-light-table/issues/new [ideas-wanted]: https://github.com/offirgolan/ember-light-table/issues?q=is%3Aissue+is%3Aopen+label%3Aideas-wanted [help-wanted]: https://github.com/offirgolan/ember-light-table/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 [pr-ideas-wanted]: https://github.com/offirgolan/ember-light-table/pulls?q=is%3Apr+is%3Aopen+label%ideas-wanted [pr-help-wanted]: https://github.com/offirgolan/ember-light-table/pulls?q=is%3Apr+is%3Aopen+label%3Ahelp+wanted [demo]: https://adopted-ember-addons.github.io/ember-light-table/ [cookbook]: https://adopted-ember-addons.github.io/ember-light-table/#/cookbook [docs]: https://adopted-ember-addons.github.io/ember-light-table/docs/ ================================================ FILE: RELEASE.md ================================================ # Release Process Releases are mostly automated using [release-it](https://github.com/release-it/release-it/) and [lerna-changelog](https://github.com/lerna/lerna-changelog/). ## Preparation Since the majority of the actual release process is automated, the primary remaining task prior to releasing is confirming that all pull requests that have been merged since the last release have been labeled with the appropriate `lerna-changelog` labels and the titles have been updated to ensure they represent something that would make sense to our users. Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall guiding principle here is that changelogs are for humans, not machines. When reviewing merged PR's the labels to be used are: * breaking - Used when the PR is considered a breaking change. * enhancement - Used when the PR adds a new feature or enhancement. * bug - Used when the PR fixes a bug included in a previous release. * documentation - Used when the PR adds or updates documentation. * internal - Used for internal changes that still require a mention in the changelog/release notes. ## Release Once the prep work is completed, the actual release is straight forward: * First, ensure that you have installed your projects dependencies: ```sh yarn install ``` * Second, ensure that you have obtained a [GitHub personal access token][generate-token] with the `repo` scope (no other permissions are needed). Make sure the token is available as the `GITHUB_AUTH` environment variable. For instance: ```bash export GITHUB_AUTH=abc123def456 ``` [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable * And last (but not least 😁) do your release. ```sh npx release-it ``` [release-it](https://github.com/release-it/release-it/) manages the actual release process. It will prompt you to to choose the version number after which you will have the chance to hand tweak the changelog to be used (for the `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging, pushing the tag and commits, etc. ================================================ FILE: addon/-private/global-options.js ================================================ import config from 'ember-get-config'; const globalOptions = config['ember-light-table'] || {}; export default globalOptions; export function mergeOptionsWithGlobals(options) { return Object.assign({}, globalOptions, options); } ================================================ FILE: addon/.gitkeep ================================================ ================================================ FILE: addon/classes/Column.js ================================================ import { A as emberArray, makeArray } from '@ember/array'; import EmberObject, { computed } from '@ember/object'; import { notEmpty, or } from '@ember/object/computed'; import { isEmpty } from '@ember/utils'; import { guidFor } from '@ember/object/internals'; import classic from 'ember-classic-decorator'; /** * @module Table * @class Column */ @classic export default class Column extends EmberObject.extend({ /** * Whether the column can be hidden. * * CSS Classes: * - `is-hideable` * * @property hideable * @type {Boolean} * @default true */ hideable: true, /** * Whether the column can is hidden. * * CSS Classes: * - `is-hidden` * * @property hidden * @type {Boolean} * @default false */ hidden: false, /** * If true, this column has been hidden due to the responsive behavior * * @property responsiveHidden * @type {Boolean} * @default false */ responsiveHidden: false, /** * @property ascending * @type {Boolean} * @default true */ ascending: true, /** * Whether the column can be sorted. * * CSS Classes: * - `is-sortable` * * @property sortable * @type {Boolean} * @default true */ sortable: true, /** * Whether the column can be resized. * * CSS Classes: * - `is-resizable` * - `is-resizing` * * @property resizable * @type {Boolean} * @default false */ resizable: false, /** * Whether the column can be reorder via drag and drop. * * CSS Classes: * - `is-draggable` * - `is-dragging` * - `is-drag-target` * - `drag-left` * - `drag-right` * * @property draggable * @type {Boolean} * @default false */ draggable: false, /** * Whether the column is a valid drop target. * * @property droppable * @type {Boolean} * @default true */ droppable: true, /** * Whether the column is sorted. * * CSS Classes: * - `is-sorted` * * @property sorted * @type {Boolean} * @default false */ sorted: false, /** * Column header label * @property label * @type {String} * @default '' */ label: '', /** * Text alignment. Possible values are ['left', 'right', 'center'] * @property align * @type {String} * @default 'left' */ align: 'left', /** * The minimum width (in px) that this column can be resized to. * @property minResizeWidth * @type {Number} * @default 0 */ minResizeWidth: 0, /** * The parent column (or group) for this sub-column. * This will only have a value if this column is a sub-column. * Note: this doesn't update if you move this sub-column to another parent after instantiation. * * @property parent * @type Column * @optional */ parent: null, /** * An array of sub columns to be grouped together * @property subColumns * @type {Array} * @optional */ subColumns: null, /** * An array of media breakpoints that determine when this column will be shown * * If we have the following breakpoints defined in `app/breakpoints.js`: * * - mobile * - tablet * - desktop * * And we want to show this column only for tablet and desktop media, the following * array should be specified: `['tablet', 'desktop']`. * * If this property is `null`, `undefined`, or `[]`, then this column will always * be shown, regardless of the current media type. * * @property breakpoints * @type {Array} * @optional */ breakpoints: null, /** * Type of column component * * You can create your own column types by running the blueprint: * `ember g column-type my-column-type` * * This will generate a component for you which represents the `` * element for the column. If you want to apply custom actions to the `th`, * or do some custom styling of the `th` with classNameBindings, all of that is * available to you in this component. * * You can then specify the custom type you created as a string here, to use it. * * * @property type * @type {String} * @default 'base' */ type: 'base', /** * Type of cell component * * You can create your own cell types by running the blueprint: * `ember g cell-type my-cell-type` * * This will generate a component for you which represents the `` * cells in the column. If you want to apply custom actions to the `td`, * or do some custom styling of the `td` with classNameBindings, all of that is * available to you in this component. * * You can then specify the custom type you created as a string here, to use it. * * @property cellType * @type {String} * @default 'base' */ cellType: 'base', /** * Component name for the column * @property component * @type {String} * @optional */ component: null, /** * Component name for the column cells. This component is automatically passed row, * column, and value variables, and you can specify a valuePath to set what property * the value is set to. * @property cellComponent * @type {String} * @optional */ cellComponent: null, /** * @property valuePath * @type {String} */ valuePath: null, /** * @property width * @type {String} */ width: null, /** * Class names to be applied to header and footer cells of this column * * @property classNames * @type {String | Array} */ classNames: null, /** * Class names to be applied to all cells of this column * * @property cellClassNames * @type {String | Array} */ cellClassNames: null, /** * A format function used to calculate a cell's value. This method will be passed * the raw value if `valuePath` is specified. * * @property format * @type {Function} */ format: null, /** * Column's unique ID. * * @property columnId * @type {String} * @private */ columnId: computed(function () { return guidFor(this); }).readOnly(), /** * True if `hidden` or `responsiveHidden` is true. * @property isHidden * @type {Boolean} */ isHidden: or('hidden', 'responsiveHidden'), /** * @property isGroupColumn * @type {Boolean} * @private */ isGroupColumn: notEmpty('subColumns'), /** * @property isVisibleGroupColumn * @type {Boolean} * @private */ isVisibleGroupColumn: computed( 'visibleSubColumns.[]', 'isHidden', function () { return !isEmpty(this.visibleSubColumns) && !this.isHidden; } ).readOnly(), /** * @property visibleSubColumns * @type {Array} * @private */ visibleSubColumns: computed( 'subColumns.@each.isHidden', 'isHidden', function () { let subColumns = this.subColumns; let isHidden = this.isHidden; return emberArray(isHidden ? [] : subColumns.filterBy('isHidden', false)); } ).readOnly(), init(options = {}) { this.setProperties(options); const subColumns = emberArray( makeArray(this.subColumns).map((sc) => Column.create(sc)) ); subColumns.setEach('parent', this); this.set('subColumns', subColumns); }, }) {} ================================================ FILE: addon/classes/Row.js ================================================ import ObjectProxy from '@ember/object/proxy'; import { computed } from '@ember/object'; import { guidFor } from '@ember/object/internals'; import classic from 'ember-classic-decorator'; /** * @module Table * @extends Ember.ObjectProxy * @class Row */ @classic export default class Row extends ObjectProxy.extend({ /** * Whether the row is hidden. * * CSS Classes: * - `is-hidden` * * @property hidden * @type {Boolean} * @default false */ hidden: false, /** * Whether the row is expanded. * * CSS Classes: * - `is-expanded` * * @property expanded * @type {Boolean} * @default false */ expanded: false, /** * Whether the row is selected. * * CSS Classes: * - `is-selected` * * @property selected * @type {Boolean} * @default false */ selected: false, /** * Class names to be applied to this row * * @property classNames * @type {String | Array} */ classNames: null, /** * Data content for this row. Since this class extends Ember.ObjectProxy, * all properties are forwarded to the content. This means that instead of * `row.content.foo` you can just do `row.foo`. Please note that methods are * not forwarded. You will not be able to do `row.save()`, instead, you would have * to do `row.content.save()`. * * @property content * @type {Object} */ content: null, /** * Rows's unique ID. * * Note: named `rowId` in order to not shadow the `content.id` property. * * @property rowId * @type {String} * @readOnly */ rowId: computed(function () { return guidFor(this); }).readOnly(), }) {} ================================================ FILE: addon/classes/Table.js ================================================ import { A as emberArray, isArray } from '@ember/array'; import { assert } from '@ember/debug'; import EmberObject, { computed } from '@ember/object'; import { empty, filterBy } from '@ember/object/computed'; import Row from 'ember-light-table/classes/Row'; import Column from 'ember-light-table/classes/Column'; import { mergeOptionsWithGlobals } from 'ember-light-table/-private/global-options'; import { isNone } from '@ember/utils'; import classic from 'ember-classic-decorator'; /** * @module Table * @private */ /** * @module Table * @class Table */ @classic export default class Table extends EmberObject.extend({ /** * @property columns * @type {Ember.Array} * @default [] */ columns: null, /** * @property rows * @type {Ember.Array} * @default [] */ rows: null, /** * @property isEmpty * @type {Boolean} */ isEmpty: empty('rows').readOnly(), /** * @property expandedRows * @type {Ember.Array} */ expandedRows: filterBy('rows', 'expanded', true).readOnly(), /** * @property selectedRows * @type {Ember.Array} */ selectedRows: filterBy('rows', 'selected', true).readOnly(), /** * @property visibleRows * @type {Ember.Array} */ visibleRows: filterBy('rows', 'hidden', false).readOnly(), /** * @property sortableColumns * @type {Ember.Array} */ sortableColumns: filterBy('visibleColumns', 'sortable', true).readOnly(), /** * @property sortedColumns * @type {Ember.Array} */ sortedColumns: filterBy('visibleColumns', 'sorted', true).readOnly(), /** * @property hideableColumns * @type {Ember.Array} */ hideableColumns: filterBy('allColumns', 'hideable', true).readOnly(), /** * @property hiddenColumns * @type {Ember.Array} */ hiddenColumns: filterBy('allColumns', 'hidden', true).readOnly(), /** * @property responsiveHiddenColumns * @type {Ember.Array} */ responsiveHiddenColumns: filterBy( 'allColumns', 'responsiveHidden', true ).readOnly(), /** * @property visibleColumns * @type {Ember.Array} */ visibleColumns: filterBy('allColumns', 'isHidden', false).readOnly(), /** * @property visibleColumnGroups * @type {Ember.Array} */ visibleColumnGroups: computed( 'columns.[]', 'columns.@each.{isHidden,isVisibleGroupColumn}', function () { return this.columns.reduce((arr, c) => { if ( c.get('isVisibleGroupColumn') || (!c.get('isGroupColumn') && !c.get('isHidden')) ) { arr.pushObject(c); } return arr; }, emberArray([])); } ).readOnly(), /** * @property visibleSubColumns * @type {Ember.Array} */ visibleSubColumns: computed('columns.@each.visibleSubColumns', function () { return emberArray([].concat(...this.columns.getEach('visibleSubColumns'))); }).readOnly(), /** * @property allColumns * @type {Ember.Array} */ allColumns: computed('columns.@each.subColumns', function () { return this.columns.reduce((arr, c) => { arr.pushObjects(c.get('isGroupColumn') ? c.get('subColumns') : [c]); return arr; }, emberArray([])); }).readOnly(), /** * @class Table * @constructor * @param {Object} options * @param {Array} options.columns * @param {Array} options.rows * @param {Object} options.rowOptions Options hash passed through to * `createRow(content, options)`. */ init(options = {}) { let { columns = [], rows = [] } = options; assert( '[ember-light-table] columns must be an array if defined', isArray(columns) ); assert( '[ember-light-table] rows must be an array if defined', isArray(rows) ); this.setProperties(mergeOptionsWithGlobals(options)); this.set('columns', emberArray(Table.createColumns(columns))); let _rows = emberArray(Table.createRows(rows, this.rowOptions)); this.set('rows', _rows); }, }) { // Rows /** * Replace all the row's content with content of the argument. If argument is an empty array rows will be cleared. * @method setRows * @param {Array} rows * @param {Object} options * @return {Array} rows */ setRows(rows = [], options = {}) { return this.rows.setObjects(Table.createRows(rows, options)); } /** * Push the object onto the end of the row array if it is not already present. * @method addRow * @param {Object} row * @param {Object} options */ addRow(row, options = {}) { if (row instanceof Row) { this.rows.addObject(row); } else if (isNone(this.rows.findBy('content', row))) { this.pushRow(row, options); } } /** * Push the objects onto the end of the row array if it is not already present. * @method addRows * @param {Array} rows * @param {Object} options */ addRows(rows = [], options = {}) { rows.forEach((r) => this.addRow(r, options)); } /** * Push the object onto the end of the row array. * @method pushRow * @param {Object} row * @param {Object} options * @return {Row} pushed row */ pushRow(row, options = {}) { let _row = Table.createRow(row, options); this.rows.pushObject(_row); return _row; } /** * Push the object onto the end of the row array. * @method pushRows * @param {Array} rows * @param {Object} options * @return {Array} pushed rows */ pushRows(rows = [], options = {}) { let _rows = Table.createRows(rows, options); this.rows.pushObjects(_rows); return _rows; } /** * Insert a row at the specified index. * @method insertRowAt * @param {Number} index * @param {Object} row * @param {Object} options * @return {Row} inserted row */ insertRowAt(index, row, options = {}) { let _row = Table.createRow(row, options); this.rows.insertAt(index, _row); return _row; } /** * Remove all occurrences of an object in the rows * @method removeRow * @param {Object} row */ removeRow(row) { if (row instanceof Row) { this.rows.removeObject(row); } else { this.rows.removeObjects(this.rows.filterBy('content', row)); } } /** * Removes each object in the passed enumerable from the rows. * @method removeRows * @param {Array} rows */ removeRows(rows = []) { rows.forEach((r) => this.removeRow(r)); } /** * Remove a row at the specified index * @method removeRowAt * @param {Number} index */ removeRowAt(index) { this.rows.removeAt(index); } // Columns /** * Replace all the column's content with content of the argument. If argument is an empty array columns will be cleared. * @method setColumns * @param {Array} columns * @return {Array} columns */ setColumns(columns = []) { return this.columns.setObjects(Table.createColumns(columns)); } /** * Push the object onto the end of the column array if it is not already present. * @method addColumn * @param {Object} column */ addColumn(column) { this.columns.addObject(Table.createColumn(column)); } /** * Push the objects onto the end of the column array if it is not already present. * @method addColumns * @param {Array} columns */ addColumns(columns = []) { this.columns.addObjects(Table.createColumns(columns)); } /** * Push the object onto the end of the column array. * @method pushColumn * @param {Object} column * @return {Column} pushed column */ pushColumn(column) { let _column = Table.createColumn(column); this.columns.pushObject(_column); return _column; } /** * Push the object onto the end of the column array. * @method pushColumns * @param {Array} columns * @return {Array} pushed columns */ pushColumns(columns = []) { let _columns = Table.createColumns(columns); this.columns.pushObjects(_columns); return _columns; } /** * Insert a column at the specified index. * @method insertColumnAt * @param {Number} index * @param {Object} column * @return {Column} inserted column */ insertColumnAt(index, column) { let _column = Table.createColumn(column); this.columns.insertAt(index, _column); return _column; } /** * Remove all occurrences of an object in the columns * @method removeColumn * @param {Object} column */ removeColumn(column) { return this.columns.removeObject(column); } /** * Removes each object in the passed enumerable from the columns. * @method removeColumns * @param {Array} columns */ removeColumns(columns = []) { return this.columns.removeObjects(columns); } /** * Remove a column at the specified index * @method removeColumnAt * @param {Number} index */ removeColumnAt(index) { this.columns.removeAt(index); } /** * Create a Row object with the given content * @method createRow * @static * @param {Object} content * @param {Object} options * @return {Row} */ static createRow(content, options = {}) { if (content instanceof Row) { return content; } else { return Row.create(Object.assign({}, options, { content })); } } /** * Create a collection of Row objects with the given collection * @method createRows * @static * @param {Array} rows * @param {Object} options * @return {Array} */ static createRows(rows = [], options = {}) { return rows.map((r) => Table.createRow(r, options)); } /** * Create a Column object with the given options * @method createColumn * @static * @param {Object} column * @return {Column} */ static createColumn(column) { if (column instanceof Column) { return column; } else { return Column.create(column); } } /** * Create a collection of Column objects with the given collection * @method createColumns * @static * @param {Array} columns * @return {Array} */ static createColumns(columns = []) { return columns.map((c) => Table.createColumn(c)); } } ================================================ FILE: addon/components/cells/base.hbs ================================================ {{#if this.column.cellComponent}} {{component (ensure-safe-component this.column.cellComponent) tableActions=this.tableActions extra=this.extra table=this.table column=this.column row=this.row value=this.value rawValue=this.rawValue }} {{else}} {{this.value}} {{/if}} ================================================ FILE: addon/components/cells/base.js ================================================ import Component from '@ember/component'; import { computed } from '@ember/object'; import { readOnly } from '@ember/object/computed'; import { htmlSafe } from '@ember/template'; /** * @module Light Table * @submodule Cell Types */ /** * @module Cell Types * @class Base Cell */ export default Component.extend({ tagName: 'td', classNames: ['lt-cell'], attributeBindings: ['style'], classNameBindings: ['align', 'isSorted', 'column.cellClassNames'], enableScaffolding: false, isSorted: readOnly('column.sorted'), style: computed('enableScaffolding', 'column.width', function () { let column = this.column; let columnWidth = column.get('width'); if (this.enableScaffolding || !column) { return; } // For performance reasons, it's more interesting to bypass cssStyleify // since it leads to a lot of garbage collections // when displaying many cells return columnWidth ? htmlSafe(`width: ${columnWidth};`) : null; }), align: computed('column.align', function () { return `align-${this.column.align}`; }), /** * @property table * @type {Table} */ table: null, /** * @property column * @type {Column} */ column: null, /** * @property row * @type {Row} */ row: null, /** * @property tableActions * @type {Object} */ tableActions: null, /** * @property extra * @type {Object} */ extra: null, /** * @property rawValue * @type {Mixed} */ rawValue: null, /** * @property value * @type {Mixed} */ value: computed('column.format', 'rawValue', function () { let rawValue = this.rawValue; let format = this.column.format; if (format && typeof format === 'function') { return format.call(this, rawValue); } return rawValue; }), }); ================================================ FILE: addon/components/columns/base.hbs ================================================ {{#if this.column.component}} {{component (ensure-safe-component this.column.component) column=this.column table=this.table tableActions=this.tableActions extra=this.extra sortIcons=this.sortIcons }} {{else}} {{#if (and this.sortIcons.iconComponent this.sortIconProperty)}} {{component (ensure-safe-component this.sortIcons.iconComponent) sortIcons=this.sortIcons sortIconProperty=this.sortIconProperty }} {{else if this.sortIconProperty}} {{/if}} {{this.label}} {{/if}} {{#if this.isResizable}} {{/if}} ================================================ FILE: addon/components/columns/base.js ================================================ import { set } from '@ember/object'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { oneWay, readOnly } from '@ember/object/computed'; import { isEmpty } from '@ember/utils'; import DraggableColumnMixin from 'ember-light-table/mixins/draggable-column'; import cssStyleify from 'ember-light-table/utils/css-styleify'; /** * @module Light Table * @submodule Column Types */ /** * @module Column Types * @class Base Column */ export default Component.extend(DraggableColumnMixin, { tagName: 'th', classNames: ['lt-column'], attributeBindings: ['style', 'colspan', 'rowspan'], classNameBindings: [ 'align', 'isGroupColumn:lt-group-column', 'isHideable', 'isSortable', 'isSorted', 'isResizable', 'isResizing', 'isDraggable', 'column.classNames', ], isGroupColumn: readOnly('column.isGroupColumn'), isSortable: readOnly('column.sortable'), isSorted: readOnly('column.sorted'), isHideable: readOnly('column.hideable'), isResizable: readOnly('column.resizable'), isDraggable: readOnly('column.draggable'), isResizing: false, style: computed('column.width', function () { return cssStyleify(this.column.getProperties(['width'])); }), align: computed('column.align', function () { return `align-${this.column.align}`; }), /** * @property label * @type {String} */ label: oneWay('column.label'), /** * @property table * @type {Table} */ table: null, /** * @property column * @type {Column} */ column: null, /** * @property tableActions * @type {Object} */ tableActions: null, /** * @property extra * @type {Object} */ extra: null, /** * @property sortIcons * @type {Object} */ sortIcons: null, /** * @property sortIconProperty * @type {String|null} * @private */ sortIconProperty: computed('column.{sortable,sorted,ascending}', function () { let sorted = this.column.sorted; if (sorted) { let ascending = this.column.ascending; return ascending ? 'iconAscending' : 'iconDescending'; } let sortable = this.column.sortable; return sortable ? 'iconSortable' : null; }), /** * @property colspan * @type {Number} */ colspan: computed('column', 'column.visibleSubColumns.[]', function () { let subColumns = this.column.visibleSubColumns; return !isEmpty(subColumns) ? subColumns.length : 1; }), /** * @property rowspan * @type {Number} */ rowspan: computed('_rowspan', 'column.visibleSubColumns.[]', { get() { if (this._rowspan) { return this._rowspan; } let subColumns = this.column.visibleSubColumns; return !isEmpty(subColumns) ? 1 : 2; }, set(key, value) { return set(this, '_rowspan', value); }, }), }); ================================================ FILE: addon/components/light-table.hbs ================================================
{{yield (hash head=(component "lt-head" tableId=(or @id this.tableId) table=this.table tableActions=this.tableActions extra=this.extra tableClassNames=this.tableClassNames sharedOptions=this.sharedOptions ) body=(component "lt-body" tableId=(or @id this.tableId) table=this.table tableActions=this.tableActions extra=this.extra tableClassNames=this.tableClassNames sharedOptions=this.sharedOptions ) foot=(component "lt-foot" tableId=(or @id this.tableId) table=this.table tableActions=this.tableActions extra=this.extra tableClassNames=this.tableClassNames sharedOptions=this.sharedOptions ) ) }}
================================================ FILE: addon/components/light-table.js ================================================ import { A as emberArray } from '@ember/array'; import Component from '@ember/component'; import { computed, observer } from '@ember/object'; import { readOnly } from '@ember/object/computed'; import { guidFor } from '@ember/object/internals'; import { isEmpty, isNone } from '@ember/utils'; import { assert } from '@ember/debug'; import { inject as service } from '@ember/service'; import Table from 'ember-light-table/classes/Table'; import cssStyleify from 'ember-light-table/utils/css-styleify'; function intersections(array1, array2) { return array1.filter((n) => { return array2.indexOf(n) > -1; }); } /** * @module Light Table * @main light-table */ /** * ```hbs * * * * * * ``` * * Please see the documentation for the [Head](../classes/t.head.html), [Body](../classes/t.body.html), and [Foot](../classes/t.foot.html) components * for more details on all possible options and actions. * * @class light-table * @main Components */ export default Component.extend({ tagName: '', media: service(), scrollbarThickness: service(), /** * @property table * @type {Table} */ table: null, /** * This is used to propagate custom user defined actions to custom cell and header components. * As an example, lets say I have a table with a column defined with `cellComponent: 'delete-user'` * * ```hbs * * * * * * ``` * * Now in the `delete-user` component, we can access that `deleteUser` action and pass it the * row object which will bubble all the way to where you defined that action. * * ```hbs * * * ``` * * * @property tableActions * @type {Object} */ tableActions: null, /** * Object to store any arbitrary configuration meant to be used by custom * components. * * ```hbs * * * * * ``` * * Now in all custom components, you can access this value like so: * * ```hbs * {{value}} * ``` * * @property extra * @type {Object} */ extra: null, /** * Table height. * * @property height * @type {String} * @default null */ height: null, /** * Class names that will be added to all tags * * @property tableClassNames * @type {String} * @default '' */ tableClassNames: '', /** * Enable responsive behavior * * @property responsive * @type {Boolean} * @default false */ responsive: false, /** * A hash to determine the number of columns to show per given breakpoint. * If this is specified, it will override any column specific breakpoints. * * If we have the following breakpoints defined in `app/breakpoints.js`: * * - mobile * - tablet * - desktop * * The following hash can be passed in: * * ```js * { * mobile: 2, * tablet: 4 * } * ``` * * If there is no rule specified for a given breakpoint (i.e. `desktop`), * all columns will be shown. * * @property breakpoints * @type {Object} * @default null */ breakpoints: null, /** * Toggles occlusion rendering functionality. Currently experimental. * If set to true, you must set {{#crossLink 't.body/estimatedRowHeight:property'}}{{/crossLink}} to * something other than the default value. * * @property occlusion * @type Boolean * @default False */ occlusion: false, /** * Estimated size of a row. Used in `vertical-collection` to determine roughly the number * of rows exist out of the viewport. * * @property estimatedRowHeight * @type Number * @default false */ estimatedRowHeight: 0, /** * Whether `vertical-collection` should recycle table rows. This speeds up performance with occlusion * rendering but may cause problems if any components expect to reset their state to the initial state * with every rerender of the list. * * @property shouldRecycle * @type Boolean * @default true */ shouldRecycle: true, /** * Table component shared options * * @property sharedOptions * @type {Object} * @private */ sharedOptions: computed( 'estimatedRowHeight', 'height', 'occlusion', 'shouldRecycle', function () { return { height: this.height, fixedHeader: false, fixedFooter: false, occlusion: this.occlusion, estimatedRowHeight: this.estimatedRowHeight, shouldRecycle: this.shouldRecycle, }; } ).readOnly(), visibleColumns: readOnly('table.visibleColumns'), /** * Calculates the total width of the visible columns via their `width` * propert. * * Returns 0 for the following conditions * - All widths are not set * - Widths are not the same unit * - Unit cannot be determined * * @property totalWidth * @type {Number} * @private */ totalWidth: computed('visibleColumns.@each.width', function () { let visibleColumns = this.visibleColumns; let widths = visibleColumns.getEach('width'); let unit = (widths[0] || '').match(/\D+$/); let totalWidth = 0; if (isEmpty(unit)) { return 0; } unit = unit[0]; /* 1. Check if all widths are present 2. Check if all widths are the same unit */ for (let i = 0; i < widths.length; i++) { let width = widths[i]; if (isNone(width) || width.indexOf(unit) === -1) { return 0; } totalWidth += parseInt(width, 10); } return `${totalWidth}${unit}`; }), style: computed( 'height', 'occlusion', 'scrollbarThickness.thickness', 'totalWidth', function () { let totalWidth = this.totalWidth; let style = { height: this.height }; if (totalWidth) { if (this.occlusion) { const scrollbarThickness = this.scrollbarThickness.thickness; style.width = `calc(${totalWidth} + ${scrollbarThickness}px)`; } else { style.width = totalWidth; } style.overflowX = 'auto'; } return cssStyleify(style); } ), init() { this._super(...arguments); let table = this.table; assert( '[ember-light-table] table must be an instance of Table', table instanceof Table ); this.set('tableId', guidFor(this)); if (isNone(this.media)) { this.set('responsive', false); } else { this.media.on('mediaChanged', () => this.onMediaChange()); } this.onMediaChange(); }, onMediaChange: observer('table.allColumns.[]', function () { let responsive = this.responsive; let matches = this.media.matches; let breakpoints = this.breakpoints; let table = this.table; let numColumns = 0; if (!responsive) { return; } this.send('onBeforeResponsiveChange', matches); if (!isNone(breakpoints)) { Object.keys(breakpoints).forEach((b) => { if (matches.indexOf(b) > -1) { numColumns = Math.max(numColumns, breakpoints[b]); } }); this._displayColumns(numColumns); } else { table.get('allColumns').forEach((c) => { let breakpoints = c.get('breakpoints'); let isMatch = isEmpty(breakpoints) || intersections(matches, breakpoints).length > 0; c.set('responsiveHidden', !isMatch); }); } this.send('onAfterResponsiveChange', matches); }), _displayColumns(numColumns) { let table = this.table; let hiddenColumns = table.get('responsiveHiddenColumns'); let visibleColumns = table.get('visibleColumns'); if (!numColumns) { hiddenColumns.setEach('responsiveHidden', false); } else if (visibleColumns.length > numColumns) { emberArray( visibleColumns.slice(numColumns, visibleColumns.length) ).setEach('responsiveHidden', true); } else if (visibleColumns.length < numColumns) { emberArray( hiddenColumns.slice(0, numColumns - visibleColumns.length) ).setEach('responsiveHidden', false); } }, // No-ops for closure actions onBeforeResponsiveChange() {}, onAfterResponsiveChange() {}, actions: { /** * onBeforeResponsiveChange action. * Called before any column visibility is altered. * * @event onBeforeResponsiveChange * @param {Array} matches list of matching breakpoints */ onBeforeResponsiveChange(/* matches */) { this.onBeforeResponsiveChange(...arguments); }, /** * onAfterResponsiveChange action. * Called after all column visibility has been altered. * * @event onAfterResponsiveChange * @param {Array} matches list of matching breakpoints */ onAfterResponsiveChange(/* matches */) { this.onAfterResponsiveChange(...arguments); }, }, }); ================================================ FILE: addon/components/lt-body.hbs ================================================
{{#let (hash row=(or this.rowComponent (component "lt-row")) spanned-row=(or this.spannedRowComponent (component "lt-spanned-row")) infinity=(or this.infinityComponent (component "lt-infinity")) ) as |lt| }} {{#if this.sharedOptions.occlusion}}
{{#if this.overwrite}} {{yield this.columns this.rows}} {{else}} {{#vertical-collection items=this.rows tagName="vertical-collection" estimateHeight=this.sharedOptions.estimatedRowHeight shouldRecycle=this.sharedOptions.shouldRecycle staticHeight=true bufferSize=this.scrollBufferRows containerSelector=".lt-scrollable" firstVisibleChanged=(action "firstVisibleChanged") lastVisibleChanged=(action "lastVisibleChanged") firstReached=(action "firstReached") lastReached=(action "lastReached") as |row| }} {{lt.row row=row columns=this.columns data-row-id=row.rowId table=this.table tableActions=this.tableActions extra=this.extra enableScaffolding=this.enableScaffolding canExpand=this.canExpand canSelect=this.canSelect click=(action "onRowClick" row) doubleClick=(action "onRowDoubleClick" row) }} {{/vertical-collection}} {{yield (hash loader=(component (ensure-safe-component lt.spanned-row) classes="lt-is-loading" ) no-data=(component (ensure-safe-component lt.spanned-row) classes="lt-no-data" ) expanded-row=(component (ensure-safe-component lt.spanned-row) visible=false ) ) this.rows }} {{/if}}
{{else}}
{{#if this.enableScaffolding}} {{#each this.columns as |column|}} {{! template-lint-disable no-inline-styles }} {{! template-lint-enable no-inline-styles }} {{/each}} {{/if}} {{#if this.overwrite}} {{yield this.columns this.rows}} {{else}} {{#each (if scrollbar (compute scrollbar.update this.rows) this.rows ) as |row| }} {{lt.row row=row columns=this.columns data-row-id=row.rowId table=this.table tableActions=this.tableActions extra=this.extra enableScaffolding=this.enableScaffolding canExpand=this.canExpand canSelect=this.canSelect click=(action "onRowClick" row) doubleClick=(action "onRowDoubleClick" row) }} {{yield (hash expanded-row=(component lt.spanned-row classes="lt-expanded-row" colspan=this.colspan yield=row visible=row.expanded ) loader=(component (ensure-safe-component lt.spanned-row) visible=false ) no-data=(component (ensure-safe-component lt.spanned-row) visible=false ) ) this.rows }} {{/each}} {{yield (hash loader=(component (ensure-safe-component lt.spanned-row) classes="lt-is-loading" colspan=this.colspan ) no-data=(component (ensure-safe-component lt.spanned-row) classes="lt-no-data" colspan=this.colspan ) expanded-row=(component (ensure-safe-component lt.spanned-row) visible=false ) ) this.rows }} {{/if}}
{{#if @onScrolledToBottom}} {{/if}}
{{/if}} {{/let}} ================================================ FILE: addon/components/lt-body.js ================================================ import Component from '@ember/component'; import { action, computed } from '@ember/object'; import { readOnly } from '@ember/object/computed'; import { cancel, debounce, once, schedule, scheduleOnce } from '@ember/runloop'; import Row from 'ember-light-table/classes/Row'; /** * @module Light Table */ /** * ```hbs * * * * Hello {{row.firstName}} * * * {{#if this.isLoading}} * {{#body.loader}} * Loading... * {{/body.loader}} * {{/if}} * * {{#if this.table.isEmpty}} * {{#body.no-data}} * No users found. * {{/body.no-data}} * {{/if}} * * * ``` * * @class t.body */ export default Component.extend({ tagName: '', /** * @property table * @type {Table} * @private */ table: null, /** * @property sharedOptions * @type {Object} * @private */ sharedOptions: null, /** * @property tableActions * @type {Object} */ tableActions: null, /** * @property extra * @type {Object} */ extra: null, /** * @property isInViewport * @default false * @type {Boolean} */ isInViewport: false, /** * Allows a user to select a row on click. All this will do is apply the necessary * CSS classes and add the row to `table.selectedRows`. If `multiSelect` is disabled * only one row will be selected at a time. * * @property canSelect * @type {Boolean} * @default true */ canSelect: true, /** * Select a row on click. If this is set to `false` and multiSelect is * enabled, using click + `shift`, `cmd`, or `ctrl` will still work as * intended, while clicking on the row will not set the row as selected. * * @property selectOnClick * @type {Boolean} * @default true */ selectOnClick: true, /** * Allows for expanding row. This will create a new row under the row that was * clicked with the template provided by `body.expanded-row`. * * ```hbs * * This is the content of the expanded row for {{row.firstName}} * * ``` * * @property canExpand * @type {Boolean} * @default false */ canExpand: false, /** * Allows a user to select multiple rows with the `ctrl`, `cmd`, and `shift` keys. * These rows can be easily accessed via `table.get('selectedRows')` * * @property multiSelect * @type {Boolean} * @default false */ multiSelect: false, /** * When multiSelect is true, this property determines whether or not `ctrl` * (or `cmd`) is required to select additional rows, one by one. When false, * simply clicking on subsequent rows will select or deselect them. * * `shift` to select many consecutive rows is unaffected by this property. * * @property multiSelectRequiresKeyboard * @type {Boolean} * @default true */ multiSelectRequiresKeyboard: true, /** * Hide scrollbar when not scrolling * * @property autoHideScrollbar * @type {Boolean} * @default true */ autoHideScrollbar: true, /** * Allows multiple rows to be expanded at once * * @property multiRowExpansion * @type {Boolean} * @default true */ multiRowExpansion: true, /** * Expand a row on click * * @property expandOnClick * @type {Boolean} * @default true */ expandOnClick: true, /** * If true, the body block will yield columns and rows, allowing you * to define your own table body * * @property overwrite * @type {Boolean} * @default false */ overwrite: false, /** * If true, the body will prepend an invisible `` that scaffolds the * widths of the table cells. * * ember-light-table uses [`table-layout: fixed`](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout). * This means, that the widths of the columns are defined by the first row * only. By prepending this scaffolding row, widths of columns only need to * be specified once. * * @property enableScaffolding * @type {Boolean} * @default false */ enableScaffolding: false, /** * ID of main table component. Used to generate divs for ember-wormhole and set scope for scroll observers * * @property tableId * @type {String} * @private */ tableId: null, /** * @property scrollBuffer * @type {Number} * @default 500 */ scrollBuffer: 500, /** * @property scrollBufferRows * @type {Number} * @default 500 / estimatedRowHeight */ scrollBufferRows: computed( 'scrollBuffer', 'sharedOptions.estimatedRowHeight', function () { return Math.ceil( this.scrollBuffer / (this.sharedOptions.estimatedRowHeight || 1) ); } ), /** * @property useVirtualScrollbar * @type {Boolean} * @default false * @private */ useVirtualScrollbar: false, /** * Set this property to scroll to a specific px offset. * * This only works when `useVirtualScrollbar` is `true`, i.e. when you are * using fixed headers / footers. * * @property scrollTo * @type {Number} * @default null */ scrollTo: null, _scrollTo: null, /** * Set this property to a `Row` to scroll that `Row` into view. * * This only works when `useVirtualScrollbar` is `true`, i.e. when you are * using fixed headers / footers. * * @property scrollToRow * @type {Row} * @default null */ scrollToRow: null, _scrollToRow: null, /** * @property targetScrollOffset * @type {Number} * @default 0 * @private */ targetScrollOffset: 0, /** * @property currentScrollOffset * @type {Number} * @default 0 * @private */ currentScrollOffset: 0, /** * @property hasReachedTargetScrollOffset * @type {Boolean} * @default true * @private */ hasReachedTargetScrollOffset: true, /** * Allows to customize the component used to render rows * * ```hbs * * * ``` * @property rowComponent * @type {Ember.Component} * @default null */ rowComponent: null, /** * Allows to customize the component used to render spanned rows * * ```hbs * * * * ``` * @property spannedRowComponent * @type {Ember.Component} * @default null */ spannedRowComponent: null, /** * Allows to customize the component used to render infinite loader * * ```hbs * * * * ``` * @property infinityComponent * @type {Ember.Component} * @default null */ infinityComponent: null, rows: readOnly('table.visibleRows'), columns: readOnly('table.visibleColumns'), colspan: readOnly('columns.length'), /** * fills the screen with row items until lt-infinity component has exited the viewport * @property scheduleScrolledToBottom */ scheduleScrolledToBottom: action(function () { if (this.isInViewport && this.onScrolledToBottom) { /* Continue scheduling onScrolledToBottom until no longer in viewport */ this._schedulerTimer = scheduleOnce( 'afterRender', this, this._debounceScrolledToBottom ); } }), _prevSelectedIndex: -1, init() { this._super(...arguments); /* We can only set `useVirtualScrollbar` once all contextual components have been initialized since fixedHeader and fixedFooter are set on t.head and t.foot initialization. */ once(this, this._setupVirtualScrollbar); }, destroy() { this._super(...arguments); this._cancelTimers(); }, _setupVirtualScrollbar() { let { fixedHeader, fixedFooter } = this.sharedOptions; this.set('useVirtualScrollbar', fixedHeader || fixedFooter); }, onRowsChange: action(function () { this._checkTargetOffsetTimer = scheduleOnce( 'afterRender', this, this.checkTargetScrollOffset ); }), setupScrollOffset: action(function (element) { let { scrollTo, _scrollTo, scrollToRow, _scrollToRow } = this; let targetScrollOffset = null; this.setProperties({ _scrollTo: scrollTo, _scrollToRow: scrollToRow }); if (scrollTo !== _scrollTo) { targetScrollOffset = Number.parseInt(scrollTo, 10); if (Number.isNaN(targetScrollOffset)) { targetScrollOffset = null; } this.setProperties({ targetScrollOffset, hasReachedTargetScrollOffset: targetScrollOffset <= 0, }); } else if (scrollToRow !== _scrollToRow) { if (scrollToRow instanceof Row) { let rowElement = element.querySelector( `[data-row-id=${scrollToRow.get('rowId')}]` ); if (rowElement instanceof Element) { targetScrollOffset = rowElement.offsetTop; } } this.setProperties({ targetScrollOffset, hasReachedTargetScrollOffset: true, }); } }), checkTargetScrollOffset() { if (!this.hasReachedTargetScrollOffset) { let targetScrollOffset = this.targetScrollOffset; let currentScrollOffset = this.currentScrollOffset; if (targetScrollOffset > currentScrollOffset) { this.set('targetScrollOffset', null); this._setTargetOffsetTimer = schedule('render', null, () => { this.set('targetScrollOffset', targetScrollOffset); }); } else { this.set('hasReachedTargetScrollOffset', true); } } }, toggleExpandedRow(row) { let multiRowExpansion = this.multiRowExpansion; let shouldExpand = !row.expanded; if (multiRowExpansion) { row.toggleProperty('expanded'); } else { this.table.expandedRows.setEach('expanded', false); row.set('expanded', shouldExpand); } }, /** * @method _debounceScrolledToBottom */ _debounceScrolledToBottom(delay = 100) { /* This debounce is needed when there is not enough delay between onScrolledToBottom calls. Without this debounce, all rows will be rendered causing immense performance problems */ this._debounceTimer = debounce(this, this.onScrolledToBottom, delay); }, /** * @method _cancelTimers */ _cancelTimers() { cancel(this._checkTargetOffsetTimer); cancel(this._setTargetOffsetTimer); cancel(this._schedulerTimer); cancel(this._debounceTimer); }, // Noop for closure actions onRowClick() {}, onRowDoubleClick() {}, onScroll() {}, firstVisibleChanged() {}, lastVisibleChanged() {}, firstReached() {}, lastReached() {}, /** * lt-infinity action to determine if component is still in viewport * @event enterViewport */ enterViewport: action(function () { this.set('isInViewport', true); }), /** * lt-infinity action to determine if component has exited the viewport * @event exitViewport */ exitViewport: action(function () { this.set('isInViewport', false); }), actions: { /** * onRowClick action. Handles selection, and row expansion. * @event onRowClick * @param {Row} row The row that was clicked * @param {Event} event The click event */ onRowClick(row, e) { let rows = this.table.rows; let multiSelect = this.multiSelect; let multiSelectRequiresKeyboard = this.multiSelectRequiresKeyboard; let canSelect = this.canSelect; let selectOnClick = this.selectOnClick; let canExpand = this.canExpand; let expandOnClick = this.expandOnClick; let isSelected = row.get('selected'); let currIndex = rows.indexOf(row); let prevIndex = this._prevSelectedIndex === -1 ? currIndex : this._prevSelectedIndex; this._prevSelectedIndex = currIndex; let toggleExpandedRow = () => { if (canExpand && expandOnClick) { this.toggleExpandedRow(row); } }; if (canSelect) { if (e.shiftKey && multiSelect) { rows .slice( Math.min(currIndex, prevIndex), Math.max(currIndex, prevIndex) + 1 ) .forEach((r) => r.set('selected', !isSelected)); } else if ( (!multiSelectRequiresKeyboard || e.ctrlKey || e.metaKey) && multiSelect ) { row.toggleProperty('selected'); } else { if (selectOnClick) { this.table.selectedRows.setEach('selected', false); row.set('selected', !isSelected); } toggleExpandedRow(); } } else { toggleExpandedRow(); } this.onRowClick(...arguments); }, /** * onRowDoubleClick action. * @event onRowDoubleClick * @param {Row} row The row that was clicked * @param {Event} event The click event */ onRowDoubleClick(/* row */) { this.onRowDoubleClick(...arguments); }, /** * onScroll action - sent when user scrolls in the Y direction * * This only works when `useVirtualScrollbar` is `true`, i.e. when you are * using fixed headers / footers. * * @event onScroll * @param {Number} scrollOffset The scroll offset in px * @param {Event} event The scroll event */ onScroll(scrollOffset /* , event */) { this.set('currentScrollOffset', scrollOffset); this.onScroll(...arguments); }, firstVisibleChanged(item, index /* , key */) { this.firstVisibleChanged(...arguments); const estimateScrollOffset = index * this.sharedOptions.estimatedRowHeight; this.onScroll(estimateScrollOffset, null); }, lastVisibleChanged(/* item, index, key */) { this.lastVisibleChanged(...arguments); }, firstReached(/* item, index, key */) { this.firstReached(...arguments); }, lastReached(/* item, index, key */) { this.lastReached(...arguments); this.onScrolledToBottom?.(); }, }, }); ================================================ FILE: addon/components/lt-column-resizer.hbs ================================================ {{yield}} ================================================ FILE: addon/components/lt-column-resizer.js ================================================ import Component from '@ember/component'; import closest from 'ember-light-table/utils/closest'; const TOP_LEVEL_CLASS = '.ember-light-table'; export default Component.extend({ classNameBindings: [':lt-column-resizer', 'isResizing'], column: null, resizeOnDrag: false, isResizing: false, startWidth: null, startX: null, colElement() { return this.element.parentNode; }, didInsertElement() { this._super(...arguments); this.__mouseMove = this._mouseMove.bind(this); this.__mouseUp = this._mouseUp.bind(this); document.addEventListener('mousemove', this.__mouseMove); document.addEventListener('mouseup', this.__mouseUp); }, willDestroyElement() { this._super(...arguments); document.removeEventListener('mousemove', this.__mouseMove); document.removeEventListener('mouseip', this.__mouseUp); }, click(e) { /* Prevent click events from propagating (i.e. onColumnClick) */ e.preventDefault(); e.stopPropagation(); }, mouseDown(e) { let column = this.colElement(); e.preventDefault(); e.stopPropagation(); this.setProperties({ isResizing: true, startWidth: column.offsetWidth, startX: e.pageX, }); let topLevel = closest(this.element, TOP_LEVEL_CLASS); topLevel.classList.add('is-resizing'); }, _mouseUp(e) { if (this.isResizing) { e.preventDefault(); e.stopPropagation(); let column = this.colElement(); let width = `${column.offsetWidth}px`; this.set('isResizing', false); this.set('column.width', width); let topLevel = closest(this.element, TOP_LEVEL_CLASS); topLevel.classList.remove('is-resizing'); this.onColumnResized(width); } }, _mouseMove(e) { if (this.isResizing) { e.preventDefault(); e.stopPropagation(); let resizeOnDrag = this.resizeOnDrag; let minResizeWidth = this.column.minResizeWidth; let { startX, startWidth } = this; let width = `${Math.max( startWidth + (e.pageX - startX), minResizeWidth )}px`; let column = this.colElement(); let index = this.table.visibleColumns.indexOf(this.column) + 1; let table = closest(this.element, TOP_LEVEL_CLASS); column.style.width = width; const headerScaffoldingCell = table.querySelector( `thead td.lt-scaffolding:nth-child(${index})` ); if (headerScaffoldingCell) { headerScaffoldingCell.style.width = width; } const footerScaffoldingCell = table.querySelector( `tfoot td.lt-scaffolding:nth-child(${index})` ); if (footerScaffoldingCell) { footerScaffoldingCell.style.width = width; } if (resizeOnDrag) { let cols = table.querySelectorAll(`tbody td:nth-child(${index})`); cols.forEach((col) => { col.style.width = width; }); } } }, // No-op for closure actions onColumnResized() {}, }); ================================================ FILE: addon/components/lt-foot.hbs ================================================ {{! Scaffolding is needed here to allow use of colspan in the footer }} {{#if (has-block)}} {{yield this.columns}} {{else}} {{#each this.columns as |column|}} {{component (concat "light-table/columns/" column.type) column=column table=this.table tableActions=this.tableActions extra=this.extra sortIcons=this.sortIcons resizeOnDrag=this.resizeOnDrag click=(action "onColumnClick" column) doubleClick=(action "onColumnDoubleClick" column) onColumnResized=(action "onColumnResized") onColumnDrag=(action "onColumnDrag") onColumnDrop=(action "onColumnDrop") }} {{/each}} {{/if}}
================================================ FILE: addon/components/lt-foot.js ================================================ import Component from '@ember/component'; import TableHeaderMixin from 'ember-light-table/mixins/table-header'; /** * @module Light Table */ /** * ```hbs * * * * ``` * If you want to define your own tfoot, just declare the contextual component in a block. * * ```hbs * * * * ``` * * will be empty * * @class t.foot * @uses TableHeaderMixin */ export default Component.extend(TableHeaderMixin, { classNames: ['lt-foot-wrap'], table: null, sharedOptions: null, sharedOptionsFixedKey: 'fixedFooter', }); ================================================ FILE: addon/components/lt-head.hbs ================================================
{{#if (has-block)}} {{yield this.columnGroups this.subColumns}} {{else}} {{! There is an issue where if there are more than 1 row and the first has a colspan, the td's fail to hold their width. Creating a scaffolding will setup the table columns correctly }} {{#if this.subColumns.length}} {{else}} {{/if}} {{#each this.columnGroups as |column|}} {{component (concat "light-table/columns/" column.type) column=column table=this.table tableActions=this.tableActions extra=this.extra sortIcons=this.sortIcons resizeOnDrag=this.resizeOnDrag click=(action "onColumnClick" column) doubleClick=(action "onColumnDoubleClick" column) onColumnResized=(action "onColumnResized") onColumnDrag=(action "onColumnDrag") onColumnDrop=(action "onColumnDrop") }} {{/each}} {{#each this.subColumns as |column|}} {{component (concat "light-table/columns/" column.type) column=column table=this.table rowspan=1 classNames="lt-sub-column" tableActions=this.tableActions extra=this.extra sortIcons=this.sortIcons resizeOnDrag=this.resizeOnDrag click=(action "onColumnClick" column) doubleClick=(action "onColumnDoubleClick" column) onColumnResized=(action "onColumnResized") onColumnDrag=(action "onColumnDrag") onColumnDrop=(action "onColumnDrop") }} {{/each}} {{/if}}
================================================ FILE: addon/components/lt-head.js ================================================ import Component from '@ember/component'; import TableHeaderMixin from 'ember-light-table/mixins/table-header'; import classic from 'ember-classic-decorator'; /** * @module Light Table */ /** * ```hbs * * {{t.head @onColumnClick=(action 'sortByColumn')}} * * ``` * * If you want to define your own thead, just declare the contextual component in a block. * * ```hbs * * * {{#each groups as |group|}} * {{!-- ... --}} * {{/each}} * * * ``` * * If you dont have grouped columns, the yielded `groups` will be an array of all visibile columns and `subColumns` * will be empty * * @class t.head * @uses TableHeaderMixin */ @classic export default class LtHeadComponent extends Component.extend( TableHeaderMixin ) { table = null; sharedOptions = null; sharedOptionsFixedKey = 'fixedHeader'; } ================================================ FILE: addon/components/lt-infinity.hbs ================================================ {{yield}} ================================================ FILE: addon/components/lt-infinity.js ================================================ import Component from '@ember/component'; import { inject as service } from '@ember/service'; export default Component.extend({ inViewport: service(), classNames: ['lt-infinity'], scrollableContent: null, scrollBuffer: 50, didInsertElement() { this._super(...arguments); const options = { viewportSpy: true, viewportTolerance: { bottom: this.scrollBuffer, }, scrollableArea: this.scrollableContent, }; const { onEnter, onExit } = this.inViewport.watchElement( this.element, options ); onEnter(this.didEnterViewport.bind(this)); onExit(this.didExitViewport.bind(this)); }, willDestroyElement() { this._super(...arguments); this.inViewport.stopWatching(this.element); }, didEnterViewport() { this.enterViewport(); }, didExitViewport() { this.exitViewport(); }, }); ================================================ FILE: addon/components/lt-row.hbs ================================================ {{#each this.columns as |column|}} {{component (concat 'light-table/cells/' column.cellType) column=column row=this.row table=this.table rawValue=(get this.row column.valuePath) tableActions=this.tableActions extra=this.extra }} {{/each}} ================================================ FILE: addon/components/lt-row.js ================================================ import Component from '@ember/component'; import { readOnly } from '@ember/object/computed'; export default Component.extend({ tagName: 'tr', classNames: ['lt-row'], classNameBindings: [ 'isSelected', 'isExpanded', 'canExpand:is-expandable', 'canSelect:is-selectable', 'row.classNames', ], attributeBindings: ['colspan', 'data-row-id'], columns: null, row: null, tableActions: null, extra: null, canExpand: false, canSelect: false, colspan: 1, isSelected: readOnly('row.selected'), isExpanded: readOnly('row.expanded'), }); ================================================ FILE: addon/components/lt-scaffolding-row.hbs ================================================ {{#each this.columns as |column|}} {{! template-lint-disable no-inline-styles }} {{! template-lint-enable no-inline-styles }} {{/each}} ================================================ FILE: addon/components/lt-scaffolding-row.js ================================================ import Component from '@ember/component'; export default Component.extend({ classNames: ['lt-scaffolding-row'], tagName: 'tr', }); ================================================ FILE: addon/components/lt-scrollable.hbs ================================================ {{#if this.virtualScrollbar}} {{yield scrollbar}} {{else}} {{yield}} {{/if}} ================================================ FILE: addon/components/lt-scrollable.js ================================================ import Component from '@ember/component'; export default Component.extend({ tagName: '', vertical: true, horizontal: false, }); ================================================ FILE: addon/components/lt-spanned-row.hbs ================================================ {{#if this.visible}} {{yield this.yield}} {{/if}} ================================================ FILE: addon/components/lt-spanned-row.js ================================================ import Component from '@ember/component'; export default Component.extend({ colspan: 1, tagName: '', visible: true, }); ================================================ FILE: addon/helpers/compute.js ================================================ // https://github.com/DockYard/ember-composable-helpers/blob/master/addon/helpers/compute.js import { helper } from '@ember/component/helper'; export function compute([action, ...params]) { return action(...params); } export default helper(compute); ================================================ FILE: addon/helpers/html-safe.js ================================================ import { helper } from '@ember/component/helper'; import { htmlSafe as _htmlSafe } from '@ember/template'; export default helper(function htmlSafe(string /*, hash*/) { return _htmlSafe(string); }); ================================================ FILE: addon/index.js ================================================ import Table from './classes/Table'; import Column from './classes/Column'; import Row from './classes/Row'; /** * ## Installation * ```shell * ember install ember-light-table * ``` * * ## Looking for help? * If it is a bug [please open an issue on GitHub](https://github.com/adopted-ember-addons/ember-light-table/issues). * * ## Usage * There are two parts to this addon. The first is the [Table](../classes/Table.html) which you create with column definitions and rows, and the second is the component declaration. * * @module Usage */ /** * ## Creating a Table Instance * * The first step is to create a table instance that will be used by the component to render * the actual table structure. This same table instance can be used add, remove, and modify * rows and columns. See the [table class documentation](../classes/Table.html) for more details. * * ```javascript * import Table from 'ember-light-table'; * * const table = Table.create({ columns: columns, rows: rows }); * ``` * * Here is a more real-word example * * ```javascript * // components/my-table.js * import { computed } from '@ember/object'; * import Table from 'ember-light-table'; * * export default Ember.Component.extend({ * model: null, * * columns: computed(function() { * return [{ * label: 'Avatar', * valuePath: 'avatar', * width: '60px', * sortable: false, * cellComponent: 'user-avatar' * }, { * label: 'First Name', * valuePath: 'firstName', * width: '150px' * }, { * label: 'Last Name', * valuePath: 'lastName', * width: '150px' * }]; * }), * * table: computed('model', function() { * return Table.create({ columns: this.get('columns'), rows: this.get('model') }); * }) * }); * ``` * * @module Usage * @submodule Table Declaration */ /** * The `light-table` component exposes 3 contextual component (head, body, and foot). * * ```hbs * {{#light-table table as |t|}} * * {{t.head}} * * {{#t.body as |body|}} * {{#body.expanded-row as |row|}} * Hello {{row.firstName}} * {{/body.expanded-row}} * * {{#if isLoading}} * {{#body.loader}} * Loading... * {{/body.loader}} * {{/if}} * * {{#if table.isEmpty}} * {{#body.no-data}} * No users found. * {{/body.no-data}} * {{/if}} * {{/t.body}} * * {{t.foot}} * * {{/light-table}} * ``` * * Each of these contextual components have a wide array of options so it is advised to look * through their documentation. * * @module Usage * @submodule Component Declaration */ export default Table; export { Table, Column, Row }; ================================================ FILE: addon/mixins/draggable-column.js ================================================ import Mixin from '@ember/object/mixin'; import { computed } from '@ember/object'; import { cancel, next } from '@ember/runloop'; let sourceColumn; export default Mixin.create({ classNameBindings: ['isDragging', 'isDragTarget', 'dragDirection'], attributeBindings: ['isDraggable:draggable'], isDragging: false, isDragTarget: false, dragDirection: computed( 'column', 'dragColumnGroup', 'isDragTarget', function () { if (this.isDragTarget) { let columns = this.dragColumnGroup; let targetIdx = columns.indexOf(this.column); let sourceIdx = columns.indexOf(sourceColumn); let direction = sourceIdx - targetIdx < 0 ? 'right' : 'left'; return `drag-${direction}`; } return ''; } ).readOnly(), /** * Array of Columns indicating where the column can be potentially dragged. * If the column is part of a group (has a parent column), this will be all of the columns in that group, * otherwise it's all of the columns in the table. * * @property dragColumnGroup * @type Array * @readonly */ dragColumnGroup: computed('column.parent', 'table.columns', function () { let parent = this.column.get('parent'); return parent ? parent.get('subColumns') : this.table.columns; }).readOnly(), isDropTarget() { let column = this.column; /* A column is a valid drop target only if its in the same group */ return ( sourceColumn && column.get('droppable') && column.get('parent') === sourceColumn.get('parent') ); }, dragStart(e) { this._super(...arguments); let column = this.column; /* NOTE: IE requires setData type to be 'text' */ e.dataTransfer.setData('text', column.get('columnId')); e.dataTransfer.effectAllowed = 'move'; sourceColumn = column; this.set('isDragging', true); this.onColumnDrag(sourceColumn, ...arguments); /* NOTE: This is a fix for Firefox to prevent the click event from being triggered after a drop. */ this.__click__ = this.click; this.click = undefined; }, dragEnter(e) { this._super(...arguments); if (this.isDropTarget()) { e.preventDefault(); this.set('isDragTarget', this.column !== sourceColumn); } }, dragOver(e) { this._super(...arguments); if (this.isDropTarget()) { e.preventDefault(); /* NOTE: dragLeave will be triggered by any child elements inside the column. This code ensures the column being dragged over continues to be identified as the current drop target */ if (!this.isDragTarget) { this.set('isDragTarget', this.column !== sourceColumn); } } }, dragLeave() { this._super(...arguments); this.set('isDragTarget', false); }, dragEnd() { this._super(...arguments); this.setProperties({ isDragTarget: false, isDragging: false }); /* If sourceColumn still references a column, it means that a successful drop did not happen. */ if (sourceColumn) { this.onColumnDrop(sourceColumn, false, ...arguments); sourceColumn = null; } /* Restore click event */ this._clickResetTimer = next(this, () => (this.click = this.__click__)); }, drop(e) { this._super(...arguments); let targetColumn = this.column; if (targetColumn.droppable) { let table = this.table; let columns = this.dragColumnGroup; let targetColumnIdx = columns.indexOf(targetColumn); e.dataTransfer.dropEffect = 'move'; e.preventDefault(); e.stopPropagation(); columns.removeObject(sourceColumn); columns.insertAt(targetColumnIdx, sourceColumn); table.notifyPropertyChange('columns'); this.setProperties({ isDragTarget: false, isDragging: false }); this.onColumnDrop(sourceColumn, true, ...arguments); sourceColumn = null; } }, destroy() { this._super(...arguments); cancel(this._clickResetTimer); }, // Noop for passed actions onColumnDrag() {}, onColumnDrop() {}, }); ================================================ FILE: addon/mixins/table-header.js ================================================ import Mixin from '@ember/object/mixin'; import { computed, trySet } from '@ember/object'; import { oneWay, readOnly } from '@ember/object/computed'; import { isEmpty } from '@ember/utils'; import { warn } from '@ember/debug'; import { inject as service } from '@ember/service'; import cssStyleify from 'ember-light-table/utils/css-styleify'; /** * @module Light Table */ /** * @class TableHeaderMixin * @extends Ember.Mixin * @private */ export default Mixin.create({ attributeBindings: ['style'], scrollbarThickness: service(), /** * @property table * @type {Table} * @private */ table: null, /** * @property sharedOptions * @type {Object} * @private */ sharedOptions: null, /** * @property tableActions * @type {Object} */ tableActions: null, /** * @property extra * @type {Object} */ extra: null, /** * @property fixed * @type {Boolean} * @default false */ fixed: false, /** * @property sortOnClick * @type {Boolean} * @default true */ sortOnClick: true, /** * @property multiColumnSort * @type {Boolean} * @default false */ multiColumnSort: false, /** * Resize all cells in the column instead of just the header / footer * * @property resizeOnDrag * @type {Boolean} * @default false */ resizeOnDrag: false, /** * CSS classes to be applied to an `` tag that is * inserted into the column's `` element when the column is sortable but * not yet sorted. * * For instance, if you have installed `ember-font-awesome` or include the * `font-awesome` assets manually (e.g. via a CDN), you can set * `iconSortable` to `'sort'`, which would yield this markup: * `` * * @property iconSortable * @type {String} * @default '' */ iconSortable: '', /** * See `iconSortable`. CSS classes to apply to `` * when the column is sorted ascending. * * @property iconAscending * @type {String} * @default '' */ iconAscending: '', /** * See `iconSortable`. CSS classes to apply to `` * when the column is sorted descending. * * @property iconDescending * @type {String} * @default '' */ iconDescending: '', /** * Custom sorting component name to use instead of the default `` template. * See `iconSortable`, `iconAsending`, or `iconDescending`. * @property iconComponent * @type {String} * @default false */ iconComponent: null, /** * ID of main table component. Used to generate divs for ember-wormhole * @type {String} */ tableId: null, renderInPlace: oneWay('fixed'), columnGroups: readOnly('table.visibleColumnGroups'), subColumns: readOnly('table.visibleSubColumns'), columns: readOnly('table.visibleColumns'), sortIcons: computed( 'iconSortable', 'iconAscending', 'iconDescending', 'iconComponent', function () { return { iconSortable: this.iconSortable, iconAscending: this.iconAscending, iconDescending: this.iconDescending, iconComponent: this.iconComponent, }; } ).readOnly(), style: computed( 'scrollbarThickness.thickness', 'sharedOptions.occlusion', function () { if (this.sharedOptions?.occlusion) { const scrollbarThickness = this.scrollbarThickness?.thickness; return cssStyleify({ paddingRight: `${scrollbarThickness}px` }); } return; } ).readOnly(), init() { this._super(...arguments); const fixed = this.fixed; const sharedOptionsFixedPath = `sharedOptions.${this.sharedOptionsFixedKey}`; trySet(this, sharedOptionsFixedPath, fixed); const height = this.sharedOptions?.height; warn( 'You did not set a `height` attribute for your table, but marked a header or footer to be fixed. This means that you have to set the table height via CSS. For more information please refer to: https://github.com/adopted-ember-addons/ember-light-table/issues/446', !fixed || (fixed && !isEmpty(height)), { id: 'ember-light-table.height-attribute' } ); }, actions: { /** * onColumnClick action. Handles column sorting. * * @event onColumnClick * @param {Column} column The column that was clicked * @param {Event} event The click event */ onColumnClick(column) { if (column.sortable && this.sortOnClick) { if (column.sorted) { column.toggleProperty('ascending'); } else { if (!this.multiColumnSort) { this.table.sortedColumns.setEach('sorted', false); } column.set('sorted', true); } } this.onColumnClick && this.onColumnClick(...arguments); }, /** * onColumnDoubleClick action. * * @event onColumnDoubleClick * @param {Column} column The column that was clicked * @param {Event} event The click event */ onColumnDoubleClick(/* column */) { this.onColumnDoubleClick && this.onColumnDoubleClick(...arguments); }, /** * onColumnResized action. * * @event onColumnResized * @param {Column} column The column that was resized * @param {String} width The final width of the column */ onColumnResized(/* column, width */) { this.onColumnResized && this.onColumnResized(...arguments); }, /** * onColumnDrag action. * * @event onColumnDrag * @param {Column} column The column that is being dragged */ onColumnDrag(/* column */) { this.onColumnDrag && this.onColumnDrag(...arguments); }, /** * onColumnDrop action. * * @event onColumnDrop * @param {Column} column The column that was dropped * @param {Boolean} isSuccess The column was successfully dropped and sorted */ onColumnDrop(/* column, isSuccess */) { this.onColumnDrop && this.onColumnDrop(...arguments); }, }, }); ================================================ FILE: addon/styles/addon.css ================================================ .ember-light-table { height: inherit; overflow: auto; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; } .ember-light-table table { table-layout: fixed; border-collapse: collapse; width: 100%; box-sizing: border-box; } .ember-light-table .lt-scaffolding { border: none; padding-top: 0; padding-bottom: 0; height: 0; margin-top: 0; margin-bottom: 0; visibility: hidden; } .ember-light-table .lt-head-wrap, .ember-light-table .lt-foot-wrap { overflow-y: auto; overflow-x: hidden; -webkit-box-flex: 0; -ms-flex: 0 0 auto; flex: 0 0 auto; } .ember-light-table .lt-body-wrap { overflow-y: hidden; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-flex: 1; -ms-flex: 1 1 auto; flex: 1 1 auto; } .ember-light-table .lt-column { position: relative; } .ember-light-table .lt-scrollable { width: 100%; -webkit-box-flex: 1; -ms-flex: 1 1 auto; flex: 1 1 auto; } .lt-infinity { min-height: 1px; } .ember-light-table .lt-scrollable.vertical-collection { overflow-y: auto; } .ember-light-table vertical-collection { width: 100%; display: table; table-layout: fixed; } .ember-light-table vertical-collection occluded-content:first-of-type { display: table-caption; } .ember-light-table .align-left { text-align: left; } .ember-light-table .align-right { text-align: right; } .ember-light-table .align-center { text-align: center; } .ember-light-table .lt-column .lt-sort-icon { float: right; } .ember-light-table .lt-column.is-draggable, .ember-light-table .lt-column.is-sortable { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .ember-light-table .lt-column.is-resizing { pointer-events: none; } .ember-light-table.is-resizing { cursor: col-resize; } .ember-light-table .lt-column .lt-column-resizer { width: 5px; cursor: col-resize; height: 100%; background: transparent; position: absolute; right: 0; top: 0; } .ember-light-table .lt-row.is-expandable, .ember-light-table .lt-row.is-selectable { cursor: pointer; } ================================================ FILE: addon/utils/closest.js ================================================ /** * A polyfill for jQuery .closest() method * @param { Object } el Dom element to start from * @param { String } selector Selector to match * @return { Object } The closest matching node or null */ const closest = (el, selector) => { let parent; while (el) { parent = el.parentElement; if (parent && parent.matches(selector)) { return parent; } el = parent; } return; }; export default closest; ================================================ FILE: addon/utils/css-styleify.js ================================================ import { dasherize } from '@ember/string'; import { htmlSafe } from '@ember/template'; import { isPresent } from '@ember/utils'; export default function cssStyleify(hash = {}) { let styles = []; Object.keys(hash).forEach((key) => { let value = hash[key]; if (isPresent(value)) { styles.push(`${dasherize(key)}: ${value}`); } }); return htmlSafe(styles.join('; ')); } ================================================ FILE: app/.gitkeep ================================================ ================================================ FILE: app/components/light-table/cells/base.js ================================================ export { default } from 'ember-light-table/components/cells/base'; ================================================ FILE: app/components/light-table/columns/base.js ================================================ export { default } from 'ember-light-table/components/columns/base'; ================================================ FILE: app/components/light-table.js ================================================ export { default } from 'ember-light-table/components/light-table'; ================================================ FILE: app/components/lt-body.js ================================================ export { default } from 'ember-light-table/components/lt-body'; ================================================ FILE: app/components/lt-column-resizer.js ================================================ export { default } from 'ember-light-table/components/lt-column-resizer'; ================================================ FILE: app/components/lt-foot.js ================================================ export { default } from 'ember-light-table/components/lt-foot'; ================================================ FILE: app/components/lt-head.js ================================================ export { default } from 'ember-light-table/components/lt-head'; ================================================ FILE: app/components/lt-infinity.js ================================================ export { default } from 'ember-light-table/components/lt-infinity'; ================================================ FILE: app/components/lt-row.js ================================================ export { default } from 'ember-light-table/components/lt-row'; ================================================ FILE: app/components/lt-scaffolding-row.js ================================================ export { default } from 'ember-light-table/components/lt-scaffolding-row'; ================================================ FILE: app/components/lt-scrollable.js ================================================ export { default } from 'ember-light-table/components/lt-scrollable'; ================================================ FILE: app/components/lt-spanned-row.js ================================================ export { default } from 'ember-light-table/components/lt-spanned-row'; ================================================ FILE: app/helpers/compute.js ================================================ export { default, compute } from 'ember-light-table/helpers/compute'; ================================================ FILE: app/helpers/html-safe.js ================================================ export { default, htmlSafe } from 'ember-light-table/helpers/html-safe'; ================================================ FILE: blueprints/cell-type/files/app/components/light-table/cells/__name__.js ================================================ import Cell from 'ember-light-table/components/cells/base'; export default Cell.extend({ }); ================================================ FILE: blueprints/cell-type/index.js ================================================ /* jshint node:true*/ module.exports = { description: 'Generates a cell type and integration test', }; ================================================ FILE: blueprints/column-type/files/app/components/light-table/columns/__name__.js ================================================ import Column from 'ember-light-table/components/columns/base'; export default Column.extend({ }); ================================================ FILE: blueprints/column-type/index.js ================================================ /* jshint node:true*/ module.exports = { description: 'Generates a column type and integration test', }; ================================================ FILE: blueprints/ember-light-table/index.js ================================================ /* jshint node:true*/ module.exports = { description: 'Install Ember Light Table dependencies', normalizeEntityName() {}, beforeInstall() { return this.addAddonToProject('ember-responsive'); }, }; ================================================ FILE: ember-cli-build.js ================================================ 'use strict'; const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function (defaults) { let app = new EmberAddon(defaults, { snippetSearchPaths: ['addon', 'tests/dummy/app'], snippetPaths: ['snippets', 'tests/dummy/snippets'], 'ember-prism': { components: ['markup-templating', 'handlebars', 'javascript'], plugins: ['line-numbers'], }, 'ember-power-select': { theme: 'bootstrap', }, 'ember-cli-babel': { includePolyfill: true, }, }); const { maybeEmbroider } = require('@embroider/test-setup'); return maybeEmbroider(app, { skipBabel: [ { package: 'qunit', }, ], }); }; ================================================ FILE: index.js ================================================ 'use strict'; module.exports = { name: require('./package').name, }; ================================================ FILE: package.json ================================================ { "name": "ember-light-table", "version": "3.0.0-beta.2", "description": "Lightweight, component based table", "keywords": [ "ember-addon", "table" ], "homepage": "https://github.com/adopted-ember-addons/ember-light-table", "repository": { "type": "git", "url": "https://github.com/adopted-ember-addons/ember-light-table.git" }, "license": "MIT", "author": "", "directories": { "doc": "doc", "test": "tests" }, "scripts": { "build": "ember build --environment=production", "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", "lint:hbs": "ember-template-lint .", "lint:hbs:fix": "ember-template-lint . --fix", "lint:js": "eslint . --cache", "lint:js:fix": "eslint . --fix", "start": "ember serve", "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", "test:ember": "ember test", "test:ember-compatibility": "ember try:each", "changelog": "lerna-changelog" }, "dependencies": { "@embroider/util": "^1.9.0", "@fortawesome/ember-fontawesome": "^0.2.2", "@fortawesome/free-brands-svg-icons": "^5.15.2", "@fortawesome/free-regular-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.2", "@glimmer/component": "^1.1.2", "@glimmer/tracking": "^1.1.2", "@html-next/vertical-collection": "^4.0.2", "ember-classic-decorator": "^3.0.1", "ember-cli-babel": "^7.26.11", "ember-cli-htmlbars": "^6.1.1", "ember-get-config": "^2.1.1", "ember-in-viewport": "^4.1.0", "ember-responsive": "^5.0.0", "ember-scrollable": "rwwagner90/ember-scrollable#e00bd9b3719d5b0ea04870edb63713d10903990b", "ember-truth-helpers": "^3.1.1", "ember-wormhole": "^0.6.0" }, "devDependencies": { "@ember/jquery": "^2.0.0", "@ember/optional-features": "^2.0.0", "@ember/test-helpers": "^2.8.1", "@embroider/test-setup": "^2.0.2", "@faker-js/faker": "^7.6.0", "babel-eslint": "^10.1.0", "broccoli-asset-rev": "^3.0.0", "concurrently": "^7.6.0", "ember-auto-import": "^2.5.0", "ember-cli": "~4.9.2", "ember-cli-code-coverage": "^1.0.3", "ember-cli-dependency-checker": "^3.3.1", "ember-cli-github-pages": "^0.2.2", "ember-cli-inject-live-reload": "^2.1.0", "ember-cli-mirage": "^3.0.0-alpha.3", "ember-cli-sass": "^11.0.1", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.2", "ember-code-snippet": "^3.0.0", "ember-composable-helpers": "^5.0.0", "ember-concurrency": "^2.3.7", "ember-data": "^3.16.0", "ember-decorators": "^6.1.1", "ember-load-initializers": "^2.1.2", "ember-power-select": "^6.0.1", "ember-prism": "^0.13.0", "ember-qunit": "^6.0.0", "ember-resolver": "^8.0.3", "ember-source": "~4.9.1", "ember-source-channel-url": "^3.0.0", "ember-template-lint": "^5.2.0", "ember-try": "^2.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-ember": "^11.2.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-qunit": "^7.3.4", "lerna-changelog": "^1.0.1", "loader.js": "^4.7.0", "miragejs": "^0.1.45", "prettier": "^2.8.1", "qunit": "^2.19.3", "qunit-dom": "^2.0.0", "release-it": "^15.6.0", "release-it-lerna-changelog": "^5.0.0", "sass": "^1.57.1", "webpack": "^5.75.0" }, "peerDependencies": { "ember-source": "^3.28.0 || ^4.0.0" }, "engines": { "node": "14.* || 16.* || >= 18" }, "publishConfig": { "registry": "https://registry.npmjs.org" }, "ember": { "edition": "octane" }, "ember-addon": { "configPath": "tests/dummy/config", "demoURL": "http://adopted-ember-addons.github.io/ember-light-table", "versionCompatibility": { "ember": ">=3.24.0 <4.0.0" } }, "release-it": { "plugins": { "release-it-lerna-changelog": { "infile": "CHANGELOG.md", "launchEditor": true } }, "git": { "tagName": "v${version}" }, "github": { "release": true, "tokenRef": "GITHUB_AUTH" } }, "volta": { "node": "14.20.0", "yarn": "1.22.17" } } ================================================ FILE: testem.js ================================================ 'use strict'; module.exports = { test_page: 'tests/index.html?hidepassed', disable_watching: true, launch_in_ci: ['Chrome'], launch_in_dev: ['Chrome'], browser_start_timeout: 120, browser_args: { Chrome: { ci: [ // --no-sandbox is needed when running Chrome inside a container process.env.CI ? '--no-sandbox' : null, '--headless', '--disable-dev-shm-usage', '--disable-software-rasterizer', '--mute-audio', '--remote-debugging-port=0', '--window-size=1440,900', ].filter(Boolean), }, }, }; ================================================ FILE: tests/dummy/app/adapters/application.js ================================================ import JSONAPIAdapter from '@ember-data/adapter/json-api'; import ENV from '../config/environment'; export default class Application extends JSONAPIAdapter { namespace = `${ENV.rootURL}api`; } ================================================ FILE: tests/dummy/app/app.js ================================================ import Application from '@ember/application'; import Resolver from 'ember-resolver'; import loadInitializers from 'ember-load-initializers'; import config from 'dummy/config/environment'; export default class App extends Application { modulePrefix = config.modulePrefix; podModulePrefix = config.podModulePrefix; Resolver = Resolver; } loadInitializers(App, config.modulePrefix); ================================================ FILE: tests/dummy/app/breakpoints.js ================================================ /* eslint-disable key-spacing */ export default { mobile: '(max-width: 768px)', tablet: '(min-width: 769px) and (max-width: 992px)', desktop: '(min-width: 993px) and (max-width: 1200px)', jumbo: '(min-width: 1201px)', }; ================================================ FILE: tests/dummy/app/components/.gitkeep ================================================ ================================================ FILE: tests/dummy/app/components/base-table.js ================================================ // BEGIN-SNIPPET base-table import Component from '@glimmer/component'; import { action } from '@ember/object'; import { isEmpty } from '@ember/utils'; import { inject as service } from '@ember/service'; import Table from 'ember-light-table'; import { restartableTask } from 'ember-concurrency'; import { tracked } from '@glimmer/tracking'; export default class BaseTable extends Component { @service store; @tracked canLoadMore = true; @tracked dir = 'asc'; @tracked limit = 10; @tracked meta = null; @tracked model = []; @tracked page = 0; @tracked sort = 'firstName'; @tracked table; constructor() { super(...arguments); this.model = this.args.model; const table = Table.create({ columns: this.columns, rows: this.model, }); const sortColumn = table.get('allColumns').findBy('valuePath', this.sort); // Setup initial sort column if (sortColumn) { sortColumn.set('sorted', true); } this.table = table; } get isLoading() { return this.fetchRecords.isRunning; } @restartableTask *fetchRecords() { const records = yield this.store.query('user', { page: this.page, limit: this.limit, sort: this.sort, dir: this.dir, }); const recordsArray = records.toArray(); this.model.push(...recordsArray); this.table.addRows(recordsArray); this.meta = records.meta; this.canLoadMore = !isEmpty(records); } @action onScrolledToBottom() { if (this.canLoadMore) { this.page = this.page + 1; this.fetchRecords.perform(); } } @action onColumnClick(column) { if (column.sorted) { this.dir = column.ascending ? 'asc' : 'desc'; this.sort = column.valuePath; this.canLoadMore = true; this.page = 0; this.model = []; this.table.setRows(this.model); } } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/code-panel.hbs ================================================
{{#each @snippets as |snippet index|}}
{{/each}}
{{yield}}
================================================ FILE: tests/dummy/app/components/code-panel.js ================================================ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { guidFor } from '@ember/object/internals'; export default class CodePanel extends Component { @tracked collapse = true; elementId = guidFor(this); } ================================================ FILE: tests/dummy/app/components/code-snippet.hbs ================================================ {{! https://github.com/ef4/ember-code-snippet/blob/master/CHANGELOG.md#300}} {{#let (get-code-snippet @name) as |snippet|}} {{! CodeBlock is provided by ember-prism }} {{/let}} ================================================ FILE: tests/dummy/app/components/colored-row.js ================================================ // BEGIN-SNIPPET colored-row import classic from 'ember-classic-decorator'; import { classNames, attributeBindings } from '@ember-decorators/component'; import { htmlSafe } from '@ember/template'; import Row from 'ember-light-table/components/lt-row'; @classic @classNames('colored-row') @attributeBindings('style') export default class ColoredRow extends Row { get style() { return htmlSafe(`background-color: ${this.row.get('color')};`); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/columns/draggable-table.hbs ================================================ {{! BEGIN-SNIPPET draggable-table }} {{#if this.isLoading}} {{/if}} Drag and drop a column onto another to reorder the columns {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/columns/draggable-table.js ================================================ // BEGIN-SNIPPET draggable-table import BaseTable from '../base-table'; export default class DraggableTable extends BaseTable { get columns() { return [ { label: 'User Details', sortable: false, align: 'center', draggable: true, subColumns: [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, align: 'center', draggable: true, cellComponent: 'user-avatar', }, { label: 'First', valuePath: 'firstName', width: '150px', draggable: true, }, { label: 'Last', valuePath: 'lastName', width: '150px', draggable: true, }, ], }, { label: 'Contact Information', sortable: false, align: 'center', draggable: true, subColumns: [ { label: 'Address', valuePath: 'address', draggable: true, }, { label: 'State', valuePath: 'state', draggable: true, }, { label: 'Country', valuePath: 'country', draggable: true, }, ], }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/columns/grouped-table.hbs ================================================ {{! BEGIN-SNIPPET grouped-table }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/columns/grouped-table.js ================================================ // BEGIN-SNIPPET grouped-table import BaseTable from '../base-table'; export default class GroupedTable extends BaseTable { get columns() { return [ { label: 'User Details', sortable: false, align: 'center', subColumns: [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First', valuePath: 'firstName', width: '150px', }, { label: 'Last', valuePath: 'lastName', width: '150px', }, ], }, { label: 'Contact Information', sortable: false, align: 'center', subColumns: [ { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ], }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/columns/resizable-table.hbs ================================================ {{! BEGIN-SNIPPET resizable-table }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/columns/resizable-table.js ================================================ // BEGIN-SNIPPET resizable-table import BaseTable from '../base-table'; export default class ResizableTable extends BaseTable { get columns() { return [ { label: 'User Details', sortable: false, align: 'center', subColumns: [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First', resizable: true, valuePath: 'firstName', width: '150px', minResizeWidth: 75, }, { label: 'Last', resizable: true, valuePath: 'lastName', width: '150px', minResizeWidth: 75, }, ], }, { label: 'Contact Information', sortable: false, align: 'center', subColumns: [ { label: 'Address', resizable: true, valuePath: 'address', minResizeWidth: 100, }, { label: 'State', resizable: true, valuePath: 'state', minResizeWidth: 100, }, { label: 'Country', resizable: true, valuePath: 'country', minResizeWidth: 100, }, ], }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/client-side-table.hbs ================================================ {{! BEGIN-SNIPPET client-side-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}}
{{option.label}}
{{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/cookbook/client-side-table.js ================================================ // BEGIN-SNIPPET client-side-table import BaseTable from '../base-table'; import { action } from '@ember/object'; import { restartableTask, timeout } from 'ember-concurrency'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; export default class PaginatedTable extends BaseTable { @service store; query = ''; @tracked meta; // Filter Input @tracked selectedFilter = this.possibleFilters.firstObject; get sortedModel() { if (this.dir === 'asc') return this.model.sortBy(this.sort); else return this.model.sortBy(this.sort).reverse(); } get isLoading() { return this.fetchRecords?.isRunning || this.setRows?.isRunning; } get possibleFilters() { return this.table.columns.filterBy('sortable', true); } get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } constructor() { super(...arguments); this.fetchRecords.perform(); } @restartableTask *fetchRecords() { const records = yield this.store.query('user', { page: 1, limit: 100 }); const recordsArray = records.toArray(); this.model.push(...recordsArray); this.meta = records.meta; yield this.filterAndSortModel.perform(); } @restartableTask *setRows(rows) { this.table.setRows([]); yield timeout(100); // Allows isLoading state to be shown this.table.setRows(rows); } @restartableTask *filterAndSortModel(debounceMs = 200) { yield timeout(debounceMs); // debounce const query = this.query; const model = this.sortedModel; let result = model; if (query !== '' && this.selectedFilter !== undefined) { const { valuePath } = this.selectedFilter; result = model.filter((m) => { return m.get(valuePath).toLowerCase().includes(query.toLowerCase()); }); } yield this.setRows.perform(result); } @action onColumnClick(column) { if (column.sorted) { this.dir = column.ascending ? 'asc' : 'desc'; this.sort = column.valuePath; this.filterAndSortModel.perform(100); } } @action updateQuery(event) { this.query = event.target.value; } @action onSearchChange() { this.filterAndSortModel.perform(); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/custom-row-table.hbs ================================================ {{! BEGIN-SNIPPET custom-row-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/cookbook/custom-row-table.js ================================================ // BEGIN-SNIPPET custom-row-table import BaseTable from '../base-table'; export default class CustomRowTable extends BaseTable { get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/custom-sort-icon-table.hbs ================================================ {{!-- BEGIN-SNIPPET custom-sort-icon-table --}} {{#if this.isLoading}} {{/if}} {{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/cookbook/custom-sort-icon-table.js ================================================ // BEGIN-SNIPPET custom-sort-icon-table import BaseTable from '../base-table'; export default class CustomSortIconTable extends BaseTable { get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/horizontal-scrolling-table.hbs ================================================ {{! BEGIN-SNIPPET horizontal-scrolling-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/cookbook/horizontal-scrolling-table.js ================================================ // BEGIN-SNIPPET horizontal-scrolling-table import BaseTable from '../base-table'; export default class HorizontalScrollingTable extends BaseTable { get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '350px', }, { label: 'Last Name', valuePath: 'lastName', width: '350px', }, { label: 'Address', valuePath: 'address', width: '350px', }, { label: 'State', valuePath: 'state', width: '350px', }, { label: 'Country', valuePath: 'country', width: '350px', }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/index-list.hbs ================================================
Client Side Search & Sort

Fully searchable and sortable tables, client side.

Custom Row Component

Need more than the default ELT rows? Simply create your own rowComponent.

Custom Sort Icon

Not a fan of fa-icons? Easily change it to any font you like using the table's iconComponent.

Horizontal Scrolling

Horizontal scrolling automatically enabled if the combined width of all columns exceeds table width.

Occlusion Rendering

The holy grail of tables. Intelligent row offloading to scroll huge lists forever, without performance degradation.

Pagination

Feel free to add any pagination to your table's footer.

Table Actions

Interact with your rows and cells the Ember way: "Data Down, Actions Up" (DDAU)

================================================ FILE: tests/dummy/app/components/cookbook/occluded-table.hbs ================================================ {{!-- BEGIN-SNIPPET occluded-table --}} {{!-- In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. --}} {{#if this.isLoading}} {{/if}} {{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/cookbook/occluded-table.js ================================================ // BEGIN-SNIPPET occluded-table import BaseTable from '../base-table'; export default class OccludedTable extends BaseTable { limit = 100; get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } constructor() { super(...arguments); this.page = 1; this.fetchRecords.perform(); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/paginated-table.hbs ================================================ {{! BEGIN-SNIPPET paginated-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{#if this.meta}}
  • {{#each (range 1 this.meta.totalPages true) as |p|}}
  • {{/each}}
{{/if}}
{{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/cookbook/paginated-table.js ================================================ // BEGIN-SNIPPET paginated-table import BaseTable from '../base-table'; import { action } from '@ember/object'; export default class PaginatedTable extends BaseTable { limit = 12; get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } constructor() { super(...arguments); this.setPage(1); } @action setPage(page) { const totalPages = this.meta?.totalPages; const currPage = this.page; if (page < 1 || page > totalPages || page === currPage) { return; } this.page = page; this.model = []; this.table.setRows(this.model); this.fetchRecords.perform(); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/cookbook/table-actions-table.hbs ================================================ {{! BEGIN-SNIPPET table-actions-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/cookbook/table-actions-table.js ================================================ // BEGIN-SNIPPET table-actions-table import BaseTable from '../base-table'; import { action } from '@ember/object'; export default class TableActionsTable extends BaseTable { get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, { label: 'Actions', width: '100px', sortable: false, cellComponent: 'user-actions', }, ]; } @action deleteUser(row) { const confirmed = window.confirm( `Are you sure you want to delete ${row.get('firstName')} ${row.get( 'lastName' )}?` ); if (confirmed) { this.table.removeRow(row); row.get('content').deleteRecord(); } } @action notifyUser(row) { window.alert( `${row.get('firstName')} ${row.get('lastName')} has been notified.` ); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/expanded-row.hbs ================================================ {{!-- BEGIN-SNIPPET expanded-row --}}
avatar

{{@row.bio}}

{{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/fa-icon-wrapper.hbs ================================================ ================================================ FILE: tests/dummy/app/components/materialize-icon.hbs ================================================ {{!-- BEGIN-SNIPPET materialize-icon --}} {{get @sortIcons @sortIconProperty}} {{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/no-data.hbs ================================================ {{!-- BEGIN-SNIPPET no-data --}}

Hi there!

Uhh... Looks like you've delete all our users

Lets see, have you tried turning it off and on?

{{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/responsive-expanded-row.hbs ================================================ {{!-- BEGIN-SNIPPET responsive-expanded-row --}}
{{#each @table.responsiveHiddenColumns as |column|}}
{{column.label}}:
{{get @row column.valuePath}}
{{/each}}
{{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/responsive-table.hbs ================================================ {{! BEGIN-SNIPPET responsive-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/responsive-table.js ================================================ // BEGIN-SNIPPET responsive-table import BaseTable from './base-table'; import { action } from '@ember/object'; export default class ResponsiveTable extends BaseTable { get columns() { return [ { width: '40px', sortable: false, cellComponent: 'row-toggle', breakpoints: ['mobile', 'tablet', 'desktop'], }, { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', breakpoints: ['tablet', 'desktop', 'jumbo'], }, { label: 'Address', valuePath: 'address', breakpoints: ['tablet', 'desktop', 'jumbo'], }, { label: 'State', valuePath: 'state', breakpoints: ['desktop', 'jumbo'], }, { label: 'Country', valuePath: 'country', breakpoints: ['jumbo'], }, ]; } @action onAfterResponsiveChange(matches) { if (matches.indexOf('jumbo') > -1) { this.table.expandedRows.setEach('expanded', false); } } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/row-toggle.hbs ================================================ {{! BEGIN-SNIPPET row-toggle }} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/rows/expandable-table.hbs ================================================ {{! BEGIN-SNIPPET expandable-table }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/rows/expandable-table.js ================================================ // BEGIN-SNIPPET expandable-table import BaseTable from '../base-table'; export default class ExpandableTable extends BaseTable { get columns() { return [ { label: 'First Name', valuePath: 'firstName', }, { label: 'Last Name', valuePath: 'lastName', }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/rows/selectable-table.hbs ================================================ {{! BEGIN-SNIPPET selectable-table }}
{{#if this.hasSelection}}
{{else}}
{{/if}}
{{#if this.isLoading}} {{/if}} {{#if (and (not this.isLoading) this.table.isEmpty)}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/rows/selectable-table.js ================================================ // BEGIN-SNIPPET selectable-table import BaseTable from '../base-table'; import { action } from '@ember/object'; export default class ExpandableTable extends BaseTable { get hasSelection() { return this.table.selectedRows; } get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } @action selectAll() { this.table.rows.setEach('selected', true); } @action deselectAll() { this.table.selectedRows.setEach('selected', false); } @action deleteAll() { this.table.removeRows(this.table.selectedRows); } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/scrolling-table.hbs ================================================ {{! BEGIN-SNIPPET scrolling-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}}
{{if (eq this.currentScrollOffset null) "N/A" (concat this.currentScrollOffset "px") }}
{{row.id}} - {{row.firstName}} {{row.lastName}}
{{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/scrolling-table.js ================================================ // BEGIN-SNIPPET scrolling-table import BaseTable from './base-table'; import { action } from '@ember/object'; export default class ScrollingTable extends BaseTable { currentScrollOffset = 0; scrollTo = 0; scrollToRow = null; get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } @action updateScrollPos(event) { this.scrollTo = event.target.value; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/simple-table.hbs ================================================ {{! BEGIN-SNIPPET simple-table }} {{! In order for `sort-up` and `sort-down` icons to work, you need to have ember-font-awesome installed or manually include the font-awesome assets, e.g. via a CDN. }} {{#if this.isLoading}} {{/if}} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/simple-table.js ================================================ // BEGIN-SNIPPET simple-table import BaseTable from './base-table'; export default class SimpleTable extends BaseTable { get columns() { return [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; } } // END-SNIPPET ================================================ FILE: tests/dummy/app/components/table-loader.hbs ================================================ {{!-- BEGIN-SNIPPET table-loader --}}
{{!-- END-SNIPPET --}} ================================================ FILE: tests/dummy/app/components/user-actions.hbs ================================================ {{! BEGIN-SNIPPET user-actions }} {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/components/user-avatar.hbs ================================================ {{! BEGIN-SNIPPET user-avatar }} user-avatar {{! END-SNIPPET }} ================================================ FILE: tests/dummy/app/controllers/.gitkeep ================================================ ================================================ FILE: tests/dummy/app/controllers/application.js ================================================ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; export default class ApplicationController extends Controller { @service router; } ================================================ FILE: tests/dummy/app/helpers/.gitkeep ================================================ ================================================ FILE: tests/dummy/app/helpers/classify.js ================================================ import { helper } from '@ember/component/helper'; import { classify as _classify } from '@ember/string'; export default helper(function classify(string /*, hash*/) { return _classify(string[0]); }); ================================================ FILE: tests/dummy/app/index.html ================================================ Ember Light Table {{content-for "head"}} {{content-for "head-footer"}} {{content-for "body"}} {{content-for "body-footer"}} ================================================ FILE: tests/dummy/app/models/.gitkeep ================================================ ================================================ FILE: tests/dummy/app/models/user.js ================================================ import Model, { attr } from '@ember-data/model'; export default class User extends Model { @attr('string') firstName; @attr('string') lastName; @attr('string') company; @attr('string') address; @attr('string') country; @attr('string') state; @attr('string') email; @attr('string') username; @attr('string') avatar; @attr('string') bio; @attr('string') color; } ================================================ FILE: tests/dummy/app/router.js ================================================ import EmberRouter from '@ember/routing/router'; import config from 'dummy/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; } Router.map(function () { this.route('responsive'); this.route('scrolling'); this.route('columns', function () { this.route('draggable'); this.route('grouped'); this.route('resizable'); }); this.route('rows', function () { this.route('expandable'); this.route('selectable'); }); this.route('cookbook', function () { this.route('client-side'); this.route('custom-row'); this.route('custom-sort-icon'); this.route('horizontal-scrolling'); this.route('occlusion-rendering'); this.route('pagination'); this.route('table-actions'); }); }); ================================================ FILE: tests/dummy/app/routes/.gitkeep ================================================ ================================================ FILE: tests/dummy/app/routes/columns/draggable.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/columns/grouped.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/columns/resizable.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/custom-row.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/custom-sort-icon.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/horizontal-scrolling.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/index.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/occlusion-rendering.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/pagination.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/sorting.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook/table-actions.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/cookbook.js ================================================ export { default } from './table-route'; ================================================ FILE: tests/dummy/app/routes/index.js ================================================ export { default } from './table-route'; ================================================ FILE: tests/dummy/app/routes/responsive.js ================================================ export { default } from './table-route'; ================================================ FILE: tests/dummy/app/routes/rows/expandable.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/rows/selectable.js ================================================ export { default } from '../table-route'; ================================================ FILE: tests/dummy/app/routes/scrolling.js ================================================ export { default } from './table-route'; ================================================ FILE: tests/dummy/app/routes/table-route.js ================================================ import { A } from '@ember/array'; import Route from '@ember/routing/route'; export default class TableRouteRoute extends Route { model() { return A([]); } resetController(controller, isExiting) { if (isExiting) { controller.set('page', 1); } } } ================================================ FILE: tests/dummy/app/serializers/application.js ================================================ export { default } from '@ember-data/serializer/json-api'; ================================================ FILE: tests/dummy/app/styles/app.scss ================================================ @import 'ember-power-select'; $accent-color: #dd6a58; @import './loader.scss'; @import './table.scss'; html, body { min-height: 100%; min-width: 100%; font-family: 'Open Sans', sans-serif; background-color: #f3f3f3; height: 100%; } label { font-weight: 600; } .ember-power-select-trigger { height: 35px; } .scrolling-select-control { width: 40%; label { float: left; margin-right: 3px; margin-bottom: 0; padding-top: 6px; } } .pagination { button { color: #d95743; padding: 5px 10px; font-size: 12px; line-height: 1.5; position: relative; float: left; margin-left: -1px; text-decoration: none; background-color: #fff; border: 1px solid #ddd; } .active button { color: #fff; background: #d4442e; border-color: #c13c27; z-index: 3; cursor: default; } } .navbar.navbar-default { background-color: white; margin: 0; .navbar-brand { color: #797979; font-size: 16px; font-weight: 400; img { height: 32px; display: inline-block; margin-top: -15px; } span { font-size: 12px; vertical-align: super; text-transform: uppercase; color: #444444; } } .navbar-nav > li > a { font-size: 14px; font-weight: 200; &.github { font-size: 24px; } } .navbar-nav.dropdown-menu li a { font-size: 13px; } .navbar-nav > li > a:focus, .navbar-nav > li > a:hover { color: $accent-color; } .navbar-nav .active, .navbar-nav .active:focus, .navbar-nav .active:hover { color: $accent-color; background-color: transparent; } } .panel { width: 75%; margin: 2.5% auto; position: relative; .table-actions { position: absolute; left: -41px; width: 40px; background-color: white; border-bottom-left-radius: 5px; border-top-left-radius: 5px; border: 1px solid #ccc; border-right-width: 0; top: 35%; cursor: pointer; .table-action { font-size: 20px; text-align: center; display: block; margin: 10px; color: #191919; opacity: 0.8; &:hover, &:active { opacity: 1; } &.delete { color: $accent-color; } } } .panel-heading { background-color: $accent-color; padding: 0; a { color: #f9f9f9; text-decoration: none; .panel-title { font-weight: 200; padding: 15px; } } } .code-snippet { background-color: #f8f8f8; max-height: 500px; overflow: auto; .nav-tabs { li > a { color: #8a8a8a; border-radius: 2px 2px 0 0; font-size: 12px; } li.active > a { background-color: #f8f8f8; color: #696969; } } .tab-content { > .active { padding: 15px 15px 0px 15px; } } pre { border: none; margin: 0; padding: 0; } } } .table-container { overflow-y: auto; &.fixed-header { overflow-y: hidden; margin-bottom: 15px; } } .user-actions { a { color: $accent-color; padding-right: 10px; font-size: 18px; width: 30px; text-decoration: none; cursor: pointer; padding-top: 10px; &:hover, &:active, &:focus { color: darken($accent-color, 5%); } } } .user-avatar { border-radius: 50%; border: 1px solid #ccc; } .row-toggle { color: $accent-color; cursor: pointer; background-color: #fff; border: 0; &:hover, &:active, &:focus { color: darken($accent-color, 5%); } } .tip { text-align: center; font-size: 13px; color: #4e4e4e; .icon-info { color: $accent-color; margin-right: 3px; } } form .form-group { input, select { outline: none; display: block; width: 100%; padding: 0 15px; border: 1px solid #d9d9d9; color: #6e6e6e; font-family: 'Roboto', sans-serif; -webkti-box-sizing: border-box; box-sizing: border-box; font-size: 14px; font-weight: 400; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: all 0.3s linear 0s; box-shadow: none; &:focus { color: #333333; border-color: #b9b9b9; outline: none; box-shadow: none; -webkit-box-shadow: none; } } } ================================================ FILE: tests/dummy/app/styles/loader.scss ================================================ $loader-color: #dd6a58; .spinner { margin: 15px auto; width: 50px; height: 50px; text-align: center; font-size: 10px; } .spinner > div { background-color: $loader-color; height: 100%; width: 4px; display: inline-block; -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; animation: sk-stretchdelay 1.2s infinite ease-in-out; } .spinner .rect2 { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; } .spinner .rect3 { -webkit-animation-delay: -1.0s; animation-delay: -1.0s; } .spinner .rect4 { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .spinner .rect5 { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } @-webkit-keyframes sk-stretchdelay { 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } 20% { -webkit-transform: scaleY(1.0) } } @keyframes sk-stretchdelay { 0%, 40%, 100% { transform: scaleY(0.4); -webkit-transform: scaleY(0.4); } 20% { transform: scaleY(1.0); -webkit-transform: scaleY(1.0); } } ================================================ FILE: tests/dummy/app/styles/table.scss ================================================ $border-color: #DADADA; .ember-light-table { width: 95%; margin: 0 auto; border-collapse: collapse; font-family: 'Open Sans', sans-serif; .multi-select { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } tfoot .lt-column { border-top: 1px solid $border-color; } thead .lt-column { border-bottom: 1px solid $border-color; } thead th, tfoot th { &.is-dragging { opacity: 0.75; background: #eee; } &.is-drag-target { &.drag-right { border-right: 1px dotted $border-color; } &.drag-left { border-left: 1px dotted $border-color; } } } .lt-column { font-weight: 200; font-size: 12px; padding: 10px; .lt-sort-icon { height:0px; width: 15px; } &.lt-group-column { border: none !important; padding-bottom: 10px; } .lt-column-resizer { border-right: 1px dashed #ccc; border-left: 1px dashed #ccc; } } .lt-row { height: 50px; &.is-selected { background-color: #DEDEDE; } &:not(.is-selected):hover { background-color: #F5F4F4; } &:last-of-type { td { border-bottom-width: 0; } } &.lt-expanded-row, &.lt-no-data { &:hover { background-color: transparent; } td { padding: 15px; } } &.colored-row { color: white; &:hover { opacity: 0.8; } } td { border-color: $border-color; border-width: 0; border-bottom-width: 1px; border-style: solid; font-size: 13px; padding: 0 10px; } } } tfoot { tr > td { border-top: 1px solid #DADADA; padding: 10px 10px 0 10px; font-size: 13px; } form { display: flex; justify-content: space-between; } .pagination { margin: 5px 0; li > a { color: darken($accent-color, 5%); } li > a:hover, li > a:focus, li.active > a:hover, li.active > a:focus, li.active > a { color: white; background: darken($accent-color, 10%); border-color: darken($accent-color, 15%); } } } .ember-light-table.occlusion { .lt-row td { vertical-align: middle; } } ================================================ FILE: tests/dummy/app/templates/application.hbs ================================================ {{outlet}}
Click on the panel header with the icon to see code snippets associated with this table.
================================================ FILE: tests/dummy/app/templates/columns/draggable.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/columns/grouped.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/columns/resizable.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/client-side.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/custom-row.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/custom-sort-icon.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/horizontal-scrolling.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/index.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/occlusion-rendering.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/pagination.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/cookbook/table-actions.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/index.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/responsive.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/rows/expandable.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/rows/selectable.hbs ================================================ ================================================ FILE: tests/dummy/app/templates/scrolling.hbs ================================================ ================================================ FILE: tests/dummy/config/ember-cli-update.json ================================================ { "schemaVersion": "1.0.0", "packages": [ { "name": "ember-cli", "version": "4.9.2", "blueprints": [ { "name": "addon", "outputRepo": "https://github.com/ember-cli/ember-addon-output", "codemodsSource": "ember-addon-codemods-manifest@1", "isBaseBlueprint": true, "options": [ "--yarn", "--no-welcome" ] } ] } ] } ================================================ FILE: tests/dummy/config/ember-try.js ================================================ 'use strict'; const getChannelURL = require('ember-source-channel-url'); const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); module.exports = async function () { return { useYarn: true, scenarios: [ { name: 'ember-lts-3.24', npm: { devDependencies: { 'ember-qunit': '^5.0.0', 'ember-source': '~3.24.3', }, }, }, { name: 'ember-lts-3.28', npm: { devDependencies: { 'ember-source': '~3.28.9', }, }, }, { name: 'ember-lts-4.4', npm: { devDependencies: { 'ember-source': '~4.4.0', }, }, }, { name: 'ember-release', npm: { devDependencies: { 'ember-source': await getChannelURL('release'), }, }, }, { name: 'ember-beta', npm: { devDependencies: { 'ember-source': await getChannelURL('beta'), }, }, }, { name: 'ember-canary', npm: { devDependencies: { 'ember-source': await getChannelURL('canary'), }, }, }, { name: 'ember-default-with-jquery', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true, }), }, npm: { devDependencies: { '@ember/jquery': '^2.0.0', 'ember-source': '~3.28.0', }, }, }, { name: 'ember-classic', env: { EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'application-template-wrapper': true, 'default-async-observers': false, 'template-only-glimmer-components': false, }), }, npm: { devDependencies: { 'ember-source': '~3.28.0', }, ember: { edition: 'classic', }, }, }, embroiderSafe(), embroiderOptimized(), ], }; }; ================================================ FILE: tests/dummy/config/environment.js ================================================ 'use strict'; module.exports = function (environment) { const ENV = { modulePrefix: 'dummy', environment, rootURL: '/', locationType: 'history', EmberENV: { EXTEND_PROTOTYPES: false, FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true }, }, APP: { // Here you can pass flags/options to your application instance // when it is created }, }; if (environment === 'development') { // ENV.APP.LOG_RESOLVER = true; // ENV.APP.LOG_ACTIVE_GENERATION = true; // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; } if (environment === 'test') { // Testem prefers this... ENV.locationType = 'none'; // keep test console output quieter ENV.APP.LOG_ACTIVE_GENERATION = false; ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; } if (environment === 'production') { ENV.locationType = 'hash'; ENV.rootURL = '/ember-light-table/'; ENV['ember-cli-mirage'] = { enabled: true, }; } return ENV; }; ================================================ FILE: tests/dummy/config/icons.js ================================================ module.exports = function () { return { 'free-brands-svg-icons': ['facebook', 'github', 'twitter'], 'free-regular-svg-icons': ['check-square', 'square', 'trash-alt'], 'free-solid-svg-icons': [ 'bell', 'caret-down', 'chevron-down', 'chevron-left', 'chevron-right', 'code', 'envelope', 'info-circle', 'sort', 'sort-down', 'sort-up', ], }; }; ================================================ FILE: tests/dummy/config/optional-features.json ================================================ { "application-template-wrapper": false, "default-async-observers": true, "jquery-integration": false, "template-only-glimmer-components": true } ================================================ FILE: tests/dummy/config/targets.js ================================================ 'use strict'; const browsers = [ 'last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions', ]; module.exports = { browsers, }; ================================================ FILE: tests/dummy/mirage/config.js ================================================ import { discoverEmberDataModels } from 'ember-cli-mirage'; import { createServer } from 'miragejs'; import { A as emberArray } from '@ember/array'; import ENV from '../config/environment'; export default function (config) { let finalConfig = { ...config, models: { ...discoverEmberDataModels(), ...config.models }, routes, }; return createServer(finalConfig); } function routes() { this.passthrough('/write-coverage'); /* Config (with defaults). Note: these only affect routes defined *after* them! */ // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server this.namespace = `${ENV.rootURL}/api`; // make this `api`, for example, if your API is namespaced // this.timing = 400; // delay for each request, automatically set to 0 during testing this.get('/users', function (schema, request) { let { page, limit, sort, dir } = request.queryParams; const collection = schema.users.all(); let { models: users } = collection; page = Number(page || 1); limit = Number(limit || 20); dir = dir || 'asc'; let meta = { page, limit, totalPages: Math.ceil(users.length / limit), }; if (sort) { users = emberArray(users).sortBy(sort); if (dir !== 'asc') { users = users.reverse(); } } let offset = (page - 1) * limit; users = users.slice(offset, offset + limit); collection.models = users; const json = this.serialize(collection); json.meta = meta; return json; }); } ================================================ FILE: tests/dummy/mirage/factories/user.js ================================================ /* This is an example factory definition. Create more files in this directory to define additional factories. */ import { Factory } from 'miragejs'; import { faker } from '@faker-js/faker'; faker.locale = 'en_US'; const MATERIAL_UI_COLORS = [ '#F44336', '#E91E63', '#9C27B0', '#009688', '#2196F3', '#4CAF50', '#FFC107', '#FF5722', '#607D8B', ]; export default Factory.extend({ firstName: () => faker.name.firstName(), lastName: () => faker.name.firstName(), company: () => faker.company.name(), address: () => faker.address.streetAddress(), country: () => faker.address.country(), state: () => faker.address.state(), email: () => faker.internet.email(), username: () => faker.internet.userName(), avatar: () => faker.internet.avatar(), bio: () => faker.lorem.paragraph(), color: () => faker.helpers.arrayElement(MATERIAL_UI_COLORS), }); ================================================ FILE: tests/dummy/mirage/models/user.js ================================================ import { Model } from 'miragejs'; export default Model.extend({}); ================================================ FILE: tests/dummy/mirage/scenarios/default.js ================================================ export default function (server) { // Seed your development database using your factories. This // data will not be loaded in your tests. server.createList('user', 300); } ================================================ FILE: tests/dummy/mirage/serializers/application.js ================================================ import { JSONAPISerializer } from 'miragejs'; export default JSONAPISerializer.extend({}); ================================================ FILE: tests/dummy/public/robots.txt ================================================ # http://www.robotstxt.org User-agent: * Disallow: ================================================ FILE: tests/helpers/has-class.js ================================================ export default function hasClass(elem, cls) { return [...elem.classList] .filter((cssClass) => cssClass === cls) .reduce((bool, next) => bool || next, false); } ================================================ FILE: tests/helpers/index.js ================================================ import { setupApplicationTest as upstreamSetupApplicationTest, setupRenderingTest as upstreamSetupRenderingTest, setupTest as upstreamSetupTest, } from 'ember-qunit'; // This file exists to provide wrappers around ember-qunit's / ember-mocha's // test setup functions. This way, you can easily extend the setup that is // needed per test type. function setupApplicationTest(hooks, options) { upstreamSetupApplicationTest(hooks, options); // Additional setup for application tests can be done here. // // For example, if you need an authenticated session for each // application test, you could do: // // hooks.beforeEach(async function () { // await authenticateSession(); // ember-simple-auth // }); // // This is also a good place to call test setup functions coming // from other addons: // // setupIntl(hooks); // ember-intl // setupMirage(hooks); // ember-cli-mirage } function setupRenderingTest(hooks, options) { upstreamSetupRenderingTest(hooks, options); // Additional setup for rendering tests can be done here. } function setupTest(hooks, options) { upstreamSetupTest(hooks, options); // Additional setup for unit tests can be done here. } export { setupApplicationTest, setupRenderingTest, setupTest }; ================================================ FILE: tests/helpers/responsive.js ================================================ /* eslint-disable */ import { A } from '@ember/array'; import { classify } from '@ember/string'; import { computed } from '@ember/object'; import { getOwner } from '@ember/application'; import { registerAsyncHelper } from '@ember/test'; import MediaService from 'ember-responsive/services/media'; MediaService.reopen({ // Change this if you want a different default breakpoint in tests. _defaultBreakpoint: 'desktop', _breakpointArr: computed('breakpoints', function() { return Object.keys(this.breakpoints) || A([]); }), _forceSetBreakpoint(breakpoint) { let found = false; const props = {}; this._breakpointArr.forEach(function(bp) { const val = bp === breakpoint; if (val) { found = true; } props[`is${classify(bp)}`] = val; }); if (found) { this.setProperties(props); } else { throw new Error( `You tried to set the breakpoint to ${breakpoint}, which is not in your app/breakpoint.js file.` ); } }, match() {}, // do not set up listeners in test init() { this._super(...arguments); this._forceSetBreakpoint(this._defaultBreakpoint); } }); export default registerAsyncHelper('setBreakpoint', function(app, breakpoint) { // this should use getOwner once that's supported const mediaService = app.__deprecatedInstance__.lookup('service:media'); mediaService._forceSetBreakpoint(breakpoint); }); export function setBreakpointForIntegrationTest(container, breakpoint) { const mediaService = getOwner(container).lookup('service:media'); mediaService._forceSetBreakpoint(breakpoint); container.set('media', mediaService); return mediaService; } // jscs: enable ================================================ FILE: tests/helpers/table-columns.js ================================================ export default [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; export const GroupedColumns = [ { label: 'User Details', sortable: false, align: 'center', subColumns: [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First', valuePath: 'firstName', width: '150px', }, { label: 'Last', valuePath: 'lastName', width: '150px', }, ], }, { label: 'Contact Information', sortable: false, align: 'center', subColumns: [ { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ], }, ]; export const ResizableColumns = [ { label: 'User Details', sortable: false, align: 'center', subColumns: [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First', resizable: true, valuePath: 'firstName', width: '150px', minResizeWidth: 75, }, { label: 'Last', resizable: true, valuePath: 'lastName', width: '150px', minResizeWidth: 75, }, ], }, { label: 'Contact Information', sortable: false, align: 'center', subColumns: [ { label: 'Address', resizable: true, valuePath: 'address', minResizeWidth: 100, }, { label: 'State', resizable: true, valuePath: 'state', minResizeWidth: 100, }, { label: 'Country', resizable: true, valuePath: 'country', minResizeWidth: 100, }, ], }, ]; ================================================ FILE: tests/index.html ================================================ Dummy Tests {{content-for "head"}} {{content-for "test-head"}} {{content-for "head-footer"}} {{content-for "test-head-footer"}} {{content-for "body"}} {{content-for "test-body"}}
{{content-for "body-footer"}} {{content-for "test-body-footer"}} ================================================ FILE: tests/integration/.gitkeep ================================================ ================================================ FILE: tests/integration/components/light-table/cells/base-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { run } from '@ember/runloop'; import { Row, Column } from 'ember-light-table'; module('Integration | Component | Cells | base', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { this.set('column', Column.create()); await render(hbs`{{light-table/cells/base column=this.column}}`); assert.dom('*').hasText(''); }); test('cell with column format', async function (assert) { this.set( 'column', Column.create({ valuePath: 'num', format(value) { return value * 2; }, }) ); this.set('row', Row.create()); await render(hbs` {{light-table/cells/base column=this.column row=this.row rawValue=2}} `); assert.dom('*').hasText('4'); }); test('cell format with no valuePath', async function (assert) { this.set( 'column', Column.create({ format() { return this.row.get('num') * 2; }, }) ); this.set('row', Row.create({ content: { num: 2 } })); await render( hbs`{{light-table/cells/base column=this.column row=this.row}}` ); assert.dom('*').hasText('4'); }); test('cell with nested valuePath', async function (assert) { this.set( 'column', Column.create({ valuePath: 'foo.bar.baz', format(value) { return value * 2; }, }) ); this.set( 'row', Row.create({ content: { foo: { bar: { baz: 2, }, }, }, }) ); await render( hbs` {{light-table/cells/base column=this.column row=this.row rawValue=(get this.row this.column.valuePath)}} ` ); assert.dom('*').hasText('4'); run(() => this.row.set(this.column.get('valuePath'), 4)); assert.dom('*').hasText('8'); }); }); ================================================ FILE: tests/integration/components/light-table/columns/base-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { Column } from 'ember-light-table'; module('Integration | Component | Columns | base', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... });" this.set('column', Column.create()); await render(hbs`{{light-table/columns/base column=this.column}}`); assert.dom('*').hasText(''); }); }); ================================================ FILE: tests/integration/components/light-table-occlusion-test.js ================================================ import { setupRenderingTest } from 'ember-qunit'; import { render, findAll, find, click, triggerEvent, } from '@ember/test-helpers'; import { module, test } from 'qunit'; import hbs from 'htmlbars-inline-precompile'; import { setupMirage } from 'ember-cli-mirage/test-support'; import Table from 'ember-light-table'; import Columns from '../../helpers/table-columns'; import hasClass from '../../helpers/has-class'; import RowComponent from 'ember-light-table/components/lt-row'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { run } from '@ember/runloop'; import registerWaiter from 'ember-raf-scheduler/test-support/register-waiter'; module('Integration | Component | light table | occlusion', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(function () { registerWaiter(); this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); }); test('it renders', async function (assert) { this.set('table', Table.create()); await render( hbs`{{light-table table=this.table height="40vh" occlusion=true estimatedRowHeight=30}}` ); assert.dom('*').hasText(''); }); test('scrolled to bottom', async function (assert) { assert.expect(4); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 50), }) ); this.set('onScrolledToBottom', () => { assert.ok(true); }); await render(hbs` {{#light-table table=this.table height='40vh' occlusion=true estimatedRowHeight=30 as |t|}} {{t.head fixed=true}} {{t.body onScrolledToBottom=this.onScrolledToBottom}} {{/light-table}} `); assert.ok( findAll('.vertical-collection tbody.lt-body tr.lt-row').length < 30, 'only some rows are rendered' ); let scrollContainer = find( '.lt-scrollable.tse-scrollable.vertical-collection' ); assert.ok(scrollContainer, 'scroll container was rendered'); let { scrollHeight } = scrollContainer; assert.ok( scrollHeight > 1500, 'scroll height is 50 rows * 30 px per row + header size' ); scrollContainer.scrollTop = scrollHeight; await triggerEvent(scrollContainer, 'scroll'); }); test('fixed header', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.head fixed=true}} {{t.body}} {{/light-table}} `); assert.dom('#lightTable_inline_head thead').doesNotExist(); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.head fixed=false}} {{t.body}} {{/light-table}} `); assert.dom('#lightTable_inline_head thead').exists({ count: 1 }); }); test('fixed footer', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.body}} {{t.foot fixed=true}} {{/light-table}} `); assert.dom('#lightTable_inline_foot tfoot').doesNotExist(); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.body}} {{t.foot fixed=false}} {{/light-table}} `); assert.dom('#lightTable_inline_foot tfoot').exists({ count: 1 }); }); test('table assumes height of container', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); this.set('fixed', true); await render(hbs`
{{#light-table table=this.table id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.body}} {{t.foot fixed=this.fixed}} {{/light-table}}
`); assert.strictEqual( find('#lightTable').offsetHeight, 500, 'table is 500px height' ); }); test('table body should consume all available space when not enough content to fill it', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.set('fixed', true); await render(hbs`
{{#light-table table=this.table id='lightTable' occlusion=true estimatedRowHeight=30 as |t|}} {{t.head fixed=true}} {{t.body}} {{#t.foot fixed=true}} Hello World {{/t.foot}} {{/light-table}}
`); const bodyHeight = find('.lt-body-wrap').offsetHeight; const headHeight = find('.lt-head-wrap').offsetHeight; const footHeight = find('.lt-foot-wrap').offsetHeight; assert.strictEqual( bodyHeight + headHeight + footHeight, 500, 'combined table content is 500px tall' ); assert.ok(bodyHeight > headHeight + footHeight, 'body is tallest element'); }); test('accepts components that are used in the body', async function (assert) { this.owner.register('component:custom-row', RowComponent); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); await render(hbs` {{#light-table table=this.table occlusion=true estimatedRowHeight=30 as |t|}} {{t.body rowComponent=(component "custom-row" classNames="custom-row")}} {{/light-table}} `); assert .dom('.lt-row.custom-row') .exists({ count: 1 }, 'row has custom-row class'); }); test('passed in components can have computed properties', async function (assert) { this.owner.register( 'component:custom-row', RowComponent.extend({ classNameBindings: ['isActive'], current: null, isActive: computed('row.content', 'current', function () { return this.row.content === this.current; }), }) ); let users = this.server.createList('user', 3); this.set('table', Table.create({ columns: Columns, rows: users })); await render(hbs` {{#light-table table=this.table height='500px' occlusion=true estimatedRowHeight=30 as |t|}} {{t.body rowComponent=(component "custom-row" classNames="custom-row" current=this.current) }} {{/light-table}} `); assert .dom('.custom-row') .exists({ count: 3 }, 'three custom rows were rendered'); assert .dom('.custom-row.is-active') .doesNotExist('none of the items are active'); run(() => { this.set('current', users[0]); }); let firstRow = find('.custom-row:nth-child(2)'); assert.ok(hasClass(firstRow, 'is-active'), 'first custom row is active'); run(() => { this.set('current', users[2]); }); let thirdRow = find('.custom-row:nth-child(4)'); assert.ok(hasClass(thirdRow, 'is-active'), 'third custom row is active'); run(() => { this.set('current', null); }); assert .dom('.custom-row.is-active') .doesNotExist('none of the items are active'); }); test('extra data and tableActions', async function (assert) { assert.expect(4); this.owner.register( 'component:some-component', Component.extend({ classNames: 'some-component', didReceiveAttrs() { this._super(); assert.strictEqual( this.extra.someData, 'someValue', 'extra data is passed' ); }, click() { this.tableActions.someAction(); }, }) ); const columns = [ { component: 'some-component', cellComponent: 'some-component', }, ]; this.set('table', Table.create({ columns, rows: [{}] })); this.actions.someAction = () => { assert.ok(true, 'table action is passed'); }; await render(hbs` {{#light-table table=this.table occlusion=true estimatedRowHeight=30 extra=(hash someData="someValue") tableActions=(hash someAction=(action "someAction") ) as |t| }} {{t.head}} {{t.body}} {{/light-table}} `); /* eslint-disable no-unused-vars */ for (const element of findAll('.some-component')) { await click(element); } }); }); ================================================ FILE: tests/integration/components/light-table-test.js ================================================ import { setupRenderingTest } from 'ember-qunit'; import { render, findAll, find, click, triggerEvent, } from '@ember/test-helpers'; import { module, test } from 'qunit'; import hbs from 'htmlbars-inline-precompile'; import { setupMirage } from 'ember-cli-mirage/test-support'; import Table from 'ember-light-table'; import Columns, { ResizableColumns } from '../../helpers/table-columns'; import hasClass from '../../helpers/has-class'; import RowComponent from 'ember-light-table/components/lt-row'; import Component from '@ember/component'; import { computed } from '@ember/object'; module('Integration | Component | light table', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(function () { this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); }); test('it renders', async function (assert) { this.set('table', Table.create()); await render(hbs`{{light-table table=this.table}}`); assert.dom('*').hasText(''); }); test('scrolled to bottom', async function (assert) { assert.expect(4); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 50), }) ); this.set('onScrolledToBottom', () => { assert.ok(true); }); await render(hbs` {{#light-table table=this.table height='40vh' as |t|}} {{t.head fixed=true}} {{t.body onScrolledToBottom=this.onScrolledToBottom}} {{/light-table}} `); assert.dom('tbody > tr').exists({ count: 50 }, '50 rows are rendered'); let scrollContainer = find('.tse-scroll-content'); assert.ok(scrollContainer, 'scroll container was rendered'); let expectedScroll = 2501; assert.strictEqual( scrollContainer.scrollHeight, expectedScroll, 'scroll height is 2500 + 1px for height of lt-infinity' ); scrollContainer.scrollTop = expectedScroll; await triggerEvent(scrollContainer, 'scroll'); }); test('scrolled to bottom (multiple tables)', async function (assert) { assert.expect(4); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 50), }) ); this.set('onScrolledToBottomTable1', () => { assert.ok(false); }); this.set('onScrolledToBottomTable2', () => { assert.ok(true); }); await render(hbs` {{#light-table table=this.table height='40vh' id='table-1' as |t|}} {{t.head fixed=true}} {{t.body onScrolledToBottom=this.onScrolledToBottomTable1}} {{/light-table}} {{#light-table table=this.table height='40vh' id='table-2' as |t|}} {{t.head fixed=true}} {{t.body onScrolledToBottom=this.onScrolledToBottomTable2}} {{/light-table}} `); assert .dom('#table-2 tbody > tr') .exists({ count: 50 }, '50 rows are rendered'); let scrollContainer = find('#table-2 .tse-scroll-content'); assert.ok(scrollContainer, 'scroll container was rendered'); let expectedScroll = 2501; assert.strictEqual( scrollContainer.scrollHeight, expectedScroll, 'scroll height is 2500 + 1px for height of lt-infinity' ); scrollContainer.scrollTop = expectedScroll; await triggerEvent(scrollContainer, 'scroll'); }); test('fixed header', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' as |t|}} {{t.head fixed=true}} {{t.body}} {{/light-table}} `); assert.dom('#lightTable_inline_head thead').doesNotExist(); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' as |t|}} {{t.head fixed=false}} {{t.body}} {{/light-table}} `); assert.dom('#lightTable_inline_head thead').exists({ count: 1 }); }); test('fixed footer', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' as |t|}} {{t.body}} {{t.foot fixed=true}} {{/light-table}} `); assert.dom('#lightTable_inline_foot tfoot').doesNotExist(); await render(hbs` {{#light-table table=this.table height='500px' id='lightTable' as |t|}} {{t.body}} {{t.foot fixed=false}} {{/light-table}} `); assert.dom('#lightTable_inline_foot tfoot').exists({ count: 1 }); }); test('table assumes height of container', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); this.set('fixed', true); await render(hbs`
{{#light-table table=this.table id='lightTable' as |t|}} {{t.body}} {{t.foot fixed=this.fixed}} {{/light-table}}
`); assert.strictEqual( find('#lightTable').offsetHeight, 500, 'table is 500px height' ); }); test('table body should consume all available space when not enough content to fill it', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.set('fixed', true); await render(hbs`
{{#light-table table=this.table id='lightTable' as |t|}} {{t.head fixed=true}} {{t.body}} {{#t.foot fixed=true}} Hello World {{/t.foot}} {{/light-table}}
`); const bodyHeight = find('.lt-body-wrap').offsetHeight; const headHeight = find('.lt-head-wrap').offsetHeight; const footHeight = find('.lt-foot-wrap').offsetHeight; assert.strictEqual( bodyHeight + headHeight + footHeight, 500, 'combined table content is 500px tall' ); assert.ok(bodyHeight > headHeight + footHeight, 'body is tallest element'); }); test('accepts components that are used in the body', async function (assert) { this.owner.register('component:custom-row', RowComponent); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); await render(hbs` {{#light-table table=this.table as |t|}} {{t.body rowComponent=(component "custom-row" classNames="custom-row")}} {{/light-table}} `); assert .dom('.lt-row.custom-row') .exists({ count: 1 }, 'row has custom-row class'); }); test('passed in components can have computed properties', async function (assert) { this.owner.register( 'component:custom-row', RowComponent.extend({ classNameBindings: ['isActive'], current: null, isActive: computed('row.content', 'current', function () { return this.row?.content === this.current; }), }) ); let users = this.server.createList('user', 3); this.set('table', Table.create({ columns: Columns, rows: users })); await render(hbs` {{#light-table table=this.table as |t|}} {{t.body rowComponent=(component "custom-row" classNames="custom-row" current=this.current) }} {{/light-table}} `); assert .dom('.custom-row') .exists({ count: 3 }, 'three custom rows were rendered'); assert .dom('.custom-row.is-active') .doesNotExist('none of the items are active'); this.set('current', users[0]); let [firstRow] = findAll('.custom-row'); assert.ok(hasClass(firstRow, 'is-active'), 'first custom row is active'); this.set('current', users[2]); let thirdRow = find('.custom-row:nth-child(3)'); assert.ok(hasClass(thirdRow, 'is-active'), 'third custom row is active'); this.set('current', null); assert .dom('.custom-row.is-active') .doesNotExist('none of the items are active'); }); test('onScroll', async function (assert) { let table = Table.create({ columns: Columns, rows: this.server.createList('user', 10), }); let expectedScroll = 50; this.setProperties({ table, onScroll(actualScroll) { assert.ok(true, 'onScroll worked'); assert.strictEqual( actualScroll, expectedScroll, 'scroll position is correct' ); }, }); await render(hbs` {{#light-table table=this.table height='40vh' as |t|}} {{t.head fixed=true}} {{t.body useVirtualScrollbar=true onScroll=this.onScroll }} {{/light-table}} `); let scrollContainer = find('.tse-scroll-content'); scrollContainer.scrollTop = expectedScroll; await triggerEvent(scrollContainer, 'scroll'); }); test('extra data and tableActions', async function (assert) { assert.expect(4); this.owner.register( 'component:some-component', Component.extend({ classNames: 'some-component', didReceiveAttrs() { this._super(); assert.strictEqual( this.extra.someData, 'someValue', 'extra data is passed' ); }, click() { this.tableActions.someAction(); }, }) ); const columns = [ { component: 'some-component', cellComponent: 'some-component', }, ]; this.set('table', Table.create({ columns, rows: [{}] })); this.actions.someAction = () => { assert.ok(true, 'table action is passed'); }; await render(hbs` {{#light-table table=this.table extra=(hash someData="someValue") tableActions=(hash someAction=(action "someAction") ) as |t| }} {{t.head}} {{t.body}} {{/light-table}} `); /* eslint-disable no-unused-vars */ for (const element of findAll('.some-component')) { await click(element); } }); test('dragging resizes columns', async function (assert) { let table = Table.create({ columns: ResizableColumns, rows: this.server.createList('user', 10), }); this.setProperties({ table }); await render(hbs` {{#light-table table=this.table height='40vh' as |t|}} {{t.head fixed=true}} {{t.body}} {{/light-table}} `); let ths = this.element.querySelectorAll('th.is-resizable'); assert.strictEqual(ths.length, 5); }); }); ================================================ FILE: tests/integration/components/lt-body-occlusion-test.js ================================================ import { click, find, triggerEvent, settled, render, } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import { setupMirage } from 'ember-cli-mirage/test-support'; import Table from 'ember-light-table'; import hasClass from '../../helpers/has-class'; import Columns from '../../helpers/table-columns'; import { run } from '@ember/runloop'; import registerWaiter from 'ember-raf-scheduler/test-support/register-waiter'; module('Integration | Component | lt body | occlusion', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(function () { this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); registerWaiter(); }); hooks.beforeEach(function () { this.set('sharedOptions', { fixedHeader: false, fixedFooter: false, height: '500px', occlusion: true, estimatedRowHeight: 30, }); }); test('it renders', async function (assert) { await render(hbs`{{lt-body sharedOptions=this.sharedOptions}}`); assert.dom('*').hasText(''); }); test('row selection - enable or disable', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.set('canSelect', false); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions canSelect=this.canSelect }}` ); let row = find('tr'); assert.notOk(hasClass(row, 'is-selectable')); assert.notOk(hasClass(row, 'is-selected')); await click(row); assert.notOk(hasClass(row, 'is-selected')); this.set('canSelect', true); assert.ok(hasClass(row, 'is-selectable')); assert.notOk(hasClass(row, 'is-selected')); await click(row); assert.ok(hasClass(row, 'is-selected')); }); test('row selection - ctrl-click to modify selection', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render( hbs` {{lt-body table=this.table scrollBuffer=200 sharedOptions=this.sharedOptions canSelect=true multiSelect=true }}` ); let firstRow = find('tr:nth-child(2)'); let middleRow = find('tr:nth-child(4)'); let lastRow = find('tr:nth-child(6)'); assert.dom('tbody tr').exists({ count: 5 }); await click(firstRow); assert .dom('tr.is-selected') .exists({ count: 1 }, 'clicking a row selects it'); await click(lastRow, { shiftKey: true }); assert .dom('tr.is-selected') .exists( { count: 5 }, 'shift-clicking another row selects it and all rows between' ); await click(middleRow, { ctrlKey: true }); assert .dom('tr.is-selected') .exists({ count: 4 }, 'ctrl-clicking a selected row deselects it'); await click(firstRow); assert .dom('tr.is-selected') .doesNotExist('clicking a selected row deselects all rows'); }); test('row selection - click to modify selection', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions canSelect=true multiSelect=true multiSelectRequiresKeyboard=false }}` ); let firstRow = find('tr:nth-child(2)'); let middleRow = find('tr:nth-child(4)'); let lastRow = find('tr:nth-child(6)'); assert.dom('tbody tr').exists({ count: 5 }); await click(firstRow); assert .dom('tr.is-selected') .exists({ count: 1 }, 'clicking a row selects it'); await click(lastRow, { shiftKey: true }); assert .dom('tr.is-selected') .exists( { count: 5 }, 'shift-clicking another row selects it and all rows between' ); await click(middleRow); assert .dom('tr.is-selected') .exists( { count: 4 }, 'clicking a selected row deselects it without affecting other selected rows' ); await click(middleRow); assert .dom('tr.is-selected') .exists( { count: 5 }, 'clicking a deselected row selects it without affecting other selected rows' ); }); test('row actions', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.actions.onRowClick = (row) => assert.ok(row); this.actions.onRowDoubleClick = (row) => assert.ok(row); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions onRowClick=(action 'onRowClick') onRowDoubleClick=(action 'onRowDoubleClick') }}` ); let row = find('tr'); await click(row); await triggerEvent(row, 'dblclick'); }); test('hidden rows', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions }}`); assert.dom('tbody tr').exists({ count: 5 }); run(() => { this.table.rows.objectAt(0).set('hidden', true); this.table.rows.objectAt(1).set('hidden', true); }); await settled(); assert.dom('tbody tr').exists({ count: 3 }); run(() => { this.table.rows.objectAt(0).set('hidden', false); }); await settled(); assert.dom('tbody tr').exists({ count: 4 }); }); test('overwrite', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#lt-body table=this.table sharedOptions=this.sharedOptions overwrite=true as |columns rows| }} {{columns.length}}, {{rows.length}} {{/lt-body}} `); assert.dom('*').hasText('6, 5'); }); }); ================================================ FILE: tests/integration/components/lt-body-test.js ================================================ import { click, findAll, find, triggerEvent, render, } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import { setupMirage } from 'ember-cli-mirage/test-support'; import Table from 'ember-light-table'; import hasClass from '../../helpers/has-class'; import Columns from '../../helpers/table-columns'; import { run } from '@ember/runloop'; import { all } from 'rsvp'; module('Integration | Component | lt body', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); hooks.beforeEach(function () { this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); }); hooks.beforeEach(function () { this.set('sharedOptions', { fixedHeader: false, fixedFooter: false, }); }); test('it renders', async function (assert) { await render( hbs`{{lt-body sharedOptions=this.sharedOptions tableId="light-table"}}` ); assert.dom('*').hasText(''); }); test('row selection - enable or disable', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.set('canSelect', false); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions canSelect=this.canSelect tableId="light-table" }}` ); let row = find('tr'); assert.notOk(hasClass(row, 'is-selectable')); assert.notOk(hasClass(row, 'is-selected')); await click(row); assert.notOk(hasClass(row, 'is-selected')); this.set('canSelect', true); assert.ok(hasClass(row, 'is-selectable')); assert.notOk(hasClass(row, 'is-selected')); await click(row); assert.ok(hasClass(row, 'is-selected')); }); test('row selection - ctrl-click to modify selection', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions canSelect=true multiSelect=true tableId="light-table" }}` ); let firstRow = find('tr:first-child'); let middleRow = find('tr:nth-child(4)'); let lastRow = find('tr:last-child'); assert.dom('tbody > tr').exists({ count: 5 }); await click(firstRow); assert .dom('tr.is-selected') .exists({ count: 1 }, 'clicking a row selects it'); await click(lastRow, { shiftKey: true }); assert .dom('tr.is-selected') .exists( { count: 5 }, 'shift-clicking another row selects it and all rows between' ); await click(middleRow, { ctrlKey: true }); assert .dom('tr.is-selected') .exists({ count: 4 }, 'ctrl-clicking a selected row deselects it'); await click(firstRow); assert .dom('tr.is-selected') .doesNotExist('clicking a selected row deselects all rows'); }); test('row selection - click to modify selection', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions canSelect=true multiSelect=true multiSelectRequiresKeyboard=false tableId="light-table" }}` ); let firstRow = find('tr:first-child'); let middleRow = find('tr:nth-child(4)'); let lastRow = find('tr:last-child'); assert.dom('tbody > tr').exists({ count: 5 }); await click(firstRow); assert .dom('tr.is-selected') .exists({ count: 1 }, 'clicking a row selects it'); await click(lastRow, { shiftKey: true }); assert .dom('tr.is-selected') .exists( { count: 5 }, 'shift-clicking another row selects it and all rows between' ); await click(middleRow); assert .dom('tr.is-selected') .exists( { count: 4 }, 'clicking a selected row deselects it without affecting other selected rows' ); await click(middleRow); assert .dom('tr.is-selected') .exists( { count: 5 }, 'clicking a deselected row selects it without affecting other selected rows' ); }); test('row expansion', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 2), }) ); this.set('canExpand', false); await render(hbs` {{#lt-body table=this.table sharedOptions=this.sharedOptions canSelect=false canExpand=this.canExpand multiRowExpansion=false tableId="light-table" as |b| }} {{#b.expanded-row}} Hello {{/b.expanded-row}} {{/lt-body}} `); let row = find('tr'); assert.notOk(hasClass(row, 'is-expandable')); await click(row); assert.dom('tr.lt-expanded-row').doesNotExist(); assert.dom('tbody > tr').exists({ count: 2 }); assert.dom('tr.lt-expanded-row').doesNotExist(); this.set('canExpand', true); assert.ok(hasClass(row, 'is-expandable')); await click(row); assert.dom('tr.lt-expanded-row').exists({ count: 1 }); assert.dom('tbody > tr').exists({ count: 3 }); assert.dom(row.nextElementSibling).hasText('Hello'); let allRows = findAll('tr'); row = allRows[allRows.length - 1]; assert.ok(hasClass(row, 'is-expandable')); await click(row); assert.dom('tr.lt-expanded-row').exists({ count: 1 }); assert.dom('tbody > tr').exists({ count: 3 }); assert.dom(row.nextElementSibling).hasText('Hello'); }); test('row expansion - multiple', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 2), }) ); await render(hbs` {{#lt-body table=this.table sharedOptions=this.sharedOptions canExpand=true tableId="light-table" as |b| }} {{#b.expanded-row}} Hello {{/b.expanded-row}} {{/lt-body}} `); let rows = findAll('tr'); assert.strictEqual(rows.length, 2); await all( rows.map(async (row) => { assert.ok(hasClass(row, 'is-expandable')); await click(row); assert.dom(row.nextElementSibling).hasText('Hello'); }) ); assert.dom('tr.lt-expanded-row').exists({ count: 2 }); }); test('row actions', async function (assert) { assert.expect(2); this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 1), }) ); this.actions.onRowClick = (row) => assert.ok(row); this.actions.onRowDoubleClick = (row) => assert.ok(row); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions onRowClick=(action 'onRowClick') onRowDoubleClick=(action 'onRowDoubleClick') tableId="light-table" }}` ); let row = find('tr'); await click(row); await triggerEvent(row, 'dblclick'); }); test('hidden rows', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions tableId="light-table" }}` ); assert.dom('tbody > tr').exists({ count: 5 }); run(() => { this.table.rows.objectAt(0).set('hidden', true); this.table.rows.objectAt(1).set('hidden', true); }); assert.dom('tbody > tr').exists({ count: 3 }); run(() => { this.table.rows.objectAt(0).set('hidden', false); }); assert.dom('tbody > tr').exists({ count: 4 }); }); test('scaffolding', async function (assert) { const users = this.server.createList('user', 1); this.set('table', Table.create({ columns: Columns, rows: users })); await render( hbs` {{lt-body table=this.table sharedOptions=this.sharedOptions enableScaffolding=true tableId="light-table" }}` ); const [scaffoldingRow, userRow] = findAll('tr'); const userCells = userRow.querySelectorAll('.lt-cell'); assert.ok( hasClass(scaffoldingRow, 'lt-scaffolding-row'), 'the first row of the is a scaffolding row' ); assert.notOk( hasClass(userRow, 'lt-scaffolding-row'), 'the second row of the is not a scaffolding row' ); assert.notOk( userRow.hasAttribute('style'), 'the second row of the has no `style` attribute' ); assert.ok( Columns.map((c, i) => { const configuredWidth = Number.parseInt(c.width, 10); const actualWidth = Number.parseInt(userCells[i].style.width); return configuredWidth ? configuredWidth === actualWidth : true; }).every(Boolean), 'the first actual data row has the correct widths assigned' ); }); test('overwrite', async function (assert) { this.set( 'table', Table.create({ columns: Columns, rows: this.server.createList('user', 5), }) ); await render(hbs` {{#lt-body table=this.table sharedOptions=this.sharedOptions overwrite=true tableId="light-table" as |columns rows| }} {{columns.length}}, {{rows.length}} {{/lt-body}} `); assert.dom('*').hasText('6, 5'); }); }); ================================================ FILE: tests/integration/components/lt-column-resizer-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt column resizer', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... }); await render(hbs`{{lt-column-resizer}}`); assert.dom('*').hasText(''); // Template block usage: await render(hbs` {{#lt-column-resizer}} template block text {{/lt-column-resizer}} `); assert.dom('*').hasText('template block text'); }); }); ================================================ FILE: tests/integration/components/lt-foot-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt foot', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... });" await render(hbs`{{lt-foot renderInPlace=true}}`); assert.dom('*').hasText(''); // Template block usage:" await render(hbs` {{#lt-foot renderInPlace=true}} template block text {{/lt-foot}} `); assert.dom('*').hasText('template block text'); }); }); ================================================ FILE: tests/integration/components/lt-head-test.js ================================================ import { triggerEvent, click, find, findAll, render, } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import Table from 'ember-light-table'; import Columns, { GroupedColumns } from '../../helpers/table-columns'; import hasClass from '../../helpers/has-class'; import Component from '@ember/component'; import { isPresent } from '@ember/utils'; module('Integration | Component | lt head', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); }); test('render columns', async function (assert) { this.set('table', Table.create({ columns: Columns })); await render(hbs` {{lt-head table=this.table renderInPlace=true }}`); assert.dom('tr > th').exists({ count: 6 }); }); test('render grouped columns', async function (assert) { this.set('table', Table.create({ columns: GroupedColumns })); await render(hbs`{{lt-head table=this.table renderInPlace=true}}`); assert.dom('tr:nth-child(2) > th').hasAttribute('colspan', '3'); assert.dom('tr:nth-child(2) > th').hasClass('lt-group-column'); assert.dom('tr').exists({ count: 3 }); assert.dom('tr > th').exists({ count: 8 }); }); test('click - non-sortable column', async function (assert) { this.set('table', Table.create({ columns: Columns })); this.set('onColumnClick', (column) => { assert.ok(column); assert.notOk(column.sortable); assert.strictEqual(column.label, 'Avatar'); }); await render( hbs` {{lt-head table=this.table renderInPlace=true onColumnClick=this.onColumnClick }}` ); assert.dom('tr > th').exists({ count: 6 }); let nonSortableHeader = find('tr > th'); click(nonSortableHeader); }); test('click - sortable column', async function (assert) { this.set('table', Table.create({ columns: Columns })); let asc = true; this.set('onColumnClick', (column) => { assert.ok(column); assert.ok(column.sortable); assert.ok(column.sorted); assert.strictEqual(column.label, 'Country'); assert.strictEqual(column.ascending, asc); }); await render( hbs` {{lt-head table=this.table renderInPlace=true onColumnClick=this.onColumnClick }}` ); let allHeaders = findAll('tr > th'); let sortableHeader = allHeaders[allHeaders.length - 1]; assert.dom('tr > th').exists({ count: 6 }); await click(sortableHeader); asc = false; await click(sortableHeader); }); test('render sort icons', async function (assert) { this.set('table', Table.create({ columns: Columns })); await render( hbs` {{lt-head table=this.table renderInPlace=true iconSortable='fa-sort' iconAscending='fa-sort-up' iconDescending='fa-sort-down' }}` ); const allHeaders = findAll('tr > th'); const sortableHeader = allHeaders[allHeaders.length - 1]; const sortIcon = sortableHeader.querySelector('.lt-sort-icon'); // Sortable case assert.ok(hasClass(sortIcon, 'fa-sort'), 'Sortable icon renders'); assert.notOk(hasClass(sortIcon, 'fa-sort-up')); assert.notOk(hasClass(sortIcon, 'fa-sort-down')); await click(sortableHeader); // Ascending case assert.ok(hasClass(sortIcon, 'fa-sort-up'), 'Ascending icon renders'); assert.notOk(hasClass(sortIcon, 'fa-sort')); assert.notOk(hasClass(sortIcon, 'fa-sort-down')); await click(sortableHeader); // Descending case assert.ok(hasClass(sortIcon, 'fa-sort-down'), 'Descending icon renders'); assert.notOk(hasClass(sortIcon, 'fa-sort')); assert.notOk(hasClass(sortIcon, 'fa-sort-up')); }); test('custom iconComponent has arguments', async function (assert) { const sortableColumns = Columns.filter((column) => { return column.sortable !== false; }); assert.expect(6 * sortableColumns.length); const iconSortable = 'unfold_more'; const iconAscending = 'fa-sort-up'; const iconDescending = 'fa-sort-down'; const iconComponent = 'custom-icon-component'; this.setProperties({ iconSortable, iconAscending, iconDescending, iconComponent, table: Table.create({ columns: Columns }), }); this.owner.register( `component:${iconComponent}`, Component.extend({ init() { this._super(...arguments); assert.ok(isPresent(this.sortIconProperty)); assert.ok(isPresent(this.sortIcons)); assert.strictEqual(this.sortIcons.iconSortable, iconSortable); assert.strictEqual(this.sortIcons.iconAscending, iconAscending); assert.strictEqual(this.sortIcons.iconDescending, iconDescending); assert.strictEqual(this.sortIcons.iconComponent, iconComponent); }, }) ); await render( hbs` {{lt-head table=this.table renderInPlace=true iconSortable=this.iconSortable iconAscending=this.iconAscending iconDescending=this.iconDescending iconComponent=this.iconComponent }}` ); }), test('double click', async function (assert) { assert.expect(4); this.set('table', Table.create({ columns: Columns })); this.set('onColumnDoubleClick', (column) => { assert.ok(column); assert.notOk(column.sortable); assert.strictEqual(column.label, 'Avatar'); }); await render( hbs` {{lt-head table=this.table renderInPlace=true onColumnDoubleClick=(action this.onColumnDoubleClick) }}` ); const allHeaders = findAll('tr > th'); const [avatarHeader] = allHeaders; assert.strictEqual(allHeaders.length, 6); await triggerEvent(avatarHeader, 'dblclick'); }); }); ================================================ FILE: tests/integration/components/lt-infinity-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt infinity', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { this.enterViewport = () => {}; this.exitViewport = () => {}; await render( hbs`{{lt-infinity enterViewport=this.enterViewport exitViewport=this.exitViewport}}` ); assert.dom('*').hasText(''); }); }); ================================================ FILE: tests/integration/components/lt-row-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt row', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... });" await render(hbs`{{lt-row}}`); assert.dom('*').hasText(''); }); }); ================================================ FILE: tests/integration/components/lt-scaffolding-row-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render, findAll } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt-scaffolding-row', function (hooks) { setupRenderingTest(hooks); test('it has lt-scaffolding-row class', async function (assert) { await render(hbs`{{lt-scaffolding-row}}`); assert.dom('.lt-scaffolding-row').exists(); }); test('it renders ', async function (assert) { await render(hbs`{{lt-scaffolding-row}}`); assert.dom('tr').exists(); }); test('it renders for each column', async function (assert) { const columns = [ { label: 'Avatar', valuePath: 'avatar', width: '60px', sortable: false, cellComponent: 'user-avatar', }, { label: 'First Name', valuePath: 'firstName', width: '150px', }, { label: 'Last Name', valuePath: 'lastName', width: '150px', }, { label: 'Address', valuePath: 'address', }, { label: 'State', valuePath: 'state', }, { label: 'Country', valuePath: 'country', }, ]; this.set('columns', columns); await render(hbs`{{lt-scaffolding-row columns=this.columns}}`); assert.strictEqual(findAll('td').length, columns.length); }); }); ================================================ FILE: tests/integration/components/lt-scrollable-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt scrollable', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with this.on('myAction', function(val) { ... }); await render(hbs`{{lt-scrollable}}`); assert.dom(this.element).hasText(''); // Template block usage: await render(hbs` {{#lt-scrollable}} template block text {{/lt-scrollable}} `); assert.dom(this.element).hasText('template block text'); }); }); ================================================ FILE: tests/integration/components/lt-spanned-row-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Component | lt spanned row', function (hooks) { setupRenderingTest(hooks); test('it renders', async function (assert) { await render(hbs`{{lt-spanned-row}}`); assert.dom(this.element).hasText(''); await render(hbs` {{#lt-spanned-row}} template block text {{/lt-spanned-row}} `); assert.dom(this.element).hasText('template block text'); }); test('visiblity', async function (assert) { this.set('visible', true); await render(hbs` {{#lt-spanned-row visible=this.visible}} template block text {{/lt-spanned-row}} `); assert.dom(this.element).hasText('template block text'); this.set('visible', false); assert.dom(this.element).hasText(''); }); test('colspan', async function (assert) { await render(hbs` {{#lt-spanned-row colspan=4}} template block text {{/lt-spanned-row}} `); assert.dom(this.element).hasText('template block text'); assert.dom('td').hasAttribute('colspan', '4'); }); test('yield', async function (assert) { await render(hbs` {{#lt-spanned-row yield=(hash name="Offir") as |row|}} {{row.name}} {{/lt-spanned-row}} `); assert.dom(this.element).hasText('Offir'); }); }); ================================================ FILE: tests/integration/helpers/compute-test.js ================================================ // https://github.com/DockYard/ember-composable-helpers/blob/master/tests/integration/helpers/compute-test.js import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Integration | Helper | {{compute}}', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { this.actions = {}; this.send = (actionName, ...args) => this.actions[actionName].apply(this, args); }); test("It calls an action and returns it's value", async function (assert) { this.actions.square = (x) => x * x; await render(hbs`{{compute (action "square") 4}}`); assert.dom(this.element).hasText('16', '4 squared is 16'); }); }); ================================================ FILE: tests/integration/helpers/html-safe-test.js ================================================ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; module('Integration | Helper | html-safe', function (hooks) { setupRenderingTest(hooks); test('it renders styles', async function (assert) { this.set('inputValue', 21); await render(hbs`{{html-safe (concat "width: " this.inputValue)}}`); assert.dom(this.element).hasText('width: 21'); }); test('it renders classes', async function (assert) { this.set('inputValue', 'red-outline'); await render( hbs`` ); assert.dom('button').hasClass('red-outline'); }); }); ================================================ FILE: tests/test-helper.js ================================================ import Application from 'dummy/app'; import config from 'dummy/config/environment'; import * as QUnit from 'qunit'; import { setApplication } from '@ember/test-helpers'; import { setup } from 'qunit-dom'; import { start } from 'ember-qunit'; setApplication(Application.create(config.APP)); setup(QUnit.assert); start(); ================================================ FILE: tests/unit/.gitkeep ================================================ ================================================ FILE: tests/unit/classes/column-test.js ================================================ import { Column } from 'ember-light-table'; import { module, test } from 'qunit'; module('Unit | Classes | Column', function () { test('create column - default options', function (assert) { let col = Column.create(); assert.ok(col); assert.false(col.hidden); assert.true(col.ascending); assert.true(col.sortable); assert.false(col.sorted); assert.false(col.sorted); assert.strictEqual(col.label, ''); assert.deepEqual(col.subColumns, []); assert.strictEqual(col.component, null); assert.strictEqual(col.cellComponent, null); assert.strictEqual(col.valuePath, null); assert.strictEqual(col.width, null); }); test('CP - isGroupColumn', function (assert) { let col = Column.create(); assert.ok(col); assert.deepEqual(col.subColumns, []); assert.false(col.get('isGroupColumn')); col.set('subColumns', [Column.create()]); assert.strictEqual(col.subColumns.length, 1); assert.true(col.get('isGroupColumn')); }); test('CP - isVisibleGroupColumn', function (assert) { let col = Column.create({ subColumns: [{}, {}], }); assert.ok(col); assert.strictEqual(col.subColumns.length, 2); assert.true(col.get('isVisibleGroupColumn')); col.set('hidden', true); assert.false(col.get('isVisibleGroupColumn')); col.set('hidden', false); assert.true(col.get('isVisibleGroupColumn')); col.subColumns.setEach('hidden', true); assert.false(col.get('isVisibleGroupColumn')); }); test('CP - visibleSubColumns', function (assert) { let col = Column.create({ subColumns: [{}, {}], }); assert.ok(col); assert.strictEqual(col.subColumns.length, 2); assert.strictEqual(col.get('visibleSubColumns.length'), 2); col.subColumns[0].set('hidden', true); assert.strictEqual(col.get('visibleSubColumns.length'), 1); col.set('hidden', true); assert.strictEqual(col.get('visibleSubColumns.length'), 0); }); test('subColumns / parent', function (assert) { let col = Column.create({ subColumns: [{}], }); assert.ok(col); assert.strictEqual(col.subColumns.length, 1); assert.strictEqual(col.subColumns[0].get('parent'), col); }); }); ================================================ FILE: tests/unit/classes/row-test.js ================================================ import { Row } from 'ember-light-table'; import { module, test } from 'qunit'; module('Unit | Classes | Row', function () { test('create row - default options', function (assert) { let row = Row.create(); assert.ok(row); assert.false(row.expanded); assert.false(row.selected); }); }); ================================================ FILE: tests/unit/classes/table-test.js ================================================ /* eslint-disable ember/use-ember-data-rfc-395-imports */ import { A as emberArray } from '@ember/array'; import { Table, Column, Row } from 'ember-light-table'; import { module, test } from 'qunit'; import DS from 'ember-data'; import EmberObject from '@ember/object'; module('Unit | Classes | Table', function () { test('create table - default options', function (assert) { let table = Table.create(); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); assert.strictEqual(table.get('columns.length'), 0); }); test('create table - with options', function (assert) { let table = Table.create({ columns: [{}, {}], rows: [{}] }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 1); assert.strictEqual(table.get('columns.length'), 2); assert.ok(table.rows[0] instanceof Row); assert.ok(table.columns[0] instanceof Column); }); test('create table - invalid constructor', function (assert) { assert.expect(2); assert.throws( () => { Table.create({ columns: [{}, {}], rows: null }); }, /\[ember-light-table] rows must be an array if defined/, 'rows is not an array' ); assert.throws( () => { Table.create({ columns: null, rows: [{}] }); }, /\[ember-light-table] columns must be an array if defined/, 'columns is not an array' ); }); test('create table - with RecordArray instance as rows', function (assert) { assert.expect(3); let models = ['Tom', 'Yehuda', 'Tomster'].map((name) => { return EmberObject.create({ name }); }); let rows = DS.RecordArray.create({ content: emberArray(models), objectAtContent(index) { return this.content[index]; }, }); let columns = [{ label: 'Name', valuePath: 'name' }]; let table = Table.create({ columns, rows }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 3); assert.strictEqual(table.get('columns.length'), 1); }); test('CP - visibleColumnGroups', function (assert) { let table = Table.create(); let col = Column.create(); let group = Column.create({ subColumns: [{}, {}], }); table.setColumns([col, group]); assert.strictEqual(table.get('visibleColumnGroups.length'), 2); col.set('hidden', true); assert.strictEqual(table.get('visibleColumnGroups.length'), 1); group.subColumns.setEach('hidden', true); assert.strictEqual(table.get('visibleColumnGroups.length'), 0); }); test('CP - visibleSubColumns', function (assert) { let table = Table.create(); let group = Column.create({ subColumns: [{}, {}], }); let group2 = Column.create({ subColumns: [{}, {}], }); table.setColumns([group, group2]); assert.strictEqual(table.get('visibleSubColumns.length'), 4); group.subColumns.setEach('hidden', true); assert.strictEqual(table.get('visibleSubColumns.length'), 2); }); test('CP - allColumns', function (assert) { let table = Table.create(); let col = Column.create(); let group = Column.create({ subColumns: [{}, {}], }); let group2 = Column.create({ subColumns: [{}, {}], }); table.setColumns([col, group, group2]); assert.strictEqual(table.get('allColumns.length'), 5); }); test('CP - isEmpty', function (assert) { let table = Table.create({ columns: [{}, {}], rows: [] }); assert.ok(table, 'table is set up correctly'); assert.ok(table.get('isEmpty'), 'table is initially empty'); table.pushRow({}); assert.notOk( table.get('isEmpty'), 'table is not empty after a row was pushed' ); table.setRows([]); assert.ok( table.get('isEmpty'), 'table is empty again after the rows were cleared' ); }); test('table method - setRows', function (assert) { let table = Table.create(); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.setRows([{}, {}], { selected: true }); assert.strictEqual(table.get('rows.length'), 2); assert.ok(table.get('rows').isEvery('selected', true)); table.setRows(); assert.strictEqual(table.get('rows.length'), 0); }); test('table method - addRow', function (assert) { let table = Table.create(); let content = { name: 'Offir' }; let row = Row.create({ content }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.addRow({}, { selected: true }); assert.strictEqual(table.get('rows.length'), 1); assert.ok(table.get('rows.firstObject.selected')); table.addRow(row); assert.strictEqual(table.get('rows.length'), 2); table.addRow(row); assert.strictEqual(table.get('rows.length'), 2); table.addRow(content); assert.strictEqual(table.get('rows.length'), 2); assert.strictEqual(table.get('rows.lastObject.name'), 'Offir'); }); test('table method - addRows', function (assert) { let table = Table.create(); let content = { name: 'Offir' }; let row = Row.create({ content }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.addRows([{}, {}], { selected: true }); assert.strictEqual(table.get('rows.length'), 2); assert.ok(table.get('rows').isEvery('selected', true)); table.addRows([row]); assert.strictEqual(table.get('rows.length'), 3); table.addRows([row]); assert.strictEqual(table.get('rows.length'), 3); table.addRows([content]); assert.strictEqual(table.get('rows.length'), 3); assert.strictEqual(table.get('rows.lastObject.name'), 'Offir'); }); test('table method - pushRow', function (assert) { let table = Table.create(); let content = { name: 'Offir' }; let row = Row.create({ content }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.addRow({}, { selected: true }); assert.strictEqual(table.get('rows.length'), 1); assert.ok(table.get('rows.firstObject.selected')); table.pushRow(row); assert.strictEqual(table.get('rows.length'), 2); table.pushRow(row); assert.strictEqual(table.get('rows.length'), 3); table.pushRow(content); assert.strictEqual(table.get('rows.length'), 4); assert.strictEqual(table.get('rows.lastObject.name'), 'Offir'); }); test('table method - pushRows', function (assert) { let table = Table.create(); let content = { name: 'Offir' }; let row = Row.create({ content }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.pushRows([{}, {}], { selected: true }); assert.strictEqual(table.get('rows.length'), 2); assert.ok(table.get('rows').isEvery('selected', true)); table.pushRows([row]); assert.strictEqual(table.get('rows.length'), 3); table.pushRows([row]); assert.strictEqual(table.get('rows.length'), 4); table.pushRows([content]); assert.strictEqual(table.get('rows.length'), 5); assert.strictEqual(table.get('rows.lastObject.name'), 'Offir'); }); test('table method - insertRowAt', function (assert) { let table = Table.create(); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.setRows([{}, {}, {}]); assert.strictEqual(table.get('rows.length'), 3); table.insertRowAt(1, { name: 'Offir' }, { selected: true }); assert.strictEqual(table.get('rows.length'), 4); assert.strictEqual(table.get('rows.1.name'), 'Offir'); assert.ok(table.get('rows.1.selected')); }); test('table method - removeRow', function (assert) { let table = Table.create(); let content = { name: 'Offir' }; let row = Row.create({ content }); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.addRow(row); assert.strictEqual(table.get('rows.length'), 1); table.removeRow(row); assert.strictEqual(table.get('rows.length'), 0); table.pushRows([row, row, row]); assert.strictEqual(table.get('rows.length'), 3); table.removeRow(row); assert.strictEqual(table.get('rows.length'), 0); table.pushRows([content, content, content]); assert.strictEqual(table.get('rows.length'), 3); table.removeRow(content); // I believe this fails because our content object is duplicated at some point during the set process // if this is an issue we'll need to find a different way to reliably identify duplicates assert.strictEqual(table.get('rows.length'), 0); }); test('table method - removeRows', function (assert) { let table = Table.create(); let row = Row.create(); let row2 = Row.create(); assert.ok(table); assert.strictEqual(table.get('rows.length'), 0); table.addRows([row, row2]); assert.strictEqual(table.get('rows.length'), 2); table.removeRows([row, row2]); assert.strictEqual(table.get('rows.length'), 0); }); test('table method - setColumns', function (assert) { let table = Table.create(); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.setColumns([{}, {}]); assert.strictEqual(table.get('columns.length'), 2); table.setColumns(); assert.strictEqual(table.get('columns.length'), 0); }); test('table method - addColumn', function (assert) { let table = Table.create(); let col = Column.create({ label: 'Name' }); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.addColumn({}); assert.strictEqual(table.get('columns.length'), 1); table.addColumn(col); assert.strictEqual(table.get('columns.length'), 2); table.addColumn(col); assert.strictEqual(table.get('columns.length'), 2); assert.strictEqual(table.get('columns.lastObject.label'), 'Name'); }); test('table method - addColumns', function (assert) { let table = Table.create(); let col = Column.create({ label: 'Name' }); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.addColumns([{}, {}]); assert.strictEqual(table.get('columns.length'), 2); table.addColumns([col]); assert.strictEqual(table.get('columns.length'), 3); table.addColumns([col]); assert.strictEqual(table.get('columns.length'), 3); assert.strictEqual(table.get('columns.lastObject.label'), 'Name'); }); test('table method - pushColumn', function (assert) { let table = Table.create(); let content = { label: 'Name' }; let col = Column.create(content); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.addColumn({}); assert.strictEqual(table.get('columns.length'), 1); table.pushColumn(col); assert.strictEqual(table.get('columns.length'), 2); table.pushColumn(col); assert.strictEqual(table.get('columns.length'), 3); table.pushColumn(content); assert.strictEqual(table.get('columns.length'), 4); assert.strictEqual(table.get('columns.lastObject.label'), 'Name'); }); test('table method - pushColumns', function (assert) { let table = Table.create(); let col = Column.create({ label: 'Name' }); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.pushColumns([{}, {}]); assert.strictEqual(table.get('columns.length'), 2); table.pushColumns([col]); assert.strictEqual(table.get('columns.length'), 3); table.pushColumns([col]); assert.strictEqual(table.get('columns.length'), 4); assert.strictEqual(table.get('columns.lastObject.label'), 'Name'); }); test('table method - insertColumnAt', function (assert) { let table = Table.create(); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.setColumns([{}, {}, {}]); assert.strictEqual(table.get('columns.length'), 3); table.insertColumnAt(1, { label: 'Offir' }); assert.strictEqual(table.get('columns.length'), 4); assert.strictEqual(table.get('columns.1.label'), 'Offir'); }); test('table method - removeColumn', function (assert) { let table = Table.create(); let col = Column.create({ label: 'Name' }); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.addColumn(col); assert.strictEqual(table.get('columns.length'), 1); table.removeColumn(col); assert.strictEqual(table.get('columns.length'), 0); table.pushColumns([col, col, col]); assert.strictEqual(table.get('columns.length'), 3); table.removeColumn(col); assert.strictEqual(table.get('columns.length'), 0); }); test('table method - removeColumns', function (assert) { let table = Table.create(); let col = Column.create(); let col2 = Column.create(); assert.ok(table); assert.strictEqual(table.get('columns.length'), 0); table.addColumns([col, col2]); assert.strictEqual(table.get('columns.length'), 2); table.removeColumns([col, col2]); assert.strictEqual(table.get('columns.length'), 0); }); test('static table method - createRow', function (assert) { let row = Table.createRow({ name: 'Offir' }, { selected: true }); assert.ok(row instanceof Row); assert.strictEqual(row.get('name'), 'Offir'); assert.ok(row.get('selected')); }); test('static table method - createRows', function (assert) { let rows = Table.createRows([{}, {}], { selected: true }); assert.strictEqual(rows.length, 2); assert.ok(rows[0] instanceof Row); assert.ok(rows[1].get('selected')); }); test('static table method - createColumn', function (assert) { let col = Table.createColumn({ label: 'Name' }); assert.ok(col instanceof Column); assert.strictEqual(col.get('label'), 'Name'); }); test('static table method - createColumns', function (assert) { let cols = Table.createColumns([{}, {}]); assert.strictEqual(cols.length, 2); assert.ok(cols[0] instanceof Column); }); test('table modifications - simple', function (assert) { let rows = emberArray([]); let table = Table.create({ columns: [], rows }); table.addRow({ firstName: 'Offir' }); assert.strictEqual(table.get('rows.length'), 1); table.addRow({ firstName: 'Taras' }); assert.strictEqual(table.get('rows.length'), 2); table.get('rows').clear(); assert.strictEqual(table.get('rows.length'), 0); }); test('table modifications - stress', function (assert) { let rows = emberArray([]); let table = Table.create({ columns: [], rows }); for (let i = 0; i < 100; i++) { table.addRow({ position: i }); } assert.strictEqual(table.get('rows.length'), 100); for (let i = 100; i < 200; i++) { table.addRow({ position: i }); } assert.strictEqual(table.get('rows.length'), 200); table.removeRowAt(5); table.removeRowAt(10); table.removeRowAt(125); assert.strictEqual(table.get('rows.length'), 197); table.removeRowAt(10); table.removeRowAt(20); table.removeRowAt(150); assert.strictEqual(table.get('rows.length'), 194); table.get('rows').clear(); assert.strictEqual(table.get('rows.length'), 0); }); test('table modifications - sort', function (assert) { let rows = emberArray([]); let table = Table.create({ columns: [], rows, enableSync: true }); let length = 5; for (let i = 0; i < length; i++) { table.addRow({ position: i }); } assert.strictEqual(table.get('rows.length'), 5); table.rows.sort((a, b) => { return a.get('position') > b.get('position') ? -1 : 1; }); assert.strictEqual(table.get('rows.length'), 5); table.rows.reverseObjects(); assert.strictEqual(table.get('rows.length'), 5); }); }); ================================================ FILE: tests/unit/mixins/table-header-test.js ================================================ import EmberObject from '@ember/object'; import TableHeaderMixin from 'ember-light-table/mixins/table-header'; import { module, test } from 'qunit'; module('Unit | Mixin | table header', function () { // Replace this with your real tests. test('it works', function (assert) { let TableHeaderObject = EmberObject.extend(TableHeaderMixin); let subject = TableHeaderObject.create(); assert.ok(subject); }); }); ================================================ FILE: yuidoc.json ================================================ { "name": "Ember Light Table", "description": "", "url": "https://github.com/adopted-ember-addons/ember-light-table", "options": { "enabledEnvironments": ["production"], "paths": [ "addon" ], "external": { "data": [{ "base": "http://emberjs.com/api/", "json": "http://builds.emberjs.com/tags/v2.0.0/ember-docs.json" }] }, "themedir": "node_modules/yuidoc-ember-theme", "helpers": ["node_modules/yuidoc-ember-theme/helpers/helpers.js"], "exclude": "vendor", "outdir": "docs", "linkNatives": true, "quiet": true, "parseOnly": false, "lint": false, "nocode": false } }