├── .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} ordered array of columns 87 | */ 88 | ColumnCollection.prototype.getColumns = function () { 89 | let cols = []; 90 | for (let i = 0, column; i < this.length; i++) { 91 | column = this[i]; 92 | cols.push(column); 93 | } 94 | cols.sort((col1, col2) => col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0)); 95 | return cols; 96 | }; 97 | 98 | /** 99 | * Get the array of visible columns, order by the order property 100 | * @returns {Array} ordered array of visible columns 101 | */ 102 | ColumnCollection.prototype.getVisibleColumns = function () { 103 | let cols = []; 104 | for (let i = 0, column; i < this.length; i++) { 105 | column = this[i]; 106 | if (column.visible) { 107 | cols.push(column); 108 | } 109 | } 110 | cols.sort((col1, col2) => col1.order < col2.order ? -1 : (col1.order > col2.order ? 1 : 0)); 111 | return cols; 112 | }; 113 | 114 | /** 115 | * @returns {int} maximum order currently in the array 116 | */ 117 | ColumnCollection.prototype.getMaxOrder = function () { 118 | let order = 0; 119 | for (let i = 0, column; i < this.length; i++) { 120 | column = this[i]; 121 | if (column.order > order) { 122 | order = column.order; 123 | } 124 | } 125 | return order; 126 | }; 127 | 128 | /** 129 | * Move a column to a new spot in the collection 130 | * @param {Object} src the column to move 131 | * @param {Object} dest the destination column 132 | * @returns {ColumnCollection} self 133 | */ 134 | ColumnCollection.prototype.moveColumn = function (src, dest) { 135 | if (src && dest) { 136 | let srcOrder = src.order, destOrder = dest.order, i, col; 137 | if (srcOrder < destOrder) { 138 | for (i = srcOrder + 1; i <= destOrder; i++) { 139 | col = this.getByOrder(i); 140 | col.order--; 141 | } 142 | } else { 143 | for (i = srcOrder - 1; i >= destOrder; i--) { 144 | col = this.getByOrder(i); 145 | col.order++; 146 | } 147 | } 148 | src.order = destOrder; 149 | } 150 | return this; 151 | }; 152 | 153 | export default ColumnCollection; 154 | -------------------------------------------------------------------------------- /connectors/jquery/scripts/build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Path from 'node:path'; 4 | import { readFile, writeFile, rmdir, mkdir } from 'node:fs/promises'; 5 | import { rollup } from 'rollup'; 6 | import { babel } from '@rollup/plugin-babel'; 7 | import PluginTerser from '@rollup/plugin-terser'; 8 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 9 | import PluginCommonjs from '@rollup/plugin-commonjs'; 10 | import { fileURLToPath } from 'node:url'; 11 | 12 | (async () => { 13 | 14 | try { 15 | await rmdir('./dist', { recursive: true }); 16 | } catch (ignored) { /* ignored */ } 17 | await mkdir('./dist'); 18 | 19 | const rollupTasks = [{ 20 | dest: 'dist/lib.es6.js', 21 | sourceMap: true, 22 | outputFormat: 'esm', 23 | babelTargets: { 24 | node: 10, 25 | }, 26 | minified: false, 27 | ecmaVersion: 6, 28 | }, { 29 | dest: 'dist/lib.es6.min.js', 30 | sourceMap: true, 31 | outputFormat: 'esm', 32 | babelTargets: { 33 | node: 10, 34 | }, 35 | minified: true, 36 | ecmaVersion: 6, 37 | }, { 38 | dest: 'dist/lib.umd.js', 39 | sourceMap: true, 40 | outputFormat: 'umd', 41 | outputExports: 'default', 42 | babelTargets: '> 0.25%, not dead', 43 | minified: false, 44 | ecmaVersion: 6, 45 | outputName: 'DGTableJQuery', 46 | }, { 47 | dest: 'dist/lib.umd.min.js', 48 | sourceMap: true, 49 | outputFormat: 'umd', 50 | outputExports: 'default', 51 | babelTargets: '> 0.25%, not dead', 52 | minified: true, 53 | ecmaVersion: 6, 54 | outputName: 'DGTableJQuery', 55 | }, { 56 | dest: 'dist/lib.cjs.js', 57 | sourceMap: true, 58 | outputFormat: 'cjs', 59 | outputExports: 'default', 60 | babelTargets: { 61 | node: 10, 62 | }, 63 | minified: false, 64 | ecmaVersion: 6, 65 | }, { 66 | dest: 'dist/lib.cjs.min.js', 67 | sourceMap: true, 68 | outputFormat: 'cjs', 69 | outputExports: 'default', 70 | babelTargets: { 71 | node: 10, 72 | }, 73 | minified: true, 74 | ecmaVersion: 6, 75 | }]; 76 | 77 | const inputFile = 'index.js'; 78 | 79 | for (let task of rollupTasks) { 80 | console.info('Generating ' + task.dest + '...'); 81 | 82 | let plugins = [ 83 | nodeResolve({ 84 | mainFields: ['module', 'main'], 85 | }), 86 | PluginCommonjs({}), 87 | ]; 88 | 89 | const pkg = JSON.parse(await readFile(Path.join(Path.dirname(fileURLToPath(import.meta.url)), '../package.json'), { encoding: 'utf8' })); 90 | const banner = [ 91 | `/*!`, 92 | ` * ${pkg.name} ${pkg.version}`, 93 | ` * ${pkg.repository.url}`, 94 | ' */\n', 95 | ].join('\n'); 96 | 97 | if (task.babelTargets) { 98 | plugins.push(babel({ 99 | sourceMap: !!task.sourceMap, 100 | presets: [ 101 | ['@babel/env', { 102 | targets: task.babelTargets, 103 | useBuiltIns: 'usage', 104 | corejs: 3, 105 | }], 106 | ], 107 | compact: false, 108 | minified: false, 109 | comments: true, 110 | retainLines: true, 111 | babelHelpers: 'bundled', 112 | exclude: 'node_modules/**/core-js/**/*', 113 | })); 114 | } 115 | 116 | if (task.minified) { 117 | plugins.push(PluginTerser({ 118 | toplevel: true, 119 | compress: { 120 | ecma: task.ecmaVersion, 121 | passes: 2, 122 | }, 123 | sourceMap: !!task.sourceMap, 124 | })); 125 | } 126 | 127 | const bundle = await rollup({ 128 | preserveSymlinks: true, 129 | treeshake: true, 130 | onwarn(warning, warn) { 131 | if (warning.code === 'THIS_IS_UNDEFINED') return; 132 | warn(warning); 133 | }, 134 | input: inputFile, 135 | plugins: plugins, 136 | external: ['jquery', 'jQuery'], 137 | }); 138 | 139 | let generated = await bundle.generate({ 140 | name: task.outputName, 141 | sourcemap: task.sourceMap ? 'hidden' : false, 142 | format: task.outputFormat, 143 | globals: { 144 | jquery: 'jQuery', 145 | }, 146 | exports: task.outputExports, 147 | }); 148 | 149 | let code = generated.output[0].code; 150 | 151 | if (task.sourceMap && generated.output[0].map) { 152 | let sourceMapOutPath = task.dest + '.map'; 153 | await writeFile(sourceMapOutPath, generated.output[0].map.toString()); 154 | code += '\n//# sourceMappingURL=' + Path.basename(sourceMapOutPath); 155 | } 156 | 157 | await writeFile(task.dest, code); 158 | } 159 | 160 | console.info('Done.'); 161 | 162 | })(); 163 | -------------------------------------------------------------------------------- /example/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "HelveticaNeue-Medium", "HelveticaNeue Medium", "Verdana", "Tahoma"; 3 | padding: 30px; 4 | } 5 | 6 | .dgtable-wrapper * { 7 | box-sizing: content-box; 8 | } 9 | 10 | .dgtable-wrapper { 11 | border: solid 1px #ccc; 12 | } 13 | 14 | .dgtable-wrapper.rtl { 15 | direction: rtl; 16 | } 17 | 18 | .dgtable { 19 | border-top: solid 1px #ccc; 20 | max-width: 100%; 21 | background-color: transparent; 22 | } 23 | 24 | .dgtable-header { 25 | max-width: 100%; 26 | overflow: hidden; 27 | background: #eee; 28 | } 29 | 30 | .dgtable-header-row { 31 | height: 26px; 32 | } 33 | 34 | .dgtable-header-cell, .dgtable-cell-preview.header { 35 | padding: 4px; 36 | height: 26px; 37 | background: #ddd; 38 | font-size: 13px; 39 | line-height: 16px; 40 | font-weight: bold; 41 | cursor: default; 42 | text-align: left; 43 | } 44 | 45 | .dgtable-header-cell { 46 | float: left; 47 | border-left: solid 1px #ccc; 48 | } 49 | 50 | .dgtable-header-cell:first-child { 51 | border-left: 0; 52 | } 53 | 54 | .dgtable-header-cell > div, .dgtable-cell-preview.header > div { 55 | border: 1px dashed transparent; 56 | white-space: nowrap; 57 | } 58 | 59 | .dgtable-header-cell > div { 60 | overflow-x: hidden; 61 | text-overflow: ellipsis; 62 | } 63 | 64 | .dgtable-header-cell.drag-over > div, .dgtable-cell-preview.header.drag-over > div { 65 | border-color: #666; 66 | background: #bbb; 67 | } 68 | 69 | .dgtable-header-cell.sortable, .dgtable-cell-preview.header.sortable { 70 | cursor: pointer; 71 | } 72 | 73 | .dgtable-header-cell.sorted .sort-arrow { 74 | float: right; 75 | display: inline-block; 76 | width: 15px; 77 | height: 6px; 78 | margin: 5px 0 0 0; 79 | background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAMAAAAi7JTKAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMAUExURQNOov///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH52dxwAAAACdFJOU/8A5bcwSgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB9JREFUCB0FwQEBAAAAgJD8Px0VoFKgRKEIpRBUEAgMBlQAL3y6umEAAAAASUVORK5CYII=") no-repeat center center; 80 | } 81 | 82 | .dgtable-header-cell.sorted.desc .sort-arrow { 83 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAMAAAAi7JTKAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMAUExURQNOov///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH52dxwAAAACdFJOU/8A5bcwSgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB9JREFUCB0FwQEBAAAAgJD8Px2BQFBBKIUoFClQKkANBywAL6PcDsUAAAAASUVORK5CYII="); 84 | } 85 | 86 | .dgtable-wrapper.rtl .dgtable-header-cell.sorted .sort-arrow { 87 | float: left; 88 | } 89 | 90 | .dgtable-wrapper.rtl .dgtable-header-cell { 91 | float: right; 92 | border-left: 0; 93 | border-right: solid 1px #ccc; 94 | } 95 | 96 | .dgtable-wrapper.rtl .dgtable-header-cell:first-child { 97 | float: right; 98 | border-right: 0; 99 | } 100 | 101 | .dgtable-row { 102 | border-top: solid 1px #ccc; 103 | height: 28px; 104 | } 105 | 106 | .dgtable-row-alt { 107 | background: #fafafa; 108 | } 109 | 110 | .dgtable-row:first-child { 111 | border-top: 0; 112 | height: 29px; 113 | } 114 | 115 | .dgtable.virtual .dgtable-row { 116 | border-top: 0; 117 | border-bottom: solid 1px #ccc; 118 | } 119 | 120 | .dgtable-cell, .dgtable-cell-preview { 121 | padding: 4px 4px 4px; 122 | font-size: 16px; 123 | line-height: 19px; 124 | } 125 | 126 | .dgtable-cell { 127 | float: left; 128 | border-left: solid 1px #ccc; 129 | min-height: 100%; 130 | } 131 | 132 | .dgtable-wrapper.rtl .dgtable-cell { 133 | float: right; 134 | border-left: 0; 135 | border-right: solid 1px #ccc; 136 | } 137 | 138 | .dgtable-wrapper.rtl .dgtable-cell:first-child { 139 | float: right; 140 | border-right: 0; 141 | } 142 | 143 | .dgtable-cell > div, .dgtable-cell-preview > div { 144 | max-height: 100%; 145 | white-space: nowrap; 146 | overflow: hidden 147 | } 148 | 149 | .dgtable-cell:first-child { 150 | border-left: 0; 151 | } 152 | -------------------------------------------------------------------------------- /example/example_borders.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "HelveticaNeue-Medium", "HelveticaNeue Medium", "Verdana", "Tahoma"; 3 | padding: 30px; 4 | } 5 | 6 | .dgtable-wrapper { 7 | border: solid 1px #ccc; 8 | } 9 | 10 | .dgtable-wrapper.rtl { 11 | direction: rtl; 12 | } 13 | 14 | .dgtable { 15 | border-top: solid 1px #ccc; 16 | max-width: 100%; 17 | background-color: transparent; 18 | } 19 | 20 | .dgtable-header { 21 | max-width: 100%; 22 | overflow: hidden; 23 | background: #eee; 24 | } 25 | 26 | .dgtable-header-row { 27 | height: 26px; 28 | } 29 | 30 | .dgtable-header-cell, .dgtable-cell-preview.header { 31 | box-sizing: border-box; 32 | padding: 4px; 33 | height: 26px; 34 | background: #ddd; 35 | font-size: 13px; 36 | line-height: 16px; 37 | font-weight: bold; 38 | cursor: default; 39 | text-align: left; 40 | } 41 | 42 | .dgtable-header-cell { 43 | float: left; 44 | border-left: solid 1px #ccc; 45 | } 46 | 47 | .dgtable-header-cell:first-child { 48 | border-left: 0; 49 | } 50 | 51 | .dgtable-header-cell > div, .dgtable-cell-preview.header > div { 52 | margin: -1px; 53 | border: 1px dashed transparent; 54 | white-space: nowrap; 55 | } 56 | 57 | .dgtable-header-cell > div { 58 | overflow-x: hidden; 59 | text-overflow: ellipsis; 60 | } 61 | 62 | .dgtable-header-cell.drag-over > div, .dgtable-cell-preview.header.drag-over > div { 63 | border-color: #666; 64 | background: #bbb; 65 | } 66 | 67 | .dgtable-header-cell.sortable, .dgtable-cell-preview.header.sortable { 68 | cursor: pointer; 69 | } 70 | 71 | .dgtable-header-cell.sorted .sort-arrow { 72 | float: right; 73 | display: inline-block; 74 | width: 15px; 75 | height: 6px; 76 | margin: 5px 0 0 0; 77 | background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAMAAAAi7JTKAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMAUExURQNOov///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH52dxwAAAACdFJOU/8A5bcwSgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB9JREFUCB0FwQEBAAAAgJD8Px0VoFKgRKEIpRBUEAgMBlQAL3y6umEAAAAASUVORK5CYII=") no-repeat center center; 78 | } 79 | 80 | .dgtable-header-cell.sorted.desc .sort-arrow { 81 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAGCAMAAAAi7JTKAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMAUExURQNOov///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH52dxwAAAACdFJOU/8A5bcwSgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB9JREFUCB0FwQEBAAAAgJD8Px2BQFBBKIUoFClQKkANBywAL6PcDsUAAAAASUVORK5CYII="); 82 | } 83 | 84 | .dgtable-wrapper.rtl .dgtable-header-cell.sorted .sort-arrow { 85 | float: left; 86 | } 87 | 88 | .dgtable-wrapper.rtl .dgtable-header-cell { 89 | float: right; 90 | border-left: 0; 91 | border-right: solid 1px #ccc; 92 | } 93 | 94 | .dgtable-wrapper.rtl .dgtable-header-cell:first-child { 95 | float: right; 96 | border-right: 0; 97 | } 98 | 99 | .dgtable-row { 100 | border-top: solid 1px #ccc; 101 | height: 28px; 102 | } 103 | 104 | .dgtable-row-alt { 105 | background: #fafafa; 106 | } 107 | 108 | .dgtable-row:first-child { 109 | border-top: 0; 110 | } 111 | 112 | .dgtable.virtual .dgtable-row { 113 | border-top: 0; 114 | border-bottom: solid 1px #ccc; 115 | } 116 | 117 | .dgtable-cell, .dgtable-cell-preview { 118 | box-sizing: border-box; 119 | padding: 5px 4px 3px; 120 | font-size: 16px; 121 | line-height: 19px; 122 | } 123 | 124 | .dgtable-cell { 125 | float: left; 126 | border-left: solid 1px #ccc; 127 | min-height: 100%; 128 | } 129 | 130 | .dgtable-wrapper.rtl .dgtable-cell { 131 | float: right; 132 | border-left: 0; 133 | border-right: solid 1px #ccc; 134 | } 135 | 136 | .dgtable-wrapper.rtl .dgtable-cell:first-child { 137 | float: right; 138 | border-right: 0; 139 | } 140 | 141 | .dgtable-cell > div, .dgtable-cell-preview > div { 142 | max-height: 100%; 143 | white-space: nowrap; 144 | overflow: hidden 145 | } 146 | 147 | .dgtable-cell:first-child { 148 | border-left: 0; 149 | } 150 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Path from 'node:path'; 4 | import { readFile, writeFile, rmdir, mkdir } from 'node:fs/promises'; 5 | import { rollup } from 'rollup'; 6 | import MagicString from 'magic-string'; 7 | import { babel } from '@rollup/plugin-babel'; 8 | import PluginTerser from '@rollup/plugin-terser'; 9 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 10 | import PluginCommonjs from '@rollup/plugin-commonjs'; 11 | import { fileURLToPath } from 'node:url'; 12 | 13 | (async () => { 14 | 15 | await rmdir('./dist', { recursive: true }); 16 | await mkdir('./dist'); 17 | 18 | const rollupTasks = [{ 19 | dest: 'dist/lib.es6.js', 20 | sourceMap: true, 21 | outputFormat: 'esm', 22 | babelTargets: { 23 | node: 10, 24 | }, 25 | minified: false, 26 | ecmaVersion: 6, 27 | }, { 28 | dest: 'dist/lib.es6.min.js', 29 | sourceMap: true, 30 | outputFormat: 'esm', 31 | babelTargets: { 32 | node: 10, 33 | }, 34 | minified: true, 35 | ecmaVersion: 6, 36 | }, { 37 | dest: 'dist/lib.umd.js', 38 | sourceMap: true, 39 | outputFormat: 'umd', 40 | outputExports: 'default', 41 | babelTargets: '> 0.25%, not dead', 42 | minified: false, 43 | ecmaVersion: 6, 44 | outputName: 'DGTable', 45 | }, { 46 | dest: 'dist/lib.umd.min.js', 47 | sourceMap: true, 48 | outputFormat: 'umd', 49 | outputExports: 'default', 50 | babelTargets: '> 0.25%, not dead', 51 | minified: true, 52 | ecmaVersion: 6, 53 | outputName: 'DGTable', 54 | }, { 55 | dest: 'dist/lib.cjs.js', 56 | sourceMap: true, 57 | outputFormat: 'cjs', 58 | outputExports: 'default', 59 | babelTargets: { 60 | node: 10, 61 | }, 62 | minified: false, 63 | ecmaVersion: 6, 64 | }, { 65 | dest: 'dist/lib.cjs.min.js', 66 | sourceMap: true, 67 | outputFormat: 'cjs', 68 | outputExports: 'default', 69 | babelTargets: { 70 | node: 10, 71 | }, 72 | minified: true, 73 | ecmaVersion: 6, 74 | }]; 75 | 76 | const inputFile = 'src/index.js'; 77 | 78 | for (let task of rollupTasks) { 79 | console.info('Generating ' + task.dest + '...'); 80 | 81 | let plugins = [ 82 | nodeResolve({ 83 | mainFields: ['module', 'main'], 84 | }), 85 | PluginCommonjs({}), 86 | ]; 87 | 88 | const pkg = JSON.parse(await readFile(Path.join(Path.dirname(fileURLToPath(import.meta.url)), '../package.json'), { encoding: 'utf8' })); 89 | const banner = [ 90 | `/*!`, 91 | ` * ${pkg.name} ${pkg.version}`, 92 | ` * ${pkg.repository.url}`, 93 | ' */\n', 94 | ].join('\n'); 95 | 96 | if (task.babelTargets) { 97 | plugins.push(babel({ 98 | sourceMap: !!task.sourceMap, 99 | presets: [ 100 | ['@babel/env', { 101 | targets: task.babelTargets, 102 | useBuiltIns: 'usage', 103 | corejs: 3, 104 | }], 105 | ], 106 | compact: false, 107 | minified: false, 108 | comments: true, 109 | retainLines: true, 110 | babelHelpers: 'bundled', 111 | exclude: 'node_modules/**/core-js/**/*', 112 | })); 113 | } 114 | 115 | if (task.minified) { 116 | plugins.push(PluginTerser({ 117 | toplevel: true, 118 | compress: { 119 | ecma: task.ecmaVersion, 120 | passes: 2, 121 | }, 122 | sourceMap: !!task.sourceMap, 123 | })); 124 | } 125 | 126 | plugins.push({ 127 | name: 'banner', 128 | 129 | renderChunk(code, chunk, _outputOptions = {}) { 130 | 131 | const magicString = new MagicString(code); 132 | magicString.prepend(banner); 133 | 134 | return { 135 | code: magicString.toString(), 136 | map: magicString.generateMap({ 137 | hires: true, 138 | }), 139 | }; 140 | }, 141 | }); 142 | 143 | const bundle = await rollup({ 144 | preserveSymlinks: true, 145 | treeshake: true, 146 | onwarn(warning, warn) { 147 | if (warning.code === 'THIS_IS_UNDEFINED') return; 148 | warn(warning); 149 | }, 150 | input: inputFile, 151 | plugins: plugins, 152 | external: [/^@danielgindi\/dom-utils(\/|$)/, /^@danielgindi\/virtual-list-helper(\/|$)/], 153 | }); 154 | 155 | let generated = await bundle.generate({ 156 | name: task.outputName, 157 | sourcemap: task.sourceMap ? 'hidden' : false, 158 | format: task.outputFormat, 159 | globals: { 160 | '@danielgindi/dom-utils/lib/ScrollHelper.js': 'domUtilsScrollHelper', 161 | '@danielgindi/dom-utils/lib/DomEventsSink.js': 'domUtilsDomEventsSink', 162 | '@danielgindi/dom-utils/lib/DomCompat.js': 'domUtilsDomCompat', 163 | '@danielgindi/dom-utils/lib/Css.js': 'domUtilsCss', 164 | '@danielgindi/virtual-list-helper': 'VirtualListHelper', 165 | }, 166 | exports: task.outputExports, 167 | }); 168 | 169 | let code = generated.output[0].code; 170 | 171 | if (task.sourceMap && generated.output[0].map) { 172 | let sourceMapOutPath = task.dest + '.map'; 173 | await writeFile(sourceMapOutPath, generated.output[0].map.toString()); 174 | code += '\n//# sourceMappingURL=' + Path.basename(sourceMapOutPath); 175 | } 176 | 177 | await writeFile(task.dest, code); 178 | } 179 | 180 | console.info('Done.'); 181 | 182 | })(); 183 | -------------------------------------------------------------------------------- /src/row_collection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Define class RowCollection 4 | function RowCollection () { 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, RowCollection.prototype); 12 | 13 | // Call initializer 14 | collection.initialize.apply(collection, arguments); 15 | 16 | return collection; 17 | } 18 | 19 | // Inherit Array 20 | RowCollection.prototype = []; 21 | 22 | RowCollection.prototype.initialize = function (options) { 23 | 24 | options = options || {}; 25 | 26 | /** @field {string} sortColumn */ 27 | this.sortColumn = options.sortColumn == null ? [] : options.sortColumn; 28 | }; 29 | 30 | /** 31 | * @param {Object|Object[]} rows - row or array of rows to add to this collection 32 | * @param {number?} at - position to insert rows at 33 | */ 34 | RowCollection.prototype.add = function (rows, at) { 35 | let isArray = ('splice' in rows && 'length' in rows), i, len; 36 | if (isArray) { 37 | if (typeof at === 'number') { 38 | for (i = 0, len = rows.length; i < len; i++) { 39 | this.splice(at++, 0, rows[i]); 40 | } 41 | } else { 42 | for (i = 0, len = rows.length; i < len; i++) { 43 | this.push(rows[i]); 44 | } 45 | } 46 | } else { 47 | if (typeof at === 'number') { 48 | this.splice(at, 0, rows); 49 | } else { 50 | this.push(rows); 51 | } 52 | } 53 | }; 54 | 55 | /** 56 | * @param {Object|Object[]=} rows Row or array of rows to add to this collection 57 | */ 58 | RowCollection.prototype.reset = function (rows) { 59 | this.length = 0; 60 | if (rows) { 61 | this.add(rows); 62 | } 63 | }; 64 | 65 | /** 66 | * @param {Function} filterFunc - Filtering function 67 | * @param {Object|null?} args - Options to pass to the function 68 | * @returns {RowCollection} success result 69 | */ 70 | RowCollection.prototype.filteredCollection = function (filterFunc, args) { 71 | if (filterFunc && args) { 72 | let rows = new RowCollection({ 73 | sortColumn: this.sortColumn, 74 | onComparatorRequired: this.onComparatorRequired, 75 | customSortingProvider: this.customSortingProvider, 76 | }); 77 | 78 | for (let i = 0, len = this.length, row; i < len; i++) { 79 | row = this[i]; 80 | if (filterFunc(row, args)) { 81 | row['__i'] = i; 82 | rows.push(row); 83 | } 84 | } 85 | return rows; 86 | } else { 87 | return null; 88 | } 89 | }; 90 | 91 | /** 92 | * @type {function(columnName: string, descending: boolean, defaultComparator: function(a,b):number)|null|undefined} 93 | */ 94 | RowCollection.prototype.onComparatorRequired = null; 95 | /** 96 | * @type {function(data: any[], sort: function(any[]):any[]):any[]|null|undefined} 97 | */ 98 | RowCollection.prototype.customSortingProvider = null; 99 | 100 | let nativeSort = RowCollection.prototype.sort; 101 | 102 | function getDefaultComparator(column, descending) { 103 | let columnName = column.column; 104 | let comparePath = column.comparePath || columnName; 105 | if (typeof comparePath === 'string') { 106 | comparePath = comparePath.split('.'); 107 | } 108 | let pathLength = comparePath.length, 109 | hasPath = pathLength > 1, 110 | i; 111 | 112 | let lessVal = descending ? 1 : -1, moreVal = descending ? -1 : 1; 113 | return function(leftRow, rightRow) { 114 | let leftVal = leftRow[comparePath[0]], 115 | rightVal = rightRow[comparePath[0]]; 116 | if (hasPath) { 117 | for (i = 1; i < pathLength; i++) { 118 | leftVal = leftVal && leftVal[comparePath[i]]; 119 | rightVal = rightVal && rightVal[comparePath[i]]; 120 | } 121 | } 122 | if (leftVal === rightVal) return 0; 123 | if (leftVal == null) return lessVal; 124 | if (rightVal == null) return moreVal; 125 | if (leftVal < rightVal) return lessVal; 126 | return moreVal; 127 | }; 128 | } 129 | 130 | /** 131 | * @returns {Function|undefined} the comparator that was used 132 | */ 133 | RowCollection.prototype.sort = function () { 134 | let comparator; 135 | 136 | if (this.sortColumn.length) { 137 | let comparators = []; 138 | 139 | for (let i = 0; i < this.sortColumn.length; i++) { 140 | comparator = null; 141 | const defaultComparator = getDefaultComparator(this.sortColumn[i], this.sortColumn[i].descending); 142 | if (this.onComparatorRequired) { 143 | comparator = this.onComparatorRequired(this.sortColumn[i].column, this.sortColumn[i].descending, defaultComparator); 144 | } 145 | if (!comparator) { 146 | comparator = defaultComparator; 147 | } 148 | comparators.push(comparator.bind(this)); 149 | } 150 | 151 | if (comparators.length === 1) { 152 | comparator = comparators[0]; 153 | } else { 154 | let len = comparators.length, 155 | value; 156 | 157 | comparator = function(leftRow, rightRow) { 158 | for (let i = 0; i < len; i++) { 159 | value = comparators[i](leftRow, rightRow); 160 | if (value !== 0) { 161 | return value; 162 | } 163 | } 164 | return value; 165 | }; 166 | } 167 | 168 | const sorter = data => nativeSort.call(data, comparator); 169 | 170 | if (this.customSortingProvider) { 171 | let results = this.customSortingProvider(this, sorter); 172 | if (results !== this) { 173 | this.splice(0, this.length, ...results); 174 | } 175 | } else { 176 | sorter(this); 177 | } 178 | } 179 | 180 | return comparator; 181 | }; 182 | 183 | export default RowCollection; 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DGTable.js 2 | ========== 3 | 4 | This is a table View for vanilla JS, which is meant to be high-performance, and allow simple user interactions with the UI, such as: 5 | * Sorting 6 | * Sorting by more than one column 7 | * Moving columns 8 | * Resizing columns 9 | * Full cell preview when hovering 10 | * Native RTL support 11 | * Variable row height 12 | 13 | Other features implemented are: 14 | * Mix absolute column widths with relative column widths 15 | * Virtual table mode (to supply high performance with hundreds of thousands of rows). This is the default. 16 | * Non-virtual table mode is fully supported, but for giant amounts of data it is not recommended. 17 | * Option to set a fixed width for the table so resizing (relative) columns will still make sure the table will not be less (and/or more) than the specified width. 18 | * Option to have both scrollbars inside the table. (set `width: DGTable.Width.SCROLL`) 19 | 20 | `jquery.dgtable` users: 21 | * Older `jquery.dgtable` can either keep using `jquery.dgtable`, or migrate to this new version which is more lightweight. 22 | * The new version's API is the same as the old one, except that: 23 | * No `$el` property 24 | * No auto-clear of jQuery data. 25 | * There is now an `emit` method instead of `trigger`. 26 | * Event arguments are now always a single value/object. 27 | * Props on DOM elements are now: `'columnName'` on a cell, `'index'/'vIndex'` on a row, `'columnName'/rowIndex'/'rowVIndex'` on a cellPreview 28 | 29 | My TODO list: 30 | * TODO: Have a simple and accurate API documentation here in the readme 31 | 32 | ## Dev environment 33 | 34 | * Using grunt over Node.js to automate validating and building. 35 | * After installing Node.js, use `npm install`, and `npm install -g grunt-cli` to prepare building environment. 36 | * Use `grunt style` to just test for correct styling. 37 | * Use `grunt build` or just `grunt` to compile for release. 38 | * I am using Google Closure Compiler, because UglifyJS does not work with the JSDoc, and there's a major difference in size & performance of the output code. 39 | * Some features of jshint are disabled because it does not work well with JSDoc which is used for Google Closue Compiler. 40 | * Indentations in my editor are set to 4 spaces, and jshint validates that. 41 | 42 | ## Me 43 | * Hi! I am Daniel Cohen Gindi. Or in short- Daniel. 44 | * danielgindi@gmail.com is my email address. 45 | * That's all you need to know. 46 | 47 | ## Help 48 | 49 | I have invested, and investing, a lot of time in this project. 50 | If you want to help, you could: 51 | * Actually code, and issue pull requests 52 | * Test the library under different conditions and browsers 53 | * Create more demo pages 54 | * Spread the word 55 | * 56 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G22LPLJ79NBYQ) 57 | 58 | ## API 59 | 60 | To create a new table, just use `var myTable = new DGTable(INIT_OPTIONS)`. 61 | 62 | #### `INIT_OPTIONS` 63 | 64 | * **columns**: `COLUMN_OPTIONS[]` (Array of COLUMN_OPTIONS objects) 65 | * **name**: `string` Name of the column 66 | * **label**: `string=name` Used for the header of this column 67 | * **dataPath**: `string=name` Path to the data to show (Defaults to `name`) 68 | * **comparePath**: `string=name` Path to the data to use for comparison (Defaults to `dataPath`) 69 | * **width**: `number|string` 70 | * A simple number (i.e `10`, `30`, `50`) will set an absolute width for the column. 71 | * A percentage (i.e `'30%'`) or a 0-1 decimal value (i.e `0.2`, `0.7`) will set a relative width for the column, out of the full table's width. 72 | * Any other value, like `0`, `null` etc. will set an automatic width mode, base of the header's content length. 73 | * **resizable**: `boolean=true` Is this column resizable? 74 | * **sortable**: `boolean=true` Is this column sortable? 75 | * **movable**: `boolean=true` Is this column movable? 76 | * **visible**: `boolean=true` Is this column visible? 77 | * **cellClasses**: `string` Classes to add to the DOM element of this cell 78 | * **ignoreMin**: `boolean=false` Should this column ignore the minimum width specified for the table columns? 79 | * **height**: `number` Suggested height for the table 80 | * **width**: `DGTable.Width=DGTable.Width.NONE` The way that the width of the table will be handled 81 | * `DGTable.Width.NONE`: No special handling 82 | * `DGTable.Width.AUTO`: Sets the width automatically 83 | * `DGTable.Width.SCROLL`: Creates a horizontal scroll when required 84 | * **virtualTable**: `boolean=true` When set, the table will work in virtual mode, which means only the visible rows are rendered. Rows must have fixed height in this mode. 85 | * **estimatedRowHeight**: `number?` Sets the estimated row height for the table. This is used for virtual table mode, to calculate the estimated scroll size. Will be auto calculated by default. 86 | * **resizableColumns**: `boolean=true` Turns on or off the resizable columns globally. 87 | * **movableColumns**: `boolean=true` Turns on or off the movable columns globally. 88 | * **sortableColumns**: `number=1` How many columns can you sort by, one after another? 89 | * **adjustColumnWidthForSortArrow**: `boolean=true` When set, the columns will automatically grow to accommodate for the sort arrow. 90 | * **relativeWidthGrowsToFillWidth**: `boolean=true` When set, relative width columns automatically grow to fill the table's width. 91 | * **relativeWidthShrinksToFillWidth**: `boolean=false` When set, relative width columns automatically shrink to fill the table's width. 92 | * **convertColumnWidthsToRelative**: `boolean=false` When set, auto-width columns are automatically converted to relatives. 93 | * **autoFillTableWidth**: `boolean=false` When set, columns are stretched proportionally to fill the table width (only if there is space left). Will supersede `relativeWidthGrowsToFillWidth` in the future. 94 | * **allowCancelSort**: `boolean=true` When set, the sorting arrows will have 3 modes - asc, desc, and cancelled. 95 | * **cellClasses**: `string` Classes to add to the DOM element of all cells 96 | * **sortColumn**: `string|string[]|COLUMN_SORT_OPTIONS|COLUMN_SORT_OPTIONS[]` Columns to sort by 97 | * Can be a column or an array of columns. 98 | * Each column is a `string` or a `COLUMN_SORT_OPTIONS`: 99 | * **column**: `string` Column name 100 | * **descending**: `boolean=false` Is this column sorted in descending order? 101 | * **cellFormatter**: `Function(string value, string columnName, Object rowData)string` *(optional)* A formatter function which will return the HTML for the cell. By default the formatter is a plain HTML formatter. 102 | * **headerCellFormatter**: `Function(string value, string columnName)string` *(optional)* A formatter function which will return the HTML for the cell's header. By default the formatter is a plain HTML formatter. 103 | * **rowsBufferSize**: `number=10` The size of the rows buffer, for virtual table 104 | * **minColumnWidth**: `number=35` In pixels, the minimum width for a column 105 | * **resizeAreaWidth**: `number=8` The size of the area where you can drag to resize. 106 | * **onComparatorRequired**: `function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):{function(a,b):number}` A callback that can pass a comparator function for each column and mode as required. 107 | * **resizerClassName**: `string='dgtable-resize'` Class name for the dragged resizing element (showing when resizing a column) 108 | * **tableClassName**: `string='dgtable'` Class name for the table 109 | * **allowCellPreview**: `boolean=true` When set, hovering on truncated cells will show a preview of the full content. 110 | * **allowHeaderCellPreview**: `boolean=true` Allow for toggling off **allowCellPreview** for headers specifically. 111 | * **cellPreviewAutoBackground**: `boolean=true` When set, the preview cell will receive its background automatically from the cell. 112 | * **cellPreviewClassName**: `string='dgtable-cell-preview'` Class name for the cell preview element 113 | * **className**: `string='dgtable-wrapper'` Element class name. 114 | * **el**: `Element?` Optional element to take over 115 | * **filter**: `Function(row: Object, args: Object): boolean` *(optional)* A filter function for using with the `filter` method 116 | 117 | #### Events triggered by DGTable: 118 | 119 | * `renderskeleton`: The table is re-drawing it's base elements, including headers. Will always be followed by a `render` event. 120 | * `render`: The table has finished rendering (after adding rows etc.). 121 | * `cellpreview`: We are about to show a cell preview - `{ el: Element, rowIndex: number|null, name: string, rowData: Object|null, cell: Element, cellEl: Element }` 122 | * At this stage you can prevent showing the preview, by calling `table.hideCellPreview` 123 | * `cellpreviewdestroy`: Cell preview element is about to be destroyed after hiding - `{ el: Element, name: string, filteredRowIndex: number|null, rowIndex: Object|null, cell: Element, cellEl: Element }` 124 | * You can use this event to release any resources that you may have used in `cellPreview` event. 125 | * `headerrowcreate`: The header row has just been created - `Element` 126 | * `headerrowdestroy`: Called just before removing the header row DOM element from the table - `Element` 127 | * `rowcreate`: A row has just been created - `{ filteredRowIndex: number, rowIndex: number, rowEl: Element, rowData: Object }` 128 | * `rowclick`: A row has just been created - `{ event: MouseEvent, rowIndex: number, rowIndex: number, rowEl: Element, rowData: Object }` 129 | * `rowdestroy`: Called just before removing a row DOM element from the table - `Element` 130 | * `addrows`: Data rows have been added to the table - `({ count: number, clear: boolean })` 131 | * `addcolumn`: A column was added - `string` (the column's name) 132 | * `removecolumn`: A column was removed - `string` (the column's name) 133 | * `movecolumn`: A column was moved - `({ name: string, src: number, dest: number })` 134 | * `showcolumn`: A column was shown - `string` (the column's name) 135 | * `hidecolumn`: A column was hidden - `string` (the column's name) 136 | * `columnwidth`: A column was resized - `({ name: string, width: number|string, oldWidth: number|string })` 137 | * `filter`: A filter was applied - `any` - the arguments passed to the filter method 138 | * `filterclear`: A filter was cleared 139 | * `sort`: The data was sorted - `{ sorts: { "column": "column's name", "descending": true|false }[], resort: true|undefined, comparator: Function }` 140 | * `headercontextmenu`: A context menu should be shown for a header cell - `({ columnName: string, pageX: number, pageY: number, bounds: { left: number, top: number, width: number, height: number } })` 141 | 142 | - Member functions: 143 | * `on(eventName, {Function?} callback)`: Adds an event listener 144 | * `once(eventName, {Function?} callback)`: Adds a one-shot event listener 145 | * `off(eventName, {Function?} callback)`: Removes an event listener 146 | * `render()`: Renders the view. Should be called after adding to the DOM, and when the viewport has changed and the table has no knowledge of it. 147 | * `clearAndRender({boolean} render = true)`: Forces a full render of the table 148 | * `setColumns({COLUMN_OPTIONS[]} columns, {boolean} render = true) {DGTable}`: Sets the table columns 149 | * `addColumn({COLUMN_OPTIONS} columnData, {string|number} before = -1, {boolean} render = true) {DGTable}`: Add a column to the table 150 | * **columnData**: Column properties. Same manner as in the **columns** options when initializing the DGTable 151 | * **before**: Column name or order to be inserted before. 152 | * *returns* Self, to allow for call chaining. 153 | * `removeColumn({string} column, {boolean} render = true) {DGTable}`: Remove a column from the table 154 | * **column**: Column name 155 | * *returns* Self, to allow for call chaining. 156 | * `setFilter({Function(row: Object, args: Object): boolean} filterFunc) {DGTable}`: Sets a new filtering function, set null for default. 157 | * **filterFunc**: The filtering function receives a row and an options object, and returns true for any row that passes the filter. 158 | * *returns* Self, to allow for call chaining. 159 | * `setCellFormatter({Function(value: *, columnName: string, row: Object):string|null} formatter) {DGTable}`: Sets a new cell formatter. 160 | * **formatter**: The cell formatter. Should return an HTML. 161 | * *returns* Self, to allow for call chaining. 162 | * `setHeaderCellFormatter({Function(label: string, columnName: string):string|null} formatter) {DGTable}`: Sets a new header cell formatter. 163 | * **formatter**: The cell formatter. Should return an HTML. 164 | * *returns* Self, to allow for call chaining. 165 | * `filter({Object} args) {DGTable}`: Filter the visible rows in the table 166 | * **args**: Options to pass to the filtering function 167 | * *returns* Self, to allow for call chaining. 168 | * `filter({{column: string, keyword: string, caseSensitive: boolean}} args) {DGTable}`: Syntax for default filtering function. 169 | * **args.column**: Name of the column to filter on 170 | * **args.keyword**: Tests the specified column if contains this keyword 171 | * **args.caseSensitive**: Use caseSensitive filtering 172 | * *returns* Self, to allow for call chaining. 173 | * `clearFilter() {DGTable}`: Clears the current filter 174 | * *returns* Self, to allow for call chaining. 175 | * `setColumnLabel({string} column, {string} label) {DGTable}`: Set a new label to a column 176 | * **column**: Name of the column 177 | * **label**: New label for the column 178 | * *returns* Self, to allow for call chaining. 179 | * `moveColumn({string|number} src, {string|number} dest, visibleOnly = true) {DGTable}`: Move a column to a new position 180 | * **src**: Name or position of the column to be moved 181 | * **dest**: Name of the column currently in the desired position, or the position itself 182 | * **visibleOnly**: Should consider only visible columns and visible-relative indexes 183 | * *returns* Self, to allow for call chaining. 184 | * `sort({string?} column, {boolean?} descending, {boolean=false} add) {DGTable}`: Sort the table. This does not render automatically, so you may need to call render() too. 185 | * **src**: Name of the column to sort on 186 | * **descending**: Sort in descending order (if not specified, defaults to false or reverses current descending mode if sorting by same column) 187 | * **add**: Should this sort be on top of the existing sort? (For multiple column sort) 188 | * *returns* Self, to allow for call chaining. 189 | * `resort() {DGTable}`: Re-sort the table using current sort specifiers. This does not render automatically, so you may need to call render() too. 190 | * *returns* Self, to allow for call chaining. 191 | * `setColumnVisible({string} column, {boolean} visible) {DGTable}`: Show or hide a column 192 | * **column**: Unique column name 193 | * **visible**: New visibility mode for the column 194 | * *returns* Self, to allow for call chaining. 195 | * `isColumnVisible({string} column, {boolean} visible) {boolean}`: Get the visibility mode of a column 196 | * *returns* True if visible 197 | * `setMinColumnWidth({number} minColumnWidth) {DGTable}`: Globally set the minimum column width 198 | * **minColumnWidth**: Minimum column width 199 | * *returns* Self, to allow for call chaining. 200 | * `getMinColumnWidth() {number}`: Get the current minimum column width 201 | * *returns* Minimum column width 202 | * `setSortableColumns({number} sortableColumns) {DGTable}`: Set the limit on concurrent columns sortedh 203 | * **sortableColumns**: Minimum column width 204 | * *returns* Self, to allow for call chaining. 205 | * `getSortableColumns() {number}`: Get the limit on concurrent columns sorted 206 | * *returns* How many sortable columns are allowed? 207 | * `getHeaderRowElement() {Element}`: Get the DOM element of the header row 208 | * *returns* a DOM element 209 | * `setMovableColumns({boolean} movableColumns) {DGTable}`: *Undocumented yet* 210 | * `getMovableColumns() {boolean}`: *Undocumented yet* 211 | * `setResizableColumns({boolean} resizableColumns) {DGTable}`: *Undocumented yet* 212 | * `getResizableColumns() {boolean}`: *Undocumented yet* 213 | * `setOnComparatorRequired({function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):{function(a,b):number}}|null comparatorCallback) {DGTable}`: Sets a functions that supplies comparators dynamically 214 | * **comparatorCallback**: a function that returns the comparator for a specific column 215 | * `setCustomSortingProvider({{function(data: any[], sort: function(any[]):any[]):any[]}|null} customSortingProvider) {DGTable}`: sets custom sorting function for a data set 216 | * **customSortingProvider**: provides a custom sorting function (not the comparator, but a sort() alternative) for a data set 217 | * `setColumnWidth({string} column, {number|string} width) {DGTable}`: *Undocumented yet* 218 | * `getColumnWidth({string} column) {string|null}`: *Undocumented yet* 219 | * `getColumnConfig({string} column name) {SERIALIZED_COLUMN}`: *Undocumented yet* 220 | * `getColumnsConfig() {Object}`: *Undocumented yet* 221 | * `getSortedColumns() {Array.}`: *Undocumented yet* 222 | * `getHtmlForRowCell(row: number, columnName: string) {string}`: Returns the HTML for specified cell in a row. 223 | * **row**: Index of row 224 | * **columnName**: Name of cell 225 | * *returns* HTML for cell. By default cell content is *not* HTML encoded, you should encode appropriately in your `cellFormatter`. 226 | * `getHtmlForRowDataCell(rowData: Object, columnName: string) {string|null}`: Returns the HTML string for a specific cell. Can be used externally for special cases (i.e. when setting a fresh HTML in the cell preview through the callback). 227 | * **rowData**: Actual row data 228 | * **columnName**: Name of column 229 | * *returns* string for the specified cell 230 | * `getDataForRow(rowIndex: number): Object`: Gets the row data for a specific row 231 | * *returns* row data at the specified index, out of all rows (not filtered) 232 | * `getRowCount(): number`: Gets the number of rows 233 | * *returns* the number of rows 234 | * `getIndexForRow(row: Object): number`: Finds the index of the specified row 235 | * *returns* the index of the specified row 236 | * `getFilteredRowCount(): number`: Gets the number of filtered rows 237 | * *returns* the number of rows in the filtered set (defaults to full row count if no filtering is active) 238 | * `getIndexForFilteredRow(row: Object): number`: Finds the index of the specified row within the filtered results 239 | * *returns* the index of the specified row 240 | * `getDataForFilteredRow(row: number): Object`: *Undocumented yet* 241 | * `getRowElement(rowIndex: number): Element`: Returns the element of the specified row (unfiltered index) 242 | * `getRowYPos(rowIndex: number): number?`: Returns the Y pos of the specified row (unfiltered index) 243 | * `tableWidthChanged() {DGTable}`: *Undocumented yet* 244 | * `tableHeightChanged() {DGTable}`: *Undocumented yet* 245 | * `addRows({Object[]} data, {number} at = -1, {boolean} resort = false, {boolean} render = true) {DGTable}`: Adds the specified rows at the specified position, and optionally resorts the data 246 | * `removeRow({number} rowIndex, {boolean} render = true) {DGTable}`: Removes one row at the specified position 247 | * `removeRows({number} rowIndex, {number} count, {boolean} render = true) {DGTable}`: Removes rows at the specified position 248 | * `refreshRow({number} rowIndex) {DGTable}`: Refreshes the row specified 249 | * *returns* Self 250 | * `refreshAllVirtualRows() {DGTable}`: Refreshes all virtual rows 251 | * *returns* Self 252 | * `setRows(data: Object[], resort: boolean=false) {DGTable}`: Rests the table rows to the provided array of rows. 253 | * **data**: New rows for the table 254 | * **resort**: Should re-sort the table? 255 | * *returns* Self, to allow for call chaining. 256 | * `getUrlForElementContent({string} id) {string?}`: *Undocumented yet* 257 | * `isWorkerSupported() {boolean}`: *Undocumented yet* 258 | * `createWebWorker({string} url) {Worker?}`: *Undocumented yet* 259 | * `unbindWebWorker({Worker} worker) {DGTable}`: *Undocumented yet* 260 | * `hideCellPreview() {DGTable}`: Hide any cell preview showing currently, or prevent showing a cell preview from within the `cellpreview` event. 261 | * `destroy()`: Destroy the table and free all of its memory. 262 | 263 | ## License 264 | 265 | All the code here is under MIT license. Which means you could do virtually anything with the code. 266 | I will appreciate it very much if you keep an attribution where appropriate. 267 | 268 | The MIT License (MIT) 269 | 270 | Copyright (c) 2013 Daniel Cohen Gindi (danielgindi@gmail.com) 271 | 272 | Permission is hereby granted, free of charge, to any person obtaining a copy 273 | of this software and associated documentation files (the "Software"), to deal 274 | in the Software without restriction, including without limitation the rights 275 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 276 | copies of the Software, and to permit persons to whom the Software is 277 | furnished to do so, subject to the following conditions: 278 | 279 | The above copyright notice and this permission notice shall be included in all 280 | copies or substantial portions of the Software. 281 | 282 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 283 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 284 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 285 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 286 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 287 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 288 | SOFTWARE. 289 | -------------------------------------------------------------------------------- /connectors/jquery/README.md: -------------------------------------------------------------------------------- 1 | DGTable.js (please migrate to @danielgindi/dgtable.js) 2 | ====================================================== 3 | 4 | This is a table View for vanilla JS, which is meant to be high-performance, and allow simple user interactions with the UI, such as: 5 | * Sorting 6 | * Sorting by more than one column 7 | * Moving columns 8 | * Resizing columns 9 | * Full cell preview when hovering 10 | * Native RTL support 11 | * Variable row height 12 | 13 | Other features implemented are: 14 | * Mix absolute column widths with relative column widths 15 | * Virtual table mode (to supply high performance with hundreds of thousands of rows). This is the default. 16 | * Non-virtual table mode is fully supported, but for giant amounts of data it is not recommended. 17 | * Option to set a fixed width for the table so resizing (relative) columns will still make sure the table will not be less (and/or more) than the specified width. 18 | * Option to have both scrollbars inside the table. (set `width: DGTable.Width.SCROLL`) 19 | 20 | `jquery.dgtable` users: 21 | * Older `jquery.dgtable` can either keep using `jquery.dgtable`, or migrate to this new version which is more lightweight. 22 | * The new version's API is the same as the old one, except that: 23 | * No `$el` property 24 | * No auto-clear of jQuery data. 25 | * There is now an `emit` method instead of `trigger`. 26 | * Event arguments are now always a single value/object. 27 | * Props on DOM elements are now: `'columnName'` on a cell, `'index'/'vIndex'` on a row, `'columnName'/rowIndex'/'rowVIndex'` on a cellPreview 28 | 29 | My TODO list: 30 | * TODO: Have a simple and accurate API documentation here in the readme 31 | 32 | ## Dev environment 33 | 34 | * Using grunt over Node.js to automate validating and building. 35 | * After installing Node.js, use `npm install`, and `npm install -g grunt-cli` to prepare building environment. 36 | * Use `grunt style` to just test for correct styling. 37 | * Use `grunt build` or just `grunt` to compile for release. 38 | * I am using Google Closure Compiler, because UglifyJS does not work with the JSDoc, and there's a major difference in size & performance of the output code. 39 | * Some features of jshint are disabled because it does not work well with JSDoc which is used for Google Closue Compiler. 40 | * Indentations in my editor are set to 4 spaces, and jshint validates that. 41 | 42 | ## Me 43 | * Hi! I am Daniel Cohen Gindi. Or in short- Daniel. 44 | * danielgindi@gmail.com is my email address. 45 | * That's all you need to know. 46 | 47 | ## Help 48 | 49 | I have invested, and investing, a lot of time in this project. 50 | If you want to help, you could: 51 | * Actually code, and issue pull requests 52 | * Test the library under different conditions and browsers 53 | * Create more demo pages 54 | * Spread the word 55 | * 56 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G22LPLJ79NBYQ) 57 | 58 | ## API 59 | 60 | To create a new table, just use `var myTable = new DGTable(INIT_OPTIONS)`. 61 | 62 | #### `INIT_OPTIONS` 63 | 64 | * **columns**: `COLUMN_OPTIONS[]` (Array of COLUMN_OPTIONS objects) 65 | * **name**: `string` Name of the column 66 | * **label**: `string=name` Used for the header of this column 67 | * **dataPath**: `string=name` Path to the data to show (Defaults to `name`) 68 | * **comparePath**: `string=name` Path to the data to use for comparison (Defaults to `dataPath`) 69 | * **width**: `number|string` 70 | * A simple number (i.e `10`, `30`, `50`) will set an absolute width for the column. 71 | * A percentage (i.e `'30%'`) or a 0-1 decimal value (i.e `0.2`, `0.7`) will set a relative width for the column, out of the full table's width. 72 | * Any other value, like `0`, `null` etc. will set an automatic width mode, base of the header's content length. 73 | * **resizable**: `boolean=true` Is this column resizable? 74 | * **sortable**: `boolean=true` Is this column sortable? 75 | * **movable**: `boolean=true` Is this column movable? 76 | * **visible**: `boolean=true` Is this column visible? 77 | * **cellClasses**: `string` Classes to add to the DOM element of this cell 78 | * **ignoreMin**: `boolean=false` Should this column ignore the minimum width specified for the table columns? 79 | * **height**: `number` Suggested height for the table 80 | * **width**: `DGTable.Width=DGTable.Width.NONE` The way that the width of the table will be handled 81 | * `DGTable.Width.NONE`: No special handling 82 | * `DGTable.Width.AUTO`: Sets the width automatically 83 | * `DGTable.Width.SCROLL`: Creates a horizontal scroll when required 84 | * **virtualTable**: `boolean=true` When set, the table will work in virtual mode, which means only the visible rows are rendered. Rows must have fixed height in this mode. 85 | * **estimatedRowHeight**: `number?` Sets the estimated row height for the table. This is used for virtual table mode, to calculate the estimated scroll size. Will be auto calculated by default. 86 | * **resizableColumns**: `boolean=true` Turns on or off the resizable columns globally. 87 | * **movableColumns**: `boolean=true` Turns on or off the movable columns globally. 88 | * **sortableColumns**: `number=1` How many columns can you sort by, one after another? 89 | * **adjustColumnWidthForSortArrow**: `boolean=true` When set, the columns will automatically grow to accommodate for the sort arrow. 90 | * **relativeWidthGrowsToFillWidth**: `boolean=true` When set, relative width columns automatically grow to fill the table's width. 91 | * **relativeWidthShrinksToFillWidth**: `boolean=false` When set, relative width columns automatically shrink to fill the table's width. 92 | * **convertColumnWidthsToRelative**: `boolean=false` When set, auto-width columns are automatically converted to relatives. 93 | * **autoFillTableWidth**: `boolean=false` When set, columns are stretched proportionally to fill the table width (only if there is space left). Will supersede `relativeWidthGrowsToFillWidth` in the future. 94 | * **allowCancelSort**: `boolean=true` When set, the sorting arrows will have 3 modes - asc, desc, and cancelled. 95 | * **cellClasses**: `string` Classes to add to the DOM element of all cells 96 | * **sortColumn**: `string|string[]|COLUMN_SORT_OPTIONS|COLUMN_SORT_OPTIONS[]` Columns to sort by 97 | * Can be a column or an array of columns. 98 | * Each column is a `string` or a `COLUMN_SORT_OPTIONS`: 99 | * **column**: `string` Column name 100 | * **descending**: `boolean=false` Is this column sorted in descending order? 101 | * **cellFormatter**: `Function(string value, string columnName, Object rowData)string` *(optional)* A formatter function which will return the HTML for the cell. By default the formatter is a plain HTML formatter. 102 | * **headerCellFormatter**: `Function(string value, string columnName)string` *(optional)* A formatter function which will return the HTML for the cell's header. By default the formatter is a plain HTML formatter. 103 | * **rowsBufferSize**: `number=10` The size of the rows buffer, for virtual table 104 | * **minColumnWidth**: `number=35` In pixels, the minimum width for a column 105 | * **resizeAreaWidth**: `number=8` The size of the area where you can drag to resize. 106 | * **onComparatorRequired**: `function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):{function(a,b):number}` A callback that can pass a comparator function for each column and mode as required. 107 | * **resizerClassName**: `string='dgtable-resize'` Class name for the dragged resizing element (showing when resizing a column) 108 | * **tableClassName**: `string='dgtable'` Class name for the table 109 | * **allowCellPreview**: `boolean=true` When set, hovering on truncated cells will show a preview of the full content. 110 | * **allowHeaderCellPreview**: `boolean=true` Allow for toggling off **allowCellPreview** for headers specifically. 111 | * **cellPreviewAutoBackground**: `boolean=true` When set, the preview cell will receive its background automatically from the cell. 112 | * **cellPreviewClassName**: `string='dgtable-cell-preview'` Class name for the cell preview element 113 | * **className**: `string='dgtable-wrapper'` Element class name. 114 | * **el**: `Element?` Optional element to take over 115 | * **filter**: `Function(row: Object, args: Object): boolean` *(optional)* A filter function for using with the `filter` method 116 | 117 | #### Events triggered by DGTable: 118 | 119 | * `renderskeleton`: The table is re-drawing it's base elements, including headers. Will always be followed by a `render` event. 120 | * `render`: The table has finished rendering (after adding rows etc.). 121 | * `cellpreview`: We are about to show a cell preview - `{ el: Element, rowIndex: number|null, name: string, rowData: Object|null, cell: Element, cellEl: Element }` 122 | * At this stage you can prevent showing the preview, by calling `table.hideCellPreview` 123 | * `cellpreviewdestroy`: Cell preview element is about to be destroyed after hiding - `{ el: Element, name: string, filteredRowIndex: number|null, rowIndex: Object|null, cell: Element, cellEl: Element }` 124 | * You can use this event to release any resources that you may have used in `cellPreview` event. 125 | * `headerrowcreate`: The header row has just been created - `Element` 126 | * `headerrowdestroy`: Called just before removing the header row DOM element from the table - `Element` 127 | * `rowcreate`: A row has just been created - `{ filteredRowIndex: number, rowIndex: number, rowEl: Element, rowData: Object }` 128 | * `rowclick`: A row has just been created - `{ event: MouseEvent, rowIndex: number, rowIndex: number, rowEl: Element, rowData: Object }` 129 | * `rowdestroy`: Called just before removing a row DOM element from the table - `Element` 130 | * `addrows`: Data rows have been added to the table - `({ count: number, clear: boolean })` 131 | * `addcolumn`: A column was added - `string` (the column's name) 132 | * `removecolumn`: A column was removed - `string` (the column's name) 133 | * `movecolumn`: A column was moved - `({ name: string, src: number, dest: number })` 134 | * `showcolumn`: A column was shown - `string` (the column's name) 135 | * `hidecolumn`: A column was hidden - `string` (the column's name) 136 | * `columnwidth`: A column was resized - `({ name: string, width: number|string, oldWidth: number|string })` 137 | * `filter`: A filter was applied - `any` - the arguments passed to the filter method 138 | * `filterclear`: A filter was cleared 139 | * `sort`: The data was sorted - `{ sorts: { "column": "column's name", "descending": true|false }[], resort: true|undefined, comparator: Function }` 140 | * `headercontextmenu`: A context menu should be shown for a header cell - `({ columnName: string, pageX: number, pageY: number, bounds: { left: number, top: number, width: number, height: number } })` 141 | 142 | - Member functions: 143 | * `on(eventName, {Function?} callback)`: Adds an event listener 144 | * `once(eventName, {Function?} callback)`: Adds a one-shot event listener 145 | * `off(eventName, {Function?} callback)`: Removes an event listener 146 | * `render()`: Renders the view. Should be called after adding to the DOM, and when the viewport has changed and the table has no knowledge of it. 147 | * `clearAndRender({boolean} render = true)`: Forces a full render of the table 148 | * `setColumns({COLUMN_OPTIONS[]} columns, {boolean} render = true) {DGTable}`: Sets the table columns 149 | * `addColumn({COLUMN_OPTIONS} columnData, {string|number} before = -1, {boolean} render = true) {DGTable}`: Add a column to the table 150 | * **columnData**: Column properties. Same manner as in the **columns** options when initializing the DGTable 151 | * **before**: Column name or order to be inserted before. 152 | * *returns* Self, to allow for call chaining. 153 | * `removeColumn({string} column, {boolean} render = true) {DGTable}`: Remove a column from the table 154 | * **column**: Column name 155 | * *returns* Self, to allow for call chaining. 156 | * `setFilter({Function(row: Object, args: Object): boolean} filterFunc) {DGTable}`: Sets a new filtering function, set null for default. 157 | * **filterFunc**: The filtering function receives a row and an options object, and returns true for any row that passes the filter. 158 | * *returns* Self, to allow for call chaining. 159 | * `setCellFormatter({Function(value: *, columnName: string, row: Object):string|null} formatter) {DGTable}`: Sets a new cell formatter. 160 | * **formatter**: The cell formatter. Should return an HTML. 161 | * *returns* Self, to allow for call chaining. 162 | * `setHeaderCellFormatter({Function(label: string, columnName: string):string|null} formatter) {DGTable}`: Sets a new header cell formatter. 163 | * **formatter**: The cell formatter. Should return an HTML. 164 | * *returns* Self, to allow for call chaining. 165 | * `filter({Object} args) {DGTable}`: Filter the visible rows in the table 166 | * **args**: Options to pass to the filtering function 167 | * *returns* Self, to allow for call chaining. 168 | * `filter({{column: string, keyword: string, caseSensitive: boolean}} args) {DGTable}`: Syntax for default filtering function. 169 | * **args.column**: Name of the column to filter on 170 | * **args.keyword**: Tests the specified column if contains this keyword 171 | * **args.caseSensitive**: Use caseSensitive filtering 172 | * *returns* Self, to allow for call chaining. 173 | * `clearFilter() {DGTable}`: Clears the current filter 174 | * *returns* Self, to allow for call chaining. 175 | * `setColumnLabel({string} column, {string} label) {DGTable}`: Set a new label to a column 176 | * **column**: Name of the column 177 | * **label**: New label for the column 178 | * *returns* Self, to allow for call chaining. 179 | * `moveColumn({string|number} src, {string|number} dest, visibleOnly = true) {DGTable}`: Move a column to a new position 180 | * **src**: Name or position of the column to be moved 181 | * **dest**: Name of the column currently in the desired position, or the position itself 182 | * **visibleOnly**: Should consider only visible columns and visible-relative indexes 183 | * *returns* Self, to allow for call chaining. 184 | * `sort({string?} column, {boolean?} descending, {boolean=false} add) {DGTable}`: Sort the table. This does not render automatically, so you may need to call render() too. 185 | * **src**: Name of the column to sort on 186 | * **descending**: Sort in descending order (if not specified, defaults to false or reverses current descending mode if sorting by same column) 187 | * **add**: Should this sort be on top of the existing sort? (For multiple column sort) 188 | * *returns* Self, to allow for call chaining. 189 | * `resort() {DGTable}`: Re-sort the table using current sort specifiers. This does not render automatically, so you may need to call render() too. 190 | * *returns* Self, to allow for call chaining. 191 | * `setColumnVisible({string} column, {boolean} visible) {DGTable}`: Show or hide a column 192 | * **column**: Unique column name 193 | * **visible**: New visibility mode for the column 194 | * *returns* Self, to allow for call chaining. 195 | * `isColumnVisible({string} column, {boolean} visible) {boolean}`: Get the visibility mode of a column 196 | * *returns* True if visible 197 | * `setMinColumnWidth({number} minColumnWidth) {DGTable}`: Globally set the minimum column width 198 | * **minColumnWidth**: Minimum column width 199 | * *returns* Self, to allow for call chaining. 200 | * `getMinColumnWidth() {number}`: Get the current minimum column width 201 | * *returns* Minimum column width 202 | * `setSortableColumns({number} sortableColumns) {DGTable}`: Set the limit on concurrent columns sortedh 203 | * **sortableColumns**: Minimum column width 204 | * *returns* Self, to allow for call chaining. 205 | * `getSortableColumns() {number}`: Get the limit on concurrent columns sorted 206 | * *returns* How many sortable columns are allowed? 207 | * `getHeaderRowElement() {Element}`: Get the DOM element of the header row 208 | * *returns* a DOM element 209 | * `setMovableColumns({boolean} movableColumns) {DGTable}`: *Undocumented yet* 210 | * `getMovableColumns() {boolean}`: *Undocumented yet* 211 | * `setResizableColumns({boolean} resizableColumns) {DGTable}`: *Undocumented yet* 212 | * `getResizableColumns() {boolean}`: *Undocumented yet* 213 | * `setOnComparatorRequired({function(columnName: string, descending: boolean, defaultComparator: function(a,b):number):{function(a,b):number}}|null comparatorCallback) {DGTable}`: Sets a functions that supplies comparators dynamically 214 | * **comparatorCallback**: a function that returns the comparator for a specific column 215 | * `setCustomSortingProvider({{function(data: any[], sort: function(any[]):any[]):any[]}|null} customSortingProvider) {DGTable}`: sets custom sorting function for a data set 216 | * **customSortingProvider**: provides a custom sorting function (not the comparator, but a sort() alternative) for a data set 217 | * `setColumnWidth({string} column, {number|string} width) {DGTable}`: *Undocumented yet* 218 | * `getColumnWidth({string} column) {string|null}`: *Undocumented yet* 219 | * `getColumnConfig({string} column name) {SERIALIZED_COLUMN}`: *Undocumented yet* 220 | * `getColumnsConfig() {Object}`: *Undocumented yet* 221 | * `getSortedColumns() {Array.}`: *Undocumented yet* 222 | * `getHtmlForRowCell(row: number, columnName: string) {string}`: Returns the HTML for specified cell in a row. 223 | * **row**: Index of row 224 | * **columnName**: Name of cell 225 | * *returns* HTML for cell. By default cell content is *not* HTML encoded, you should encode appropriately in your `cellFormatter`. 226 | * `getHtmlForRowDataCell(rowData: Object, columnName: string) {string|null}`: Returns the HTML string for a specific cell. Can be used externally for special cases (i.e. when setting a fresh HTML in the cell preview through the callback). 227 | * **rowData**: Actual row data 228 | * **columnName**: Name of column 229 | * *returns* string for the specified cell 230 | * `getDataForRow(rowIndex: number): Object`: Gets the row data for a specific row 231 | * *returns* row data at the specified index, out of all rows (not filtered) 232 | * `getRowCount(): number`: Gets the number of rows 233 | * *returns* the number of rows 234 | * `getIndexForRow(row: Object): number`: Finds the index of the specified row 235 | * *returns* the index of the specified row 236 | * `getFilteredRowCount(): number`: Gets the number of filtered rows 237 | * *returns* the number of rows in the filtered set (defaults to full row count if no filtering is active) 238 | * `getIndexForFilteredRow(row: Object): number`: Finds the index of the specified row within the filtered results 239 | * *returns* the index of the specified row 240 | * `getDataForFilteredRow(row: number): Object`: *Undocumented yet* 241 | * `getRowElement(rowIndex: number): Element`: Returns the element of the specified row (unfiltered index) 242 | * `getRowYPos(rowIndex: number): number?`: Returns the Y pos of the specified row (unfiltered index) 243 | * `tableWidthChanged() {DGTable}`: *Undocumented yet* 244 | * `tableHeightChanged() {DGTable}`: *Undocumented yet* 245 | * `addRows({Object[]} data, {number} at = -1, {boolean} resort = false, {boolean} render = true) {DGTable}`: Adds the specified rows at the specified position, and optionally resorts the data 246 | * `removeRow({number} rowIndex, {boolean} render = true) {DGTable}`: Removes one row at the specified position 247 | * `removeRows({number} rowIndex, {number} count, {boolean} render = true) {DGTable}`: Removes rows at the specified position 248 | * `refreshRow({number} rowIndex) {DGTable}`: Refreshes the row specified 249 | * *returns* Self 250 | * `refreshAllVirtualRows() {DGTable}`: Refreshes all virtual rows 251 | * *returns* Self 252 | * `setRows(data: Object[], resort: boolean=false) {DGTable}`: Rests the table rows to the provided array of rows. 253 | * **data**: New rows for the table 254 | * **resort**: Should re-sort the table? 255 | * *returns* Self, to allow for call chaining. 256 | * `getUrlForElementContent({string} id) {string?}`: *Undocumented yet* 257 | * `isWorkerSupported() {boolean}`: *Undocumented yet* 258 | * `createWebWorker({string} url) {Worker?}`: *Undocumented yet* 259 | * `unbindWebWorker({Worker} worker) {DGTable}`: *Undocumented yet* 260 | * `hideCellPreview() {DGTable}`: Hide any cell preview showing currently, or prevent showing a cell preview from within the `cellpreview` event. 261 | * `destroy()`: Destroy the table and free all of its memory. 262 | 263 | ## License 264 | 265 | All the code here is under MIT license. Which means you could do virtually anything with the code. 266 | I will appreciate it very much if you keep an attribution where appropriate. 267 | 268 | The MIT License (MIT) 269 | 270 | Copyright (c) 2013 Daniel Cohen Gindi (danielgindi@gmail.com) 271 | 272 | Permission is hereby granted, free of charge, to any person obtaining a copy 273 | of this software and associated documentation files (the "Software"), to deal 274 | in the Software without restriction, including without limitation the rights 275 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 276 | copies of the Software, and to permit persons to whom the Software is 277 | furnished to do so, subject to the following conditions: 278 | 279 | The above copyright notice and this permission notice shall be included in all 280 | copies or substantial portions of the Software. 281 | 282 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 283 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 284 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 285 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 286 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 287 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 288 | SOFTWARE. 289 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DGTable.js Example 6 | 7 | 8 | 9 | 15 | 16 | 320 | 321 | 322 |
323 | 324 | 325 |
326 | 327 | | | | 328 | | 329 | 330 |
331 | 332 |
333 | 334 | 417 |
418 | 429 | 430 | 431 | --------------------------------------------------------------------------------