├── .husky
├── .gitignore
└── pre-commit
├── .gitignore
├── connectors
└── jquery
│ ├── .npmignore
│ ├── index.js
│ ├── package.json
│ ├── scripts
│ └── build.js
│ └── README.md
├── .npmignore
├── src
├── util.js
├── by_column_filter.js
├── SelectionHelper.js
├── column_collection.js
└── row_collection.js
├── LICENSE
├── package.json
├── eslint.config.mjs
├── example
├── example.css
├── example_borders.css
└── example.html
├── scripts
└── build.js
└── README.md
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | dist/
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run lint
--------------------------------------------------------------------------------
/connectors/jquery/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | scripts/
3 | tests/
4 | .eslintignore
5 | .eslintrc.js
6 | .mocharc.yml
7 | .npmignore
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | scripts/
3 | tests/
4 | .eslintignore
5 | .eslintrc.js
6 | .huskyrc
7 | .mocharc.yml
8 | .npmignore
9 | .husky/
10 | example/
11 | connectors/
12 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const find = function find (array, predicate) {
4 | for (let i = 0, len = array.length; i >= 0 && i < len; i += 1) {
5 | if (predicate(array[i], i, array))
6 | return array[i];
7 | }
8 | };
9 |
10 | export const htmlEncode = function htmlEncode (text) {
11 | return text.replace(/&/g, "&")
12 | .replace(//g, ">")
14 | .replace(/'/g, "'")
15 | .replace(/"/g, """)
16 | .replace(/\n/g, '
');
17 | };
18 |
--------------------------------------------------------------------------------
/src/by_column_filter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function ByColumnFilter (row, args) {
4 |
5 | let column = args.column;
6 | let keyword = args.keyword == null ? '' : args.keyword.toString();
7 |
8 | if (!keyword || !column) return true;
9 |
10 | let actualVal = row[column];
11 | if (actualVal == null) {
12 | return false;
13 | }
14 |
15 | actualVal = actualVal.toString();
16 |
17 | if (!args.caseSensitive) {
18 | actualVal = actualVal.toLowerCase();
19 | keyword = keyword.toLowerCase();
20 | }
21 |
22 | return actualVal.indexOf(keyword) !== -1;
23 | }
24 |
25 | export default ByColumnFilter;
--------------------------------------------------------------------------------
/connectors/jquery/index.js:
--------------------------------------------------------------------------------
1 | import jQuery from 'jquery';
2 | import DGTable from '@danielgindi/dgtable.js';
3 |
4 | export default class DGTableJQuery extends DGTable {
5 | constructor(options) {
6 | super(options);
7 |
8 | this.$el = jQuery(this.el)
9 | .data('dgtable', this)
10 | .on('remove', () => this.destroy());
11 |
12 | this.on('headerrowdestroy', () => {
13 | const headerRow = this._p?.headerRow;
14 | if (!headerRow) return;
15 |
16 | jQuery(headerRow).find(`div.${this._o.tableClassName}-header-cell`).remove();
17 | });
18 | }
19 |
20 | destroy() {
21 | if (this._p?.table)
22 | jQuery(this._p.table).empty();
23 | if (this._p?.tbody)
24 | jQuery(this._p.tbody).empty();
25 | return super.destroy();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Daniel Cohen Gindi (danielgindi@gmail.com)
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.
--------------------------------------------------------------------------------
/connectors/jquery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.dgtable",
3 | "description": "High-performance table View for jQuery",
4 | "version": "2.0.5",
5 | "main": "dist/lib.cjs.min.js",
6 | "module": "dist/lib.es6.min.js",
7 | "broswer": "dist/lib.umd.min.js",
8 | "type": "module",
9 | "author": {
10 | "name": "Daniel Cohen Gindi",
11 | "email": "danielgindi@gmail.com"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/danielgindi/dgtable.js.git"
16 | },
17 | "scripts": {
18 | "build": "node scripts/build.js",
19 | "prepublishOnly": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || npm run build"
20 | },
21 | "bugs": {
22 | "url": "https://github.com/danielgindi/dgtable.js/issues"
23 | },
24 | "licenses": [
25 | {
26 | "type": "MIT",
27 | "url": "https://github.com/danielgindi/dgtable.js/blob/master/LICENSE"
28 | }
29 | ],
30 | "dependencies": {
31 | "@danielgindi/dgtable.js": "^2.0.5"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "^7.28.5",
35 | "@babel/preset-env": "^7.28.5",
36 | "@babel/runtime": "^7.28.4",
37 | "@rollup/plugin-babel": "^6.1.0",
38 | "@rollup/plugin-terser": "^0.4.4",
39 | "core-js": "^3.47.0",
40 | "fs-extra": "^11.3.2",
41 | "globals": "^15",
42 | "rollup": "^4.53.3"
43 | },
44 | "peerDependencies": {
45 | "jquery": "*"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@danielgindi/dgtable.js",
3 | "description": "High-performance table View for vanilla JS",
4 | "version": "2.0.5",
5 | "main": "dist/lib.cjs.min.js",
6 | "module": "dist/lib.es6.min.js",
7 | "broswer": "dist/lib.umd.min.js",
8 | "type": "module",
9 | "author": {
10 | "name": "Daniel Cohen Gindi",
11 | "email": "danielgindi@gmail.com"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/danielgindi/dgtable.js.git"
16 | },
17 | "scripts": {
18 | "build": "npm run lint && node ./scripts/build.js",
19 | "lint": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || eslint -f codeframe ./",
20 | "lint-fix": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || eslint -f codeframe --fix ./",
21 | "prepare": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || husky",
22 | "prepublishOnly": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || npm run build"
23 | },
24 | "bugs": {
25 | "url": "https://github.com/danielgindi/dgtable.js/issues"
26 | },
27 | "licenses": [
28 | {
29 | "type": "MIT",
30 | "url": "https://github.com/danielgindi/dgtable.js/blob/master/LICENSE"
31 | }
32 | ],
33 | "dependencies": {
34 | "@danielgindi/dom-utils": "^1.0.11",
35 | "@danielgindi/virtual-list-helper": "^1.0.17",
36 | "mitt": "^3.0.1"
37 | },
38 | "devDependencies": {
39 | "@babel/core": "^7.28.5",
40 | "@babel/preset-env": "^7.28.5",
41 | "@babel/runtime": "^7.28.4",
42 | "@rollup/plugin-babel": "^6.1.0",
43 | "@rollup/plugin-commonjs": "^28.0.9",
44 | "@rollup/plugin-node-resolve": "^15.3.1",
45 | "@rollup/plugin-terser": "^0.4.4",
46 | "core-js": "^3.47.0",
47 | "eslint": "^9.39.1",
48 | "eslint-formatter-codeframe": "^7.32.2",
49 | "fs-extra": "^11.3.2",
50 | "globals": "^15",
51 | "husky": "^9.1.7",
52 | "pinst": "^3.0.0",
53 | "rollup": "^4.53.3"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/SelectionHelper.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 |
3 | 'use strict';
4 |
5 | // saveSelection/restoreSelection courtesy of Tim Down, with my improvements
6 | // https://stackoverflow.com/questions/13949059/persisting-the-changes-of-range-objects-after-selection-in-html/13950376#13950376
7 |
8 | function isChildOf(child, parent) {
9 | while ((child = child.parentNode) && child !== parent);
10 | return !!child;
11 | }
12 |
13 | class SelectionHelper {
14 |
15 | static saveSelection(el) {
16 | let range = window.getSelection().getRangeAt(0);
17 |
18 | if (el !== range.commonAncestorContainer && !isChildOf(range.commonAncestorContainer, el))
19 | return null;
20 |
21 | let preSelectionRange = range.cloneRange();
22 | preSelectionRange.selectNodeContents(el);
23 | preSelectionRange.setEnd(range.startContainer, range.startOffset);
24 | let start = preSelectionRange.toString().length;
25 |
26 | return {
27 | start: start,
28 | end: start + range.toString().length,
29 | };
30 | }
31 |
32 | static restoreSelection(el, savedSel) {
33 | let charIndex = 0;
34 | let nodeStack = [el], node, foundStart = false, stop = false;
35 | let range = document.createRange();
36 | range.setStart(el, 0);
37 | range.collapse(true);
38 |
39 | while (!stop && (node = nodeStack.pop())) {
40 | if (node.nodeType === 3) {
41 | let nextCharIndex = charIndex + node.length;
42 | if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
43 | range.setStart(node, savedSel.start - charIndex);
44 | foundStart = true;
45 | }
46 | if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
47 | range.setEnd(node, savedSel.end - charIndex);
48 | stop = true;
49 | }
50 | charIndex = nextCharIndex;
51 | } else {
52 | let i = node.childNodes.length;
53 | while (i--) {
54 | nodeStack.push(node.childNodes[i]);
55 | }
56 | }
57 | }
58 |
59 | let sel = window.getSelection();
60 | sel.removeAllRanges();
61 | sel.addRange(range);
62 | }
63 | }
64 |
65 | export default SelectionHelper;
66 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 | import js from "@eslint/js";
5 | import { FlatCompat } from "@eslint/eslintrc";
6 |
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 | const compat = new FlatCompat({
10 | baseDirectory: __dirname,
11 | recommendedConfig: js.configs.recommended,
12 | allConfig: js.configs.all,
13 | });
14 |
15 | export default [
16 | {
17 | ignores: [
18 | "tests/**/*",
19 | "test/**/*",
20 | "!tests/**/*tests.js",
21 | "!test/**/*tests.js",
22 | "!tests/**/*_test.js",
23 | "!test/**/*_test.js",
24 | "!tests/**/*_helpers.js",
25 | "!test/**/*_helpers.js",
26 | "dist/**/*",
27 | "connectors/jquery/dist/**/*",
28 | "example/**/*",
29 | ],
30 | },
31 | ...compat.extends("eslint:recommended"),
32 | {
33 | languageOptions: {
34 | globals: {
35 | ...globals.browser,
36 | ...globals.node,
37 | },
38 |
39 | ecmaVersion: 2020,
40 | sourceType: "module",
41 | },
42 |
43 | rules: {
44 | semi: ["warn", "always"],
45 | "comma-dangle": ["warn", "always-multiline"],
46 | "comma-style": ["warn", "last"],
47 | "no-var": ["warn"],
48 |
49 | "arrow-spacing": ["warn", {
50 | before: true,
51 | after: true,
52 | }],
53 |
54 | "space-infix-ops": ["warn", {
55 | int32Hint: true,
56 | }],
57 |
58 | "keyword-spacing": ["warn", {
59 | before: true,
60 | after: true,
61 | }],
62 |
63 | "space-unary-ops": ["warn", {
64 | words: true,
65 | nonwords: false,
66 | }],
67 |
68 | "comma-spacing": ["warn", {
69 | before: false,
70 | after: true,
71 | }],
72 |
73 | "object-curly-spacing": ["warn", "always"],
74 |
75 | "no-unused-vars": ["warn", {
76 | vars: "all",
77 | args: "after-used",
78 | varsIgnorePattern: "[iIgnored]|^_",
79 | ignoreRestSiblings: false,
80 | argsIgnorePattern: "^_",
81 | caughtErrors: "all",
82 | caughtErrorsIgnorePattern: "^ignore",
83 | }],
84 |
85 | "no-console": "warn",
86 | "no-extra-semi": "warn",
87 | "no-unreachable": "warn",
88 |
89 | "no-fallthrough": ["error", {
90 | commentPattern: "break[\\s\\w]*omitted|fallthrough",
91 | }],
92 |
93 | "no-useless-escape": "warn",
94 | "no-constant-condition": "warn",
95 | "no-return-await": "warn",
96 | "no-async-promise-executor": "warn",
97 | },
98 | }, {
99 | files: [
100 | "tests/**/*tests.js",
101 | "test/**/*tests.js",
102 | "tests/**/*_test.js",
103 | "test/**/*_test.js",
104 | ],
105 |
106 | languageOptions: {
107 | globals: {
108 | ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, "off"])),
109 | ...globals.node,
110 | ...globals.mocha,
111 | ...globals.jest,
112 | },
113 | },
114 |
115 | rules: {
116 | "no-console": "off",
117 | },
118 | }, {
119 | files: ["*db*/**/migrations/**/*.js"],
120 |
121 | rules: {
122 | "no-console": "off",
123 | },
124 | }, {
125 | files: ["scripts/**/*.js", "**/.eslintrc.js"],
126 |
127 | languageOptions: {
128 | globals: {
129 | ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, "off"])),
130 | ...globals.node,
131 | },
132 | },
133 | },
134 | ];
135 |
--------------------------------------------------------------------------------
/src/column_collection.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Define class RowCollection
4 | function ColumnCollection () {
5 |
6 | // Instantiate an Array. Seems like the `.length = ` of an inherited Array does not work well.
7 | // I will not use the IFRAME solution either in fear of memory leaks, and we're supporting large datasets...
8 | let collection = [];
9 |
10 | // Synthetically set the 'prototype'
11 | Object.assign(collection, ColumnCollection.prototype);
12 |
13 | // Call initializer
14 | collection.initialize.apply(collection, arguments);
15 |
16 | return collection;
17 | }
18 |
19 | // Inherit Array
20 | ColumnCollection.prototype = [];
21 |
22 | ColumnCollection.prototype.initialize = function () {
23 |
24 | };
25 |
26 | /**
27 | * Get the column by this name
28 | * @param {string} column column name
29 | * @returns {Object} the column object
30 | */
31 | ColumnCollection.prototype.get = function (column) {
32 | for (let i = 0, len = this.length; i < len; i++) {
33 | if (this[i].name === column) {
34 | return this[i];
35 | }
36 | }
37 | return null;
38 | };
39 |
40 | /**
41 | * Get the index of the column by this name
42 | * @param {string} column column name
43 | * @returns {int} the index of this column
44 | */
45 | ColumnCollection.prototype.indexOf = function (column) {
46 | for (let i = 0, len = this.length; i < len; i++) {
47 | if (this[i].name === column) {
48 | return i;
49 | }
50 | }
51 | return -1;
52 | };
53 |
54 | /**
55 | * Get the column by the specified order
56 | * @param {number} order the column's order
57 | * @returns {Object} the column object
58 | */
59 | ColumnCollection.prototype.getByOrder = function (order) {
60 | for (let i = 0, len = this.length; i < len; i++) {
61 | if (this[i].order === order) {
62 | return this[i];
63 | }
64 | }
65 | return null;
66 | };
67 |
68 | /**
69 | * Normalize order
70 | * @returns {ColumnCollection} self
71 | */
72 | ColumnCollection.prototype.normalizeOrder = function () {
73 | let ordered = [], i;
74 | for (i = 0; i < this.length; i++) {
75 | ordered.push(this[i]);
76 | }
77 | ordered.sort(function(col1, col2){ return col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0); });
78 | for (i = 0; i < ordered.length; i++) {
79 | ordered[i].order = i;
80 | }
81 | return this;
82 | };
83 |
84 | /**
85 | * Get the array of columns, order by the order property
86 | * @returns {Array