├── __tests__ ├── e2e │ ├── support │ │ └── index.ts │ ├── tsconfig.json │ ├── mocks │ │ └── simple.html │ ├── plugins │ │ └── index.js │ └── specs │ │ └── simple.ts ├── helpers │ ├── transform-vue-template.js │ ├── mount-mixin-components.ts │ └── utils.ts └── integration │ ├── sample-data.json │ └── table-with-pager.ts ├── dist ├── themes │ ├── bootstrap-3.d.ts │ ├── bootstrap-4.d.ts │ ├── themes │ │ ├── bootstrap-3.d.ts │ │ └── bootstrap-4.d.ts │ ├── vuejs-datatable.d.ts │ ├── classes │ │ ├── handlers │ │ │ ├── index.d.ts │ │ │ ├── __mocks__ │ │ │ │ └── default-handler.d.ts │ │ │ ├── default-handler.d.ts │ │ │ └── i-handler.d.ts │ │ ├── __mocks__ │ │ │ ├── column.d.ts │ │ │ ├── settings.d.ts │ │ │ └── table-type.d.ts │ │ ├── index.d.ts │ │ ├── settings.d.ts │ │ ├── datatable-factory.d.ts │ │ └── table-type.d.ts │ ├── bootstrap-3.esm.js.map │ ├── bootstrap-4.esm.js.map │ ├── components │ │ ├── mixins │ │ │ └── table-type-consumer-factory.d.ts │ │ ├── vue-datatable-cell │ │ │ └── vue-datatable-cell.d.ts │ │ ├── vue-datatable-pager │ │ │ ├── vue-datatable-pager-button │ │ │ │ └── vue-datatable-pager-button.d.ts │ │ │ └── vue-datatable-pager.d.ts │ │ └── vue-datatable-header │ │ │ └── vue-datatable-header.d.ts │ ├── vuejs-datatable.esm.d.ts │ ├── bootstrap-3.esm.js │ ├── bootstrap-4.esm.js │ ├── bootstrap-3.js │ ├── bootstrap-4.js │ ├── bootstrap-3.js.map │ ├── bootstrap-4.js.map │ └── utils.d.ts ├── dependencies.txt ├── vuejs-datatable.d.ts ├── classes │ ├── handlers │ │ ├── index.d.ts │ │ ├── __mocks__ │ │ │ └── default-handler.d.ts │ │ ├── default-handler.d.ts │ │ └── i-handler.d.ts │ ├── __mocks__ │ │ ├── column.d.ts │ │ ├── settings.d.ts │ │ └── table-type.d.ts │ ├── index.d.ts │ ├── settings.d.ts │ ├── datatable-factory.d.ts │ └── table-type.d.ts ├── components │ ├── mixins │ │ └── table-type-consumer-factory.d.ts │ ├── vue-datatable-cell │ │ └── vue-datatable-cell.d.ts │ ├── vue-datatable-pager │ │ ├── vue-datatable-pager-button │ │ │ └── vue-datatable-pager-button.d.ts │ │ └── vue-datatable-pager.d.ts │ └── vue-datatable-header │ │ └── vue-datatable-header.d.ts ├── vuejs-datatable.esm.d.ts └── utils.d.ts ├── src ├── classes │ ├── handlers │ │ ├── index.ts │ │ ├── __mocks__ │ │ │ └── default-handler.ts │ │ ├── i-handler.ts │ │ └── default-handler.ts │ ├── index.ts │ ├── __mocks__ │ │ ├── settings.ts │ │ ├── column.ts │ │ └── table-type.ts │ ├── settings.spec.ts │ ├── settings.ts │ └── table-type.spec.ts ├── __mocks__ │ ├── vue-datatable-header.vue │ ├── vue-datatable.vue │ └── vue-datatable-pager.vue ├── html.shim.d.ts ├── components │ ├── vue-datatable-pager │ │ ├── vue-datatable-pager-button │ │ │ ├── vue-datatable-pager-button.html │ │ │ ├── vue-datatable-pager-button.ts │ │ │ └── vue-datatable-pager-button.spec.ts │ │ └── vue-datatable-pager.html │ ├── vue-datatable-header │ │ ├── vue-datatable-header.html │ │ └── vue-datatable-header.ts │ ├── vue-datatable-cell │ │ ├── vue-datatable-cell.html │ │ ├── vue-datatable-cell.ts │ │ └── vue-datatable-cell.spec.ts │ ├── mixins │ │ └── table-type-consumer-factory.ts │ └── vue-datatable │ │ └── vue-datatable.html ├── vuejs-datatable.ts ├── global.d.ts ├── themes │ ├── bootstrap-3.ts │ └── bootstrap-4.ts ├── vuejs-datatable.esm.ts ├── utils.ts └── utils.spec.ts ├── docs └── assets │ ├── images │ ├── icons.png │ ├── icons@2x.png │ ├── widgets.png │ └── widgets@2x.png │ ├── js │ └── bootstrap-3.js │ └── css │ └── additional-styles.css ├── tslint.json ├── cypress.json ├── jest.unit.config.js ├── jest.integration.config.js ├── .babelrc ├── tutorials ├── src │ ├── customize │ │ └── index.md │ ├── ajax │ │ └── index.md │ ├── no-pager │ │ ├── demo.ts │ │ └── index.md │ ├── pager-types │ │ ├── demo.ts │ │ └── index.md │ ├── basic │ │ ├── demo.ts │ │ └── index.md │ ├── prebuilt-theme │ │ ├── demo.ts │ │ └── index.md │ ├── multiple-tables │ │ ├── demo.ts │ │ └── index.md │ ├── custom-theme │ │ ├── index.md │ │ └── demo.ts │ ├── ajax-data │ │ ├── index.md │ │ └── demo.ts │ ├── utils.ts │ ├── ajax-handler │ │ ├── index.md │ │ └── demo.ts │ ├── custom-template │ │ └── index.md │ ├── limit-rows-processing │ │ ├── index.md │ │ └── demo.ts │ └── bundlers │ │ └── index.md ├── build │ ├── build-utils.ts │ ├── exec-script-transform.ts │ ├── rollupize.ts │ └── build-examples.ts ├── tutorials.json └── assets │ └── additional-styles.scss ├── .vscode ├── settings.json └── launch.json ├── renovate.json ├── .gitignore ├── ci ├── github_key.pub ├── log.ts ├── spawn-exec.ts ├── run-ci.ts └── travis_key.enc ├── typedoc.json ├── jest.config.js ├── LICENSE ├── karma.conf.js └── .travis.yml /__tests__/e2e/support/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-3.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-4.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/dependencies.txt: -------------------------------------------------------------------------------- 1 | No third parties dependencies -------------------------------------------------------------------------------- /dist/themes/themes/bootstrap-3.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/themes/themes/bootstrap-4.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/vuejs-datatable.d.ts: -------------------------------------------------------------------------------- 1 | export * from './vuejs-datatable.esm'; 2 | -------------------------------------------------------------------------------- /dist/themes/vuejs-datatable.d.ts: -------------------------------------------------------------------------------- 1 | export * from './vuejs-datatable.esm'; 2 | -------------------------------------------------------------------------------- /src/classes/handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './i-handler'; 2 | export * from './default-handler'; 3 | -------------------------------------------------------------------------------- /dist/classes/handlers/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './i-handler'; 2 | export * from './default-handler'; 3 | -------------------------------------------------------------------------------- /dist/themes/classes/handlers/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './i-handler'; 2 | export * from './default-handler'; 3 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GerkinDev/vuejs-datatable/HEAD/docs/assets/images/icons.png -------------------------------------------------------------------------------- /src/__mocks__/vue-datatable-header.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GerkinDev/vuejs-datatable/HEAD/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GerkinDev/vuejs-datatable/HEAD/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GerkinDev/vuejs-datatable/HEAD/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /dist/themes/bootstrap-3.esm.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bootstrap-3.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"} -------------------------------------------------------------------------------- /dist/themes/bootstrap-4.esm.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bootstrap-4.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"} -------------------------------------------------------------------------------- /src/html.shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.html" { 2 | export const render: () => any; 3 | //export const staticRenderFns: () => any; 4 | } 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@diaspora/dev-tslint" 4 | ], 5 | "rules": { 6 | "no-implicit-dependencies": [true, ["vuejs-datatable"]] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dist/classes/__mocks__/column.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const getRepresentation: jest.Mock; 3 | export declare const Column: jest.Mock; 4 | -------------------------------------------------------------------------------- /src/classes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './column'; 2 | export * from './datatable-factory'; 3 | export * from './handlers'; 4 | export * from './settings'; 5 | export * from './table-type'; 6 | -------------------------------------------------------------------------------- /dist/classes/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './column'; 2 | export * from './datatable-factory'; 3 | export * from './handlers'; 4 | export * from './settings'; 5 | export * from './table-type'; 6 | -------------------------------------------------------------------------------- /dist/themes/classes/__mocks__/column.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const getRepresentation: jest.Mock; 3 | export declare const Column: jest.Mock; 4 | -------------------------------------------------------------------------------- /dist/themes/classes/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './column'; 2 | export * from './datatable-factory'; 3 | export * from './handlers'; 4 | export * from './settings'; 5 | export * from './table-type'; 6 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrationFolder": "__tests__/e2e/specs", 3 | "supportFile": "__tests__/e2e/support/index.ts", 4 | "pluginsFile": "__tests__/e2e/plugins/index.js", 5 | "projectId": "dbqwx7" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/vue-datatable-pager/vue-datatable-pager-button/vue-datatable-pager-button.html: -------------------------------------------------------------------------------- 1 |
  • 5 | {{ value }} 6 |
  • 7 | -------------------------------------------------------------------------------- /jest.unit.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('./jest.config'), 3 | collectCoverage: true, 4 | testMatch: [ 5 | '/src/**/*.{spec,test}.[jt]s', 6 | '!**/__tests__/helpers/**/*.[jt]s', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /jest.integration.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('./jest.config'), 3 | collectCoverage: false, 4 | testMatch: [ 5 | '**/__tests__/integration/**/*.[jt]s', 6 | '!**/__tests__/helpers/**/*.[jt]s', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "@babel/preset-env", { 4 | "targets": "> 0.5%, not dead, cover 90%", 5 | "useBuiltIns": "usage", 6 | "corejs": 3 7 | }], 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tutorials/src/customize/index.md: -------------------------------------------------------------------------------- 1 | This plugin's goal is to be as versatile as possible. It can be configured for CSS frameworks such as Bootstrap 3 & 4, and several parts of the templates can be overriden. Check the tutorials below for more infos. -------------------------------------------------------------------------------- /dist/classes/__mocks__/settings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const get: jest.Mock; 3 | export declare const set: jest.Mock; 4 | export declare const merge: jest.Mock; 5 | export declare const Settings: jest.Mock; 6 | -------------------------------------------------------------------------------- /dist/themes/classes/__mocks__/settings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const get: jest.Mock; 3 | export declare const set: jest.Mock; 4 | export declare const merge: jest.Mock; 5 | export declare const Settings: jest.Mock; 6 | -------------------------------------------------------------------------------- /__tests__/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": "../node_modules", 5 | "target": "es5", 6 | "lib": ["es6", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": [ 10 | "**/*.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.options": { 3 | "extensions": [".js", ".vue"] 4 | }, 5 | "eslint.validate": [ 6 | "javascript", 7 | "javascriptreact", 8 | { "language": "vue", "autoFix": true } 9 | ], 10 | "vetur.validation.template": false, 11 | } 12 | -------------------------------------------------------------------------------- /dist/classes/__mocks__/table-type.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const getTableDefinition: jest.Mock; 3 | export declare const getPagerDefinition: jest.Mock; 4 | export declare const setting: jest.Mock; 5 | export declare const TableType: jest.Mock; 6 | -------------------------------------------------------------------------------- /src/vuejs-datatable.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { VuejsDatatableFactory } from './vuejs-datatable.esm'; 3 | export * from './vuejs-datatable.esm'; 4 | 5 | // The IIFE exposes the global `VuejsDatatable`. See the rollup config file & https://github.com/rollup/rollup/issues/494 6 | Vue.use( VuejsDatatableFactory ); 7 | -------------------------------------------------------------------------------- /dist/themes/classes/__mocks__/table-type.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const getTableDefinition: jest.Mock; 3 | export declare const getPagerDefinition: jest.Mock; 4 | export declare const setting: jest.Mock; 5 | export declare const TableType: jest.Mock; 6 | -------------------------------------------------------------------------------- /src/__mocks__/vue-datatable.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "schedule": "before 10pm on Monday", 6 | "automerge": true, 7 | "baseBranches": ["staging"], 8 | "branchPrefix": "greenkeeper/", 9 | "groupName": "all", 10 | "rangeStrategy": "replace", 11 | "commitMessagePrefix": "chore: 🤖 Renovate auto-bump" 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | tutorials/builds/ 5 | 6 | tutorials/assets/assets/vuejs-datatable\.js 7 | 8 | coverage/ 9 | 10 | stats/ 11 | 12 | dist/ 13 | 14 | docs/ 15 | 16 | \.rpt2_cache/ 17 | 18 | \.vscode/csak-timelog\.json 19 | 20 | tutorials/\.tmp/ 21 | 22 | cypress/videos/ 23 | 24 | cypress/screenshots/ 25 | 26 | .env 27 | -------------------------------------------------------------------------------- /src/classes/__mocks__/settings.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | export const get = jest.fn(); 3 | export const set = jest.fn(); 4 | export const merge = jest.fn(); 5 | export const Settings = jest.fn().mockImplementation( function mockSettings( this: any ) { 6 | this.get = get; 7 | this.set = set; 8 | this.merge = merge; 9 | return this; 10 | } ); 11 | -------------------------------------------------------------------------------- /tutorials/src/ajax/index.md: -------------------------------------------------------------------------------- 1 | You may want to use *ajax requests* to populate your table with data sourced from an API. In those cases, you can check out the following tutorials: 2 | 3 | * [Using `data` function](./ajax-data.html) if you want to use the *ajax* logic once 4 | * [Create a `handler`](./ajax-handler.html) if you want to use the *ajax* logic several times. 5 | -------------------------------------------------------------------------------- /src/__mocks__/vue-datatable-pager.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /ci/github_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== 2 | -------------------------------------------------------------------------------- /dist/classes/handlers/__mocks__/default-handler.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const filterHandler: jest.Mock; 3 | export declare const sortHandler: jest.Mock; 4 | export declare const paginateHandler: jest.Mock; 5 | export declare const displayHandler: jest.Mock; 6 | export declare const DefaultHandler: jest.Mock; 7 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "file", 3 | "out": "docs", 4 | "readme": "README.md", 5 | "exclude": [ 6 | "./**/*.spec.*", 7 | "./dist/**/*", 8 | "./ci/**/*", 9 | "./docs/**/*", 10 | "./tutorials/**/*" 11 | ], 12 | 13 | "tutorials-map": "./tutorials/tutorials.json", 14 | "tutorials-directory": "./tutorials/dist" 15 | } 16 | -------------------------------------------------------------------------------- /dist/themes/classes/handlers/__mocks__/default-handler.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare const filterHandler: jest.Mock; 3 | export declare const sortHandler: jest.Mock; 4 | export declare const paginateHandler: jest.Mock; 5 | export declare const displayHandler: jest.Mock; 6 | export declare const DefaultHandler: jest.Mock; 7 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import { IDict } from './utils'; 4 | import { VueDatatable } from './components/vue-datatable/vue-datatable'; 5 | 6 | // see https://stackoverflow.com/questions/48076772/typescript-add-properties-to-vue-library-definitions 7 | declare module 'vue/types/vue' { 8 | interface Vue { 9 | $datatables: IDict>; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dist/components/mixins/table-type-consumer-factory.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { TableType } from '../../classes'; 3 | export interface ITableTypeConsumer extends Vue { 4 | readonly tableType: TableType; 5 | } 6 | export declare const tableTypeConsumerFactory: (tableType: TableType) => import("vue").VueConstructor & (new () => ITableTypeConsumer); 7 | -------------------------------------------------------------------------------- /dist/themes/components/mixins/table-type-consumer-factory.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { TableType } from '../../classes'; 3 | export interface ITableTypeConsumer extends Vue { 4 | readonly tableType: TableType; 5 | } 6 | export declare const tableTypeConsumerFactory: (tableType: TableType) => import("vue").VueConstructor & (new () => ITableTypeConsumer); 7 | -------------------------------------------------------------------------------- /src/components/vue-datatable-header/vue-datatable-header.html: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | {{ column.label }} 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/themes/bootstrap-3.ts: -------------------------------------------------------------------------------- 1 | import { VuejsDatatableFactory } from 'vuejs-datatable'; 2 | 3 | VuejsDatatableFactory.registerTableType( 'bootstrap-3-datatable', tableType => 4 | tableType.mergeSettings( { 5 | pager: { 6 | classes: { 7 | disabled: 'disabled', 8 | pager: 'pagination', 9 | selected: 'active', 10 | }, 11 | icons: { 12 | next: '»', 13 | previous: '«', 14 | }, 15 | }, 16 | table: { 17 | class: 'table', 18 | } 19 | } ) ); 20 | -------------------------------------------------------------------------------- /ci/log.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: no-implicit-dependencies 2 | import chalk from 'chalk'; 3 | 4 | // tslint:disable: no-console 5 | export const logStep = ( str: string ) => console.log( chalk.cyan( `==> ${str}` ) ); 6 | export const logError = ( str: string ) => console.error( chalk.red( `[ERR] ${str}` ) ); 7 | export const logDebug = ( str: string ) => console.debug( chalk.magenta( `[DBG] ${str}` ) ); 8 | export const logInfo = ( str: string ) => console.info( chalk.blue( `[INF] ${str}` ) ); 9 | -------------------------------------------------------------------------------- /src/components/vue-datatable-cell/vue-datatable-cell.html: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | 16 | 20 | {{ content }} 21 | 22 | -------------------------------------------------------------------------------- /dist/vuejs-datatable.esm.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Originally authored by Patrick Stephan: https://www.patrickstephan.me 3 | * Currently owned and maintained by Alexandre Germain: https://github.com/GerkinDev 4 | */ 5 | import { DatatableFactory } from './classes'; 6 | export * from './classes/handlers'; 7 | export { TColumnsDefinition, TDataFn, IDataFnParams, ITableContentParam, VueDatatable } from './components/vue-datatable/vue-datatable'; 8 | export declare const VuejsDatatableFactory: DatatableFactory; 9 | -------------------------------------------------------------------------------- /src/themes/bootstrap-4.ts: -------------------------------------------------------------------------------- 1 | import { VuejsDatatableFactory } from 'vuejs-datatable'; 2 | 3 | VuejsDatatableFactory.registerTableType( 'bootstrap-4-datatable', tableType => 4 | tableType.mergeSettings( { 5 | pager: { 6 | classes: { 7 | disabled: 'disabled', 8 | li: 'page-item', 9 | pager: 'pagination', 10 | selected: 'active', 11 | }, 12 | icons: { 13 | next: '»', 14 | previous: '«', 15 | }, 16 | }, 17 | table: { 18 | class: 'table', 19 | }, 20 | } ) ); 21 | -------------------------------------------------------------------------------- /dist/themes/vuejs-datatable.esm.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Originally authored by Patrick Stephan: https://www.patrickstephan.me 3 | * Currently owned and maintained by Alexandre Germain: https://github.com/GerkinDev 4 | */ 5 | import { DatatableFactory } from './classes'; 6 | export * from './classes/handlers'; 7 | export { TColumnsDefinition, TDataFn, IDataFnParams, ITableContentParam, VueDatatable } from './components/vue-datatable/vue-datatable'; 8 | export declare const VuejsDatatableFactory: DatatableFactory; 9 | -------------------------------------------------------------------------------- /src/vuejs-datatable.esm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Originally authored by Patrick Stephan: https://www.patrickstephan.me 3 | * Currently owned and maintained by Alexandre Germain: https://github.com/GerkinDev 4 | */ 5 | 6 | import { DatatableFactory } from './classes'; 7 | export * from './classes/handlers'; 8 | 9 | export { TColumnsDefinition, TDataFn, IDataFnParams, ITableContentParam, VueDatatable } from './components/vue-datatable/vue-datatable'; 10 | 11 | export const VuejsDatatableFactory = new DatatableFactory(); 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-ts', 3 | moduleFileExtensions: [ 4 | 'js', 5 | 'ts', 6 | 'vue' 7 | ], 8 | transform: { 9 | '.*\\.ts$': 'ts-jest', 10 | '.*\\.html$': '/__tests__/helpers/transform-vue-template.js' 11 | }, 12 | collectCoverageFrom: [ 13 | 'src/**/*.{[jt]s,vue}', 14 | '!src/vuejs-datatable-{esm,}.ts', 15 | '!src/themes/**', 16 | '!**/node_modules/**' 17 | ], 18 | globals: { 19 | 'ts-jest': { 20 | 'diagnostics': false 21 | } 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/classes/handlers/__mocks__/default-handler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | export const filterHandler = jest.fn(); 3 | export const sortHandler = jest.fn(); 4 | export const paginateHandler = jest.fn(); 5 | export const displayHandler = jest.fn(); 6 | export const DefaultHandler = jest.fn().mockImplementation( function( this: any ) { 7 | this.filterHandler = filterHandler; 8 | this.sortHandler = sortHandler; 9 | this.paginateHandler = paginateHandler; 10 | this.displayHandler = displayHandler; 11 | return this; 12 | } ); 13 | -------------------------------------------------------------------------------- /__tests__/helpers/transform-vue-template.js: -------------------------------------------------------------------------------- 1 | const compiler = require('vue-template-compiler'); 2 | const transpileVueTemplate = require('vue-template-es2015-compiler'); 3 | 4 | const toFunction = code => `function(){${code}}`; 5 | 6 | module.exports = { 7 | process(code){ 8 | const compiled = compiler.compile(code); 9 | const postProcessed = transpileVueTemplate(("module.exports={render:" + (toFunction(compiled.render)) + ",staticRenderFns:[" + (compiled.staticRenderFns.map(toFunction).join(',')) + "]}")) 10 | return postProcessed; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/classes/__mocks__/column.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // Import this named export into your test file: 3 | export const getRepresentation = jest.fn( function( this: any, row: any ) { 4 | return this.field ? row[this.field] : this.representedAs( row ); 5 | } ); 6 | export const Column = jest.fn().mockImplementation( function( this: any, config: any ) { 7 | Object.assign( this, config ); 8 | this.matches = ( row: {}, filter: string ) => `${ this.getRepresentation( row ) }`.toLowerCase().indexOf( filter.toLowerCase() ) > -1; 9 | this.getRepresentation = getRepresentation.bind( this ); 10 | this.filterable = true; 11 | return this; 12 | } ); 13 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-3.esm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vuejs-datatable v2.0.0-alpha.7, module theme:bootstrap-3 build esm 3 | * License: MIT (see git+https://github.com/GerkinDev/vuejs-datatable.git/blob/master/LICENSE for the full license) 4 | * Repository: git+https://github.com/GerkinDev/vuejs-datatable.git 5 | * Generated on 2019-12-29 at 17:05:12. 6 | * By Patrick Stephan, GerkinDev (https://ithoughts.io/) 7 | */ 8 | 9 | import{VuejsDatatableFactory as e}from"vuejs-datatable";e.registerTableType("bootstrap-3-datatable",e=>e.mergeSettings({pager:{classes:{disabled:"disabled",pager:"pagination",selected:"active"},icons:{next:"»",previous:"«"}},table:{class:"table"}})); 10 | //# sourceMappingURL=bootstrap-3.esm.js.map 11 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-4.esm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vuejs-datatable v2.0.0-alpha.7, module theme:bootstrap-4 build esm 3 | * License: MIT (see git+https://github.com/GerkinDev/vuejs-datatable.git/blob/master/LICENSE for the full license) 4 | * Repository: git+https://github.com/GerkinDev/vuejs-datatable.git 5 | * Generated on 2019-12-29 at 17:05:12. 6 | * By Patrick Stephan, GerkinDev (https://ithoughts.io/) 7 | */ 8 | 9 | import{VuejsDatatableFactory as e}from"vuejs-datatable";e.registerTableType("bootstrap-4-datatable",e=>e.mergeSettings({pager:{classes:{disabled:"disabled",li:"page-item",pager:"pagination",selected:"active"},icons:{next:"»",previous:"«"}},table:{class:"table"}})); 10 | //# sourceMappingURL=bootstrap-4.esm.js.map 11 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vuejs-datatable v2.0.0-alpha.7, module theme:bootstrap-3 build iife 3 | * License: MIT (see git+https://github.com/GerkinDev/vuejs-datatable.git/blob/master/LICENSE for the full license) 4 | * Repository: git+https://github.com/GerkinDev/vuejs-datatable.git 5 | * Generated on 2019-12-29 at 17:05:12. 6 | * By Patrick Stephan, GerkinDev (https://ithoughts.io/) 7 | */ 8 | 9 | !function(e){"use strict";VuejsDatatable.VuejsDatatableFactory.registerTableType("bootstrap-3-datatable",(function(e){return e.mergeSettings({pager:{classes:{disabled:"disabled",pager:"pagination",selected:"active"},icons:{next:"»",previous:"«"}},table:{class:"table"}})}))}(); 10 | //# sourceMappingURL=bootstrap-3.js.map 11 | -------------------------------------------------------------------------------- /docs/assets/js/bootstrap-3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vuejs-datatable v2.0.0-alpha.7, module theme:bootstrap-3 build iife 3 | * License: MIT (see git+https://github.com/GerkinDev/vuejs-datatable.git/blob/master/LICENSE for the full license) 4 | * Repository: git+https://github.com/GerkinDev/vuejs-datatable.git 5 | * Generated on 2019-12-29 at 17:05:12. 6 | * By Patrick Stephan, GerkinDev (https://ithoughts.io/) 7 | */ 8 | 9 | !function(e){"use strict";VuejsDatatable.VuejsDatatableFactory.registerTableType("bootstrap-3-datatable",(function(e){return e.mergeSettings({pager:{classes:{disabled:"disabled",pager:"pagination",selected:"active"},icons:{next:"»",previous:"«"}},table:{class:"table"}})}))}(); 10 | //# sourceMappingURL=bootstrap-3.js.map 11 | -------------------------------------------------------------------------------- /src/classes/__mocks__/table-type.ts: -------------------------------------------------------------------------------- 1 | import { Settings } from './settings'; 2 | 3 | /* eslint-disable no-undef */ 4 | // Import this named export into your test file: 5 | export const getTableDefinition = jest.fn( v => v ); 6 | export const getPagerDefinition = jest.fn( v => v ); 7 | export const setting = jest.fn( v => v ); 8 | export const TableType = jest.fn().mockImplementation( function mockTableType( this: any, id: string ) { 9 | Object.defineProperty( this, 'id', { 10 | get: () => id, 11 | } ); 12 | this.getTableDefinition = () => getTableDefinition( { name: id } ); 13 | this.getPagerDefinition = () => getPagerDefinition( { name: `${ id }-pager` } ); 14 | this.handler = {}; 15 | this.settings = new Settings(); 16 | this.setting = setting; 17 | return this; 18 | } ); 19 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vuejs-datatable v2.0.0-alpha.7, module theme:bootstrap-4 build iife 3 | * License: MIT (see git+https://github.com/GerkinDev/vuejs-datatable.git/blob/master/LICENSE for the full license) 4 | * Repository: git+https://github.com/GerkinDev/vuejs-datatable.git 5 | * Generated on 2019-12-29 at 17:05:12. 6 | * By Patrick Stephan, GerkinDev (https://ithoughts.io/) 7 | */ 8 | 9 | !function(e){"use strict";VuejsDatatable.VuejsDatatableFactory.registerTableType("bootstrap-4-datatable",(function(e){return e.mergeSettings({pager:{classes:{disabled:"disabled",li:"page-item",pager:"pagination",selected:"active"},icons:{next:"»",previous:"«"}},table:{class:"table"}})}))}(); 10 | //# sourceMappingURL=bootstrap-4.js.map 11 | -------------------------------------------------------------------------------- /tutorials/src/no-pager/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | declare var rows: IPeople[]; 7 | const app = new Vue( { 8 | el: '#demo-app', 9 | data: { 10 | filter: '', 11 | columns: [ 12 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 13 | { label: 'Username', field: 'user.username' }, 14 | { label: 'First Name', field: 'user.first_name' }, 15 | { label: 'Last Name', field: 'user.last_name' }, 16 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 17 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 18 | ] as TColumnsDefinition, 19 | rows, 20 | }, 21 | } ); 22 | -------------------------------------------------------------------------------- /tutorials/src/pager-types/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | declare var rows: IPeople[]; 7 | const app = new Vue( { 8 | el: '#demo-app', 9 | data: { 10 | filter: '', 11 | columns: [ 12 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 13 | { label: 'Username', field: 'user.username' }, 14 | { label: 'First Name', field: 'user.first_name' }, 15 | { label: 'Last Name', field: 'user.last_name' }, 16 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 17 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 18 | ] as TColumnsDefinition, 19 | rows, 20 | page: 1, 21 | }, 22 | } ); 23 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-3.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bootstrap-3.js","sources":["../../src/themes/bootstrap-3.ts"],"sourcesContent":["import { VuejsDatatableFactory } from 'vuejs-datatable';\n\nVuejsDatatableFactory.registerTableType( 'bootstrap-3-datatable', tableType =>\n\ttableType.mergeSettings( {\n\t\tpager: {\n\t\t\tclasses: {\n\t\t\t\tdisabled: 'disabled',\n\t\t\t\tpager: 'pagination',\n\t\t\t\tselected: 'active',\n\t\t\t},\n\t\t\ticons: {\n\t\t\t\tnext: '»',\n\t\t\t\tprevious: '«',\n\t\t\t},\n\t\t},\n\t\ttable: {\n\t\t\tclass: 'table',\n\t\t}\n\t} ) );\n"],"names":["registerTableType","tableType","mergeSettings","pager","classes","disabled","selected","icons","next","previous","table","class"],"mappings":";;;;;;;;+DAEsBA,kBAAmB,yBAAyB,SAAAC,UACjEA,EAAUC,cAAe,CACxBC,MAAO,CACNC,QAAS,CACRC,SAAU,WACVF,MAAO,aACPG,SAAU,UAEXC,MAAO,CACNC,KAAM,IACNC,SAAU,MAGZC,MAAO,CACNC,MAAO"} -------------------------------------------------------------------------------- /tutorials/src/basic/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | // Defined on window 7 | declare var rows: IPeople[]; 8 | const app = new Vue( { 9 | el: '#demo-app', 10 | data: { 11 | filter: '', 12 | columns: [ 13 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 14 | { label: 'Username', field: 'user.username' }, 15 | { label: 'First Name', field: 'user.first_name' }, 16 | { label: 'Last Name', field: 'user.last_name' }, 17 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 18 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 19 | ] as TColumnsDefinition, 20 | rows, 21 | page: 1, 22 | }, 23 | } ); 24 | -------------------------------------------------------------------------------- /tutorials/src/prebuilt-theme/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | // Defined on window 7 | declare var rows: IPeople[]; 8 | const app = new Vue( { 9 | el: '#demo-app', 10 | data: { 11 | filter: '', 12 | columns: [ 13 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 14 | { label: 'Username', field: 'user.username' }, 15 | { label: 'First Name', field: 'user.first_name' }, 16 | { label: 'Last Name', field: 'user.last_name' }, 17 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 18 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 19 | ] as TColumnsDefinition, 20 | rows, 21 | page: 1, 22 | }, 23 | } ); 24 | -------------------------------------------------------------------------------- /tutorials/build/build-utils.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | export const rewriteBaseVueApp = ( page: string, vueApp: string, pageSlug: string ) => { 4 | pageSlug = `tutorial-${pageSlug}`; 5 | const vueAppSegments = vueApp.match( /]+id="demo-app"[^>]*>/ ); 6 | if ( !vueAppSegments || vueAppSegments.length === 0 ) { 7 | throw new Error(); 8 | } 9 | 10 | const openingDivApp = vueAppSegments[0]; 11 | const openingDivClass = openingDivApp.match( /(\sclass=")([^"]*)(")/ ); 12 | 13 | const newOpeningDiv = openingDivClass ? 14 | openingDivApp.replace( openingDivClass[0], `$1${pageSlug} $2$3` ) : 15 | openingDivApp.replace( '>', ` class="${pageSlug}">` ); 16 | return page.replace( vueApp, vueApp.replace( openingDivApp, newOpeningDiv ).split( '\n' ).map( s => s.trim() ).join( '' ) ); 17 | }; 18 | 19 | export const tempDir = resolve( __dirname, '..', '.tmp' ); 20 | -------------------------------------------------------------------------------- /dist/components/vue-datatable-cell/vue-datatable-cell.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { TClassVal } from '../../utils'; 3 | /** 4 | * This component is responsible of the display of a single table cell. 5 | */ 6 | export declare class VueDatatableCell extends Vue { 7 | /** The column of this cell */ 8 | private readonly column; 9 | /** The row of this cell */ 10 | private readonly row; 11 | /** 12 | * The representation of the row in the current column. 13 | * You can customize the cell content by changing [[IColumnDefinition.field]] or [[IColumnDefinition.representedAs]] 14 | */ 15 | get content(): string; 16 | /** The styles to apply to this cell */ 17 | get cellStyles(): { 18 | [key: string]: string; 19 | }; 20 | get cellClass(): TClassVal | undefined; 21 | } 22 | -------------------------------------------------------------------------------- /dist/themes/components/vue-datatable-cell/vue-datatable-cell.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { TClassVal } from '../../utils'; 3 | /** 4 | * This component is responsible of the display of a single table cell. 5 | */ 6 | export declare class VueDatatableCell extends Vue { 7 | /** The column of this cell */ 8 | private readonly column; 9 | /** The row of this cell */ 10 | private readonly row; 11 | /** 12 | * The representation of the row in the current column. 13 | * You can customize the cell content by changing [[IColumnDefinition.field]] or [[IColumnDefinition.representedAs]] 14 | */ 15 | get content(): string; 16 | /** The styles to apply to this cell */ 17 | get cellStyles(): { 18 | [key: string]: string; 19 | }; 20 | get cellClass(): TClassVal | undefined; 21 | } 22 | -------------------------------------------------------------------------------- /dist/themes/bootstrap-4.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bootstrap-4.js","sources":["../../src/themes/bootstrap-4.ts"],"sourcesContent":["import { VuejsDatatableFactory } from 'vuejs-datatable';\n\nVuejsDatatableFactory.registerTableType( 'bootstrap-4-datatable', tableType =>\n\ttableType.mergeSettings( {\n\t\tpager: {\n\t\t\tclasses: {\n\t\t\t\tdisabled: 'disabled',\n\t\t\t\tli: 'page-item',\n\t\t\t\tpager: 'pagination',\n\t\t\t\tselected: 'active',\n\t\t\t},\n\t\t\ticons: {\n\t\t\t\tnext: '»',\n\t\t\t\tprevious: '«',\n\t\t\t},\n\t\t},\n\t\ttable: {\n\t\t\tclass: 'table',\n\t\t},\n\t} ) );\n"],"names":["registerTableType","tableType","mergeSettings","pager","classes","disabled","li","selected","icons","next","previous","table","class"],"mappings":";;;;;;;;+DAEsBA,kBAAmB,yBAAyB,SAAAC,UACjEA,EAAUC,cAAe,CACxBC,MAAO,CACNC,QAAS,CACRC,SAAU,WACVC,GAAI,YACJH,MAAO,aACPI,SAAU,UAEXC,MAAO,CACNC,KAAM,IACNC,SAAU,MAGZC,MAAO,CACNC,MAAO"} -------------------------------------------------------------------------------- /src/components/mixins/table-type-consumer-factory.ts: -------------------------------------------------------------------------------- 1 | import { Component, Provide, Vue } from 'vue-property-decorator'; 2 | 3 | import { TableType } from '../../classes'; 4 | 5 | export interface ITableTypeConsumer extends Vue { 6 | readonly tableType: TableType; 7 | } 8 | 9 | export const tableTypeConsumerFactory = ( tableType: TableType ): typeof Vue & ( new() => ITableTypeConsumer ) => { 10 | /** 11 | * This mixin provide a [[TableType]] to inner components, which allow them to access [[Settings]] (through [[TableType.setting]]) 12 | */ 13 | @Component 14 | class TableTypeConsumer extends Vue implements ITableTypeConsumer { 15 | /** 16 | * Provide the current [[TableType]] to inner components. 17 | * 18 | * @vue Provide `table-type` 19 | */ 20 | @Provide( 'table-type' ) 21 | public get tableType() { 22 | return tableType; 23 | } 24 | } 25 | return TableTypeConsumer; 26 | }; 27 | -------------------------------------------------------------------------------- /tutorials/src/multiple-tables/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | declare var rows: IPeople[]; 7 | const app = new Vue( { 8 | el: '#demo-app', 9 | data: { 10 | filter: '', 11 | filter2: '', 12 | columns: [ 13 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 14 | { label: 'Username', field: 'user.username' }, 15 | { label: 'First Name', field: 'user.first_name' }, 16 | { label: 'Last Name', field: 'user.last_name' }, 17 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 18 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 19 | ] as TColumnsDefinition, 20 | rows, 21 | page: 1, 22 | page2: 1, 23 | }, 24 | computed: { 25 | tables: () => ( Vue as any ).$datatables, 26 | }, 27 | } ); 28 | -------------------------------------------------------------------------------- /tutorials/src/custom-theme/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | You can customize the appearance of your table by merging a [Settings Props object](../interfaces/isettingsproperties.html) in your [Table Type](../classes/tabletype.html), by using [TableType.mergeSettings](../classes/tabletype.html#mergesettings). 4 | 5 |
    6 | 7 | The example bellow shows a configuration using FontAwesome icons. 8 |
    9 | 10 | ## Demo 11 | 12 |
    13 |
    14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 | 21 | ## Code 22 | 23 | ### Typescript 24 | 25 | ```TS``` 26 | 27 | ### HTML 28 | 29 | ```HTML``` 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/vue-datatable/vue-datatable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 18 | 21 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 40 | 41 |
    30 | 31 |
    42 | -------------------------------------------------------------------------------- /tutorials/src/basic/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Here is the bare minimum example to use __vuejs-datatable__. You can check out other tutorials for more advanced usages. 4 | 5 | ## Demo 6 | 7 |
    8 |
    9 |
    10 |
    11 | 12 | 13 |
    14 |
    15 | 16 |
    17 | 18 | 19 |
    20 |
    21 |
    22 | 23 | ## Code 24 | 25 | ### Typescript 26 | 27 | ```TS``` 28 | 29 | ### HTML 30 | 31 | ```HTML``` 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /dist/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare type TMaybePromise = T | Promise; 2 | export declare const ensurePromise: (value: TMaybePromise) => Promise; 3 | export interface IDict { 4 | [key: string]: T; 5 | } 6 | export declare type TClassVal = string | string[] | IDict; 7 | export declare const classValType: any[]; 8 | export declare const mergeClassVals: (mainObj: TClassVal, ...objs: (string | IDict | string[] | null | undefined)[]) => string[]; 9 | export declare const namespace = "vue-datatable"; 10 | export declare const namespaceEvent: (event: string) => string; 11 | /** 12 | * Enumeration of text alignment in a cell. 13 | */ 14 | export declare const enum EColAlign { 15 | Left = "left", 16 | Center = "center", 17 | Right = "right" 18 | } 19 | /** 20 | * Enumeration of the different display modes available for the pager. 21 | * 22 | * @tutorial pager-types 23 | */ 24 | export declare const enum EPagerType { 25 | Short = "short", 26 | Abbreviated = "abbreviated", 27 | Long = "long" 28 | } 29 | export declare const valueToString: (val: any) => string; 30 | -------------------------------------------------------------------------------- /dist/themes/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare type TMaybePromise = T | Promise; 2 | export declare const ensurePromise: (value: TMaybePromise) => Promise; 3 | export interface IDict { 4 | [key: string]: T; 5 | } 6 | export declare type TClassVal = string | string[] | IDict; 7 | export declare const classValType: any[]; 8 | export declare const mergeClassVals: (mainObj: TClassVal, ...objs: (string | IDict | string[] | null | undefined)[]) => string[]; 9 | export declare const namespace = "vue-datatable"; 10 | export declare const namespaceEvent: (event: string) => string; 11 | /** 12 | * Enumeration of text alignment in a cell. 13 | */ 14 | export declare const enum EColAlign { 15 | Left = "left", 16 | Center = "center", 17 | Right = "right" 18 | } 19 | /** 20 | * Enumeration of the different display modes available for the pager. 21 | * 22 | * @tutorial pager-types 23 | */ 24 | export declare const enum EPagerType { 25 | Short = "short", 26 | Abbreviated = "abbreviated", 27 | Long = "long" 28 | } 29 | export declare const valueToString: (val: any) => string; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alexandre Germain 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. 22 | -------------------------------------------------------------------------------- /__tests__/helpers/mount-mixin-components.ts: -------------------------------------------------------------------------------- 1 | import { mount, shallowMount, ThisTypedMountOptions } from '@vue/test-utils'; 2 | import { Mixins, Vue } from 'vue-property-decorator'; 3 | 4 | import { ITableTypeConsumer, tableTypeConsumerFactory } from '../../src/components/mixins/table-type-consumer-factory'; 5 | import { VueDatatablePager } from '../../src/components/vue-datatable-pager/vue-datatable-pager'; 6 | import { TableType } from './../../src/classes/table-type'; 7 | import { VueDatatable } from '../../src/components/vue-datatable/vue-datatable'; 8 | 9 | export const mountVueDatatablePager = ( 10 | mountShallow: boolean, 11 | tableType: TableType, 12 | mountOptions: ThisTypedMountOptions> & ITableTypeConsumer & Vue>, 13 | ) => 14 | ( mountShallow ? shallowMount : mount )( Mixins( VueDatatablePager, tableTypeConsumerFactory( tableType ) ), mountOptions ); 15 | 16 | export const mountVueDatatable = ( 17 | mountShallow: boolean, 18 | tableType: TableType, 19 | mountOptions: ThisTypedMountOptions> & ITableTypeConsumer & Vue>, 20 | ) => ( mountShallow ? shallowMount : mount )( Mixins( VueDatatable, tableTypeConsumerFactory( tableType ) ), mountOptions ); 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "Debug jest test", 10 | "request": "launch", 11 | "args": [ 12 | "--runInBand" 13 | ], 14 | "cwd": "${workspaceFolder}", 15 | "console": "integratedTerminal", 16 | "internalConsoleOptions": "neverOpen", 17 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 18 | } 19 | { 20 | "type": "node", 21 | "name": "Debug current jest test file", 22 | "request": "launch", 23 | "args": [ 24 | "--runInBand", 25 | "--testPathPattern=${fileBasenameNoExtension}" 26 | ], 27 | "cwd": "${workspaceFolder}", 28 | "console": "integratedTerminal", 29 | "internalConsoleOptions": "neverOpen", 30 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /tutorials/src/ajax-data/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | You can customize a single [VueDatatable](../../classes/vuedatatable.html) by providing a *data function* instead of the normal array of objects. This function should be a valid [TDataFn](../../globals.html#tdatafn). 4 | 5 |
    6 | 7 | If you want to customize the behavior of multiple tables, you may want to check how to use custom handlers. You may also want to debounce your data processing to avoid requests spamming. 8 |
    9 | 10 | ## Demo 11 | 12 |
    13 |
    14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 | 21 | ## Code 22 | 23 | ### Typescript 24 | 25 | ```TS``` 26 | 27 | ### HTML 28 | 29 | ```HTML``` 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /dist/components/vue-datatable-pager/vue-datatable-pager-button/vue-datatable-pager-button.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | /** 3 | * A control button used by the pager. 4 | */ 5 | export declare class VueDatatablePagerButton extends Vue { 6 | /** 7 | * Defines if the button is triggerable or not. 8 | * 9 | * @vue-prop 10 | */ 11 | private readonly disabled; 12 | /** 13 | * Represents if the pager button is the currently selected one. 14 | * 15 | * @vue-prop 16 | */ 17 | private readonly selected; 18 | /** 19 | * The page index of the button. 20 | * 21 | * @vue-prop 22 | */ 23 | private readonly value; 24 | /** 25 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 26 | * 27 | * @vue Inject `table-type` 28 | */ 29 | private readonly tableType; 30 | /** HTML classes to set on list items tags. */ 31 | get liClasses(): string[]; 32 | /** CSS styles to apply on the list items tags */ 33 | get liStyles(): { 34 | cursor: string; 35 | }; 36 | /** 37 | * Emits an event if the button is not [[VueDatatablePagerButton.disabled]]. 38 | * 39 | * @vue-event vuejs-datatable::set-page. 40 | * @returns Nothing. 41 | */ 42 | sendClick(): void; 43 | } 44 | -------------------------------------------------------------------------------- /dist/themes/components/vue-datatable-pager/vue-datatable-pager-button/vue-datatable-pager-button.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | /** 3 | * A control button used by the pager. 4 | */ 5 | export declare class VueDatatablePagerButton extends Vue { 6 | /** 7 | * Defines if the button is triggerable or not. 8 | * 9 | * @vue-prop 10 | */ 11 | private readonly disabled; 12 | /** 13 | * Represents if the pager button is the currently selected one. 14 | * 15 | * @vue-prop 16 | */ 17 | private readonly selected; 18 | /** 19 | * The page index of the button. 20 | * 21 | * @vue-prop 22 | */ 23 | private readonly value; 24 | /** 25 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 26 | * 27 | * @vue Inject `table-type` 28 | */ 29 | private readonly tableType; 30 | /** HTML classes to set on list items tags. */ 31 | get liClasses(): string[]; 32 | /** CSS styles to apply on the list items tags */ 33 | get liStyles(): { 34 | cursor: string; 35 | }; 36 | /** 37 | * Emits an event if the button is not [[VueDatatablePagerButton.disabled]]. 38 | * 39 | * @vue-event vuejs-datatable::set-page. 40 | * @returns Nothing. 41 | */ 42 | sendClick(): void; 43 | } 44 | -------------------------------------------------------------------------------- /src/classes/settings.spec.ts: -------------------------------------------------------------------------------- 1 | import { Settings } from './settings'; 2 | 3 | it( 'can retrieve properties', () => { 4 | const settings = new Settings(); 5 | 6 | expect( settings.get( 'table.sorting.sortAsc' ) ).toBe( '↓' ); 7 | } ); 8 | 9 | it( 'can set properties', () => { 10 | const settings = new Settings(); 11 | settings.set( 'table.class', 'test-table' ); 12 | 13 | expect( settings.get( 'table.class' ) ).toBe( 'test-table' ); 14 | } ); 15 | 16 | describe( 'Settings merging', () => { 17 | it( 'Should override existing props', () => { 18 | const settings = new Settings(); 19 | settings.merge( { 20 | pager: { 21 | classes: { 22 | selected: 'active', 23 | }, 24 | }, 25 | table: { 26 | class: 'table class', 27 | }, 28 | } ); 29 | 30 | expect( settings.get( 'table.class' ) ).toBe( 'table class' ); 31 | expect( settings.get( 'table.sorting.sortAsc' ) ).toBe( '↓' ); 32 | expect( settings.get( 'pager.classes.selected' ) ).toBe( 'active' ); 33 | expect( settings.get( 'pager.classes.disabled' ) ).toBe( 'disabled' ); 34 | expect( settings.get( 'pager.icons.previous' ) ).toBe( '<' ); 35 | } ); 36 | it( 'Should merge new props', () => { 37 | const settings = new Settings(); 38 | settings.merge( { 39 | foo: { 40 | bar: 'baz', 41 | }, 42 | } as any ); 43 | 44 | expect( settings.get( 'foo.bar' ) ).toBe( 'baz' ); 45 | expect( settings.get( 'table.sorting.sortAsc' ) ).toBe( '↓' ); 46 | } ); 47 | } ); 48 | -------------------------------------------------------------------------------- /src/components/vue-datatable-cell/vue-datatable-cell.ts: -------------------------------------------------------------------------------- 1 | import { Component, Prop, Vue } from 'vue-property-decorator'; 2 | 3 | import { Column } from '../../classes/column'; 4 | import { TClassVal } from '../../utils'; 5 | 6 | import template from './vue-datatable-cell.html'; 7 | 8 | /** 9 | * This component is responsible of the display of a single table cell. 10 | */ 11 | @Component( { 12 | ...template, 13 | } ) 14 | export class VueDatatableCell extends Vue { 15 | /** The column of this cell */ 16 | @Prop( { type: Column, required: true } ) private readonly column!: Column; 17 | /** The row of this cell */ 18 | @Prop( { type: Object, required: true } ) private readonly row!: TRow; 19 | 20 | /** 21 | * The representation of the row in the current column. 22 | * You can customize the cell content by changing [[IColumnDefinition.field]] or [[IColumnDefinition.representedAs]] 23 | */ 24 | public get content(): string { 25 | return this.column.getRepresentation( this.row ); 26 | } 27 | /** The styles to apply to this cell */ 28 | public get cellStyles(): {[key: string]: string} { 29 | return { 'text-align': this.column.align }; 30 | } 31 | 32 | public get cellClass(): TClassVal | undefined { 33 | if ( typeof this.column.class === 'function' ) { 34 | return this.column.class( this.row ); 35 | } else if ( this.column.class ) { 36 | return this.column.class; 37 | } else { 38 | return undefined; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /__tests__/e2e/mocks/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
    5 |
    6 |
    7 |
    8 | 9 | 10 | {{filter}} 11 |
    12 |
    13 | 14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 | 21 | 22 | 23 | 24 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /dist/components/vue-datatable-header/vue-datatable-header.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | /** 3 | * A control button used by the pager. 4 | */ 5 | export declare class VueDatatableHeader extends Vue { 6 | /** 7 | * The current sort direction for the current column. 8 | * 9 | * @vue-model 10 | */ 11 | private readonly direction; 12 | /** 13 | * The [[Column]] instance this header is for. 14 | * 15 | * @vue-prop 16 | */ 17 | private readonly column; 18 | /** 19 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 20 | * 21 | * @vue Inject `table-type` 22 | */ 23 | private readonly tableType; 24 | /** `true` if this column is sortable. */ 25 | private get canSort(); 26 | /** `true` if this column is sorted in *ascending* mode. */ 27 | private get isSortedAscending(); 28 | /** `true` if this column is sorted in *descending* mode. */ 29 | private get isSortedDescending(); 30 | /** Get the HTML content of the header's sort icon */ 31 | get sortButtonHtml(): string; 32 | /** 33 | * Toggles the sort order, looping between states `null => 'asc' => 'desc'`. 34 | * 35 | * @vue-event change Emitted when the sort direction or column is changed. 36 | * @vue-event-param change newDirection - The new direction. 37 | * @vue-event-param change sortedColumn - The column the sort is done on. 38 | * @returns nothing. 39 | */ 40 | toggleSort(): void; 41 | } 42 | -------------------------------------------------------------------------------- /dist/themes/components/vue-datatable-header/vue-datatable-header.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | /** 3 | * A control button used by the pager. 4 | */ 5 | export declare class VueDatatableHeader extends Vue { 6 | /** 7 | * The current sort direction for the current column. 8 | * 9 | * @vue-model 10 | */ 11 | private readonly direction; 12 | /** 13 | * The [[Column]] instance this header is for. 14 | * 15 | * @vue-prop 16 | */ 17 | private readonly column; 18 | /** 19 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 20 | * 21 | * @vue Inject `table-type` 22 | */ 23 | private readonly tableType; 24 | /** `true` if this column is sortable. */ 25 | private get canSort(); 26 | /** `true` if this column is sorted in *ascending* mode. */ 27 | private get isSortedAscending(); 28 | /** `true` if this column is sorted in *descending* mode. */ 29 | private get isSortedDescending(); 30 | /** Get the HTML content of the header's sort icon */ 31 | get sortButtonHtml(): string; 32 | /** 33 | * Toggles the sort order, looping between states `null => 'asc' => 'desc'`. 34 | * 35 | * @vue-event change Emitted when the sort direction or column is changed. 36 | * @vue-event-param change newDirection - The new direction. 37 | * @vue-event-param change sortedColumn - The column the sort is done on. 38 | * @returns nothing. 39 | */ 40 | toggleSort(): void; 41 | } 42 | -------------------------------------------------------------------------------- /tutorials/tutorials.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundlers": { 3 | "title": "Configuring bundlers & common issues" 4 | }, 5 | "basic": { 6 | "title": "Basic table" 7 | }, 8 | "pager-types": { 9 | "title": "Pager types" 10 | }, 11 | "ajax": { 12 | "title": "Ajax table", 13 | "description": "Source your data table from an API", 14 | "children": { 15 | "ajax-handler": { 16 | "title": "Ajax table (with handler customization)" 17 | }, 18 | "ajax-data": { 19 | "title": "Ajax table (via data function)" 20 | } 21 | } 22 | }, 23 | "customize": { 24 | "title": "Tables customization", 25 | "Description": "Adapt the plugin's appearance the way you want", 26 | "children": { 27 | "custom-template": { 28 | "title": "Custom template", 29 | "description": "Change the way HTML is built" 30 | }, 31 | "custom-theme": { 32 | "title": "Custom theme", 33 | "description": "See how to set custom themes on your data tables.", 34 | "children": { 35 | "prebuilt-theme": { 36 | "title": "Prebuilt theme" 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "multiple-tables": { 43 | "title": "Multiple tables" 44 | }, 45 | "no-pager": { 46 | "title": "No pager" 47 | }, 48 | "limit-rows-processing": { 49 | "title": "Limit rows processing" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/vue-datatable-pager/vue-datatable-pager.html: -------------------------------------------------------------------------------- 1 | 66 | -------------------------------------------------------------------------------- /tutorials/src/custom-theme/demo.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { TColumnsDefinition, VuejsDatatableFactory } from 'vuejs-datatable'; 3 | 4 | import { IPeople } from '../utils'; 5 | 6 | VuejsDatatableFactory.useDefaultType( false ) 7 | .registerTableType( 'datatable', tableType => tableType.mergeSettings( { 8 | table: { 9 | class: 'table table-hover table-striped', 10 | sorting: { 11 | sortAsc: '', 12 | sortDesc: '', 13 | sortNone: '', 14 | }, 15 | }, 16 | pager: { 17 | classes: { 18 | pager: 'pagination text-center', 19 | selected: 'active', 20 | }, 21 | icons: { 22 | next: '', 23 | previous: '', 24 | }, 25 | }, 26 | } ) ); 27 | 28 | // Defined on window 29 | declare var rows: IPeople[]; 30 | 31 | const app = new Vue( { 32 | el: '#demo-app', 33 | data: { 34 | filter: '', 35 | columns: [ 36 | { label: 'id', field: 'id' }, 37 | { label: 'Username', field: 'user.username' }, 38 | { label: 'First Name', field: 'user.first_name' }, 39 | { label: 'Last Name', field: 'user.last_name' }, 40 | { label: 'Email', field: 'user.email' }, 41 | { 42 | label: 'Address', 43 | representedAs: row => `${ row.address }
    ${ row.city }, ${ row.state }`, 44 | interpolate: true, 45 | sortable: false, 46 | filterable: false, 47 | }, 48 | ] as TColumnsDefinition, 49 | rows, 50 | page: 1, 51 | }, 52 | } ); 53 | -------------------------------------------------------------------------------- /tutorials/src/pager-types/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | The [VueDatatablePager](../classes/vuedatatablepager.html) allows you to customize the overall appearance of the paging, using the `type` property. If this property isn't set, it fallbacks to `long`. 4 | 5 |
    6 | 7 | To customize further the classes & HTML content of the table type, please see the tutorial about custom themes. 8 |
    9 | 10 | ## Demo 11 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 |
    25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    34 |
    35 | 36 | ## Code 37 | 38 | ### Typescript 39 | 40 | ```TS``` 41 | 42 | ### HTML 43 | 44 | ```HTML``` 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /__tests__/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | // helper for async assertions. 4 | // Use like this: 5 | // 6 | // vm.a = 123 7 | // waitForUpdate(() => { 8 | // expect(vm.$el.textContent).toBe('123') 9 | // vm.a = 234 10 | // }) 11 | // .then(() => { 12 | // // more assertions... 13 | // }) 14 | // .then(done) 15 | export const waitForUpdate = ( initialCb?: Function ) => { 16 | let end; 17 | const queue = initialCb ? [ initialCb ] : []; 18 | 19 | function shift() { 20 | const job = queue.shift(); 21 | if ( queue.length ) { 22 | let hasError = false; 23 | try { 24 | job.wait ? job( shift ) : job(); 25 | } catch ( e ) { 26 | hasError = true; 27 | const done = queue[queue.length - 1]; 28 | if ( done && done.fail ) { 29 | done.fail( e ); 30 | } 31 | } 32 | if ( !hasError && !job.wait ) { 33 | if ( queue.length ) { 34 | Vue.nextTick( shift ); 35 | } 36 | } 37 | } else if ( job && ( job.fail || job === end ) ) { 38 | job(); // done 39 | } 40 | } 41 | 42 | Vue.nextTick( () => { 43 | if ( !queue.length || ( !end && !queue[queue.length - 1].fail ) ) { 44 | throw new Error( 'waitForUpdate chain is missing .then(done)' ); 45 | } 46 | shift(); 47 | } ); 48 | 49 | const chainer = { 50 | then: nextCb => { 51 | queue.push( nextCb ); 52 | return chainer; 53 | }, 54 | thenWaitFor: wait => { 55 | if ( typeof wait === 'number' ) { 56 | wait = timeout( wait ); 57 | } 58 | wait.wait = true; 59 | queue.push( wait ); 60 | return chainer; 61 | }, 62 | end: endFn => { 63 | queue.push( endFn ); 64 | end = endFn; 65 | }, 66 | }; 67 | 68 | return chainer; 69 | }; 70 | 71 | export const timeout = n => next => setTimeout( next, n ); 72 | -------------------------------------------------------------------------------- /tutorials/src/no-pager/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | The [VueDatatablePager](../classes/vuedatatablepager.html) is the component in charge of controling pagination of a [VueDatatable](../classes/vuedatatable.html). 4 | 5 | If you do not link a [VueDatatablePager](../classes/vuedatatablepager.html) to your [VueDatatable](../classes/vuedatatable.html), then the table will show all rows without pagination, without taking into account the `per-page` prop. 6 | 7 |
    8 | 9 | Check out the Pager types tutorial to see the different aspects available for the pager. You can also learn more on how to manage multiple tables with pagers on the same page. 10 |
    11 | 12 | ## Demo 13 | 14 |
    15 |
    16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 23 |
    24 | 25 |
    26 |
    27 |
    28 | 29 | ## Code 30 | 31 | ### Typescript 32 | 33 | ```TS``` 34 | 35 | ### HTML 36 | 37 | ```HTML``` 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tutorials/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary, isArray } from 'lodash'; 2 | import { Path } from 'object-path'; 3 | 4 | const padLeft0 = ( str: string | number, targetLength: number ) => '0'.repeat( targetLength - `${ str }`.length ) + str; 5 | export const makeQueryStringFromObj = ( obj: Dictionary ) => Object 6 | .entries( obj ) 7 | .map( ( [ key, val ] ) => `${ encodeURIComponent( key ) }=${ encodeURIComponent( val ) }` ) 8 | .join( '&' ); 9 | export const formatUtcDate = ( dateTime: Date ) => { 10 | const date = `${ padLeft0( dateTime.getUTCMonth() + 1, 2 ) }/${ padLeft0( dateTime.getUTCDate(), 2 ) }/${ padLeft0( dateTime.getUTCFullYear(), 4 ) }`; 11 | const amPm = dateTime.getUTCHours() === 0 || dateTime.getUTCHours() < 12 ? 'am' : 'pm'; 12 | const hour = dateTime.getUTCHours() % 12 ? dateTime.getUTCHours() % 12 : 12; 13 | const time = `${ padLeft0( hour, 2 ) }:${ padLeft0( dateTime.getUTCMinutes(), 2 ) }${ amPm } UTC`; 14 | return `${ date } ${ time }`; 15 | }; 16 | export const colFieldToStr = ( path: symbol | Path | null ) => { 17 | if ( path === null ) { 18 | throw new Error(); 19 | } 20 | return isArray( path ) ? path.join( '.' ) : String( path ); 21 | }; 22 | 23 | export interface ISpaceXLaunch { 24 | flight_number: string; 25 | mission_name: string; 26 | launch_date_utc: string; 27 | rocket: { 28 | rocket_name: string; 29 | }; 30 | launch_site: { 31 | site_name_long: string; 32 | }; 33 | links: { 34 | mission_patch_small: string; 35 | reddit_campaign: string; 36 | }; 37 | } 38 | export interface IPeople { 39 | id: number; 40 | 41 | address: string; 42 | city: string; 43 | state: string; 44 | 45 | user: { 46 | username: string; 47 | first_name: string; 48 | last_name: string; 49 | email: string; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /tutorials/src/ajax-handler/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | You can create a custom [Table Type](../../classes/tabletype.html) dedicated to AJAX-sourced data. This approach has the advantage of being totally reuasble, while the [AJAX via data function](./ajax-data.html) may be less reuasble across multiple table instances. 4 | 5 | The example bellow reuses 2 times the same logic, changing only the `data` parameter to a different endpoint. 6 | 7 |
    8 | 9 | If you want to customize the behavior of your table for a single use, you may want to check the use of the data function. You may also want to debounce your data processing to avoid requests spamming. 10 |
    11 | 12 | ## Demo 13 | 14 |
    15 |
    16 |
    17 |

    Past launches

    18 | 19 | 20 |
    21 |
    22 |

    Upcoming launches

    23 | 24 | 25 |
    26 |
    27 |
    28 | 29 | ## Code 30 | 31 | ### Typescript 32 | 33 | ```TS``` 34 | 35 | ### HTML 36 | 37 | ```HTML``` 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tutorials/src/prebuilt-theme/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | This version of VueJS Datatable is shipped with a couple of presets for CSS framework you can use. 4 | 5 | For now, following themes are available: 6 | 7 | * bootstrap-3 8 | * bootstrap-4 9 | 10 | Once loaded, you can use the themed components named `${theme}-datatable` for the table, and `${theme}-datatable-pager` for the pager. 11 | 12 | You can load up a theme using either 13 | 14 | * the *ESM* version if importing it through a javascript file 15 | 16 | ```ts 17 | import 'vuejs-datatable/dist/themes/bootstrap-3.esm'; 18 | ``` 19 | 20 | * the *IIFE* version if loading the theme directly, like via a CDN. 21 | 22 | ```html 23 | 24 | 25 | ``` 26 | 27 | ## Demo 28 | 29 |
    30 | 31 | Even if you see nothing particular with the table below, you can see using your browser's inspector that it has the class table, which is defined by the bootstrap-3 theme. 32 |
    33 | 34 |
    35 |
    36 |
    37 | 38 | 39 |
    40 |
    41 |
    42 |
    43 |
    44 | 45 | ## Code 46 | 47 | ### Typescript 48 | 49 | ```TS``` 50 | 51 | ### HTML 52 | 53 | ```HTML``` 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/assets/css/additional-styles.css: -------------------------------------------------------------------------------- 1 | #demo-app nav>.pagination{position:relative;display:inline-block;vertical-align:middle}#demo-app nav>.pagination>li{position:relative;color:#fff;background-color:#337ab7;border-color:#2e6da4;display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}#demo-app nav>.pagination>li.active{background-color:#286090;border-color:#204d74}#demo-app nav>.pagination>li:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}#demo-app nav>.pagination>li:last-child:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}#demo-app nav>.pagination>li:not(:first-child):not(:last-child){border-radius:0}#demo-app nav>.pagination>li:first-child{margin-left:0}#demo-app nav>.pagination>li>span:focus,#demo-app nav>.pagination>li>span:hover{color:inherit;background:none}#demo-app nav>.pagination>li>span{border:none;background:none;color:inherit;padding:0;margin:0}#demo-app.tutorial-pager-types .pagers-table{display:grid;grid-template-columns:1fr 1fr;margin-top:15px}#demo-app.tutorial-pager-types .pagers-table ul{padding:0;margin:0 0 5px}#demo-app .pagination-no-margin .pagination{margin:0}dl.inlined-elements{display:table}dl.inlined-elements>div{display:table-row}dl.inlined-elements>div>dt,dl.inlined-elements>div>dd{display:table-cell}dl.inlined-elements>div>dt::after{content:" :\00A0"}.alert{padding:10px;margin:10px 0;border:1px solid transparent;border-radius:10px;box-shadow:0 0 4px rgba(0,0,0,0.25)}.alert.alert-warning{background-color:rgba(254,209,56,0.4);border-color:#fed035}.alert.alert-info{background-color:rgba(0,132,255,0.4);border-color:#0082fc} 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Jul 27 2017 20:25:47 GMT-0500 (CDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | // 'src/**/*.js', 19 | 'tests/spec/index.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | }, 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['Chrome'], 60 | 61 | 62 | // Continuous Integration mode 63 | // if true, Karma captures browsers, runs the tests and exits 64 | singleRun: false, 65 | 66 | // Concurrency level 67 | // how many browser should be started simultaneous 68 | concurrency: Infinity 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export type TMaybePromise = T | Promise; 3 | 4 | const isPromise = ( value: any ): value is Promise => value && typeof value.then === 'function'; 5 | export const ensurePromise = ( value: TMaybePromise ): Promise => { 6 | if ( isPromise( value ) ) { 7 | return value; 8 | } else { 9 | return Promise.resolve( value ); 10 | } 11 | }; 12 | 13 | export interface IDict {[key: string]: T; } 14 | export type TClassVal = string | string[] | IDict; 15 | export const classValType: any[] = [ String, Array, Object ]; 16 | export const mergeClassVals = ( mainObj: TClassVal, ...objs: Array ): string[] => 17 | Object.entries( 18 | Object.assign( 19 | canonicalClassVals( mainObj ), 20 | ...( objs.map( v => canonicalClassVals( v ) ) ) ) as IDict ) 21 | .filter( ( [, enabled] ) => enabled ) 22 | .map( ( [className] ) => className ); 23 | 24 | const canonicalClassVals = ( classVal: TClassVal | null | undefined ): IDict => { 25 | if ( typeof classVal === 'string' ) { 26 | classVal = classVal.split( /\s+/g ); 27 | } 28 | if ( Array.isArray( classVal ) ) { 29 | return classVal.reduce( ( acc, className ) => { 30 | acc[className] = true; 31 | return acc; 32 | }, {} as IDict ); 33 | } 34 | return classVal || {}; 35 | }; 36 | 37 | export const namespace = 'vue-datatable'; 38 | export const namespaceEvent = ( event: string ) => `${namespace}::${event}`; 39 | 40 | /** 41 | * Enumeration of text alignment in a cell. 42 | */ 43 | export const enum EColAlign { 44 | Left = 'left', 45 | Center = 'center', 46 | Right = 'right', 47 | } 48 | 49 | /** 50 | * Enumeration of the different display modes available for the pager. 51 | * 52 | * @tutorial pager-types 53 | */ 54 | export const enum EPagerType { 55 | Short = 'short', 56 | Abbreviated = 'abbreviated', 57 | Long = 'long', 58 | } 59 | 60 | export const valueToString = ( val: any ): string => { 61 | if ( val === null || typeof val === 'undefined' ) { 62 | return ''; 63 | } else { 64 | return val.toString(); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/components/vue-datatable-pager/vue-datatable-pager-button/vue-datatable-pager-button.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, Prop, Vue } from 'vue-property-decorator'; 2 | 3 | import { TableType } from '../../../classes'; 4 | import { mergeClassVals, namespaceEvent } from './../../../utils'; 5 | 6 | import template from './vue-datatable-pager-button.html'; 7 | 8 | /** 9 | * A control button used by the pager. 10 | */ 11 | @Component( { 12 | ...template, 13 | } ) 14 | export class VueDatatablePagerButton extends Vue { 15 | /** 16 | * Defines if the button is triggerable or not. 17 | * 18 | * @vue-prop 19 | */ 20 | @Prop( { type: Boolean, default: false } ) private readonly disabled!: boolean; 21 | /** 22 | * Represents if the pager button is the currently selected one. 23 | * 24 | * @vue-prop 25 | */ 26 | @Prop( { type: Boolean, default: false } ) private readonly selected!: boolean; 27 | /** 28 | * The page index of the button. 29 | * 30 | * @vue-prop 31 | */ 32 | @Prop( { type: Number } ) private readonly value!: number | null; 33 | 34 | /** 35 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 36 | * 37 | * @vue Inject `table-type` 38 | */ 39 | @Inject( 'table-type' ) private readonly tableType!: TableType; 40 | 41 | /** HTML classes to set on list items tags. */ 42 | public get liClasses() { 43 | return mergeClassVals( 44 | this.tableType.setting( 'pager.classes.li' ), 45 | this.disabled ? this.tableType.setting( 'pager.classes.disabled' ) : undefined, 46 | this.selected ? this.tableType.setting( 'pager.classes.selected' ) : undefined, 47 | ); 48 | } 49 | 50 | /** CSS styles to apply on the list items tags */ 51 | public get liStyles() { 52 | return { cursor: this.disabled ? 'not-allowed' : 'pointer' }; 53 | } 54 | 55 | /** 56 | * Emits an event if the button is not [[VueDatatablePagerButton.disabled]]. 57 | * 58 | * @vue-event vuejs-datatable::set-page. 59 | * @returns Nothing. 60 | */ 61 | public sendClick() { 62 | if ( !this.disabled ) { 63 | this.$parent.$emit( namespaceEvent( 'set-page' ), this.value ); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { ensurePromise, mergeClassVals, TClassVal } from './utils'; 2 | 3 | describe( 'Ensure promise', () => { 4 | it( 'Should return a promise as-is', () => { 5 | // tslint:disable-next-line: no-inferred-empty-object-type 6 | const val = new Promise( () => ( {} ) ); 7 | // tslint:disable-next-line: no-floating-promises 8 | expect( ensurePromise( val ) ).toBe( val ); 9 | } ); 10 | it( 'Should return a promise-like as-is', () => { 11 | const val = { then: () => ( {} ) }; 12 | // tslint:disable-next-line: no-floating-promises 13 | expect( ensurePromise( val ) ).toBe( val ); 14 | } ); 15 | it( 'Should wrap a non-promise-like in a promise', async () => { 16 | const val = {}; 17 | const ensured = ensurePromise( val ); 18 | // tslint:disable-next-line: no-floating-promises 19 | expect( ensured ).toBeInstanceOf( Promise ); 20 | // tslint:disable-next-line: no-inferred-empty-object-type 21 | expect( await ensured ).toBe( val ); 22 | } ); 23 | } ); 24 | describe( 'Merge class lists', () => { 25 | it.each` 26 | a | b | result 27 | ${undefined} | ${undefined} | ${[]} 28 | ${undefined} | ${'baz qux'} | ${['baz', 'qux']} 29 | ${undefined} | ${['baz', 'qux']} | ${['baz', 'qux']} 30 | ${undefined} | ${{ baz: true, qux: true }} | ${['baz', 'qux']} 31 | ${'foo bar'} | ${'baz qux'} | ${['foo', 'bar', 'baz', 'qux']} 32 | ${'foo bar'} | ${['baz', 'qux']} | ${['foo', 'bar', 'baz', 'qux']} 33 | ${'foo bar'} | ${{ baz: true, qux: true }} | ${['foo', 'bar', 'baz', 'qux']} 34 | ${['foo', 'bar']} | ${['baz', 'qux']} | ${['foo', 'bar', 'baz', 'qux']} 35 | ${['foo', 'bar']} | ${{ baz: true, qux: true }} | ${['foo', 'bar', 'baz', 'qux']} 36 | ${{ foo: true, bar: true }} | ${{ baz: true, qux: true }} | ${['foo', 'bar', 'baz', 'qux']} 37 | `( 'Merge `$a` with `$b` should result in `$result`', ( { a, b, result }: {a: TClassVal; b: TClassVal; result: string[]} ) => { 38 | expect( mergeClassVals( a, b ) ).toEqual( result ); 39 | } ); 40 | } ); 41 | -------------------------------------------------------------------------------- /tutorials/src/custom-template/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | This plugin is built to be as versatile as possible, and allow you to inject your own HTML rendering using several [slots](https://vuejs.org/v2/guide/components-slots.html). 4 | 5 | ### Datatable 6 | 7 | > See the [Datatable API doc](../classes/vuedatatable.html) 8 | 9 | #### `footer` slot 10 | 11 | This footer is displayed at the bottom of your data table. 12 | 13 | ##### Signature 14 | 15 | | Prop | Type | Description | 16 | |------|--------|-------------| 17 | | `rows` | `TRow[]` | The list of rows currently displayed by the table. It only contains the current page. | 18 | | `columns` | [`Column[]`](../classes/column.html) | The columns of the table | 19 | | `pagination` | [`IPageRange`](../interfaces/ipagerange.html) | An object describing the current pagination status | 20 | 21 | ##### Example 22 | 23 | ```html 24 | 25 | 30 | 31 | ``` 32 | 33 | #### `default` slot 34 | 35 | This slot is used to render each rows. It completely overrides the default row rendering process. 36 | 37 | ##### Signature 38 | 39 | | Prop | Type | Description | 40 | |------|--------|-------------| 41 | | `row` | `TRow` | The current row that it is appending to the table. | 42 | | `index` | `number` | The current index of the row in the displayed page. | 43 | | `columns` | `Column[]` | The [columns](../classes/column.html) of the table | 44 | 45 | ##### Example 46 | 47 | ```html 48 | 49 | 56 | 57 | ``` 58 | 59 | #### `no-results` slot 60 | 61 | This slot is displayed if the table do not contain any rows. 62 | 63 | ##### Signature 64 | 65 | | Prop | Type | Description | 66 | |------|--------|-------------| 67 | 68 | ##### Example 69 | 70 | ```html 71 | 72 | 75 | 76 | ``` 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | branches: 3 | only: 4 | - master 5 | - staging 6 | - "/^greenkeeper/.*$/" 7 | notifications: 8 | email: false 9 | node_js: 10 | - '10' 11 | matrix: 12 | include: 13 | - node_js: lts/* 14 | env: CC_SAMPLE=true 15 | addons: 16 | apt: 17 | packages: 18 | - libgconf-2-4 19 | cache: 20 | directories: 21 | - node_modules 22 | - "/home/travis/.cache" 23 | install: npm install 24 | before_script: 25 | - | 26 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 27 | chmod +x ./cc-test-reporter 28 | if [ "$CC_SAMPLE" = "true" ]; then echo "This run will be used for codeclimate";./cc-test-reporter before-build; fi 29 | script: 30 | - npm run ci 31 | after_script: 32 | - 'if [ "$CC_SAMPLE" = "true" ]; then echo "This run is used for codeclimate"; ./cc-test-reporter 33 | after-build --exit-code $TRAVIS_TEST_RESULT; fi 34 | 35 | ' 36 | env: 37 | matrix: 38 | secure: MjXS56QY72ddLPXbYhbKBwBCI07Iq2L8jzLsgGlrs6O2rv9+0ZJEY6t7NszhJn8nNa1mWTjwh/Px3g3j/2FIk7CUdynj1R5Ui+YuTYQBW70/o7Z9Lnr83yohx+RhM0vXYqtlNX4ry57CPgVo01ahelVj832wk351PaqKqwyBR2jy+p3l9vnGUPsb3w5XGYUYQ1l81q5sSo/t05DqCfeduVPpMbx+sEN/soBkUO0Pc8rxpW61UNJwfNpqGVQuX2NKeU3fhYdw0UA9mSJvyx6cRPntiUB8Dpl5r65qTfxJJToSIjAJEk73lVgnVaPrgE6fhgcqjA2obskA4Jx4IMiOuLHwiBjACZTQ5PClBmYcMrKpQkpMa6qQ/hz/q11VMKjY3UCsrdlUWgnlJhDrhJsHpgR55sVmQKZBTaFLoE1+IjOYavFVcMJha8RGZnYzcxw76F4XeoN9vq1/NjASWbwFEIjfGhDNSYwyaBUtIKkLb3JQNXW9wBc2od3Qw7LlkgxbEVUB6T7vGTW15aoMpvPImiiNg1dRBudVPRvCt8z3ViNWzpM67DcHDdhtGokoUsz/OHGyy7Dw435QqwgkAUAB/nNmA8uE1fYslMqYFga7AvOlA4+n7f+Avc+gRgrfebf1EFTkWFLXGSboUQSGVBMbxyKXWe4ImtuZaMsLH6QWvW8= 39 | deploy: 40 | provider: npm 41 | email: alexandre.j.p.germain@gmail.com 42 | api_key: 43 | secure: xB8qZxWoa3nNQzwGnBNRSOYsPgGnNGgmxQwe8s3mOmEOaTCRZmmz8la+uuEk2JIntCoF6TzNxnMnt4ctv3SO4Ncdy333+VgXNMPQr6m9njLlO9c+EL33VkUbdVbLvTV6WK2R1eyIzpZxQh1MERigFSOGRc0EJIfoe18kvk/yA2D8tQPL1gE/aaWCdgqcsSBtnaQI9YOErFj+EhBSrev2k2SAsrYrz0ATqhuTya9wSckmW2XTGCjvfWUc+EaQYnpFlH+yoprZABffL/RxsT2GiMpJhG7JRHKCVT6Nl20GQji5sZIlsznNcfc8EUuFiVRZuDb/nwouHpoYGMOQfnNsmmru6RGi2YpXgpFAZOvzuL0Vs8zuwxMZwym2sfSm0fml3TX5Ir5uqpnYRiGpTjJ6GrBu29IlzA5auKd+IZn7SRZo6ZSKCIxEzzN/e7+Gm4Tob/7echMZvxVx8Dr/yXMkcxqC4oYnQvxxn1ALQLIVscPrNthmajB5YY0em2xPIHjHMZEEw8A8Y+gdUt41gUpdT0b+Gu5Evci0m61i/rbedaA8Vlk/gOKeULP9+LKc+mJaOVSKwfLU7ydc4fvzB4ANvP6yWHP/QpyOY9tP3tP3H4/9laQF9rOs1AjgxURIfF+cBek0JzgUrU4E2VGacY1oArPW7q79wOpNvWRPFXltggw= 44 | on: 45 | tags: false 46 | repo: GerkinDev/vuejs-datatable 47 | branch: staging 48 | condition: "-f ~/do-release" 49 | -------------------------------------------------------------------------------- /tutorials/src/limit-rows-processing/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | If the processing of your rows is very expensive (like if it is fetched from an API, or requires a heavy computation), you may want to limit the number of calls to this function. For instance, if you want to automatically refresh data on text filter changes, you would avoid re-process everything on every key press. 4 | 5 | This process of limitation is *very important* to reduce your load & improve performances, and it can be seen in two main variations: 6 | 7 |
    8 |
    debouncing
    runs after a cooldown of n ms
    9 |
    throttling
    runs at most every n ms
    10 |
    11 | 12 | You can check out [this nice article on CSS-Tricks](https://css-tricks.com/debouncing-throttling-explained-examples/) for more informations about the difference between those kind of functions. 13 | 14 | In this example, we use the [`_.debounce` function from *lodash*](https://lodash.com/docs/4.17.11#debounce) to call `processRows` only when the user stop typing for 1s. 15 | 16 |
    17 | 18 | See the ajax tutorial via data function or custom handlers for a more detailed example of AJAX data querying. 19 |
    20 | 21 | ## Demo 22 | 23 |
    24 |
    25 |
    26 |
    27 | 28 | 29 |
    30 |
    31 | 32 | {{callsCount}} 33 |
    34 | 35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 | 42 | ## Code 43 | 44 | ### Typescript 45 | 46 | ```TS``` 47 | 48 | ### HTML 49 | 50 | ```HTML``` 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ci/spawn-exec.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess, exec, spawn } from 'child_process'; 2 | import { logDebug } from './log'; 3 | 4 | export const spawnStream = async ( cmd: string, args: string[], attachOuts?: boolean ) => { 5 | const prefix = `${cmd} ${args.join( ' ' )}`; 6 | const cmdProcess = spawn( cmd, args ); 7 | return streamChildProcess( cmdProcess, prefix, attachOuts ); 8 | }; 9 | export const execStream = async ( cmd: string, attachOuts?: boolean ) => { 10 | const cmdProcess = exec( cmd ); 11 | return streamChildProcess( cmdProcess, cmd, attachOuts ); 12 | }; 13 | 14 | const loggableData = ( buffer: Buffer ) => buffer.toString().replace( /\r?\n$/, '' ); 15 | const formatOutputText = ( prefix: string, data: string | Buffer ) => { 16 | const dataStr = typeof data === 'string' ? data : loggableData( data ); 17 | const padLeft = prefix.replace( /.(?!$)/g, ' ' ).replace( /.$/g, '|' ) + ' '; 18 | return `${prefix} ${dataStr.replace( /\r?\n/g, '\n' + padLeft )}`; 19 | }; 20 | 21 | const streamChildProcess = async ( child: ChildProcess, logPrefix: string, attachOuts = false ) => new Promise( ( res, rej ) => { 22 | if ( attachOuts ) { 23 | if ( child.stdout ) { 24 | child.stdout.on( 'data', data => { 25 | logDebug( formatOutputText( `"${logPrefix}" stdout:`, data ) ); 26 | } ); 27 | } 28 | 29 | if ( child.stderr ) { 30 | child.stderr.on( 'data', data => { 31 | logDebug( formatOutputText( `"${logPrefix}" stderr:`, data ) ); 32 | } ); 33 | } 34 | 35 | child.on( 'exit', code => { 36 | if ( code === 0 ) { 37 | return res(); 38 | } else { 39 | // Create the error 40 | const err = new Error( `Child process exited with code ${code}` ); 41 | ( err as any ).code = code; 42 | return rej( err ); 43 | } 44 | } ); 45 | } else { 46 | let stdout = ''; 47 | let stderr = ''; 48 | if ( child.stdout ) { 49 | child.stdout.on( 'data', data => { 50 | stdout += '\n' + loggableData( data ); 51 | } ); 52 | } 53 | 54 | if ( child.stderr ) { 55 | child.stderr.on( 'data', data => { 56 | stderr += '\n' + loggableData( data ); 57 | } ); 58 | } 59 | 60 | child.on( 'exit', code => { 61 | if ( code === 0 ) { 62 | return res(); 63 | } else { 64 | // Log command outputs for debugging purposes 65 | logDebug( formatOutputText( `"${logPrefix}" stdout:`, stdout ) ); 66 | logDebug( formatOutputText( `"${logPrefix}" stderr:`, stderr ) ); 67 | 68 | // Create the error 69 | const err = new Error( `Child process exited with code ${code}` ); 70 | ( err as any ).code = code; 71 | return rej( err ); 72 | } 73 | } ); 74 | } 75 | } ); 76 | -------------------------------------------------------------------------------- /tutorials/src/multiple-tables/index.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Datatable and pager can be associated with each other using the [VueDatatable.name](../classes/vuedatatable.html#name) & [VueDatatablePager.table](../classes/vuedatatablepager.html#table) property. Simply set the same value for both properties, and the pager will control the associated table. 4 | 5 |
    6 | 7 | Check out the Pager styles tutorial to see the different aspects available for the pager.
    8 | If you want to dynamically generate names from the host component, you can use the $vnode.tag property (TODO: Find a link or reference explaining what is this property, exactly... If you got a reference, please post a PR ;) ). 9 |
    10 | 11 |
    12 | 13 | If the relation between the table and the pager is broken, the table will be displayed in no pager mode. 14 |
    15 | 16 | ## Demo 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 | 28 | 29 |
    30 |
    31 |
    32 |
    33 |
    34 | 35 | 36 |
    37 |
    38 |
    39 | 40 | 41 |
    42 |
    43 |
    44 | 45 | ## Code 46 | 47 | ### Typescript 48 | 49 | ```TS``` 50 | 51 | ### HTML 52 | 53 | ```HTML``` 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /__tests__/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | const { writeFile: _writeFile, access } = require( 'fs' ); 2 | const { dirname, resolve: resolvePath } = require( 'path' ); 3 | const { exec } = require( 'child_process' ); 4 | const mkdirp = require( 'mkdirp' ); 5 | const { F_OK } = require( 'constants' ); 6 | 7 | const { rollup } = require( 'rollup' ); 8 | const typescript = require( 'rollup-plugin-typescript2' ); 9 | const resolve = require( 'rollup-plugin-node-resolve' ); 10 | const commonjs = require( 'rollup-plugin-commonjs' ); 11 | const replace = require( 'rollup-plugin-replace' ); 12 | 13 | const writeFile = ( path, data, opts ) => new Promise( ( res, rej ) => _writeFile( path, data, opts, err => err ? rej( err ) : res() ) ); 14 | const mkdir = ( path, opts ) => new Promise( ( res, rej ) => mkdirp( path, opts, err => err ? rej( err ) : res() ) ); 15 | 16 | module.exports = on => { 17 | // Trigger build if the module is not already built 18 | on( 'before:browser:launch', () => { 19 | return new Promise( ( res, rej ) => { 20 | access( resolvePath( '../../../dist/vuejs-datatable.js' ), F_OK, err => { 21 | if ( err ) { 22 | console.log( 'Missing built library, build it on-the-fly.' ); 23 | exec( 'npm run build', err2 => { 24 | if ( err2 ) { 25 | console.error( 'Build failed !' ); 26 | return rej( err2 ); 27 | } else { 28 | console.info( 'Build succeeded !' ); 29 | return res(); 30 | } 31 | } ); 32 | } else { 33 | return res(); 34 | } 35 | } ); 36 | }); 37 | }), 38 | on( 'file:preprocessor', async file => { 39 | const confBundle = await rollup( { 40 | input: file.filePath, 41 | plugins: [ 42 | typescript( { 43 | objectHashIgnoreUnknownHack: true, 44 | clean: true,//environment === 'production', 45 | tsconfigOverride: require('../tsconfig.json') 46 | } ), 47 | resolve( { 48 | extensions: [ '.ts', '.js', '.json' ], 49 | browser: true, 50 | } ), 51 | commonjs( { 52 | namedExports: { 53 | // left-hand side can be an absolute path, a path 54 | // relative to the current directory, or the name 55 | // of a module in node_modules 56 | 'object-path': [ 'get', 'set' ], 57 | }, 58 | } ), 59 | replace( { 60 | 'process.env.NODE_ENV': JSON.stringify( 'production' ), 61 | } ), 62 | ] 63 | } ); 64 | const outBundle = await confBundle.generate( { format: 'iife' } ); 65 | const { output: [{ code }] } = outBundle; 66 | const outFileName = file.outputPath.replace( /\.ts(x?)$/, '-out-bundled.js$1' ); 67 | const outDirName = dirname( outFileName ); 68 | await mkdir( outDirName ); 69 | await writeFile( outFileName, code ); 70 | return outFileName; 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /src/components/vue-datatable-header/vue-datatable-header.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, Model, Prop, Vue } from 'vue-property-decorator'; 2 | 3 | import { Column, ESortDir, TableType } from '../../classes'; 4 | 5 | import template from './vue-datatable-header.html'; 6 | 7 | /** 8 | * A control button used by the pager. 9 | */ 10 | @Component( { 11 | ...template, 12 | } ) 13 | export class VueDatatableHeader extends Vue { 14 | /** 15 | * The current sort direction for the current column. 16 | * 17 | * @vue-model 18 | */ 19 | @Model( 'change', { type: String } ) private readonly direction!: ESortDir | null; 20 | 21 | /** 22 | * The [[Column]] instance this header is for. 23 | * 24 | * @vue-prop 25 | */ 26 | @Prop( { type: Object, required: true } ) private readonly column!: Column; 27 | 28 | /** 29 | * The [[TableType]] instance provided through [[TableTypeConsumer.tableType]]. 30 | * 31 | * @vue Inject `table-type` 32 | */ 33 | @Inject( 'table-type' ) private readonly tableType!: TableType; 34 | 35 | /** `true` if this column is sortable. */ 36 | private get canSort(): boolean { 37 | return this.column.sortable; 38 | } 39 | 40 | /** `true` if this column is sorted in *ascending* mode. */ 41 | private get isSortedAscending(): boolean { 42 | return this.direction === ESortDir.Asc; 43 | } 44 | 45 | /** `true` if this column is sorted in *descending* mode. */ 46 | private get isSortedDescending(): boolean { 47 | return this.direction === ESortDir.Desc; 48 | } 49 | 50 | /** Get the HTML content of the header's sort icon */ 51 | public get sortButtonHtml(): string { 52 | const htmlContents = this.tableType.setting( 'table.sorting' ); 53 | 54 | if ( this.isSortedAscending ) { 55 | return htmlContents.sortAsc; 56 | } else if ( this.isSortedDescending ) { 57 | return htmlContents.sortDesc; 58 | } else { 59 | return htmlContents.sortNone; 60 | } 61 | } 62 | 63 | /** 64 | * Toggles the sort order, looping between states `null => 'asc' => 'desc'`. 65 | * 66 | * @vue-event change Emitted when the sort direction or column is changed. 67 | * @vue-event-param change newDirection - The new direction. 68 | * @vue-event-param change sortedColumn - The column the sort is done on. 69 | * @returns nothing. 70 | */ 71 | public toggleSort(): void { 72 | if ( !this.canSort ) { 73 | return; 74 | } 75 | if ( !this.direction || this.direction === null ) { 76 | this.$emit( 'change', ESortDir.Asc, this.column ); 77 | } else if ( this.direction === ESortDir.Asc ) { 78 | this.$emit( 'change', ESortDir.Desc, this.column ); 79 | } else { 80 | this.$emit( 'change', null, this.column ); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tutorials/build/exec-script-transform.ts: -------------------------------------------------------------------------------- 1 | import { F_OK } from 'constants'; 2 | import { promises } from 'fs'; 3 | // tslint:disable-next-line: no-implicit-dependencies 4 | import jscc from 'jscc'; 5 | import { basename, dirname, join, relative, resolve } from 'path'; 6 | 7 | import { tempDir } from './build-utils'; 8 | 9 | export const readFile = async ( path: string, encoding = 'UTF-8' ) => { 10 | const content = await promises.readFile( path, encoding ); 11 | if ( content instanceof Buffer ) { 12 | return content.toString( encoding ); 13 | } 14 | return content; 15 | }; 16 | 17 | const wrapScript = ( script: string ) => 18 | `let inited = false; 19 | const runDemo = () => { 20 | if ( inited ) { 21 | return; 22 | } 23 | inited = true; 24 | 25 | // ----------------------------- 26 | 27 | ${ script.trim().split( /\n/g ).map( s => `\t${s}` ).join( '\n' ) } 28 | } 29 | 30 | ( document as any ).addEventListener && ( document as any ).addEventListener( 'DOMContentLoaded', runDemo, false ); 31 | ( window as any ).addEventListener && ( window as any ).addEventListener( 'load', runDemo, false ); 32 | ( document as any ).attachEvent && ( document as any ).attachEvent( 'onreadystatechange', runDemo ); 33 | ( window as any ).attachEvent && ( window as any ).attachEvent( 'onload', runDemo ); 34 | `; 35 | 36 | export const generateWrappedScript = async ( sourceFile: string ) => { 37 | const { code: content } = jscc( await readFile( sourceFile ), sourceFile, { values: { _DISPLAY: '0' }} ); 38 | 39 | try { 40 | await promises.access( tempDir, F_OK ); 41 | } catch { 42 | await promises.mkdir( tempDir ); 43 | } 44 | const tempFile = resolve( tempDir, `${ basename( dirname( sourceFile ) ) }.ts` ); 45 | 46 | // Extract imports 47 | const hoistedStatements: string[] = []; 48 | const contentNoHoist = content.replace( /import\s+(?:.*?from\s+)?(['"])(\S+)\1;?/g, ( fullMatch, quoteType: string, targetModulePath: string ) => { 49 | const moduleNameStr = quoteType + targetModulePath + quoteType; 50 | if ( targetModulePath.startsWith( '.' ) ) { 51 | const resolvedFilePath = relative( tempDir, sourceFile ); 52 | const relativeModule = join( dirname( resolvedFilePath ), targetModulePath ).replace( /\\/g, '/' ); 53 | hoistedStatements.push( fullMatch.replace( moduleNameStr, `'${relativeModule}'` ) ); 54 | } else { 55 | hoistedStatements.push( fullMatch.replace( moduleNameStr, `'${targetModulePath}'` ) ); 56 | } 57 | return ''; 58 | } ).trim().replace( /^\s*declare\s+(var|let|const)\s+.*?$/gm, fullMatch => { 59 | hoistedStatements.push( fullMatch ); 60 | return ''; 61 | } ).trim(); 62 | await promises.writeFile( tempFile, `${hoistedStatements.map( s => s.trim() ).join( '\n' )} 63 | 64 | ${wrapScript( contentNoHoist )}` ); 65 | return tempFile; 66 | }; 67 | -------------------------------------------------------------------------------- /tutorials/src/ajax-data/demo.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import Vue from 'vue'; 3 | import { IDataFnParams, ITableContentParam, TColumnsDefinition } from 'vuejs-datatable'; 4 | 5 | import { colFieldToStr, formatUtcDate, ISpaceXLaunch, makeQueryStringFromObj } from '../utils'; 6 | 7 | const API_URL = 'https://api.spacexdata.com/v3/launches/upcoming'; 8 | const app = new Vue( { 9 | el: '#demo-app', 10 | 11 | data: { 12 | columns: [ 13 | { label: 'Flight number', field: 'flight_number' }, 14 | { label: 'Mission name', field: 'mission_name' }, 15 | { label: 'Launch date', field: 'launch_date_utc', representedAs: row => formatUtcDate( new Date( row.launch_date_utc ) ) }, 16 | { label: 'Rocket type', field: 'rocket.rocket_name', sortable: false }, 17 | { label: 'Launch site', field: 'launch_site.site_name_long', sortable: false }, 18 | { 19 | field: 'links.mission_patch_small', 20 | interpolate: true, 21 | label: 'Mission patch', 22 | representedAs: row => row.links.mission_patch_small ? 23 | `${ row.mission_name } patch` : 24 | `No patch for mission "${ row.mission_name }" available`, 25 | sortable: false, 26 | }, 27 | { 28 | field: 'links.reddit_campaign', 29 | interpolate: true, 30 | label: 'Reddit link', 31 | representedAs: row => `${ row.mission_name } Reddit thread`, 32 | sortable: false, 33 | }, 34 | ] as TColumnsDefinition, 35 | page: 1, 36 | async getData( { sortBy, sortDir, perPage, page }: IDataFnParams ) { 37 | const sortParams = sortBy && sortDir ? { 38 | order: sortDir, 39 | sort: colFieldToStr( sortBy ).replace( /\./g, '/' ), 40 | } : {}; 41 | const params = { 42 | // Sorting 43 | ...sortParams, 44 | 45 | // Filtering 46 | // See https://documenter.getpostman.com/view/2025350/RWaEzAiG#json-field-masking 47 | filter: this.columns.map( col => colFieldToStr( col.field! ).replace( /\./g, '/' ) ).join( ',' ), 48 | 49 | // Paging 50 | limit: perPage || 10, 51 | offset: ( ( page - 1 ) * perPage ) || 0, 52 | }; 53 | 54 | const url = `${ API_URL }?${ makeQueryStringFromObj( params ) }`; 55 | 56 | const { 57 | // Data to display 58 | data, 59 | // Get the total number of matched items 60 | headers: { 'spacex-api-count': totalCount }, 61 | } = await axios.get( url ); 62 | 63 | return { 64 | rows: data, 65 | totalRowCount: totalCount, 66 | } as ITableContentParam; 67 | }, 68 | }, 69 | } ); 70 | -------------------------------------------------------------------------------- /tutorials/src/limit-rows-processing/demo.ts: -------------------------------------------------------------------------------- 1 | import { debounce } from 'lodash'; 2 | import Vue from 'vue'; 3 | import { IDataFnParams, IDisplayHandlerParam, ITableContentParam, TColumnsDefinition, VueDatatable } from 'vuejs-datatable'; 4 | 5 | import { IPeople } from '../utils'; 6 | /*#if _DISPLAY == 1 7 | import { queryApiForData } from '../utils'; 8 | //#endif */ 9 | declare var rows: IPeople[]; 10 | 11 | const app = new Vue( { 12 | el: '#demo-app', 13 | data: { 14 | filter: '', 15 | columns: [ 16 | { label: 'ID', field: 'id', align: 'center', filterable: false }, 17 | { label: 'Username', field: 'user.username' }, 18 | { label: 'First Name', field: 'user.first_name' }, 19 | { label: 'Last Name', field: 'user.last_name' }, 20 | { label: 'Email', field: 'user.email', align: 'right', sortable: false }, 21 | { label: 'Address', representedAs: row => `${ row.address }, ${ row.city }, ${ row.state }`, align: 'right', sortable: false }, 22 | ] as TColumnsDefinition, 23 | async someLongOperation( this: VueDatatable, params: IDataFnParams ): Promise> { 24 | this.$root.$data.callsCount++; 25 | /*#if _DISPLAY == 1 26 | const { data, totalRows } = await queryApiForData( params ); 27 | 28 | return { 29 | rows: data, 30 | totalRowCount: totalRows, 31 | }; 32 | //#else */ 33 | // This part is only for the executable code in the demo page 34 | // Wait some time to simulate HTTP request 35 | await new Promise( resolve => setTimeout( resolve, ( Math.random() * 400 ) + 100 ) ); 36 | const ensurePromise = ( value: T | Promise ): Promise => { 37 | if ( value && typeof ( value as any ).then === 'function' ) { 38 | return value as Promise; 39 | } else { 40 | return Promise.resolve( value ); 41 | } 42 | }; 43 | const outObj: Partial> = { source: rows }; 44 | return ensurePromise( this.handler.filterHandler( rows, params.filter, this.normalizedColumns ) ) 45 | .then( filteredData => ensurePromise( this.handler.sortHandler( outObj.filtered = filteredData, { field: params.sortBy } as any, params.sortDir ) ) ) 46 | .then( sortedData => ensurePromise( this.handler.paginateHandler( outObj.sorted = sortedData, params.perPage, this.page ) ) ) 47 | .then( pagedData => ensurePromise( this.handler.displayHandler( Object.assign( { paged: pagedData }, outObj ) as IDisplayHandlerParam ) ) ); 48 | // Below is only for the displayed content in the demo page 49 | //#endif 50 | }, 51 | page: 1, 52 | callsCount: 0, 53 | }, 54 | mounted() { 55 | this.$datatables.debounced.processRows = debounce( this.$datatables.debounced.processRows, 1000 ); 56 | }, 57 | } ); 58 | -------------------------------------------------------------------------------- /__tests__/integration/sample-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "id": 0, "title": "In laboris eiusmod labore et excepteur exercitation excepteur ad eiusmod labore cillum." }, 3 | { "id": 1, "title": "Veniam ex duis ex minim." }, 4 | { "id": 2, "title": "Anim excepteur irure laborum adipisicing velit esse excepteur adipisicing sit aute ullamco magna ad." }, 5 | { "id": 3, "title": "Mollit irure labore aute deserunt quis duis proident ipsum ullamco pariatur." }, 6 | { "id": 4, "title": "Velit enim ut pariatur ipsum cupidatat id cupidatat anim minim ea Lorem pariatur enim amet." }, 7 | { "id": 5, "title": "Eiusmod consequat incididunt velit amet do elit consequat ut laboris." }, 8 | { "id": 6, "title": "Et id fugiat laborum ex occaecat commodo tempor voluptate enim elit enim sunt." }, 9 | { "id": 7, "title": "Enim consectetur ipsum irure ut esse laboris ipsum elit excepteur nisi non consequat exercitation aliqua." }, 10 | { "id": 8, "title": "Anim sit consequat deserunt magna mollit non proident excepteur duis ullamco." }, 11 | { "id": 9, "title": "Laborum ullamco fugiat minim proident sunt tempor nulla excepteur veniam do velit pariatur." }, 12 | { "id": 10, "title": "Ad id anim amet aliqua exercitation exercitation nulla exercitation aliquip excepteur laborum cillum fugiat nostrud." }, 13 | { "id": 11, "title": "Duis laborum quis adipisicing aute veniam commodo nisi laborum cillum adipisicing amet pariatur amet." }, 14 | { "id": 12, "title": "Esse et cupidatat irure nisi proident anim nulla aute exercitation amet dolore aliqua quis ea." }, 15 | { "id": 13, "title": "Non ea officia excepteur magna pariatur amet non quis cupidatat eiusmod aliqua minim esse consectetur." }, 16 | { "id": 14, "title": "Enim sint laboris cillum incididunt anim consectetur." }, 17 | { "id": 15, "title": "Ullamco excepteur officia fugiat enim ex consectetur elit." }, 18 | { "id": 16, "title": "Cupidatat eiusmod proident aliqua in sunt voluptate." }, 19 | { "id": 17, "title": "Incididunt in duis ipsum duis aliqua eiusmod adipisicing occaecat cupidatat consequat pariatur consequat quis." }, 20 | { "id": 18, "title": "Et sit magna et ex ullamco proident veniam labore deserunt cupidatat." }, 21 | { "id": 19, "title": "Nisi proident dolor ipsum culpa magna in do ad reprehenderit proident magna cillum Lorem reprehenderit." }, 22 | { "id": 20, "title": "Labore anim culpa commodo nisi officia labore dolor aute et id culpa ad laboris velit." }, 23 | { "id": 21, "title": "Sunt consectetur ex ex officia reprehenderit veniam." }, 24 | { "id": 22, "title": "Lorem duis adipisicing et velit." }, 25 | { "id": 23, "title": "Adipisicing reprehenderit tempor excepteur aliqua." }, 26 | { "id": 24, "title": "Eiusmod cillum eiusmod veniam quis aute adipisicing irure nisi." } 27 | ] 28 | -------------------------------------------------------------------------------- /tutorials/assets/additional-styles.scss: -------------------------------------------------------------------------------- 1 | #demo-app { 2 | nav > .pagination{ 3 | position: relative; 4 | display: inline-block; 5 | vertical-align: middle; 6 | 7 | > li { 8 | position: relative; 9 | color: #fff; 10 | background-color: #337ab7; 11 | border-color: #2e6da4; 12 | display: inline-block; 13 | padding: 6px 12px; 14 | margin-bottom: 0; 15 | font-size: 14px; 16 | font-weight: 400; 17 | line-height: 1.42857143; 18 | text-align: center; 19 | white-space: nowrap; 20 | vertical-align: middle; 21 | -ms-touch-action: manipulation; 22 | touch-action: manipulation; 23 | cursor: pointer; 24 | -webkit-user-select: none; 25 | -moz-user-select: none; 26 | -ms-user-select: none; 27 | user-select: none; 28 | background-image: none; 29 | border: 1px solid transparent; 30 | border-radius: 4px; 31 | 32 | &.active{ 33 | background-color: #286090; 34 | border-color: #204d74; 35 | } 36 | &:first-child:not(:last-child) { 37 | border-top-right-radius: 0; 38 | border-bottom-right-radius: 0; 39 | } 40 | &:last-child:not(:first-child) { 41 | border-top-left-radius: 0; 42 | border-bottom-left-radius: 0; 43 | } 44 | &:not(:first-child):not(:last-child) { 45 | border-radius: 0; 46 | } 47 | &:first-child { 48 | margin-left: 0; 49 | } 50 | > span:focus, 51 | > span:hover { 52 | color: inherit; 53 | background: none; 54 | } 55 | > span { 56 | border: none; 57 | background: none; 58 | color: inherit; 59 | padding: 0; 60 | margin: 0; 61 | } 62 | } 63 | } 64 | 65 | &.tutorial-pager-types { 66 | .pagers-table{ 67 | display: grid; 68 | grid-template-columns: 1fr 1fr; 69 | margin-top: 15px; 70 | 71 | ul{ 72 | padding: 0; 73 | margin: 0 0 5px; 74 | } 75 | } 76 | } 77 | .pagination-no-margin .pagination{ 78 | margin: 0; 79 | } 80 | } 81 | 82 | 83 | 84 | 85 | 86 | dl.inlined-elements{ 87 | display: table; 88 | 89 | > div { 90 | display: table-row; 91 | 92 | > dt, > dd{ 93 | display: table-cell; 94 | } 95 | > dt::after{ 96 | content: " :\00A0"; 97 | } 98 | } 99 | } 100 | 101 | 102 | .alert{ 103 | padding: 10px; 104 | margin: 10px 0; 105 | border: 1px solid transparent; 106 | border-radius: 10px; 107 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); 108 | 109 | $warn-color: #fed138; 110 | &.alert-warning{ 111 | background-color: fade-out($color: $warn-color, $amount: 0.6); 112 | border-color: darken($color: $warn-color, $amount: 0.6); 113 | } 114 | $info-color: #0084ff; 115 | &.alert-info{ 116 | background-color: fade-out($color: $info-color, $amount: 0.6); 117 | border-color: darken($color: $info-color, $amount: 0.6); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tutorials/src/bundlers/index.md: -------------------------------------------------------------------------------- 1 | ## Common issues 2 | 3 | ### All bundlers 4 | 5 | ### `Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.` 6 | 7 | This usually means that *vuejs-datatable* is not correctly installed on the instance of *vue* you are trying to use. 8 | 9 | #### If you are using the IIFE build 10 | 11 | Make sure that your application is not shipping its own copy of *vue*. Scripts should be loaded in the following order: 12 | 13 | 1. Vue 14 | 2. Vuejs-datatable 15 | 3. Your application code 16 | 17 | #### If you are using the ESM build 18 | 19 | Make sure that your application code & *vuejs-datatable* share the same instance of Vue. 20 | 21 | This library simply imports the module `vue`, so make sure that your application imports `vue` also. 22 | 23 | ```ts 24 | // Wrong 25 | const Vue = require( 'vue/dist/vue.esm.js' ); 26 | import Vue from 'vue/dist/vue.min.js'; 27 | // Right 28 | const Vue = require( 'vue' ); 29 | import Vue from 'vue'; 30 | ``` 31 | 32 | Instead of targeting the right build in your code, configure your bundler to alias the `vue` module as your desired `vue` dist file. Check below for [how to alias `vue`](#alias-the-vue-module). 33 | 34 | #### `You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.` 35 | 36 | ### Rollup 37 | 38 | #### `'get' is not exported by node_modules/object-path/index.js` & `'set' is not exported by node_modules/object-path/index.js` 39 | 40 | `object-path` being a *commonjs* module, you need to configure rollup to handle it properly. First, install the [plugin `rollup-plugin-commonjs`](https://www.npmjs.com/package/rollup-plugin-commonjs). 41 | 42 | ```bash 43 | npm install --save-dev rollup-plugin-commonjs 44 | ``` 45 | 46 | Then, set up rollup: 47 | 48 | ```js 49 | // ... 50 | import commonjs from 'rollup-plugin-commonjs'; 51 | 52 | export default { 53 | // ... 54 | plugins: [ 55 | // ... 56 | commonjs( { 57 | namedExports: { 58 | 'object-path': [ 'get', 'set' ], 59 | } 60 | } ), 61 | // ... 62 | ], 63 | }; 64 | ``` 65 | 66 | ## Tips & tricks 67 | 68 | ### Alias the `vue` module 69 | 70 | Because *vuejs-datatable* imports bare `vue`, you should configure your bundler to alias the distribution you want to use. 71 | 72 | #### Rollup 73 | 74 | Install the [plugin `rollup-plugin-alias`](https://www.npmjs.com/package/rollup-plugin-commonjs). 75 | 76 | ```bash 77 | npm install --save-dev rollup-plugin-alias 78 | ``` 79 | 80 | Configure rollup to alias `vue` to the distribution you want. 81 | 82 | ```js 83 | import alias from 'rollup-plugin-alias'; 84 | 85 | export default { 86 | // ... 87 | plugins: [ 88 | // ... 89 | alias( { 90 | entries: [ 91 | { find:'vue', replacement: require.resolve( 'vue/dist/vue.esm.js' ) }, 92 | ] 93 | } ), 94 | // ... 95 | ], 96 | }; 97 | ``` 98 | -------------------------------------------------------------------------------- /dist/classes/handlers/default-handler.d.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | import { ESortDir, IDisplayHandlerParam, IDisplayHandlerResult, IHandler } from './i-handler'; 4 | /** 5 | * This handler is an implementation of [[IHandler]], configured to manipulate an array of rows as input. 6 | * Handlers are called in this order: filter, sort, paginate, display. 7 | * 8 | * In case you are overriding *one* of those handlers, make sure that its return value is compatible with subsequent handlers. Otherwise, you'll require to override all of them. 9 | * 10 | * @tutorial ajax-handler 11 | */ 12 | export declare class DefaultHandler implements IHandler { 13 | /** 14 | * Filter the provided rows, checking if at least a cell contains one of the specified filters. 15 | * 16 | * @param data - The data to apply filter on. 17 | * @param filters - The strings to search in cells. 18 | * @param columns - The columns of the table. 19 | * @returns the filtered data rows. 20 | */ 21 | filterHandler(data: TRow[], filters: string[] | string | undefined, columns: Array>): TMaybePromise; 22 | /** 23 | * Sort the given rows depending on a specific column & sort order. 24 | * 25 | * @param filteredData - Data outputed from [[Handler.filterHandler]]. 26 | * @param sortColumn - The column used for sorting. 27 | * @param sortDir - The direction of the sort. 28 | * @returns the sorted rows. 29 | */ 30 | sortHandler(filteredData: TRow[], sortColumn: Column | null, sortDir: ESortDir | null): TMaybePromise; 31 | /** 32 | * Split the rows list to display the requested page index. 33 | * 34 | * @param sortedData - Data outputed from [[Handler.sortHandler]]. 35 | * @param perPage - The total number of items per page. 36 | * @param pageNumber - The index of the page to display. 37 | * @returns the requested page's rows. 38 | */ 39 | paginateHandler(sortedData: TRow[], perPage: number | null, pageNumber: number): TMaybePromise; 40 | /** 41 | * Handler to post-process the paginated data, and determine which data to actually display. 42 | * 43 | * @param processSteps - The result of each processing steps, stored in an object. Each step is the result of one of the processing function 44 | * @returns the processed values to set on the datatable. 45 | */ 46 | displayHandler({ sorted, paged }: IDisplayHandlerParam): TMaybePromise>; 47 | /** 48 | * Check if the provided row contains the filter string in *any* column. 49 | * 50 | * @param row - The data row to search in. 51 | * @param filterString - The string to match in a column. 52 | * @param columns - The list of columns in the table. 53 | * @returns `true` if any column contains the searched string. 54 | */ 55 | rowMatches(row: TRow, filterString: string, columns: Array>): boolean; 56 | } 57 | -------------------------------------------------------------------------------- /dist/themes/classes/handlers/default-handler.d.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | import { ESortDir, IDisplayHandlerParam, IDisplayHandlerResult, IHandler } from './i-handler'; 4 | /** 5 | * This handler is an implementation of [[IHandler]], configured to manipulate an array of rows as input. 6 | * Handlers are called in this order: filter, sort, paginate, display. 7 | * 8 | * In case you are overriding *one* of those handlers, make sure that its return value is compatible with subsequent handlers. Otherwise, you'll require to override all of them. 9 | * 10 | * @tutorial ajax-handler 11 | */ 12 | export declare class DefaultHandler implements IHandler { 13 | /** 14 | * Filter the provided rows, checking if at least a cell contains one of the specified filters. 15 | * 16 | * @param data - The data to apply filter on. 17 | * @param filters - The strings to search in cells. 18 | * @param columns - The columns of the table. 19 | * @returns the filtered data rows. 20 | */ 21 | filterHandler(data: TRow[], filters: string[] | string | undefined, columns: Array>): TMaybePromise; 22 | /** 23 | * Sort the given rows depending on a specific column & sort order. 24 | * 25 | * @param filteredData - Data outputed from [[Handler.filterHandler]]. 26 | * @param sortColumn - The column used for sorting. 27 | * @param sortDir - The direction of the sort. 28 | * @returns the sorted rows. 29 | */ 30 | sortHandler(filteredData: TRow[], sortColumn: Column | null, sortDir: ESortDir | null): TMaybePromise; 31 | /** 32 | * Split the rows list to display the requested page index. 33 | * 34 | * @param sortedData - Data outputed from [[Handler.sortHandler]]. 35 | * @param perPage - The total number of items per page. 36 | * @param pageNumber - The index of the page to display. 37 | * @returns the requested page's rows. 38 | */ 39 | paginateHandler(sortedData: TRow[], perPage: number | null, pageNumber: number): TMaybePromise; 40 | /** 41 | * Handler to post-process the paginated data, and determine which data to actually display. 42 | * 43 | * @param processSteps - The result of each processing steps, stored in an object. Each step is the result of one of the processing function 44 | * @returns the processed values to set on the datatable. 45 | */ 46 | displayHandler({ sorted, paged }: IDisplayHandlerParam): TMaybePromise>; 47 | /** 48 | * Check if the provided row contains the filter string in *any* column. 49 | * 50 | * @param row - The data row to search in. 51 | * @param filterString - The string to match in a column. 52 | * @param columns - The list of columns in the table. 53 | * @returns `true` if any column contains the searched string. 54 | */ 55 | rowMatches(row: TRow, filterString: string, columns: Array>): boolean; 56 | } 57 | -------------------------------------------------------------------------------- /src/classes/handlers/i-handler.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | 4 | /** 5 | * Represents the sort direction of a column, eg ascending or descending 6 | */ 7 | export const enum ESortDir { 8 | Asc = 'asc', 9 | Desc = 'desc', 10 | } 11 | 12 | export interface IDisplayHandlerResult { 13 | /** The actual rows to display */ 14 | rows: TRow[]; 15 | /** The total number of rows in the table. It counts also items on other pages. The pages in the pagination component are calculated using this value. */ 16 | totalRowCount: number; 17 | } 18 | 19 | export interface IDisplayHandlerParam { 20 | /** The original [[Datatable.data]] property of the datatable. */ 21 | source: TRow[] | TSource; 22 | /** The return value of [[Handler.filterHandler]]. */ 23 | filtered: TRow[] | TFiltered; 24 | /** The return value of [[Handler.sortHandler]]. */ 25 | sorted: TRow[] | TSorted; 26 | /** The return value of [[Handler.paginateHandler]]. */ 27 | paged: TRow[] | TPaged; 28 | } 29 | 30 | export type TFilterHandler = 31 | ( data: TIn, filters: string | string[], columns: Array> ) => TMaybePromise; 32 | export type TSortHandler = 33 | ( filteredData: TIn, sortColumn: Column | null, sortDir: ESortDir | null ) => TMaybePromise; 34 | export type TPaginateHandler = 35 | ( sortedData: TIn, perPage: number | null, pageNumber: number ) => TMaybePromise; 36 | export type TDisplayHandler = 37 | ( operationResults: IDisplayHandlerParam ) => TMaybePromise>; 38 | 39 | /** 40 | * This interface exposes methods used to manipulate table data, like filtering, sorting, or paginating. 41 | * You can implement this interface, or override [[DefaultHandler]]'s instance's members to customize the behavior of your [[VueDatatable]]. 42 | * Handlers are called in this order: filter, sort, paginate, display. 43 | * 44 | * @tutorial ajax-handler 45 | */ 46 | export interface IHandler { 47 | /** Filter the provided rows, checking if at least a cell contains one of the specified filters. It supports promises. Defaults to [[Handler.defaultFilterHandler]]. */ 48 | filterHandler: TFilterHandler; 49 | /** Sort the given rows depending on a specific column & sort order. It suports promises. Defaults to [[Handler.defaultSortHandler]]. */ 50 | sortHandler: TSortHandler; 51 | /** Split the rows list to display the requested page index. It supports promises. Defaults to [[Handler.defaultPaginateHandler]]. */ 52 | paginateHandler: TPaginateHandler; 53 | /** Handler to post-process the paginated data, and determine which data to actually display. It supports promises. Defaults to [[Handler.defaultDisplayHandler]]. */ 54 | displayHandler: TDisplayHandler; 55 | } 56 | -------------------------------------------------------------------------------- /tutorials/src/ajax-handler/demo.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import Vue from 'vue'; 3 | import { TColumnsDefinition, VuejsDatatableFactory } from 'vuejs-datatable'; 4 | 5 | import { colFieldToStr, formatUtcDate, ISpaceXLaunch, makeQueryStringFromObj } from '../utils'; 6 | 7 | VuejsDatatableFactory.registerTableType( 'ajaxtable', tableType => tableType 8 | .setFilterHandler( ( source, filter, columns ) => ( { 9 | // See https://documenter.getpostman.com/view/2025350/RWaEzAiG#json-field-masking 10 | filter: columns.map( col => colFieldToStr( col.field! ).replace( /\./g, '/' ) ).join( ',' ), 11 | } ) ) 12 | .setSortHandler( ( endpointDesc, sortColumn, sortDir ) => ( { 13 | ...endpointDesc, 14 | 15 | ...( sortColumn && sortDir ? { 16 | order: sortDir, 17 | sort: colFieldToStr( sortColumn.field! ).replace( /\./g, '/' ), 18 | } : {} ), 19 | } ) ) 20 | .setPaginateHandler( ( endpointDesc, perPage, pageIndex ) => ( { 21 | ...endpointDesc, 22 | 23 | ...( perPage !== null ? { 24 | limit: perPage || 10, 25 | offset: ( ( pageIndex - 1 ) * perPage ) || 0, 26 | } : {} ), 27 | } ) ) 28 | // Alias our process steps, because the source, here, is our API url, and paged is the complete query string 29 | .setDisplayHandler( async ( { source: baseEndPoint, paged: endpointDesc } ) => { 30 | const url = `${ baseEndPoint }?${ makeQueryStringFromObj( endpointDesc ) }`; 31 | 32 | const { 33 | // Data to display 34 | data, 35 | // Get the total number of matched items 36 | headers: { 'spacex-api-count': totalCount }, 37 | } = await axios.get( url ); 38 | 39 | return { 40 | rows: data, 41 | totalRowCount: parseInt( totalCount, 10 ), 42 | }; 43 | } ) ); 44 | 45 | const app = new Vue( { 46 | el: '#demo-app', 47 | data: { 48 | columns: [ 49 | { label: 'Flight number', field: 'flight_number' }, 50 | { label: 'Mission name', field: 'mission_name' }, 51 | { label: 'Launch date', field: 'launch_date_utc', representedAs: row => formatUtcDate( new Date( row.launch_date_utc ) ) }, 52 | { label: 'Rocket type', field: 'rocket.rocket_name', sortable: false }, 53 | { label: 'Launch site', field: 'launch_site.site_name_long', sortable: false }, 54 | { 55 | field: 'links.mission_patch_small', 56 | interpolate: true, 57 | label: 'Mission patch', 58 | representedAs: row => row.links.mission_patch_small ? 59 | `${ row.mission_name } patch` : 60 | `No patch for mission "${ row.mission_name }" available`, 61 | sortable: false, 62 | }, 63 | { 64 | field: 'links.reddit_campaign', 65 | interpolate: true, 66 | label: 'Reddit link', 67 | representedAs: row => `${ row.mission_name } Reddit thread`, 68 | sortable: false, 69 | }, 70 | ] as TColumnsDefinition, 71 | page: 1, 72 | 73 | apiUrlPast: 'https://api.spacexdata.com/v3/launches/past', 74 | apiUrlUpcoming: 'https://api.spacexdata.com/v3/launches/upcoming', 75 | }, 76 | } ); 77 | -------------------------------------------------------------------------------- /dist/classes/handlers/i-handler.d.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | /** 4 | * Represents the sort direction of a column, eg ascending or descending 5 | */ 6 | export declare const enum ESortDir { 7 | Asc = "asc", 8 | Desc = "desc" 9 | } 10 | export interface IDisplayHandlerResult { 11 | /** The actual rows to display */ 12 | rows: TRow[]; 13 | /** The total number of rows in the table. It counts also items on other pages. The pages in the pagination component are calculated using this value. */ 14 | totalRowCount: number; 15 | } 16 | export interface IDisplayHandlerParam { 17 | /** The original [[Datatable.data]] property of the datatable. */ 18 | source: TRow[] | TSource; 19 | /** The return value of [[Handler.filterHandler]]. */ 20 | filtered: TRow[] | TFiltered; 21 | /** The return value of [[Handler.sortHandler]]. */ 22 | sorted: TRow[] | TSorted; 23 | /** The return value of [[Handler.paginateHandler]]. */ 24 | paged: TRow[] | TPaged; 25 | } 26 | export declare type TFilterHandler = (data: TIn, filters: string | string[], columns: Array>) => TMaybePromise; 27 | export declare type TSortHandler = (filteredData: TIn, sortColumn: Column | null, sortDir: ESortDir | null) => TMaybePromise; 28 | export declare type TPaginateHandler = (sortedData: TIn, perPage: number | null, pageNumber: number) => TMaybePromise; 29 | export declare type TDisplayHandler = (operationResults: IDisplayHandlerParam) => TMaybePromise>; 30 | /** 31 | * This interface exposes methods used to manipulate table data, like filtering, sorting, or paginating. 32 | * You can implement this interface, or override [[DefaultHandler]]'s instance's members to customize the behavior of your [[VueDatatable]]. 33 | * Handlers are called in this order: filter, sort, paginate, display. 34 | * 35 | * @tutorial ajax-handler 36 | */ 37 | export interface IHandler { 38 | /** Filter the provided rows, checking if at least a cell contains one of the specified filters. It supports promises. Defaults to [[Handler.defaultFilterHandler]]. */ 39 | filterHandler: TFilterHandler; 40 | /** Sort the given rows depending on a specific column & sort order. It suports promises. Defaults to [[Handler.defaultSortHandler]]. */ 41 | sortHandler: TSortHandler; 42 | /** Split the rows list to display the requested page index. It supports promises. Defaults to [[Handler.defaultPaginateHandler]]. */ 43 | paginateHandler: TPaginateHandler; 44 | /** Handler to post-process the paginated data, and determine which data to actually display. It supports promises. Defaults to [[Handler.defaultDisplayHandler]]. */ 45 | displayHandler: TDisplayHandler; 46 | } 47 | -------------------------------------------------------------------------------- /dist/components/vue-datatable-pager/vue-datatable-pager.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { EPagerType } from '../../utils'; 3 | import { VueDatatable } from '../vue-datatable/vue-datatable'; 4 | import { TableType } from './../../classes'; 5 | /** 6 | * The component that is used to manage & change pages on a [[VueDatatable]]. 7 | */ 8 | export declare class VueDatatablePager> extends Vue { 9 | /** 10 | * The id of the associated [[VueDatatable]]. 11 | * 12 | * @vue-prop 13 | */ 14 | readonly table: string; 15 | /** 16 | * The kind of the pager 17 | * 18 | * @vue-prop 19 | */ 20 | readonly type: EPagerType; 21 | /** 22 | * The number of pages visible on each side (only for [[EPageType.Abbreviated]]) 23 | * 24 | * @vue-prop 25 | */ 26 | readonly sidesCount: number; 27 | get sidesIndexes(): number[]; 28 | private ptableInstance; 29 | get tableInstance(): VueDatatable; 30 | /** Returns `true` if the pager has an associated [[VueDatatable]] with some rows. */ 31 | get show(): boolean; 32 | /** The total number of rows in the associated [[VueDatatable]]. */ 33 | private get totalRows(); 34 | /** The total number of pages in the associated [[VueDatatable]]. */ 35 | totalPages: number; 36 | /** The current page index in the associated [[VueDatatable]]. */ 37 | page: number; 38 | /** HTML class on the wrapping `ul` around the pager buttons. */ 39 | get paginationClass(): string; 40 | /** HTML content of the previous page's button. */ 41 | get previousIcon(): string; 42 | /** HTML content of the next page's button. */ 43 | get nextIcon(): string; 44 | protected readonly tableType: TableType; 45 | get identifier(): string; 46 | /** 47 | * Try to link the pager with the table, or bind the `vuejs-datatable::ready` event to watch for new tables addition. 48 | */ 49 | created(): void; 50 | /** 51 | * Link the pager with the table, assign to the table some properties, and trigger an event on the table. 52 | * 53 | * @vue-event VueDatatable.vuejs-datatable::pager-bound 54 | * @vue-event VueDatatable.vuejs-datatable::page-count-changed 55 | * @vue-event VueDatatable.vuejs-datatable::page-changed 56 | * @param tableName - The name of the table to bind the pager with. 57 | * @returns `true` if the link is succesfull, or `false` if it could not find a table to associate with. 58 | */ 59 | private linkWithTable; 60 | /** 61 | * Callback of the `vuejs-datatable::page-count-changed` event, setting the total pages count. 62 | * 63 | * @param totalPages - The new total pages count emitted by the datatable. 64 | */ 65 | private onPageCountChanged; 66 | /** 67 | * Callback of the `vuejs-datatable::page-changed` event, setting the page index. 68 | * 69 | * @param page - The page index emitted by the datatable. 70 | */ 71 | private onPageChanged; 72 | /** 73 | * Propagate new page from the pager to the datatable. 74 | * 75 | * @param page - The page index emitted by sub buttons. 76 | */ 77 | private onSetPage; 78 | } 79 | -------------------------------------------------------------------------------- /dist/themes/classes/handlers/i-handler.d.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | /** 4 | * Represents the sort direction of a column, eg ascending or descending 5 | */ 6 | export declare const enum ESortDir { 7 | Asc = "asc", 8 | Desc = "desc" 9 | } 10 | export interface IDisplayHandlerResult { 11 | /** The actual rows to display */ 12 | rows: TRow[]; 13 | /** The total number of rows in the table. It counts also items on other pages. The pages in the pagination component are calculated using this value. */ 14 | totalRowCount: number; 15 | } 16 | export interface IDisplayHandlerParam { 17 | /** The original [[Datatable.data]] property of the datatable. */ 18 | source: TRow[] | TSource; 19 | /** The return value of [[Handler.filterHandler]]. */ 20 | filtered: TRow[] | TFiltered; 21 | /** The return value of [[Handler.sortHandler]]. */ 22 | sorted: TRow[] | TSorted; 23 | /** The return value of [[Handler.paginateHandler]]. */ 24 | paged: TRow[] | TPaged; 25 | } 26 | export declare type TFilterHandler = (data: TIn, filters: string | string[], columns: Array>) => TMaybePromise; 27 | export declare type TSortHandler = (filteredData: TIn, sortColumn: Column | null, sortDir: ESortDir | null) => TMaybePromise; 28 | export declare type TPaginateHandler = (sortedData: TIn, perPage: number | null, pageNumber: number) => TMaybePromise; 29 | export declare type TDisplayHandler = (operationResults: IDisplayHandlerParam) => TMaybePromise>; 30 | /** 31 | * This interface exposes methods used to manipulate table data, like filtering, sorting, or paginating. 32 | * You can implement this interface, or override [[DefaultHandler]]'s instance's members to customize the behavior of your [[VueDatatable]]. 33 | * Handlers are called in this order: filter, sort, paginate, display. 34 | * 35 | * @tutorial ajax-handler 36 | */ 37 | export interface IHandler { 38 | /** Filter the provided rows, checking if at least a cell contains one of the specified filters. It supports promises. Defaults to [[Handler.defaultFilterHandler]]. */ 39 | filterHandler: TFilterHandler; 40 | /** Sort the given rows depending on a specific column & sort order. It suports promises. Defaults to [[Handler.defaultSortHandler]]. */ 41 | sortHandler: TSortHandler; 42 | /** Split the rows list to display the requested page index. It supports promises. Defaults to [[Handler.defaultPaginateHandler]]. */ 43 | paginateHandler: TPaginateHandler; 44 | /** Handler to post-process the paginated data, and determine which data to actually display. It supports promises. Defaults to [[Handler.defaultDisplayHandler]]. */ 45 | displayHandler: TDisplayHandler; 46 | } 47 | -------------------------------------------------------------------------------- /dist/themes/components/vue-datatable-pager/vue-datatable-pager.d.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { EPagerType } from '../../utils'; 3 | import { VueDatatable } from '../vue-datatable/vue-datatable'; 4 | import { TableType } from './../../classes'; 5 | /** 6 | * The component that is used to manage & change pages on a [[VueDatatable]]. 7 | */ 8 | export declare class VueDatatablePager> extends Vue { 9 | /** 10 | * The id of the associated [[VueDatatable]]. 11 | * 12 | * @vue-prop 13 | */ 14 | readonly table: string; 15 | /** 16 | * The kind of the pager 17 | * 18 | * @vue-prop 19 | */ 20 | readonly type: EPagerType; 21 | /** 22 | * The number of pages visible on each side (only for [[EPageType.Abbreviated]]) 23 | * 24 | * @vue-prop 25 | */ 26 | readonly sidesCount: number; 27 | get sidesIndexes(): number[]; 28 | private ptableInstance; 29 | get tableInstance(): VueDatatable; 30 | /** Returns `true` if the pager has an associated [[VueDatatable]] with some rows. */ 31 | get show(): boolean; 32 | /** The total number of rows in the associated [[VueDatatable]]. */ 33 | private get totalRows(); 34 | /** The total number of pages in the associated [[VueDatatable]]. */ 35 | totalPages: number; 36 | /** The current page index in the associated [[VueDatatable]]. */ 37 | page: number; 38 | /** HTML class on the wrapping `ul` around the pager buttons. */ 39 | get paginationClass(): string; 40 | /** HTML content of the previous page's button. */ 41 | get previousIcon(): string; 42 | /** HTML content of the next page's button. */ 43 | get nextIcon(): string; 44 | protected readonly tableType: TableType; 45 | get identifier(): string; 46 | /** 47 | * Try to link the pager with the table, or bind the `vuejs-datatable::ready` event to watch for new tables addition. 48 | */ 49 | created(): void; 50 | /** 51 | * Link the pager with the table, assign to the table some properties, and trigger an event on the table. 52 | * 53 | * @vue-event VueDatatable.vuejs-datatable::pager-bound 54 | * @vue-event VueDatatable.vuejs-datatable::page-count-changed 55 | * @vue-event VueDatatable.vuejs-datatable::page-changed 56 | * @param tableName - The name of the table to bind the pager with. 57 | * @returns `true` if the link is succesfull, or `false` if it could not find a table to associate with. 58 | */ 59 | private linkWithTable; 60 | /** 61 | * Callback of the `vuejs-datatable::page-count-changed` event, setting the total pages count. 62 | * 63 | * @param totalPages - The new total pages count emitted by the datatable. 64 | */ 65 | private onPageCountChanged; 66 | /** 67 | * Callback of the `vuejs-datatable::page-changed` event, setting the page index. 68 | * 69 | * @param page - The page index emitted by the datatable. 70 | */ 71 | private onPageChanged; 72 | /** 73 | * Propagate new page from the pager to the datatable. 74 | * 75 | * @param page - The page index emitted by sub buttons. 76 | */ 77 | private onSetPage; 78 | } 79 | -------------------------------------------------------------------------------- /tutorials/build/rollupize.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: no-implicit-dependencies 2 | import { F_OK } from 'constants'; 3 | import { promises } from 'fs'; 4 | // tslint:disable-next-line: no-implicit-dependencies 5 | import jscc from 'jscc'; 6 | // tslint:disable-next-line: no-implicit-dependencies 7 | import { memoize } from 'lodash'; 8 | import { isAbsolute, resolve } from 'path'; 9 | // tslint:disable-next-line: no-implicit-dependencies 10 | import { InputOptions, OutputOptions, rollup } from 'rollup'; 11 | 12 | import { generateWrappedScript, readFile } from './exec-script-transform'; 13 | 14 | const buildRollup = async ( input: InputOptions, output: OutputOptions ) => { 15 | const confBundle = await rollup( input ); 16 | const outBundle = await confBundle.generate( output ); 17 | const { output: [{ code }] } = outBundle; 18 | return { 19 | code, 20 | outBundle, 21 | }; 22 | }; 23 | const loadMjsInJs = memoize( async ( filepath: string ) => { 24 | // See https://github.com/rollup/rollup/blob/master/bin/src/run/loadConfigFile.ts 25 | const { code } = await buildRollup( { 26 | external: id => ( !id.startsWith( '.' ) && !isAbsolute( id ) ) || id.slice( -5, id.length ) === '.json', 27 | input: filepath, 28 | onwarn: () => ( void( 0 ) ), 29 | treeshake: false, 30 | }, { format: 'cjs' } ); 31 | const codeWithExports = `let exports = {};${code};exports;`; 32 | 33 | // Swap require for a custom one 34 | const requireSave = require; 35 | // @ts-ignore 36 | require = ( path: string ) => { 37 | if ( path.startsWith( '.' ) ) { 38 | return requireSave( resolve( __dirname, '../..', path ) ); 39 | } 40 | return requireSave( path ); 41 | }; 42 | try { 43 | // tslint:disable-next-line: no-eval 44 | return eval( codeWithExports ); 45 | } finally { 46 | require = requireSave; 47 | } 48 | } ); 49 | const getRollupPlugins = async ( iife = true ) => { 50 | const rollupOpts = await loadMjsInJs( resolve( __dirname, '../../rollup.config.js' ) ); 51 | const pluginsFactory = rollupOpts.getPlugins; 52 | return pluginsFactory( iife, 'demo' ); 53 | }; 54 | 55 | export const rollupize = async ( sourceFile: string ) => { 56 | // Check if the script file exist. If it does not, return `undefined` 57 | try { 58 | await promises.access( sourceFile, F_OK ); 59 | } catch { 60 | return undefined; 61 | } 62 | 63 | // Wrap scripts for single execution 64 | const execFile = await generateWrappedScript( sourceFile ); 65 | 66 | try { 67 | // generate code 68 | const { code: outCodeDisplay } = jscc( await readFile( sourceFile ), sourceFile, { values: { _DISPLAY: '1' }} ); 69 | const { code: outCodeExec } = await buildRollup( { 70 | external: [ 'vuejs-datatable', 'axios', 'lodash', 'vue' ], 71 | input: execFile, 72 | plugins: await getRollupPlugins( true ), 73 | }, { 74 | format: 'iife', 75 | globals: { 76 | 'axios': 'axios', 77 | 'lodash': '_', 78 | 'vue': 'Vue', 79 | 'vuejs-datatable': 'VuejsDatatable', 80 | }, 81 | } ); 82 | 83 | return { 84 | display: outCodeDisplay, 85 | exec: outCodeExec, 86 | }; 87 | } catch ( e ) { 88 | // tslint:disable-next-line: no-console 89 | console.error( `An error occured in the transformation of ${ sourceFile }:`, e ); 90 | throw e; 91 | } finally { 92 | // await unlink( tempFile ); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /__tests__/integration/table-with-pager.ts: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from '@vue/test-utils'; 2 | 3 | import { TableType } from '../../src/classes'; 4 | import { DefaultHandler } from '../../src/classes/handlers/default-handler'; 5 | import { Settings } from '../../src/classes/settings'; 6 | import { VueDatatablePager } from '../../src/components/vue-datatable-pager/vue-datatable-pager'; 7 | import { VueDatatable } from '../../src/components/vue-datatable/vue-datatable'; 8 | import { mountVueDatatable, mountVueDatatablePager } from '../helpers/mount-mixin-components'; 9 | 10 | const localVue = createLocalVue(); 11 | 12 | beforeEach( () => { 13 | jest.clearAllMocks(); 14 | localVue.prototype.$datatables = {}; 15 | } ); 16 | describe( 'Wait for pager', () => { 17 | it( 'Table should not process rows while waiting for the pager', () => { 18 | const processRowsMock = jest.spyOn( ( VueDatatable as any ).extendOptions.methods, 'processRows' as any ); 19 | const tableType = new TableType( 'bar' ); 20 | const table = mountVueDatatable( false, tableType, { 21 | localVue, 22 | propsData: { 23 | columns: [], 24 | data: [], 25 | name: 'foo', 26 | waitForPager: true, 27 | }, 28 | } ); 29 | 30 | expect( processRowsMock ).not.toHaveBeenCalled(); 31 | } ); 32 | it( 'Table should process rows once the pager is associated.', done => { 33 | const processRowsMock = jest.spyOn( ( VueDatatable as any ).extendOptions.methods, 'processRows' as any ); 34 | const tableType = new TableType( 'bar' ); 35 | const table = mountVueDatatable( false, tableType, { 36 | localVue, 37 | propsData: { 38 | columns: [], 39 | data: [], 40 | name: 'bar', 41 | waitForPager: true, 42 | }, 43 | } ); 44 | const pager = mountVueDatatablePager( false, tableType, { 45 | localVue, 46 | propsData: { 47 | table: 'bar', 48 | }, 49 | } ); 50 | 51 | table.vm.$nextTick( () => { 52 | try { 53 | expect( processRowsMock ).toHaveBeenCalledTimes( 1 ); 54 | } catch ( e ) { 55 | done( e ); 56 | } 57 | done(); 58 | } ); 59 | } ); 60 | } ); 61 | describe( 'No wait for pager', () => { 62 | it( 'Table should process rows right after initialization', done => { 63 | const processRowsMock = jest.spyOn( ( VueDatatable as any ).extendOptions.methods, 'processRows' as any ); 64 | const tableType = new TableType( 'bar' ); 65 | const table = mountVueDatatable( false, tableType, { 66 | localVue, 67 | propsData: { 68 | columns: [], 69 | data: [], 70 | name: 'foo', 71 | }, 72 | } ); 73 | 74 | table.vm.$nextTick( () => { 75 | try { 76 | expect( processRowsMock ).toHaveBeenCalledTimes( 1 ); 77 | } catch ( e ) { 78 | done( e ); 79 | } 80 | done(); 81 | } ); 82 | } ); 83 | it( 'Table should process rows once after pager declaration', done => { 84 | const processRowsMock = jest.spyOn( ( VueDatatable as any ).extendOptions.methods, 'processRows' as any ); 85 | const tableType = new TableType( 'bar' ); 86 | const table = mountVueDatatable( false, tableType, { 87 | localVue, 88 | propsData: { 89 | columns: [], 90 | data: [], 91 | name: 'foo', 92 | }, 93 | } ); 94 | const pager = mountVueDatatablePager( false, tableType, { 95 | localVue, 96 | propsData: { 97 | table: 'foo', 98 | }, 99 | } ); 100 | 101 | table.vm.$nextTick( () => { 102 | try { 103 | expect( processRowsMock ).toHaveBeenCalledTimes( 1 ); 104 | } catch ( e ) { 105 | done( e ); 106 | } 107 | done(); 108 | } ); 109 | } ); 110 | } ); 111 | -------------------------------------------------------------------------------- /dist/classes/settings.d.ts: -------------------------------------------------------------------------------- 1 | import { Path } from 'object-path'; 2 | /** 3 | * @tutorial custom-theme 4 | */ 5 | export interface ISettingsProperties { 6 | /** The classes to apply on the `table` tag itself. */ 7 | table: { 8 | class: string; 9 | row: { 10 | /** The classes to apply on each row (`tr` tag). */ 11 | class: string; 12 | }; 13 | sorting: { 14 | /** The HTML representing the sort icon when the column isn't sorted. */ 15 | sortNone: string; 16 | /** The HTML representing the sort icon when sorting the column ascending. */ 17 | sortAsc: string; 18 | /** The HTML representing the sort icon when sorting the column descending. */ 19 | sortDesc: string; 20 | }; 21 | }; 22 | pager: { 23 | classes: { 24 | /** The class to apply on the pager's `ul` tag. */ 25 | pager: string; 26 | /** The class to apply on the page's `li` tag. */ 27 | li: string; 28 | /** The class to apply on the current page's `li` tag. */ 29 | selected: string; 30 | /** The class to apply on a disabled page's `li` tag. */ 31 | disabled: string; 32 | }; 33 | icons: { 34 | /** The HTML representing the *Previous page* icon. */ 35 | previous: string; 36 | /** The HTML representing the *Next page* icon. */ 37 | next: string; 38 | }; 39 | }; 40 | } 41 | export declare type DeepPartial = { 42 | [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends ReadonlyArray ? ReadonlyArray> : DeepPartial; 43 | }; 44 | /** 45 | * @summary Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 46 | * @description Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 47 | * Create a new instance of this class & customize it to use different CSS frameworks. 48 | * No default style is set. See [[tutorial:custom-theme]] for more infos on customizing styles. 49 | * To edit settings contained by an instance of this class, either edit the [[Settings.properties]] object, or use the [[Settings.merge]] method. 50 | * 51 | * @tutorial custom-theme Cutomize your tables for another CSS framework or your own styling. 52 | */ 53 | export declare class Settings { 54 | /** Tree of settings values. */ 55 | private properties; 56 | /** 57 | * Get a value at a specific path. 58 | * 59 | * @param path - Path to the value to get. 60 | * @returns the value at the specified path 61 | */ 62 | get(path: Path): any; 63 | /** 64 | * Defines a value at a specific path 65 | * 66 | * @param path - Path to the value to set. 67 | * @param value - New value to set. 68 | * @returns `this` for chaining. 69 | */ 70 | set(path: Path, value: any): this; 71 | /** 72 | * Merges a new settings object within the Settings instance. 73 | * 74 | * @param settings - New settings object to merge with the current object of the Settings instance. 75 | * @returns `this` for chaining. 76 | */ 77 | merge(settings: DeepPartial): this; 78 | /** 79 | * Merges two objects deeply, and return the 1st parameter once transformed. 80 | * 81 | * @param obj1 - The base item to merge, which will be returned. 82 | * @param obj2 - The object to inject into `obj1`. 83 | * @returns The first object once merged. 84 | */ 85 | static mergeObjects(obj1: T, obj2: DeepPartial): T; 86 | } 87 | -------------------------------------------------------------------------------- /dist/themes/classes/settings.d.ts: -------------------------------------------------------------------------------- 1 | import { Path } from 'object-path'; 2 | /** 3 | * @tutorial custom-theme 4 | */ 5 | export interface ISettingsProperties { 6 | /** The classes to apply on the `table` tag itself. */ 7 | table: { 8 | class: string; 9 | row: { 10 | /** The classes to apply on each row (`tr` tag). */ 11 | class: string; 12 | }; 13 | sorting: { 14 | /** The HTML representing the sort icon when the column isn't sorted. */ 15 | sortNone: string; 16 | /** The HTML representing the sort icon when sorting the column ascending. */ 17 | sortAsc: string; 18 | /** The HTML representing the sort icon when sorting the column descending. */ 19 | sortDesc: string; 20 | }; 21 | }; 22 | pager: { 23 | classes: { 24 | /** The class to apply on the pager's `ul` tag. */ 25 | pager: string; 26 | /** The class to apply on the page's `li` tag. */ 27 | li: string; 28 | /** The class to apply on the current page's `li` tag. */ 29 | selected: string; 30 | /** The class to apply on a disabled page's `li` tag. */ 31 | disabled: string; 32 | }; 33 | icons: { 34 | /** The HTML representing the *Previous page* icon. */ 35 | previous: string; 36 | /** The HTML representing the *Next page* icon. */ 37 | next: string; 38 | }; 39 | }; 40 | } 41 | export declare type DeepPartial = { 42 | [P in keyof T]?: T[P] extends Array ? Array> : T[P] extends ReadonlyArray ? ReadonlyArray> : DeepPartial; 43 | }; 44 | /** 45 | * @summary Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 46 | * @description Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 47 | * Create a new instance of this class & customize it to use different CSS frameworks. 48 | * No default style is set. See [[tutorial:custom-theme]] for more infos on customizing styles. 49 | * To edit settings contained by an instance of this class, either edit the [[Settings.properties]] object, or use the [[Settings.merge]] method. 50 | * 51 | * @tutorial custom-theme Cutomize your tables for another CSS framework or your own styling. 52 | */ 53 | export declare class Settings { 54 | /** Tree of settings values. */ 55 | private properties; 56 | /** 57 | * Get a value at a specific path. 58 | * 59 | * @param path - Path to the value to get. 60 | * @returns the value at the specified path 61 | */ 62 | get(path: Path): any; 63 | /** 64 | * Defines a value at a specific path 65 | * 66 | * @param path - Path to the value to set. 67 | * @param value - New value to set. 68 | * @returns `this` for chaining. 69 | */ 70 | set(path: Path, value: any): this; 71 | /** 72 | * Merges a new settings object within the Settings instance. 73 | * 74 | * @param settings - New settings object to merge with the current object of the Settings instance. 75 | * @returns `this` for chaining. 76 | */ 77 | merge(settings: DeepPartial): this; 78 | /** 79 | * Merges two objects deeply, and return the 1st parameter once transformed. 80 | * 81 | * @param obj1 - The base item to merge, which will be returned. 82 | * @param obj2 - The object to inject into `obj1`. 83 | * @returns The first object once merged. 84 | */ 85 | static mergeObjects(obj1: T, obj2: DeepPartial): T; 86 | } 87 | -------------------------------------------------------------------------------- /ci/run-ci.ts: -------------------------------------------------------------------------------- 1 | 2 | import { execSync } from 'child_process'; 3 | // tslint:disable-next-line: no-implicit-dependencies 4 | import { prerelease } from 'semver'; 5 | 6 | import { IRepoConfig, release, runTests } from './actions'; 7 | import { logDebug, logError, logInfo, logStep } from './log'; 8 | import { execStream } from './spawn-exec'; 9 | 10 | // tslint:disable-next-line: no-var-requires 11 | const renovateConfig = require( '../renovate.json' ); 12 | // tslint:disable-next-line: no-var-requires 13 | const packageFile = require( '../package.json' ); 14 | const currentBranch = process.env.TRAVIS_BRANCH || execSync( "git branch | grep \\* | cut -d ' ' -f2" ).toString().trim(); 15 | const lastCommitMessage = execSync( 'git show -s --format=%B' ).toString().trim(); 16 | const repoConf: IRepoConfig = { 17 | gitUser: { 18 | email: 'travis@travis-ci.org', 19 | name: 'Travis CI', 20 | }, 21 | repoUrl: packageFile.repository.url, 22 | }; 23 | const committer = execSync( "git show -s --format='%ae'" ).toString().trim(); 24 | 25 | const main = async () => { 26 | const actions: Array<{label: string; genFn: () => Promise}> = []; 27 | 28 | actions.push( { 29 | genFn: async () => { 30 | if ( !await runTests() ) { 31 | logError( 'Stopping here since tests failed.' ); 32 | process.exit( 1 ); 33 | } 34 | }, 35 | label: 'Running tests', 36 | } ); 37 | 38 | actions.push( { 39 | genFn: async () => { 40 | if ( committer === repoConf.gitUser.email ) { 41 | logInfo( 'Travis CI did the last commit. Abort.' ); 42 | process.exit( 0 ); 43 | } 44 | }, 45 | label: 'Check if previous committer is Travis CI', 46 | } ); 47 | 48 | if ( currentBranch === 'staging' && process.env.CC_SAMPLE === 'true' ) { 49 | const messageWithCurrentTag = packageFile['standard-version'].releaseCommitMessageFormat.replace( '{{previousTag}}', packageFile.version ); 50 | if ( lastCommitMessage.startsWith( renovateConfig.commitMessagePrefix ) ) { 51 | actions.push( { 52 | genFn: async () => { 53 | await execStream( 'git checkout staging' ); 54 | 55 | // Bump version 56 | const prereleaseSegments = prerelease( packageFile.version ); 57 | await release( repoConf, 'chore: 🤖 Applying renovate update', messageWithCurrentTag, prereleaseSegments ? prereleaseSegments[0] : undefined ); 58 | }, 59 | label: 'Handling renovate update', 60 | } ); 61 | 62 | } else { 63 | const matchPrerelease = lastCommitMessage.match( /^chore: 🤖 Prepare (\w+) prerelease$/ ); 64 | if ( matchPrerelease ) { 65 | actions.push( { 66 | genFn: () => release( repoConf, 'chore: 🤖 Release manual prerelease', messageWithCurrentTag, matchPrerelease[1] ), 67 | label: 'Handling prerelease', 68 | } ); 69 | 70 | } else if ( lastCommitMessage === 'chore: 🤖 Prepare release' ) { 71 | actions.push( { 72 | genFn: () => release( repoConf, 'chore: 🤖 Release manual release', messageWithCurrentTag ), 73 | label: 'Handling release', 74 | } ); 75 | } else { 76 | actions.push( { 77 | genFn: async () => { 78 | logDebug( 'Had following infos: ' + JSON.stringify( { lastCommitMessage, renovateConfig } ) ); 79 | }, 80 | label: 'No deploy action to do. Finished', 81 | } ); 82 | } 83 | } 84 | } else { 85 | actions.push( { 86 | genFn: async () => { 87 | logDebug( 'Had following infos: ' + JSON.stringify( { currentBranch, CC_SAMPLE: process.env.CC_SAMPLE } ) ); 88 | }, 89 | label: 'No deploy action to do. Finished', 90 | } ); 91 | } 92 | 93 | for ( const { label, genFn } of actions ) { 94 | logStep( label ); 95 | await genFn(); 96 | } 97 | }; 98 | 99 | main() 100 | .catch( e => { 101 | // tslint:disable-next-line: no-console 102 | console.error( e ); 103 | process.exit( ( e as any ).code || 1 ); 104 | } ); 105 | -------------------------------------------------------------------------------- /tutorials/build/build-examples.ts: -------------------------------------------------------------------------------- 1 | import { promises, readdirSync, statSync } from 'fs'; 2 | const { readFile, writeFile } = promises; 3 | import chalk from 'chalk'; 4 | // tslint:disable-next-line: no-implicit-dependencies 5 | import { ensureDirSync } from 'fs-extra'; 6 | import { join, resolve } from 'path'; 7 | 8 | import { rewriteBaseVueApp } from './build-utils'; 9 | import { rollupize } from './rollupize'; 10 | 11 | const tutorialSourcesDir = resolve( __dirname, '../src' ); 12 | const tutorialOutDir = resolve( __dirname, '../dist' ); 13 | 14 | const dirs = readdirSync( tutorialSourcesDir ) 15 | // Only dirs 16 | .filter( file => { 17 | const stat = statSync( join( tutorialSourcesDir, file ) ); 18 | return stat.isDirectory(); 19 | } ); 20 | 21 | // tslint:disable-next-line: no-console 22 | const printDelimiter = () => console.log( chalk.bold( '--------------------' ) ); 23 | 24 | const allOperations = dirs.map( ( dir, index ) => async () => { 25 | if ( index !== 0 ) { 26 | printDelimiter(); 27 | } 28 | const startDate = Date.now(); 29 | // tslint:disable-next-line: no-console 30 | console.log( `Starting build of tutorial "${chalk.green( dir )}"` ); 31 | const absDir = resolve( tutorialSourcesDir, dir ); 32 | const jsFile = join( absDir, 'demo.ts' ); 33 | const htmlFile = join( absDir, 'index.md' ); 34 | const [ scripts, mdTutoCst ] = await Promise.all( [ 35 | rollupize( jsFile ), 36 | readFile( htmlFile, 'utf-8' ), 37 | ] ); 38 | let mdTuto = mdTutoCst!; 39 | 40 | ensureDirSync( tutorialOutDir ); 41 | 42 | const htmlAppCodeMatch = mdTuto.match( /(]+id="demo-app".+?<\/div>)(?=\s+## Code)/gms ); 43 | if ( htmlAppCodeMatch && htmlAppCodeMatch.length >= 1 ) { 44 | const htmlAppCode = htmlAppCodeMatch[0]; 45 | mdTuto = rewriteBaseVueApp( mdTuto, htmlAppCode, dir ); 46 | const toMdCodeBlock = ( type: string, code: string ) => 47 | `\`\`\`${ type }\n${ code.replace( /\t/g, ' ' ) }\n\`\`\``; 48 | mdTuto = mdTuto.replace( '```HTML```', toMdCodeBlock( 'html', htmlAppCode ) ); 49 | if ( scripts ) { 50 | mdTuto = mdTuto.replace( '```TS```', toMdCodeBlock( 'ts', scripts.display ) ); 51 | } 52 | 53 | if ( scripts ) { 54 | try { 55 | const demoScriptName = `demo-${dir}.js`; 56 | 57 | mdTuto = mdTuto.replace( '', `` ); 58 | await writeFile( resolve( tutorialOutDir, demoScriptName ), scripts.exec, 'UTF-8' ); 59 | } catch ( e ) { 60 | // tslint:disable-next-line: no-console 61 | console.warn( `For demo "${ dir }":`, e ); 62 | } 63 | } else { 64 | throw new Error(); 65 | } 66 | } 67 | 68 | mdTuto = mdTuto.replace( 69 | '', 70 | ` 71 | 72 | 73 | ` ); 74 | 75 | await writeFile( resolve( tutorialOutDir, `${ dir }.md` ), mdTuto, 'UTF-8' ); 76 | const deltaTime = ( Date.now() - startDate ) / 1000; 77 | // tslint:disable-next-line: no-console 78 | console.log( `Ended build of tutorial "${chalk.green( dir )}" in ${chalk.yellow( deltaTime + 's' )}` ); 79 | } ); 80 | 81 | allOperations.reduce( ( acc, op ) => acc.then( op ), Promise.resolve() ).then( () => { 82 | printDelimiter(); 83 | // tslint:disable-next-line: no-console 84 | console.info( 'Tutorials transformed !' ); 85 | process.exit( 0 ); 86 | } ).catch( e => { 87 | // tslint:disable-next-line: no-console 88 | console.error( 'An error occured while building the docs:', e ); 89 | process.exit( 1 ); 90 | } ); 91 | -------------------------------------------------------------------------------- /__tests__/e2e/specs/simple.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import '../../../tutorials/assets/rows'; 4 | import { IPeople } from '../../../tutorials/src/utils'; 5 | 6 | declare const rows: IPeople[]; 7 | 8 | describe( 'Basic table', () => { 9 | const appId = '#demo-app'; 10 | 11 | it( 'Row format should be respected', () => { 12 | cy.visit( '__tests__/e2e/mocks/simple.html' ); 13 | 14 | // Test initial state 15 | cy.get( appId + ' table tbody tr:nth-child(1)' ).within( () => { 16 | cy.get( 'td:nth-child(1)' ).contains( '1' ); 17 | cy.get( 'td:nth-child(2)' ).contains( 'dprice0@blogs.com' ); 18 | cy.get( 'td:nth-child(3)' ).contains( '3 Toban Park, Pocatello, Idaho' ); 19 | } ); 20 | } ); 21 | it( 'Pagination should work with default values', () => { 22 | cy.visit( '__tests__/e2e/mocks/simple.html' ); 23 | 24 | // Test initial state 25 | cy.get( appId + ' table tbody' ) 26 | .children() 27 | .should( 'have.length', 25 ) 28 | 29 | .get( appId + ' table tbody tr:nth-child(1) td:first-child' ) 30 | .contains( '1' ) 31 | 32 | .get( appId + ' table + nav ul' ) 33 | .children() 34 | .should( 'have.length', 4 ) 35 | 36 | .get( appId + ' table + nav ul li:nth-child(1)' ) 37 | .should( 'have.class', 'active' ); 38 | 39 | // Change page 40 | cy.get( appId + ' table + nav ul li:nth-child(2)' ) 41 | .click(); 42 | 43 | // Check new state 44 | cy.get( appId + ' table tbody' ) 45 | .children() 46 | .should( 'have.length', 25 ) 47 | 48 | .get( appId + ' table tbody tr:nth-child(1) td:first-child' ) 49 | .contains( '26' ) 50 | 51 | .get( appId + ' table + nav ul li:nth-child(1)' ) 52 | .should( 'not.have.class', 'active' ) 53 | 54 | .get( appId + ' table + nav ul li:nth-child(2)' ) 55 | .should( 'have.class', 'active' ); 56 | } ); 57 | it( 'Filtering should hide unmatched elements', () => { 58 | cy.visit( '__tests__/e2e/mocks/simple.html' ); 59 | 60 | // Type some filter 61 | cy.get( '#filter' ) 62 | .type( 'cs' ); 63 | 64 | cy.get( appId + ' table tbody' ) 65 | .children() 66 | .should( 'have.length', 6 ) 67 | 68 | .get( `${appId} table tbody tr` ) 69 | .each( e => { 70 | cy.wrap( e ).should( 'contain.html', 'cs' ); 71 | } ); 72 | 73 | cy.get( appId + ' table + nav ul' ) 74 | .children() 75 | .should( 'have.length', 1 ); 76 | } ); 77 | it( 'Sorting should reorder elements', () => { 78 | cy.visit( '__tests__/e2e/mocks/simple.html' ); 79 | let prevCellVal: string | undefined; 80 | 81 | // Change sort 82 | cy.get( 'th:nth-child(2)' ) 83 | .click(); 84 | 85 | cy.get( appId + ' table tbody' ) 86 | .children() 87 | .each( row => { 88 | const cell = row.children()[1]; 89 | const cellVal = cell.innerHTML.trim(); 90 | if ( prevCellVal ) { 91 | cy.wrap( cellVal ).should( 'be.above', prevCellVal ); 92 | } 93 | prevCellVal = cellVal; 94 | } ).then( () => { 95 | prevCellVal = undefined; 96 | } ); 97 | 98 | // Reverse sort 99 | cy.get( 'th:nth-child(2)' ) 100 | .click(); 101 | 102 | cy.get( appId + ' table tbody' ) 103 | .children() 104 | .each( row => { 105 | const cell = row.children()[1]; 106 | const cellVal = cell.innerHTML.trim(); 107 | if ( prevCellVal ) { 108 | cy.wrap( cellVal ).should( 'be.below', prevCellVal ); 109 | } 110 | prevCellVal = cellVal; 111 | } ).then( () => { 112 | prevCellVal = undefined; 113 | } ); 114 | 115 | // Disable sort 116 | cy.get( 'th:nth-child(2)' ) 117 | .click(); 118 | 119 | cy.get( appId + ' table tbody' ) 120 | .children() 121 | .each( row => { 122 | const cell = row.children()[0]; 123 | const cellVal = cell.innerHTML.trim(); 124 | if ( prevCellVal ) { 125 | cy.wrap( parseInt( cellVal, 10 ) ).should( 'be.above', parseInt( prevCellVal, 10 ) ); 126 | } 127 | prevCellVal = cellVal; 128 | } ).then( () => { 129 | prevCellVal = undefined; 130 | } ); 131 | } ); 132 | } ); 133 | -------------------------------------------------------------------------------- /dist/classes/datatable-factory.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { PluginObject } from 'vue'; 2 | import { TableType } from './table-type'; 3 | /** 4 | * Registers Vuejs-Datatable components globally in VueJS. 5 | * 6 | * @example 7 | * import { DatatableFactory } from 'vuejs-datatable'; 8 | * const myDatatableFactory = new DatatableFactory() 9 | * .registerTableType( new TableType( 'my-table', {}) ) 10 | * Vue.use( myDatatableFactory ); 11 | */ 12 | export declare class DatatableFactory implements PluginObject { 13 | /** A reference to the Vue instance the plugin is installed in. It may be used to check if the factory was already installed */ 14 | private vueInstance?; 15 | /** Registry of declared table types. */ 16 | private readonly tableTypes; 17 | /** The default table type to use if no other configuration was provided. */ 18 | private readonly defaultTableType; 19 | /** 20 | * Initialize the default factory 21 | */ 22 | constructor(); 23 | /** 24 | * Get a table type by its identifier. 25 | * 26 | * @param id - The identifier of the table type. If not provided, it will default to the default table type. 27 | * @returns the table type registered with that identifier. 28 | */ 29 | getTableType(id?: string): TableType | undefined; 30 | /** 31 | * Controls the use of the default table type. 32 | * 33 | * @param use - `true` to use the default type, `false` otherwise. 34 | * @returns `this` for chaining. 35 | */ 36 | useDefaultType(use: boolean): this; 37 | /** 38 | * Check if the factory uses the default table type. 39 | * 40 | * @returns a boolean indicating if the factory uses the default table type. 41 | */ 42 | useDefaultType(): boolean; 43 | /** 44 | * Creates a new table type with a specified prefix, that you can customize using a callback. 45 | * 46 | * @param nameOrTableType - The name of the component to register, or a [[TableType]] object. 47 | * @param callback - An optional function to execute, that configures the newly created [[TableType]]. It takes a single parameter: the newly created [[TableType]], and should 48 | * return the transformed table type. 49 | * @returns `this` for chaining. 50 | */ 51 | registerTableType(nameOrTableType: string | TableType, callback?: (tableType: TableType) => TableType | undefined): this; 52 | /** 53 | * Creates a new table type with a specified prefix, that you can customize using a callback. 54 | * 55 | * @param nameOrTableType - The name of the component to register, or a [[TableType]] object. 56 | * @returns `this` for chaining. 57 | */ 58 | deregisterTableType(nameOrTableType: string | TableType): this; 59 | /** 60 | * Declares global components exported by vuejs-datatable, & load configs. 61 | * 62 | * @param Vue - The Vue instance to configure. 63 | * @returns nothing. 64 | */ 65 | install(vue: typeof Vue): void; 66 | /** 67 | * Declares a pair of components (a Datatable & a Datatable-Pager) sharing a config. 68 | * 69 | * @param id - The base name of the datatable type. 70 | * @param tableType - The configuration object that describes both datatable & the related pager. 71 | * @returns `this` for chaining. 72 | */ 73 | private installTableType; 74 | /** 75 | * Remove a table type definition from vue (the datatable & its associated pager). 76 | * This should be used carefully, because Vue won't be able to instanciate new instances of this table type. 77 | * 78 | * @param id - The base name of the datatable type to forget. 79 | * @returns `this` for chaining. 80 | */ 81 | private uninstallTableType; 82 | } 83 | -------------------------------------------------------------------------------- /dist/themes/classes/datatable-factory.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { PluginObject } from 'vue'; 2 | import { TableType } from './table-type'; 3 | /** 4 | * Registers Vuejs-Datatable components globally in VueJS. 5 | * 6 | * @example 7 | * import { DatatableFactory } from 'vuejs-datatable'; 8 | * const myDatatableFactory = new DatatableFactory() 9 | * .registerTableType( new TableType( 'my-table', {}) ) 10 | * Vue.use( myDatatableFactory ); 11 | */ 12 | export declare class DatatableFactory implements PluginObject { 13 | /** A reference to the Vue instance the plugin is installed in. It may be used to check if the factory was already installed */ 14 | private vueInstance?; 15 | /** Registry of declared table types. */ 16 | private readonly tableTypes; 17 | /** The default table type to use if no other configuration was provided. */ 18 | private readonly defaultTableType; 19 | /** 20 | * Initialize the default factory 21 | */ 22 | constructor(); 23 | /** 24 | * Get a table type by its identifier. 25 | * 26 | * @param id - The identifier of the table type. If not provided, it will default to the default table type. 27 | * @returns the table type registered with that identifier. 28 | */ 29 | getTableType(id?: string): TableType | undefined; 30 | /** 31 | * Controls the use of the default table type. 32 | * 33 | * @param use - `true` to use the default type, `false` otherwise. 34 | * @returns `this` for chaining. 35 | */ 36 | useDefaultType(use: boolean): this; 37 | /** 38 | * Check if the factory uses the default table type. 39 | * 40 | * @returns a boolean indicating if the factory uses the default table type. 41 | */ 42 | useDefaultType(): boolean; 43 | /** 44 | * Creates a new table type with a specified prefix, that you can customize using a callback. 45 | * 46 | * @param nameOrTableType - The name of the component to register, or a [[TableType]] object. 47 | * @param callback - An optional function to execute, that configures the newly created [[TableType]]. It takes a single parameter: the newly created [[TableType]], and should 48 | * return the transformed table type. 49 | * @returns `this` for chaining. 50 | */ 51 | registerTableType(nameOrTableType: string | TableType, callback?: (tableType: TableType) => TableType | undefined): this; 52 | /** 53 | * Creates a new table type with a specified prefix, that you can customize using a callback. 54 | * 55 | * @param nameOrTableType - The name of the component to register, or a [[TableType]] object. 56 | * @returns `this` for chaining. 57 | */ 58 | deregisterTableType(nameOrTableType: string | TableType): this; 59 | /** 60 | * Declares global components exported by vuejs-datatable, & load configs. 61 | * 62 | * @param Vue - The Vue instance to configure. 63 | * @returns nothing. 64 | */ 65 | install(vue: typeof Vue): void; 66 | /** 67 | * Declares a pair of components (a Datatable & a Datatable-Pager) sharing a config. 68 | * 69 | * @param id - The base name of the datatable type. 70 | * @param tableType - The configuration object that describes both datatable & the related pager. 71 | * @returns `this` for chaining. 72 | */ 73 | private installTableType; 74 | /** 75 | * Remove a table type definition from vue (the datatable & its associated pager). 76 | * This should be used carefully, because Vue won't be able to instanciate new instances of this table type. 77 | * 78 | * @param id - The base name of the datatable type to forget. 79 | * @returns `this` for chaining. 80 | */ 81 | private uninstallTableType; 82 | } 83 | -------------------------------------------------------------------------------- /src/classes/settings.ts: -------------------------------------------------------------------------------- 1 | import { get, Path, set } from 'object-path'; 2 | 3 | /** 4 | * @tutorial custom-theme 5 | */ 6 | export interface ISettingsProperties { 7 | /** The classes to apply on the `table` tag itself. */ 8 | table: { 9 | class: string; 10 | row: { 11 | /** The classes to apply on each row (`tr` tag). */ 12 | class: string; 13 | }; 14 | sorting: { 15 | /** The HTML representing the sort icon when the column isn't sorted. */ 16 | sortNone: string; 17 | /** The HTML representing the sort icon when sorting the column ascending. */ 18 | sortAsc: string; 19 | /** The HTML representing the sort icon when sorting the column descending. */ 20 | sortDesc: string; 21 | }; 22 | }; 23 | pager: { 24 | classes: { 25 | /** The class to apply on the pager's `ul` tag. */ 26 | pager: string; 27 | /** The class to apply on the page's `li` tag. */ 28 | li: string; 29 | /** The class to apply on the current page's `li` tag. */ 30 | selected: string; 31 | /** The class to apply on a disabled page's `li` tag. */ 32 | disabled: string; 33 | }; 34 | icons: { 35 | /** The HTML representing the *Previous page* icon. */ 36 | previous: string; 37 | /** The HTML representing the *Next page* icon. */ 38 | next: string; 39 | }; 40 | }; 41 | } 42 | export type DeepPartial = { 43 | [P in keyof T]?: T[P] extends Array 44 | ? Array> 45 | : T[P] extends ReadonlyArray 46 | ? ReadonlyArray> 47 | : DeepPartial 48 | }; 49 | 50 | /** 51 | * @summary Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 52 | * @description Settings class used by Datatable's components to get various values, such as class names, labels, icons, etc etc. 53 | * Create a new instance of this class & customize it to use different CSS frameworks. 54 | * No default style is set. See [[tutorial:custom-theme]] for more infos on customizing styles. 55 | * To edit settings contained by an instance of this class, either edit the [[Settings.properties]] object, or use the [[Settings.merge]] method. 56 | * 57 | * @tutorial custom-theme Cutomize your tables for another CSS framework or your own styling. 58 | */ 59 | export class Settings { 60 | /** Tree of settings values. */ 61 | private properties: ISettingsProperties = { 62 | table: { 63 | class: '', 64 | row: { 65 | class: '', 66 | }, 67 | sorting: { 68 | sortAsc: '↓', 69 | sortDesc: '↑', 70 | sortNone: '↕', 71 | }, 72 | }, 73 | 74 | pager: { 75 | classes: { 76 | disabled: 'disabled', 77 | li: '', 78 | pager: '', 79 | selected: 'active', 80 | }, 81 | icons: { 82 | next: '>', 83 | previous: '<', 84 | }, 85 | }, 86 | }; 87 | 88 | /** 89 | * Get a value at a specific path. 90 | * 91 | * @param path - Path to the value to get. 92 | * @returns the value at the specified path 93 | */ 94 | public get( path: Path ): any { 95 | return get( this.properties, path ); 96 | } 97 | 98 | /** 99 | * Defines a value at a specific path 100 | * 101 | * @param path - Path to the value to set. 102 | * @param value - New value to set. 103 | * @returns `this` for chaining. 104 | */ 105 | public set( path: Path, value: any ): this { 106 | set( this.properties, path, value ); 107 | 108 | return this; 109 | } 110 | 111 | /** 112 | * Merges a new settings object within the Settings instance. 113 | * 114 | * @param settings - New settings object to merge with the current object of the Settings instance. 115 | * @returns `this` for chaining. 116 | */ 117 | public merge( settings: DeepPartial ): this { 118 | this.properties = Settings.mergeObjects( this.properties, settings ); 119 | 120 | return this; 121 | } 122 | 123 | /** 124 | * Merges two objects deeply, and return the 1st parameter once transformed. 125 | * 126 | * @param obj1 - The base item to merge, which will be returned. 127 | * @param obj2 - The object to inject into `obj1`. 128 | * @returns The first object once merged. 129 | */ 130 | public static mergeObjects( obj1: T, obj2: DeepPartial ): T { 131 | for ( const key in obj2 ) { 132 | if ( typeof obj2[key] === 'object' ) { 133 | obj1[key] = Settings.mergeObjects( obj1[key] || {} as any, obj2[key] as any ) as any; 134 | } else { 135 | obj1[key] = obj2[key] as any; 136 | } 137 | } 138 | 139 | return obj1; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/classes/table-type.spec.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | jest.mock( '../components/vue-datatable/vue-datatable' ); 4 | jest.mock( '../components/vue-datatable-pager/vue-datatable-pager' ); 5 | jest.mock( './settings' ); 6 | jest.mock( './handlers/default-handler' ); 7 | import { VueDatatablePager } from '../components/vue-datatable-pager/vue-datatable-pager'; 8 | import { VueDatatable } from '../components/vue-datatable/vue-datatable'; 9 | import { DefaultHandler } from './handlers/default-handler'; 10 | // @ts-ignore 11 | import { get, merge, set, Settings } from './settings'; 12 | import { TableType } from './table-type'; 13 | 14 | it( 'is initialized with name, a new settings settings, and a new handler', () => { 15 | const tabletype1 = new TableType( 'name' ); 16 | expect( tabletype1.id ).toBe( 'name' ); 17 | expect( typeof tabletype1.settings ).toBe( 'object' ); 18 | expect( typeof tabletype1.handler ).toBe( 'object' ); 19 | 20 | const tabletype2 = new TableType( 'name2' ); 21 | expect( tabletype2.id ).toBe( 'name2' ); 22 | expect( typeof tabletype1.settings ).toBe( 'object' ); 23 | expect( typeof tabletype1.handler ).toBe( 'object' ); 24 | 25 | expect( Settings ).toHaveBeenCalledTimes( 2 ); 26 | expect( tabletype1.settings ).toEqual( tabletype2.settings ); 27 | expect( tabletype1.settings ).not.toBe( tabletype2.settings ); 28 | expect( DefaultHandler ).toHaveBeenCalledTimes( 2 ); 29 | expect( tabletype1.handler ).toEqual( tabletype2.handler ); 30 | expect( tabletype1.handler ).not.toBe( tabletype2.handler ); 31 | } ); 32 | it( 'can customize handlers', () => { 33 | const tabletype = new TableType( 'name' ); 34 | 35 | expect( typeof tabletype.setFilterHandler ).toBe( 'function' ); 36 | const filterHandlerMock = jest.fn(); 37 | expect( tabletype.setFilterHandler( filterHandlerMock ) ).toBe( tabletype ); 38 | expect( tabletype.handler.filterHandler ).toBe( filterHandlerMock ); 39 | 40 | expect( typeof tabletype.setSortHandler ).toBe( 'function' ); 41 | const sortHandlerMock = jest.fn(); 42 | expect( tabletype.setSortHandler( sortHandlerMock ) ).toBe( tabletype ); 43 | expect( tabletype.handler.sortHandler ).toBe( sortHandlerMock ); 44 | 45 | expect( typeof tabletype.setPaginateHandler ).toBe( 'function' ); 46 | const paginateHandlerMock = jest.fn(); 47 | expect( tabletype.setPaginateHandler( paginateHandlerMock ) ).toBe( tabletype ); 48 | expect( tabletype.handler.paginateHandler ).toBe( paginateHandlerMock ); 49 | } ); 50 | 51 | describe( 'Settings getter/setter/merge', () => { 52 | it( 'Get', () => { 53 | const tabletype = new TableType( 'name' ); 54 | tabletype.setting( 'foo.bar' ); 55 | expect( get ).toHaveBeenCalledTimes( 1 ); 56 | expect( get ).toHaveBeenCalledWith( 'foo.bar' ); 57 | } ); 58 | it( 'Set', () => { 59 | const tabletype = new TableType( 'name' ); 60 | const obj = {}; 61 | tabletype.setting( 'foo.bar', obj ); 62 | expect( set ).toHaveBeenCalledTimes( 1 ); 63 | expect( set ).toHaveBeenCalledWith( 'foo.bar', obj ); 64 | } ); 65 | it( 'Merge', () => { 66 | const tabletype = new TableType( 'name' ); 67 | const obj = {}; 68 | tabletype.mergeSettings( obj ); 69 | expect( merge ).toHaveBeenCalledTimes( 1 ); 70 | expect( merge ).toHaveBeenCalledWith( obj ); 71 | } ); 72 | } ); 73 | describe( 'Can generate table/pager definitions', () => { 74 | it( 'Generate table', () => { 75 | const tabletype = new TableType( 'name' ); 76 | 77 | const tableDefinition = tabletype.getTableDefinition(); 78 | const componentProtoIndex = ( ( tableDefinition as any ).extendOptions.mixins as Array ).indexOf( VueDatatable ); 79 | expect( componentProtoIndex ).not.toBe( -1 ); 80 | const otherMixins = ( ( tableDefinition as any ).extendOptions.mixins as Array ) 81 | .filter( ( v, i ) => i !== componentProtoIndex ); 82 | expect( otherMixins ).toHaveLength( 1 ); 83 | expect( ( otherMixins[0] as any ).extendOptions.computed.tableType.get() ).toBe( tabletype ); 84 | } ); 85 | it( 'Generate pager', () => { 86 | const tabletype = new TableType( 'name' ); 87 | 88 | const pagerDefinition = tabletype.getPagerDefinition(); 89 | const componentProtoIndex = ( ( pagerDefinition as any ).extendOptions.mixins as Array ).indexOf( VueDatatablePager ); 90 | expect( componentProtoIndex ).not.toBe( -1 ); 91 | const otherMixins = ( ( pagerDefinition as any ).extendOptions.mixins as Array ) 92 | .filter( ( v, i ) => i !== componentProtoIndex ); 93 | expect( otherMixins ).toHaveLength( 1 ); 94 | expect( ( otherMixins[0] as any ).extendOptions.computed.tableType.get() ).toBe( tabletype ); 95 | } ); 96 | } ); 97 | -------------------------------------------------------------------------------- /ci/travis_key.enc: -------------------------------------------------------------------------------- 1 | U2FsdGVkX18NRwFVNsPwswxYyza6WgxD1UsWqcptce5pFU7Tcf6MpdoEEAozrm0b 2 | DvlSBoTVorM2eMTO7BOBQunVgFZCi4cPZGNzrw8On4LxUpnZp5I+s1KB2bQp76y2 3 | +vXATwgyuDPfZpEP32jZqwT4pXxpZBu7CoNQ1aqrbmUPvowMzsiD1CMWjYPfMNoS 4 | HzFHFcVxWIRRL2YaQmwYXucgFF8pdaI5GEap1sb8pZqUo9SQG2qD+1USeTQaz4m0 5 | cdZGmElY8ZEigZM4r9RZMnR2n6y1HWXhub7Ue7iR4NUQx3ciUonI4Lv+TE3hFAsA 6 | SoYCA0Lb6ayzeLN3yQ4rdm7MIrZzZ8fHVTGpsU6e282T9vognzUDgWJsxJgT12fB 7 | Ac024wLQ/+AEApWA3T8JLAgnARwxL6LEemEWIrWqTrCSzkul2y9TlDug7ruEroLD 8 | LYDwiNM+8P4MVWgfTWU/oXYS//miDYhEH2IkAGpeSAfaCRCuCDrwtxHItSne7DOI 9 | 3Ep7iCXlR9fUZVTEGgIBop1JM5C4TFmO7x/bF/ESKQtBZ619Cvpq9njCYKzrjgc2 10 | xYB+T2NpRxMcJEHbtK0V7SRq45Ew8BJnJW1MXBKeyidjVxmBQ/LmfR0ddfYh6gOd 11 | 4FkNGyTqGhk9DM6TB5smcbXN/14TnH+EbRlpT59qmwBb0EKLES/c6kokRoL/6vGD 12 | AcRVKHF7kvPopxetCBCRWcr9+gbZG2BUx+uGHyBad4B9wuKtx1Yz8kyTGdGjCyYq 13 | E6pqT5AGV/92VO9ar39n7YuwXgHr6PH8Dzdw5UQJxJoLJEhf5RexePsTePUr2MhR 14 | 7nsQwcYa9XR2CikaFhPZPKsDdfBJMBJvABho+4Df7opwAubFsZOjcwHIS9mLqmvm 15 | 2vuQqFJ3Ld2LVffCgOjM2PYmh4dVM6nTJk9d5ABMIvNFQbg2qu6ql5RaDnpEd/ZK 16 | QevZMQlkBc0fHjgApwbc0JN+qiYKPlqNQqP2j18NCUlTsDMWlt5PHXz2qjVnkKcm 17 | /Wg6alAuxdcQAqSyjB56xr6qFN6gyvZ0BC231L1E9l/KM4MPT7HeBqEHHdF0s9cn 18 | 7NIp9RYwxAedKcnB+n5D0PSoAX0W8snLc4u+Hx/LSHAD894aYp64d0j4X+Lx0m6k 19 | HDe5GzrxJy3n7XcMPRPncoU11vvugt3qVtmQslGXTWdPe9hBZTV443hnBILc2NWQ 20 | 2ahp46z6Q8FtD1Mpr0NQ3YxbP68mr8vH0OxhcEk7dAlZh9Yvflo837ZjgkbkCNli 21 | ykX2iWa9MI3ezuxQdEiVI40pvQe6lGCaxHOIlQsiX/8Ufk9k/5+0q1kQwrtZwb/z 22 | O+KLejSJOM69JjQwgDDepUXEt0W+Caf3+kG8d3GJM4bjwLYzDMBUVxfikTVHaKKS 23 | pPkzmrHEyancXB0dza5JvCYpdIv8mAtKYmQlcSghg5aH11KnkbJBqJHduW0+8iwE 24 | JNkYdxFd5acmhpsgk2ucYzLwk5d1UwkGEOEaswSg9oLHHb3YD7pLNlNuyudYFMaa 25 | /ENob1E0rpsRJz6473MWuE0YTwHPGkNAq6+6+ey2mpDpsNlOO/7QnNotMSmuNSli 26 | QrN85tVoUisRxrAmPXptRZ4u+ZHguS982YRFpYChwAe1iiwYbbnPDvfw7uWdeyEk 27 | Pd/fYNvfYSqkCb0nWz4Z9ANJI7S2bHlBF5zALzDhyGmgqBaqAhBsIj20lEK59onA 28 | Me5LAtXNKrRXUZppD4un9IbjnIEURB8OQVFnfx2gXBOKmFQDsorVl02aYVjhvsSl 29 | DR9t30/mR7XLgWsadZuJp+LHFEYSdxLoHNv1/s9mZDjgaOfWHSLxqh7JVkJVvBGV 30 | YfJ91MyEVHazlbOVJmNqksOfmxTwKWM8TVq6SRIk12LOKMI+VWXRa4VWRMVG7aA+ 31 | bH3GOt0gfjPw+JMKC17YUwg9ci2qPPGfPB6lrccPqs8ikt6t5T3SOVUC8khFKi0j 32 | +9DKj7rQcSE8VD8Z44H7XCWsjuClfAjqR7oes0Vp3wKFdTvr0Ukla5YdqLo0vxsA 33 | E0cWIdAqb/BykpCg7DkaS5UGZZgBxunoionV/xNCH6/IPsd2jgGqnt47pT5Ixt3A 34 | golukWGI8j3hfFthZ9/tQ3+q5JE4mXIh5SBqAsg1KdPqYQDjnbxRw3FXmF8pElpF 35 | 8lh15bPazvzzu/Tn7ebXx8ctioobakSDfBL27CaqC2rtmIgecCsQn0H4EF4I1UCh 36 | YumMXrfkYZSByhXaEm8wv3iJpJBFgQENihFqQG/AQEXH7FaXkyGb+y2DhPfZ64DK 37 | joH1FW3Hf9xnYCcnCr62AM4tbMSa1t4ZJNI1rLXAB8cxbs9dzGvGsrWvHkBn85O8 38 | YSStfzOn1fZTcQk5F9qnUPxeIVS1fGzf4KCJGCCtqNO0jUrTa5tAzfpkHa5umm0H 39 | y+wmQOJA+vps+btBErKiUORwU4KbeLDFP8CKig8eNo6onaQpx3daNC01matIg/6C 40 | 2C9DuRjSNohT6KrOBU652D0grDtxScCqYLv7LRS5wMbr+FDEPV2tHtKNbRuel64j 41 | xIaf70UziTY4UEUrRfpsi20TYTznCN+UIgIV1ISg7/JwyXInMSvGtF/6avpqvPby 42 | 7x4gCJnJWcOMpVOHKHm/S38HUXhowc5fVEXFU/G7IIb7PQOHPfoFh25Qrjz6LVXz 43 | fpeBcKkttJDDvErYc4MIFwXRPJs+2NJOLMWmPGJlTJJI2yHgRAidCozcgp9cX7cM 44 | uiD0xd5GqWgWUydYbKAo/Yg/NkASJZQd651mFs0pYBggikmMrpNKlf4g8Xhi7ltS 45 | k5DwkvB2HqJBWvM5kUR6MSWaN817S6qTyRQQQdjuobyzDepXJ3syQsuLh8k66Kv4 46 | +fcPn3r52hwlIXZI19ETw1Za0loiUrIbrHCYtldrPSZ1Ez3/z2R+WSbAS/rCiAuT 47 | cNic/Gatw1isR0EPUTAIn/wnM3gcknzSXQWwtaZRg8+Z1IFfr7T9WgFEeXMhyqw3 48 | 8gwn6+uGSwbVAwhF2clDrGCVlWvqmCsBKpe3ThaiphQeh2jpMxXrsK8u6MLuFGXk 49 | 9HWc10iVykxEhqdaEJOihctnjKcd2eXcVTH9EwywT9JSoqLPV4ufJzycvhXHblzj 50 | iX7k7TtKk0tlAx0w2UBVf5zMe07ME/6Nn3DUKyjQb1umMErUG+q/5C9Yk8T2A7mD 51 | wLH3suuW7YIR0UeuyCFg1HElQso+L664UnfD3u+c53FYoJnTh/YdINU201fCjjcQ 52 | biQZRw8J5OFRBSUMdhcMhRSVYVq5PZ7enP3cqRCWPNaFUupROdfgnxNkyU9yQdZZ 53 | ZYf+E9VrbtfOESK3addt67lnEeURZqyfx6nI+UXheSQToMvKuSV+jRU/CAe2b81Y 54 | 03BFgafUmYpNOA5ZXgYCDp1/+pYNnHXaUowGvli/b8U/JQspYQ7mLfAlRAOuLfnk 55 | P9Bpl4U4JFBNmhZwtqLKkHEhDfT96Hm/1fcEC3qiHnpe2IPkCpySgNZk5WnjktlN 56 | 7OXWLjMFOLEh2gOLPYBqn8XfaQn80jBDnz+ZU8X08cVxue8ypycijzmCyNnRYTqJ 57 | B4kkQ5zKOa/i5H/WdfMu7ZIGZrEnp57Zp7i1/kp7uNn8uw1vKPMOXO3LKvFENv51 58 | MXLpKezW/jt5aLu5VBbaMfsOWpeaDRU4mh80XpilESHZRT27b/82CE+eWc6HKFGV 59 | ov6FQr5oZ8U9JgEzunAFmMp9+WIUfSzLatcKmPAAKPW4JidXRZHCRXCVok7XSftc 60 | +yuFSrynOgzBhwqeyFVV19RCjgkVQqfnbytYxWIpZ83hhRvdLQSHg08sRh887Hz6 61 | 8Nj5ONTMM3zgOm5jjU1i9L1VVKF2KEWjd55zPwtMy+SiY1rtcTyb2KjYoZoYT+br 62 | Ur7G/W1JFISSv4bMXkP0wo1IsrTJ9X/oqv5gup8D0+aAsHXFtM1fC1yX32dgaSnY 63 | 6M6DE0nE2Yll2tJH1jDEhuOGQ5yZFsb0xTZz3fJTTYJiV8E0W1s7HoTLldWQpRR2 64 | fAsK5QI20i4sCQ9yo0HoRLSs2u1WCPc7YO1v09atwHwvuZ01CnbXkpw2wQCk5RTO 65 | rsM0USn17VzKWnV0Wh5PZTlRXSiEgDrvttpzSS2dhgvdvJ6CRusgA9QclAQmWFEC 66 | GYsND4TOARbBZr7KAsICDqGUZams6wlfAPrFYhBd+L8OgmflmbgynHY0ydH+W+AR 67 | KiUCnmdDfpP9i9+X1CYW23od/g7Yy2FYv/+ANLn4IdXAPRRsrdKdFEUCwkqkVHDM 68 | NvGMNHwGE94VUK8T0i84s2694LnPYqmwJxiys+Nvsq939R8AZ3qWO9Hn6T5+52i8 69 | -------------------------------------------------------------------------------- /src/components/vue-datatable-pager/vue-datatable-pager-button/vue-datatable-pager-button.spec.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: no-implicit-dependencies 2 | import { createLocalVue, mount } from '@vue/test-utils'; 3 | import Vue, { ComponentOptions } from 'vue'; 4 | 5 | jest.mock( '../../../classes/table-type' ); 6 | jest.mock( '../vue-datatable-pager' ); 7 | // @ts-ignore 8 | import { setting, TableType } from '../../../classes/table-type'; 9 | import { namespaceEvent } from '../../../utils'; 10 | import { VueDatatablePagerButton } from './vue-datatable-pager-button'; 11 | 12 | const localVue = createLocalVue(); 13 | const setPageEvent = namespaceEvent( 'set-page' ); 14 | beforeEach( () => { 15 | jest.clearAllMocks(); 16 | } ); 17 | 18 | describe( 'Events', () => { 19 | it( 'click emits set-page event on parent', () => { 20 | const wrapper = mount( VueDatatablePagerButton, { 21 | localVue, 22 | parentComponent: {} as ComponentOptions, 23 | propsData: { value: 42 }, 24 | provide: { 'table-type': new TableType( 'tmp' ) }, 25 | } ); 26 | const mockParentEmit = jest.spyOn( wrapper.vm.$parent, '$emit' ); 27 | 28 | wrapper.find( 'li' ).trigger( 'click' ); 29 | expect( mockParentEmit ).toHaveBeenCalledTimes( 1 ); 30 | expect( mockParentEmit ).toHaveBeenCalledWith( setPageEvent, 42 ); 31 | } ); 32 | it( 'click when disabled should not emit set-page event on parent', () => { 33 | const wrapper = mount( VueDatatablePagerButton, { 34 | localVue, 35 | parentComponent: {} as ComponentOptions, 36 | propsData: { value: 42, disabled: true }, 37 | provide: { 'table-type': new TableType( 'tmp' ) }, 38 | } ); 39 | const mockParentEmit = jest.spyOn( wrapper.vm.$parent, '$emit' ); 40 | 41 | wrapper.find( 'li' ).trigger( 'click' ); 42 | expect( mockParentEmit ).not.toHaveBeenCalled(); 43 | } ); 44 | } ); 45 | 46 | describe( 'Template', () => { 47 | describe( 'HTML & slots', () => { 48 | it( 'builds base HTML', () => { 49 | const wrapper = mount( VueDatatablePagerButton, { 50 | localVue, 51 | parentComponent: {} as ComponentOptions, 52 | provide: { 'table-type': new TableType( 'tmp' ) }, 53 | } ); 54 | 55 | expect( wrapper.element.textContent.trim() ).toBe( '' ); 56 | expect( wrapper.element.nodeName ).toBe( 'LI' ); 57 | expect( wrapper.element.children.length ).toBe( 0 ); 58 | } ); 59 | it( 'displays passed value', () => { 60 | const wrapper = mount( VueDatatablePagerButton, { 61 | localVue, 62 | parentComponent: {} as ComponentOptions, 63 | propsData: { value: 1 }, 64 | provide: { 'table-type': new TableType( 'tmp' ) }, 65 | } ); 66 | 67 | expect( wrapper.element.textContent.trim() ).toBe( '1' ); 68 | } ); 69 | it( 'slot overrides value', () => { 70 | const wrapper = mount( VueDatatablePagerButton, { 71 | localVue, 72 | parentComponent: {} as ComponentOptions, 73 | propsData: { value: 1 }, 74 | provide: { 'table-type': new TableType( 'tmp' ) }, 75 | slots: { default: 'foo' }, 76 | } ); 77 | 78 | expect( wrapper.element.textContent.trim() ).toBe( 'foo' ); 79 | } ); 80 | } ); 81 | describe( 'Classes', () => { 82 | it( 'uses the correct selected class', () => { 83 | setting.mockImplementation( p => p === 'pager.classes.li' ? 'LI' : 'SELECTED' ); 84 | const wrapper = mount( VueDatatablePagerButton, { 85 | localVue, 86 | parentComponent: {} as ComponentOptions, 87 | propsData: { value: 1, selected: true }, 88 | provide: { 'table-type': new TableType( 'tmp' ) }, 89 | } ); 90 | 91 | expect( setting ).toHaveBeenCalledTimes( 2 ); 92 | expect( setting ).toHaveBeenCalledWith( 'pager.classes.li' ); 93 | expect( wrapper.element.classList ).toContain( 'LI' ); 94 | expect( setting ).toHaveBeenCalledWith( 'pager.classes.selected' ); 95 | expect( wrapper.element.classList ).toContain( 'SELECTED' ); 96 | } ); 97 | 98 | it( 'uses the correct disabled class', () => { 99 | setting.mockImplementation( p => p === 'pager.classes.li' ? 'LI' : 'DISABLED' ); 100 | const wrapper = mount( VueDatatablePagerButton, { 101 | localVue, 102 | parentComponent: {} as ComponentOptions, 103 | propsData: { value: 1, disabled: true }, 104 | provide: { 'table-type': new TableType( 'tmp' ) }, 105 | } ); 106 | 107 | expect( setting ).toHaveBeenCalledTimes( 2 ); 108 | expect( setting ).toHaveBeenCalledWith( 'pager.classes.li' ); 109 | expect( wrapper.element.classList ).toContain( 'LI' ); 110 | expect( setting ).toHaveBeenCalledWith( 'pager.classes.disabled' ); 111 | expect( wrapper.element.classList ).toContain( 'DISABLED' ); 112 | } ); 113 | } ); 114 | } ); 115 | -------------------------------------------------------------------------------- /dist/classes/table-type.d.ts: -------------------------------------------------------------------------------- 1 | import { Path } from 'object-path'; 2 | import { IHandler, TDisplayHandler, TFilterHandler, TPaginateHandler, TSortHandler } from './handlers'; 3 | import { DeepPartial, ISettingsProperties, Settings } from './settings'; 4 | import { VueDatatablePager } from '../components/vue-datatable-pager/vue-datatable-pager'; 5 | import { VueDatatable } from '../components/vue-datatable/vue-datatable'; 6 | /** 7 | * Defines a type of Datatable, with its [[Settings]] object. 8 | */ 9 | export declare class TableType { 10 | readonly id: string; 11 | readonly handler: IHandler; 12 | /** Settings object used to get various values for the datatable & other components */ 13 | readonly settings: Settings; 14 | get tableTypeConsumer(): import("vue").VueConstructor & (new () => import("../components/mixins/table-type-consumer-factory").ITableTypeConsumer); 15 | /** 16 | * Creates a new datatable type, instanciating a new [[Settings]] object. 17 | * 18 | * @param id - The identifier of this datatable type 19 | * @param handler - Transformation functions to use for table operations 20 | */ 21 | constructor(id: string, handler?: IHandler); 22 | /** 23 | * Override the table type's handler method with the provided one. It can be used to override a single handler step, or to change the behavior of a table type at runtime. 24 | * 25 | * @param type - The type of the handler (`'filter' | 'sort' | 'paginate' | 'display'`) 26 | * @param closure - The new handler to set. 27 | */ 28 | private setHandler; 29 | /** 30 | * Defines the function used to filter data 31 | * 32 | * @see Handler#filterHandler 33 | * @tutorial ajax-handler 34 | * @param closure - The function to use for sorting. 35 | * @returns `this` for chaining. 36 | */ 37 | setFilterHandler: (closure: TFilterHandler) => this; 38 | /** 39 | * Defines the function used to sort data 40 | * 41 | * @see Handler#sortHandler 42 | * @tutorial ajax-handler 43 | * @param closure - The function to use for sorting. 44 | * @returns `this` for chaining. 45 | */ 46 | setSortHandler: (closure: TSortHandler) => this; 47 | /** 48 | * Defines the function used to paginate data 49 | * 50 | * @see Handler#paginateHandler 51 | * @tutorial ajax-handler 52 | * @param closure - The function to use for pagination. 53 | * @returns `this` for chaining. 54 | */ 55 | setPaginateHandler: (closure: TPaginateHandler) => this; 56 | /** 57 | * Defines the function used to paginate data 58 | * 59 | * @see Handler#displayHandler 60 | * @tutorial ajax-handler 61 | * @param closure - The function to use to post-process processed steps & extract rows & total count. 62 | * @returns `this` for chaining. 63 | */ 64 | setDisplayHandler: (closure: TDisplayHandler) => this; 65 | /** 66 | * Set a [[Settings]] value at a specific path. 67 | * 68 | * @param path - Path to the setting value to set. 69 | * @param value - Value to set at the specified path. 70 | * @returns `this` for chaining. 71 | */ 72 | setting(path: Path, value: any): this; 73 | /** 74 | * Get a [[Settings]] value at a specific path. 75 | * 76 | * @param path - Path to the setting value to get. 77 | * @returns the value at the given path. 78 | */ 79 | setting(path: Path): any; 80 | /** 81 | * Merge a settings object with the [[TableType.setting]] object of the instance. 82 | * 83 | * @param settings - Values to merge. 84 | * @returns `this` for chaining. 85 | */ 86 | mergeSettings(settings: DeepPartial): this; 87 | /** 88 | * Factory function that copy the [[VueDatatable]] prototype, and configure as this type. 89 | * 90 | * @returns a new factored [[VueDatatable]] constructor. 91 | */ 92 | getTableDefinition(): typeof VueDatatable; 93 | /** 94 | * Factory function that copy the [[VueDatatablePager]] prototype, and configure as this type. 95 | * 96 | * @returns a new factored [[VueDatatablePager]] constructor. 97 | */ 98 | getPagerDefinition(): typeof VueDatatablePager; 99 | } 100 | -------------------------------------------------------------------------------- /dist/themes/classes/table-type.d.ts: -------------------------------------------------------------------------------- 1 | import { Path } from 'object-path'; 2 | import { IHandler, TDisplayHandler, TFilterHandler, TPaginateHandler, TSortHandler } from './handlers'; 3 | import { DeepPartial, ISettingsProperties, Settings } from './settings'; 4 | import { VueDatatablePager } from '../components/vue-datatable-pager/vue-datatable-pager'; 5 | import { VueDatatable } from '../components/vue-datatable/vue-datatable'; 6 | /** 7 | * Defines a type of Datatable, with its [[Settings]] object. 8 | */ 9 | export declare class TableType { 10 | readonly id: string; 11 | readonly handler: IHandler; 12 | /** Settings object used to get various values for the datatable & other components */ 13 | readonly settings: Settings; 14 | get tableTypeConsumer(): import("vue").VueConstructor & (new () => import("../components/mixins/table-type-consumer-factory").ITableTypeConsumer); 15 | /** 16 | * Creates a new datatable type, instanciating a new [[Settings]] object. 17 | * 18 | * @param id - The identifier of this datatable type 19 | * @param handler - Transformation functions to use for table operations 20 | */ 21 | constructor(id: string, handler?: IHandler); 22 | /** 23 | * Override the table type's handler method with the provided one. It can be used to override a single handler step, or to change the behavior of a table type at runtime. 24 | * 25 | * @param type - The type of the handler (`'filter' | 'sort' | 'paginate' | 'display'`) 26 | * @param closure - The new handler to set. 27 | */ 28 | private setHandler; 29 | /** 30 | * Defines the function used to filter data 31 | * 32 | * @see Handler#filterHandler 33 | * @tutorial ajax-handler 34 | * @param closure - The function to use for sorting. 35 | * @returns `this` for chaining. 36 | */ 37 | setFilterHandler: (closure: TFilterHandler) => this; 38 | /** 39 | * Defines the function used to sort data 40 | * 41 | * @see Handler#sortHandler 42 | * @tutorial ajax-handler 43 | * @param closure - The function to use for sorting. 44 | * @returns `this` for chaining. 45 | */ 46 | setSortHandler: (closure: TSortHandler) => this; 47 | /** 48 | * Defines the function used to paginate data 49 | * 50 | * @see Handler#paginateHandler 51 | * @tutorial ajax-handler 52 | * @param closure - The function to use for pagination. 53 | * @returns `this` for chaining. 54 | */ 55 | setPaginateHandler: (closure: TPaginateHandler) => this; 56 | /** 57 | * Defines the function used to paginate data 58 | * 59 | * @see Handler#displayHandler 60 | * @tutorial ajax-handler 61 | * @param closure - The function to use to post-process processed steps & extract rows & total count. 62 | * @returns `this` for chaining. 63 | */ 64 | setDisplayHandler: (closure: TDisplayHandler) => this; 65 | /** 66 | * Set a [[Settings]] value at a specific path. 67 | * 68 | * @param path - Path to the setting value to set. 69 | * @param value - Value to set at the specified path. 70 | * @returns `this` for chaining. 71 | */ 72 | setting(path: Path, value: any): this; 73 | /** 74 | * Get a [[Settings]] value at a specific path. 75 | * 76 | * @param path - Path to the setting value to get. 77 | * @returns the value at the given path. 78 | */ 79 | setting(path: Path): any; 80 | /** 81 | * Merge a settings object with the [[TableType.setting]] object of the instance. 82 | * 83 | * @param settings - Values to merge. 84 | * @returns `this` for chaining. 85 | */ 86 | mergeSettings(settings: DeepPartial): this; 87 | /** 88 | * Factory function that copy the [[VueDatatable]] prototype, and configure as this type. 89 | * 90 | * @returns a new factored [[VueDatatable]] constructor. 91 | */ 92 | getTableDefinition(): typeof VueDatatable; 93 | /** 94 | * Factory function that copy the [[VueDatatablePager]] prototype, and configure as this type. 95 | * 96 | * @returns a new factored [[VueDatatablePager]] constructor. 97 | */ 98 | getPagerDefinition(): typeof VueDatatablePager; 99 | } 100 | -------------------------------------------------------------------------------- /src/components/vue-datatable-cell/vue-datatable-cell.spec.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: no-implicit-dependencies 2 | import { createLocalVue, mount } from '@vue/test-utils'; 3 | import { CreateElement } from 'vue'; 4 | 5 | jest.mock( '../../classes/column' ); 6 | // @ts-ignore 7 | import { Column, getRepresentation } from '../../classes/column'; 8 | import { EColAlign } from '../../utils'; 9 | import { VueDatatableCell } from './vue-datatable-cell'; 10 | 11 | const localVue = createLocalVue(); 12 | 13 | const TestComponent = { 14 | name: 'test-component', 15 | props: [ 'row', 'column' ], 16 | render: ( c: CreateElement ) => c( '' ), 17 | }; 18 | localVue.component( TestComponent.name, TestComponent ); 19 | 20 | beforeEach( () => { 21 | jest.clearAllMocks(); 22 | } ); 23 | describe( 'Display values', () => { 24 | it( 'Should display the column representation with interpolation', () => { 25 | getRepresentation.mockReturnValue( '

    retRepresentation

    ' ); 26 | const col = new Column( { label: '', interpolate: true } ); 27 | const row = {}; 28 | const wrapper = mount( VueDatatableCell, { 29 | localVue, 30 | propsData: { column: col, row }, 31 | } ); 32 | 33 | expect( wrapper.element ).toBeInstanceOf( HTMLTableCellElement ); 34 | expect( wrapper.element.innerHTML.trim() ).toBe( '

    retRepresentation

    ' ); 35 | expect( getRepresentation ).toHaveBeenCalledTimes( 1 ); 36 | expect( getRepresentation ).toHaveBeenCalledWith( row ); 37 | } ); 38 | 39 | it( 'Should display the column representation without interpolation', () => { 40 | getRepresentation.mockReturnValue( '

    retRepresentation

    ' ); 41 | const col = new Column( { label: '', interpolate: false } ); 42 | const row = {}; 43 | const wrapper = mount( VueDatatableCell, { 44 | localVue, 45 | propsData: { column: col, row }, 46 | } ); 47 | 48 | expect( wrapper.element ).toBeInstanceOf( HTMLTableCellElement ); 49 | expect( wrapper.element.innerHTML.trim() ).toBe( '<p>retRepresentation</p>' ); 50 | expect( getRepresentation ).toHaveBeenCalledTimes( 1 ); 51 | expect( getRepresentation ).toHaveBeenCalledWith( row ); 52 | } ); 53 | 54 | it( 'Should display the component', () => { 55 | const col = new Column( { label: '', component: 'test-component' } ); 56 | const row = {}; 57 | const wrapper = mount( VueDatatableCell, { 58 | localVue, 59 | propsData: { column: col, row }, 60 | } ); 61 | 62 | expect( wrapper.element ).toBeInstanceOf( HTMLTableCellElement ); 63 | expect( wrapper.contains( TestComponent ) ).toBe( true ); 64 | const mountedTestComponent = wrapper.find( TestComponent ); 65 | expect( mountedTestComponent.props().row ).toBe( row ); 66 | expect( mountedTestComponent.props().column ).toBe( col ); 67 | } ); 68 | } ); 69 | it( 'can change text alignment', () => { 70 | const wrapperL = mount( VueDatatableCell, { 71 | propsData: { column: new Column( { label: '', align: EColAlign.Left } ), row: {}}, 72 | } ); 73 | expect( wrapperL.element.style.textAlign ).toBe( 'left' ); 74 | 75 | const wrapperC = mount( VueDatatableCell, { 76 | propsData: { column: new Column( { label: '', align: EColAlign.Center } ), row: {}}, 77 | } ); 78 | expect( wrapperC.element.style.textAlign ).toBe( 'center' ); 79 | 80 | const wrapperR = mount( VueDatatableCell, { 81 | propsData: { column: new Column( { label: '', align: EColAlign.Right } ), row: {}}, 82 | } ); 83 | expect( wrapperR.element.style.textAlign ).toBe( 'right' ); 84 | } ); 85 | describe( 'Cells classes', () => { 86 | it( 'should not use any class if the column `class` property is not set', () => { 87 | const col = new Column( { label: '' } ); 88 | const row = {}; 89 | const wrapper = mount( VueDatatableCell, { 90 | localVue, 91 | propsData: { column: col, row }, 92 | } ); 93 | 94 | expect( wrapper.vm.cellClass ).toBeUndefined(); 95 | } ); 96 | it( 'should call the class function if the column `class` property is set to a function', () => { 97 | const classFn = jest.fn().mockReturnValue( ['hello', 'world'] ); 98 | const col = new Column( { label: '', class: classFn } ); 99 | const row = {}; 100 | const wrapper = mount( VueDatatableCell, { 101 | localVue, 102 | propsData: { column: col, row }, 103 | } ); 104 | 105 | expect( wrapper.vm.cellClass ).toEqual( ['hello', 'world'] ); 106 | expect( classFn ).toHaveBeenCalledWith( row ); 107 | } ); 108 | it( 'should return column classes if not nil nor a function', () => { 109 | const col = new Column( { label: '', class: ['hello', 'world'] } ); 110 | const row = {}; 111 | const wrapper = mount( VueDatatableCell, { 112 | localVue, 113 | propsData: { column: col, row }, 114 | } ); 115 | 116 | expect( wrapper.vm.cellClass ).toEqual( ['hello', 'world'] ); 117 | } ); 118 | } ); 119 | -------------------------------------------------------------------------------- /src/classes/handlers/default-handler.ts: -------------------------------------------------------------------------------- 1 | import { TMaybePromise } from '../../utils'; 2 | import { Column } from '../column'; 3 | import { ESortDir, IDisplayHandlerParam, IDisplayHandlerResult, IHandler } from './i-handler'; 4 | 5 | // From https://stackoverflow.com/a/48660568/4839162 6 | const stableSort = ( arr: T[], compare: ( a: T, b: T ) => number ) => arr 7 | .map( ( item, index ) => ( { 8 | index, 9 | item, 10 | } ) ) 11 | .sort( ( a, b ) => compare( a.item, b.item ) || a.index - b.index ) 12 | .map( ( { item } ) => item ); 13 | 14 | /** 15 | * This handler is an implementation of [[IHandler]], configured to manipulate an array of rows as input. 16 | * Handlers are called in this order: filter, sort, paginate, display. 17 | * 18 | * In case you are overriding *one* of those handlers, make sure that its return value is compatible with subsequent handlers. Otherwise, you'll require to override all of them. 19 | * 20 | * @tutorial ajax-handler 21 | */ 22 | export class DefaultHandler implements IHandler { 23 | /** 24 | * Filter the provided rows, checking if at least a cell contains one of the specified filters. 25 | * 26 | * @param data - The data to apply filter on. 27 | * @param filters - The strings to search in cells. 28 | * @param columns - The columns of the table. 29 | * @returns the filtered data rows. 30 | */ 31 | public filterHandler( data: TRow[], filters: string[] | string | undefined, columns: Array> ): TMaybePromise { 32 | const filtersArr = ( Array.isArray( filters ) ? filters : ( filters || '' ).split( /\s/ ) ) 33 | .filter( v => !!v ); 34 | 35 | if ( filtersArr.length === 0 ) { 36 | return data; 37 | } 38 | 39 | return data.filter( row => filtersArr.some( filter => this.rowMatches( row, filter, columns ) ) ); 40 | } 41 | 42 | /** 43 | * Sort the given rows depending on a specific column & sort order. 44 | * 45 | * @param filteredData - Data outputed from [[Handler.filterHandler]]. 46 | * @param sortColumn - The column used for sorting. 47 | * @param sortDir - The direction of the sort. 48 | * @returns the sorted rows. 49 | */ 50 | public sortHandler( filteredData: TRow[], sortColumn: Column | null, sortDir: ESortDir | null ): TMaybePromise { 51 | if ( !sortColumn || sortDir === null ) { 52 | return filteredData; 53 | } 54 | 55 | return stableSort( filteredData, ( a: TRow, b: TRow ) => { 56 | const valA = sortColumn.getRepresentation( a ); 57 | const valB = sortColumn.getRepresentation( b ); 58 | 59 | if ( valA === valB ) { 60 | return 0; 61 | } 62 | 63 | let sortVal = valA > valB ? 1 : -1; 64 | 65 | if ( sortDir === 'desc' ) { 66 | sortVal *= -1; 67 | } 68 | 69 | return sortVal; 70 | } ); 71 | } 72 | 73 | /** 74 | * Split the rows list to display the requested page index. 75 | * 76 | * @param sortedData - Data outputed from [[Handler.sortHandler]]. 77 | * @param perPage - The total number of items per page. 78 | * @param pageNumber - The index of the page to display. 79 | * @returns the requested page's rows. 80 | */ 81 | public paginateHandler( sortedData: TRow[], perPage: number | null, pageNumber: number ): TMaybePromise { 82 | if ( perPage === null || perPage < 1 || pageNumber < 1 ) { 83 | return sortedData; 84 | } 85 | 86 | const startIndex = ( pageNumber - 1 ) * perPage; 87 | const endIndex = ( pageNumber * perPage ); 88 | 89 | return sortedData.slice( startIndex, endIndex ); 90 | } 91 | 92 | /** 93 | * Handler to post-process the paginated data, and determine which data to actually display. 94 | * 95 | * @param processSteps - The result of each processing steps, stored in an object. Each step is the result of one of the processing function 96 | * @returns the processed values to set on the datatable. 97 | */ 98 | public displayHandler( { sorted, paged }: IDisplayHandlerParam ): TMaybePromise> { 99 | return { 100 | rows: paged, 101 | totalRowCount: sorted.length, 102 | }; 103 | } 104 | 105 | /** 106 | * Check if the provided row contains the filter string in *any* column. 107 | * 108 | * @param row - The data row to search in. 109 | * @param filterString - The string to match in a column. 110 | * @param columns - The list of columns in the table. 111 | * @returns `true` if any column contains the searched string. 112 | */ 113 | public rowMatches( row: TRow, filterString: string, columns: Array> ): boolean { 114 | const filterableColumns = columns 115 | .filter( column => column.filterable ); 116 | return filterableColumns.length > 0 ? 117 | columns.some( column => column.matches( row, filterString ) ) : 118 | true; 119 | } 120 | } 121 | --------------------------------------------------------------------------------