├── .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 | 
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 | 
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 = `
11 |
12 | `;
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 | };
--------------------------------------------------------------------------------