├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation.md │ ├── feature_request.md │ └── question.md └── workflows │ ├── bad-files-check.yml │ ├── lint-and-test.yml │ ├── playwright.yml │ └── unit-tests.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── bower.json ├── build ├── Bundler.mjs └── rollup.mjs ├── dist ├── css │ ├── tabulator.css │ ├── tabulator.css.map │ ├── tabulator.min.css │ ├── tabulator.min.css.map │ ├── tabulator_bootstrap3.css │ ├── tabulator_bootstrap3.css.map │ ├── tabulator_bootstrap3.min.css │ ├── tabulator_bootstrap3.min.css.map │ ├── tabulator_bootstrap4.css │ ├── tabulator_bootstrap4.css.map │ ├── tabulator_bootstrap4.min.css │ ├── tabulator_bootstrap4.min.css.map │ ├── tabulator_bootstrap5.css │ ├── tabulator_bootstrap5.css.map │ ├── tabulator_bootstrap5.min.css │ ├── tabulator_bootstrap5.min.css.map │ ├── tabulator_bulma.css │ ├── tabulator_bulma.css.map │ ├── tabulator_bulma.min.css │ ├── tabulator_bulma.min.css.map │ ├── tabulator_materialize.css │ ├── tabulator_materialize.css.map │ ├── tabulator_materialize.min.css │ ├── tabulator_materialize.min.css.map │ ├── tabulator_midnight.css │ ├── tabulator_midnight.css.map │ ├── tabulator_midnight.min.css │ ├── tabulator_midnight.min.css.map │ ├── tabulator_modern.css │ ├── tabulator_modern.css.map │ ├── tabulator_modern.min.css │ ├── tabulator_modern.min.css.map │ ├── tabulator_semanticui.css │ ├── tabulator_semanticui.css.map │ ├── tabulator_semanticui.min.css │ ├── tabulator_semanticui.min.css.map │ ├── tabulator_simple.css │ ├── tabulator_simple.css.map │ ├── tabulator_simple.min.css │ ├── tabulator_simple.min.css.map │ ├── tabulator_site.css │ ├── tabulator_site.css.map │ ├── tabulator_site.min.css │ ├── tabulator_site.min.css.map │ ├── tabulator_site_dark.css │ ├── tabulator_site_dark.css.map │ ├── tabulator_site_dark.min.css │ └── tabulator_site_dark.min.css.map └── js │ ├── jquery_wrapper.js │ ├── tabulator.js │ ├── tabulator.js.map │ ├── tabulator.min.js │ ├── tabulator.min.js.map │ ├── tabulator_esm.js │ ├── tabulator_esm.js.map │ ├── tabulator_esm.min.js │ ├── tabulator_esm.min.js.map │ ├── tabulator_esm.min.mjs │ ├── tabulator_esm.min.mjs.map │ ├── tabulator_esm.mjs │ └── tabulator_esm.mjs.map ├── jest.config.js ├── package-lock.json ├── package.json ├── playwright.config.js ├── src ├── js │ ├── builds │ │ ├── esm.js │ │ ├── jquery_wrapper.js │ │ └── usd.js │ ├── core │ │ ├── ColumnManager.js │ │ ├── CoreFeature.js │ │ ├── FooterManager.js │ │ ├── Module.js │ │ ├── RowManager.js │ │ ├── Tabulator.js │ │ ├── TabulatorFull.js │ │ ├── cell │ │ │ ├── Cell.js │ │ │ └── CellComponent.js │ │ ├── column │ │ │ ├── Column.js │ │ │ ├── ColumnComponent.js │ │ │ └── defaults │ │ │ │ └── options.js │ │ ├── defaults │ │ │ └── options.js │ │ ├── modules │ │ │ ├── core.js │ │ │ └── optional.js │ │ ├── rendering │ │ │ ├── Renderer.js │ │ │ └── renderers │ │ │ │ ├── BasicHorizontal.js │ │ │ │ ├── BasicVertical.js │ │ │ │ ├── VirtualDomHorizontal.js │ │ │ │ └── VirtualDomVertical.js │ │ ├── row │ │ │ ├── PseudoRow.js │ │ │ ├── Row.js │ │ │ └── RowComponent.js │ │ └── tools │ │ │ ├── Alert.js │ │ │ ├── ComponentFunctionBinder.js │ │ │ ├── DataLoader.js │ │ │ ├── DependencyRegistry.js │ │ │ ├── DeprecationAdvisor.js │ │ │ ├── ExternalEventBus.js │ │ │ ├── Helpers.js │ │ │ ├── InteractionMonitor.js │ │ │ ├── InternalEventBus.js │ │ │ ├── ModuleBinder.js │ │ │ ├── OptionsList.js │ │ │ ├── Popup.js │ │ │ └── TableRegistry.js │ └── modules │ │ ├── Accessor │ │ ├── Accessor.js │ │ └── defaults │ │ │ └── accessors.js │ │ ├── Ajax │ │ ├── Ajax.js │ │ └── defaults │ │ │ ├── config.js │ │ │ ├── contentTypeFormatters.js │ │ │ ├── loaderPromise.js │ │ │ └── urlGenerator.js │ │ ├── Clipboard │ │ ├── Clipboard.js │ │ ├── defaults │ │ │ ├── pasteActions.js │ │ │ └── pasteParsers.js │ │ └── extensions │ │ │ ├── extensions.js │ │ │ └── keybindings │ │ │ ├── actions.js │ │ │ └── bindings.js │ │ ├── ColumnCalcs │ │ ├── CalcComponent.js │ │ ├── ColumnCalcs.js │ │ └── defaults │ │ │ └── calculations.js │ │ ├── Comms │ │ └── Comms.js │ │ ├── DataTree │ │ └── DataTree.js │ │ ├── Download │ │ ├── Download.js │ │ └── defaults │ │ │ ├── downloaders.js │ │ │ └── downloaders │ │ │ ├── csv.js │ │ │ ├── html.js │ │ │ ├── json.js │ │ │ ├── jsonLines.js │ │ │ ├── pdf.js │ │ │ └── xlsx.js │ │ ├── Edit │ │ ├── Edit.js │ │ ├── List.js │ │ ├── defaults │ │ │ ├── editors.js │ │ │ └── editors │ │ │ │ ├── adaptable.js │ │ │ │ ├── date.js │ │ │ │ ├── datetime.js │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ ├── number.js │ │ │ │ ├── progress.js │ │ │ │ ├── range.js │ │ │ │ ├── star.js │ │ │ │ ├── textarea.js │ │ │ │ ├── tickCross.js │ │ │ │ └── time.js │ │ └── inputMask.js │ │ ├── Export │ │ ├── Export.js │ │ ├── ExportColumn.js │ │ ├── ExportRow.js │ │ └── defaults │ │ │ ├── columnLookups.js │ │ │ └── rowLookups.js │ │ ├── Filter │ │ ├── Filter.js │ │ └── defaults │ │ │ └── filters.js │ │ ├── Format │ │ ├── Format.js │ │ └── defaults │ │ │ ├── formatters.js │ │ │ └── formatters │ │ │ ├── adaptable.js │ │ │ ├── array.js │ │ │ ├── buttonCross.js │ │ │ ├── buttonTick.js │ │ │ ├── color.js │ │ │ ├── datetime.js │ │ │ ├── datetimediff.js │ │ │ ├── handle.js │ │ │ ├── html.js │ │ │ ├── image.js │ │ │ ├── json.js │ │ │ ├── link.js │ │ │ ├── lookup.js │ │ │ ├── money.js │ │ │ ├── plaintext.js │ │ │ ├── progress.js │ │ │ ├── rownum.js │ │ │ ├── star.js │ │ │ ├── textarea.js │ │ │ ├── tickCross.js │ │ │ ├── toggle.js │ │ │ └── traffic.js │ │ ├── FrozenColumns │ │ └── FrozenColumns.js │ │ ├── FrozenRows │ │ └── FrozenRows.js │ │ ├── GroupRows │ │ ├── Group.js │ │ ├── GroupComponent.js │ │ └── GroupRows.js │ │ ├── History │ │ ├── History.js │ │ ├── defaults │ │ │ ├── redoers.js │ │ │ └── undoers.js │ │ └── extensions │ │ │ ├── extensions.js │ │ │ └── keybindings │ │ │ ├── actions.js │ │ │ └── bindings.js │ │ ├── HtmlTableImport │ │ └── HtmlTableImport.js │ │ ├── Import │ │ ├── Import.js │ │ └── defaults │ │ │ ├── importers.js │ │ │ └── importers │ │ │ ├── array.js │ │ │ ├── csv.js │ │ │ ├── json.js │ │ │ └── xlsx.js │ │ ├── Interaction │ │ └── Interaction.js │ │ ├── Keybindings │ │ ├── Keybindings.js │ │ └── defaults │ │ │ ├── actions.js │ │ │ └── bindings.js │ │ ├── Layout │ │ ├── Layout.js │ │ └── defaults │ │ │ ├── modes.js │ │ │ └── modes │ │ │ ├── fitColumns.js │ │ │ ├── fitData.js │ │ │ ├── fitDataGeneral.js │ │ │ └── fitDataStretch.js │ │ ├── Localize │ │ ├── Localize.js │ │ └── defaults │ │ │ └── langs.js │ │ ├── Menu │ │ └── Menu.js │ │ ├── MoveColumns │ │ └── MoveColumns.js │ │ ├── MoveRows │ │ ├── MoveRows.js │ │ └── defaults │ │ │ ├── receivers.js │ │ │ └── senders.js │ │ ├── Mutator │ │ ├── Mutator.js │ │ └── defaults │ │ │ └── mutators.js │ │ ├── Page │ │ ├── Page.js │ │ └── defaults │ │ │ ├── pageCounters.js │ │ │ └── pageCounters │ │ │ ├── pages.js │ │ │ └── rows.js │ │ ├── Persistence │ │ ├── Persistence.js │ │ └── defaults │ │ │ ├── readers.js │ │ │ └── writers.js │ │ ├── Popup │ │ └── Popup.js │ │ ├── Print │ │ └── Print.js │ │ ├── ReactiveData │ │ └── ReactiveData.js │ │ ├── ResizeColumns │ │ └── ResizeColumns.js │ │ ├── ResizeRows │ │ └── ResizeRows.js │ │ ├── ResizeTable │ │ └── ResizeTable.js │ │ ├── ResponsiveLayout │ │ ├── ResponsiveLayout.js │ │ └── extensions │ │ │ ├── extensions.js │ │ │ └── formatters │ │ │ └── responsiveCollapse.js │ │ ├── SelectRange │ │ ├── Range.js │ │ ├── RangeComponent.js │ │ ├── SelectRange.js │ │ └── extensions │ │ │ ├── clipboard │ │ │ ├── pasteActions.js │ │ │ └── pasteParsers.js │ │ │ ├── export │ │ │ ├── columnLookups.js │ │ │ └── rowLookups.js │ │ │ ├── extensions.js │ │ │ └── keybindings │ │ │ ├── actions.js │ │ │ └── bindings.js │ │ ├── SelectRow │ │ ├── SelectRow.js │ │ └── extensions │ │ │ ├── extensions.js │ │ │ └── formatters │ │ │ └── rowSelection.js │ │ ├── Sort │ │ ├── Sort.js │ │ └── defaults │ │ │ ├── sorters.js │ │ │ └── sorters │ │ │ ├── alphanum.js │ │ │ ├── array.js │ │ │ ├── boolean.js │ │ │ ├── date.js │ │ │ ├── datetime.js │ │ │ ├── exists.js │ │ │ ├── number.js │ │ │ ├── string.js │ │ │ └── time.js │ │ ├── Spreadsheet │ │ ├── GridCalculator.js │ │ ├── Sheet.js │ │ ├── SheetComponent.js │ │ └── Spreadsheet.js │ │ ├── Tooltip │ │ └── Tooltip.js │ │ └── Validate │ │ ├── Validate.js │ │ └── defaults │ │ └── validators.js └── scss │ ├── tabulator.scss │ └── themes │ ├── bootstrap │ ├── functions4.scss │ ├── functions5.scss │ ├── tabulator_bootstrap3.scss │ ├── tabulator_bootstrap4.scss │ ├── tabulator_bootstrap5.scss │ ├── variables3.scss │ ├── variables4.scss │ └── variables5.scss │ ├── bulma │ ├── tabulator_bulma.scss │ └── variables.scss │ ├── materialize │ ├── tabulator_materialize.scss │ └── variables.scss │ ├── semanticui │ ├── tabulator_semanticui.scss │ ├── variables.scss │ └── variables_table.scss │ ├── tabulator_midnight.scss │ ├── tabulator_modern.scss │ ├── tabulator_simple.scss │ ├── tabulator_site.scss │ └── tabulator_site_dark.scss └── test ├── e2e ├── basic.spec.js └── index.html └── unit ├── modules ├── Accessor.spec.js ├── Ajax.spec.js ├── Clipboard.spec.js ├── ColumnCalcs.spec.js ├── Comms.spec.js ├── DataTree.spec.js ├── Download.spec.js ├── Edit.spec.js ├── Export.spec.js ├── Filter.spec.js ├── Format.spec.js ├── FrozenColumns.spec.js ├── FrozenRows.spec.js ├── GroupRows.spec.js ├── History.spec.js ├── HtmlTableImport.spec.js ├── Import.spec.js ├── Interaction.spec.js ├── Keybindings.spec.js ├── Layout.spec.js ├── Localize.spec.js ├── Menu.spec.js ├── MoveColumns.spec.js ├── MoveRows.spec.js ├── Mutator.spec.js ├── Page.spec.js ├── Persistence.spec.js ├── Popup.spec.js ├── Print.spec.js ├── ReactiveData.spec.js ├── ResizeColumns.spec.js ├── ResizeRows.spec.js ├── ResizeTable.spec.js ├── ResponsiveLayout.spec.js ├── SelectRange.spec.js ├── SelectRow.spec.js ├── Sort.spec.js ├── Spreadsheet.spec.js ├── Tooltip.spec.js └── Validate.spec.js └── setup.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | dist 3 | examples 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true, 6 | "amd": true, 7 | }, 8 | globals: { 9 | 'luxon': 'readonly', 10 | 'XLSX': 'readonly', 11 | 'jspdf': 'readonly' 12 | }, 13 | "extends": "eslint:recommended", 14 | "parserOptions": { 15 | "ecmaVersion": "latest", 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "semi": "error", 20 | "indent": ["error", "tab", {VariableDeclarator:0, "SwitchCase": 1}], 21 | "no-unused-vars": ["warn", { "vars": "all", "args": "none", "ignoreRestSiblings": false }], 22 | "no-fallthrough": "off", 23 | "no-inner-declarations": "off", 24 | "no-prototype-builtins": "off", 25 | "no-empty": ["error", { "allowEmptyCatch": true }], 26 | // "curly": "error", 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug with Tabulator 4 | title: '' 5 | labels: Possible Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | 18 | **Describe the bug** 19 | A clear and concise description of what the bug is. 20 | 21 | **Tabulator Info** 22 | - Which version of Tabulator are you using? 23 | 24 | **Working Example** 25 | YOU MUST include a link to a JS Fiddle or Codepen that demonstrates the problem, it is very hard to diagnose an issue from a simple description. 26 | 32 | 33 | **To Reproduce** 34 | A step by step guide to recreate the issue in your JS Fiddle or Codepen: 35 | 1. Go to '...' 36 | 2. Click on '....' 37 | 3. Scroll down to '....' 38 | 4. See error 39 | 40 | **Expected behavior** 41 | A clear and concise description of what you expected to happen. 42 | 43 | **Screenshots** 44 | If applicable, add screenshots to help explain your problem. 45 | 46 | **Desktop (please complete the following information):** 47 | - OS: [e.g. iOS] 48 | - Browser [e.g. chrome, safari] 49 | - Version [e.g. 22] 50 | 51 | **Smartphone (please complete the following information):** 52 | - Device: [e.g. iPhone6] 53 | - OS: [e.g. iOS8.1] 54 | - Browser [e.g. stock browser, safari] 55 | - Version [e.g. 22] 56 | 57 | **Additional context** 58 | Add any other context about the problem here. 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Report an issue with the documentation on the tabulator.info website 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Website Page** 11 | A link to the page with the issue 12 | 13 | **Describe the issue** 14 | A clear and concise description of what the issue is. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Suggested Feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | *Is your feature request related to a problem? Please describe.** 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered** 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question (QUESTIONS MUST BE ASKED ON STACK OVERFLOW!!! DO NOT CREATE AN ISSUE!!!) 3 | about: Please ask questions on Stack Overflow, NOT on GitHub 4 | title: '' 5 | labels: Invalid, Question - Ask On Stack Overflow 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please ask questions on www.stackoverflow.com the issues list is now reserved for feature requests and bug reports. 11 | 12 | Questions asked in the issue list will be automatically closed! 13 | -------------------------------------------------------------------------------- /.github/workflows/bad-files-check.yml: -------------------------------------------------------------------------------- 1 | name: Bad files check 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | check: 7 | name: Dist check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out Git repository 11 | uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | 15 | 16 | - name: Get specific changed files in dist 17 | id: changed-files-specific 18 | uses: tj-actions/changed-files@v41 19 | with: 20 | files: | 21 | dist 22 | 23 | - name: Check file existence 24 | id: check_files 25 | uses: andstor/file-existence-action@v1 26 | with: 27 | files: "yarn.lock" 28 | 29 | - name: Fail if dist files changed 30 | if: steps.changed-files-specific.outputs.any_changed == 'true' 31 | run: | 32 | echo "Oops! Looks like you modified some files in dist/. Please remove them from your PR, thanks!" 33 | exit 1 34 | 35 | - name: Fail if yarn lock exists 36 | if: steps.check_files.outputs.files_exists == 'true' 37 | run: | 38 | echo "Oops! Looks like you checked in a yarn.lock file, we use npm and package-lock.json. Please remove it from your PR, thanks!" 39 | exit 1 40 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and build 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but only for the main branch 5 | push: 6 | branches: 7 | - main 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | linting: 13 | name: Linting 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out Git repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Node.js 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 18 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Lint 28 | run: npm run lint 29 | 30 | - name: Build 31 | run: npm run build -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | pull_request: 6 | branches: [ main, master ] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [18, 20, 22] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install dependencies 20 | run: npm ci 21 | - name: Install Playwright Browsers 22 | run: npx playwright install --with-deps 23 | - name: Build dist files 24 | run: npm run build 25 | - name: Run Playwright tests 26 | run: npx playwright test 27 | - uses: actions/upload-artifact@v4 28 | if: ${{ !cancelled() }} 29 | with: 30 | name: playwright-report-node-${{ matrix.node-version }} 31 | path: playwright-report/ 32 | retention-days: 30 33 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | pull_request: 6 | branches: [ main, master ] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [18, 20, 22] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install modules 20 | run: npm install 21 | - name: Run tests 22 | run: npm run test:unit 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | 4 | node_modules/ 5 | examples/ 6 | npm-debug.log 7 | 8 | # Playwright 9 | /test-results/ 10 | /playwright-report/ 11 | /blob-report/ 12 | /playwright/.cache/ 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Getting Help 2 | If you need help with any Tabulator features, please ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/tabulator) 3 | 4 | Further help resources can be found in the [Community Help Guide](http://tabulator.info/community#help) and the [Documentation Section](http://tabulator.info/) of the Tabulator website 5 | 6 | **QUESTIONS MUST NOT BE ASKED IN THE ISSUE TRACKER, IT IS FOR BUG REPORTS AND FEATURE REQUESTS ONLY** 7 | 8 | ## Reporting A Bug 9 | Please read the [Bug Reporting Guide](http://tabulator.info/community#bug) before creating any Bug Report issues on this repo. 10 | 11 | **BUG REPORTS WILL NOT BE ACCEPTED WITHOUT A [JS Fiddle](https://jsfiddle.net/) or [Codepen](https://codepen.io/) TO DEMONSTRATE THE ISSUE** 12 | 13 | 14 | ## Requesting A New Feature 15 | Please read the [Feature Request Guide](http://tabulator.info/community#feature) before creating any Feature Request issues on this repo. 16 | 17 | ## Contributing To Tabulator 18 | There are many ways that you can contribute to Tabulator. Checkout the [Community Contribution Guide](http://tabulator.info/community#contribute) to find out how you can start contributing 19 | 20 | 21 | ## Pull Requests 22 | If you are interested in contributing code to the Tabulator repo, please read the [Pull Request Guide](http://tabulator.info/community#pullrequest) before submitting your first PR 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 Oli Folkerd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | An easy to use interactive table generation JavaScript library 11 |

12 | 13 |

14 | Full documentation & demos can be found at: http://tabulator.info 15 |

16 | 17 | *** 18 | ![Tabulator Table](http://tabulator.info/images/tabulator_table.jpg) 19 | *** 20 | 21 | 22 | Features 23 | ================================ 24 | Tabulator allows you to create interactive tables in seconds from any HTML Table, Javascript Array or JSON formatted data. 25 | 26 | Simply include the library and the css in your project and you're away! 27 | 28 | Tabulator is packed with useful features including: 29 | 30 | ![Tabulator Features](http://olifolkerd.github.io/tabulator/images/featurelist_share.png) 31 | 32 | 33 | Frontend Framework Support 34 | ================================ 35 | Tabulator is built to work with all the major front end JavaScript frameworks including React, Angular and Vue. 36 | 37 | 38 | Setup 39 | ================================ 40 | Setting up tabulator could not be simpler. 41 | 42 | Include the library and the css 43 | ```html 44 | 45 | 46 | ``` 47 | 48 | Create an element to hold the table 49 | ```html 50 |
51 | ``` 52 | 53 | Turn the element into a tabulator with some simple javascript 54 | ```js 55 | var table = new Tabulator("#example-table", {}); 56 | ``` 57 | 58 | 59 | ### Bower Installation 60 | To get Tabulator via the Bower package manager, open a terminal in your project directory and run the following command: 61 | ``` 62 | bower install tabulator --save 63 | ``` 64 | 65 | ### NPM Installation 66 | To get Tabulator via the NPM package manager, open a terminal in your project directory and run the following command: 67 | ``` 68 | npm install tabulator-tables --save 69 | ``` 70 | 71 | ### CDN - UNPKG 72 | To access Tabulator directly from the UNPKG CDN servers, include the following two lines at the start of your project, instead of the locally hosted versions: 73 | ```html 74 | 75 | 76 | ``` 77 | 78 | Testing 79 | ================================ 80 | Tabulator comes with both Unit and End-to-End (E2E) tests. Here’s how you can run them: 81 | 82 | ```bash 83 | # Unit test 84 | npm run test:unit 85 | 86 | # E2E test 87 | npm run build # Make sure to build the project first 88 | npx playwright test # Run the tests 89 | # or 90 | npm run test:e2e 91 | 92 | # Run all tests 93 | npm run test 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | if (api.env("test")) { 3 | return { 4 | presets: [["@babel/preset-env", { targets: { node: "current" } }]], 5 | }; 6 | } 7 | 8 | return { 9 | presets: [["@babel/env", { modules: false }]], 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabulator", 3 | "main": "dist/js/tabulator.js", 4 | "version": "6.3.1", 5 | "description": "Interactive table generation JavaScript library", 6 | "keywords": [ 7 | "table", 8 | "grid", 9 | "datagrid", 10 | "tabulator", 11 | "editable", 12 | "cookie", 13 | "jquery", 14 | "jqueryui", 15 | "sort", 16 | "format", 17 | "resizable", 18 | "list", 19 | "scrollable", 20 | "ajax", 21 | "json", 22 | "widget", 23 | "jquery", 24 | "react", 25 | "angular", 26 | "vue" 27 | ], 28 | "authors": [ 29 | "Oli Folkerd" 30 | ], 31 | "license": "MIT", 32 | "homepage": "https://github.com/olifolkerd/tabulator", 33 | "ignore": [ 34 | "**/.*", 35 | "node_modules", 36 | "bower_components", 37 | "test", 38 | "tests" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /build/rollup.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module'; 2 | const require = createRequire(import.meta.url); 3 | 4 | import Bundler from "./Bundler.mjs"; 5 | const pkg = require("../package.json"); 6 | 7 | var bundler = new Bundler(pkg.version, process.env.TARGET); 8 | 9 | export default bundler.bundle(); 10 | -------------------------------------------------------------------------------- /dist/js/jquery_wrapper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Tabulator package. 3 | * 4 | * (c) Oliver Folkerd 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Full Documentation & Demos can be found at: http://olifolkerd.github.io/tabulator/ 10 | * 11 | */ 12 | 13 | (function (root, factory) { 14 | "use strict"; 15 | if (typeof define === 'function' && define.amd) { 16 | define(['jquery', 'tabulator', 'jquery-ui'], factory); 17 | } 18 | else if(typeof module !== 'undefined' && module.exports) { 19 | module.exports = factory( 20 | require('jquery'), 21 | require('tabulator'), 22 | require('jquery-ui') 23 | ); 24 | } 25 | else { 26 | factory(root.jQuery, root.Tabulator); 27 | } 28 | }(this, function ($, Tabulator) { 29 | 30 | $.widget("ui.tabulator", { 31 | _create:function(){ 32 | var options = Object.assign({}, this.options); 33 | var props = []; 34 | 35 | delete options.create; 36 | delete options.disabled; 37 | 38 | this.table = new Tabulator(this.element[0], options); 39 | window.table = this.table; 40 | 41 | //retrieve properties on prototype 42 | props = Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(this.table))); 43 | 44 | //retrieve properties added by modules 45 | props = props.concat(Object.getOwnPropertyNames(this.table)); 46 | 47 | //map tabulator functions to jquery wrapper 48 | for(let key of props){ 49 | if(typeof this.table[key] === "function" && key.charAt(0) !== "_"){ 50 | this[key] = this.table[key].bind(this.table); 51 | } 52 | } 53 | }, 54 | 55 | _setOption: function(option, value){ 56 | console.error("Tabulator jQuery wrapper does not support setting options after the table has been instantiated"); 57 | }, 58 | 59 | _destroy: function(option, value){ 60 | this.table.destroy(); 61 | }, 62 | }); 63 | })); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabulator-tables", 3 | "version": "6.3.1", 4 | "description": "Interactive table generation JavaScript library", 5 | "style": "dist/css/tabulator.css", 6 | "main": "dist/js/tabulator.js", 7 | "module": "dist/js/tabulator_esm.mjs", 8 | "exports": { 9 | ".": { 10 | "require": "./dist/js/tabulator.js", 11 | "import": "./dist/js/tabulator_esm.mjs" 12 | }, 13 | "./dist/css/*.css": "./dist/css/*.css", 14 | "./dist/js/*.js": "./dist/js/*.js", 15 | "./src/scss/*.scss": "./src/scss/*.scss", 16 | "./src/js/*.js": "./src/js/*.js" 17 | }, 18 | "sideEffects": [ 19 | "**/*.css", 20 | "**/*.scss" 21 | ], 22 | "scripts": { 23 | "lint": "eslint src --fix", 24 | "prebuild": "eslint src --fix", 25 | "build": "rollup -c build/rollup.mjs", 26 | "dev": "rollup -c build/rollup.mjs -w --environment TARGET:dev", 27 | "dev:css": "rollup -c build/rollup.mjs -w --environment TARGET:css", 28 | "dev:esm": "rollup -c build/rollup.mjs -w --environment TARGET:esm", 29 | "dev:umd": "rollup -c build/rollup.mjs -w --environment TARGET:umd", 30 | "dev:wrappers": "rollup -c build/rollup.mjs -w --environment TARGET:wrappers ", 31 | "test:unit": "jest", 32 | "test:e2e": "npm run build && npx playwright test", 33 | "test": "npm run test:unit && npm run test:e2e" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/olifolkerd/tabulator.git" 38 | }, 39 | "keywords": [ 40 | "table", 41 | "grid", 42 | "datagrid", 43 | "tabulator", 44 | "editable", 45 | "sort", 46 | "format", 47 | "resizable", 48 | "list", 49 | "scrollable", 50 | "ajax", 51 | "json", 52 | "widget", 53 | "jquery", 54 | "react", 55 | "angular", 56 | "vue" 57 | ], 58 | "author": "Oli Folkerd", 59 | "license": "MIT", 60 | "bugs": { 61 | "url": "https://github.com/olifolkerd/tabulator/issues" 62 | }, 63 | "homepage": "https://tabulator.info/", 64 | "devDependencies": { 65 | "@babel/core": "^7.26.9", 66 | "@babel/preset-env": "^7.26.9", 67 | "@playwright/test": "^1.50.1", 68 | "@rollup/plugin-node-resolve": "^15.2.3", 69 | "@rollup/plugin-terser": "^0.4.4", 70 | "@types/node": "^22.13.5", 71 | "babel-jest": "^29.7.0", 72 | "eslint": "^8.57.0", 73 | "eslint-plugin-only-warn": "^1.1.0", 74 | "fs-extra": "^11.2.0", 75 | "globby": "^14.0.1", 76 | "jest": "^29.7.0", 77 | "jest-environment-jsdom": "^29.7.0", 78 | "node-sass": "^9.0.0", 79 | "postcss": "^8.4.35", 80 | "postcss-prettify": "^0.3.4", 81 | "rollup": "^4.12.1", 82 | "rollup-plugin-license": "^3.3.1", 83 | "rollup-plugin-postcss": "^4.0.2" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig, devices } from '@playwright/test'; 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // import dotenv from 'dotenv'; 9 | // import path from 'path'; 10 | // dotenv.config({ path: path.resolve(__dirname, '.env') }); 11 | 12 | /** 13 | * @see https://playwright.dev/docs/test-configuration 14 | */ 15 | export default defineConfig({ 16 | testDir: './test/e2e', 17 | /* Run tests in files in parallel */ 18 | fullyParallel: true, 19 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 20 | forbidOnly: !!process.env.CI, 21 | /* Retry on CI only */ 22 | retries: process.env.CI ? 2 : 0, 23 | /* Opt out of parallel tests on CI. */ 24 | workers: process.env.CI ? 1 : undefined, 25 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 26 | reporter: 'html', 27 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 28 | use: { 29 | /* Base URL to use in actions like `await page.goto('/')`. */ 30 | // baseURL: 'http://127.0.0.1:3000', 31 | 32 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 33 | trace: 'on-first-retry', 34 | }, 35 | 36 | /* Configure projects for major browsers */ 37 | projects: [ 38 | { 39 | name: 'chromium', 40 | use: { ...devices['Desktop Chrome'] }, 41 | }, 42 | ], 43 | 44 | /* Run your local dev server before starting the tests */ 45 | webServer: { 46 | command: 'npx serve test -p 3000', 47 | port: 3000, 48 | timeout: 60 * 1000, 49 | reuseExistingServer: !process.env.CI, 50 | }, 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /src/js/builds/esm.js: -------------------------------------------------------------------------------- 1 | export * from '../core/modules/optional.js'; 2 | export {default as Tabulator} from '../core/Tabulator.js'; 3 | export {default as TabulatorFull} from '../core/TabulatorFull.js'; 4 | 5 | export {default as CellComponent} from '../core/cell/CellComponent.js'; 6 | export {default as ColumnComponent} from '../core/column/ColumnComponent.js'; 7 | export {default as RowComponent} from '../core/row/RowComponent.js'; 8 | export {default as GroupComponent} from '../modules/GroupRows/GroupComponent.js'; 9 | export {default as CalcComponent} from '../modules/ColumnCalcs/CalcComponent.js'; 10 | export {default as RangeComponent} from '../modules/SelectRange/RangeComponent.js'; 11 | export {default as SheetComponent} from '../modules/Spreadsheet/SheetComponent.js'; 12 | 13 | export {default as PseudoRow} from '../core/row/PseudoRow.js'; 14 | 15 | export {default as Module} from '../core/Module.js'; 16 | export {default as Renderer} from '../core/rendering/Renderer.js'; 17 | -------------------------------------------------------------------------------- /src/js/builds/jquery_wrapper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Tabulator package. 3 | * 4 | * (c) Oliver Folkerd 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | * 9 | * Full Documentation & Demos can be found at: http://olifolkerd.github.io/tabulator/ 10 | * 11 | */ 12 | 13 | (function (root, factory) { 14 | "use strict"; 15 | if (typeof define === 'function' && define.amd) { 16 | define(['jquery', 'tabulator', 'jquery-ui'], factory); 17 | } 18 | else if(typeof module !== 'undefined' && module.exports) { 19 | module.exports = factory( 20 | require('jquery'), 21 | require('tabulator'), 22 | require('jquery-ui') 23 | ); 24 | } 25 | else { 26 | factory(root.jQuery, root.Tabulator); 27 | } 28 | }(this, function ($, Tabulator) { 29 | 30 | $.widget("ui.tabulator", { 31 | _create:function(){ 32 | var options = Object.assign({}, this.options); 33 | var props = []; 34 | 35 | delete options.create; 36 | delete options.disabled; 37 | 38 | this.table = new Tabulator(this.element[0], options); 39 | window.table = this.table; 40 | 41 | //retrieve properties on prototype 42 | props = Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(this.table))); 43 | 44 | //retrieve properties added by modules 45 | props = props.concat(Object.getOwnPropertyNames(this.table)); 46 | 47 | //map tabulator functions to jquery wrapper 48 | for(let key of props){ 49 | if(typeof this.table[key] === "function" && key.charAt(0) !== "_"){ 50 | this[key] = this.table[key].bind(this.table); 51 | } 52 | } 53 | }, 54 | 55 | _setOption: function(option, value){ 56 | console.error("Tabulator jQuery wrapper does not support setting options after the table has been instantiated"); 57 | }, 58 | 59 | _destroy: function(option, value){ 60 | this.table.destroy(); 61 | }, 62 | }); 63 | })); 64 | -------------------------------------------------------------------------------- /src/js/builds/usd.js: -------------------------------------------------------------------------------- 1 | export { default } from '../core/TabulatorFull.js'; -------------------------------------------------------------------------------- /src/js/core/CoreFeature.js: -------------------------------------------------------------------------------- 1 | export default class CoreFeature{ 2 | 3 | constructor(table){ 4 | this.table = table; 5 | } 6 | 7 | ////////////////////////////////////////// 8 | /////////////// DataLoad ///////////////// 9 | ////////////////////////////////////////// 10 | 11 | reloadData(data, silent, columnsChanged){ 12 | return this.table.dataLoader.load(data, undefined, undefined, undefined, silent, columnsChanged); 13 | } 14 | 15 | ////////////////////////////////////////// 16 | ///////////// Localization /////////////// 17 | ////////////////////////////////////////// 18 | 19 | langText(){ 20 | return this.table.modules.localize.getText(...arguments); 21 | } 22 | 23 | langBind(){ 24 | return this.table.modules.localize.bind(...arguments); 25 | } 26 | 27 | langLocale(){ 28 | return this.table.modules.localize.getLocale(...arguments); 29 | } 30 | 31 | 32 | ////////////////////////////////////////// 33 | ////////// Inter Table Comms ///////////// 34 | ////////////////////////////////////////// 35 | 36 | commsConnections(){ 37 | return this.table.modules.comms.getConnections(...arguments); 38 | } 39 | 40 | commsSend(){ 41 | return this.table.modules.comms.send(...arguments); 42 | } 43 | 44 | ////////////////////////////////////////// 45 | //////////////// Layout ///////////////// 46 | ////////////////////////////////////////// 47 | 48 | layoutMode(){ 49 | return this.table.modules.layout.getMode(); 50 | } 51 | 52 | layoutRefresh(force){ 53 | return this.table.modules.layout.layout(force); 54 | } 55 | 56 | 57 | ////////////////////////////////////////// 58 | /////////////// Event Bus //////////////// 59 | ////////////////////////////////////////// 60 | 61 | subscribe(){ 62 | return this.table.eventBus.subscribe(...arguments); 63 | } 64 | 65 | unsubscribe(){ 66 | return this.table.eventBus.unsubscribe(...arguments); 67 | } 68 | 69 | subscribed(key){ 70 | return this.table.eventBus.subscribed(key); 71 | } 72 | 73 | subscriptionChange(){ 74 | return this.table.eventBus.subscriptionChange(...arguments); 75 | } 76 | 77 | dispatch(){ 78 | return this.table.eventBus.dispatch(...arguments); 79 | } 80 | 81 | chain(){ 82 | return this.table.eventBus.chain(...arguments); 83 | } 84 | 85 | confirm(){ 86 | return this.table.eventBus.confirm(...arguments); 87 | } 88 | 89 | dispatchExternal(){ 90 | return this.table.externalEvents.dispatch(...arguments); 91 | } 92 | 93 | subscribedExternal(key){ 94 | return this.table.externalEvents.subscribed(key); 95 | } 96 | 97 | subscriptionChangeExternal(){ 98 | return this.table.externalEvents.subscriptionChange(...arguments); 99 | } 100 | 101 | ////////////////////////////////////////// 102 | //////////////// Options ///////////////// 103 | ////////////////////////////////////////// 104 | 105 | options(key){ 106 | return this.table.options[key]; 107 | } 108 | 109 | setOption(key, value){ 110 | if(typeof value !== "undefined"){ 111 | this.table.options[key] = value; 112 | } 113 | 114 | return this.table.options[key]; 115 | } 116 | 117 | ////////////////////////////////////////// 118 | /////////// Deprecation Checks /////////// 119 | ////////////////////////////////////////// 120 | 121 | deprecationCheck(oldOption, newOption, convert){ 122 | return this.table.deprecationAdvisor.check(oldOption, newOption, convert); 123 | } 124 | 125 | deprecationCheckMsg(oldOption, msg){ 126 | return this.table.deprecationAdvisor.checkMsg(oldOption, msg); 127 | } 128 | 129 | deprecationMsg(msg){ 130 | return this.table.deprecationAdvisor.msg(msg); 131 | } 132 | ////////////////////////////////////////// 133 | //////////////// Modules ///////////////// 134 | ////////////////////////////////////////// 135 | 136 | module(key){ 137 | return this.table.module(key); 138 | } 139 | } -------------------------------------------------------------------------------- /src/js/core/FooterManager.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from './CoreFeature.js'; 2 | 3 | export default class FooterManager extends CoreFeature{ 4 | 5 | constructor(table){ 6 | super(table); 7 | 8 | this.active = false; 9 | this.element = this.createElement(); //containing element 10 | this.containerElement = this.createContainerElement(); //containing element 11 | this.external = false; 12 | } 13 | 14 | initialize(){ 15 | this.initializeElement(); 16 | } 17 | 18 | createElement(){ 19 | var el = document.createElement("div"); 20 | 21 | el.classList.add("tabulator-footer"); 22 | 23 | return el; 24 | } 25 | 26 | 27 | createContainerElement(){ 28 | var el = document.createElement("div"); 29 | 30 | el.classList.add("tabulator-footer-contents"); 31 | 32 | this.element.appendChild(el); 33 | 34 | return el; 35 | } 36 | 37 | initializeElement(){ 38 | if(this.table.options.footerElement){ 39 | 40 | switch(typeof this.table.options.footerElement){ 41 | case "string": 42 | if(this.table.options.footerElement[0] === "<"){ 43 | this.containerElement.innerHTML = this.table.options.footerElement; 44 | }else{ 45 | this.external = true; 46 | this.containerElement = document.querySelector(this.table.options.footerElement); 47 | } 48 | break; 49 | 50 | default: 51 | this.element = this.table.options.footerElement; 52 | break; 53 | } 54 | } 55 | } 56 | 57 | getElement(){ 58 | return this.element; 59 | } 60 | 61 | append(element){ 62 | this.activate(); 63 | 64 | this.containerElement.appendChild(element); 65 | this.table.rowManager.adjustTableSize(); 66 | } 67 | 68 | prepend(element){ 69 | this.activate(); 70 | 71 | this.element.insertBefore(element, this.element.firstChild); 72 | this.table.rowManager.adjustTableSize(); 73 | } 74 | 75 | remove(element){ 76 | element.parentNode.removeChild(element); 77 | this.deactivate(); 78 | } 79 | 80 | deactivate(force){ 81 | if(!this.element.firstChild || force){ 82 | if(!this.external){ 83 | this.element.parentNode.removeChild(this.element); 84 | } 85 | this.active = false; 86 | } 87 | } 88 | 89 | activate(){ 90 | if(!this.active){ 91 | this.active = true; 92 | if(!this.external){ 93 | this.table.element.appendChild(this.getElement()); 94 | this.table.element.style.display = ''; 95 | } 96 | } 97 | } 98 | 99 | redraw(){ 100 | this.dispatch("footer-redraw"); 101 | } 102 | } -------------------------------------------------------------------------------- /src/js/core/Module.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from './CoreFeature.js'; 2 | import Popup from './tools/Popup.js'; 3 | 4 | export default class Module extends CoreFeature{ 5 | 6 | constructor(table, name){ 7 | super(table); 8 | 9 | this._handler = null; 10 | } 11 | 12 | initialize(){ 13 | // setup module when table is initialized, to be overridden in module 14 | } 15 | 16 | 17 | /////////////////////////////////// 18 | ////// Options Registration /////// 19 | /////////////////////////////////// 20 | 21 | registerTableOption(key, value){ 22 | this.table.optionsList.register(key, value); 23 | } 24 | 25 | registerColumnOption(key, value){ 26 | this.table.columnManager.optionsList.register(key, value); 27 | } 28 | 29 | /////////////////////////////////// 30 | /// Public Function Registration /// 31 | /////////////////////////////////// 32 | 33 | registerTableFunction(name, func){ 34 | if(typeof this.table[name] === "undefined"){ 35 | this.table[name] = (...args) => { 36 | this.table.initGuard(name); 37 | 38 | return func(...args); 39 | }; 40 | }else{ 41 | console.warn("Unable to bind table function, name already in use", name); 42 | } 43 | } 44 | 45 | registerComponentFunction(component, func, handler){ 46 | return this.table.componentFunctionBinder.bind(component, func, handler); 47 | } 48 | 49 | /////////////////////////////////// 50 | ////////// Data Pipeline ////////// 51 | /////////////////////////////////// 52 | 53 | registerDataHandler(handler, priority){ 54 | this.table.rowManager.registerDataPipelineHandler(handler, priority); 55 | this._handler = handler; 56 | } 57 | 58 | registerDisplayHandler(handler, priority){ 59 | this.table.rowManager.registerDisplayPipelineHandler(handler, priority); 60 | this._handler = handler; 61 | } 62 | 63 | displayRows(adjust){ 64 | var index = this.table.rowManager.displayRows.length - 1, 65 | lookupIndex; 66 | 67 | if(this._handler){ 68 | lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => { 69 | return item.handler === this._handler; 70 | }); 71 | 72 | if(lookupIndex > -1){ 73 | index = lookupIndex; 74 | } 75 | } 76 | 77 | if(adjust){ 78 | index = index + adjust; 79 | } 80 | 81 | if(this._handler){ 82 | if(index > -1){ 83 | return this.table.rowManager.getDisplayRows(index); 84 | }else{ 85 | return this.activeRows(); 86 | } 87 | } 88 | } 89 | 90 | activeRows(){ 91 | return this.table.rowManager.activeRows; 92 | } 93 | 94 | refreshData(renderInPosition, handler){ 95 | if(!handler){ 96 | handler = this._handler; 97 | } 98 | 99 | if(handler){ 100 | this.table.rowManager.refreshActiveData(handler, false, renderInPosition); 101 | } 102 | } 103 | 104 | /////////////////////////////////// 105 | //////// Footer Management //////// 106 | /////////////////////////////////// 107 | 108 | footerAppend(element){ 109 | return this.table.footerManager.append(element); 110 | } 111 | 112 | footerPrepend(element){ 113 | return this.table.footerManager.prepend(element); 114 | } 115 | 116 | footerRemove(element){ 117 | return this.table.footerManager.remove(element); 118 | } 119 | 120 | /////////////////////////////////// 121 | //////// Popups Management //////// 122 | /////////////////////////////////// 123 | 124 | popup(menuEl, menuContainer){ 125 | return new Popup(this.table, menuEl, menuContainer); 126 | } 127 | 128 | /////////////////////////////////// 129 | //////// Alert Management //////// 130 | /////////////////////////////////// 131 | 132 | alert(content, type){ 133 | return this.table.alertManager.alert(content, type); 134 | } 135 | 136 | clearAlert(){ 137 | return this.table.alertManager.clear(); 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /src/js/core/TabulatorFull.js: -------------------------------------------------------------------------------- 1 | //tabulator with all modules installed 2 | import {default as Tabulator} from './Tabulator.js'; 3 | import * as allModules from '../core/modules/optional.js'; 4 | 5 | class TabulatorFull extends Tabulator { 6 | static extendModule(){ 7 | Tabulator.initializeModuleBinder(allModules); 8 | Tabulator._extendModule(...arguments); 9 | } 10 | 11 | static registerModule(){ 12 | Tabulator.initializeModuleBinder(allModules); 13 | Tabulator._registerModule(...arguments); 14 | } 15 | 16 | constructor(element, options, modules){ 17 | super(element, options, allModules); 18 | } 19 | } 20 | 21 | export default TabulatorFull; 22 | -------------------------------------------------------------------------------- /src/js/core/cell/CellComponent.js: -------------------------------------------------------------------------------- 1 | //public cell object 2 | export default class CellComponent { 3 | 4 | constructor (cell){ 5 | this._cell = cell; 6 | 7 | return new Proxy(this, { 8 | get: function(target, name, receiver) { 9 | if (typeof target[name] !== "undefined") { 10 | return target[name]; 11 | }else{ 12 | return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name); 13 | } 14 | } 15 | }); 16 | } 17 | 18 | getValue(){ 19 | return this._cell.getValue(); 20 | } 21 | 22 | getOldValue(){ 23 | return this._cell.getOldValue(); 24 | } 25 | 26 | getInitialValue(){ 27 | return this._cell.initialValue; 28 | } 29 | 30 | getElement(){ 31 | return this._cell.getElement(); 32 | } 33 | 34 | getRow(){ 35 | return this._cell.row.getComponent(); 36 | } 37 | 38 | getData(transform){ 39 | return this._cell.row.getData(transform); 40 | } 41 | getType(){ 42 | return "cell"; 43 | } 44 | getField(){ 45 | return this._cell.column.getField(); 46 | } 47 | 48 | getColumn(){ 49 | return this._cell.column.getComponent(); 50 | } 51 | 52 | setValue(value, mutate){ 53 | if(typeof mutate == "undefined"){ 54 | mutate = true; 55 | } 56 | 57 | this._cell.setValue(value, mutate); 58 | } 59 | 60 | restoreOldValue(){ 61 | this._cell.setValueActual(this._cell.getOldValue()); 62 | } 63 | 64 | restoreInitialValue(){ 65 | this._cell.setValueActual(this._cell.initialValue); 66 | } 67 | 68 | checkHeight(){ 69 | this._cell.checkHeight(); 70 | } 71 | 72 | getTable(){ 73 | return this._cell.table; 74 | } 75 | 76 | _getSelf(){ 77 | return this._cell; 78 | } 79 | } -------------------------------------------------------------------------------- /src/js/core/column/ColumnComponent.js: -------------------------------------------------------------------------------- 1 | //public column object 2 | export default class ColumnComponent { 3 | constructor (column){ 4 | this._column = column; 5 | this.type = "ColumnComponent"; 6 | 7 | return new Proxy(this, { 8 | get: function(target, name, receiver) { 9 | if (typeof target[name] !== "undefined") { 10 | return target[name]; 11 | }else{ 12 | return target._column.table.componentFunctionBinder.handle("column", target._column, name); 13 | } 14 | } 15 | }); 16 | } 17 | 18 | getElement(){ 19 | return this._column.getElement(); 20 | } 21 | 22 | getDefinition(){ 23 | return this._column.getDefinition(); 24 | } 25 | 26 | getField(){ 27 | return this._column.getField(); 28 | } 29 | 30 | getTitleDownload() { 31 | return this._column.getTitleDownload(); 32 | } 33 | 34 | getCells(){ 35 | var cells = []; 36 | 37 | this._column.cells.forEach(function(cell){ 38 | cells.push(cell.getComponent()); 39 | }); 40 | 41 | return cells; 42 | } 43 | 44 | isVisible(){ 45 | return this._column.visible; 46 | } 47 | 48 | show(){ 49 | if(this._column.isGroup){ 50 | this._column.columns.forEach(function(column){ 51 | column.show(); 52 | }); 53 | }else{ 54 | this._column.show(); 55 | } 56 | } 57 | 58 | hide(){ 59 | if(this._column.isGroup){ 60 | this._column.columns.forEach(function(column){ 61 | column.hide(); 62 | }); 63 | }else{ 64 | this._column.hide(); 65 | } 66 | } 67 | 68 | toggle(){ 69 | if(this._column.visible){ 70 | this.hide(); 71 | }else{ 72 | this.show(); 73 | } 74 | } 75 | 76 | delete(){ 77 | return this._column.delete(); 78 | } 79 | 80 | getSubColumns(){ 81 | var output = []; 82 | 83 | if(this._column.columns.length){ 84 | this._column.columns.forEach(function(column){ 85 | output.push(column.getComponent()); 86 | }); 87 | } 88 | 89 | return output; 90 | } 91 | 92 | getParentColumn(){ 93 | return this._column.getParentComponent(); 94 | } 95 | 96 | _getSelf(){ 97 | return this._column; 98 | } 99 | 100 | scrollTo(position, ifVisible){ 101 | return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible); 102 | } 103 | 104 | getTable(){ 105 | return this._column.table; 106 | } 107 | 108 | move(to, after){ 109 | var toColumn = this._column.table.columnManager.findColumn(to); 110 | 111 | if(toColumn){ 112 | this._column.table.columnManager.moveColumn(this._column, toColumn, after); 113 | }else{ 114 | console.warn("Move Error - No matching column found:", toColumn); 115 | } 116 | } 117 | 118 | getNextColumn(){ 119 | var nextCol = this._column.nextColumn(); 120 | 121 | return nextCol ? nextCol.getComponent() : false; 122 | } 123 | 124 | getPrevColumn(){ 125 | var prevCol = this._column.prevColumn(); 126 | 127 | return prevCol ? prevCol.getComponent() : false; 128 | } 129 | 130 | updateDefinition(updates){ 131 | return this._column.updateDefinition(updates); 132 | } 133 | 134 | getWidth(){ 135 | return this._column.getWidth(); 136 | } 137 | 138 | setWidth(width){ 139 | var result; 140 | 141 | if(width === true){ 142 | result = this._column.reinitializeWidth(true); 143 | }else{ 144 | result = this._column.setWidth(width); 145 | } 146 | 147 | this._column.table.columnManager.rerenderColumns(true); 148 | 149 | return result; 150 | } 151 | } -------------------------------------------------------------------------------- /src/js/core/column/defaults/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "title": undefined, 3 | "field": undefined, 4 | "columns": undefined, 5 | "visible": undefined, 6 | "hozAlign": undefined, 7 | "vertAlign": undefined, 8 | "width": undefined, 9 | "minWidth": 40, 10 | "maxWidth": undefined, 11 | "maxInitialWidth": undefined, 12 | "cssClass": undefined, 13 | "variableHeight": undefined, 14 | "headerVertical": undefined, 15 | "headerHozAlign": undefined, 16 | "headerWordWrap": false, 17 | "editableTitle": undefined, 18 | }; -------------------------------------------------------------------------------- /src/js/core/defaults/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | debugEventsExternal:false, //flag to console log events 4 | debugEventsInternal:false, //flag to console log events 5 | debugInvalidOptions:true, //allow toggling of invalid option warnings 6 | debugInvalidComponentFuncs:true, //allow toggling of invalid component warnings 7 | debugInitialization:true, //allow toggling of pre initialization function call warnings 8 | debugDeprecation:true, //allow toggling of deprecation warnings 9 | 10 | height:false, //height of tabulator 11 | minHeight:false, //minimum height of tabulator 12 | maxHeight:false, //maximum height of tabulator 13 | 14 | columnHeaderVertAlign:"top", //vertical alignment of column headers 15 | 16 | popupContainer:false, 17 | 18 | columns:[],//store for colum header info 19 | columnDefaults:{}, //store column default props 20 | rowHeader:false, 21 | 22 | data:false, //default starting data 23 | 24 | autoColumns:false, //build columns from data row structure 25 | autoColumnsDefinitions:false, 26 | 27 | nestedFieldSeparator:".", //separator for nested data 28 | 29 | footerElement:false, //hold footer element 30 | 31 | index:"id", //filed for row index 32 | 33 | textDirection:"auto", 34 | 35 | addRowPos:"bottom", //position to insert blank rows, top|bottom 36 | 37 | headerVisible:true, //hide header 38 | 39 | renderVertical:"virtual", 40 | renderHorizontal:"basic", 41 | renderVerticalBuffer:0, // set virtual DOM buffer size 42 | 43 | scrollToRowPosition:"top", 44 | scrollToRowIfVisible:true, 45 | 46 | scrollToColumnPosition:"left", 47 | scrollToColumnIfVisible:true, 48 | 49 | rowFormatter:false, 50 | rowFormatterPrint:null, 51 | rowFormatterClipboard:null, 52 | rowFormatterHtmlOutput:null, 53 | 54 | rowHeight:null, 55 | 56 | placeholder:false, 57 | 58 | dataLoader:true, 59 | dataLoaderLoading:false, 60 | dataLoaderError:false, 61 | dataLoaderErrorTimeout:3000, 62 | dataSendParams:{}, 63 | dataReceiveParams:{}, 64 | 65 | dependencies:{}, 66 | }; 67 | -------------------------------------------------------------------------------- /src/js/core/modules/core.js: -------------------------------------------------------------------------------- 1 | export {default as LayoutModule} from '../../modules/Layout/Layout.js'; 2 | export {default as LocalizeModule} from '../../modules/Localize/Localize.js'; 3 | export {default as CommsModule} from '../../modules/Comms/Comms.js'; -------------------------------------------------------------------------------- /src/js/core/modules/optional.js: -------------------------------------------------------------------------------- 1 | export {default as AccessorModule} from '../../modules/Accessor/Accessor.js'; 2 | export {default as AjaxModule} from '../../modules/Ajax/Ajax.js'; 3 | export {default as ClipboardModule} from '../../modules/Clipboard/Clipboard.js'; 4 | export {default as ColumnCalcsModule} from '../../modules/ColumnCalcs/ColumnCalcs.js'; 5 | export {default as DataTreeModule} from '../../modules/DataTree/DataTree.js'; 6 | export {default as DownloadModule} from '../../modules/Download/Download.js'; 7 | export {default as EditModule} from '../../modules/Edit/Edit.js'; 8 | export {default as ExportModule} from '../../modules/Export/Export.js'; 9 | export {default as FilterModule} from '../../modules/Filter/Filter.js'; 10 | export {default as FormatModule} from '../../modules/Format/Format.js'; 11 | export {default as FrozenColumnsModule} from '../../modules/FrozenColumns/FrozenColumns.js'; 12 | export {default as FrozenRowsModule} from '../../modules/FrozenRows/FrozenRows.js'; 13 | export {default as GroupRowsModule} from '../../modules/GroupRows/GroupRows.js'; 14 | export {default as HistoryModule} from '../../modules/History/History.js'; 15 | export {default as HtmlTableImportModule} from '../../modules/HtmlTableImport/HtmlTableImport.js'; 16 | export {default as ImportModule} from '../../modules/Import/Import.js'; 17 | export {default as InteractionModule} from '../../modules/Interaction/Interaction.js'; 18 | export {default as KeybindingsModule} from '../../modules/Keybindings/Keybindings.js'; 19 | export {default as MenuModule} from '../../modules/Menu/Menu.js'; 20 | export {default as MoveColumnsModule} from '../../modules/MoveColumns/MoveColumns.js'; 21 | export {default as MoveRowsModule} from '../../modules/MoveRows/MoveRows.js'; 22 | export {default as MutatorModule} from '../../modules/Mutator/Mutator.js'; 23 | export {default as PageModule} from '../../modules/Page/Page.js'; 24 | export {default as PersistenceModule} from '../../modules/Persistence/Persistence.js'; 25 | export {default as PopupModule} from '../../modules/Popup/Popup.js'; 26 | export {default as PrintModule} from '../../modules/Print/Print.js'; 27 | export {default as ReactiveDataModule} from '../../modules/ReactiveData/ReactiveData.js'; 28 | export {default as ResizeColumnsModule} from '../../modules/ResizeColumns/ResizeColumns.js'; 29 | export {default as ResizeRowsModule} from '../../modules/ResizeRows/ResizeRows.js'; 30 | export {default as ResizeTableModule} from '../../modules/ResizeTable/ResizeTable.js'; 31 | export {default as ResponsiveLayoutModule} from '../../modules/ResponsiveLayout/ResponsiveLayout.js'; 32 | export {default as SelectRowModule} from '../../modules/SelectRow/SelectRow.js'; 33 | export {default as SelectRangeModule} from '../../modules/SelectRange/SelectRange.js'; 34 | export {default as SortModule} from '../../modules/Sort/Sort.js'; 35 | export {default as SpreadsheetModule} from '../../modules/Spreadsheet/Spreadsheet.js'; 36 | export {default as TooltipModule} from '../../modules/Tooltip/Tooltip.js'; 37 | export {default as ValidateModule} from '../../modules/Validate/Validate.js'; 38 | -------------------------------------------------------------------------------- /src/js/core/rendering/renderers/BasicHorizontal.js: -------------------------------------------------------------------------------- 1 | import Renderer from '../Renderer.js'; 2 | 3 | export default class BasicHorizontal extends Renderer{ 4 | constructor(table){ 5 | super(table); 6 | } 7 | 8 | renderRowCells(row, inFragment) { 9 | const rowFrag = document.createDocumentFragment(); 10 | row.cells.forEach((cell) => { 11 | rowFrag.appendChild(cell.getElement()); 12 | }); 13 | row.element.appendChild(rowFrag); 14 | 15 | if(!inFragment){ 16 | row.cells.forEach((cell) => { 17 | cell.cellRendered(); 18 | }); 19 | } 20 | } 21 | 22 | reinitializeColumnWidths(columns){ 23 | columns.forEach(function(column){ 24 | column.reinitializeWidth(); 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /src/js/core/rendering/renderers/BasicVertical.js: -------------------------------------------------------------------------------- 1 | import Renderer from '../Renderer.js'; 2 | import Helpers from '../../tools/Helpers.js'; 3 | 4 | export default class BasicVertical extends Renderer{ 5 | constructor(table){ 6 | super(table); 7 | 8 | this.verticalFillMode = "fill"; 9 | 10 | this.scrollTop = 0; 11 | this.scrollLeft = 0; 12 | 13 | this.scrollTop = 0; 14 | this.scrollLeft = 0; 15 | } 16 | 17 | clearRows(){ 18 | var element = this.tableElement; 19 | 20 | // element.children.detach(); 21 | while(element.firstChild) element.removeChild(element.firstChild); 22 | 23 | element.scrollTop = 0; 24 | element.scrollLeft = 0; 25 | 26 | element.style.minWidth = ""; 27 | element.style.minHeight = ""; 28 | element.style.display = ""; 29 | element.style.visibility = ""; 30 | } 31 | 32 | renderRows() { 33 | var element = this.tableElement, 34 | onlyGroupHeaders = true, 35 | tableFrag = document.createDocumentFragment(), 36 | rows = this.rows(); 37 | 38 | rows.forEach((row, index) => { 39 | this.styleRow(row, index); 40 | row.initialize(false, true); 41 | 42 | if (row.type !== "group") { 43 | onlyGroupHeaders = false; 44 | } 45 | 46 | tableFrag.appendChild(row.getElement()); 47 | }); 48 | 49 | element.appendChild(tableFrag); 50 | 51 | rows.forEach((row) => { 52 | row.rendered(); 53 | 54 | if(!row.heightInitialized) { 55 | row.calcHeight(true); 56 | } 57 | }); 58 | 59 | rows.forEach((row) => { 60 | if(!row.heightInitialized) { 61 | row.setCellHeight(); 62 | } 63 | }); 64 | 65 | if(onlyGroupHeaders){ 66 | element.style.minWidth = this.table.columnManager.getWidth() + "px"; 67 | }else{ 68 | element.style.minWidth = ""; 69 | } 70 | } 71 | 72 | 73 | rerenderRows(callback){ 74 | this.clearRows(); 75 | 76 | if(callback){ 77 | callback(); 78 | } 79 | 80 | this.renderRows(); 81 | 82 | if(!this.rows().length){ 83 | this.table.rowManager.tableEmpty(); 84 | } 85 | } 86 | 87 | scrollToRowNearestTop(row){ 88 | var rowTop = Helpers.elOffset(row.getElement()).top; 89 | 90 | return !(Math.abs(this.elementVertical.scrollTop - rowTop) > Math.abs(this.elementVertical.scrollTop + this.elementVertical.clientHeight - rowTop)); 91 | } 92 | 93 | scrollToRow(row){ 94 | var rowEl = row.getElement(); 95 | 96 | this.elementVertical.scrollTop = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top + this.elementVertical.scrollTop; 97 | } 98 | 99 | visibleRows(includingBuffer){ 100 | return this.rows(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/js/core/row/PseudoRow.js: -------------------------------------------------------------------------------- 1 | export default class PseudoRow { 2 | 3 | constructor (type){ 4 | this.type = type; 5 | this.element = this._createElement(); 6 | } 7 | 8 | _createElement(){ 9 | var el = document.createElement("div"); 10 | el.classList.add("tabulator-row"); 11 | return el; 12 | } 13 | 14 | getElement(){ 15 | return this.element; 16 | } 17 | 18 | getComponent(){ 19 | return false; 20 | } 21 | 22 | getData(){ 23 | return {}; 24 | } 25 | 26 | getHeight(){ 27 | return this.element.outerHeight; 28 | } 29 | 30 | initialize(){} 31 | 32 | reinitialize(){} 33 | 34 | normalizeHeight(){} 35 | 36 | generateCells(){} 37 | 38 | reinitializeHeight(){} 39 | 40 | calcHeight(){} 41 | 42 | setCellHeight(){} 43 | 44 | clearCellHeight(){} 45 | 46 | rendered(){} 47 | } -------------------------------------------------------------------------------- /src/js/core/row/RowComponent.js: -------------------------------------------------------------------------------- 1 | //public row object 2 | export default class RowComponent { 3 | 4 | constructor (row){ 5 | this._row = row; 6 | 7 | return new Proxy(this, { 8 | get: function(target, name, receiver) { 9 | if (typeof target[name] !== "undefined") { 10 | return target[name]; 11 | }else{ 12 | return target._row.table.componentFunctionBinder.handle("row", target._row, name); 13 | } 14 | } 15 | }); 16 | } 17 | 18 | getData(transform){ 19 | return this._row.getData(transform); 20 | } 21 | 22 | getElement(){ 23 | return this._row.getElement(); 24 | } 25 | 26 | getCells(){ 27 | var cells = []; 28 | 29 | this._row.getCells().forEach(function(cell){ 30 | cells.push(cell.getComponent()); 31 | }); 32 | 33 | return cells; 34 | } 35 | 36 | getCell(column){ 37 | var cell = this._row.getCell(column); 38 | return cell ? cell.getComponent() : false; 39 | } 40 | 41 | getIndex(){ 42 | return this._row.getData("data")[this._row.table.options.index]; 43 | } 44 | 45 | getPosition(){ 46 | return this._row.getPosition(); 47 | } 48 | 49 | watchPosition(callback){ 50 | return this._row.watchPosition(callback); 51 | } 52 | 53 | delete(){ 54 | return this._row.delete(); 55 | } 56 | 57 | scrollTo(position, ifVisible){ 58 | return this._row.table.rowManager.scrollToRow(this._row, position, ifVisible); 59 | } 60 | 61 | move(to, after){ 62 | this._row.moveToRow(to, after); 63 | } 64 | 65 | update(data){ 66 | return this._row.updateData(data); 67 | } 68 | 69 | normalizeHeight(){ 70 | this._row.normalizeHeight(true); 71 | } 72 | 73 | _getSelf(){ 74 | return this._row; 75 | } 76 | 77 | reformat(){ 78 | return this._row.reinitialize(); 79 | } 80 | 81 | getTable(){ 82 | return this._row.table; 83 | } 84 | 85 | getNextRow(){ 86 | var row = this._row.nextRow(); 87 | return row ? row.getComponent() : row; 88 | } 89 | 90 | getPrevRow(){ 91 | var row = this._row.prevRow(); 92 | return row ? row.getComponent() : row; 93 | } 94 | } -------------------------------------------------------------------------------- /src/js/core/tools/Alert.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from '../CoreFeature.js'; 2 | 3 | export default class Alert extends CoreFeature{ 4 | constructor(table){ 5 | super(table); 6 | 7 | this.element = this._createAlertElement(); 8 | this.msgElement = this._createMsgElement(); 9 | this.type = null; 10 | 11 | this.element.appendChild(this.msgElement); 12 | } 13 | 14 | _createAlertElement(){ 15 | var el = document.createElement("div"); 16 | el.classList.add("tabulator-alert"); 17 | return el; 18 | } 19 | 20 | _createMsgElement(){ 21 | var el = document.createElement("div"); 22 | el.classList.add("tabulator-alert-msg"); 23 | el.setAttribute("role", "alert"); 24 | return el; 25 | } 26 | 27 | _typeClass(){ 28 | return "tabulator-alert-state-" + this.type; 29 | } 30 | 31 | alert(content, type = "msg"){ 32 | if(content){ 33 | this.clear(); 34 | 35 | this.dispatch("alert-show", type); 36 | 37 | this.type = type; 38 | 39 | while(this.msgElement.firstChild) this.msgElement.removeChild(this.msgElement.firstChild); 40 | 41 | this.msgElement.classList.add(this._typeClass()); 42 | 43 | if(typeof content === "function"){ 44 | content = content(); 45 | } 46 | 47 | if(content instanceof HTMLElement){ 48 | this.msgElement.appendChild(content); 49 | }else{ 50 | this.msgElement.innerHTML = content; 51 | } 52 | 53 | this.table.element.appendChild(this.element); 54 | } 55 | } 56 | 57 | clear(){ 58 | this.dispatch("alert-hide", this.type); 59 | 60 | if(this.element.parentNode){ 61 | this.element.parentNode.removeChild(this.element); 62 | } 63 | 64 | this.msgElement.classList.remove(this._typeClass()); 65 | } 66 | } -------------------------------------------------------------------------------- /src/js/core/tools/ComponentFunctionBinder.js: -------------------------------------------------------------------------------- 1 | export default class ComponentFunctionBinder{ 2 | 3 | constructor(table){ 4 | this.table = table; 5 | 6 | this.bindings = {}; 7 | } 8 | 9 | bind(type, funcName, handler){ 10 | if(!this.bindings[type]){ 11 | this.bindings[type] = {}; 12 | } 13 | 14 | if(this.bindings[type][funcName]){ 15 | console.warn("Unable to bind component handler, a matching function name is already bound", type, funcName, handler); 16 | }else{ 17 | this.bindings[type][funcName] = handler; 18 | } 19 | } 20 | 21 | handle(type, component, name){ 22 | if(this.bindings[type] && this.bindings[type][name] && typeof this.bindings[type][name].bind === 'function'){ 23 | return this.bindings[type][name].bind(null, component); 24 | }else{ 25 | if(name !== "then" && typeof name === "string" && !name.startsWith("_")){ 26 | if(this.table.options.debugInvalidComponentFuncs){ 27 | console.error("The " + type + " component does not have a " + name + " function, have you checked that you have the correct Tabulator module installed?"); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/js/core/tools/DependencyRegistry.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from '../CoreFeature.js'; 2 | 3 | export default class DependencyRegistry extends CoreFeature{ 4 | 5 | constructor(table){ 6 | super(table); 7 | 8 | this.deps = {}; 9 | 10 | this.props = { 11 | 12 | }; 13 | } 14 | 15 | initialize(){ 16 | this.deps = Object.assign({}, this.options("dependencies")); 17 | } 18 | 19 | lookup(key, prop, silent){ 20 | if(Array.isArray(key)){ 21 | for (const item of key) { 22 | var match = this.lookup(item, prop, true); 23 | 24 | if(match){ 25 | break; 26 | } 27 | } 28 | 29 | if(match){ 30 | return match; 31 | }else{ 32 | this.error(key); 33 | } 34 | }else{ 35 | if(prop){ 36 | return this.lookupProp(key, prop, silent); 37 | }else{ 38 | return this.lookupKey(key, silent); 39 | } 40 | } 41 | } 42 | 43 | lookupProp(key, prop, silent){ 44 | var dependency; 45 | 46 | if(this.props[key] && this.props[key][prop]){ 47 | return this.props[key][prop]; 48 | }else{ 49 | dependency = this.lookupKey(key, silent); 50 | 51 | if(dependency){ 52 | if(!this.props[key]){ 53 | this.props[key] = {}; 54 | } 55 | 56 | this.props[key][prop] = dependency[prop] || dependency; 57 | return this.props[key][prop]; 58 | } 59 | } 60 | } 61 | 62 | lookupKey(key, silent){ 63 | var dependency; 64 | 65 | if(this.deps[key]){ 66 | dependency = this.deps[key]; 67 | }else if(window[key]){ 68 | this.deps[key] = window[key]; 69 | dependency = this.deps[key]; 70 | }else{ 71 | if(!silent){ 72 | this.error(key); 73 | } 74 | } 75 | 76 | return dependency; 77 | } 78 | 79 | error(key){ 80 | console.error("Unable to find dependency", key, "Please check documentation and ensure you have imported the required library into your project"); 81 | } 82 | } -------------------------------------------------------------------------------- /src/js/core/tools/DeprecationAdvisor.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from '../CoreFeature.js'; 2 | 3 | export default class DeprecationAdvisor extends CoreFeature{ 4 | 5 | constructor(table){ 6 | super(table); 7 | } 8 | 9 | _warnUser(){ 10 | if(this.options("debugDeprecation")){ 11 | console.warn(...arguments); 12 | } 13 | } 14 | 15 | check(oldOption, newOption, convert){ 16 | var msg = ""; 17 | 18 | if(typeof this.options(oldOption) !== "undefined"){ 19 | msg = "Deprecated Setup Option - Use of the %c" + oldOption + "%c option is now deprecated"; 20 | 21 | if(newOption){ 22 | msg = msg + ", Please use the %c" + newOption + "%c option instead"; 23 | this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;'); 24 | 25 | if(convert){ 26 | this.table.options[newOption] = this.table.options[oldOption]; 27 | } 28 | }else{ 29 | this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;'); 30 | } 31 | 32 | return false; 33 | }else{ 34 | return true; 35 | } 36 | } 37 | 38 | checkMsg(oldOption, msg){ 39 | if(typeof this.options(oldOption) !== "undefined"){ 40 | this._warnUser("%cDeprecated Setup Option - Use of the %c" + oldOption + " %c option is now deprecated, " + msg, 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;'); 41 | 42 | return false; 43 | }else{ 44 | return true; 45 | } 46 | } 47 | 48 | msg(msg){ 49 | this._warnUser(msg); 50 | } 51 | } -------------------------------------------------------------------------------- /src/js/core/tools/ExternalEventBus.js: -------------------------------------------------------------------------------- 1 | export default class ExternalEventBus { 2 | 3 | constructor(table, optionsList, debug){ 4 | this.table = table; 5 | this.events = {}; 6 | this.optionsList = optionsList || {}; 7 | this.subscriptionNotifiers = {}; 8 | 9 | this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this); 10 | this.debug = debug; 11 | } 12 | 13 | subscriptionChange(key, callback){ 14 | if(!this.subscriptionNotifiers[key]){ 15 | this.subscriptionNotifiers[key] = []; 16 | } 17 | 18 | this.subscriptionNotifiers[key].push(callback); 19 | 20 | if(this.subscribed(key)){ 21 | this._notifySubscriptionChange(key, true); 22 | } 23 | } 24 | 25 | subscribe(key, callback){ 26 | if(!this.events[key]){ 27 | this.events[key] = []; 28 | } 29 | 30 | this.events[key].push(callback); 31 | 32 | this._notifySubscriptionChange(key, true); 33 | } 34 | 35 | unsubscribe(key, callback){ 36 | var index; 37 | 38 | if(this.events[key]){ 39 | if(callback){ 40 | index = this.events[key].findIndex((item) => { 41 | return item === callback; 42 | }); 43 | 44 | if(index > -1){ 45 | this.events[key].splice(index, 1); 46 | }else{ 47 | console.warn("Cannot remove event, no matching event found:", key, callback); 48 | return; 49 | } 50 | }else{ 51 | delete this.events[key]; 52 | } 53 | }else{ 54 | console.warn("Cannot remove event, no events set on:", key); 55 | return; 56 | } 57 | 58 | this._notifySubscriptionChange(key, false); 59 | } 60 | 61 | subscribed(key){ 62 | return this.events[key] && this.events[key].length; 63 | } 64 | 65 | _notifySubscriptionChange(key, subscribed){ 66 | var notifiers = this.subscriptionNotifiers[key]; 67 | 68 | if(notifiers){ 69 | notifiers.forEach((callback)=>{ 70 | callback(subscribed); 71 | }); 72 | } 73 | } 74 | 75 | _dispatch(){ 76 | var args = Array.from(arguments), 77 | key = args.shift(), 78 | result; 79 | 80 | if(this.events[key]){ 81 | this.events[key].forEach((callback, i) => { 82 | let callResult = callback.apply(this.table, args); 83 | 84 | if(!i){ 85 | result = callResult; 86 | } 87 | }); 88 | } 89 | 90 | return result; 91 | } 92 | 93 | _debugDispatch(){ 94 | var args = Array.from(arguments), 95 | key = args[0]; 96 | 97 | args[0] = "ExternalEvent:" + args[0]; 98 | 99 | if(this.debug === true || this.debug.includes(key)){ 100 | console.log(...args); 101 | } 102 | 103 | return this._dispatch(...arguments); 104 | } 105 | } -------------------------------------------------------------------------------- /src/js/core/tools/Helpers.js: -------------------------------------------------------------------------------- 1 | export default class Helpers{ 2 | 3 | static elVisible(el){ 4 | return !(el.offsetWidth <= 0 && el.offsetHeight <= 0); 5 | } 6 | 7 | static elOffset(el){ 8 | var box = el.getBoundingClientRect(); 9 | 10 | return { 11 | top: box.top + window.pageYOffset - document.documentElement.clientTop, 12 | left: box.left + window.pageXOffset - document.documentElement.clientLeft 13 | }; 14 | } 15 | 16 | static retrieveNestedData(separator, field, data){ 17 | var structure = separator ? field.split(separator) : [field], 18 | length = structure.length, 19 | output; 20 | 21 | for(let i = 0; i < length; i++){ 22 | 23 | data = data[structure[i]]; 24 | 25 | output = data; 26 | 27 | if(!data){ 28 | break; 29 | } 30 | } 31 | 32 | return output; 33 | } 34 | 35 | static deepClone(obj, clone, list = []){ 36 | var objectProto = {}.__proto__, 37 | arrayProto = [].__proto__; 38 | 39 | if (!clone){ 40 | clone = Object.assign(Array.isArray(obj) ? [] : {}, obj); 41 | } 42 | 43 | for(var i in obj) { 44 | let subject = obj[i], 45 | match, copy; 46 | 47 | if(subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)){ 48 | match = list.findIndex((item) => { 49 | return item.subject === subject; 50 | }); 51 | 52 | if(match > -1){ 53 | clone[i] = list[match].copy; 54 | }else{ 55 | copy = Object.assign(Array.isArray(subject) ? [] : {}, subject); 56 | 57 | list.unshift({subject, copy}); 58 | 59 | clone[i] = this.deepClone(subject, copy, list); 60 | } 61 | } 62 | } 63 | 64 | return clone; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/js/core/tools/OptionsList.js: -------------------------------------------------------------------------------- 1 | export default class OptionsList { 2 | constructor(table, msgType, defaults = {}){ 3 | this.table = table; 4 | this.msgType = msgType; 5 | this.registeredDefaults = Object.assign({}, defaults); 6 | } 7 | 8 | register(option, value){ 9 | this.registeredDefaults[option] = value; 10 | } 11 | 12 | generate(defaultOptions, userOptions = {}){ 13 | var output = Object.assign({}, this.registeredDefaults), 14 | warn = this.table.options.debugInvalidOptions || userOptions.debugInvalidOptions === true; 15 | 16 | Object.assign(output, defaultOptions); 17 | 18 | for (let key in userOptions){ 19 | if(!output.hasOwnProperty(key)){ 20 | if(warn){ 21 | console.warn("Invalid " + this.msgType + " option:", key); 22 | } 23 | 24 | output[key] = userOptions.key; 25 | } 26 | } 27 | 28 | 29 | for (let key in output){ 30 | if(key in userOptions){ 31 | output[key] = userOptions[key]; 32 | }else{ 33 | if(Array.isArray(output[key])){ 34 | output[key] = Object.assign([], output[key]); 35 | }else if(typeof output[key] === "object" && output[key] !== null){ 36 | output[key] = Object.assign({}, output[key]); 37 | }else if (typeof output[key] === "undefined"){ 38 | delete output[key]; 39 | } 40 | } 41 | } 42 | 43 | return output; 44 | } 45 | } -------------------------------------------------------------------------------- /src/js/core/tools/TableRegistry.js: -------------------------------------------------------------------------------- 1 | export default class TableRegistry { 2 | static registry = { 3 | tables:[], 4 | 5 | register(table){ 6 | TableRegistry.registry.tables.push(table); 7 | }, 8 | 9 | deregister(table){ 10 | var index = TableRegistry.registry.tables.indexOf(table); 11 | 12 | if(index > -1){ 13 | TableRegistry.registry.tables.splice(index, 1); 14 | } 15 | }, 16 | 17 | lookupTable(query, silent){ 18 | var results = [], 19 | matches, match; 20 | 21 | if(typeof query === "string"){ 22 | matches = document.querySelectorAll(query); 23 | 24 | if(matches.length){ 25 | for(var i = 0; i < matches.length; i++){ 26 | match = TableRegistry.registry.matchElement(matches[i]); 27 | 28 | if(match){ 29 | results.push(match); 30 | } 31 | } 32 | } 33 | 34 | }else if((typeof HTMLElement !== "undefined" && query instanceof HTMLElement) || query instanceof TableRegistry){ 35 | match = TableRegistry.registry.matchElement(query); 36 | 37 | if(match){ 38 | results.push(match); 39 | } 40 | }else if(Array.isArray(query)){ 41 | query.forEach(function(item){ 42 | results = results.concat(TableRegistry.registry.lookupTable(item)); 43 | }); 44 | }else{ 45 | if(!silent){ 46 | console.warn("Table Connection Error - Invalid Selector", query); 47 | } 48 | } 49 | 50 | return results; 51 | }, 52 | 53 | matchElement(element){ 54 | return TableRegistry.registry.tables.find(function(table){ 55 | return element instanceof TableRegistry ? table === element : table.element === element; 56 | }); 57 | } 58 | }; 59 | 60 | 61 | static findTable(query){ 62 | var results = TableRegistry.registry.lookupTable(query, true); 63 | return Array.isArray(results) && !results.length ? false : results; 64 | } 65 | } -------------------------------------------------------------------------------- /src/js/modules/Accessor/Accessor.js: -------------------------------------------------------------------------------- 1 | import Module from '../../core/Module.js'; 2 | import Helpers from '../../core/tools/Helpers.js'; 3 | 4 | import defaultAccessors from './defaults/accessors.js'; 5 | 6 | export default class Accessor extends Module{ 7 | 8 | static moduleName = "accessor"; 9 | 10 | //load defaults 11 | static accessors = defaultAccessors; 12 | 13 | constructor(table){ 14 | super(table); 15 | 16 | this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types 17 | 18 | this.registerColumnOption("accessor"); 19 | this.registerColumnOption("accessorParams"); 20 | this.registerColumnOption("accessorData"); 21 | this.registerColumnOption("accessorDataParams"); 22 | this.registerColumnOption("accessorDownload"); 23 | this.registerColumnOption("accessorDownloadParams"); 24 | this.registerColumnOption("accessorClipboard"); 25 | this.registerColumnOption("accessorClipboardParams"); 26 | this.registerColumnOption("accessorPrint"); 27 | this.registerColumnOption("accessorPrintParams"); 28 | this.registerColumnOption("accessorHtmlOutput"); 29 | this.registerColumnOption("accessorHtmlOutputParams"); 30 | } 31 | 32 | initialize(){ 33 | this.subscribe("column-layout", this.initializeColumn.bind(this)); 34 | this.subscribe("row-data-retrieve", this.transformRow.bind(this)); 35 | } 36 | 37 | //initialize column accessor 38 | initializeColumn(column){ 39 | var match = false, 40 | config = {}; 41 | 42 | this.allowedTypes.forEach((type) => { 43 | var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), 44 | accessor; 45 | 46 | if(column.definition[key]){ 47 | accessor = this.lookupAccessor(column.definition[key]); 48 | 49 | if(accessor){ 50 | match = true; 51 | 52 | config[key] = { 53 | accessor:accessor, 54 | params: column.definition[key + "Params"] || {}, 55 | }; 56 | } 57 | } 58 | }); 59 | 60 | if(match){ 61 | column.modules.accessor = config; 62 | } 63 | } 64 | 65 | lookupAccessor(value){ 66 | var accessor = false; 67 | 68 | //set column accessor 69 | switch(typeof value){ 70 | case "string": 71 | if(Accessor.accessors[value]){ 72 | accessor = Accessor.accessors[value]; 73 | }else{ 74 | console.warn("Accessor Error - No such accessor found, ignoring: ", value); 75 | } 76 | break; 77 | 78 | case "function": 79 | accessor = value; 80 | break; 81 | } 82 | 83 | return accessor; 84 | } 85 | 86 | //apply accessor to row 87 | transformRow(row, type){ 88 | var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), 89 | rowComponent = row.getComponent(); 90 | 91 | //clone data object with deep copy to isolate internal data from returned result 92 | var data = Helpers.deepClone(row.data || {}); 93 | 94 | this.table.columnManager.traverse(function(column){ 95 | var value, accessor, params, colComponent; 96 | 97 | if(column.modules.accessor){ 98 | 99 | accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false; 100 | 101 | if(accessor){ 102 | value = column.getFieldValue(data); 103 | 104 | if(value != "undefined"){ 105 | colComponent = column.getComponent(); 106 | params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params; 107 | column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent)); 108 | } 109 | } 110 | } 111 | }); 112 | 113 | return data; 114 | } 115 | } -------------------------------------------------------------------------------- /src/js/modules/Accessor/defaults/accessors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | rownum:function(value, data, type, params, column, row){ 3 | return row.getPosition(); 4 | } 5 | }; -------------------------------------------------------------------------------- /src/js/modules/Ajax/defaults/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | method: "GET", 3 | }; -------------------------------------------------------------------------------- /src/js/modules/Ajax/defaults/contentTypeFormatters.js: -------------------------------------------------------------------------------- 1 | function generateParamsList(data, prefix){ 2 | var output = []; 3 | 4 | prefix = prefix || ""; 5 | 6 | if(Array.isArray(data)){ 7 | data.forEach((item, i) => { 8 | output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i)); 9 | }); 10 | }else if (typeof data === "object"){ 11 | for (var key in data){ 12 | output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key)); 13 | } 14 | }else{ 15 | output.push({key:prefix, value:data}); 16 | } 17 | 18 | return output; 19 | } 20 | 21 | export default { 22 | "json":{ 23 | headers:{ 24 | 'Content-Type': 'application/json', 25 | }, 26 | body:function(url, config, params){ 27 | return JSON.stringify(params); 28 | }, 29 | }, 30 | "form":{ 31 | headers:{ 32 | }, 33 | body:function(url, config, params){ 34 | 35 | var output = generateParamsList(params), 36 | form = new FormData(); 37 | 38 | output.forEach(function(item){ 39 | form.append(item.key, item.value); 40 | }); 41 | 42 | return form; 43 | }, 44 | }, 45 | }; -------------------------------------------------------------------------------- /src/js/modules/Ajax/defaults/loaderPromise.js: -------------------------------------------------------------------------------- 1 | export default function(url, config, params){ 2 | var contentType; 3 | 4 | return new Promise((resolve, reject) => { 5 | //set url 6 | url = this.urlGenerator.call(this.table, url, config, params); 7 | 8 | //set body content if not GET request 9 | if(config.method.toUpperCase() != "GET"){ 10 | contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType]; 11 | if(contentType){ 12 | 13 | for(var key in contentType.headers){ 14 | if(!config.headers){ 15 | config.headers = {}; 16 | } 17 | 18 | if(typeof config.headers[key] === "undefined"){ 19 | config.headers[key] = contentType.headers[key]; 20 | } 21 | } 22 | 23 | config.body = contentType.body.call(this, url, config, params); 24 | 25 | }else{ 26 | console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType); 27 | } 28 | } 29 | 30 | if(url){ 31 | //configure headers 32 | if(typeof config.headers === "undefined"){ 33 | config.headers = {}; 34 | } 35 | 36 | if(typeof config.headers.Accept === "undefined"){ 37 | config.headers.Accept = "application/json"; 38 | } 39 | 40 | if(typeof config.headers["X-Requested-With"] === "undefined"){ 41 | config.headers["X-Requested-With"] = "XMLHttpRequest"; 42 | } 43 | 44 | if(typeof config.mode === "undefined"){ 45 | config.mode = "cors"; 46 | } 47 | 48 | if(config.mode == "cors"){ 49 | if(typeof config.headers["Origin"] === "undefined"){ 50 | config.headers["Origin"] = window.location.origin; 51 | } 52 | 53 | if(typeof config.credentials === "undefined"){ 54 | config.credentials = 'same-origin'; 55 | } 56 | }else{ 57 | if(typeof config.credentials === "undefined"){ 58 | config.credentials = 'include'; 59 | } 60 | } 61 | 62 | //send request 63 | fetch(url, config) 64 | .then((response)=>{ 65 | if(response.ok) { 66 | response.json() 67 | .then((data)=>{ 68 | resolve(data); 69 | }).catch((error)=>{ 70 | reject(error); 71 | console.warn("Ajax Load Error - Invalid JSON returned", error); 72 | }); 73 | }else{ 74 | console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText); 75 | reject(response); 76 | } 77 | }) 78 | .catch((error)=>{ 79 | console.error("Ajax Load Error - Connection Error: ", error); 80 | reject(error); 81 | }); 82 | }else{ 83 | console.warn("Ajax Load Error - No URL Set"); 84 | resolve([]); 85 | } 86 | }); 87 | } -------------------------------------------------------------------------------- /src/js/modules/Ajax/defaults/urlGenerator.js: -------------------------------------------------------------------------------- 1 | function generateParamsList(data, prefix){ 2 | var output = []; 3 | 4 | prefix = prefix || ""; 5 | 6 | if(Array.isArray(data)){ 7 | data.forEach((item, i) => { 8 | output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i)); 9 | }); 10 | }else if (typeof data === "object"){ 11 | for (var key in data){ 12 | output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key)); 13 | } 14 | }else{ 15 | output.push({key:prefix, value:data}); 16 | } 17 | 18 | return output; 19 | } 20 | 21 | function serializeParams(params){ 22 | var output = generateParamsList(params), 23 | encoded = []; 24 | 25 | output.forEach(function(item){ 26 | encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value)); 27 | }); 28 | 29 | return encoded.join("&"); 30 | } 31 | 32 | export default function(url, config, params){ 33 | if(url){ 34 | if(params && Object.keys(params).length){ 35 | if(!config.method || config.method.toLowerCase() == "get"){ 36 | config.method = "get"; 37 | 38 | url += (url.includes("?") ? "&" : "?") + serializeParams(params); 39 | } 40 | } 41 | } 42 | 43 | return url; 44 | } -------------------------------------------------------------------------------- /src/js/modules/Clipboard/defaults/pasteActions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | replace:function(data){ 3 | return this.table.setData(data); 4 | }, 5 | update:function(data){ 6 | return this.table.updateOrAddData(data); 7 | }, 8 | insert:function(data){ 9 | return this.table.addData(data); 10 | }, 11 | }; -------------------------------------------------------------------------------- /src/js/modules/Clipboard/defaults/pasteParsers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | table:function(clipboard){ 3 | var data = [], 4 | headerFindSuccess = true, 5 | columns = this.table.columnManager.columns, 6 | columnMap = [], 7 | rows = []; 8 | 9 | //get data from clipboard into array of columns and rows. 10 | clipboard = clipboard.split("\n"); 11 | 12 | clipboard.forEach(function(row){ 13 | data.push(row.split("\t")); 14 | }); 15 | 16 | if(data.length && !(data.length === 1 && data[0].length < 2)){ 17 | 18 | //check if headers are present by title 19 | data[0].forEach(function(value){ 20 | var column = columns.find(function(column){ 21 | return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim(); 22 | }); 23 | 24 | if(column){ 25 | columnMap.push(column); 26 | }else{ 27 | headerFindSuccess = false; 28 | } 29 | }); 30 | 31 | //check if column headers are present by field 32 | if(!headerFindSuccess){ 33 | headerFindSuccess = true; 34 | columnMap = []; 35 | 36 | data[0].forEach(function(value){ 37 | var column = columns.find(function(column){ 38 | return value && column.field && value.trim() && column.field.trim() === value.trim(); 39 | }); 40 | 41 | if(column){ 42 | columnMap.push(column); 43 | }else{ 44 | headerFindSuccess = false; 45 | } 46 | }); 47 | 48 | if(!headerFindSuccess){ 49 | columnMap = this.table.columnManager.columnsByIndex; 50 | } 51 | } 52 | 53 | //remove header row if found 54 | if(headerFindSuccess){ 55 | data.shift(); 56 | } 57 | 58 | data.forEach(function(item){ 59 | var row = {}; 60 | 61 | item.forEach(function(value, i){ 62 | if(columnMap[i]){ 63 | row[columnMap[i].field] = value; 64 | } 65 | }); 66 | 67 | rows.push(row); 68 | }); 69 | 70 | return rows; 71 | }else{ 72 | return false; 73 | } 74 | }, 75 | }; -------------------------------------------------------------------------------- /src/js/modules/Clipboard/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | import bindings from './keybindings/bindings.js'; 2 | import actions from './keybindings/actions.js'; 3 | 4 | export default { 5 | keybindings:{ 6 | bindings:bindings, 7 | actions:actions 8 | }, 9 | }; -------------------------------------------------------------------------------- /src/js/modules/Clipboard/extensions/keybindings/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | copyToClipboard:function(e){ 3 | if(!this.table.modules.edit.currentCell){ 4 | if(this.table.modExists("clipboard", true)){ 5 | this.table.modules.clipboard.copy(false, true); 6 | } 7 | } 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/js/modules/Clipboard/extensions/keybindings/bindings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | copyToClipboard:["ctrl + 67", "meta + 67"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/js/modules/ColumnCalcs/CalcComponent.js: -------------------------------------------------------------------------------- 1 | export default class CalcComponent{ 2 | constructor (row){ 3 | this._row = row; 4 | 5 | return new Proxy(this, { 6 | get: function(target, name, receiver) { 7 | if (typeof target[name] !== "undefined") { 8 | return target[name]; 9 | }else{ 10 | return target._row.table.componentFunctionBinder.handle("row", target._row, name); 11 | } 12 | } 13 | }); 14 | } 15 | 16 | getData(transform){ 17 | return this._row.getData(transform); 18 | } 19 | 20 | getElement(){ 21 | return this._row.getElement(); 22 | } 23 | 24 | getTable(){ 25 | return this._row.table; 26 | } 27 | 28 | getCells(){ 29 | var cells = []; 30 | 31 | this._row.getCells().forEach(function(cell){ 32 | cells.push(cell.getComponent()); 33 | }); 34 | 35 | return cells; 36 | } 37 | 38 | getCell(column){ 39 | var cell = this._row.getCell(column); 40 | return cell ? cell.getComponent() : false; 41 | } 42 | 43 | _getSelf(){ 44 | return this._row; 45 | } 46 | } -------------------------------------------------------------------------------- /src/js/modules/ColumnCalcs/defaults/calculations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "avg":function(values, data, calcParams){ 3 | var output = 0, 4 | precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : 2; 5 | 6 | if(values.length){ 7 | output = values.reduce(function(sum, value){ 8 | return Number(sum) + Number(value); 9 | }); 10 | 11 | output = output / values.length; 12 | 13 | output = precision !== false ? output.toFixed(precision) : output; 14 | } 15 | 16 | return parseFloat(output).toString(); 17 | }, 18 | "max":function(values, data, calcParams){ 19 | var output = null, 20 | precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; 21 | 22 | values.forEach(function(value){ 23 | 24 | value = Number(value); 25 | 26 | if(value > output || output === null){ 27 | output = value; 28 | } 29 | }); 30 | 31 | return output !== null ? (precision !== false ? output.toFixed(precision) : output) : ""; 32 | }, 33 | "min":function(values, data, calcParams){ 34 | var output = null, 35 | precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; 36 | 37 | values.forEach(function(value){ 38 | 39 | value = Number(value); 40 | 41 | if(value < output || output === null){ 42 | output = value; 43 | } 44 | }); 45 | 46 | return output !== null ? (precision !== false ? output.toFixed(precision) : output) : ""; 47 | }, 48 | "sum":function(values, data, calcParams){ 49 | var output = 0, 50 | precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; 51 | 52 | if(values.length){ 53 | values.forEach(function(value){ 54 | value = Number(value); 55 | 56 | output += !isNaN(value) ? Number(value) : 0; 57 | }); 58 | } 59 | 60 | return precision !== false ? output.toFixed(precision) : output; 61 | }, 62 | "concat":function(values, data, calcParams){ 63 | var output = 0; 64 | 65 | if(values.length){ 66 | output = values.reduce(function(sum, value){ 67 | return String(sum) + String(value); 68 | }); 69 | } 70 | 71 | return output; 72 | }, 73 | "count":function(values, data, calcParams){ 74 | var output = 0; 75 | 76 | if(values.length){ 77 | values.forEach(function(value){ 78 | if(value){ 79 | output ++; 80 | } 81 | }); 82 | } 83 | 84 | return output; 85 | }, 86 | "unique":function(values, data, calcParams){ 87 | var unique = values.filter((value, index) => { 88 | return (values || value === 0) && values.indexOf(value) === index; 89 | }); 90 | 91 | return unique.length; 92 | }, 93 | }; -------------------------------------------------------------------------------- /src/js/modules/Comms/Comms.js: -------------------------------------------------------------------------------- 1 | import Module from '../../core/Module.js'; 2 | 3 | export default class Comms extends Module{ 4 | 5 | static moduleName = "comms"; 6 | 7 | constructor(table){ 8 | super(table); 9 | } 10 | 11 | initialize(){ 12 | this.registerTableFunction("tableComms", this.receive.bind(this)); 13 | } 14 | 15 | getConnections(selectors){ 16 | var connections = [], 17 | connection; 18 | 19 | connection = this.table.constructor.registry.lookupTable(selectors); 20 | 21 | connection.forEach((con) =>{ 22 | if(this.table !== con){ 23 | connections.push(con); 24 | } 25 | }); 26 | 27 | return connections; 28 | } 29 | 30 | send(selectors, module, action, data){ 31 | var connections = this.getConnections(selectors); 32 | 33 | connections.forEach((connection) => { 34 | connection.tableComms(this.table.element, module, action, data); 35 | }); 36 | 37 | if(!connections.length && selectors){ 38 | console.warn("Table Connection Error - No tables matching selector found", selectors); 39 | } 40 | } 41 | 42 | receive(table, module, action, data){ 43 | if(this.table.modExists(module)){ 44 | return this.table.modules[module].commsReceived(table, action, data); 45 | }else{ 46 | console.warn("Inter-table Comms Error - no such module:", module); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders.js: -------------------------------------------------------------------------------- 1 | import csv from './downloaders/csv.js'; 2 | import json from './downloaders/json.js'; 3 | import pdf from './downloaders/pdf.js'; 4 | import xlsx from './downloaders/xlsx.js'; 5 | import html from './downloaders/html.js'; 6 | import jsonLines from './downloaders/jsonLines.js'; 7 | 8 | export default { 9 | csv:csv, 10 | json:json, 11 | jsonLines:jsonLines, 12 | pdf:pdf, 13 | xlsx:xlsx, 14 | html:html, 15 | }; -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/csv.js: -------------------------------------------------------------------------------- 1 | export default function(list, options = {}, setFileContents){ 2 | var delimiter = options.delimiter ? options.delimiter : ",", 3 | fileContents = [], 4 | headers = []; 5 | 6 | list.forEach((row) => { 7 | var item = []; 8 | 9 | switch(row.type){ 10 | case "group": 11 | console.warn("Download Warning - CSV downloader cannot process row groups"); 12 | break; 13 | 14 | case "calc": 15 | console.warn("Download Warning - CSV downloader cannot process column calculations"); 16 | break; 17 | 18 | case "header": 19 | row.columns.forEach((col, i) => { 20 | if(col && col.depth === 1){ 21 | headers[i] = typeof col.value == "undefined" || col.value === null ? "" : ('"' + String(col.value).split('"').join('""') + '"'); 22 | } 23 | }); 24 | break; 25 | 26 | case "row": 27 | row.columns.forEach((col) => { 28 | 29 | if(col){ 30 | 31 | switch(typeof col.value){ 32 | case "object": 33 | col.value = col.value !== null ? JSON.stringify(col.value) : ""; 34 | break; 35 | 36 | case "undefined": 37 | col.value = ""; 38 | break; 39 | } 40 | 41 | item.push('"' + String(col.value).split('"').join('""') + '"'); 42 | } 43 | }); 44 | 45 | fileContents.push(item.join(delimiter)); 46 | break; 47 | } 48 | }); 49 | 50 | if(headers.length){ 51 | fileContents.unshift(headers.join(delimiter)); 52 | } 53 | 54 | fileContents = fileContents.join("\n"); 55 | 56 | if(options.bom){ 57 | fileContents = "\ufeff" + fileContents; 58 | } 59 | 60 | setFileContents(fileContents, "text/csv"); 61 | } 62 | -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/html.js: -------------------------------------------------------------------------------- 1 | export default function(list, options, setFileContents){ 2 | if(this.modExists("export", true)){ 3 | setFileContents(this.modules.export.generateHTMLTable(list), "text/html"); 4 | } 5 | } -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/json.js: -------------------------------------------------------------------------------- 1 | export default function(list, options, setFileContents){ 2 | var fileContents = []; 3 | 4 | list.forEach((row) => { 5 | var item = {}; 6 | 7 | switch(row.type){ 8 | case "header": 9 | break; 10 | 11 | case "group": 12 | console.warn("Download Warning - JSON downloader cannot process row groups"); 13 | break; 14 | 15 | case "calc": 16 | console.warn("Download Warning - JSON downloader cannot process column calculations"); 17 | break; 18 | 19 | case "row": 20 | row.columns.forEach((col) => { 21 | if(col){ 22 | item[col.component.getTitleDownload() || col.component.getField()] = col.value; 23 | } 24 | }); 25 | 26 | fileContents.push(item); 27 | break; 28 | } 29 | }); 30 | 31 | fileContents = JSON.stringify(fileContents, null, '\t'); 32 | 33 | setFileContents(fileContents, "application/json"); 34 | } -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/jsonLines.js: -------------------------------------------------------------------------------- 1 | export default function (list, options, setFileContents) { 2 | const fileContents = []; 3 | 4 | list.forEach((row) => { 5 | const item = {}; 6 | 7 | switch (row.type) { 8 | case "header": 9 | break; 10 | 11 | case "group": 12 | console.warn("Download Warning - JSON downloader cannot process row groups"); 13 | break; 14 | 15 | case "calc": 16 | console.warn("Download Warning - JSON downloader cannot process column calculations"); 17 | break; 18 | 19 | case "row": 20 | row.columns.forEach((col) => { 21 | if (col) { 22 | item[col.component.getTitleDownload() || col.component.getField()] = col.value; 23 | } 24 | }); 25 | 26 | fileContents.push(JSON.stringify(item)); 27 | break; 28 | } 29 | }); 30 | 31 | setFileContents(fileContents.join("\n"), "application/x-ndjson"); 32 | } -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/pdf.js: -------------------------------------------------------------------------------- 1 | export default function(list, options = {}, setFileContents){ 2 | var header = [], 3 | body = [], 4 | autoTableParams = {}, 5 | rowGroupStyles = options.rowGroupStyles || { 6 | fontStyle: "bold", 7 | fontSize: 12, 8 | cellPadding: 6, 9 | fillColor: 220, 10 | }, 11 | rowCalcStyles = options.rowCalcStyles || { 12 | fontStyle: "bold", 13 | fontSize: 10, 14 | cellPadding: 4, 15 | fillColor: 232, 16 | }, 17 | jsPDFParams = options.jsPDF || {}, 18 | title = options.title ? options.title : "", 19 | jspdfLib, doc; 20 | 21 | if(!jsPDFParams.orientation){ 22 | jsPDFParams.orientation = options.orientation || "landscape"; 23 | } 24 | 25 | if(!jsPDFParams.unit){ 26 | jsPDFParams.unit = "pt"; 27 | } 28 | 29 | //parse row list 30 | list.forEach((row) => { 31 | switch(row.type){ 32 | case "header": 33 | header.push(parseRow(row)); 34 | break; 35 | 36 | case "group": 37 | body.push(parseRow(row, rowGroupStyles)); 38 | break; 39 | 40 | case "calc": 41 | body.push(parseRow(row, rowCalcStyles)); 42 | break; 43 | 44 | case "row": 45 | body.push(parseRow(row)); 46 | break; 47 | } 48 | }); 49 | 50 | function parseRow(row, styles){ 51 | var rowData = []; 52 | 53 | row.columns.forEach((col) =>{ 54 | var cell; 55 | 56 | if(col){ 57 | switch(typeof col.value){ 58 | case "object": 59 | col.value = col.value !== null ? JSON.stringify(col.value) : ""; 60 | break; 61 | 62 | case "undefined": 63 | col.value = ""; 64 | break; 65 | } 66 | 67 | cell = { 68 | content:col.value, 69 | colSpan:col.width, 70 | rowSpan:col.height, 71 | }; 72 | 73 | if(styles){ 74 | cell.styles = styles; 75 | } 76 | 77 | rowData.push(cell); 78 | } 79 | }); 80 | 81 | return rowData; 82 | } 83 | 84 | 85 | //configure PDF 86 | jspdfLib = this.dependencyRegistry.lookup("jspdf", "jsPDF"); 87 | doc = new jspdfLib(jsPDFParams); //set document to landscape, better for most tables 88 | 89 | if(options.autoTable){ 90 | if(typeof options.autoTable === "function"){ 91 | autoTableParams = options.autoTable(doc) || {}; 92 | }else{ 93 | autoTableParams = options.autoTable; 94 | } 95 | } 96 | 97 | if(title){ 98 | autoTableParams.didDrawPage = function(data) { 99 | doc.text(title, 40, 30); 100 | }; 101 | } 102 | 103 | autoTableParams.head = header; 104 | autoTableParams.body = body; 105 | 106 | doc.autoTable(autoTableParams); 107 | 108 | if(options.documentProcessing){ 109 | options.documentProcessing(doc); 110 | } 111 | 112 | setFileContents(doc.output("arraybuffer"), "application/pdf"); 113 | } 114 | -------------------------------------------------------------------------------- /src/js/modules/Download/defaults/downloaders/xlsx.js: -------------------------------------------------------------------------------- 1 | import CoreFeature from '../../../../core/CoreFeature.js'; 2 | 3 | export default function(list, options, setFileContents){ 4 | var self = this, 5 | sheetName = options.sheetName || "Sheet1", 6 | XLSXLib = this.dependencyRegistry.lookup("XLSX"), 7 | workbook = XLSXLib.utils.book_new(), 8 | tableFeatures = new CoreFeature(this), 9 | compression = 'compress' in options ? options.compress : true, 10 | writeOptions = options.writeOptions || {bookType:'xlsx', bookSST:true, compression}, 11 | output; 12 | 13 | writeOptions.type = 'binary'; 14 | 15 | workbook.SheetNames = []; 16 | workbook.Sheets = {}; 17 | 18 | function generateSheet(){ 19 | var rows = [], 20 | merges = [], 21 | worksheet = {}, 22 | range = {s: {c:0, r:0}, e: {c:(list[0] ? list[0].columns.reduce((a, b) => a + (b && b.width ? b.width : 1), 0) : 0), r:list.length }}; 23 | 24 | //parse row list 25 | list.forEach((row, i) => { 26 | var rowData = []; 27 | 28 | row.columns.forEach(function(col, j){ 29 | 30 | if(col){ 31 | rowData.push(!(col.value instanceof Date) && typeof col.value === "object" ? JSON.stringify(col.value) : col.value); 32 | 33 | if(col.width > 1 || col.height > -1){ 34 | if(col.height > 1 || col.width > 1){ 35 | merges.push({s:{r:i,c:j},e:{r:i + col.height - 1,c:j + col.width - 1}}); 36 | } 37 | } 38 | }else{ 39 | rowData.push(""); 40 | } 41 | }); 42 | 43 | rows.push(rowData); 44 | }); 45 | 46 | //convert rows to worksheet 47 | XLSXLib.utils.sheet_add_aoa(worksheet, rows); 48 | 49 | worksheet['!ref'] = XLSXLib.utils.encode_range(range); 50 | 51 | if(merges.length){ 52 | worksheet["!merges"] = merges; 53 | } 54 | 55 | return worksheet; 56 | } 57 | 58 | if(options.sheetOnly){ 59 | setFileContents(generateSheet()); 60 | return; 61 | } 62 | 63 | if(options.sheets){ 64 | for(var sheet in options.sheets){ 65 | 66 | if(options.sheets[sheet] === true){ 67 | workbook.SheetNames.push(sheet); 68 | workbook.Sheets[sheet] = generateSheet(); 69 | }else{ 70 | 71 | workbook.SheetNames.push(sheet); 72 | 73 | tableFeatures.commsSend(options.sheets[sheet], "download", "intercept",{ 74 | type:"xlsx", 75 | options:{sheetOnly:true}, 76 | active:self.active, 77 | intercept:function(data){ 78 | workbook.Sheets[sheet] = data; 79 | } 80 | }); 81 | } 82 | } 83 | }else{ 84 | workbook.SheetNames.push(sheetName); 85 | workbook.Sheets[sheetName] = generateSheet(); 86 | } 87 | 88 | if(options.documentProcessing){ 89 | workbook = options.documentProcessing(workbook); 90 | } 91 | 92 | //convert workbook to binary array 93 | function s2ab(s) { 94 | var buf = new ArrayBuffer(s.length); 95 | var view = new Uint8Array(buf); 96 | for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; 97 | return buf; 98 | } 99 | 100 | output = XLSXLib.write(workbook, writeOptions); 101 | 102 | setFileContents(s2ab(output), "application/octet-stream"); 103 | } 104 | -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors.js: -------------------------------------------------------------------------------- 1 | import input from './editors/input.js'; 2 | import textarea from './editors/textarea.js'; 3 | import number from './editors/number.js'; 4 | import range from './editors/range.js'; 5 | import date from './editors/date.js'; 6 | import time from './editors/time.js'; 7 | import datetime from './editors/datetime.js'; 8 | import list from './editors/list.js'; 9 | import star from './editors/star.js'; 10 | import progress from './editors/progress.js'; 11 | import tickCross from './editors/tickCross.js'; 12 | import adaptable from './editors/adaptable.js'; 13 | 14 | export default { 15 | input:input, 16 | textarea:textarea, 17 | number:number, 18 | range:range, 19 | date:date, 20 | time:time, 21 | datetime:datetime, 22 | list:list, 23 | star:star, 24 | progress:progress, 25 | tickCross:tickCross, 26 | adaptable:adaptable, 27 | }; -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/adaptable.js: -------------------------------------------------------------------------------- 1 | export default function(cell, onRendered, success, cancel, params){ 2 | var column = cell._getSelf().column, 3 | lookup, editorFunc, editorParams; 4 | 5 | function defaultLookup(cell){ 6 | var value = cell.getValue(), 7 | editor = "input"; 8 | 9 | switch(typeof value){ 10 | case "number": 11 | editor = "number"; 12 | break; 13 | 14 | case "boolean": 15 | editor = "tickCross"; 16 | break; 17 | 18 | case "string": 19 | if(value.includes("\n")){ 20 | editor = "textarea"; 21 | } 22 | break; 23 | } 24 | 25 | return editor; 26 | } 27 | 28 | lookup = params.editorLookup ? params.editorLookup(cell) : defaultLookup(cell); 29 | 30 | if(params.paramsLookup){ 31 | editorParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup, cell) : params.paramsLookup[lookup]; 32 | } 33 | 34 | editorFunc = this.table.modules.edit.lookupEditor(lookup, column); 35 | 36 | return editorFunc.call(this, cell, onRendered, success, cancel, editorParams || {}); 37 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/datetime.js: -------------------------------------------------------------------------------- 1 | //input element 2 | export default function(cell, onRendered, success, cancel, editorParams){ 3 | var inputFormat = editorParams.format, 4 | vertNav = editorParams.verticalNavigation || "editor", 5 | DT = inputFormat ? (this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime")) : null, 6 | newDatetime; 7 | 8 | //create and style input 9 | var cellValue = cell.getValue(), 10 | input = document.createElement("input"); 11 | 12 | input.type = "datetime-local"; 13 | input.style.padding = "4px"; 14 | input.style.width = "100%"; 15 | input.style.boxSizing = "border-box"; 16 | 17 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 18 | for (let key in editorParams.elementAttributes){ 19 | if(key.charAt(0) == "+"){ 20 | key = key.slice(1); 21 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 22 | }else{ 23 | input.setAttribute(key, editorParams.elementAttributes[key]); 24 | } 25 | } 26 | } 27 | 28 | cellValue = typeof cellValue !== "undefined" ? cellValue : ""; 29 | 30 | if(inputFormat){ 31 | if(DT){ 32 | if(DT.isDateTime(cellValue)){ 33 | newDatetime = cellValue; 34 | }else if(inputFormat === "iso"){ 35 | newDatetime = DT.fromISO(String(cellValue)); 36 | }else{ 37 | newDatetime = DT.fromFormat(String(cellValue), inputFormat); 38 | } 39 | 40 | cellValue = newDatetime.toFormat("yyyy-MM-dd") + "T" + newDatetime.toFormat("HH:mm"); 41 | }else{ 42 | console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); 43 | } 44 | } 45 | 46 | input.value = cellValue; 47 | 48 | onRendered(function(){ 49 | if(cell.getType() === "cell"){ 50 | input.focus({preventScroll: true}); 51 | input.style.height = "100%"; 52 | 53 | if(editorParams.selectContents){ 54 | input.select(); 55 | } 56 | } 57 | }); 58 | 59 | function onChange(){ 60 | var value = input.value, 61 | luxDateTime; 62 | 63 | if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){ 64 | 65 | if(value && inputFormat){ 66 | luxDateTime = DT.fromISO(String(value)); 67 | 68 | switch(inputFormat){ 69 | case true: 70 | value = luxDateTime; 71 | break; 72 | 73 | case "iso": 74 | value = luxDateTime.toISO(); 75 | break; 76 | 77 | default: 78 | value = luxDateTime.toFormat(inputFormat); 79 | } 80 | } 81 | 82 | if(success(value)){ 83 | cellValue = input.value; //persist value if successfully validated incase editor is used as header filter 84 | } 85 | }else{ 86 | cancel(); 87 | } 88 | } 89 | 90 | //submit new value on blur 91 | input.addEventListener("blur", function(e) { 92 | if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) { 93 | onChange(); // only on a "true" blur; not when focusing browser's date/time picker 94 | } 95 | }); 96 | 97 | //submit new value on enter 98 | input.addEventListener("keydown", function(e){ 99 | switch(e.keyCode){ 100 | // case 9: 101 | case 13: 102 | onChange(); 103 | break; 104 | 105 | case 27: 106 | cancel(); 107 | break; 108 | 109 | case 35: 110 | case 36: 111 | e.stopPropagation(); 112 | break; 113 | 114 | case 38: //up arrow 115 | case 40: //down arrow 116 | if(vertNav == "editor"){ 117 | e.stopImmediatePropagation(); 118 | e.stopPropagation(); 119 | } 120 | break; 121 | } 122 | }); 123 | 124 | return input; 125 | } 126 | -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/input.js: -------------------------------------------------------------------------------- 1 | import maskInput from '../../inputMask.js'; 2 | 3 | //input element 4 | export default function(cell, onRendered, success, cancel, editorParams){ 5 | //create and style input 6 | var cellValue = cell.getValue(), 7 | input = document.createElement("input"); 8 | 9 | input.setAttribute("type", editorParams.search ? "search" : "text"); 10 | 11 | input.style.padding = "4px"; 12 | input.style.width = "100%"; 13 | input.style.boxSizing = "border-box"; 14 | 15 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 16 | for (let key in editorParams.elementAttributes){ 17 | if(key.charAt(0) == "+"){ 18 | key = key.slice(1); 19 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 20 | }else{ 21 | input.setAttribute(key, editorParams.elementAttributes[key]); 22 | } 23 | } 24 | } 25 | 26 | input.value = typeof cellValue !== "undefined" ? cellValue : ""; 27 | 28 | onRendered(function(){ 29 | if(cell.getType() === "cell"){ 30 | input.focus({preventScroll: true}); 31 | input.style.height = "100%"; 32 | 33 | if(editorParams.selectContents){ 34 | input.select(); 35 | } 36 | } 37 | }); 38 | 39 | function onChange(e){ 40 | if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){ 41 | if(success(input.value)){ 42 | cellValue = input.value; //persist value if successfully validated incase editor is used as header filter 43 | } 44 | }else{ 45 | cancel(); 46 | } 47 | } 48 | 49 | //submit new value on blur or change 50 | input.addEventListener("change", onChange); 51 | input.addEventListener("blur", onChange); 52 | 53 | //submit new value on enter 54 | input.addEventListener("keydown", function(e){ 55 | switch(e.keyCode){ 56 | // case 9: 57 | case 13: 58 | onChange(e); 59 | break; 60 | 61 | case 27: 62 | cancel(); 63 | break; 64 | 65 | case 35: 66 | case 36: 67 | e.stopPropagation(); 68 | break; 69 | } 70 | }); 71 | 72 | if(editorParams.mask){ 73 | maskInput(input, editorParams); 74 | } 75 | 76 | return input; 77 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/list.js: -------------------------------------------------------------------------------- 1 | import List from '../../List.js'; 2 | 3 | export default function(cell, onRendered, success, cancel, editorParams){ 4 | var list = new List(this, cell, onRendered, success, cancel, editorParams); 5 | 6 | return list.input; 7 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/number.js: -------------------------------------------------------------------------------- 1 | import maskInput from '../../inputMask.js'; 2 | 3 | //input element with type of number 4 | export default function(cell, onRendered, success, cancel, editorParams){ 5 | var cellValue = cell.getValue(), 6 | vertNav = editorParams.verticalNavigation || "editor", 7 | input = document.createElement("input"); 8 | 9 | input.setAttribute("type", "number"); 10 | 11 | if(typeof editorParams.max != "undefined"){ 12 | input.setAttribute("max", editorParams.max); 13 | } 14 | 15 | if(typeof editorParams.min != "undefined"){ 16 | input.setAttribute("min", editorParams.min); 17 | } 18 | 19 | if(typeof editorParams.step != "undefined"){ 20 | input.setAttribute("step", editorParams.step); 21 | } 22 | 23 | //create and style input 24 | input.style.padding = "4px"; 25 | input.style.width = "100%"; 26 | input.style.boxSizing = "border-box"; 27 | 28 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 29 | for (let key in editorParams.elementAttributes){ 30 | if(key.charAt(0) == "+"){ 31 | key = key.slice(1); 32 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 33 | }else{ 34 | input.setAttribute(key, editorParams.elementAttributes[key]); 35 | } 36 | } 37 | } 38 | 39 | input.value = cellValue; 40 | 41 | var blurFunc = function(e){ 42 | onChange(); 43 | }; 44 | 45 | onRendered(function () { 46 | if(cell.getType() === "cell"){ 47 | //submit new value on blur 48 | input.removeEventListener("blur", blurFunc); 49 | 50 | input.focus({preventScroll: true}); 51 | input.style.height = "100%"; 52 | 53 | //submit new value on blur 54 | input.addEventListener("blur", blurFunc); 55 | 56 | if(editorParams.selectContents){ 57 | input.select(); 58 | } 59 | } 60 | }); 61 | 62 | function onChange(){ 63 | var value = input.value; 64 | 65 | if(!isNaN(value) && value !==""){ 66 | value = Number(value); 67 | } 68 | 69 | if(value !== cellValue){ 70 | if(success(value)){ 71 | cellValue = value; //persist value if successfully validated incase editor is used as header filter 72 | } 73 | }else{ 74 | cancel(); 75 | } 76 | } 77 | 78 | //submit new value on enter 79 | input.addEventListener("keydown", function(e){ 80 | switch(e.keyCode){ 81 | case 13: 82 | // case 9: 83 | onChange(); 84 | break; 85 | 86 | case 27: 87 | cancel(); 88 | break; 89 | 90 | case 38: //up arrow 91 | case 40: //down arrow 92 | if(vertNav == "editor"){ 93 | e.stopImmediatePropagation(); 94 | e.stopPropagation(); 95 | } 96 | break; 97 | 98 | case 35: 99 | case 36: 100 | e.stopPropagation(); 101 | break; 102 | } 103 | }); 104 | 105 | if(editorParams.mask){ 106 | maskInput(input, editorParams); 107 | } 108 | 109 | return input; 110 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/range.js: -------------------------------------------------------------------------------- 1 | //input element with type of number 2 | export default function(cell, onRendered, success, cancel, editorParams){ 3 | var cellValue = cell.getValue(), 4 | input = document.createElement("input"); 5 | 6 | input.setAttribute("type", "range"); 7 | 8 | if (typeof editorParams.max != "undefined") { 9 | input.setAttribute("max", editorParams.max); 10 | } 11 | 12 | if (typeof editorParams.min != "undefined") { 13 | input.setAttribute("min", editorParams.min); 14 | } 15 | 16 | if (typeof editorParams.step != "undefined") { 17 | input.setAttribute("step", editorParams.step); 18 | } 19 | 20 | //create and style input 21 | input.style.padding = "4px"; 22 | input.style.width = "100%"; 23 | input.style.boxSizing = "border-box"; 24 | 25 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 26 | for (let key in editorParams.elementAttributes){ 27 | if(key.charAt(0) == "+"){ 28 | key = key.slice(1); 29 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 30 | }else{ 31 | input.setAttribute(key, editorParams.elementAttributes[key]); 32 | } 33 | } 34 | } 35 | 36 | input.value = cellValue; 37 | 38 | onRendered(function () { 39 | if(cell.getType() === "cell"){ 40 | input.focus({preventScroll: true}); 41 | input.style.height = "100%"; 42 | } 43 | }); 44 | 45 | function onChange(){ 46 | var value = input.value; 47 | 48 | if(!isNaN(value) && value !==""){ 49 | value = Number(value); 50 | } 51 | 52 | if(value != cellValue){ 53 | if(success(value)){ 54 | cellValue = value; //persist value if successfully validated incase editor is used as header filter 55 | } 56 | }else{ 57 | cancel(); 58 | } 59 | } 60 | 61 | //submit new value on blur 62 | input.addEventListener("blur", function(e){ 63 | onChange(); 64 | }); 65 | 66 | //submit new value on enter 67 | input.addEventListener("keydown", function(e){ 68 | switch(e.keyCode){ 69 | case 13: 70 | // case 9: 71 | onChange(); 72 | break; 73 | 74 | case 27: 75 | cancel(); 76 | break; 77 | } 78 | }); 79 | 80 | return input; 81 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/textarea.js: -------------------------------------------------------------------------------- 1 | import maskInput from '../../inputMask.js'; 2 | 3 | //resizable text area element 4 | export default function(cell, onRendered, success, cancel, editorParams){ 5 | var cellValue = cell.getValue(), 6 | vertNav = editorParams.verticalNavigation || "hybrid", 7 | value = String(cellValue !== null && typeof cellValue !== "undefined" ? cellValue : ""), 8 | input = document.createElement("textarea"), 9 | scrollHeight = 0; 10 | 11 | //create and style input 12 | input.style.display = "block"; 13 | input.style.padding = "2px"; 14 | input.style.height = "100%"; 15 | input.style.width = "100%"; 16 | input.style.boxSizing = "border-box"; 17 | input.style.whiteSpace = "pre-wrap"; 18 | input.style.resize = "none"; 19 | 20 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 21 | for (let key in editorParams.elementAttributes){ 22 | if(key.charAt(0) == "+"){ 23 | key = key.slice(1); 24 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 25 | }else{ 26 | input.setAttribute(key, editorParams.elementAttributes[key]); 27 | } 28 | } 29 | } 30 | 31 | input.value = value; 32 | 33 | onRendered(function(){ 34 | if(cell.getType() === "cell"){ 35 | input.focus({preventScroll: true}); 36 | input.style.height = "100%"; 37 | 38 | input.scrollHeight; 39 | input.style.height = input.scrollHeight + "px"; 40 | cell.getRow().normalizeHeight(); 41 | 42 | if(editorParams.selectContents){ 43 | input.select(); 44 | } 45 | } 46 | }); 47 | 48 | function onChange(e){ 49 | 50 | if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){ 51 | 52 | if(success(input.value)){ 53 | cellValue = input.value; //persist value if successfully validated incase editor is used as header filter 54 | } 55 | 56 | setTimeout(function(){ 57 | cell.getRow().normalizeHeight(); 58 | },300); 59 | }else{ 60 | cancel(); 61 | } 62 | } 63 | 64 | //submit new value on blur or change 65 | input.addEventListener("change", onChange); 66 | input.addEventListener("blur", onChange); 67 | 68 | input.addEventListener("keyup", function(){ 69 | 70 | input.style.height = ""; 71 | 72 | var heightNow = input.scrollHeight; 73 | 74 | input.style.height = heightNow + "px"; 75 | 76 | if(heightNow != scrollHeight){ 77 | scrollHeight = heightNow; 78 | cell.getRow().normalizeHeight(); 79 | } 80 | }); 81 | 82 | input.addEventListener("keydown", function(e){ 83 | 84 | switch(e.keyCode){ 85 | 86 | case 13: 87 | if(e.shiftKey && editorParams.shiftEnterSubmit){ 88 | onChange(e); 89 | } 90 | break; 91 | 92 | case 27: 93 | cancel(); 94 | break; 95 | 96 | case 38: //up arrow 97 | if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart)){ 98 | e.stopImmediatePropagation(); 99 | e.stopPropagation(); 100 | } 101 | 102 | break; 103 | 104 | case 40: //down arrow 105 | if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart !== input.value.length)){ 106 | e.stopImmediatePropagation(); 107 | e.stopPropagation(); 108 | } 109 | break; 110 | 111 | case 35: 112 | case 36: 113 | e.stopPropagation(); 114 | break; 115 | } 116 | }); 117 | 118 | if(editorParams.mask){ 119 | maskInput(input, editorParams); 120 | } 121 | 122 | return input; 123 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/tickCross.js: -------------------------------------------------------------------------------- 1 | //checkbox 2 | export default function(cell, onRendered, success, cancel, editorParams){ 3 | var value = cell.getValue(), 4 | input = document.createElement("input"), 5 | tristate = editorParams.tristate, 6 | indetermValue = typeof editorParams.indeterminateValue === "undefined" ? null : editorParams.indeterminateValue, 7 | indetermState = false, 8 | trueValueSet = Object.keys(editorParams).includes("trueValue"), 9 | falseValueSet = Object.keys(editorParams).includes("falseValue"); 10 | 11 | input.setAttribute("type", "checkbox"); 12 | input.style.marginTop = "5px"; 13 | input.style.boxSizing = "border-box"; 14 | 15 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 16 | for (let key in editorParams.elementAttributes){ 17 | if(key.charAt(0) == "+"){ 18 | key = key.slice(1); 19 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 20 | }else{ 21 | input.setAttribute(key, editorParams.elementAttributes[key]); 22 | } 23 | } 24 | } 25 | 26 | input.value = value; 27 | 28 | if(tristate && (typeof value === "undefined" || value === indetermValue || value === "")){ 29 | indetermState = true; 30 | input.indeterminate = true; 31 | } 32 | 33 | if(this.table.browser != "firefox" && this.table.browser != "safari"){ //prevent blur issue on mac firefox 34 | onRendered(function(){ 35 | if(cell.getType() === "cell"){ 36 | input.focus({preventScroll: true}); 37 | } 38 | }); 39 | } 40 | 41 | input.checked = trueValueSet ? value === editorParams.trueValue : (value === true || value === "true" || value === "True" || value === 1); 42 | 43 | function setValue(blur){ 44 | var checkedValue = input.checked; 45 | 46 | if(trueValueSet && checkedValue){ 47 | checkedValue = editorParams.trueValue; 48 | }else if(falseValueSet && !checkedValue){ 49 | checkedValue = editorParams.falseValue; 50 | } 51 | 52 | if(tristate){ 53 | if(!blur){ 54 | if(input.checked && !indetermState){ 55 | input.checked = false; 56 | input.indeterminate = true; 57 | indetermState = true; 58 | return indetermValue; 59 | }else{ 60 | indetermState = false; 61 | return checkedValue; 62 | } 63 | }else{ 64 | if(indetermState){ 65 | return indetermValue; 66 | }else{ 67 | return checkedValue; 68 | } 69 | } 70 | }else{ 71 | return checkedValue; 72 | } 73 | } 74 | 75 | //submit new value on blur 76 | input.addEventListener("change", function(e){ 77 | success(setValue()); 78 | }); 79 | 80 | input.addEventListener("blur", function(e){ 81 | success(setValue(true)); 82 | }); 83 | 84 | //submit new value on enter 85 | input.addEventListener("keydown", function(e){ 86 | if(e.keyCode == 13){ 87 | success(setValue()); 88 | } 89 | if(e.keyCode == 27){ 90 | cancel(); 91 | } 92 | }); 93 | 94 | return input; 95 | } -------------------------------------------------------------------------------- /src/js/modules/Edit/defaults/editors/time.js: -------------------------------------------------------------------------------- 1 | //input element 2 | export default function(cell, onRendered, success, cancel, editorParams){ 3 | var inputFormat = editorParams.format, 4 | vertNav = editorParams.verticalNavigation || "editor", 5 | DT = inputFormat ? (window.DateTime || luxon.DateTime) : null, 6 | newDatetime; 7 | 8 | //create and style input 9 | var cellValue = cell.getValue(), 10 | input = document.createElement("input"); 11 | 12 | input.type = "time"; 13 | input.style.padding = "4px"; 14 | input.style.width = "100%"; 15 | input.style.boxSizing = "border-box"; 16 | 17 | if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ 18 | for (let key in editorParams.elementAttributes){ 19 | if(key.charAt(0) == "+"){ 20 | key = key.slice(1); 21 | input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); 22 | }else{ 23 | input.setAttribute(key, editorParams.elementAttributes[key]); 24 | } 25 | } 26 | } 27 | 28 | cellValue = typeof cellValue !== "undefined" ? cellValue : ""; 29 | 30 | if(inputFormat){ 31 | if(DT){ 32 | if(DT.isDateTime(cellValue)){ 33 | newDatetime = cellValue; 34 | }else if(inputFormat === "iso"){ 35 | newDatetime = DT.fromISO(String(cellValue)); 36 | }else{ 37 | newDatetime = DT.fromFormat(String(cellValue), inputFormat); 38 | } 39 | 40 | cellValue = newDatetime.toFormat("HH:mm"); 41 | 42 | }else{ 43 | console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); 44 | } 45 | } 46 | 47 | input.value = cellValue; 48 | 49 | onRendered(function(){ 50 | if(cell.getType() == "cell"){ 51 | input.focus({preventScroll: true}); 52 | input.style.height = "100%"; 53 | 54 | if(editorParams.selectContents){ 55 | input.select(); 56 | } 57 | } 58 | }); 59 | 60 | function onChange(){ 61 | var value = input.value, 62 | luxTime; 63 | 64 | if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){ 65 | 66 | if(value && inputFormat){ 67 | luxTime = DT.fromFormat(String(value), "hh:mm"); 68 | 69 | switch(inputFormat){ 70 | case true: 71 | value = luxTime; 72 | break; 73 | 74 | case "iso": 75 | value = luxTime.toISO(); 76 | break; 77 | 78 | default: 79 | value = luxTime.toFormat(inputFormat); 80 | } 81 | } 82 | 83 | if(success(value)){ 84 | cellValue = input.value; //persist value if successfully validated incase editor is used as header filter 85 | } 86 | }else{ 87 | cancel(); 88 | } 89 | } 90 | 91 | //submit new value on blur 92 | input.addEventListener("blur", function(e) { 93 | if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) { 94 | onChange(); // only on a "true" blur; not when focusing browser's date/time picker 95 | } 96 | }); 97 | 98 | //submit new value on enter 99 | input.addEventListener("keydown", function(e){ 100 | switch(e.keyCode){ 101 | // case 9: 102 | case 13: 103 | onChange(); 104 | break; 105 | 106 | case 27: 107 | cancel(); 108 | break; 109 | 110 | case 35: 111 | case 36: 112 | e.stopPropagation(); 113 | break; 114 | 115 | case 38: //up arrow 116 | case 40: //down arrow 117 | if(vertNav == "editor"){ 118 | e.stopImmediatePropagation(); 119 | e.stopPropagation(); 120 | } 121 | break; 122 | } 123 | }); 124 | 125 | return input; 126 | } 127 | -------------------------------------------------------------------------------- /src/js/modules/Edit/inputMask.js: -------------------------------------------------------------------------------- 1 | export default function maskInput(el, options){ 2 | var mask = options.mask, 3 | maskLetter = typeof options.maskLetterChar !== "undefined" ? options.maskLetterChar : "A", 4 | maskNumber = typeof options.maskNumberChar !== "undefined" ? options.maskNumberChar : "9", 5 | maskWildcard = typeof options.maskWildcardChar !== "undefined" ? options.maskWildcardChar : "*"; 6 | 7 | function fillSymbols(index){ 8 | var symbol = mask[index]; 9 | if(typeof symbol !== "undefined" && symbol !== maskWildcard && symbol !== maskLetter && symbol !== maskNumber){ 10 | el.value = el.value + "" + symbol; 11 | fillSymbols(index+1); 12 | } 13 | } 14 | 15 | el.addEventListener("keydown", (e) => { 16 | var index = el.value.length, 17 | char = e.key; 18 | 19 | if(e.keyCode > 46 && !e.ctrlKey && !e.metaKey){ 20 | if(index >= mask.length){ 21 | e.preventDefault(); 22 | e.stopPropagation(); 23 | return false; 24 | }else{ 25 | switch(mask[index]){ 26 | case maskLetter: 27 | if(char.toUpperCase() == char.toLowerCase()){ 28 | e.preventDefault(); 29 | e.stopPropagation(); 30 | return false; 31 | } 32 | break; 33 | 34 | case maskNumber: 35 | if(isNaN(char)){ 36 | e.preventDefault(); 37 | e.stopPropagation(); 38 | return false; 39 | } 40 | break; 41 | 42 | case maskWildcard: 43 | break; 44 | 45 | default: 46 | if(char !== mask[index]){ 47 | e.preventDefault(); 48 | e.stopPropagation(); 49 | return false; 50 | } 51 | } 52 | } 53 | } 54 | 55 | return; 56 | }); 57 | 58 | el.addEventListener("keyup", (e) => { 59 | if(e.keyCode > 46){ 60 | if(options.maskAutoFill){ 61 | fillSymbols(el.value.length); 62 | } 63 | } 64 | }); 65 | 66 | 67 | if(!el.placeholder){ 68 | el.placeholder = mask; 69 | } 70 | 71 | if(options.maskAutoFill){ 72 | fillSymbols(el.value.length); 73 | } 74 | } -------------------------------------------------------------------------------- /src/js/modules/Export/ExportColumn.js: -------------------------------------------------------------------------------- 1 | export default class ExportColumn{ 2 | constructor(value, component, width, height, depth){ 3 | this.value = value; 4 | this.component = component || false; 5 | this.width = width; 6 | this.height = height; 7 | this.depth = depth; 8 | } 9 | } -------------------------------------------------------------------------------- /src/js/modules/Export/ExportRow.js: -------------------------------------------------------------------------------- 1 | export default class ExportRow{ 2 | constructor(type, columns, component, indent){ 3 | this.type = type; 4 | this.columns = columns; 5 | this.component = component || false; 6 | this.indent = indent || 0; 7 | } 8 | } -------------------------------------------------------------------------------- /src/js/modules/Export/defaults/columnLookups.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | }; -------------------------------------------------------------------------------- /src/js/modules/Export/defaults/rowLookups.js: -------------------------------------------------------------------------------- 1 | export default { 2 | visible:function(){ 3 | return this.rowManager.getVisibleRows(false, true); 4 | }, 5 | all:function(){ 6 | return this.rowManager.rows; 7 | }, 8 | selected:function(){ 9 | return this.modules.selectRow.selectedRows; 10 | }, 11 | active:function(){ 12 | if(this.options.pagination){ 13 | return this.rowManager.getDisplayRows(this.rowManager.displayRows.length - 2); 14 | }else{ 15 | return this.rowManager.getDisplayRows(); 16 | } 17 | }, 18 | }; -------------------------------------------------------------------------------- /src/js/modules/Filter/defaults/filters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | //equal to 4 | "=":function(filterVal, rowVal, rowData, filterParams){ 5 | return rowVal == filterVal ? true : false; 6 | }, 7 | 8 | //less than 9 | "<":function(filterVal, rowVal, rowData, filterParams){ 10 | return rowVal < filterVal ? true : false; 11 | }, 12 | 13 | //less than or equal to 14 | "<=":function(filterVal, rowVal, rowData, filterParams){ 15 | return rowVal <= filterVal ? true : false; 16 | }, 17 | 18 | //greater than 19 | ">":function(filterVal, rowVal, rowData, filterParams){ 20 | return rowVal > filterVal ? true : false; 21 | }, 22 | 23 | //greater than or equal to 24 | ">=":function(filterVal, rowVal, rowData, filterParams){ 25 | return rowVal >= filterVal ? true : false; 26 | }, 27 | 28 | //not equal to 29 | "!=":function(filterVal, rowVal, rowData, filterParams){ 30 | return rowVal != filterVal ? true : false; 31 | }, 32 | 33 | "regex":function(filterVal, rowVal, rowData, filterParams){ 34 | 35 | if(typeof filterVal == "string"){ 36 | filterVal = new RegExp(filterVal); 37 | } 38 | 39 | return filterVal.test(rowVal); 40 | }, 41 | 42 | //contains the string 43 | "like":function(filterVal, rowVal, rowData, filterParams){ 44 | if(filterVal === null || typeof filterVal === "undefined"){ 45 | return rowVal === filterVal ? true : false; 46 | }else{ 47 | if(typeof rowVal !== 'undefined' && rowVal !== null){ 48 | return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1; 49 | } 50 | else{ 51 | return false; 52 | } 53 | } 54 | }, 55 | 56 | //contains the keywords 57 | "keywords":function(filterVal, rowVal, rowData, filterParams){ 58 | var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator), 59 | value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(), 60 | matches = []; 61 | 62 | keywords.forEach((keyword) =>{ 63 | if(value.includes(keyword)){ 64 | matches.push(true); 65 | } 66 | }); 67 | 68 | return filterParams.matchAll ? matches.length === keywords.length : !!matches.length; 69 | }, 70 | 71 | //starts with the string 72 | "starts":function(filterVal, rowVal, rowData, filterParams){ 73 | if(filterVal === null || typeof filterVal === "undefined"){ 74 | return rowVal === filterVal ? true : false; 75 | }else{ 76 | if(typeof rowVal !== 'undefined' && rowVal !== null){ 77 | return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase()); 78 | } 79 | else{ 80 | return false; 81 | } 82 | } 83 | }, 84 | 85 | //ends with the string 86 | "ends":function(filterVal, rowVal, rowData, filterParams){ 87 | if(filterVal === null || typeof filterVal === "undefined"){ 88 | return rowVal === filterVal ? true : false; 89 | }else{ 90 | if(typeof rowVal !== 'undefined' && rowVal !== null){ 91 | return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase()); 92 | } 93 | else{ 94 | return false; 95 | } 96 | } 97 | }, 98 | 99 | //in array 100 | "in":function(filterVal, rowVal, rowData, filterParams){ 101 | if(Array.isArray(filterVal)){ 102 | return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true; 103 | }else{ 104 | console.warn("Filter Error - filter value is not an array:", filterVal); 105 | return false; 106 | } 107 | }, 108 | }; -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters.js: -------------------------------------------------------------------------------- 1 | import plaintext from './formatters/plaintext.js'; 2 | import html from './formatters/html.js'; 3 | import textarea from './formatters/textarea.js'; 4 | import money from './formatters/money.js'; 5 | import link from './formatters/link.js'; 6 | import image from './formatters/image.js'; 7 | import tickCross from './formatters/tickCross.js'; 8 | import datetime from './formatters/datetime.js'; 9 | import datetimediff from './formatters/datetimediff.js'; 10 | import lookup from './formatters/lookup.js'; 11 | import star from './formatters/star.js'; 12 | import traffic from './formatters/traffic.js'; 13 | import progress from './formatters/progress.js'; 14 | import color from './formatters/color.js'; 15 | import buttonTick from './formatters/buttonTick.js'; 16 | import buttonCross from './formatters/buttonCross.js'; 17 | import toggle from './formatters/toggle.js'; 18 | import rownum from './formatters/rownum.js'; 19 | import handle from './formatters/handle.js'; 20 | import adaptable from './formatters/adaptable.js'; 21 | import array from './formatters/array.js'; 22 | import json from './formatters/json.js'; 23 | 24 | export default { 25 | plaintext:plaintext, 26 | html:html, 27 | textarea:textarea, 28 | money:money, 29 | link:link, 30 | image:image, 31 | tickCross:tickCross, 32 | datetime:datetime, 33 | datetimediff:datetimediff, 34 | lookup:lookup, 35 | star:star, 36 | traffic:traffic, 37 | progress:progress, 38 | color:color, 39 | buttonTick:buttonTick, 40 | buttonCross:buttonCross, 41 | toggle:toggle, 42 | rownum:rownum, 43 | handle:handle, 44 | adaptable:adaptable, 45 | array:array, 46 | json:json, 47 | }; -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/adaptable.js: -------------------------------------------------------------------------------- 1 | export default function(cell, params, onRendered){ 2 | var lookup, formatterFunc, formatterParams; 3 | 4 | function defaultLookup(cell){ 5 | var value = cell.getValue(), 6 | formatter = "plaintext"; 7 | 8 | switch(typeof value){ 9 | case "boolean": 10 | formatter = "tickCross"; 11 | break; 12 | 13 | case "string": 14 | if(value.includes("\n")){ 15 | formatter = "textarea"; 16 | } 17 | break; 18 | } 19 | 20 | return formatter; 21 | } 22 | 23 | lookup = params.formatterLookup ? params.formatterLookup(cell) : defaultLookup(cell); 24 | 25 | if(params.paramsLookup){ 26 | formatterParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup, cell) : params.paramsLookup[lookup]; 27 | } 28 | 29 | formatterFunc = this.table.modules.format.lookupFormatter(lookup); 30 | 31 | return formatterFunc.call(this, cell, formatterParams || {}, onRendered); 32 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/array.js: -------------------------------------------------------------------------------- 1 | import Helpers from '../../../../core/tools/Helpers.js'; 2 | 3 | export default function(cell, formatterParams, onRendered){ 4 | var delimiter = formatterParams.delimiter || ",", 5 | value = cell.getValue(), 6 | table = this.table, 7 | valueMap; 8 | 9 | if(formatterParams.valueMap){ 10 | if(typeof formatterParams.valueMap === "string"){ 11 | valueMap = function(value){ 12 | return value.map((item) => { 13 | return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, formatterParams.valueMap, item); 14 | }); 15 | }; 16 | }else{ 17 | valueMap = formatterParams.valueMap; 18 | } 19 | } 20 | 21 | if(Array.isArray(value)){ 22 | if(valueMap){ 23 | value = valueMap(value); 24 | } 25 | 26 | return value.join(delimiter); 27 | }else{ 28 | return value; 29 | } 30 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/buttonCross.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | return ''; 3 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/buttonTick.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | return ''; 3 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/color.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue()); 3 | return ""; 4 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/datetime.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); 3 | var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; 4 | var outputFormat = formatterParams.outputFormat || "dd/MM/yyyy HH:mm:ss"; 5 | var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; 6 | var value = cell.getValue(); 7 | 8 | if(typeof DT != "undefined"){ 9 | var newDatetime; 10 | 11 | if(DT.isDateTime(value)){ 12 | newDatetime = value; 13 | }else if(inputFormat === "iso"){ 14 | newDatetime = DT.fromISO(String(value)); 15 | }else{ 16 | newDatetime = DT.fromFormat(String(value), inputFormat); 17 | } 18 | 19 | if(newDatetime.isValid){ 20 | if(formatterParams.timezone){ 21 | newDatetime = newDatetime.setZone(formatterParams.timezone); 22 | } 23 | 24 | return newDatetime.toFormat(outputFormat); 25 | }else{ 26 | if(invalid === true || !value){ 27 | return value; 28 | }else if(typeof invalid === "function"){ 29 | return invalid(value); 30 | }else{ 31 | return invalid; 32 | } 33 | } 34 | }else{ 35 | console.error("Format Error - 'datetime' formatter is dependant on luxon.js"); 36 | } 37 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/datetimediff.js: -------------------------------------------------------------------------------- 1 | export default function (cell, formatterParams, onRendered) { 2 | var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); 3 | var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; 4 | var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; 5 | var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false; 6 | var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : "days"; 7 | var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false; 8 | var date = typeof formatterParams.date !== "undefined" ? formatterParams.date : DT.now(); 9 | var value = cell.getValue(); 10 | 11 | if(typeof DT != "undefined"){ 12 | var newDatetime; 13 | 14 | if(DT.isDateTime(value)){ 15 | newDatetime = value; 16 | }else if(inputFormat === "iso"){ 17 | newDatetime = DT.fromISO(String(value)); 18 | }else{ 19 | newDatetime = DT.fromFormat(String(value), inputFormat); 20 | } 21 | 22 | if (newDatetime.isValid){ 23 | if(humanize){ 24 | return newDatetime.diff(date, unit).toHuman() + (suffix ? " " + suffix : ""); 25 | }else{ 26 | return parseInt(newDatetime.diff(date, unit)[unit]) + (suffix ? " " + suffix : ""); 27 | } 28 | } else { 29 | 30 | if (invalid === true) { 31 | return value; 32 | } else if (typeof invalid === "function") { 33 | return invalid(value); 34 | } else { 35 | return invalid; 36 | } 37 | } 38 | }else{ 39 | console.error("Format Error - 'datetimediff' formatter is dependant on luxon.js"); 40 | } 41 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/handle.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | cell.getElement().classList.add("tabulator-row-handle"); 3 | return "
"; 4 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/html.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | return cell.getValue(); 3 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/image.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var el = document.createElement("img"), 3 | src = cell.getValue(); 4 | 5 | if(formatterParams.urlPrefix){ 6 | src = formatterParams.urlPrefix + cell.getValue(); 7 | } 8 | 9 | if(formatterParams.urlSuffix){ 10 | src = src + formatterParams.urlSuffix; 11 | } 12 | 13 | el.setAttribute("src", src); 14 | 15 | switch(typeof formatterParams.height){ 16 | case "number": 17 | el.style.height = formatterParams.height + "px"; 18 | break; 19 | 20 | case "string": 21 | el.style.height = formatterParams.height; 22 | break; 23 | } 24 | 25 | switch(typeof formatterParams.width){ 26 | case "number": 27 | el.style.width = formatterParams.width + "px"; 28 | break; 29 | 30 | case "string": 31 | el.style.width = formatterParams.width; 32 | break; 33 | } 34 | 35 | el.addEventListener("load", function(){ 36 | cell.getRow().normalizeHeight(); 37 | }); 38 | 39 | return el; 40 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/json.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var indent = formatterParams.indent || "\t", 3 | multiline = typeof formatterParams.multiline === "undefined" ? true : formatterParams.multiline, 4 | replacer = formatterParams.replacer || null, 5 | value = cell.getValue(); 6 | 7 | if(multiline){ 8 | cell.getElement().style.whiteSpace = "pre-wrap"; 9 | } 10 | 11 | return JSON.stringify(value, replacer, indent); 12 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/link.js: -------------------------------------------------------------------------------- 1 | import Helpers from '../../../../core/tools/Helpers.js'; 2 | 3 | 4 | export default function(cell, formatterParams, onRendered){ 5 | var value = cell.getValue(), 6 | urlPrefix = formatterParams.urlPrefix || "", 7 | download = formatterParams.download, 8 | label = value, 9 | el = document.createElement("a"), 10 | data; 11 | 12 | function labelTraverse(path, data){ 13 | var item = path.shift(), 14 | value = data[item]; 15 | 16 | if(path.length && typeof value === "object"){ 17 | return labelTraverse(path, value); 18 | } 19 | 20 | return value; 21 | } 22 | 23 | if(formatterParams.labelField){ 24 | data = cell.getData(); 25 | label = labelTraverse(formatterParams.labelField.split(this.table.options.nestedFieldSeparator), data); 26 | } 27 | 28 | if(formatterParams.label){ 29 | switch(typeof formatterParams.label){ 30 | case "string": 31 | label = formatterParams.label; 32 | break; 33 | 34 | case "function": 35 | label = formatterParams.label(cell); 36 | break; 37 | } 38 | } 39 | 40 | if(label){ 41 | if(formatterParams.urlField){ 42 | data = cell.getData(); 43 | 44 | value = Helpers.retrieveNestedData(this.table.options.nestedFieldSeparator, formatterParams.urlField, data); 45 | } 46 | 47 | if(formatterParams.url){ 48 | switch(typeof formatterParams.url){ 49 | case "string": 50 | value = formatterParams.url; 51 | break; 52 | 53 | case "function": 54 | value = formatterParams.url(cell); 55 | break; 56 | } 57 | } 58 | 59 | el.setAttribute("href", urlPrefix + value); 60 | 61 | if(formatterParams.target){ 62 | el.setAttribute("target", formatterParams.target); 63 | } 64 | 65 | if(formatterParams.download){ 66 | 67 | if(typeof download == "function"){ 68 | download = download(cell); 69 | }else{ 70 | download = download === true ? "" : download; 71 | } 72 | 73 | el.setAttribute("download", download); 74 | } 75 | 76 | el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label)); 77 | 78 | return el; 79 | }else{ 80 | return " "; 81 | } 82 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/lookup.js: -------------------------------------------------------------------------------- 1 | export default function (cell, formatterParams, onRendered) { 2 | var value = cell.getValue(); 3 | 4 | if (typeof formatterParams[value] === "undefined") { 5 | console.warn('Missing display value for ' + value); 6 | return value; 7 | } 8 | 9 | return formatterParams[value]; 10 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/money.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var floatVal = parseFloat(cell.getValue()), 3 | sign = "", 4 | number, integer, decimal, rgx, value; 5 | 6 | var decimalSym = formatterParams.decimal || "."; 7 | var thousandSym = formatterParams.thousand || ","; 8 | var negativeSign = formatterParams.negativeSign || "-"; 9 | var symbol = formatterParams.symbol || ""; 10 | var after = !!formatterParams.symbolAfter; 11 | var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2; 12 | 13 | if(isNaN(floatVal)){ 14 | return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); 15 | } 16 | 17 | if(floatVal < 0){ 18 | floatVal = Math.abs(floatVal); 19 | sign = negativeSign; 20 | } 21 | 22 | number = precision !== false ? floatVal.toFixed(precision) : floatVal; 23 | number = String(number).split("."); 24 | 25 | integer = number[0]; 26 | decimal = number.length > 1 ? decimalSym + number[1] : ""; 27 | 28 | if (formatterParams.thousand !== false) { 29 | rgx = /(\d+)(\d{3})/; 30 | 31 | while (rgx.test(integer)){ 32 | integer = integer.replace(rgx, "$1" + thousandSym + "$2"); 33 | } 34 | } 35 | 36 | value = integer + decimal; 37 | 38 | if(sign === true){ 39 | value = "(" + value + ")"; 40 | return after ? value + symbol : symbol + value; 41 | }else{ 42 | return after ? sign + value + symbol : sign + symbol + value; 43 | } 44 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/plaintext.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); 3 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/rownum.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var content = document.createElement("span"); 3 | var row = cell.getRow(); 4 | var table = cell.getTable(); 5 | 6 | row.watchPosition((position) => { 7 | if (formatterParams.relativeToPage) { 8 | position += table.modules.page.getPageSize() * (table.modules.page.getPage() - 1); 9 | } 10 | content.innerText = position; 11 | }); 12 | 13 | return content; 14 | } 15 | -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/star.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var value = cell.getValue(), 3 | element = cell.getElement(), 4 | maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5, 5 | stars = document.createElement("span"), 6 | star = document.createElementNS('http://www.w3.org/2000/svg', "svg"), 7 | starActive = '', 8 | starInactive = ''; 9 | 10 | //style stars holder 11 | stars.style.verticalAlign = "middle"; 12 | 13 | //style star 14 | star.setAttribute("width", "14"); 15 | star.setAttribute("height", "14"); 16 | star.setAttribute("viewBox", "0 0 512 512"); 17 | star.setAttribute("xml:space", "preserve"); 18 | star.style.padding = "0 1px"; 19 | 20 | value = value && !isNaN(value) ? parseInt(value) : 0; 21 | 22 | value = Math.max(0, Math.min(value, maxStars)); 23 | 24 | for(var i=1;i<= maxStars;i++){ 25 | var nextStar = star.cloneNode(true); 26 | nextStar.innerHTML = i <= value ? starActive : starInactive; 27 | 28 | stars.appendChild(nextStar); 29 | } 30 | 31 | element.style.whiteSpace = "nowrap"; 32 | element.style.overflow = "hidden"; 33 | element.style.textOverflow = "ellipsis"; 34 | 35 | element.setAttribute("aria-label", value); 36 | 37 | return stars; 38 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/textarea.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | cell.getElement().style.whiteSpace = "pre-wrap"; 3 | return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); 4 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/tickCross.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var value = cell.getValue(), 3 | element = cell.getElement(), 4 | empty = formatterParams.allowEmpty, 5 | truthy = formatterParams.allowTruthy, 6 | trueValueSet = Object.keys(formatterParams).includes("trueValue"), 7 | tick = typeof formatterParams.tickElement !== "undefined" ? formatterParams.tickElement : '', 8 | cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : ''; 9 | 10 | if((trueValueSet && value === formatterParams.trueValue) || (!trueValueSet && ((truthy && value) || (value === true || value === "true" || value === "True" || value === 1 || value === "1")))){ 11 | element.setAttribute("aria-checked", true); 12 | return tick || ""; 13 | }else{ 14 | if(empty && (value === "null" || value === "" || value === null || typeof value === "undefined")){ 15 | element.setAttribute("aria-checked", "mixed"); 16 | return ""; 17 | }else{ 18 | element.setAttribute("aria-checked", false); 19 | return cross || ""; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/toggle.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var value = cell.getValue(), 3 | size = formatterParams.size ||15, 4 | sizePx = size + "px", 5 | containEl, switchEl, 6 | onValue = formatterParams.hasOwnProperty("onValue") ? formatterParams.onValue : true, 7 | offValue = formatterParams.hasOwnProperty("offValue") ? formatterParams.offValue : false, 8 | 9 | 10 | state = formatterParams.onTruthy ? value : value === onValue; 11 | 12 | 13 | containEl = document.createElement("div"); 14 | containEl.classList.add("tabulator-toggle"); 15 | 16 | if(state){ 17 | containEl.classList.add("tabulator-toggle-on"); 18 | containEl.style.flexDirection = "row-reverse"; 19 | 20 | if(formatterParams.onColor){ 21 | containEl.style.background = formatterParams.onColor; 22 | } 23 | }else{ 24 | if(formatterParams.offColor){ 25 | containEl.style.background = formatterParams.offColor; 26 | } 27 | } 28 | 29 | containEl.style.width = (2.5 * size) + "px"; 30 | containEl.style.borderRadius = sizePx; 31 | 32 | if(formatterParams.clickable){ 33 | containEl.addEventListener("click", (e) => { 34 | cell.setValue(state ? offValue : onValue); 35 | }); 36 | } 37 | 38 | switchEl = document.createElement("div"); 39 | switchEl.classList.add("tabulator-toggle-switch"); 40 | 41 | switchEl.style.height = sizePx; 42 | switchEl.style.width = sizePx; 43 | switchEl.style.borderRadius = sizePx; 44 | 45 | containEl.appendChild(switchEl); 46 | 47 | return containEl; 48 | } -------------------------------------------------------------------------------- /src/js/modules/Format/defaults/formatters/traffic.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var value = this.sanitizeHTML(cell.getValue()) || 0, 3 | el = document.createElement("span"), 4 | max = formatterParams && formatterParams.max ? formatterParams.max : 100, 5 | min = formatterParams && formatterParams.min ? formatterParams.min : 0, 6 | colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"], 7 | color = "#666666", 8 | percent, percentValue; 9 | 10 | if(isNaN(value) || typeof cell.getValue() === "undefined"){ 11 | return; 12 | } 13 | 14 | el.classList.add("tabulator-traffic-light"); 15 | 16 | //make sure value is in range 17 | percentValue = parseFloat(value) <= max ? parseFloat(value) : max; 18 | percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min; 19 | 20 | //workout percentage 21 | percent = (max - min) / 100; 22 | percentValue = Math.round((percentValue - min) / percent); 23 | 24 | //set color 25 | switch(typeof colors){ 26 | case "string": 27 | color = colors; 28 | break; 29 | case "function": 30 | color = colors(value); 31 | break; 32 | case "object": 33 | if(Array.isArray(colors)){ 34 | var unit = 100 / colors.length; 35 | var index = Math.floor(percentValue / unit); 36 | 37 | index = Math.min(index, colors.length - 1); 38 | index = Math.max(index, 0); 39 | color = colors[index]; 40 | break; 41 | } 42 | } 43 | 44 | el.style.backgroundColor = color; 45 | 46 | return el; 47 | } -------------------------------------------------------------------------------- /src/js/modules/GroupRows/GroupComponent.js: -------------------------------------------------------------------------------- 1 | //public group object 2 | export default class GroupComponent { 3 | constructor (group){ 4 | this._group = group; 5 | this.type = "GroupComponent"; 6 | 7 | return new Proxy(this, { 8 | get: function(target, name, receiver) { 9 | if (typeof target[name] !== "undefined") { 10 | return target[name]; 11 | }else{ 12 | return target._group.groupManager.table.componentFunctionBinder.handle("group", target._group, name); 13 | } 14 | } 15 | }); 16 | } 17 | 18 | getKey(){ 19 | return this._group.key; 20 | } 21 | 22 | getField(){ 23 | return this._group.field; 24 | } 25 | 26 | getElement(){ 27 | return this._group.element; 28 | } 29 | 30 | getRows(){ 31 | return this._group.getRows(true); 32 | } 33 | 34 | getSubGroups(){ 35 | return this._group.getSubGroups(true); 36 | } 37 | 38 | getParentGroup(){ 39 | return this._group.parent ? this._group.parent.getComponent() : false; 40 | } 41 | 42 | isVisible(){ 43 | return this._group.visible; 44 | } 45 | 46 | show(){ 47 | this._group.show(); 48 | } 49 | 50 | hide(){ 51 | this._group.hide(); 52 | } 53 | 54 | toggle(){ 55 | this._group.toggleVisibility(); 56 | } 57 | 58 | scrollTo(position, ifVisible){ 59 | return this._group.groupManager.table.rowManager.scrollToRow(this._group, position, ifVisible); 60 | } 61 | 62 | _getSelf(){ 63 | return this._group; 64 | } 65 | 66 | getTable(){ 67 | return this._group.groupManager.table; 68 | } 69 | } -------------------------------------------------------------------------------- /src/js/modules/History/defaults/redoers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | cellEdit: function(action){ 3 | action.component.setValueProcessData(action.data.newValue); 4 | action.component.cellRendered(); 5 | }, 6 | 7 | rowAdd: function(action){ 8 | var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); 9 | 10 | if(this.table.options.groupBy && this.table.modExists("groupRows")){ 11 | this.table.modules.groupRows.updateGroupRows(true); 12 | } 13 | 14 | this._rebindRow(action.component, newRow); 15 | 16 | this.table.rowManager.checkPlaceholder(); 17 | }, 18 | 19 | rowDelete:function(action){ 20 | action.component.deleteActual(); 21 | 22 | this.table.rowManager.checkPlaceholder(); 23 | }, 24 | 25 | rowMove: function(action){ 26 | this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posTo), action.data.after); 27 | 28 | this.table.rowManager.regenerateRowPositions(); 29 | this.table.rowManager.reRenderInPosition(); 30 | }, 31 | }; -------------------------------------------------------------------------------- /src/js/modules/History/defaults/undoers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | cellEdit: function(action){ 3 | action.component.setValueProcessData(action.data.oldValue); 4 | action.component.cellRendered(); 5 | }, 6 | 7 | rowAdd: function(action){ 8 | action.component.deleteActual(); 9 | 10 | this.table.rowManager.checkPlaceholder(); 11 | }, 12 | 13 | rowDelete: function(action){ 14 | var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); 15 | 16 | if(this.table.options.groupBy && this.table.modExists("groupRows")){ 17 | this.table.modules.groupRows.updateGroupRows(true); 18 | } 19 | 20 | this._rebindRow(action.component, newRow); 21 | 22 | this.table.rowManager.checkPlaceholder(); 23 | }, 24 | 25 | rowMove: function(action){ 26 | var after = (action.data.posFrom - action.data.posTo) > 0; 27 | 28 | this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posFrom), after); 29 | 30 | this.table.rowManager.regenerateRowPositions(); 31 | this.table.rowManager.reRenderInPosition(); 32 | }, 33 | }; -------------------------------------------------------------------------------- /src/js/modules/History/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | import bindings from './keybindings/bindings.js'; 2 | import actions from './keybindings/actions.js'; 3 | 4 | export default { 5 | keybindings:{ 6 | bindings:bindings, 7 | actions:actions 8 | }, 9 | }; -------------------------------------------------------------------------------- /src/js/modules/History/extensions/keybindings/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | undo:function(e){ 3 | var cell = false; 4 | if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){ 5 | 6 | cell = this.table.modules.edit.currentCell; 7 | 8 | if(!cell){ 9 | e.preventDefault(); 10 | this.table.modules.history.undo(); 11 | } 12 | } 13 | }, 14 | 15 | redo:function(e){ 16 | var cell = false; 17 | if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){ 18 | 19 | cell = this.table.modules.edit.currentCell; 20 | 21 | if(!cell){ 22 | e.preventDefault(); 23 | this.table.modules.history.redo(); 24 | } 25 | } 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/js/modules/History/extensions/keybindings/bindings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | undo:["ctrl + 90", "meta + 90"], 3 | redo:["ctrl + 89", "meta + 89"], 4 | }; 5 | -------------------------------------------------------------------------------- /src/js/modules/Import/defaults/importers.js: -------------------------------------------------------------------------------- 1 | import csv from './importers/csv.js'; 2 | import json from './importers/json.js'; 3 | import array from './importers/array.js'; 4 | import xlsx from './importers/xlsx.js'; 5 | 6 | export default { 7 | csv:csv, 8 | json:json, 9 | array:array, 10 | xlsx:xlsx, 11 | }; -------------------------------------------------------------------------------- /src/js/modules/Import/defaults/importers/array.js: -------------------------------------------------------------------------------- 1 | export default function (input){ 2 | return input; 3 | } -------------------------------------------------------------------------------- /src/js/modules/Import/defaults/importers/csv.js: -------------------------------------------------------------------------------- 1 | export default function(input){ 2 | var data = [], 3 | row = 0, 4 | col = 0, 5 | inQuote = false; 6 | 7 | //Iterate over each character 8 | for (let index = 0; index < input.length; index++) { 9 | let char = input[index], 10 | nextChar = input[index+1]; 11 | 12 | //Initialize empty row 13 | if(!data[row]){ 14 | data[row] = []; 15 | } 16 | 17 | //Initialize empty column 18 | if(!data[row][col]){ 19 | data[row][col] = ""; 20 | } 21 | 22 | //Handle quotation mark inside string 23 | if (char == '"' && inQuote && nextChar == '"') { 24 | data[row][col] += char; 25 | index++; 26 | continue; 27 | } 28 | 29 | //Begin / End Quote 30 | if (char == '"') { 31 | inQuote = !inQuote; 32 | continue; 33 | } 34 | 35 | //Next column (if not in quote) 36 | if (char == ',' && !inQuote) { 37 | col++; 38 | continue; 39 | } 40 | 41 | //New row if new line and not in quote (CRLF) 42 | if (char == '\r' && nextChar == '\n' && !inQuote) { 43 | col = 0; 44 | row++; 45 | index++; 46 | continue; 47 | } 48 | 49 | //New row if new line and not in quote (CR or LF) 50 | if ((char == '\r' || char == '\n') && !inQuote) { 51 | col = 0; 52 | row++; 53 | continue; 54 | } 55 | 56 | //Normal Character, append to column 57 | data[row][col] += char; 58 | } 59 | 60 | return data; 61 | } -------------------------------------------------------------------------------- /src/js/modules/Import/defaults/importers/json.js: -------------------------------------------------------------------------------- 1 | export default function(input){ 2 | try { 3 | return JSON.parse(input); 4 | } catch(e) { 5 | console.warn("JSON Import Error - File contents is invalid JSON", e); 6 | return Promise.reject(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/js/modules/Import/defaults/importers/xlsx.js: -------------------------------------------------------------------------------- 1 | export default function(input){ 2 | var XLSXLib = this.dependencyRegistry.lookup("XLSX"), 3 | workbook2 = XLSXLib.read(input), 4 | sheet = workbook2.Sheets[workbook2.SheetNames[0]]; 5 | 6 | return XLSXLib.utils.sheet_to_json(sheet, {header: 1 }); 7 | } -------------------------------------------------------------------------------- /src/js/modules/Keybindings/defaults/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | keyBlock:function(e){ 3 | e.stopPropagation(); 4 | e.preventDefault(); 5 | }, 6 | 7 | scrollPageUp:function(e){ 8 | var rowManager = this.table.rowManager, 9 | newPos = rowManager.scrollTop - rowManager.element.clientHeight; 10 | 11 | e.preventDefault(); 12 | 13 | if(rowManager.displayRowsCount){ 14 | if(newPos >= 0){ 15 | rowManager.element.scrollTop = newPos; 16 | }else{ 17 | rowManager.scrollToRow(rowManager.getDisplayRows()[0]); 18 | } 19 | } 20 | 21 | this.table.element.focus(); 22 | }, 23 | 24 | scrollPageDown:function(e){ 25 | var rowManager = this.table.rowManager, 26 | newPos = rowManager.scrollTop + rowManager.element.clientHeight, 27 | scrollMax = rowManager.element.scrollHeight; 28 | 29 | e.preventDefault(); 30 | 31 | if(rowManager.displayRowsCount){ 32 | if(newPos <= scrollMax){ 33 | rowManager.element.scrollTop = newPos; 34 | }else{ 35 | rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); 36 | } 37 | } 38 | 39 | this.table.element.focus(); 40 | 41 | }, 42 | 43 | scrollToStart:function(e){ 44 | var rowManager = this.table.rowManager; 45 | 46 | e.preventDefault(); 47 | 48 | if(rowManager.displayRowsCount){ 49 | rowManager.scrollToRow(rowManager.getDisplayRows()[0]); 50 | } 51 | 52 | this.table.element.focus(); 53 | }, 54 | 55 | scrollToEnd:function(e){ 56 | var rowManager = this.table.rowManager; 57 | 58 | e.preventDefault(); 59 | 60 | if(rowManager.displayRowsCount){ 61 | rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); 62 | } 63 | 64 | this.table.element.focus(); 65 | }, 66 | 67 | navPrev:function(e){ 68 | this.dispatch("keybinding-nav-prev", e); 69 | }, 70 | 71 | navNext:function(e){ 72 | this.dispatch("keybinding-nav-next", e); 73 | }, 74 | 75 | navLeft:function(e){ 76 | this.dispatch("keybinding-nav-left", e); 77 | }, 78 | 79 | navRight:function(e){ 80 | this.dispatch("keybinding-nav-right", e); 81 | }, 82 | 83 | navUp:function(e){ 84 | this.dispatch("keybinding-nav-up", e); 85 | }, 86 | 87 | navDown:function(e){ 88 | this.dispatch("keybinding-nav-down", e); 89 | }, 90 | }; 91 | -------------------------------------------------------------------------------- /src/js/modules/Keybindings/defaults/bindings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | navPrev:"shift + 9", 3 | navNext:9, 4 | navUp:38, 5 | navDown:40, 6 | navLeft:37, 7 | navRight:39, 8 | scrollPageUp:33, 9 | scrollPageDown:34, 10 | scrollToStart:36, 11 | scrollToEnd:35, 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/modules/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import Module from '../../core/Module.js'; 2 | 3 | import defaultModes from './defaults/modes.js'; 4 | 5 | export default class Layout extends Module{ 6 | 7 | static moduleName = "layout"; 8 | 9 | //load defaults 10 | static modes = defaultModes; 11 | 12 | constructor(table){ 13 | super(table, "layout"); 14 | 15 | this.mode = null; 16 | 17 | this.registerTableOption("layout", "fitData"); //layout type 18 | this.registerTableOption("layoutColumnsOnNewData", false); //update column widths on setData 19 | 20 | this.registerColumnOption("widthGrow"); 21 | this.registerColumnOption("widthShrink"); 22 | } 23 | 24 | //initialize layout system 25 | initialize(){ 26 | var layout = this.table.options.layout; 27 | 28 | if(Layout.modes[layout]){ 29 | this.mode = layout; 30 | }else{ 31 | console.warn("Layout Error - invalid mode set, defaulting to 'fitData' : " + layout); 32 | this.mode = 'fitData'; 33 | } 34 | 35 | this.table.element.setAttribute("tabulator-layout", this.mode); 36 | this.subscribe("column-init", this.initializeColumn.bind(this)); 37 | } 38 | 39 | initializeColumn(column){ 40 | if(column.definition.widthGrow){ 41 | column.definition.widthGrow = Number(column.definition.widthGrow); 42 | } 43 | if(column.definition.widthShrink){ 44 | column.definition.widthShrink = Number(column.definition.widthShrink); 45 | } 46 | } 47 | 48 | getMode(){ 49 | return this.mode; 50 | } 51 | 52 | //trigger table layout 53 | layout(dataChanged){ 54 | 55 | var variableHeight = this.table.columnManager.columnsByIndex.find((column) => column.definition.variableHeight || column.definition.formatter === "textarea"); 56 | 57 | this.dispatch("layout-refreshing"); 58 | Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged); 59 | 60 | if(variableHeight){ 61 | this.table.rowManager.normalizeHeight(true); 62 | } 63 | 64 | this.dispatch("layout-refreshed"); 65 | } 66 | } -------------------------------------------------------------------------------- /src/js/modules/Layout/defaults/modes.js: -------------------------------------------------------------------------------- 1 | import fitData from './modes/fitData.js'; 2 | import fitDataGeneral from './modes/fitDataGeneral.js'; 3 | import fitDataStretch from './modes/fitDataStretch.js'; 4 | import fitColumns from './modes/fitColumns.js'; 5 | 6 | export default { 7 | fitData:fitData, 8 | fitDataFill:fitDataGeneral, 9 | fitDataTable:fitDataGeneral, 10 | fitDataStretch:fitDataStretch, 11 | fitColumns:fitColumns , 12 | }; -------------------------------------------------------------------------------- /src/js/modules/Layout/defaults/modes/fitData.js: -------------------------------------------------------------------------------- 1 | //resize columns to fit data they contain 2 | export default function(columns, forced){ 3 | if(forced){ 4 | this.table.columnManager.renderer.reinitializeColumnWidths(columns); 5 | } 6 | 7 | if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ 8 | this.table.modules.responsiveLayout.update(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/js/modules/Layout/defaults/modes/fitDataGeneral.js: -------------------------------------------------------------------------------- 1 | //resize columns to fit data they contain and stretch row to fill table, also used for fitDataTable 2 | export default function(columns, forced){ 3 | columns.forEach(function(column){ 4 | column.reinitializeWidth(); 5 | }); 6 | 7 | if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ 8 | this.table.modules.responsiveLayout.update(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/js/modules/Layout/defaults/modes/fitDataStretch.js: -------------------------------------------------------------------------------- 1 | //resize columns to fit data the contain and stretch last column to fill table 2 | export default function(columns, forced){ 3 | var colsWidth = 0, 4 | tableWidth = this.table.rowManager.element.clientWidth, 5 | gap = 0, 6 | lastCol = false; 7 | 8 | columns.forEach((column, i) => { 9 | if(!column.widthFixed){ 10 | column.reinitializeWidth(); 11 | } 12 | 13 | if(this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible){ 14 | lastCol = column; 15 | } 16 | 17 | if(column.visible){ 18 | colsWidth += column.getWidth(); 19 | } 20 | }); 21 | 22 | if(lastCol){ 23 | gap = tableWidth - colsWidth + lastCol.getWidth(); 24 | 25 | if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ 26 | lastCol.setWidth(0); 27 | this.table.modules.responsiveLayout.update(); 28 | } 29 | 30 | if(gap > 0){ 31 | lastCol.setWidth(gap); 32 | }else{ 33 | lastCol.reinitializeWidth(); 34 | } 35 | }else{ 36 | if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ 37 | this.table.modules.responsiveLayout.update(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/js/modules/Localize/defaults/langs.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "default":{ //hold default locale text 3 | "groups":{ 4 | "item":"item", 5 | "items":"items", 6 | }, 7 | "columns":{ 8 | }, 9 | "data":{ 10 | "loading":"Loading", 11 | "error":"Error", 12 | }, 13 | "pagination":{ 14 | "page_size":"Page Size", 15 | "page_title":"Show Page", 16 | "first":"First", 17 | "first_title":"First Page", 18 | "last":"Last", 19 | "last_title":"Last Page", 20 | "prev":"Prev", 21 | "prev_title":"Prev Page", 22 | "next":"Next", 23 | "next_title":"Next Page", 24 | "all":"All", 25 | "counter":{ 26 | "showing": "Showing", 27 | "of": "of", 28 | "rows": "rows", 29 | "pages": "pages", 30 | } 31 | }, 32 | "headerFilters":{ 33 | "default":"filter column...", 34 | "columns":{} 35 | } 36 | }, 37 | }; -------------------------------------------------------------------------------- /src/js/modules/MoveRows/defaults/receivers.js: -------------------------------------------------------------------------------- 1 | export default{ 2 | insert:function(fromRow, toRow, fromTable){ 3 | this.table.addRow(fromRow.getData(), undefined, toRow); 4 | return true; 5 | }, 6 | 7 | add:function(fromRow, toRow, fromTable){ 8 | this.table.addRow(fromRow.getData()); 9 | return true; 10 | }, 11 | 12 | update:function(fromRow, toRow, fromTable){ 13 | if(toRow){ 14 | toRow.update(fromRow.getData()); 15 | return true; 16 | } 17 | 18 | return false; 19 | }, 20 | 21 | replace:function(fromRow, toRow, fromTable){ 22 | if(toRow){ 23 | this.table.addRow(fromRow.getData(), undefined, toRow); 24 | toRow.delete(); 25 | return true; 26 | } 27 | 28 | return false; 29 | }, 30 | }; -------------------------------------------------------------------------------- /src/js/modules/MoveRows/defaults/senders.js: -------------------------------------------------------------------------------- 1 | export default { 2 | delete:function(fromRow, toRow, toTable){ 3 | fromRow.delete(); 4 | } 5 | }; -------------------------------------------------------------------------------- /src/js/modules/Mutator/defaults/mutators.js: -------------------------------------------------------------------------------- 1 | export default {}; -------------------------------------------------------------------------------- /src/js/modules/Page/defaults/pageCounters.js: -------------------------------------------------------------------------------- 1 | import rows from './pageCounters/rows.js'; 2 | import pages from './pageCounters/pages.js'; 3 | 4 | 5 | export default { 6 | rows:rows, 7 | pages:pages, 8 | }; -------------------------------------------------------------------------------- /src/js/modules/Page/defaults/pageCounters/pages.js: -------------------------------------------------------------------------------- 1 | export default function(pageSize, currentRow, currentPage, totalRows, totalPages){ 2 | 3 | var el = document.createElement("span"), 4 | showingEl = document.createElement("span"), 5 | valueEl = document.createElement("span"), 6 | ofEl = document.createElement("span"), 7 | totalEl = document.createElement("span"), 8 | rowsEl = document.createElement("span"); 9 | 10 | this.table.modules.localize.langBind("pagination|counter|showing", (value) => { 11 | showingEl.innerHTML = value; 12 | }); 13 | 14 | valueEl.innerHTML = " " + currentPage + " "; 15 | 16 | this.table.modules.localize.langBind("pagination|counter|of", (value) => { 17 | ofEl.innerHTML = value; 18 | }); 19 | 20 | totalEl.innerHTML = " " + totalPages + " "; 21 | 22 | this.table.modules.localize.langBind("pagination|counter|pages", (value) => { 23 | rowsEl.innerHTML = value; 24 | }); 25 | 26 | el.appendChild(showingEl); 27 | el.appendChild(valueEl); 28 | el.appendChild(ofEl); 29 | el.appendChild(totalEl); 30 | el.appendChild(rowsEl); 31 | 32 | return el; 33 | } -------------------------------------------------------------------------------- /src/js/modules/Page/defaults/pageCounters/rows.js: -------------------------------------------------------------------------------- 1 | export default function(pageSize, currentRow, currentPage, totalRows, totalPages){ 2 | var el = document.createElement("span"), 3 | showingEl = document.createElement("span"), 4 | valueEl = document.createElement("span"), 5 | ofEl = document.createElement("span"), 6 | totalEl = document.createElement("span"), 7 | rowsEl = document.createElement("span"); 8 | 9 | this.table.modules.localize.langBind("pagination|counter|showing", (value) => { 10 | showingEl.innerHTML = value; 11 | }); 12 | 13 | this.table.modules.localize.langBind("pagination|counter|of", (value) => { 14 | ofEl.innerHTML = value; 15 | }); 16 | 17 | this.table.modules.localize.langBind("pagination|counter|rows", (value) => { 18 | rowsEl.innerHTML = value; 19 | }); 20 | 21 | if(totalRows){ 22 | valueEl.innerHTML = " " + currentRow + "-" + Math.min((currentRow + pageSize - 1), totalRows) + " "; 23 | 24 | totalEl.innerHTML = " " + totalRows + " "; 25 | 26 | el.appendChild(showingEl); 27 | el.appendChild(valueEl); 28 | el.appendChild(ofEl); 29 | el.appendChild(totalEl); 30 | el.appendChild(rowsEl); 31 | }else{ 32 | valueEl.innerHTML = " 0 "; 33 | 34 | el.appendChild(showingEl); 35 | el.appendChild(valueEl); 36 | el.appendChild(rowsEl); 37 | } 38 | 39 | return el; 40 | } -------------------------------------------------------------------------------- /src/js/modules/Persistence/defaults/readers.js: -------------------------------------------------------------------------------- 1 | // read persistance information from storage 2 | export default { 3 | local:function(id, type){ 4 | var data = localStorage.getItem(id + "-" + type); 5 | 6 | return data ? JSON.parse(data) : false; 7 | }, 8 | cookie:function(id, type){ 9 | var cookie = document.cookie, 10 | key = id + "-" + type, 11 | cookiePos = cookie.indexOf(key + "="), 12 | end, data; 13 | 14 | //if cookie exists, decode and load column data into tabulator 15 | if(cookiePos > -1){ 16 | cookie = cookie.slice(cookiePos); 17 | 18 | end = cookie.indexOf(";"); 19 | 20 | if(end > -1){ 21 | cookie = cookie.slice(0, end); 22 | } 23 | 24 | data = cookie.replace(key + "=", ""); 25 | } 26 | 27 | return data ? JSON.parse(data) : false; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/js/modules/Persistence/defaults/writers.js: -------------------------------------------------------------------------------- 1 | //write persistence information to storage 2 | export default { 3 | local:function(id, type, data){ 4 | localStorage.setItem(id + "-" + type, JSON.stringify(data)); 5 | }, 6 | cookie:function(id, type, data){ 7 | var expireDate = new Date(); 8 | 9 | expireDate.setDate(expireDate.getDate() + 10000); 10 | 11 | document.cookie = id + "-" + type + "=" + JSON.stringify(data) + "; expires=" + expireDate.toUTCString(); 12 | } 13 | }; -------------------------------------------------------------------------------- /src/js/modules/ResponsiveLayout/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | import responsiveCollapse from './formatters/responsiveCollapse.js'; 2 | 3 | export default { 4 | format:{ 5 | formatters:{ 6 | responsiveCollapse:responsiveCollapse, 7 | } 8 | } 9 | }; -------------------------------------------------------------------------------- /src/js/modules/ResponsiveLayout/extensions/formatters/responsiveCollapse.js: -------------------------------------------------------------------------------- 1 | export default function(cell, formatterParams, onRendered){ 2 | var el = document.createElement("div"), 3 | config = cell.getRow()._row.modules.responsiveLayout; 4 | 5 | el.classList.add("tabulator-responsive-collapse-toggle"); 6 | 7 | el.innerHTML = ` 8 | 9 | 10 | 11 | 12 | 13 | 14 | `; 15 | 16 | cell.getElement().classList.add("tabulator-row-handle"); 17 | 18 | function toggleList(isOpen){ 19 | var collapseEl = config.element; 20 | 21 | config.open = isOpen; 22 | 23 | if(collapseEl){ 24 | 25 | if(config.open){ 26 | el.classList.add("open"); 27 | collapseEl.style.display = ''; 28 | }else{ 29 | el.classList.remove("open"); 30 | collapseEl.style.display = 'none'; 31 | } 32 | } 33 | } 34 | 35 | el.addEventListener("click", function(e){ 36 | e.stopImmediatePropagation(); 37 | toggleList(!config.open); 38 | cell.getTable().rowManager.adjustTableSize(); 39 | }); 40 | 41 | toggleList(config.open); 42 | 43 | return el; 44 | } -------------------------------------------------------------------------------- /src/js/modules/SelectRange/RangeComponent.js: -------------------------------------------------------------------------------- 1 | export default class RangeComponent { 2 | constructor(range) { 3 | this._range = range; 4 | 5 | return new Proxy(this, { 6 | get: function (target, name, receiver) { 7 | if (typeof target[name] !== "undefined") { 8 | return target[name]; 9 | } else { 10 | return target._range.table.componentFunctionBinder.handle("range", target._range, name); 11 | } 12 | }, 13 | }); 14 | } 15 | 16 | getElement() { 17 | return this._range.element; 18 | } 19 | 20 | getData() { 21 | return this._range.getData(); 22 | } 23 | 24 | getCells() { 25 | return this._range.getCells(true, true); 26 | } 27 | 28 | getStructuredCells() { 29 | return this._range.getStructuredCells(); 30 | } 31 | 32 | getRows() { 33 | return this._range.getRows().map((row) => row.getComponent()); 34 | } 35 | 36 | getColumns() { 37 | return this._range.getColumns().map((column) => column.getComponent()); 38 | } 39 | 40 | getBounds() { 41 | return this._range.getBounds(); 42 | } 43 | 44 | getTopEdge() { 45 | return this._range.top; 46 | } 47 | 48 | getBottomEdge() { 49 | return this._range.bottom; 50 | } 51 | 52 | getLeftEdge() { 53 | return this._range.left; 54 | } 55 | 56 | getRightEdge() { 57 | return this._range.right; 58 | } 59 | 60 | setBounds(start, end){ 61 | if(this._range.destroyedGuard("setBounds")){ 62 | this._range.setBounds(start ? start._cell : start, end ? end._cell : end); 63 | } 64 | } 65 | 66 | setStartBound(start){ 67 | if(this._range.destroyedGuard("setStartBound")){ 68 | this._range.setEndBound(start ? start._cell : start); 69 | this._range.rangeManager.layoutElement(); 70 | } 71 | } 72 | 73 | setEndBound(end){ 74 | if(this._range.destroyedGuard("setEndBound")){ 75 | this._range.setEndBound(end ? end._cell : end); 76 | this._range.rangeManager.layoutElement(); 77 | } 78 | } 79 | 80 | clearValues(){ 81 | if(this._range.destroyedGuard("clearValues")){ 82 | this._range.clearValues(); 83 | } 84 | } 85 | 86 | remove(){ 87 | if(this._range.destroyedGuard("remove")){ 88 | this._range.destroy(true); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/clipboard/pasteActions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | range:function(data){ 3 | var rows = [], 4 | range = this.table.modules.selectRange.activeRange, 5 | singleCell = false, 6 | bounds, startCell, startRow, rowWidth, dataLength; 7 | 8 | dataLength = data.length; 9 | 10 | if(range){ 11 | bounds = range.getBounds(); 12 | startCell = bounds.start; 13 | 14 | if(bounds.start === bounds.end){ 15 | singleCell = true; 16 | } 17 | 18 | if(startCell){ 19 | rows = this.table.rowManager.activeRows.slice(); 20 | startRow = rows.indexOf(startCell.row); 21 | 22 | if(singleCell){ 23 | rowWidth = data.length; 24 | }else{ 25 | rowWidth = (rows.indexOf(bounds.end.row) - startRow) + 1; 26 | } 27 | 28 | 29 | if(startRow >-1){ 30 | this.table.blockRedraw(); 31 | 32 | rows = rows.slice(startRow, startRow + rowWidth); 33 | 34 | rows.forEach((row, i) => { 35 | row.updateData(data[i % dataLength]); 36 | }); 37 | 38 | this.table.restoreRedraw(); 39 | } 40 | } 41 | } 42 | 43 | return rows; 44 | } 45 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/clipboard/pasteParsers.js: -------------------------------------------------------------------------------- 1 | export default { 2 | range:function(clipboard){ 3 | var data = [], 4 | rows = [], 5 | range = this.table.modules.selectRange.activeRange, 6 | singleCell = false, 7 | bounds, startCell, colWidth, columnMap, startCol; 8 | 9 | if(range){ 10 | bounds = range.getBounds(); 11 | startCell = bounds.start; 12 | 13 | if(bounds.start === bounds.end){ 14 | singleCell = true; 15 | } 16 | 17 | if(startCell){ 18 | //get data from clipboard into array of columns and rows. 19 | clipboard = clipboard.split("\n"); 20 | 21 | clipboard.forEach(function(row){ 22 | data.push(row.split("\t")); 23 | }); 24 | 25 | if(data.length){ 26 | columnMap = this.table.columnManager.getVisibleColumnsByIndex(); 27 | startCol = columnMap.indexOf(startCell.column); 28 | 29 | if(startCol > -1){ 30 | if(singleCell){ 31 | colWidth = data[0].length; 32 | }else{ 33 | colWidth = (columnMap.indexOf(bounds.end.column) - startCol) + 1; 34 | } 35 | 36 | columnMap = columnMap.slice(startCol, startCol + colWidth); 37 | 38 | data.forEach((item) => { 39 | var row = {}; 40 | var itemLength = item.length; 41 | 42 | columnMap.forEach(function(col, i){ 43 | row[col.field] = item[i % itemLength]; 44 | }); 45 | 46 | rows.push(row); 47 | }); 48 | 49 | return rows; 50 | } 51 | } 52 | } 53 | } 54 | 55 | return false; 56 | } 57 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/export/columnLookups.js: -------------------------------------------------------------------------------- 1 | export default { 2 | range:function(){ 3 | var columns = this.modules.selectRange.selectedColumns(); 4 | 5 | if(this.columnManager.rowHeader){ 6 | columns.unshift(this.columnManager.rowHeader); 7 | } 8 | 9 | return columns; 10 | }, 11 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/export/rowLookups.js: -------------------------------------------------------------------------------- 1 | export default { 2 | range:function(){ 3 | return this.modules.selectRange.selectedRows(); 4 | }, 5 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | import bindings from './keybindings/bindings.js'; 2 | import actions from './keybindings/actions.js'; 3 | import pasteActions from './clipboard/pasteActions.js'; 4 | import pasteParsers from './clipboard/pasteParsers.js'; 5 | import columnLookups from './export/columnLookups.js'; 6 | import rowLookups from './export/rowLookups.js'; 7 | 8 | export default { 9 | keybindings:{ 10 | bindings:bindings, 11 | actions:actions 12 | }, 13 | clipboard:{ 14 | pasteActions:pasteActions, 15 | pasteParsers:pasteParsers 16 | }, 17 | export:{ 18 | columnLookups:columnLookups, 19 | rowLookups:rowLookups, 20 | } 21 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/keybindings/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | rangeJumpLeft: function(e){ 3 | this.dispatch("keybinding-nav-range", e, "left", true, false); 4 | }, 5 | rangeJumpRight: function(e){ 6 | this.dispatch("keybinding-nav-range", e, "right", true, false); 7 | }, 8 | rangeJumpUp: function(e){ 9 | this.dispatch("keybinding-nav-range", e, "up", true, false); 10 | }, 11 | rangeJumpDown: function(e){ 12 | this.dispatch("keybinding-nav-range", e, "down", true, false); 13 | }, 14 | rangeExpandLeft: function(e){ 15 | this.dispatch("keybinding-nav-range", e, "left", false, true); 16 | }, 17 | rangeExpandRight: function(e){ 18 | this.dispatch("keybinding-nav-range", e, "right", false, true); 19 | }, 20 | rangeExpandUp: function(e){ 21 | this.dispatch("keybinding-nav-range", e, "up", false, true); 22 | }, 23 | rangeExpandDown: function(e){ 24 | this.dispatch("keybinding-nav-range", e, "down", false, true); 25 | }, 26 | rangeExpandJumpLeft: function(e){ 27 | this.dispatch("keybinding-nav-range", e, "left", true, true); 28 | }, 29 | rangeExpandJumpRight: function(e){ 30 | this.dispatch("keybinding-nav-range", e, "right", true, true); 31 | }, 32 | rangeExpandJumpUp: function(e){ 33 | this.dispatch("keybinding-nav-range", e, "up", true, true); 34 | }, 35 | rangeExpandJumpDown: function(e){ 36 | this.dispatch("keybinding-nav-range", e, "down", true, true); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/js/modules/SelectRange/extensions/keybindings/bindings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | rangeJumpUp:["ctrl + 38", "meta + 38"], 3 | rangeJumpDown:["ctrl + 40", "meta + 40"], 4 | rangeJumpLeft:["ctrl + 37", "meta + 37"], 5 | rangeJumpRight:["ctrl + 39", "meta + 39"], 6 | rangeExpandUp:"shift + 38", 7 | rangeExpandDown:"shift + 40", 8 | rangeExpandLeft:"shift + 37", 9 | rangeExpandRight:"shift + 39", 10 | rangeExpandJumpUp:["ctrl + shift + 38", "meta + shift + 38"], 11 | rangeExpandJumpDown:["ctrl + shift + 40", "meta + shift + 40"], 12 | rangeExpandJumpLeft:["ctrl + shift + 37", "meta + shift + 37"], 13 | rangeExpandJumpRight:["ctrl + shift + 39", "meta + shift + 39"], 14 | }; 15 | -------------------------------------------------------------------------------- /src/js/modules/SelectRow/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | import rowSelection from './formatters/rowSelection.js'; 2 | 3 | export default { 4 | format:{ 5 | formatters:{ 6 | rowSelection:rowSelection, 7 | } 8 | } 9 | }; -------------------------------------------------------------------------------- /src/js/modules/SelectRow/extensions/formatters/rowSelection.js: -------------------------------------------------------------------------------- 1 | import RowComponent from '../../../../core/row/RowComponent.js'; 2 | 3 | export default function(cell, formatterParams, onRendered){ 4 | var checkbox = document.createElement("input"); 5 | var blocked = false; 6 | 7 | checkbox.type = 'checkbox'; 8 | 9 | checkbox.setAttribute("aria-label", "Select Row"); 10 | 11 | if(this.table.modExists("selectRow", true)){ 12 | 13 | checkbox.addEventListener("click", (e) => { 14 | e.stopPropagation(); 15 | }); 16 | 17 | if(typeof cell.getRow == 'function'){ 18 | var row = cell.getRow(); 19 | 20 | if(row instanceof RowComponent){ 21 | 22 | checkbox.addEventListener("change", (e) => { 23 | if(this.table.options.selectableRowsRangeMode === "click"){ 24 | if(!blocked){ 25 | row.toggleSelect(); 26 | }else{ 27 | blocked = false; 28 | } 29 | }else{ 30 | row.toggleSelect(); 31 | } 32 | }); 33 | 34 | if(this.table.options.selectableRowsRangeMode === "click"){ 35 | checkbox.addEventListener("click", (e) => { 36 | blocked = true; 37 | this.table.modules.selectRow.handleComplexRowClick(row._row, e); 38 | }); 39 | } 40 | 41 | checkbox.checked = row.isSelected && row.isSelected(); 42 | this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox); 43 | }else{ 44 | checkbox = ""; 45 | } 46 | }else { 47 | checkbox.addEventListener("change", (e) => { 48 | if(this.table.modules.selectRow.selectedRows.length){ 49 | this.table.deselectRow(); 50 | }else { 51 | this.table.selectRow(formatterParams.rowRange); 52 | } 53 | }); 54 | 55 | this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox); 56 | } 57 | } 58 | 59 | return checkbox; 60 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters.js: -------------------------------------------------------------------------------- 1 | import number from './sorters/number.js'; 2 | import string from './sorters/string.js'; 3 | import date from './sorters/date.js'; 4 | import time from './sorters/time.js'; 5 | import datetime from './sorters/datetime.js'; 6 | import boolean from './sorters/boolean.js'; 7 | import array from './sorters/array.js'; 8 | import exists from './sorters/exists.js'; 9 | import alphanum from './sorters/alphanum.js'; 10 | 11 | export default { 12 | number:number, 13 | string:string, 14 | date:date, 15 | time:time, 16 | datetime:datetime, 17 | boolean:boolean, 18 | array:array, 19 | exists:exists, 20 | alphanum:alphanum 21 | }; -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/alphanum.js: -------------------------------------------------------------------------------- 1 | //sort alpha numeric strings 2 | export default function(as, bs, aRow, bRow, column, dir, params){ 3 | var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/; 4 | var alignEmptyValues = params.alignEmptyValues; 5 | var emptyAlign = 0; 6 | 7 | //handle empty values 8 | if(!as && as!== 0){ 9 | emptyAlign = !bs && bs!== 0 ? 0 : -1; 10 | }else if(!bs && bs!== 0){ 11 | emptyAlign = 1; 12 | }else{ 13 | 14 | if(isFinite(as) && isFinite(bs)) return as - bs; 15 | a = String(as).toLowerCase(); 16 | b = String(bs).toLowerCase(); 17 | if(a === b) return 0; 18 | if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1; 19 | a = a.match(rx); 20 | b = b.match(rx); 21 | L = a.length > b.length ? b.length : a.length; 22 | while(i < L){ 23 | a1= a[i]; 24 | b1= b[i++]; 25 | if(a1 !== b1){ 26 | if(isFinite(a1) && isFinite(b1)){ 27 | if(a1.charAt(0) === "0") a1 = "." + a1; 28 | if(b1.charAt(0) === "0") b1 = "." + b1; 29 | return a1 - b1; 30 | } 31 | else return a1 > b1 ? 1 : -1; 32 | } 33 | } 34 | 35 | return a.length > b.length; 36 | } 37 | 38 | //fix empty values in position 39 | if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ 40 | emptyAlign *= -1; 41 | } 42 | 43 | return emptyAlign; 44 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/array.js: -------------------------------------------------------------------------------- 1 | import Helpers from '../../../../core/tools/Helpers.js'; 2 | 3 | //sort if element contains any data 4 | export default function(a, b, aRow, bRow, column, dir, params){ 5 | var type = params.type || "length", 6 | alignEmptyValues = params.alignEmptyValues, 7 | emptyAlign = 0, 8 | table = this.table, 9 | valueMap; 10 | 11 | if(params.valueMap){ 12 | if(typeof params.valueMap === "string"){ 13 | valueMap = function(value){ 14 | return value.map((item) => { 15 | return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, params.valueMap, item); 16 | }); 17 | }; 18 | }else{ 19 | valueMap = params.valueMap; 20 | } 21 | } 22 | 23 | function calc(value){ 24 | var result; 25 | 26 | if(valueMap){ 27 | value = valueMap(value); 28 | } 29 | 30 | switch(type){ 31 | case "length": 32 | result = value.length; 33 | break; 34 | 35 | case "sum": 36 | result = value.reduce(function(c, d){ 37 | return c + d; 38 | }); 39 | break; 40 | 41 | case "max": 42 | result = Math.max.apply(null, value) ; 43 | break; 44 | 45 | case "min": 46 | result = Math.min.apply(null, value) ; 47 | break; 48 | 49 | case "avg": 50 | result = value.reduce(function(c, d){ 51 | return c + d; 52 | }) / value.length; 53 | break; 54 | 55 | case "string": 56 | result = value.join(""); 57 | break; 58 | } 59 | 60 | return result; 61 | } 62 | 63 | //handle non array values 64 | if(!Array.isArray(a)){ 65 | emptyAlign = !Array.isArray(b) ? 0 : -1; 66 | }else if(!Array.isArray(b)){ 67 | emptyAlign = 1; 68 | }else{ 69 | if(type === "string"){ 70 | return String(calc(a)).toLowerCase().localeCompare(String(calc(b)).toLowerCase()); 71 | }else{ 72 | return calc(b) - calc(a); 73 | } 74 | } 75 | 76 | //fix empty values in position 77 | if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ 78 | emptyAlign *= -1; 79 | } 80 | 81 | return emptyAlign; 82 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/boolean.js: -------------------------------------------------------------------------------- 1 | //sort booleans 2 | export default function(a, b, aRow, bRow, column, dir, params){ 3 | var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0; 4 | var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0; 5 | 6 | return el1 - el2; 7 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/date.js: -------------------------------------------------------------------------------- 1 | import datetime from './datetime.js'; 2 | 3 | //sort date 4 | export default function(a, b, aRow, bRow, column, dir, params){ 5 | if(!params.format){ 6 | params.format = "dd/MM/yyyy"; 7 | } 8 | 9 | return datetime.call(this, a, b, aRow, bRow, column, dir, params); 10 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/datetime.js: -------------------------------------------------------------------------------- 1 | //sort datetime 2 | export default function(a, b, aRow, bRow, column, dir, params){ 3 | var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "DateTime"); 4 | var format = params.format || "dd/MM/yyyy HH:mm:ss", 5 | alignEmptyValues = params.alignEmptyValues, 6 | emptyAlign = 0; 7 | 8 | if(typeof DT != "undefined"){ 9 | if(!DT.isDateTime(a)){ 10 | if(format === "iso"){ 11 | a = DT.fromISO(String(a)); 12 | }else{ 13 | a = DT.fromFormat(String(a), format); 14 | } 15 | } 16 | 17 | if(!DT.isDateTime(b)){ 18 | if(format === "iso"){ 19 | b = DT.fromISO(String(b)); 20 | }else{ 21 | b = DT.fromFormat(String(b), format); 22 | } 23 | } 24 | 25 | if(!a.isValid){ 26 | emptyAlign = !b.isValid ? 0 : -1; 27 | }else if(!b.isValid){ 28 | emptyAlign = 1; 29 | }else{ 30 | //compare valid values 31 | return a - b; 32 | } 33 | 34 | //fix empty values in position 35 | if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ 36 | emptyAlign *= -1; 37 | } 38 | 39 | return emptyAlign; 40 | 41 | }else{ 42 | console.error("Sort Error - 'datetime' sorter is dependant on luxon.js"); 43 | } 44 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/exists.js: -------------------------------------------------------------------------------- 1 | //sort if element contains any data 2 | export default function(a, b, aRow, bRow, column, dir, params){ 3 | var el1 = typeof a == "undefined" ? 0 : 1; 4 | var el2 = typeof b == "undefined" ? 0 : 1; 5 | 6 | return el1 - el2; 7 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/number.js: -------------------------------------------------------------------------------- 1 | //sort numbers 2 | export default function(a, b, aRow, bRow, column, dir, params){ 3 | var alignEmptyValues = params.alignEmptyValues; 4 | var decimal = params.decimalSeparator; 5 | var thousand = params.thousandSeparator; 6 | var emptyAlign = 0; 7 | 8 | a = String(a); 9 | b = String(b); 10 | 11 | if(thousand){ 12 | a = a.split(thousand).join(""); 13 | b = b.split(thousand).join(""); 14 | } 15 | 16 | if(decimal){ 17 | a = a.split(decimal).join("."); 18 | b = b.split(decimal).join("."); 19 | } 20 | 21 | a = parseFloat(a); 22 | b = parseFloat(b); 23 | 24 | //handle non numeric values 25 | if(isNaN(a)){ 26 | emptyAlign = isNaN(b) ? 0 : -1; 27 | }else if(isNaN(b)){ 28 | emptyAlign = 1; 29 | }else{ 30 | //compare valid values 31 | return a - b; 32 | } 33 | 34 | //fix empty values in position 35 | if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ 36 | emptyAlign *= -1; 37 | } 38 | 39 | return emptyAlign; 40 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/string.js: -------------------------------------------------------------------------------- 1 | //sort strings 2 | export default function(a, b, aRow, bRow, column, dir, params){ 3 | var alignEmptyValues = params.alignEmptyValues; 4 | var emptyAlign = 0; 5 | var locale; 6 | 7 | //handle empty values 8 | if(!a){ 9 | emptyAlign = !b ? 0 : -1; 10 | }else if(!b){ 11 | emptyAlign = 1; 12 | }else{ 13 | //compare valid values 14 | switch(typeof params.locale){ 15 | case "boolean": 16 | if(params.locale){ 17 | locale = this.langLocale(); 18 | } 19 | break; 20 | case "string": 21 | locale = params.locale; 22 | break; 23 | } 24 | 25 | return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale); 26 | } 27 | 28 | //fix empty values in position 29 | if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ 30 | emptyAlign *= -1; 31 | } 32 | 33 | return emptyAlign; 34 | } -------------------------------------------------------------------------------- /src/js/modules/Sort/defaults/sorters/time.js: -------------------------------------------------------------------------------- 1 | import datetime from './datetime.js'; 2 | 3 | //sort times 4 | export default function(a, b, aRow, bRow, column, dir, params){ 5 | if(!params.format){ 6 | params.format = "HH:mm"; 7 | } 8 | 9 | return datetime.call(this, a, b, aRow, bRow, column, dir, params); 10 | } -------------------------------------------------------------------------------- /src/js/modules/Spreadsheet/GridCalculator.js: -------------------------------------------------------------------------------- 1 | export default class GridCalculator{ 2 | constructor(columns, rows){ 3 | this.columnCount = columns; 4 | this.rowCount = rows; 5 | 6 | this.columnString = []; 7 | this.columns = []; 8 | this.rows = []; 9 | } 10 | 11 | genColumns(data){ 12 | var colCount = Math.max(this.columnCount, Math.max(...data.map(item => item.length))); 13 | 14 | this.columnString = []; 15 | this.columns = []; 16 | 17 | for(let i = 1; i <= colCount; i++){ 18 | this.incrementChar(this.columnString.length - 1); 19 | this.columns.push(this.columnString.join("")); 20 | } 21 | 22 | return this.columns; 23 | } 24 | 25 | genRows(data){ 26 | var rowCount = Math.max(this.rowCount, data.length); 27 | 28 | this.rows = []; 29 | 30 | for(let i = 1; i <= rowCount; i++){ 31 | this.rows.push(i); 32 | } 33 | 34 | return this.rows; 35 | } 36 | 37 | incrementChar(i){ 38 | let char = this.columnString[i]; 39 | 40 | if(char){ 41 | if(char !== "Z"){ 42 | this.columnString[i] = String.fromCharCode(this.columnString[i].charCodeAt(0) + 1); 43 | }else{ 44 | this.columnString[i] = "A"; 45 | 46 | if(i){ 47 | this.incrementChar(i-1); 48 | }else{ 49 | this.columnString.push("A"); 50 | } 51 | } 52 | }else{ 53 | this.columnString.push("A"); 54 | } 55 | } 56 | 57 | setRowCount(count){ 58 | this.rowCount = count; 59 | } 60 | 61 | setColumnCount(count){ 62 | this.columnCount = count; 63 | } 64 | } -------------------------------------------------------------------------------- /src/js/modules/Spreadsheet/SheetComponent.js: -------------------------------------------------------------------------------- 1 | export default class SheetComponent { 2 | constructor(sheet) { 3 | this._sheet = sheet; 4 | 5 | return new Proxy(this, { 6 | get: function (target, name, receiver) { 7 | if (typeof target[name] !== "undefined") { 8 | return target[name]; 9 | } else { 10 | return target._sheet.table.componentFunctionBinder.handle("sheet", target._sheet, name); 11 | } 12 | }, 13 | }); 14 | } 15 | 16 | getTitle(){ 17 | return this._sheet.title; 18 | } 19 | 20 | getKey(){ 21 | return this._sheet.key; 22 | } 23 | 24 | getDefinition(){ 25 | return this._sheet.getDefinition(); 26 | } 27 | 28 | getData() { 29 | return this._sheet.getData(); 30 | } 31 | 32 | setData(data) { 33 | return this._sheet.setData(data); 34 | } 35 | 36 | clear(){ 37 | return this._sheet.clear(); 38 | } 39 | 40 | remove(){ 41 | return this._sheet.remove(); 42 | } 43 | 44 | active(){ 45 | return this._sheet.active(); 46 | } 47 | 48 | setTitle(title){ 49 | return this._sheet.setTitle(title); 50 | } 51 | 52 | setRows(rows){ 53 | return this._sheet.setRows(rows); 54 | } 55 | 56 | setColumns(columns){ 57 | return this._sheet.setColumns(columns); 58 | } 59 | } -------------------------------------------------------------------------------- /src/scss/themes/bootstrap/functions4.scss: -------------------------------------------------------------------------------- 1 | // Bootstrap functions 2 | // 3 | // Utility mixins and functions for evaluating source code across our variables, maps, and mixins. 4 | 5 | // Ascending 6 | // Used to evaluate Sass maps like our grid breakpoints. 7 | @mixin _assert-ascending($map, $map-name) { 8 | $prev-key: null; 9 | $prev-num: null; 10 | @each $key, $num in $map { 11 | @if $prev-num == null { 12 | // Do nothing 13 | } @else if not comparable($prev-num, $num) { 14 | @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !"; 15 | } @else if $prev-num >= $num { 16 | @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !"; 17 | } 18 | $prev-key: $key; 19 | $prev-num: $num; 20 | } 21 | } 22 | 23 | // Starts at zero 24 | // Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0. 25 | @mixin _assert-starts-at-zero($map) { 26 | $values: map-values($map); 27 | $first-value: nth($values, 1); 28 | @if $first-value != 0 { 29 | @warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}."; 30 | } 31 | } 32 | 33 | // Replace `$search` with `$replace` in `$string` 34 | // Used on our SVG icon backgrounds for custom forms. 35 | // 36 | // @author Hugo Giraudel 37 | // @param {String} $string - Initial string 38 | // @param {String} $search - Substring to replace 39 | // @param {String} $replace ('') - New value 40 | // @return {String} - Updated string 41 | @function str-replace($string, $search, $replace: "") { 42 | $index: str-index($string, $search); 43 | 44 | @if $index { 45 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 46 | } 47 | 48 | @return $string; 49 | } 50 | 51 | // Color contrast 52 | @function color-yiq($color) { 53 | $r: red($color); 54 | $g: green($color); 55 | $b: blue($color); 56 | 57 | $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000; 58 | 59 | @if ($yiq >= $yiq-contrasted-threshold) { 60 | @return $yiq-text-dark; 61 | } @else { 62 | @return $yiq-text-light; 63 | } 64 | } 65 | 66 | // Retrieve color Sass maps 67 | @function color($key: "blue") { 68 | @return map-get($colors, $key); 69 | } 70 | 71 | @function theme-color($key: "primary") { 72 | @return map-get($theme-colors, $key); 73 | } 74 | 75 | @function gray($key: "100") { 76 | @return map-get($grays, $key); 77 | } 78 | 79 | // Request a theme color level 80 | @function theme-color-level($color-name: "primary", $level: 0) { 81 | $color: theme-color($color-name); 82 | $color-base: if($level > 0, $black, $white); 83 | $level: abs($level); 84 | 85 | @return mix($color-base, $color, $level * $theme-color-interval); 86 | } 87 | 88 | 89 | // Tables 90 | 91 | @mixin table-row-variant($state, $background) { 92 | // Exact selectors below required to override `.table-striped` and prevent 93 | // inheritance to nested tables. 94 | .table-#{$state} { 95 | &, 96 | > th, 97 | > td { 98 | background-color: $background; 99 | } 100 | } 101 | 102 | // Hover states for `.table-hover` 103 | // Note: this is not available for cells or rows within `thead` or `tfoot`. 104 | .table-hover { 105 | $hover-background: darken($background, 5%); 106 | 107 | .table-#{$state} { 108 | @include hover { 109 | background-color: $hover-background; 110 | 111 | > td, 112 | > th { 113 | background-color: $hover-background; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/scss/themes/materialize/variables.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Materialize variables 3 | // ========================================================================== 4 | // 5 | // Table of Contents: 6 | // 7 | // 1. Colors 8 | // 2. Badges 9 | // 3. Buttons 10 | // 4. Cards 11 | // 5. Carousel 12 | // 6. Collapsible 13 | // 7. Chips 14 | // 8. Date + Time Picker 15 | // 9. Dropdown 16 | // 10. Forms 17 | // 11. Global 18 | // 12. Grid 19 | // 13. Navigation Bar 20 | // 14. Side Navigation 21 | // 15. Photo Slider 22 | // 16. Spinners | Loaders 23 | // 17. Tabs 24 | // 18. Tables 25 | // 19. Toasts 26 | // 20. Typography 27 | // 21. Footer 28 | // 22. Flow Text 29 | // 23. Collections 30 | // 24. Progress Bar 31 | 32 | 33 | // 18. Tables 34 | // ========================================================================== 35 | 36 | $materialize-red: ( 37 | "base": #e51c23, 38 | "lighten-5": #fdeaeb, 39 | "lighten-4": #f8c1c3, 40 | "lighten-3": #f3989b, 41 | "lighten-2": #ee6e73, 42 | "lighten-1": #ea454b, 43 | "darken-1": #d0181e, 44 | "darken-2": #b9151b, 45 | "darken-3": #a21318, 46 | "darken-4": #8b1014, 47 | ); 48 | 49 | $colors: ( 50 | "materialize-red": $materialize-red, 51 | ) !default; 52 | 53 | // usage: color("name_of_color", "type_of_color") 54 | // to avoid to repeating map-get($colors, ...) 55 | @function color($color, $type) { 56 | @if map-has-key($colors, $color) { 57 | $curr_color: map-get($colors, $color); 58 | @if map-has-key($curr_color, $type) { 59 | @return map-get($curr_color, $type); 60 | } 61 | } 62 | @warn "Unknown `#{$color}` - `#{$type}` in $colors."; 63 | @return null; 64 | } 65 | 66 | $table-border-color: rgba(0,0,0,.12) !default; 67 | $table-striped-color: #f8f8f8 !default; 68 | 69 | 70 | $primary-color: color("materialize-red", "lighten-2") !default; 71 | -------------------------------------------------------------------------------- /src/scss/themes/tabulator_simple.scss: -------------------------------------------------------------------------------- 1 | 2 | //Main Theme Variables 3 | $backgroundColor: #fff !default; //background color of tabulator 4 | $borderColor:#999 !default; //border to tabulator 5 | $textSize:14px !default; //table text size 6 | 7 | //header theming 8 | $headerBackgroundColor:#fff !default; //border to tabulator 9 | $headerTextColor:#555 !default; //header text color 10 | $headerBorderColor:#ddd !default; //header border color 11 | $headerSeparatorColor:#999 !default; //header bottom separator color 12 | $headerMargin:4px !default; //padding round header 13 | 14 | //column header arrows 15 | $sortArrowActive: #666 !default; 16 | $sortArrowInactive: #bbb !default; 17 | 18 | //row theming 19 | $rowBackgroundColor:#fff !default; //table row background color 20 | $rowAltBackgroundColor:#fff !default; //table row background color 21 | $rowBorderColor:#ddd !default; //table border color 22 | $rowTextColor:#333 !default; //table text color 23 | $rowHoverBackground:#bbb !default; //row background color on hover 24 | 25 | $rowSelectedBackground: #9ABCEA !default; //row background color when selected 26 | $rowSelectedBackgroundHover: #769BCC !default;//row background color when selected and hovered 27 | 28 | 29 | $editBoxColor:#1D68CD !default; //border color for edit boxes 30 | $errorColor:#dd0000 !default; //error indication 31 | 32 | //footer theming 33 | $footerBackgroundColor:#fff !default; //border to tabulator 34 | $footerTextColor:#555 !default; //footer text color 35 | $footerBorderColor:#aaa !default; //footer border color 36 | $footerSeparatorColor:#999 !default; //footer bottom separator color 37 | $footerActiveColor:#d00 !default; //footer bottom active text color 38 | 39 | @import "../tabulator.scss"; 40 | 41 | .tabulator{ 42 | border:none; 43 | background-color: $backgroundColor; 44 | 45 | .tabulator-header{ 46 | .tabulator-calcs-holder{ 47 | background:darken($headerBackgroundColor, 5%) !important; 48 | 49 | border-bottom:1px solid $headerSeparatorColor; 50 | 51 | .tabulator-row{ 52 | background:darken($headerBackgroundColor, 5%) !important; 53 | } 54 | } 55 | } 56 | 57 | .tabulator-tableholder{ 58 | .tabulator-placeholder{ 59 | span{ 60 | color:#000; 61 | } 62 | } 63 | } 64 | 65 | .tabulator-footer{ 66 | .tabulator-calcs-holder{ 67 | background:darken($footerBackgroundColor, 5%) !important; 68 | 69 | border-bottom:1px solid $footerBackgroundColor; 70 | 71 | .tabulator-row{ 72 | background:darken($footerBackgroundColor, 5%) !important; 73 | } 74 | } 75 | 76 | .tabulator-spreadsheet-tabs{ 77 | .tabulator-spreadsheet-tab{ 78 | font-weight: normal; 79 | 80 | &.tabulator-spreadsheet-tab-active{ 81 | color:$footerActiveColor; 82 | font-weight: bold; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | .tabulator-row{ 90 | border-bottom:1px solid $rowBorderColor; 91 | 92 | .tabulator-cell{ 93 | &:last-of-type{ 94 | border-right: none; 95 | } 96 | 97 | &.tabulator-row-header{ 98 | border-bottom:none; 99 | } 100 | } 101 | 102 | &.tabulator-group{ 103 | span{ 104 | color:#666; 105 | } 106 | } 107 | } 108 | 109 | .tabulator-print-table{ 110 | .tabulator-print-table-group{ 111 | span{ 112 | margin-left:10px; 113 | color:#666; 114 | } 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /test/e2e/basic.spec.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { test, expect } from "@playwright/test"; 3 | import { join } from "path"; 4 | 5 | test.describe("Tabulator functionality", () => { 6 | test.beforeEach(async ({ page }) => { 7 | const htmlPath = join(__dirname, "index.html"); 8 | await page.goto(`file://${htmlPath}`); 9 | await page.waitForSelector(".tabulator"); 10 | }); 11 | 12 | test("should initialize table correctly", async ({ page }) => { 13 | // Check that table is initialized 14 | const tableExists = await page.isVisible(".tabulator"); 15 | expect(tableExists).toBeTruthy(); 16 | 17 | // Check row count 18 | await page.waitForSelector(".tabulator-row"); 19 | const rowCount = await page.locator(".tabulator-row").count(); 20 | expect(rowCount).toBe(5); 21 | 22 | // Check column count 23 | const columnCount = await page.locator(".tabulator-col").count(); 24 | expect(columnCount).toBe(4); 25 | }); 26 | 27 | test.describe("Row operations", () => { 28 | test("should add a new row", async ({ page }) => { 29 | await page.evaluate(() => { 30 | window.testTable.addRow({ 31 | id: 6, 32 | name: "Frank", 33 | age: 29, 34 | gender: "Male", 35 | }); 36 | }); 37 | await page.waitForTimeout(300); 38 | 39 | const newRowCount = await page.locator(".tabulator-row").count(); 40 | expect(newRowCount).toBe(6); 41 | }); 42 | 43 | test("should update an existing row", async ({ page }) => { 44 | await page.evaluate(() => { 45 | window.testTable.updateRow(1, { name: "Alice Updated" }); 46 | }); 47 | await page.waitForTimeout(300); 48 | 49 | const updatedName = await page.evaluate(() => { 50 | return window.testTable.getRow(1).getData().name; 51 | }); 52 | 53 | expect(updatedName).toBe("Alice Updated"); 54 | }); 55 | 56 | test("should delete a row", async ({ page }) => { 57 | await page.evaluate(() => { 58 | window.testTable.deleteRow(1); 59 | }); 60 | await page.waitForTimeout(300); 61 | 62 | const finalRowCount = await page.locator(".tabulator-row").count(); 63 | expect(finalRowCount).toBe(4); // Original count minus one 64 | }); 65 | }); 66 | 67 | test.describe("Filtering functionality", () => { 68 | test("should filter rows by gender", async ({ page }) => { 69 | await page.evaluate(() => { 70 | window.testTable.setFilter("gender", "=", "Female"); 71 | }); 72 | await page.waitForTimeout(300); 73 | 74 | const filteredRowCount = await page.locator(".tabulator-row").count(); 75 | expect(filteredRowCount).toBe(2); // 2 females in our data 76 | }); 77 | 78 | test("should clear filters", async ({ page }) => { 79 | // First apply a filter 80 | await page.evaluate(() => { 81 | window.testTable.setFilter("gender", "=", "Female"); 82 | }); 83 | await page.waitForTimeout(300); 84 | 85 | // Then clear it 86 | await page.evaluate(() => { 87 | window.testTable.clearFilter(); 88 | }); 89 | await page.waitForTimeout(300); 90 | 91 | const rowCountAfterClearingFilter = await page 92 | .locator(".tabulator-row") 93 | .count(); 94 | expect(rowCountAfterClearingFilter).toBe(5); // Back to original count 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/e2e/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tabulator Test 6 | 7 | 8 | 19 | 20 | 21 |

Tabulator Test

22 |
23 | 24 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/unit/modules/Comms.spec.js: -------------------------------------------------------------------------------- 1 | import Module from '../../../src/js/core/Module.js'; 2 | import Comms from '../../../src/js/modules/Comms/Comms.js'; 3 | 4 | // Override the Module.prototype.registerTableFunction to avoid dependency on the full module system 5 | const originalRegisterTableFunction = Module.prototype.registerTableFunction; 6 | Module.prototype.registerTableFunction = function(name, func) { 7 | this.table[name] = func.bind(this); 8 | }; 9 | 10 | // This test file focuses only on the essential functionality of the Comms module 11 | describe('Comms', function(){ 12 | let comms; 13 | let mockTable; 14 | 15 | beforeEach(function(){ 16 | // Create mock element 17 | const element = document.createElement("div"); 18 | element.id = "table1"; 19 | document.body.appendChild(element); 20 | 21 | // Create mock table with only what we need for Comms 22 | mockTable = { 23 | element: element, 24 | modExists: jest.fn(), 25 | modules: { 26 | testModule: { 27 | commsReceived: jest.fn() 28 | } 29 | }, 30 | initGuard: jest.fn() // Add mock initGuard 31 | }; 32 | 33 | // Create the module to test 34 | comms = new Comms(mockTable); 35 | 36 | // Manually assign receive function without initialization 37 | mockTable.tableComms = function(sourceTable, module, action, data) { 38 | return comms.receive(sourceTable, module, action, data); 39 | }; 40 | }); 41 | 42 | afterEach(function(){ 43 | document.body.removeChild(document.getElementById("table1")); 44 | }); 45 | 46 | test('module can be created', function(){ 47 | expect(comms).toBeDefined(); 48 | expect(comms.table).toBe(mockTable); 49 | }); 50 | 51 | test('receive calls module commsReceived method', function(){ 52 | // Mock the modExists method to return true 53 | mockTable.modExists.mockReturnValue(true); 54 | 55 | // Mock sourceTable 56 | const mockSourceTable = document.createElement("div"); 57 | 58 | // Call receive 59 | comms.receive(mockSourceTable, "testModule", "testAction", {test: "data"}); 60 | 61 | // Verify the module's commsReceived was called correctly 62 | expect(mockTable.modExists).toHaveBeenCalledWith("testModule"); 63 | expect(mockTable.modules.testModule.commsReceived).toHaveBeenCalledWith( 64 | mockSourceTable, 65 | "testAction", 66 | {test: "data"} 67 | ); 68 | }); 69 | 70 | test('receive warns if module does not exist', function(){ 71 | const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); 72 | 73 | // Mock the modExists method to return false 74 | mockTable.modExists.mockReturnValue(false); 75 | 76 | // Mock sourceTable 77 | const mockSourceTable = document.createElement("div"); 78 | 79 | comms.receive(mockSourceTable, "nonexistentModule", "testAction", {test: "data"}); 80 | 81 | expect(consoleSpy).toHaveBeenCalled(); 82 | consoleSpy.mockRestore(); 83 | }); 84 | }); 85 | 86 | // Restore the original method after tests 87 | afterAll(() => { 88 | Module.prototype.registerTableFunction = originalRegisterTableFunction; 89 | }); -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JSDOM test setup helpers 3 | * 4 | * This file provides helper functions for common DOM element mocking 5 | * needs in Tabulator tests. 6 | */ 7 | 8 | // Create an element with automatic spy functions on common methods 9 | global.createSpyElement = (tagName) => { 10 | const element = document.createElement(tagName); 11 | 12 | // Add spies to common DOM methods 13 | element.appendChild = jest.fn().mockImplementation(element.appendChild); 14 | element.addEventListener = jest.fn().mockImplementation(element.addEventListener); 15 | element.classList.add = jest.fn().mockImplementation(element.classList.add); 16 | element.classList.remove = jest.fn().mockImplementation(element.classList.remove); 17 | 18 | return element; 19 | }; 20 | 21 | // Helper method to create mock events with required properties 22 | global.createMockEvent = (type, props = {}) => { 23 | // Create appropriate event type 24 | let event; 25 | 26 | if (type === 'click' || type === 'mousedown' || type === 'mouseup') { 27 | event = new MouseEvent(type, { bubbles: true, cancelable: true, ...props }); 28 | } else { 29 | event = new Event(type, { bubbles: true, cancelable: true, ...props }); 30 | } 31 | 32 | // Add any additional properties 33 | Object.entries(props).forEach(([key, value]) => { 34 | if (!event[key]) { 35 | event[key] = value; 36 | } 37 | }); 38 | 39 | // Add spy on preventDefault 40 | const originalPreventDefault = event.preventDefault; 41 | event.preventDefault = jest.fn().mockImplementation(() => originalPreventDefault.call(event)); 42 | 43 | return event; 44 | }; 45 | 46 | // Helper for column creation 47 | global.createMockColumn = (definition = {}) => { 48 | return { 49 | definition, 50 | titleElement: { 51 | insertBefore: jest.fn(), 52 | firstChild: {} 53 | }, 54 | getComponent: jest.fn().mockReturnValue({ column: true }) 55 | }; 56 | }; --------------------------------------------------------------------------------