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 |
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 |
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 |
26 |
27 |
Showing rows {{pagination.from}} to {{pagination.to}} of {{pagination.of}} items.
28 |
29 |
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 |
50 |
55 |
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 |
73 | Nothing to see here
74 |
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 |
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 | `` :
24 | ``,
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 | `` :
60 | ``,
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