diff --git a/.eslintignore b/.eslintignore
index 53c37a1..52bb028 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1 +1,2 @@
-dist
\ No newline at end of file
+dist
+docs/frappe-datatable.js
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
index 91703fe..7ea222e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -55,7 +55,7 @@
"generator-star-spacing": [2, "both"],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
- "indent": [2, 2, { "SwitchCase": 1 }],
+ "indent": [2, 4, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, {"before": true, "after": true}],
"linebreak-style": 0,
diff --git a/dist/frappe-datatable.cjs.css b/dist/frappe-datatable.cjs.css
new file mode 100644
index 0000000..30dd637
--- /dev/null
+++ b/dist/frappe-datatable.cjs.css
@@ -0,0 +1,255 @@
+/* This file is processed by postcss */
+/* variables */
+
+.data-table {
+
+ /* styling */
+ width: 100%;
+ position: relative;
+ overflow: auto;
+}
+
+/* resets */
+
+.data-table *, .data-table *::after, .data-table *::before {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+
+.data-table button, .data-table input {
+ overflow: visible;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ margin: 0;
+ padding: 0;
+ }
+
+.data-table .input-style {
+ outline: none;
+ width: 100%;
+ border: none;
+ }
+
+.data-table *, .data-table *:focus {
+ outline: none;
+ border-radius: 0px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+
+.data-table table {
+ border-collapse: collapse;
+ }
+
+.data-table table td {
+ padding: 0;
+ border: 1px solid #d1d8dd;
+ }
+
+.data-table thead td {
+ border-bottom-width: 1px;
+ }
+
+.data-table .freeze-container {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -ms-flex-line-pack: center;
+ align-content: center;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ background-color: #f5f7fa;
+ opacity: 0.5;
+ font-size: 2em;
+ }
+
+.data-table .freeze-container span {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ }
+
+.data-table .trash-container {
+ position: absolute;
+ bottom: 0;
+ left: 30%;
+ right: 30%;
+ height: 70px;
+ background: palevioletred;
+ opacity: 0.5;
+ }
+
+.data-table .hide {
+ display: none;
+ }
+
+.body-scrollable {
+ max-height: 500px;
+ overflow: auto;
+ border-bottom: 1px solid #d1d8dd;
+}
+
+.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
+ background-color: #f5f7fa;
+ }
+
+.data-table-header {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: white;
+ font-weight: bold;
+}
+
+.data-table-header .content span:not(.column-resizer) {
+ cursor: pointer;
+ }
+
+.data-table-header .column-resizer {
+ display: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 4px;
+ width: 0.25rem;
+ height: 100%;
+ background-color: rgb(82, 146, 247);
+ cursor: col-resize;
+ }
+
+.data-table-header .data-table-dropdown {
+ position: absolute;
+ right: 10px;
+ display: -webkit-inline-box;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ vertical-align: top;
+ text-align: left;
+ }
+
+.data-table-header .data-table-dropdown.is-active .data-table-dropdown-list {
+ display: block;
+ }
+
+.data-table-header .data-table-dropdown.is-active .data-table-dropdown-toggle {
+ display: block;
+ }
+
+.data-table-header .data-table-dropdown-toggle {
+ display: none;
+ background-color: transparent;
+ border: none;
+ }
+
+.data-table-header .data-table-dropdown-list {
+ display: none;
+ font-weight: normal;
+
+ position: absolute;
+ min-width: 128px;
+ min-width: 8rem;
+ top: 100%;
+ right: 0;
+ z-index: 1;
+ background-color: white;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
+ box-shadow: 0 2px 3px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
+ padding-bottom: 8px;
+ padding-bottom: 0.5rem;
+ padding-top: 8px;
+ padding-top: 0.5rem;
+ }
+
+.data-table-header .data-table-dropdown-list> div {
+ padding: 8px 16px;
+ padding: 0.5rem 1rem;
+ }
+
+.data-table-header .data-table-dropdown-list> div:hover {
+ background-color: #f5f7fa;
+ }
+
+.data-table-header .data-table-col.remove-column {
+ background-color: #FD8B8B;
+ -webkit-transition: 300ms background-color ease-in-out;
+ transition: 300ms background-color ease-in-out;
+ }
+
+.data-table-header .data-table-col.sortable-chosen {
+ background-color: #f5f7fa;
+ }
+
+.data-table-col {
+ position: relative;
+}
+
+.data-table-col .content {
+ padding: 8px;
+ padding: 0.5rem;
+ border: 2px solid transparent;
+ }
+
+.data-table-col .content.ellipsis {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+.data-table-col .edit-cell {
+ display: none;
+ padding: 8px;
+ padding: 0.5rem;
+ background: #fff;
+ z-index: 1;
+ height: 100%;
+ }
+
+.data-table-col.selected .content {
+ border: 2px solid rgb(82, 146, 247);
+ }
+
+.data-table-col.editing .content {
+ display: none;
+ }
+
+.data-table-col.editing .edit-cell {
+ border: 2px solid rgb(82, 146, 247);
+ display: block;
+ }
+
+.data-table-col.highlight {
+ background-color: #f5f7fa;
+ }
+
+.data-table-col:hover .column-resizer {
+ display: inline-block;
+ }
+
+.data-table-col:hover .data-table-dropdown-toggle {
+ display: block;
+ }
+
+.data-table-row.row-highlight {
+ background-color: #f5f7fa;
+ }
+
+.noselect {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+body.data-table-resize {
+ cursor: col-resize;
+}
\ No newline at end of file
diff --git a/dist/frappe-datatable.cjs.js b/dist/frappe-datatable.cjs.js
index 0d523be..e194a4e 100644
--- a/dist/frappe-datatable.cjs.js
+++ b/dist/frappe-datatable.cjs.js
@@ -6,175 +6,185 @@ var Sortable = _interopDefault(require('sortablejs'));
var Clusterize = _interopDefault(require('clusterize.js'));
function $(expr, con) {
- return typeof expr === 'string' ?
- (con || document).querySelector(expr) :
- expr || null;
+ return typeof expr === 'string' ?
+ (con || document).querySelector(expr) :
+ expr || null;
}
$.each = (expr, con) => {
- return typeof expr === 'string' ?
- Array.from((con || document).querySelectorAll(expr)) :
- expr || null;
+ return typeof expr === 'string' ?
+ Array.from((con || document).querySelectorAll(expr)) :
+ expr || null;
};
$.create = (tag, o) => {
- let element = document.createElement(tag);
+ let element = document.createElement(tag);
- for (let i in o) {
- let val = o[i];
+ for (let i in o) {
+ let val = o[i];
- if (i === 'inside') {
- $(val).appendChild(element);
- } else
- if (i === 'around') {
- let ref = $(val);
- ref.parentNode.insertBefore(element, ref);
- element.appendChild(ref);
- } else
- if (i === 'styles') {
- if (typeof val === 'object') {
- Object.keys(val).map(prop => {
- element.style[prop] = val[prop];
- });
- }
+ if (i === 'inside') {
+ $(val).appendChild(element);
} else
- if (i in element) {
+ if (i === 'around') {
+ let ref = $(val);
+ ref.parentNode.insertBefore(element, ref);
+ element.appendChild(ref);
+ } else
+ if (i === 'styles') {
+ if (typeof val === 'object') {
+ Object.keys(val).map(prop => {
+ element.style[prop] = val[prop];
+ });
+ }
+ } else
+ if (i in element) {
element[i] = val;
- } else {
+ } else {
element.setAttribute(i, val);
- }
- }
+ }
+ }
- return element;
+ return element;
};
$.on = (element, event, selector, callback) => {
- if (!callback) {
- callback = selector;
- $.bind(element, event, callback);
- } else {
- $.delegate(element, event, selector, callback);
- }
+ if (!callback) {
+ callback = selector;
+ $.bind(element, event, callback);
+ } else {
+ $.delegate(element, event, selector, callback);
+ }
};
$.off = (element, event, handler) => {
- element.removeEventListener(event, handler);
+ element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
- event.split(/\s+/).forEach(function (event) {
- element.addEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.addEventListener(event, callback);
+ });
};
$.delegate = (element, event, selector, callback) => {
- element.addEventListener(event, function (e) {
- const delegatedTarget = e.target.closest(selector);
- if (delegatedTarget) {
- e.delegatedTarget = delegatedTarget;
- callback.call(this, e, delegatedTarget);
- }
- });
+ element.addEventListener(event, function (e) {
+ const delegatedTarget = e.target.closest(selector);
+ if (delegatedTarget) {
+ e.delegatedTarget = delegatedTarget;
+ callback.call(this, e, delegatedTarget);
+ }
+ });
};
$.unbind = (element, o) => {
- if (element) {
- for (let event in o) {
- let callback = o[event];
+ if (element) {
+ for (let event in o) {
+ let callback = o[event];
- event.split(/\s+/).forEach(function (event) {
- element.removeEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.removeEventListener(event, callback);
+ });
+ }
}
- }
};
$.fire = (target, type, properties) => {
- let evt = document.createEvent('HTMLEvents');
+ let evt = document.createEvent('HTMLEvents');
- evt.initEvent(type, true, true);
+ evt.initEvent(type, true, true);
- for (let j in properties) {
- evt[j] = properties[j];
- }
+ for (let j in properties) {
+ evt[j] = properties[j];
+ }
- return target.dispatchEvent(evt);
+ return target.dispatchEvent(evt);
};
$.data = (element, attrs) => { // eslint-disable-line
- if (!attrs) {
- return element.dataset;
- }
+ if (!attrs) {
+ return element.dataset;
+ }
- for (const attr in attrs) {
- element.dataset[attr] = attrs[attr];
- }
+ for (const attr in attrs) {
+ element.dataset[attr] = attrs[attr];
+ }
};
$.style = (elements, styleMap) => { // eslint-disable-line
- if (typeof styleMap === 'string') {
- return $.getStyle(elements, styleMap);
- }
-
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- elements.map(element => {
- for (const prop in styleMap) {
- element.style[prop] = styleMap[prop];
+ if (typeof styleMap === 'string') {
+ return $.getStyle(elements, styleMap);
}
- });
+
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ elements.map(element => {
+ for (const prop in styleMap) {
+ element.style[prop] = styleMap[prop];
+ }
+ });
};
$.removeStyle = (elements, styleProps) => {
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- if (!Array.isArray(styleProps)) {
- styleProps = [styleProps];
- }
-
- elements.map(element => {
- for (const prop of styleProps) {
- element.style[prop] = '';
+ if (!Array.isArray(elements)) {
+ elements = [elements];
}
- });
+
+ if (!Array.isArray(styleProps)) {
+ styleProps = [styleProps];
+ }
+
+ elements.map(element => {
+ for (const prop of styleProps) {
+ element.style[prop] = '';
+ }
+ });
};
$.getStyle = (element, prop) => {
- let val = getComputedStyle(element)[prop];
+ let val = getComputedStyle(element)[prop];
- if (['width', 'height'].includes(prop)) {
- val = parseFloat(val);
- }
+ if (['width', 'height'].includes(prop)) {
+ val = parseFloat(val);
+ }
- return val;
+ return val;
};
$.closest = (selector, element) => {
- if (!element) return null;
+ if (!element) return null;
- if (element.matches(selector)) {
- return element;
- }
+ if (element.matches(selector)) {
+ return element;
+ }
- return $.closest(selector, element.parentNode);
+ return $.closest(selector, element.parentNode);
};
$.inViewport = (el, parentEl) => {
- const { top, left, bottom, right } = el.getBoundingClientRect();
- const { top: pTop, left: pLeft, bottom: pBottom, right: pRight } = parentEl.getBoundingClientRect();
+ const {
+ top,
+ left,
+ bottom,
+ right
+ } = el.getBoundingClientRect();
+ const {
+ top: pTop,
+ left: pLeft,
+ bottom: pBottom,
+ right: pRight
+ } = parentEl.getBoundingClientRect();
- return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
+ return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
};
$.scrollTop = function scrollTop(element, pixels) {
- requestAnimationFrame(() => {
- element.scrollTop = pixels;
- });
+ requestAnimationFrame(() => {
+ element.scrollTop = pixels;
+ });
};
/**
@@ -720,26 +730,26 @@ function throttle(func, wait, options) {
var throttle_1 = throttle;
function camelCaseToDash(str) {
- return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
+ return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
}
function makeDataAttributeString(props) {
- const keys = Object.keys(props);
+ const keys = Object.keys(props);
- return keys
- .map((key) => {
- const _key = camelCaseToDash(key);
- const val = props[key];
+ return keys
+ .map((key) => {
+ const _key = camelCaseToDash(key);
+ const val = props[key];
- if (val === undefined) return '';
- return `data-${_key}="${val}" `;
- })
- .join('')
- .trim();
+ if (val === undefined) return '';
+ return `data-${_key}="${val}" `;
+ })
+ .join('')
+ .trim();
}
function getDefault(a, b) {
- return a !== undefined ? a : b;
+ return a !== undefined ? a : b;
}
@@ -753,62 +763,62 @@ function getDefault(a, b) {
function copyTextToClipboard(text) {
- // https://stackoverflow.com/a/30810322/5353542
- var textArea = document.createElement('textarea');
+ // https://stackoverflow.com/a/30810322/5353542
+ var textArea = document.createElement('textarea');
- //
- // *** This styling is an extra step which is likely not required. ***
- //
- // Why is it here? To ensure:
- // 1. the element is able to have focus and selection.
- // 2. if element was to flash render it has minimal visual impact.
- // 3. less flakyness with selection and copying which **might** occur if
- // the textarea element is not visible.
- //
- // The likelihood is the element won't even render, not even a flash,
- // so some of these are just precautions. However in IE the element
- // is visible whilst the popup box asking the user for permission for
- // the web page to copy to the clipboard.
- //
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
- // Place in top-left corner of screen regardless of scroll position.
- textArea.style.position = 'fixed';
- textArea.style.top = 0;
- textArea.style.left = 0;
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
- // Ensure it has a small width and height. Setting to 1px / 1em
- // doesn't work as this gives a negative w/h on some browsers.
- textArea.style.width = '2em';
- textArea.style.height = '2em';
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
- // We don't need padding, reducing the size if it does flash render.
- textArea.style.padding = 0;
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
- // Clean up any borders.
- textArea.style.border = 'none';
- textArea.style.outline = 'none';
- textArea.style.boxShadow = 'none';
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
- // Avoid flash of white box if rendered for any reason.
- textArea.style.background = 'transparent';
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
- textArea.value = text;
+ textArea.value = text;
- document.body.appendChild(textArea);
+ document.body.appendChild(textArea);
- textArea.select();
+ textArea.select();
- try {
- document.execCommand('copy');
- } catch (err) {
- console.log('Oops, unable to copy');
- }
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
- document.body.removeChild(textArea);
+ document.body.removeChild(textArea);
}
function isNumeric(val) {
- return !isNaN(val);
+ return !isNaN(val);
}
let throttle$1 = throttle_1;
@@ -816,936 +826,985 @@ let throttle$1 = throttle_1;
let debounce$2 = debounce_1;
function promisify(fn, context = null) {
- return (...args) => {
- return new Promise(resolve => {
- setTimeout(() => {
- const out = fn.apply(context, args);
- resolve(out);
- }, 0);
- });
- };
+ return (...args) => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ const out = fn.apply(context, args);
+ resolve(out);
+ }, 0);
+ });
+ };
}
function linkProperties(target, source, properties) {
- const props = properties.reduce((acc, prop) => {
- acc[prop] = {
- get() {
- return source[prop];
- }
- };
- return acc;
- }, {});
- Object.defineProperties(target, props);
+ const props = properties.reduce((acc, prop) => {
+ acc[prop] = {
+ get() {
+ return source[prop];
+ }
+ };
+ return acc;
+ }, {});
+ Object.defineProperties(target, props);
}
class DataManager {
- constructor(options) {
- this.options = options;
- this.sortRows = promisify(this.sortRows, this);
- this.switchColumn = promisify(this.switchColumn, this);
- this.removeColumn = promisify(this.removeColumn, this);
- this.filterRows = promisify(this.filterRows, this);
- }
-
- init(data) {
- if (!data) {
- data = this.options.data;
+ constructor(options) {
+ this.options = options;
+ this.sortRows = promisify(this.sortRows, this);
+ this.switchColumn = promisify(this.switchColumn, this);
+ this.removeColumn = promisify(this.removeColumn, this);
+ this.filterRows = promisify(this.filterRows, this);
}
- this.data = data;
-
- this.rowCount = 0;
- this.columns = [];
- this.rows = [];
-
- this.prepareColumns();
- this.prepareRows();
-
- this.prepareNumericColumns();
- }
-
- // computed property
- get currentSort() {
- const col = this.columns.find(col => col.sortOrder !== 'none');
- return col || {
- colIndex: -1,
- sortOrder: 'none'
- };
- }
-
- prepareColumns() {
- this.columns = [];
- this.validateColumns();
- this.prepareDefaultColumns();
- this.prepareHeader();
- }
-
- prepareDefaultColumns() {
- if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
- const cell = {
- id: '_checkbox',
- content: this.getCheckboxHTML(),
- editable: false,
- resizable: false,
- sortable: false,
- focusable: false,
- dropdown: false,
- width: 25
- };
- this.columns.push(cell);
- }
-
- if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
- let cell = {
- id: '_rowIndex',
- content: '',
- align: 'center',
- editable: false,
- resizable: false,
- focusable: false,
- dropdown: false
- };
-
- this.columns.push(cell);
- }
- }
-
- prepareRow(row, i) {
- const baseRowCell = {
- rowIndex: i
- };
-
- return row
- .map((cell, i) => this.prepareCell(cell, i))
- .map(cell => Object.assign({}, baseRowCell, cell));
- }
-
- prepareHeader() {
- let columns = this.columns.concat(this.options.columns);
- const baseCell = {
- isHeader: 1,
- editable: true,
- sortable: true,
- resizable: true,
- focusable: true,
- dropdown: true,
- width: null,
- format: (value) => {
- if (value === null || value === undefined) {
- return '';
- }
- return value + '';
- }
- };
-
- this.columns = columns
- .map((cell, i) => this.prepareCell(cell, i))
- .map(col => Object.assign({}, baseCell, col))
- .map(col => {
- col.id = col.id || col.content;
- return col;
- });
- }
-
- prepareCell(content, i) {
- const cell = {
- content: '',
- align: 'left',
- sortOrder: 'none',
- colIndex: i,
- column: this.columns[i]
- };
-
- if (content !== null && typeof content === 'object') {
- // passed as column/header
- Object.assign(cell, content);
- } else {
- cell.content = content;
- }
-
- return cell;
- }
-
- prepareNumericColumns() {
- const row0 = this.getRow(0);
- if (!row0) return;
- this.columns = this.columns.map((column, i) => {
-
- const cellValue = row0[i].content;
- if (!column.align && cellValue && isNumeric(cellValue)) {
- column.align = 'right';
- }
-
- return column;
- });
- }
-
- prepareRows() {
- this.validateData(this.data);
-
- this.rows = this.data.map((d, i) => {
- const index = this._getNextRowCount();
-
- let row = [];
-
- if (Array.isArray(d)) {
- // row is an array
- if (this.options.addCheckboxColumn) {
- row.push(this.getCheckboxHTML());
- }
- if (this.options.addSerialNoColumn) {
- row.push((index + 1) + '');
- }
- row = row.concat(d);
-
- while (row.length < this.columns.length) {
- row.push('');
+ init(data) {
+ if (!data) {
+ data = this.options.data;
}
- } else {
- // row is a dict
- for (let col of this.columns) {
- if (col.id === '_checkbox') {
- row.push(this.getCheckboxHTML());
- } else if (col.id === '_rowIndex') {
- row.push((index + 1) + '');
- } else {
- row.push(d[col.id]);
- }
+ this.data = data;
+
+ this.rowCount = 0;
+ this.columns = [];
+ this.rows = [];
+
+ this.prepareColumns();
+ this.prepareRows();
+
+ this.prepareNumericColumns();
+ }
+
+ // computed property
+ get currentSort() {
+ const col = this.columns.find(col => col.sortOrder !== 'none');
+ return col || {
+ colIndex: -1,
+ sortOrder: 'none'
+ };
+ }
+
+ prepareColumns() {
+ this.columns = [];
+ this.validateColumns();
+ this.prepareDefaultColumns();
+ this.prepareHeader();
+ }
+
+ prepareDefaultColumns() {
+ if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
+ const cell = {
+ id: '_checkbox',
+ content: this.getCheckboxHTML(),
+ editable: false,
+ resizable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 25
+ };
+ this.columns.push(cell);
}
- }
- return this.prepareRow(row, index);
- });
- }
+ if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
+ let cell = {
+ id: '_rowIndex',
+ content: '',
+ align: 'center',
+ editable: false,
+ resizable: false,
+ focusable: false,
+ dropdown: false
+ };
- validateColumns() {
- const columns = this.options.columns;
- if (!Array.isArray(columns)) {
- throw new DataError('`columns` must be an array');
+ this.columns.push(cell);
+ }
}
- columns.forEach((column, i) => {
- if (typeof column !== 'string' && typeof column !== 'object') {
- throw new DataError(`column "${i}" must be a string or an object`);
- }
- });
- }
+ prepareHeader() {
+ let columns = this.columns.concat(this.options.columns);
+ const baseCell = {
+ isHeader: 1,
+ editable: true,
+ sortable: true,
+ resizable: true,
+ focusable: true,
+ dropdown: true,
+ width: null,
+ format: (value) => {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return value + '';
+ }
+ };
- validateData(data) {
- if (Array.isArray(data) &&
- (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
- return true;
+ this.columns = columns
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(col => Object.assign({}, baseCell, col))
+ .map(col => {
+ col.id = col.id || col.content;
+ return col;
+ });
}
- throw new DataError('`data` must be an array of arrays or objects');
- }
- appendRows(rows) {
- this.validateData(rows);
+ prepareCell(content, i) {
+ const cell = {
+ content: '',
+ align: 'left',
+ sortOrder: 'none',
+ colIndex: i,
+ column: this.columns[i]
+ };
- this.rows = this.rows.concat(this.prepareRows(rows));
- }
-
- sortRows(colIndex, sortOrder = 'none') {
- colIndex = +colIndex;
-
- // reset sortOrder and update for colIndex
- this.getColumns()
- .map(col => {
- if (col.colIndex === colIndex) {
- col.sortOrder = sortOrder;
+ if (content !== null && typeof content === 'object') {
+ // passed as column/header
+ Object.assign(cell, content);
} else {
- col.sortOrder = 'none';
+ cell.content = content;
}
- });
- this._sortRows(colIndex, sortOrder);
- }
-
- _sortRows(colIndex, sortOrder) {
-
- if (this.currentSort.colIndex === colIndex) {
- // reverse the array if only sortOrder changed
- if (
- (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
- (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
- ) {
- this.reverseArray(this.rows);
- this.currentSort.sortOrder = sortOrder;
- return;
- }
+ return cell;
}
- this.rows.sort((a, b) => {
- const _aIndex = a[0].rowIndex;
- const _bIndex = b[0].rowIndex;
- const _a = a[colIndex].content;
- const _b = b[colIndex].content;
+ prepareNumericColumns() {
+ const row0 = this.getRow(0);
+ if (!row0) return;
+ this.columns = this.columns.map((column, i) => {
- if (sortOrder === 'none') {
- return _aIndex - _bIndex;
- } else if (sortOrder === 'asc') {
- if (_a < _b) return -1;
- if (_a > _b) return 1;
- if (_a === _b) return 0;
- } else if (sortOrder === 'desc') {
- if (_a < _b) return 1;
- if (_a > _b) return -1;
- if (_a === _b) return 0;
- }
- return 0;
- });
+ const cellValue = row0[i].content;
+ if (!column.align && cellValue && isNumeric(cellValue)) {
+ column.align = 'right';
+ }
- if (this.hasColumnById('_rowIndex')) {
- // update row index
- const srNoColIndex = this.getColumnIndexById('_rowIndex');
- this.rows = this.rows.map((row, index) => {
- return row.map(cell => {
- if (cell.colIndex === srNoColIndex) {
- cell.content = (index + 1) + '';
- }
- return cell;
+ return column;
});
- });
- }
- }
-
- reverseArray(array) {
- let left = null;
- let right = null;
- let length = array.length;
-
- for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
- const temporary = array[left];
-
- array[left] = array[right];
- array[right] = temporary;
- }
- }
-
- switchColumn(index1, index2) {
- // update columns
- const temp = this.columns[index1];
- this.columns[index1] = this.columns[index2];
- this.columns[index2] = temp;
-
- this.columns[index1].colIndex = index1;
- this.columns[index2].colIndex = index2;
-
- // update rows
- this.rows = this.rows.map(row => {
- const newCell1 = Object.assign({}, row[index1], { colIndex: index2 });
- const newCell2 = Object.assign({}, row[index2], { colIndex: index1 });
-
- let newRow = row.map(cell => {
- // make object copy
- return Object.assign({}, cell);
- });
-
- newRow[index2] = newCell1;
- newRow[index1] = newCell2;
-
- return newRow;
- });
- }
-
- removeColumn(index) {
- index = +index;
- const filter = cell => cell.colIndex !== index;
- const map = (cell, i) => Object.assign({}, cell, { colIndex: i });
- // update columns
- this.columns = this.columns
- .filter(filter)
- .map(map);
-
- // update rows
- this.rows = this.rows.map(row => {
- const newRow = row
- .filter(filter)
- .map(map);
-
- return newRow;
- });
- }
-
- updateRow(row, rowIndex) {
- if (row.length < this.columns.length) {
- if (this.hasColumnById('_rowIndex')) {
- const val = (rowIndex + 1) + '';
-
- row = [val].concat(row);
- }
-
- if (this.hasColumnById('_checkbox')) {
- const val = '';
-
- row = [val].concat(row);
- }
}
- const _row = this.prepareRow(row, rowIndex);
- const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
- this.rows[index] = _row;
+ prepareRows() {
+ this.validateData(this.data);
- return _row;
- }
+ this.rows = this.data.map((d, i) => {
+ const index = this._getNextRowCount();
- updateCell(colIndex, rowIndex, options) {
- let cell;
- if (typeof colIndex === 'object') {
- // cell object was passed,
- // must have colIndex, rowIndex
- cell = colIndex;
- colIndex = cell.colIndex;
- rowIndex = cell.rowIndex;
- // the object passed must be merged with original cell
- options = cell;
- }
- cell = this.getCell(colIndex, rowIndex);
+ let row = [];
- // mutate object directly
- for (let key in options) {
- const newVal = options[key];
- if (newVal !== undefined) {
- cell[key] = newVal;
- }
+ if (Array.isArray(d)) {
+ // row is an array
+ if (this.options.addCheckboxColumn) {
+ row.push(this.getCheckboxHTML());
+ }
+ if (this.options.addSerialNoColumn) {
+ row.push((index + 1) + '');
+ }
+ row = row.concat(d);
+
+ while (row.length < this.columns.length) {
+ row.push('');
+ }
+
+ } else {
+ // row is an object
+ for (let col of this.columns) {
+ if (col.id === '_checkbox') {
+ row.push(this.getCheckboxHTML());
+ } else if (col.id === '_rowIndex') {
+ row.push((index + 1) + '');
+ } else {
+ row.push(d[col.id]);
+ }
+ }
+ }
+
+ return this.prepareRow(row, {
+ rowIndex: index
+ });
+ });
}
- return cell;
- }
+ prepareRow(row, props) {
+ const baseRowCell = {
+ rowIndex: props.rowIndex
+ };
- updateColumn(colIndex, keyValPairs) {
- const column = this.getColumn(colIndex);
- for (let key in keyValPairs) {
- const newVal = keyValPairs[key];
- if (newVal !== undefined) {
- column[key] = newVal;
- }
- }
- return column;
- }
+ row = row
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(cell => Object.assign({}, baseRowCell, cell));
- filterRows(keyword, colIndex) {
- let rowsToHide = [];
- let rowsToShow = [];
- const cells = this.rows.map(row => row[colIndex]);
-
- cells.forEach(cell => {
- const hay = cell.content.toLowerCase();
- const needle = (keyword || '').toLowerCase();
-
- if (!needle || hay.includes(needle)) {
- rowsToShow.push(cell.rowIndex);
- } else {
- rowsToHide.push(cell.rowIndex);
- }
- });
-
- return {rowsToHide, rowsToShow};
- }
-
- getRowCount() {
- return this.rowCount;
- }
-
- _getNextRowCount() {
- const val = this.rowCount;
-
- this.rowCount++;
- return val;
- }
-
- getRows(start, end) {
- return this.rows.slice(start, end);
- }
-
- getColumns(skipStandardColumns) {
- let columns = this.columns;
-
- if (skipStandardColumns) {
- columns = columns.slice(this.getStandardColumnCount());
+ // monkey patched in array object
+ row.meta = props;
+ return row;
}
- return columns;
- }
+ validateColumns() {
+ const columns = this.options.columns;
+ if (!Array.isArray(columns)) {
+ throw new DataError('`columns` must be an array');
+ }
- getStandardColumnCount() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ columns.forEach((column, i) => {
+ if (typeof column !== 'string' && typeof column !== 'object') {
+ throw new DataError(`column "${i}" must be a string or an object`);
+ }
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ validateData(data) {
+ if (Array.isArray(data) &&
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
+ return true;
+ }
+ throw new DataError('`data` must be an array of arrays or objects');
}
- return 0;
- }
+ appendRows(rows) {
+ this.validateData(rows);
- getColumnCount(skipStandardColumns) {
- let val = this.columns.length;
-
- if (skipStandardColumns) {
- val = val - this.getStandardColumnCount();
+ this.rows = this.rows.concat(this.prepareRows(rows));
}
- return val;
- }
+ sortRows(colIndex, sortOrder = 'none') {
+ colIndex = +colIndex;
- getColumn(colIndex) {
- colIndex = +colIndex;
- return this.columns.find(col => col.colIndex === colIndex);
- }
+ // reset sortOrder and update for colIndex
+ this.getColumns()
+ .map(col => {
+ if (col.colIndex === colIndex) {
+ col.sortOrder = sortOrder;
+ } else {
+ col.sortOrder = 'none';
+ }
+ });
- getRow(rowIndex) {
- rowIndex = +rowIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex);
- }
+ this._sortRows(colIndex, sortOrder);
+ }
- getCell(colIndex, rowIndex) {
- rowIndex = +rowIndex;
- colIndex = +colIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
- }
+ _sortRows(colIndex, sortOrder) {
- get() {
- return {
- columns: this.columns,
- rows: this.rows
- };
- }
+ if (this.currentSort.colIndex === colIndex) {
+ // reverse the array if only sortOrder changed
+ if (
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
+ ) {
+ this.reverseArray(this.rows);
+ this.currentSort.sortOrder = sortOrder;
+ return;
+ }
+ }
- hasColumn(name) {
- return Boolean(this.columns.find(col => col.content === name));
- }
+ this.rows.sort((a, b) => {
+ const _aIndex = a[0].rowIndex;
+ const _bIndex = b[0].rowIndex;
+ const _a = a[colIndex].content;
+ const _b = b[colIndex].content;
- hasColumnById(id) {
- return Boolean(this.columns.find(col => col.id === id));
- }
+ if (sortOrder === 'none') {
+ return _aIndex - _bIndex;
+ } else if (sortOrder === 'asc') {
+ if (_a < _b) return -1;
+ if (_a > _b) return 1;
+ if (_a === _b) return 0;
+ } else if (sortOrder === 'desc') {
+ if (_a < _b) return 1;
+ if (_a > _b) return -1;
+ if (_a === _b) return 0;
+ }
+ return 0;
+ });
- getColumnIndex(name) {
- return this.columns.findIndex(col => col.content === name);
- }
+ if (this.hasColumnById('_rowIndex')) {
+ // update row index
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
+ this.rows.forEach((row, index) => {
+ row.forEach(cell => {
+ if (cell.colIndex === srNoColIndex) {
+ cell.content = (index + 1) + '';
+ }
+ });
+ });
+ }
+ }
- getColumnIndexById(id) {
- return this.columns.findIndex(col => col.id === id);
- }
+ reverseArray(array) {
+ let left = null;
+ let right = null;
+ let length = array.length;
- getCheckboxHTML() {
- return '';
- }
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
+ const temporary = array[left];
+
+ array[left] = array[right];
+ array[right] = temporary;
+ }
+ }
+
+ switchColumn(index1, index2) {
+ // update columns
+ const temp = this.columns[index1];
+ this.columns[index1] = this.columns[index2];
+ this.columns[index2] = temp;
+
+ this.columns[index1].colIndex = index1;
+ this.columns[index2].colIndex = index2;
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newCell1 = Object.assign({}, row[index1], {
+ colIndex: index2
+ });
+ const newCell2 = Object.assign({}, row[index2], {
+ colIndex: index1
+ });
+
+ let newRow = row.map(cell => {
+ // make object copy
+ return Object.assign({}, cell);
+ });
+
+ newRow[index2] = newCell1;
+ newRow[index1] = newCell2;
+
+ return newRow;
+ });
+ }
+
+ removeColumn(index) {
+ index = +index;
+ const filter = cell => cell.colIndex !== index;
+ const map = (cell, i) => Object.assign({}, cell, {
+ colIndex: i
+ });
+ // update columns
+ this.columns = this.columns
+ .filter(filter)
+ .map(map);
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newRow = row
+ .filter(filter)
+ .map(map);
+
+ return newRow;
+ });
+ }
+
+ updateRow(row, rowIndex) {
+ if (row.length < this.columns.length) {
+ if (this.hasColumnById('_rowIndex')) {
+ const val = (rowIndex + 1) + '';
+
+ row = [val].concat(row);
+ }
+
+ if (this.hasColumnById('_checkbox')) {
+ const val = '';
+
+ row = [val].concat(row);
+ }
+ }
+
+ const _row = this.prepareRow(row, rowIndex);
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
+ this.rows[index] = _row;
+
+ return _row;
+ }
+
+ updateCell(colIndex, rowIndex, options) {
+ let cell;
+ if (typeof colIndex === 'object') {
+ // cell object was passed,
+ // must have colIndex, rowIndex
+ cell = colIndex;
+ colIndex = cell.colIndex;
+ rowIndex = cell.rowIndex;
+ // the object passed must be merged with original cell
+ options = cell;
+ }
+ cell = this.getCell(colIndex, rowIndex);
+
+ // mutate object directly
+ for (let key in options) {
+ const newVal = options[key];
+ if (newVal !== undefined) {
+ cell[key] = newVal;
+ }
+ }
+
+ return cell;
+ }
+
+ updateColumn(colIndex, keyValPairs) {
+ const column = this.getColumn(colIndex);
+ for (let key in keyValPairs) {
+ const newVal = keyValPairs[key];
+ if (newVal !== undefined) {
+ column[key] = newVal;
+ }
+ }
+ return column;
+ }
+
+ filterRows(keyword, colIndex) {
+ let rowsToHide = [];
+ let rowsToShow = [];
+ const cells = this.rows.map(row => row[colIndex]);
+
+ cells.forEach(cell => {
+ const hay = cell.content.toLowerCase();
+ const needle = (keyword || '').toLowerCase();
+
+ if (!needle || hay.includes(needle)) {
+ rowsToShow.push(cell.rowIndex);
+ } else {
+ rowsToHide.push(cell.rowIndex);
+ }
+ });
+
+ return {
+ rowsToHide,
+ rowsToShow
+ };
+ }
+
+ getRowCount() {
+ return this.rowCount;
+ }
+
+ _getNextRowCount() {
+ const val = this.rowCount;
+
+ this.rowCount++;
+ return val;
+ }
+
+ getRows(start, end) {
+ return this.rows.slice(start, end);
+ }
+
+ getColumns(skipStandardColumns) {
+ let columns = this.columns;
+
+ if (skipStandardColumns) {
+ columns = columns.slice(this.getStandardColumnCount());
+ }
+
+ return columns;
+ }
+
+ getStandardColumnCount() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getColumnCount(skipStandardColumns) {
+ let val = this.columns.length;
+
+ if (skipStandardColumns) {
+ val = val - this.getStandardColumnCount();
+ }
+
+ return val;
+ }
+
+ getColumn(colIndex) {
+ colIndex = +colIndex;
+ return this.columns.find(col => col.colIndex === colIndex);
+ }
+
+ getRow(rowIndex) {
+ rowIndex = +rowIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex);
+ }
+
+ getCell(colIndex, rowIndex) {
+ rowIndex = +rowIndex;
+ colIndex = +colIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
+ }
+
+ get() {
+ return {
+ columns: this.columns,
+ rows: this.rows
+ };
+ }
+
+ hasColumn(name) {
+ return Boolean(this.columns.find(col => col.content === name));
+ }
+
+ hasColumnById(id) {
+ return Boolean(this.columns.find(col => col.id === id));
+ }
+
+ getColumnIndex(name) {
+ return this.columns.findIndex(col => col.content === name);
+ }
+
+ getColumnIndexById(id) {
+ return this.columns.findIndex(col => col.id === id);
+ }
+
+ getCheckboxHTML() {
+ return '';
+ }
}
// Custom Errors
class DataError extends TypeError {}
class ColumnManager {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options',
- 'fireEvent',
- 'header',
- 'datamanager',
- 'style',
- 'wrapper',
- 'rowmanager',
- 'bodyScrollable'
- ]);
+ linkProperties(this, this.instance, [
+ 'options',
+ 'fireEvent',
+ 'header',
+ 'datamanager',
+ 'style',
+ 'wrapper',
+ 'rowmanager',
+ 'bodyScrollable'
+ ]);
- this.bindEvents();
- getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
- }
+ this.bindEvents();
+ getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
+ }
- renderHeader() {
- this.header.innerHTML = '';
- this.refreshHeader();
- }
+ renderHeader() {
+ this.header.innerHTML = '';
+ this.refreshHeader();
+ }
- refreshHeader() {
- const columns = this.datamanager.getColumns();
+ refreshHeader() {
+ const columns = this.datamanager.getColumns();
- if (!$('.data-table-col', this.header)) {
- // insert html
+ if (!$('.data-table-col', this.header)) {
+ // insert html
- let html = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- if (this.options.enableInlineFilters) {
- html += this.rowmanager.getRowHTML(columns, { isFilter: 1 });
- }
+ let html = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ if (this.options.enableInlineFilters) {
+ html += this.rowmanager.getRowHTML(columns, {
+ isFilter: 1
+ });
+ }
- $('thead', this.header).innerHTML = html;
+ $('thead', this.header).innerHTML = html;
- this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
+ this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
- if (this.$filterRow) {
- // hide filter row immediately, so it doesn't disturb layout
- $.style(this.$filterRow, {
- display: 'none'
- });
- }
- } else {
- // refresh dom state
- const $cols = $.each('.data-table-col', this.header);
- if (columns.length < $cols.length) {
- // deleted column
- $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- return;
- }
+ if (this.$filterRow) {
+ // hide filter row immediately, so it doesn't disturb layout
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ }
+ } else {
+ // refresh dom state
+ const $cols = $.each('.data-table-col', this.header);
+ if (columns.length < $cols.length) {
+ // deleted column
+ $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ return;
+ }
- $cols.map(($col, i) => {
- const column = columns[i];
- // column sorted or order changed
- // update colIndex of each header cell
- $.data($col, {
- colIndex: column.colIndex
- });
+ $cols.map(($col, i) => {
+ const column = columns[i];
+ // column sorted or order changed
+ // update colIndex of each header cell
+ $.data($col, {
+ colIndex: column.colIndex
+ });
- // refresh sort indicator
- const sortIndicator = $('.sort-indicator', $col);
- if (sortIndicator) {
- sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ // refresh sort indicator
+ const sortIndicator = $('.sort-indicator', $col);
+ if (sortIndicator) {
+ sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ }
+ });
}
- });
+ // reset columnMap
+ this.$columnMap = [];
}
- // reset columnMap
- this.$columnMap = [];
- }
- bindEvents() {
- this.bindDropdown();
- this.bindResizeColumn();
- this.bindMoveColumn();
- this.bindFilter();
- }
-
- bindDropdown() {
- let $activeDropdown;
- $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
- const $dropdown = $.closest('.data-table-dropdown', $button);
-
- if (!$dropdown.classList.contains('is-active')) {
- deactivateDropdown();
- $dropdown.classList.add('is-active');
- $activeDropdown = $dropdown;
- } else {
- deactivateDropdown();
- }
- });
-
- $.on(document.body, 'click', (e) => {
- if (e.target.matches('.data-table-dropdown-toggle')) return;
- deactivateDropdown();
- });
-
- const dropdownItems = this.options.headerDropdown;
-
- $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
- const $col = $.closest('.data-table-col', $item);
- const { index } = $.data($item);
- const { colIndex } = $.data($col);
- let callback = dropdownItems[index].action;
-
- callback && callback.call(this.instance, this.getColumn(colIndex));
- });
-
- function deactivateDropdown(e) {
- $activeDropdown && $activeDropdown.classList.remove('is-active');
- $activeDropdown = null;
+ bindEvents() {
+ this.bindDropdown();
+ this.bindResizeColumn();
+ this.bindMoveColumn();
+ this.bindFilter();
}
- }
- bindResizeColumn() {
- let isDragging = false;
- let $resizingCell, startWidth, startX;
+ bindDropdown() {
+ let $activeDropdown;
+ $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
+ const $dropdown = $.closest('.data-table-dropdown', $button);
- $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
- document.body.classList.add('data-table-resize');
- const $cell = $handle.parentNode.parentNode;
- $resizingCell = $cell;
- const { colIndex } = $.data($resizingCell);
- const col = this.getColumn(colIndex);
-
- if (col && col.resizable === false) {
- return;
- }
-
- isDragging = true;
- startWidth = $.style($('.content', $resizingCell), 'width');
- startX = e.pageX;
- });
-
- $.on(document.body, 'mouseup', (e) => {
- document.body.classList.remove('data-table-resize');
- if (!$resizingCell) return;
- isDragging = false;
-
- const { colIndex } = $.data($resizingCell);
- this.setColumnWidth(colIndex);
- this.style.setBodyStyle();
- $resizingCell = null;
- });
-
- $.on(document.body, 'mousemove', (e) => {
- if (!isDragging) return;
- const finalWidth = startWidth + (e.pageX - startX);
- const { colIndex } = $.data($resizingCell);
-
- if (this.getColumnMinWidth(colIndex) > finalWidth) {
- // don't resize past minWidth
- return;
- }
- this.datamanager.updateColumn(colIndex, { width: finalWidth });
- this.setColumnHeaderWidth(colIndex);
- });
- }
-
- bindMoveColumn() {
- let initialized;
-
- const initialize = () => {
- if (initialized) {
- $.off(document.body, 'mousemove', initialize);
- return;
- }
- const ready = $('.data-table-col', this.header);
- if (!ready) return;
-
- const $parent = $('.data-table-row', this.header);
-
- this.sortable = Sortable.create($parent, {
- onEnd: (e) => {
- const { oldIndex, newIndex } = e;
- const $draggedCell = e.item;
- const { colIndex } = $.data($draggedCell);
- if (+colIndex === newIndex) return;
-
- this.switchColumn(oldIndex, newIndex);
- },
- preventOnFilter: false,
- filter: '.column-resizer, .data-table-dropdown',
- animation: 150
- });
- };
-
- $.on(document.body, 'mousemove', initialize);
- }
-
- bindSortColumn() {
-
- $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
- const $cell = span.closest('.data-table-col');
- let { colIndex, sortOrder } = $.data($cell);
- sortOrder = getDefault(sortOrder, 'none');
- const col = this.getColumn(colIndex);
-
- if (col && col.sortable === false) {
- return;
- }
-
- // reset sort indicator
- $('.sort-indicator', this.header).textContent = '';
- $.each('.data-table-col', this.header).map($cell => {
- $.data($cell, {
- sortOrder: 'none'
+ if (!$dropdown.classList.contains('is-active')) {
+ deactivateDropdown();
+ $dropdown.classList.add('is-active');
+ $activeDropdown = $dropdown;
+ } else {
+ deactivateDropdown();
+ }
});
- });
- let nextSortOrder, textContent;
- if (sortOrder === 'none') {
- nextSortOrder = 'asc';
- textContent = '▲';
- } else if (sortOrder === 'asc') {
- nextSortOrder = 'desc';
- textContent = '▼';
- } else if (sortOrder === 'desc') {
- nextSortOrder = 'none';
- textContent = '';
- }
-
- $.data($cell, {
- sortOrder: nextSortOrder
- });
- $('.sort-indicator', $cell).textContent = textContent;
-
- this.sortColumn(colIndex, nextSortOrder);
- });
- }
-
- sortColumn(colIndex, nextSortOrder) {
- this.instance.freeze();
- this.sortRows(colIndex, nextSortOrder)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onSortColumn', this.getColumn(colIndex));
- });
- }
-
- removeColumn(colIndex) {
- const removedCol = this.getColumn(colIndex);
- this.instance.freeze();
- this.datamanager.removeColumn(colIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onRemoveColumn', removedCol);
- });
- }
-
- switchColumn(oldIndex, newIndex) {
- this.instance.freeze();
- this.datamanager.switchColumn(oldIndex, newIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => {
- this.setColumnWidth(oldIndex);
- this.setColumnWidth(newIndex);
- this.instance.unfreeze();
- })
- .then(() => {
- this.fireEvent('onSwitchColumn',
- this.getColumn(oldIndex), this.getColumn(newIndex)
- );
- });
- }
-
- toggleFilter() {
- this.isFilterShown = this.isFilterShown || false;
-
- if (this.isFilterShown) {
- $.style(this.$filterRow, {
- display: 'none'
- });
- } else {
- $.style(this.$filterRow, {
- display: ''
- });
- }
-
- this.isFilterShown = !this.isFilterShown;
- this.style.setBodyStyle();
- }
-
- focusFilter(colIndex) {
- if (!this.isFilterShown) return;
-
- const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
- $filterInput.focus();
- }
-
- bindFilter() {
- if (!this.options.enableInlineFilters) return;
- const handler = e => {
- const $filterCell = $.closest('.data-table-col', e.target);
- const { colIndex } = $.data($filterCell);
- const keyword = e.target.value;
-
- this.datamanager.filterRows(keyword, colIndex)
- .then(({ rowsToHide, rowsToShow }) => {
- rowsToHide.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.add('hide');
- });
- rowsToShow.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.remove('hide');
- });
+ $.on(document.body, 'click', (e) => {
+ if (e.target.matches('.data-table-dropdown-toggle')) return;
+ deactivateDropdown();
});
- };
- $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300));
- }
- sortRows(colIndex, sortOrder) {
- return this.datamanager.sortRows(colIndex, sortOrder);
- }
+ const dropdownItems = this.options.headerDropdown;
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
+ $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
+ const $col = $.closest('.data-table-col', $item);
+ const {
+ index
+ } = $.data($item);
+ const {
+ colIndex
+ } = $.data($col);
+ let callback = dropdownItems[index].action;
- getColumns() {
- return this.datamanager.getColumns();
- }
+ callback && callback.call(this.instance, this.getColumn(colIndex));
+ });
- setColumnWidth(colIndex) {
- colIndex = +colIndex;
- this._columnWidthMap = this._columnWidthMap || [];
-
- const { width } = this.getColumn(colIndex);
-
- let index = this._columnWidthMap[colIndex];
- const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
- const styles = {
- width: width + 'px'
- };
-
- index = this.style.setStyle(selector, styles, index);
- this._columnWidthMap[colIndex] = index;
- }
-
- setColumnHeaderWidth(colIndex) {
- colIndex = +colIndex;
- this.$columnMap = this.$columnMap || [];
- const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
- const { width } = this.getColumn(colIndex);
-
- let $column = this.$columnMap[colIndex];
- if (!$column) {
- $column = this.header.querySelector(selector);
- this.$columnMap[colIndex] = $column;
+ function deactivateDropdown(e) {
+ $activeDropdown && $activeDropdown.classList.remove('is-active');
+ $activeDropdown = null;
+ }
}
- $column.style.width = width + 'px';
- }
+ bindResizeColumn() {
+ let isDragging = false;
+ let $resizingCell, startWidth, startX;
- getColumnMinWidth(colIndex) {
- colIndex = +colIndex;
- return this.getColumn(colIndex).minWidth || 24;
- }
+ $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
+ document.body.classList.add('data-table-resize');
+ const $cell = $handle.parentNode.parentNode;
+ $resizingCell = $cell;
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ const col = this.getColumn(colIndex);
- getFirstColumnIndex() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ if (col && col.resizable === false) {
+ return;
+ }
+
+ isDragging = true;
+ startWidth = $.style($('.content', $resizingCell), 'width');
+ startX = e.pageX;
+ });
+
+ $.on(document.body, 'mouseup', (e) => {
+ document.body.classList.remove('data-table-resize');
+ if (!$resizingCell) return;
+ isDragging = false;
+
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ this.setColumnWidth(colIndex);
+ this.style.setBodyStyle();
+ $resizingCell = null;
+ });
+
+ $.on(document.body, 'mousemove', (e) => {
+ if (!isDragging) return;
+ const finalWidth = startWidth + (e.pageX - startX);
+ const {
+ colIndex
+ } = $.data($resizingCell);
+
+ if (this.getColumnMinWidth(colIndex) > finalWidth) {
+ // don't resize past minWidth
+ return;
+ }
+ this.datamanager.updateColumn(colIndex, {
+ width: finalWidth
+ });
+ this.setColumnHeaderWidth(colIndex);
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ bindMoveColumn() {
+ let initialized;
+
+ const initialize = () => {
+ if (initialized) {
+ $.off(document.body, 'mousemove', initialize);
+ return;
+ }
+ const ready = $('.data-table-col', this.header);
+ if (!ready) return;
+
+ const $parent = $('.data-table-row', this.header);
+
+ this.sortable = Sortable.create($parent, {
+ onEnd: (e) => {
+ const {
+ oldIndex,
+ newIndex
+ } = e;
+ const $draggedCell = e.item;
+ const {
+ colIndex
+ } = $.data($draggedCell);
+ if (+colIndex === newIndex) return;
+
+ this.switchColumn(oldIndex, newIndex);
+ },
+ preventOnFilter: false,
+ filter: '.column-resizer, .data-table-dropdown',
+ animation: 150
+ });
+ };
+
+ $.on(document.body, 'mousemove', initialize);
}
- return 0;
- }
+ bindSortColumn() {
- getHeaderCell$(colIndex) {
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
+ const $cell = span.closest('.data-table-col');
+ let {
+ colIndex,
+ sortOrder
+ } = $.data($cell);
+ sortOrder = getDefault(sortOrder, 'none');
+ const col = this.getColumn(colIndex);
- getLastColumnIndex() {
- return this.datamanager.getColumnCount() - 1;
- }
+ if (col && col.sortable === false) {
+ return;
+ }
- getSerialColumnIndex() {
- const columns = this.datamanager.getColumns();
+ // reset sort indicator
+ $('.sort-indicator', this.header).textContent = '';
+ $.each('.data-table-col', this.header).map($cell => {
+ $.data($cell, {
+ sortOrder: 'none'
+ });
+ });
- return columns.findIndex(column => column.content.includes('Sr. No'));
- }
+ let nextSortOrder, textContent;
+ if (sortOrder === 'none') {
+ nextSortOrder = 'asc';
+ textContent = '▲';
+ } else if (sortOrder === 'asc') {
+ nextSortOrder = 'desc';
+ textContent = '▼';
+ } else if (sortOrder === 'desc') {
+ nextSortOrder = 'none';
+ textContent = '';
+ }
+
+ $.data($cell, {
+ sortOrder: nextSortOrder
+ });
+ $('.sort-indicator', $cell).textContent = textContent;
+
+ this.sortColumn(colIndex, nextSortOrder);
+ });
+ }
+
+ sortColumn(colIndex, nextSortOrder) {
+ this.instance.freeze();
+ this.sortRows(colIndex, nextSortOrder)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
+ });
+ }
+
+ removeColumn(colIndex) {
+ const removedCol = this.getColumn(colIndex);
+ this.instance.freeze();
+ this.datamanager.removeColumn(colIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onRemoveColumn', removedCol);
+ });
+ }
+
+ switchColumn(oldIndex, newIndex) {
+ this.instance.freeze();
+ this.datamanager.switchColumn(oldIndex, newIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => {
+ this.setColumnWidth(oldIndex);
+ this.setColumnWidth(newIndex);
+ this.instance.unfreeze();
+ })
+ .then(() => {
+ this.fireEvent('onSwitchColumn',
+ this.getColumn(oldIndex), this.getColumn(newIndex)
+ );
+ });
+ }
+
+ toggleFilter() {
+ this.isFilterShown = this.isFilterShown || false;
+
+ if (this.isFilterShown) {
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ } else {
+ $.style(this.$filterRow, {
+ display: ''
+ });
+ }
+
+ this.isFilterShown = !this.isFilterShown;
+ this.style.setBodyStyle();
+ }
+
+ focusFilter(colIndex) {
+ if (!this.isFilterShown) return;
+
+ const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
+ $filterInput.focus();
+ }
+
+ bindFilter() {
+ if (!this.options.enableInlineFilters) return;
+ const handler = e => {
+ const $filterCell = $.closest('.data-table-col', e.target);
+ const {
+ colIndex
+ } = $.data($filterCell);
+ const keyword = e.target.value;
+
+ this.datamanager.filterRows(keyword, colIndex)
+ .then(({
+ rowsToHide,
+ rowsToShow
+ }) => {
+ rowsToHide.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.add('hide');
+ });
+ rowsToShow.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.remove('hide');
+ });
+ });
+ };
+ $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300));
+ }
+
+ sortRows(colIndex, sortOrder) {
+ return this.datamanager.sortRows(colIndex, sortOrder);
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ setColumnWidth(colIndex) {
+ colIndex = +colIndex;
+ this._columnWidthMap = this._columnWidthMap || [];
+
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let index = this._columnWidthMap[colIndex];
+ const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
+ const styles = {
+ width: width + 'px'
+ };
+
+ index = this.style.setStyle(selector, styles, index);
+ this._columnWidthMap[colIndex] = index;
+ }
+
+ setColumnHeaderWidth(colIndex) {
+ colIndex = +colIndex;
+ this.$columnMap = this.$columnMap || [];
+ const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let $column = this.$columnMap[colIndex];
+ if (!$column) {
+ $column = this.header.querySelector(selector);
+ this.$columnMap[colIndex] = $column;
+ }
+
+ $column.style.width = width + 'px';
+ }
+
+ getColumnMinWidth(colIndex) {
+ colIndex = +colIndex;
+ return this.getColumn(colIndex).minWidth || 24;
+ }
+
+ getFirstColumnIndex() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getHeaderCell$(colIndex) {
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+
+ getLastColumnIndex() {
+ return this.datamanager.getColumnCount() - 1;
+ }
+
+ getSerialColumnIndex() {
+ const columns = this.datamanager.getColumns();
+
+ return columns.findIndex(column => column.content.includes('Sr. No'));
+ }
}
// eslint-disable-next-line
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
- // add dropdown buttons
- const dropdownItems = this.options.headerDropdown;
+ // add dropdown buttons
+ const dropdownItems = this.options.headerDropdown;
- return `
${dropdownButton}
+ return `${dropdownButton}
${dropdownItems.map((d, i) => `
${d.label}
`).join('')}
@@ -1753,603 +1812,649 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
};
class CellManager {
- constructor(instance) {
- this.instance = instance;
- this.wrapper = this.instance.wrapper;
- this.options = this.instance.options;
- this.style = this.instance.style;
- this.bodyScrollable = this.instance.bodyScrollable;
- this.columnmanager = this.instance.columnmanager;
- this.rowmanager = this.instance.rowmanager;
- this.datamanager = this.instance.datamanager;
- this.keyboard = this.instance.keyboard;
+ constructor(instance) {
+ this.instance = instance;
+ this.wrapper = this.instance.wrapper;
+ this.options = this.instance.options;
+ this.style = this.instance.style;
+ this.bodyScrollable = this.instance.bodyScrollable;
+ this.columnmanager = this.instance.columnmanager;
+ this.rowmanager = this.instance.rowmanager;
+ this.datamanager = this.instance.datamanager;
+ this.keyboard = this.instance.keyboard;
- this.bindEvents();
- }
+ this.bindEvents();
+ }
- bindEvents() {
- this.bindFocusCell();
- this.bindEditCell();
- this.bindKeyboardSelection();
- this.bindCopyCellContents();
- this.bindMouseEvents();
- }
+ bindEvents() {
+ this.bindFocusCell();
+ this.bindEditCell();
+ this.bindKeyboardSelection();
+ this.bindCopyCellContents();
+ this.bindMouseEvents();
+ }
- bindFocusCell() {
- this.bindKeyboardNav();
- }
+ bindFocusCell() {
+ this.bindKeyboardNav();
+ }
- bindEditCell() {
- this.$editingCell = null;
+ bindEditCell() {
+ this.$editingCell = null;
- $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
- this.activateEditing(cell);
- });
+ $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
+ this.activateEditing(cell);
+ });
+
+ this.keyboard.on('enter', (e) => {
+ if (this.$focusedCell && !this.$editingCell) {
+ // enter keypress on focused cell
+ this.activateEditing(this.$focusedCell);
+ } else if (this.$editingCell) {
+ // enter keypress on editing cell
+ this.submitEditing();
+ this.deactivateEditing();
+ }
+ });
+ }
+
+ bindKeyboardNav() {
+ const focusCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+
+ if (direction === 'left' || direction === 'shift+tab') {
+ $cell = this.getLeftCell$($cell);
+ } else if (direction === 'right' || direction === 'tab') {
+ $cell = this.getRightCell$($cell);
+ } else if (direction === 'up') {
+ $cell = this.getAboveCell$($cell);
+ } else if (direction === 'down') {
+ $cell = this.getBelowCell$($cell);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ const focusLastCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ if (direction === 'left') {
+ $cell = this.getLeftMostCell$(rowIndex);
+ } else if (direction === 'right') {
+ $cell = this.getRightMostCell$(rowIndex);
+ } else if (direction === 'up') {
+ $cell = this.getTopMostCell$(colIndex);
+ } else if (direction === 'down') {
+ $cell = this.getBottomMostCell$(colIndex);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
+ direction => this.keyboard.on(direction, () => focusCell(direction))
+ );
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
+ );
+
+ this.keyboard.on('esc', () => {
+ this.deactivateEditing();
+ });
+
+ if (this.options.enableInlineFilters) {
+ this.keyboard.on('ctrl+f', (e) => {
+ const $cell = $.closest('.data-table-col', e.target);
+ let {
+ colIndex
+ } = $.data($cell);
+
+ this.activateFilter(colIndex);
+ return true;
+ });
+ }
+ }
+
+ bindKeyboardSelection() {
+ const getNextSelectionCursor = (direction) => {
+ let $selectionCursor = this.getSelectionCursor();
+
+ if (direction === 'left') {
+ $selectionCursor = this.getLeftCell$($selectionCursor);
+ } else if (direction === 'right') {
+ $selectionCursor = this.getRightCell$($selectionCursor);
+ } else if (direction === 'up') {
+ $selectionCursor = this.getAboveCell$($selectionCursor);
+ } else if (direction === 'down') {
+ $selectionCursor = this.getBelowCell$($selectionCursor);
+ }
+
+ return $selectionCursor;
+ };
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('shift+' + direction,
+ () => this.selectArea(getNextSelectionCursor(direction)))
+ );
+ }
+
+ bindCopyCellContents() {
+ this.keyboard.on('ctrl+c', () => {
+ this.copyCellContents(this.$focusedCell, this.$selectionCursor);
+ });
+ }
+
+ bindMouseEvents() {
+ let mouseDown = null;
+
+ $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
+ mouseDown = true;
+ this.focusCell($(e.delegatedTarget));
+ });
+
+ $.on(this.bodyScrollable, 'mouseup', () => {
+ mouseDown = false;
+ });
+
+ const selectArea = (e) => {
+ if (!mouseDown) return;
+ this.selectArea($(e.delegatedTarget));
+ };
+
+ $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50));
+ }
+
+ focusCell($cell, {
+ skipClearSelection = 0
+ } = {}) {
+ if (!$cell) return;
+
+ // don't focus if already editing cell
+ if ($cell === this.$editingCell) return;
+
+ const {
+ colIndex,
+ isHeader
+ } = $.data($cell);
+ if (isHeader) {
+ return;
+ }
+
+ const column = this.columnmanager.getColumn(colIndex);
+ if (column.focusable === false) {
+ return;
+ }
+
+ this.scrollToCell($cell);
- this.keyboard.on('enter', (e) => {
- if (this.$focusedCell && !this.$editingCell) {
- // enter keypress on focused cell
- this.activateEditing(this.$focusedCell);
- } else if (this.$editingCell) {
- // enter keypress on editing cell
- this.submitEditing();
this.deactivateEditing();
- }
- });
- }
+ if (!skipClearSelection) {
+ this.clearSelection();
+ }
- bindKeyboardNav() {
- const focusCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
+ if (this.$focusedCell) {
+ this.$focusedCell.classList.remove('selected');
+ }
- let $cell = this.$focusedCell;
+ this.$focusedCell = $cell;
+ $cell.classList.add('selected');
- if (direction === 'left' || direction === 'shift+tab') {
- $cell = this.getLeftCell$($cell);
- } else if (direction === 'right' || direction === 'tab') {
- $cell = this.getRightCell$($cell);
- } else if (direction === 'up') {
- $cell = this.getAboveCell$($cell);
- } else if (direction === 'down') {
- $cell = this.getBelowCell$($cell);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- const focusLastCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
-
- let $cell = this.$focusedCell;
- const { rowIndex, colIndex } = $.data($cell);
-
- if (direction === 'left') {
- $cell = this.getLeftMostCell$(rowIndex);
- } else if (direction === 'right') {
- $cell = this.getRightMostCell$(rowIndex);
- } else if (direction === 'up') {
- $cell = this.getTopMostCell$(colIndex);
- } else if (direction === 'down') {
- $cell = this.getBottomMostCell$(colIndex);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
- direction => this.keyboard.on(direction, () => focusCell(direction))
- );
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
- );
-
- this.keyboard.on('esc', () => {
- this.deactivateEditing();
- });
-
- if (this.options.enableInlineFilters) {
- this.keyboard.on('ctrl+f', (e) => {
- const $cell = $.closest('.data-table-col', e.target);
- let { colIndex } = $.data($cell);
-
- this.activateFilter(colIndex);
- return true;
- });
- }
- }
-
- bindKeyboardSelection() {
- const getNextSelectionCursor = (direction) => {
- let $selectionCursor = this.getSelectionCursor();
-
- if (direction === 'left') {
- $selectionCursor = this.getLeftCell$($selectionCursor);
- } else if (direction === 'right') {
- $selectionCursor = this.getRightCell$($selectionCursor);
- } else if (direction === 'up') {
- $selectionCursor = this.getAboveCell$($selectionCursor);
- } else if (direction === 'down') {
- $selectionCursor = this.getBelowCell$($selectionCursor);
- }
-
- return $selectionCursor;
- };
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('shift+' + direction,
- () => this.selectArea(getNextSelectionCursor(direction)))
- );
- }
-
- bindCopyCellContents() {
- this.keyboard.on('ctrl+c', () => {
- this.copyCellContents(this.$focusedCell, this.$selectionCursor);
- });
- }
-
- bindMouseEvents() {
- let mouseDown = null;
-
- $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
- mouseDown = true;
- this.focusCell($(e.delegatedTarget));
- });
-
- $.on(this.bodyScrollable, 'mouseup', () => {
- mouseDown = false;
- });
-
- const selectArea = (e) => {
- if (!mouseDown) return;
- this.selectArea($(e.delegatedTarget));
- };
-
- $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50));
- }
-
- focusCell($cell, { skipClearSelection = 0 } = {}) {
- if (!$cell) return;
-
- // don't focus if already editing cell
- if ($cell === this.$editingCell) return;
-
- const { colIndex, isHeader } = $.data($cell);
- if (isHeader) {
- return;
- }
-
- const column = this.columnmanager.getColumn(colIndex);
- if (column.focusable === false) {
- return;
- }
-
- this.scrollToCell($cell);
-
- this.deactivateEditing();
- if (!skipClearSelection) {
- this.clearSelection();
- }
-
- if (this.$focusedCell) {
- this.$focusedCell.classList.remove('selected');
- }
-
- this.$focusedCell = $cell;
- $cell.classList.add('selected');
-
- // so that keyboard nav works
- $cell.focus();
-
- this.highlightRowColumnHeader($cell);
- }
-
- highlightRowColumnHeader($cell) {
- const { colIndex, rowIndex } = $.data($cell);
- const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
- const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
- const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
-
- if (this.lastHeaders) {
- $.removeStyle(this.lastHeaders, 'backgroundColor');
- }
-
- const colHeader = $(colHeaderSelector, this.wrapper);
- const rowHeader = $(rowHeaderSelector, this.wrapper);
-
- $.style([colHeader, rowHeader], {
- backgroundColor: '#f5f7fa' // light-bg
- });
-
- this.lastHeaders = [colHeader, rowHeader];
- }
-
- selectAreaOnClusterChanged() {
- if (!(this.$focusedCell && this.$selectionCursor)) return;
- const { colIndex, rowIndex } = $.data(this.$selectionCursor);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell || $cell === this.$selectionCursor) return;
-
- // selectArea needs $focusedCell
- const fCell = $.data(this.$focusedCell);
- this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
-
- this.selectArea($cell);
- }
-
- focusCellOnClusterChanged() {
- if (!this.$focusedCell) return;
-
- const { colIndex, rowIndex } = $.data(this.$focusedCell);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell) return;
- // this function is called after selectAreaOnClusterChanged,
- // focusCell calls clearSelection which resets the area selection
- // so a flag to skip it
- this.focusCell($cell, { skipClearSelection: 1 });
- }
-
- selectArea($selectionCursor) {
- if (!this.$focusedCell) return;
-
- if (this._selectArea(this.$focusedCell, $selectionCursor)) {
- // valid selection
- this.$selectionCursor = $selectionCursor;
- }
- };
-
- _selectArea($cell1, $cell2) {
- if ($cell1 === $cell2) return false;
-
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return false;
-
- this.clearSelection();
- cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
- return true;
- }
-
- getCellsInRange($cell1, $cell2) {
- let colIndex1, rowIndex1, colIndex2, rowIndex2;
-
- if (typeof $cell1 === 'number') {
- [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
- } else
- if (typeof $cell1 === 'object') {
-
- if (!($cell1 && $cell2)) {
- return false;
- }
-
- const cell1 = $.data($cell1);
- const cell2 = $.data($cell2);
-
- colIndex1 = cell1.colIndex;
- rowIndex1 = cell1.rowIndex;
- colIndex2 = cell2.colIndex;
- rowIndex2 = cell2.rowIndex;
- }
-
- if (rowIndex1 > rowIndex2) {
- [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
- }
-
- if (colIndex1 > colIndex2) {
- [colIndex1, colIndex2] = [colIndex2, colIndex1];
- }
-
- if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
- return false;
- }
-
- let cells = [];
- let colIndex = colIndex1;
- let rowIndex = rowIndex1;
- let rowIndices = [];
-
- while (rowIndex <= rowIndex2) {
- rowIndices.push(rowIndex);
- rowIndex++;
- }
-
- rowIndices.map(rowIndex => {
- while (colIndex <= colIndex2) {
- cells.push([colIndex, rowIndex]);
- colIndex++;
- }
- colIndex = colIndex1;
- });
-
- return cells;
- }
-
- clearSelection() {
- $.each('.data-table-col.highlight', this.bodyScrollable)
- .map(cell => cell.classList.remove('highlight'));
-
- this.$selectionCursor = null;
- }
-
- getSelectionCursor() {
- return this.$selectionCursor || this.$focusedCell;
- }
-
- activateEditing($cell) {
- this.focusCell($cell);
- const { rowIndex, colIndex } = $.data($cell);
-
- const col = this.columnmanager.getColumn(colIndex);
- if (col && (col.editable === false || col.focusable === false)) {
- return;
- }
-
- const cell = this.getCell(colIndex, rowIndex);
- if (cell && cell.editable === false) {
- return;
- }
-
- if (this.$editingCell) {
- const { _rowIndex, _colIndex } = $.data(this.$editingCell);
-
- if (rowIndex === _rowIndex && colIndex === _colIndex) {
- // editing the same cell
- return;
- }
- }
-
- this.$editingCell = $cell;
- $cell.classList.add('editing');
-
- const $editCell = $('.edit-cell', $cell);
- $editCell.innerHTML = '';
-
- const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
-
- if (editor) {
- this.currentCellEditor = editor;
- // initialize editing input with cell value
- editor.initValue(cell.content, rowIndex, col);
- }
- }
-
- deactivateEditing() {
- // keep focus on the cell so that keyboard navigation works
- if (this.$focusedCell) this.$focusedCell.focus();
-
- if (!this.$editingCell) return;
- this.$editingCell.classList.remove('editing');
- this.$editingCell = null;
- }
-
- getEditor(colIndex, rowIndex, value, parent) {
- // debugger;
- const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
- if (obj && obj.setValue) return obj;
-
- // editing fallback
- const $input = $.create('input', {
- class: 'input-style',
- type: 'text',
- inside: parent
- });
-
- return {
- initValue(value) {
- $input.focus();
- $input.value = value;
- },
- getValue() {
- return $input.value;
- },
- setValue(value) {
- $input.value = value;
- }
- };
- }
-
- submitEditing() {
- if (!this.$editingCell) return;
- const $cell = this.$editingCell;
- const { rowIndex, colIndex } = $.data($cell);
- const col = this.datamanager.getColumn(colIndex);
-
- if ($cell) {
- const editor = this.currentCellEditor;
-
- if (editor) {
- const value = editor.getValue();
- const done = editor.setValue(value, rowIndex, col);
- const oldValue = this.getCell(colIndex, rowIndex).content;
-
- // update cell immediately
- this.updateCell(colIndex, rowIndex, value);
+ // so that keyboard nav works
$cell.focus();
- if (done && done.then) {
- // revert to oldValue if promise fails
- done.catch((e) => {
- console.log(e);
- this.updateCell(colIndex, rowIndex, oldValue);
- });
+ this.highlightRowColumnHeader($cell);
+ }
+
+ highlightRowColumnHeader($cell) {
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell);
+ const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
+ const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
+ const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
+
+ if (this.lastHeaders) {
+ $.removeStyle(this.lastHeaders, 'backgroundColor');
}
- }
+
+ const colHeader = $(colHeaderSelector, this.wrapper);
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
+
+ $.style([colHeader, rowHeader], {
+ backgroundColor: '#f5f7fa' // light-bg
+ });
+
+ this.lastHeaders = [colHeader, rowHeader];
}
- this.currentCellEditor = null;
- }
+ selectAreaOnClusterChanged() {
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$selectionCursor);
+ const $cell = this.getCell$(colIndex, rowIndex);
- copyCellContents($cell1, $cell2) {
- if (!$cell2 && $cell1) {
- // copy only focusedCell
- const { colIndex, rowIndex } = $.data($cell1);
- const cell = this.getCell(colIndex, rowIndex);
- copyTextToClipboard(cell.content);
- return;
+ if (!$cell || $cell === this.$selectionCursor) return;
+
+ // selectArea needs $focusedCell
+ const fCell = $.data(this.$focusedCell);
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
+
+ this.selectArea($cell);
}
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return;
+ focusCellOnClusterChanged() {
+ if (!this.$focusedCell) return;
- const values = cells
- // get cell objects
- .map(index => this.getCell(...index))
- // convert to array of rows
- .reduce((acc, curr) => {
- const rowIndex = curr.rowIndex;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$focusedCell);
+ const $cell = this.getCell$(colIndex, rowIndex);
- acc[rowIndex] = acc[rowIndex] || [];
- acc[rowIndex].push(curr.content);
-
- return acc;
- }, [])
- // join values by tab
- .map(row => row.join('\t'))
- // join rows by newline
- .join('\n');
-
- copyTextToClipboard(values);
- }
-
- activateFilter(colIndex) {
- this.columnmanager.toggleFilter();
- this.columnmanager.focusFilter(colIndex);
-
- if (!this.columnmanager.isFilterShown) {
- // put focus back on cell
- this.$focusedCell.focus();
+ if (!$cell) return;
+ // this function is called after selectAreaOnClusterChanged,
+ // focusCell calls clearSelection which resets the area selection
+ // so a flag to skip it
+ this.focusCell($cell, {
+ skipClearSelection: 1
+ });
}
- }
- updateCell(colIndex, rowIndex, value) {
- const cell = this.datamanager.updateCell(colIndex, rowIndex, {
- content: value
- });
- this.refreshCell(cell);
- }
+ selectArea($selectionCursor) {
+ if (!this.$focusedCell) return;
- refreshCell(cell) {
- const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
- $cell.innerHTML = this.getCellContent(cell);
- }
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
+ // valid selection
+ this.$selectionCursor = $selectionCursor;
+ }
+ };
- isStandardCell(colIndex) {
- // Standard cells are in Sr. No and Checkbox column
- return colIndex < this.columnmanager.getFirstColumnIndex();
- }
+ _selectArea($cell1, $cell2) {
+ if ($cell1 === $cell2) return false;
- getCell$(colIndex, rowIndex) {
- return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
- }
+ const cells = this.getCellsInRange($cell1, $cell2);
+ if (!cells) return false;
- getAboveCell$($cell) {
- const { colIndex } = $.data($cell);
- const $aboveRow = $cell.parentElement.previousElementSibling;
+ this.clearSelection();
+ cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
+ return true;
+ }
- return $(`[data-col-index="${colIndex}"]`, $aboveRow);
- }
+ getCellsInRange($cell1, $cell2) {
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
- getBelowCell$($cell) {
- const { colIndex } = $.data($cell);
- const $belowRow = $cell.parentElement.nextElementSibling;
+ if (typeof $cell1 === 'number') {
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
+ } else
+ if (typeof $cell1 === 'object') {
- return $(`[data-col-index="${colIndex}"]`, $belowRow);
- }
+ if (!($cell1 && $cell2)) {
+ return false;
+ }
- getLeftCell$($cell) {
- return $cell.previousElementSibling;
- }
+ const cell1 = $.data($cell1);
+ const cell2 = $.data($cell2);
- getRightCell$($cell) {
- return $cell.nextElementSibling;
- }
+ colIndex1 = cell1.colIndex;
+ rowIndex1 = cell1.rowIndex;
+ colIndex2 = cell2.colIndex;
+ rowIndex2 = cell2.rowIndex;
+ }
- getLeftMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
- }
+ if (rowIndex1 > rowIndex2) {
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
+ }
- getRightMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
- }
+ if (colIndex1 > colIndex2) {
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
+ }
- getTopMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
- }
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
+ return false;
+ }
- getBottomMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
- }
+ let cells = [];
+ let colIndex = colIndex1;
+ let rowIndex = rowIndex1;
+ let rowIndices = [];
- getCell(colIndex, rowIndex) {
- return this.instance.datamanager.getCell(colIndex, rowIndex);
- }
+ while (rowIndex <= rowIndex2) {
+ rowIndices.push(rowIndex);
+ rowIndex++;
+ }
- getCellAttr($cell) {
- return this.instance.getCellAttr($cell);
- }
+ rowIndices.map(rowIndex => {
+ while (colIndex <= colIndex2) {
+ cells.push([colIndex, rowIndex]);
+ colIndex++;
+ }
+ colIndex = colIndex1;
+ });
- getRowHeight() {
- return $.style($('.data-table-row', this.bodyScrollable), 'height');
- }
+ return cells;
+ }
- scrollToCell($cell) {
- if ($.inViewport($cell, this.bodyScrollable)) return false;
+ clearSelection() {
+ $.each('.data-table-col.highlight', this.bodyScrollable)
+ .map(cell => cell.classList.remove('highlight'));
- const { rowIndex } = $.data($cell);
- this.rowmanager.scrollToRow(rowIndex);
- return false;
- }
+ this.$selectionCursor = null;
+ }
- getRowCountPerPage() {
- return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
- }
+ getSelectionCursor() {
+ return this.$selectionCursor || this.$focusedCell;
+ }
- getCellHTML(cell) {
- const { rowIndex, colIndex, isHeader, isFilter } = cell;
- const dataAttr = makeDataAttributeString({
- rowIndex,
- colIndex,
- isHeader,
- isFilter
- });
+ activateEditing($cell) {
+ this.focusCell($cell);
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
- return `
+ const col = this.columnmanager.getColumn(colIndex);
+ if (col && (col.editable === false || col.focusable === false)) {
+ return;
+ }
+
+ const cell = this.getCell(colIndex, rowIndex);
+ if (cell && cell.editable === false) {
+ return;
+ }
+
+ if (this.$editingCell) {
+ const {
+ _rowIndex,
+ _colIndex
+ } = $.data(this.$editingCell);
+
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
+ // editing the same cell
+ return;
+ }
+ }
+
+ this.$editingCell = $cell;
+ $cell.classList.add('editing');
+
+ const $editCell = $('.edit-cell', $cell);
+ $editCell.innerHTML = '';
+
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
+
+ if (editor) {
+ this.currentCellEditor = editor;
+ // initialize editing input with cell value
+ editor.initValue(cell.content, rowIndex, col);
+ }
+ }
+
+ deactivateEditing() {
+ // keep focus on the cell so that keyboard navigation works
+ if (this.$focusedCell) this.$focusedCell.focus();
+
+ if (!this.$editingCell) return;
+ this.$editingCell.classList.remove('editing');
+ this.$editingCell = null;
+ }
+
+ getEditor(colIndex, rowIndex, value, parent) {
+ // debugger;
+ const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
+ if (obj && obj.setValue) return obj;
+
+ // editing fallback
+ const $input = $.create('input', {
+ class: 'input-style',
+ type: 'text',
+ inside: parent
+ });
+
+ return {
+ initValue(value) {
+ $input.focus();
+ $input.value = value;
+ },
+ getValue() {
+ return $input.value;
+ },
+ setValue(value) {
+ $input.value = value;
+ }
+ };
+ }
+
+ submitEditing() {
+ if (!this.$editingCell) return;
+ const $cell = this.$editingCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+ const col = this.datamanager.getColumn(colIndex);
+
+ if ($cell) {
+ const editor = this.currentCellEditor;
+
+ if (editor) {
+ const value = editor.getValue();
+ const done = editor.setValue(value, rowIndex, col);
+ const oldValue = this.getCell(colIndex, rowIndex).content;
+
+ // update cell immediately
+ this.updateCell(colIndex, rowIndex, value);
+ $cell.focus();
+
+ if (done && done.then) {
+ // revert to oldValue if promise fails
+ done.catch((e) => {
+ console.log(e);
+ this.updateCell(colIndex, rowIndex, oldValue);
+ });
+ }
+ }
+ }
+
+ this.currentCellEditor = null;
+ }
+
+ copyCellContents($cell1, $cell2) {
+ if (!$cell2 && $cell1) {
+ // copy only focusedCell
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell1);
+ const cell = this.getCell(colIndex, rowIndex);
+ copyTextToClipboard(cell.content);
+ return;
+ }
+ const cells = this.getCellsInRange($cell1, $cell2);
+
+ if (!cells) return;
+
+ const values = cells
+ // get cell objects
+ .map(index => this.getCell(...index))
+ // convert to array of rows
+ .reduce((acc, curr) => {
+ const rowIndex = curr.rowIndex;
+
+ acc[rowIndex] = acc[rowIndex] || [];
+ acc[rowIndex].push(curr.content);
+
+ return acc;
+ }, [])
+ // join values by tab
+ .map(row => row.join('\t'))
+ // join rows by newline
+ .join('\n');
+
+ copyTextToClipboard(values);
+ }
+
+ activateFilter(colIndex) {
+ this.columnmanager.toggleFilter();
+ this.columnmanager.focusFilter(colIndex);
+
+ if (!this.columnmanager.isFilterShown) {
+ // put focus back on cell
+ this.$focusedCell.focus();
+ }
+ }
+
+ updateCell(colIndex, rowIndex, value) {
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
+ content: value
+ });
+ this.refreshCell(cell);
+ }
+
+ refreshCell(cell) {
+ const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
+ $cell.innerHTML = this.getCellContent(cell);
+ }
+
+ isStandardCell(colIndex) {
+ // Standard cells are in Sr. No and Checkbox column
+ return colIndex < this.columnmanager.getFirstColumnIndex();
+ }
+
+ getCell$(colIndex, rowIndex) {
+ return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
+ }
+
+ getAboveCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $aboveRow = $cell.parentElement.previousElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $aboveRow);
+ }
+
+ getBelowCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $belowRow = $cell.parentElement.nextElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $belowRow);
+ }
+
+ getLeftCell$($cell) {
+ return $cell.previousElementSibling;
+ }
+
+ getRightCell$($cell) {
+ return $cell.nextElementSibling;
+ }
+
+ getLeftMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
+ }
+
+ getRightMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
+ }
+
+ getTopMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
+ }
+
+ getBottomMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getCellAttr($cell) {
+ return this.instance.getCellAttr($cell);
+ }
+
+ getRowHeight() {
+ return $.style($('.data-table-row', this.bodyScrollable), 'height');
+ }
+
+ scrollToCell($cell) {
+ if ($.inViewport($cell, this.bodyScrollable)) return false;
+
+ const {
+ rowIndex
+ } = $.data($cell);
+ this.rowmanager.scrollToRow(rowIndex);
+ return false;
+ }
+
+ getRowCountPerPage() {
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
+ }
+
+ getCellHTML(cell) {
+ const {
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ } = cell;
+ const dataAttr = makeDataAttributeString({
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ });
+
+ return `
${this.getCellContent(cell)}
|
`;
- }
-
- getCellContent(cell) {
- const { isHeader } = cell;
-
- const editable = !isHeader && cell.editable !== false;
- const editCellHTML = editable ? this.getEditCellHTML() : '';
-
- const sortable = isHeader && cell.sortable !== false;
- const sortIndicator = sortable ? '' : '';
-
- const resizable = isHeader && cell.resizable !== false;
- const resizeColumn = resizable ? '' : '';
-
- const hasDropdown = isHeader && cell.dropdown !== false;
- const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
-
- let contentHTML;
- if (cell.isHeader || cell.isFilter || !cell.column.format) {
- contentHTML = cell.content;
- } else {
- contentHTML = cell.column.format(cell.content, cell);
}
- return `
+ getCellContent(cell) {
+ const {
+ isHeader
+ } = cell;
+
+ const editable = !isHeader && cell.editable !== false;
+ const editCellHTML = editable ? this.getEditCellHTML() : '';
+
+ const sortable = isHeader && cell.sortable !== false;
+ const sortIndicator = sortable ? '' : '';
+
+ const resizable = isHeader && cell.resizable !== false;
+ const resizeColumn = resizable ? '' : '';
+
+ const hasDropdown = isHeader && cell.dropdown !== false;
+ const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
+
+ let contentHTML;
+ if (cell.isHeader || cell.isFilter || !cell.column.format) {
+ contentHTML = cell.content;
+ } else {
+ contentHTML = cell.column.format(cell.content, cell);
+ }
+
+ return `
${(contentHTML)}
${sortIndicator}
@@ -2358,704 +2463,719 @@ class CellManager {
${editCellHTML}
`;
- }
+ }
- getEditCellHTML() {
- return `
+ getEditCellHTML() {
+ return `
`;
- }
+ }
- cellSelector(colIndex, rowIndex) {
- return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
- }
+ cellSelector(colIndex, rowIndex) {
+ return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
+ }
}
class RowManager {
- constructor(instance) {
- this.instance = instance;
- this.options = this.instance.options;
- this.wrapper = this.instance.wrapper;
- this.bodyScrollable = this.instance.bodyScrollable;
+ constructor(instance) {
+ this.instance = instance;
+ this.options = this.instance.options;
+ this.wrapper = this.instance.wrapper;
+ this.bodyScrollable = this.instance.bodyScrollable;
- this.bindEvents();
- this.refreshRows = promisify(this.refreshRows, this);
- }
-
- get datamanager() {
- return this.instance.datamanager;
- }
-
- get cellmanager() {
- return this.instance.cellmanager;
- }
-
- bindEvents() {
- this.bindCheckbox();
- }
-
- bindCheckbox() {
- if (!this.options.addCheckboxColumn) return;
-
- // map of checked rows
- this.checkMap = [];
-
- $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
- const $cell = $checkbox.closest('.data-table-col');
- const { rowIndex, isHeader } = $.data($cell);
- const checked = $checkbox.checked;
-
- if (isHeader) {
- this.checkAll(checked);
- } else {
- this.checkRow(rowIndex, checked);
- }
- });
- }
-
- refreshRows() {
- this.instance.renderBody();
- this.instance.setDimensions();
- }
-
- refreshRow(row, rowIndex) {
- const _row = this.datamanager.updateRow(row, rowIndex);
-
- _row.forEach(cell => {
- this.cellmanager.refreshCell(cell);
- });
- }
-
- getCheckedRows() {
- if (!this.checkMap) {
- return [];
+ this.bindEvents();
+ this.refreshRows = promisify(this.refreshRows, this);
}
- return this.checkMap
- .map((c, rowIndex) => {
- if (c) {
- return rowIndex;
+ get datamanager() {
+ return this.instance.datamanager;
+ }
+
+ get cellmanager() {
+ return this.instance.cellmanager;
+ }
+
+ bindEvents() {
+ this.bindCheckbox();
+ }
+
+ bindCheckbox() {
+ if (!this.options.addCheckboxColumn) return;
+
+ // map of checked rows
+ this.checkMap = [];
+
+ $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
+ const $cell = $checkbox.closest('.data-table-col');
+ const {
+ rowIndex,
+ isHeader
+ } = $.data($cell);
+ const checked = $checkbox.checked;
+
+ if (isHeader) {
+ this.checkAll(checked);
+ } else {
+ this.checkRow(rowIndex, checked);
+ }
+ });
+ }
+
+ refreshRows() {
+ this.instance.renderBody();
+ this.instance.setDimensions();
+ }
+
+ refreshRow(row, rowIndex) {
+ const _row = this.datamanager.updateRow(row, rowIndex);
+
+ _row.forEach(cell => {
+ this.cellmanager.refreshCell(cell);
+ });
+ }
+
+ getCheckedRows() {
+ if (!this.checkMap) {
+ return [];
}
- return null;
- })
- .filter(c => {
- return c !== null || c !== undefined;
- });
- }
- highlightCheckedRows() {
- this.getCheckedRows()
- .map(rowIndex => this.checkRow(rowIndex, true));
- }
-
- checkRow(rowIndex, toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- this.checkMap[rowIndex] = value;
- // set checkbox value explicitly
- $.each(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`, this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight row
- this.highlightRow(rowIndex, toggle);
- }
-
- checkAll(toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- if (toggle) {
- this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
- } else {
- this.checkMap = [];
- }
- // set checkbox value
- $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight all
- this.highlightAll(toggle);
- }
-
- highlightRow(rowIndex, toggle = true) {
- const $row = this.getRow$(rowIndex);
- if (!$row) return;
-
- if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
- $row.classList.add('row-unhighlight');
- return;
+ return this.checkMap
+ .map((c, rowIndex) => {
+ if (c) {
+ return rowIndex;
+ }
+ return null;
+ })
+ .filter(c => {
+ return c !== null || c !== undefined;
+ });
}
- if (toggle && $row.classList.contains('row-unhighlight')) {
- $row.classList.remove('row-unhighlight');
+ highlightCheckedRows() {
+ this.getCheckedRows()
+ .map(rowIndex => this.checkRow(rowIndex, true));
}
- this._highlightedRows = this._highlightedRows || {};
-
- if (toggle) {
- $row.classList.add('row-highlight');
- this._highlightedRows[rowIndex] = $row;
- } else {
- $row.classList.remove('row-highlight');
- delete this._highlightedRows[rowIndex];
- }
- }
-
- highlightAll(toggle = true) {
- if (toggle) {
- this.bodyScrollable.classList.add('row-highlight-all');
- } else {
- this.bodyScrollable.classList.remove('row-highlight-all');
- for (const rowIndex in this._highlightedRows) {
- const $row = this._highlightedRows[rowIndex];
- $row.classList.remove('row-highlight');
- }
- this._highlightedRows = {};
- }
- }
-
- getRow$(rowIndex) {
- return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- }
-
- getTotalRows() {
- return this.datamanager.getRowCount();
- }
-
- getFirstRowIndex() {
- return 0;
- }
-
- getLastRowIndex() {
- return this.datamanager.getRowCount() - 1;
- }
-
- scrollToRow(rowIndex) {
- rowIndex = +rowIndex;
- this._lastScrollTo = this._lastScrollTo || 0;
- const $row = this.getRow$(rowIndex);
- if ($.inViewport($row, this.bodyScrollable)) return;
-
- const { height } = $row.getBoundingClientRect();
- const { top, bottom } = this.bodyScrollable.getBoundingClientRect();
- const rowsInView = Math.floor((bottom - top) / height);
-
- let offset = 0;
- if (rowIndex > this._lastScrollTo) {
- offset = height * ((rowIndex + 1) - rowsInView);
- } else {
- offset = height * ((rowIndex + 1) - 1);
+ checkRow(rowIndex, toggle) {
+ const value = toggle ? 1 : 0;
+ const selector = rowIndex =>
+ `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
+ // update internal map
+ this.checkMap[rowIndex] = value;
+ // set checkbox value explicitly
+ $.each(selector(rowIndex), this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight row
+ this.highlightRow(rowIndex, toggle);
}
- this._lastScrollTo = rowIndex;
- $.scrollTop(this.bodyScrollable, offset);
- }
+ checkAll(toggle) {
+ const value = toggle ? 1 : 0;
- getRowHTML(row, props) {
- const dataAttr = makeDataAttributeString(props);
-
- if (props.isFilter) {
- row = row.map(cell => (Object.assign(cell, {
- content: this.getFilterInput({ colIndex: cell.colIndex }),
- isFilter: 1,
- isHeader: undefined,
- editable: false
- })));
+ // update internal map
+ if (toggle) {
+ this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
+ } else {
+ this.checkMap = [];
+ }
+ // set checkbox value
+ $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight all
+ this.highlightAll(toggle);
}
- return `
-
- ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
-
- `;
- }
+ highlightRow(rowIndex, toggle = true) {
+ const $row = this.getRow$(rowIndex);
+ if (!$row) return;
- getFilterInput(props) {
- const dataAttr = makeDataAttributeString(props);
- return ``;
- }
+ if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
+ $row.classList.add('row-unhighlight');
+ return;
+ }
+
+ if (toggle && $row.classList.contains('row-unhighlight')) {
+ $row.classList.remove('row-unhighlight');
+ }
+
+ this._highlightedRows = this._highlightedRows || {};
+
+ if (toggle) {
+ $row.classList.add('row-highlight');
+ this._highlightedRows[rowIndex] = $row;
+ } else {
+ $row.classList.remove('row-highlight');
+ delete this._highlightedRows[rowIndex];
+ }
+ }
+
+ highlightAll(toggle = true) {
+ if (toggle) {
+ this.bodyScrollable.classList.add('row-highlight-all');
+ } else {
+ this.bodyScrollable.classList.remove('row-highlight-all');
+ for (const rowIndex in this._highlightedRows) {
+ const $row = this._highlightedRows[rowIndex];
+ $row.classList.remove('row-highlight');
+ }
+ this._highlightedRows = {};
+ }
+ }
+
+ getRow$(rowIndex) {
+ return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ }
+
+ getTotalRows() {
+ return this.datamanager.getRowCount();
+ }
+
+ getFirstRowIndex() {
+ return 0;
+ }
+
+ getLastRowIndex() {
+ return this.datamanager.getRowCount() - 1;
+ }
+
+ scrollToRow(rowIndex) {
+ rowIndex = +rowIndex;
+ this._lastScrollTo = this._lastScrollTo || 0;
+ const $row = this.getRow$(rowIndex);
+ if ($.inViewport($row, this.bodyScrollable)) return;
+
+ const {
+ height
+ } = $row.getBoundingClientRect();
+ const {
+ top,
+ bottom
+ } = this.bodyScrollable.getBoundingClientRect();
+ const rowsInView = Math.floor((bottom - top) / height);
+
+ let offset = 0;
+ if (rowIndex > this._lastScrollTo) {
+ offset = height * ((rowIndex + 1) - rowsInView);
+ } else {
+ offset = height * ((rowIndex + 1) - 1);
+ }
+
+ this._lastScrollTo = rowIndex;
+ $.scrollTop(this.bodyScrollable, offset);
+ }
+
+ getRowHTML(row, props) {
+ const dataAttr = makeDataAttributeString(props);
+
+ if (props.isFilter) {
+ row = row.map(cell => (Object.assign(cell, {
+ content: this.getFilterInput({
+ colIndex: cell.colIndex
+ }),
+ isFilter: 1,
+ isHeader: undefined,
+ editable: false
+ })));
+ }
+
+ return `
+
+ ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
+
+ `;
+ }
+
+ getFilterInput(props) {
+ const dataAttr = makeDataAttributeString(props);
+ return ``;
+ }
}
class BodyRenderer {
- constructor(instance) {
- this.instance = instance;
- this.options = instance.options;
- this.datamanager = instance.datamanager;
- this.rowmanager = instance.rowmanager;
- this.cellmanager = instance.cellmanager;
- this.bodyScrollable = instance.bodyScrollable;
- this.log = instance.log;
- this.appendRemainingData = promisify(this.appendRemainingData, this);
- }
-
- render() {
- if (this.options.enableClusterize) {
- this.renderBodyWithClusterize();
- } else {
- this.renderBodyHTML();
- }
- }
-
- renderBodyHTML() {
- const rows = this.datamanager.getRows();
-
- this.bodyScrollable.innerHTML = `
-
- `;
- this.instance.setDimensions();
- this.restoreState();
- }
-
- renderBodyWithClusterize() {
- // first page
- const rows = this.datamanager.getRows(0, 20);
- const initialData = this.getDataForClusterize(rows);
-
- if (!this.clusterize) {
- // empty body
- this.bodyScrollable.innerHTML = `
-
- `;
-
- // first 20 rows will appended
- // rest of them in nextTick
- this.clusterize = new Clusterize({
- rows: initialData,
- scrollElem: this.bodyScrollable,
- contentElem: $('tbody', this.bodyScrollable),
- callbacks: {
- clusterChanged: () => {
- this.restoreState();
- }
- },
- /* eslint-disable */
- no_data_text: this.options.noDataMessage,
- no_data_class: 'empty-state'
- /* eslint-enable */
- });
-
- // setDimensions requires atleast 1 row to exist in dom
- this.instance.setDimensions();
- } else {
- this.clusterize.update(initialData);
+ constructor(instance) {
+ this.instance = instance;
+ this.options = instance.options;
+ this.datamanager = instance.datamanager;
+ this.rowmanager = instance.rowmanager;
+ this.cellmanager = instance.cellmanager;
+ this.bodyScrollable = instance.bodyScrollable;
+ this.log = instance.log;
+ this.appendRemainingData = promisify(this.appendRemainingData, this);
}
- this.appendRemainingData();
- }
+ render() {
+ if (this.options.enableClusterize) {
+ this.renderBodyWithClusterize();
+ } else {
+ this.renderBodyHTML();
+ }
+ }
- restoreState() {
- this.rowmanager.highlightCheckedRows();
- this.cellmanager.selectAreaOnClusterChanged();
- this.cellmanager.focusCellOnClusterChanged();
- }
+ renderBodyHTML() {
+ const rows = this.datamanager.getRows();
- appendRemainingData() {
- const rows = this.datamanager.getRows(20);
- const data = this.getDataForClusterize(rows);
- this.clusterize.append(data);
- }
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML(rows)}
+
+ `;
+ this.instance.setDimensions();
+ this.restoreState();
+ }
- getDataForClusterize(rows) {
- return rows.map((row) => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex }));
- }
-}
+ renderBodyWithClusterize() {
+ // first page
+ const rows = this.datamanager.getRows(0, 20);
+ const initialData = this.getDataForClusterize(rows);
-function getBodyHTML(rows) {
- return `
-
- ${rows.map(row => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
-
- `;
+ if (!this.clusterize) {
+ // empty body
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML([])}
+
+ `;
+
+ // first 20 rows will appended
+ // rest of them in nextTick
+ this.clusterize = new Clusterize({
+ rows: initialData,
+ scrollElem: this.bodyScrollable,
+ contentElem: $('tbody', this.bodyScrollable),
+ callbacks: {
+ clusterChanged: () => {
+ this.restoreState();
+ }
+ },
+ /* eslint-disable */
+ no_data_text: this.options.noDataMessage,
+ no_data_class: 'empty-state'
+ /* eslint-enable */
+ });
+
+ // setDimensions requires atleast 1 row to exist in dom
+ this.instance.setDimensions();
+ } else {
+ this.clusterize.update(initialData);
+ }
+
+ this.appendRemainingData();
+ }
+
+ restoreState() {
+ this.rowmanager.highlightCheckedRows();
+ this.cellmanager.selectAreaOnClusterChanged();
+ this.cellmanager.focusCellOnClusterChanged();
+ }
+
+ appendRemainingData() {
+ const rows = this.datamanager.getRows(20);
+ const data = this.getDataForClusterize(rows);
+ this.clusterize.append(data);
+ }
+
+ getDataForClusterize(rows) {
+ return rows.map((row) => this.rowmanager.getRowHTML(row, row.meta));
+ }
+
+ getBodyHTML(rows) {
+ return `
+
+ ${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
+
+ `;
+ }
}
class Style {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options', 'datamanager', 'columnmanager',
- 'header', 'bodyScrollable', 'getColumn'
- ]);
+ linkProperties(this, this.instance, [
+ 'options', 'datamanager', 'columnmanager',
+ 'header', 'bodyScrollable', 'getColumn'
+ ]);
- this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
- instance.datatableWrapper.classList.add(this.scopeClass);
+ this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
+ instance.datatableWrapper.classList.add(this.scopeClass);
- const styleEl = document.createElement('style');
- instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
- this.styleEl = styleEl;
- this.styleSheet = styleEl.sheet;
+ const styleEl = document.createElement('style');
+ instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
+ this.styleEl = styleEl;
+ this.styleSheet = styleEl.sheet;
- this.bindResizeWindow();
- }
+ this.bindResizeWindow();
+ }
+
+ bindResizeWindow() {
+ if (this.options.layout === 'fluid') {
+ $.on(window, 'resize', throttle$1(() => {
+ this.distributeRemainingWidth();
+ this.refreshColumnWidth();
+ this.setBodyStyle();
+ }, 300));
+ }
+ }
+
+ destroy() {
+ this.styleEl.remove();
+ }
+
+ setStyle(selector, styleMap, index = -1) {
+ const styles = Object.keys(styleMap)
+ .map(prop => {
+ if (!prop.includes('-')) {
+ prop = camelCaseToDash(prop);
+ }
+ return `${prop}:${styleMap[prop]};`;
+ })
+ .join('');
+ let prefixedSelector = selector
+ .split(',')
+ .map(r => `.${this.scopeClass} ${r}`)
+ .join(',');
+
+ let ruleString = `${prefixedSelector} { ${styles} }`;
+
+ let _index = this.styleSheet.cssRules.length;
+ if (index !== -1) {
+ this.styleSheet.deleteRule(index);
+ _index = index;
+ }
+
+ this.styleSheet.insertRule(ruleString, _index);
+ return _index;
+ }
+
+ setDimensions() {
+ this.setHeaderStyle();
+
+ this.setupMinWidth();
+ this.setupNaturalColumnWidth();
+ this.setupColumnWidth();
- bindResizeWindow() {
- if (this.options.layout === 'fluid') {
- $.on(window, 'resize', throttle$1(() => {
this.distributeRemainingWidth();
- this.refreshColumnWidth();
+ this.setColumnStyle();
+ this.setDefaultCellHeight();
this.setBodyStyle();
- }, 300));
- }
- }
-
- destroy() {
- this.styleEl.remove();
- }
-
- setStyle(rule, styleMap, index = -1) {
- const styles = Object.keys(styleMap)
- .map(prop => {
- if (!prop.includes('-')) {
- prop = camelCaseToDash(prop);
- }
- return `${prop}:${styleMap[prop]};`;
- })
- .join('');
- let ruleString = `.${this.scopeClass} ${rule} { ${styles} }`;
-
- let _index = this.styleSheet.cssRules.length;
- if (index !== -1) {
- this.styleSheet.deleteRule(index);
- _index = index;
}
- this.styleSheet.insertRule(ruleString, _index);
- return _index;
- }
+ setHeaderStyle() {
+ if (this.options.layout === 'fluid') {
+ // setting width as 0 will ensure that the
+ // header doesn't take the available space
+ $.style(this.header, {
+ width: 0
+ });
+ }
- setDimensions() {
- this.setHeaderStyle();
+ $.style(this.header, {
+ margin: 0
+ });
- this.setupMinWidth();
- this.setupNaturalColumnWidth();
- this.setupColumnWidth();
+ // don't show resize cursor on nonResizable columns
+ const nonResizableColumnsSelector = this.datamanager.getColumns()
+ .filter(col => col.resizable === false)
+ .map(col => col.colIndex)
+ .map(i => `.data-table-header [data-col-index="${i}"]`)
+ .join();
- this.distributeRemainingWidth();
- this.setColumnStyle();
- this.setDefaultCellHeight();
- this.setBodyStyle();
- }
-
- setHeaderStyle() {
- if (this.options.layout === 'fluid') {
- // setting width as 0 will ensure that the
- // header doesn't take the available space
- $.style(this.header, {
- width: 0
- });
+ this.setStyle(nonResizableColumnsSelector, {
+ cursor: 'pointer'
+ });
}
- $.style(this.header, {
- margin: 0
- });
+ setupMinWidth() {
+ $.each('.data-table-col[data-is-header]', this.header).map(col => {
+ const width = $.style($('.content', col), 'width');
+ const {
+ colIndex
+ } = $.data(col);
+ const column = this.getColumn(colIndex);
- // don't show resize cursor on nonResizable columns
- const nonResizableColumnsSelector = this.datamanager.getColumns()
- .filter(col => col.resizable === false)
- .map(col => col.colIndex)
- .map(i => `.data-table-header [data-col-index="${i}"]`)
- .join();
-
- this.setStyle(nonResizableColumnsSelector, {
- cursor: 'pointer'
- });
- }
-
- setupMinWidth() {
- $.each('.data-table-col[data-is-header]', this.header).map(col => {
- const width = $.style($('.content', col), 'width');
- const {
- colIndex
- } = $.data(col);
- const column = this.getColumn(colIndex);
-
- if (!column.minWidth) {
- // only set this once
- column.minWidth = width;
- }
- });
- }
-
- setupNaturalColumnWidth() {
- if (!$('.data-table-row')) return;
-
- // set initial width as naturally calculated by table's first row
- $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
- const {
- colIndex
- } = $.data($cell);
- const column = this.datamanager.getColumn(colIndex);
-
- let naturalWidth = $.style($('.content', $cell), 'width');
-
- if (column.id === '_rowIndex') {
- // width based on rowCount
- const rowCount = this.datamanager.getRowCount();
- const digits = (rowCount + '').length;
- if (digits > 2) {
- naturalWidth = naturalWidth + ((digits - 2) * 8);
- }
- }
-
- column.naturalWidth = naturalWidth;
- });
- }
-
- setupColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- if (!column.width) {
- column.width = column.naturalWidth;
- }
- if (column.width < column.minWidth) {
- column.width = column.minWidth;
- }
- });
- }
-
- distributeRemainingWidth() {
- if (this.options.layout !== 'fluid') return;
-
- const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
- const headerWidth = $.style(this.header, 'width');
- const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
- const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
-
- resizableColumns.map(col => {
- const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
- let finalWidth = Math.floor(width + deltaWidth) - 2;
-
- this.datamanager.updateColumn(col.colIndex, {
- width: finalWidth
- });
- });
- }
-
- setDefaultCellHeight() {
- if (this.__cellHeightSet) return;
- const height = this.options.cellHeight || $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
- if (height) {
- this.setCellHeight(height);
- this.__cellHeightSet = true;
+ if (!column.minWidth) {
+ // only set this once
+ column.minWidth = width;
+ }
+ });
}
- }
- setCellHeight(height) {
- this.setStyle('.data-table-col .content', {
- height: height + 'px'
- });
- this.setStyle('.data-table-col .edit-cell', {
- height: height + 'px'
- });
- }
+ setupNaturalColumnWidth() {
+ if (!$('.data-table-row')) return;
- setColumnStyle() {
- // align columns
- this.datamanager.getColumns()
- .map(column => {
- // alignment
- if (['left', 'center', 'right'].includes(column.align)) {
- this.setStyle(`[data-col-index="${column.colIndex}"]`, {
- 'text-align': column.align
- });
+ // set initial width as naturally calculated by table's first row
+ $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
+ const {
+ colIndex
+ } = $.data($cell);
+ const column = this.datamanager.getColumn(colIndex);
+
+ let naturalWidth = $.style($('.content', $cell), 'width');
+
+ if (column.id === '_rowIndex') {
+ // width based on rowCount
+ const rowCount = this.datamanager.getRowCount();
+ const digits = (rowCount + '').length;
+ if (digits > 1) {
+ naturalWidth = naturalWidth + ((digits - 1) * 8);
+ }
+ }
+
+ column.naturalWidth = naturalWidth;
+ });
+ }
+
+ setupColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ if (!column.width) {
+ column.width = column.naturalWidth;
+ }
+ if (column.width < column.minWidth) {
+ column.width = column.minWidth;
+ }
+ });
+ }
+
+ distributeRemainingWidth() {
+ if (this.options.layout !== 'fluid') return;
+
+ const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
+ const headerWidth = $.style(this.header, 'width');
+ const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
+ const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
+
+ resizableColumns.map(col => {
+ const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
+ let finalWidth = Math.floor(width + deltaWidth) - 2;
+
+ this.datamanager.updateColumn(col.colIndex, {
+ width: finalWidth
+ });
+ });
+ }
+
+ setDefaultCellHeight() {
+ if (this.__cellHeightSet) return;
+ const height = this.options.cellHeight ||
+ $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
+ if (height) {
+ this.setCellHeight(height);
+ this.__cellHeightSet = true;
}
- // width
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- this.setBodyStyle();
- }
+ }
- refreshColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- }
+ setCellHeight(height) {
+ this.setStyle('.data-table-col .content', {
+ height: height + 'px'
+ });
+ this.setStyle('.data-table-col .edit-cell', {
+ height: height + 'px'
+ });
+ }
- setBodyStyle() {
- const width = $.style(this.header, 'width');
+ setColumnStyle() {
+ // align columns
+ this.datamanager.getColumns()
+ .map(column => {
+ // alignment
+ if (['left', 'center', 'right'].includes(column.align)) {
+ this.setStyle(`[data-col-index="${column.colIndex}"]`, {
+ 'text-align': column.align
+ });
+ }
+ // width
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ this.setBodyStyle();
+ }
- $.style(this.bodyScrollable, {
- width: width + 'px'
- });
+ refreshColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ }
- $.style(this.bodyScrollable, {
- marginTop: $.style(this.header, 'height') + 'px'
- });
+ setBodyStyle() {
+ const width = $.style(this.header, 'width');
- $.style($('table', this.bodyScrollable), {
- margin: 0
- });
- }
+ $.style(this.bodyScrollable, {
+ width: width + 'px'
+ });
- getColumnHeaderElement(colIndex) {
- colIndex = +colIndex;
- if (colIndex < 0) return null;
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.style(this.bodyScrollable, {
+ marginTop: $.style(this.header, 'height') + 'px'
+ });
+
+ $.style($('table', this.bodyScrollable), {
+ margin: 0
+ });
+ }
+
+ getColumnHeaderElement(colIndex) {
+ colIndex = +colIndex;
+ if (colIndex < 0) return null;
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
}
const KEYCODES = {
- 13: 'enter',
- 91: 'meta',
- 16: 'shift',
- 17: 'ctrl',
- 18: 'alt',
- 37: 'left',
- 38: 'up',
- 39: 'right',
- 40: 'down',
- 9: 'tab',
- 27: 'esc',
- 67: 'c',
- 70: 'f'
+ 13: 'enter',
+ 91: 'meta',
+ 16: 'shift',
+ 17: 'ctrl',
+ 18: 'alt',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 9: 'tab',
+ 27: 'esc',
+ 67: 'c',
+ 70: 'f'
};
class Keyboard {
- constructor(element) {
- this.listeners = {};
- $.on(element, 'keydown', this.handler.bind(this));
- }
-
- handler(e) {
- let key = KEYCODES[e.keyCode];
-
- if (e.shiftKey && key !== 'shift') {
- key = 'shift+' + key;
+ constructor(element) {
+ this.listeners = {};
+ $.on(element, 'keydown', this.handler.bind(this));
}
- if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
- key = 'ctrl+' + key;
- }
+ handler(e) {
+ let key = KEYCODES[e.keyCode];
- const listeners = this.listeners[key];
-
- if (listeners && listeners.length > 0) {
- for (let listener of listeners) {
- const preventBubbling = listener(e);
- if (preventBubbling === undefined || preventBubbling === true) {
- e.preventDefault();
+ if (e.shiftKey && key !== 'shift') {
+ key = 'shift+' + key;
+ }
+
+ if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
+ key = 'ctrl+' + key;
+ }
+
+ const listeners = this.listeners[key];
+
+ if (listeners && listeners.length > 0) {
+ for (let listener of listeners) {
+ const preventBubbling = listener(e);
+ if (preventBubbling === undefined || preventBubbling === true) {
+ e.preventDefault();
+ }
+ }
}
- }
}
- }
- on(key, listener) {
- const keys = key.split(',').map(k => k.trim());
+ on(key, listener) {
+ const keys = key.split(',').map(k => k.trim());
- keys.map(key => {
- this.listeners[key] = this.listeners[key] || [];
- this.listeners[key].push(listener);
- });
- }
+ keys.map(key => {
+ this.listeners[key] = this.listeners[key] || [];
+ this.listeners[key].push(listener);
+ });
+ }
}
var DEFAULT_OPTIONS = {
- columns: [],
- data: [],
- dropdownButton: '▼',
- headerDropdown: [
- {
- label: 'Sort Ascending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'asc');
- }
+ columns: [],
+ data: [],
+ dropdownButton: '▼',
+ headerDropdown: [
+ {
+ label: 'Sort Ascending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'asc');
+ }
+ },
+ {
+ label: 'Sort Descending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'desc');
+ }
+ },
+ {
+ label: 'Reset sorting',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'none');
+ }
+ },
+ {
+ label: 'Remove column',
+ action: function (column) {
+ this.removeColumn(column.colIndex);
+ }
+ }
+ ],
+ events: {
+ onRemoveColumn(column) {},
+ onSwitchColumn(column1, column2) {},
+ onSortColumn(column) {}
},
- {
- label: 'Sort Descending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'desc');
- }
+ sortIndicator: {
+ asc: '↑',
+ desc: '↓',
+ none: ''
},
- {
- label: 'Reset sorting',
- action: function (column) {
- this.sortColumn(column.colIndex, 'none');
- }
- },
- {
- label: 'Remove column',
- action: function (column) {
- this.removeColumn(column.colIndex);
- }
- }
- ],
- events: {
- onRemoveColumn(column) {},
- onSwitchColumn(column1, column2) {},
- onSortColumn(column) {}
- },
- sortIndicator: {
- asc: '↑',
- desc: '↓',
- none: ''
- },
- freezeMessage: '',
- getEditor: () => {},
- addSerialNoColumn: true,
- addCheckboxColumn: false,
- enableClusterize: true,
- enableLogs: false,
- layout: 'fixed', // fixed, fluid
- noDataMessage: 'No Data',
- cellHeight: null,
- enableInlineFilters: false
+ freezeMessage: '',
+ getEditor: () => {},
+ addSerialNoColumn: true,
+ addCheckboxColumn: false,
+ enableClusterize: true,
+ enableLogs: false,
+ layout: 'fixed', // fixed, fluid
+ noDataMessage: 'No Data',
+ cellHeight: null,
+ enableInlineFilters: false
};
class DataTable {
- constructor(wrapper, options) {
- DataTable.instances++;
+ constructor(wrapper, options) {
+ DataTable.instances++;
- if (typeof wrapper === 'string') {
- // css selector
- wrapper = document.querySelector(wrapper);
- }
- this.wrapper = wrapper;
- if (!(this.wrapper instanceof HTMLElement)) {
- throw new Error('Invalid argument given for `wrapper`');
+ if (typeof wrapper === 'string') {
+ // css selector
+ wrapper = document.querySelector(wrapper);
+ }
+ this.wrapper = wrapper;
+ if (!(this.wrapper instanceof HTMLElement)) {
+ throw new Error('Invalid argument given for `wrapper`');
+ }
+
+ this.options = Object.assign({}, DEFAULT_OPTIONS, options);
+ this.options.headerDropdown =
+ DEFAULT_OPTIONS.headerDropdown
+ .concat(options.headerDropdown || []);
+ // custom user events
+ this.events = Object.assign({}, DEFAULT_OPTIONS.events, options.events || {});
+ this.fireEvent = this.fireEvent.bind(this);
+
+ this.prepare();
+
+ this.style = new Style(this);
+ this.keyboard = new Keyboard(this.wrapper);
+ this.datamanager = new DataManager(this.options);
+ this.rowmanager = new RowManager(this);
+ this.columnmanager = new ColumnManager(this);
+ this.cellmanager = new CellManager(this);
+ this.bodyRenderer = new BodyRenderer(this);
+
+ if (this.options.data) {
+ this.refresh();
+ }
}
- this.options = Object.assign({}, DEFAULT_OPTIONS, options);
- this.options.headerDropdown =
- DEFAULT_OPTIONS.headerDropdown
- .concat(options.headerDropdown || []);
- // custom user events
- this.events = Object.assign(
- {}, DEFAULT_OPTIONS.events, options.events || {}
- );
- this.fireEvent = this.fireEvent.bind(this);
-
- this.prepare();
-
- this.style = new Style(this);
- this.keyboard = new Keyboard(this.wrapper);
- this.datamanager = new DataManager(this.options);
- this.rowmanager = new RowManager(this);
- this.columnmanager = new ColumnManager(this);
- this.cellmanager = new CellManager(this);
- this.bodyRenderer = new BodyRenderer(this);
-
- if (this.options.data) {
- this.refresh();
+ prepare() {
+ this.prepareDom();
+ this.unfreeze();
}
- }
- prepare() {
- this.prepareDom();
- this.unfreeze();
- }
-
- prepareDom() {
- this.wrapper.innerHTML = `
+ prepareDom() {
+ this.wrapper.innerHTML = `
@@ -3069,110 +3189,110 @@ class DataTable {
`;
- this.datatableWrapper = $('.data-table', this.wrapper);
- this.header = $('.data-table-header', this.wrapper);
- this.bodyScrollable = $('.body-scrollable', this.wrapper);
- this.freezeContainer = $('.freeze-container', this.wrapper);
- }
-
- refresh(data) {
- this.datamanager.init(data);
- this.render();
- this.setDimensions();
- }
-
- destroy() {
- this.wrapper.innerHTML = '';
- this.style.destroy();
- }
-
- appendRows(rows) {
- this.datamanager.appendRows(rows);
- this.rowmanager.refreshRows();
- }
-
- refreshRow(row, rowIndex) {
- this.rowmanager.refreshRow(row, rowIndex);
- }
-
- render() {
- this.renderHeader();
- this.renderBody();
- }
-
- renderHeader() {
- this.columnmanager.renderHeader();
- }
-
- renderBody() {
- this.bodyRenderer.render();
- }
-
- setDimensions() {
- this.style.setDimensions();
- }
-
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
-
- getColumns() {
- return this.datamanager.getColumns();
- }
-
- getRows() {
- return this.datamanager.getRows();
- }
-
- getCell(colIndex, rowIndex) {
- return this.datamanager.getCell(colIndex, rowIndex);
- }
-
- getColumnHeaderElement(colIndex) {
- return this.columnmanager.getColumnHeaderElement(colIndex);
- }
-
- getViewportHeight() {
- if (!this.viewportHeight) {
- this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ this.datatableWrapper = $('.data-table', this.wrapper);
+ this.header = $('.data-table-header', this.wrapper);
+ this.bodyScrollable = $('.body-scrollable', this.wrapper);
+ this.freezeContainer = $('.freeze-container', this.wrapper);
}
- return this.viewportHeight;
- }
-
- sortColumn(colIndex, sortOrder) {
- this.columnmanager.sortColumn(colIndex, sortOrder);
- }
-
- removeColumn(colIndex) {
- this.columnmanager.removeColumn(colIndex);
- }
-
- scrollToLastColumn() {
- this.datatableWrapper.scrollLeft = 9999;
- }
-
- freeze() {
- $.style(this.freezeContainer, {
- display: ''
- });
- }
-
- unfreeze() {
- $.style(this.freezeContainer, {
- display: 'none'
- });
- }
-
- fireEvent(eventName, ...args) {
- this.events[eventName].apply(this, args);
- }
-
- log() {
- if (this.options.enableLogs) {
- console.log.apply(console, arguments);
+ refresh(data) {
+ this.datamanager.init(data);
+ this.render();
+ this.setDimensions();
+ }
+
+ destroy() {
+ this.wrapper.innerHTML = '';
+ this.style.destroy();
+ }
+
+ appendRows(rows) {
+ this.datamanager.appendRows(rows);
+ this.rowmanager.refreshRows();
+ }
+
+ refreshRow(row, rowIndex) {
+ this.rowmanager.refreshRow(row, rowIndex);
+ }
+
+ render() {
+ this.renderHeader();
+ this.renderBody();
+ }
+
+ renderHeader() {
+ this.columnmanager.renderHeader();
+ }
+
+ renderBody() {
+ this.bodyRenderer.render();
+ }
+
+ setDimensions() {
+ this.style.setDimensions();
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ getRows() {
+ return this.datamanager.getRows();
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getColumnHeaderElement(colIndex) {
+ return this.columnmanager.getColumnHeaderElement(colIndex);
+ }
+
+ getViewportHeight() {
+ if (!this.viewportHeight) {
+ this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ }
+
+ return this.viewportHeight;
+ }
+
+ sortColumn(colIndex, sortOrder) {
+ this.columnmanager.sortColumn(colIndex, sortOrder);
+ }
+
+ removeColumn(colIndex) {
+ this.columnmanager.removeColumn(colIndex);
+ }
+
+ scrollToLastColumn() {
+ this.datatableWrapper.scrollLeft = 9999;
+ }
+
+ freeze() {
+ $.style(this.freezeContainer, {
+ display: ''
+ });
+ }
+
+ unfreeze() {
+ $.style(this.freezeContainer, {
+ display: 'none'
+ });
+ }
+
+ fireEvent(eventName, ...args) {
+ this.events[eventName].apply(this, args);
+ }
+
+ log() {
+ if (this.options.enableLogs) {
+ console.log.apply(console, arguments);
+ }
}
- }
}
DataTable.instances = 0;
diff --git a/dist/frappe-datatable.css b/dist/frappe-datatable.css
index 90ab6d1..30dd637 100644
--- a/dist/frappe-datatable.css
+++ b/dist/frappe-datatable.css
@@ -1,3 +1,4 @@
+/* This file is processed by postcss */
/* variables */
.data-table {
diff --git a/dist/frappe-datatable.js b/dist/frappe-datatable.js
index 3bdc2fe..fab99f7 100644
--- a/dist/frappe-datatable.js
+++ b/dist/frappe-datatable.js
@@ -5,175 +5,185 @@ Sortable = Sortable && Sortable.hasOwnProperty('default') ? Sortable['default']
Clusterize = Clusterize && Clusterize.hasOwnProperty('default') ? Clusterize['default'] : Clusterize;
function $(expr, con) {
- return typeof expr === 'string' ?
- (con || document).querySelector(expr) :
- expr || null;
+ return typeof expr === 'string' ?
+ (con || document).querySelector(expr) :
+ expr || null;
}
$.each = (expr, con) => {
- return typeof expr === 'string' ?
- Array.from((con || document).querySelectorAll(expr)) :
- expr || null;
+ return typeof expr === 'string' ?
+ Array.from((con || document).querySelectorAll(expr)) :
+ expr || null;
};
$.create = (tag, o) => {
- let element = document.createElement(tag);
+ let element = document.createElement(tag);
- for (let i in o) {
- let val = o[i];
+ for (let i in o) {
+ let val = o[i];
- if (i === 'inside') {
- $(val).appendChild(element);
- } else
- if (i === 'around') {
- let ref = $(val);
- ref.parentNode.insertBefore(element, ref);
- element.appendChild(ref);
- } else
- if (i === 'styles') {
- if (typeof val === 'object') {
- Object.keys(val).map(prop => {
- element.style[prop] = val[prop];
- });
- }
+ if (i === 'inside') {
+ $(val).appendChild(element);
} else
- if (i in element) {
+ if (i === 'around') {
+ let ref = $(val);
+ ref.parentNode.insertBefore(element, ref);
+ element.appendChild(ref);
+ } else
+ if (i === 'styles') {
+ if (typeof val === 'object') {
+ Object.keys(val).map(prop => {
+ element.style[prop] = val[prop];
+ });
+ }
+ } else
+ if (i in element) {
element[i] = val;
- } else {
+ } else {
element.setAttribute(i, val);
- }
- }
+ }
+ }
- return element;
+ return element;
};
$.on = (element, event, selector, callback) => {
- if (!callback) {
- callback = selector;
- $.bind(element, event, callback);
- } else {
- $.delegate(element, event, selector, callback);
- }
+ if (!callback) {
+ callback = selector;
+ $.bind(element, event, callback);
+ } else {
+ $.delegate(element, event, selector, callback);
+ }
};
$.off = (element, event, handler) => {
- element.removeEventListener(event, handler);
+ element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
- event.split(/\s+/).forEach(function (event) {
- element.addEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.addEventListener(event, callback);
+ });
};
$.delegate = (element, event, selector, callback) => {
- element.addEventListener(event, function (e) {
- const delegatedTarget = e.target.closest(selector);
- if (delegatedTarget) {
- e.delegatedTarget = delegatedTarget;
- callback.call(this, e, delegatedTarget);
- }
- });
+ element.addEventListener(event, function (e) {
+ const delegatedTarget = e.target.closest(selector);
+ if (delegatedTarget) {
+ e.delegatedTarget = delegatedTarget;
+ callback.call(this, e, delegatedTarget);
+ }
+ });
};
$.unbind = (element, o) => {
- if (element) {
- for (let event in o) {
- let callback = o[event];
+ if (element) {
+ for (let event in o) {
+ let callback = o[event];
- event.split(/\s+/).forEach(function (event) {
- element.removeEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.removeEventListener(event, callback);
+ });
+ }
}
- }
};
$.fire = (target, type, properties) => {
- let evt = document.createEvent('HTMLEvents');
+ let evt = document.createEvent('HTMLEvents');
- evt.initEvent(type, true, true);
+ evt.initEvent(type, true, true);
- for (let j in properties) {
- evt[j] = properties[j];
- }
+ for (let j in properties) {
+ evt[j] = properties[j];
+ }
- return target.dispatchEvent(evt);
+ return target.dispatchEvent(evt);
};
$.data = (element, attrs) => { // eslint-disable-line
- if (!attrs) {
- return element.dataset;
- }
+ if (!attrs) {
+ return element.dataset;
+ }
- for (const attr in attrs) {
- element.dataset[attr] = attrs[attr];
- }
+ for (const attr in attrs) {
+ element.dataset[attr] = attrs[attr];
+ }
};
$.style = (elements, styleMap) => { // eslint-disable-line
- if (typeof styleMap === 'string') {
- return $.getStyle(elements, styleMap);
- }
-
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- elements.map(element => {
- for (const prop in styleMap) {
- element.style[prop] = styleMap[prop];
+ if (typeof styleMap === 'string') {
+ return $.getStyle(elements, styleMap);
}
- });
+
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ elements.map(element => {
+ for (const prop in styleMap) {
+ element.style[prop] = styleMap[prop];
+ }
+ });
};
$.removeStyle = (elements, styleProps) => {
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- if (!Array.isArray(styleProps)) {
- styleProps = [styleProps];
- }
-
- elements.map(element => {
- for (const prop of styleProps) {
- element.style[prop] = '';
+ if (!Array.isArray(elements)) {
+ elements = [elements];
}
- });
+
+ if (!Array.isArray(styleProps)) {
+ styleProps = [styleProps];
+ }
+
+ elements.map(element => {
+ for (const prop of styleProps) {
+ element.style[prop] = '';
+ }
+ });
};
$.getStyle = (element, prop) => {
- let val = getComputedStyle(element)[prop];
+ let val = getComputedStyle(element)[prop];
- if (['width', 'height'].includes(prop)) {
- val = parseFloat(val);
- }
+ if (['width', 'height'].includes(prop)) {
+ val = parseFloat(val);
+ }
- return val;
+ return val;
};
$.closest = (selector, element) => {
- if (!element) return null;
+ if (!element) return null;
- if (element.matches(selector)) {
- return element;
- }
+ if (element.matches(selector)) {
+ return element;
+ }
- return $.closest(selector, element.parentNode);
+ return $.closest(selector, element.parentNode);
};
$.inViewport = (el, parentEl) => {
- const { top, left, bottom, right } = el.getBoundingClientRect();
- const { top: pTop, left: pLeft, bottom: pBottom, right: pRight } = parentEl.getBoundingClientRect();
+ const {
+ top,
+ left,
+ bottom,
+ right
+ } = el.getBoundingClientRect();
+ const {
+ top: pTop,
+ left: pLeft,
+ bottom: pBottom,
+ right: pRight
+ } = parentEl.getBoundingClientRect();
- return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
+ return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
};
$.scrollTop = function scrollTop(element, pixels) {
- requestAnimationFrame(() => {
- element.scrollTop = pixels;
- });
+ requestAnimationFrame(() => {
+ element.scrollTop = pixels;
+ });
};
/**
@@ -719,26 +729,26 @@ function throttle(func, wait, options) {
var throttle_1 = throttle;
function camelCaseToDash(str) {
- return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
+ return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
}
function makeDataAttributeString(props) {
- const keys = Object.keys(props);
+ const keys = Object.keys(props);
- return keys
- .map((key) => {
- const _key = camelCaseToDash(key);
- const val = props[key];
+ return keys
+ .map((key) => {
+ const _key = camelCaseToDash(key);
+ const val = props[key];
- if (val === undefined) return '';
- return `data-${_key}="${val}" `;
- })
- .join('')
- .trim();
+ if (val === undefined) return '';
+ return `data-${_key}="${val}" `;
+ })
+ .join('')
+ .trim();
}
function getDefault(a, b) {
- return a !== undefined ? a : b;
+ return a !== undefined ? a : b;
}
@@ -752,62 +762,62 @@ function getDefault(a, b) {
function copyTextToClipboard(text) {
- // https://stackoverflow.com/a/30810322/5353542
- var textArea = document.createElement('textarea');
+ // https://stackoverflow.com/a/30810322/5353542
+ var textArea = document.createElement('textarea');
- //
- // *** This styling is an extra step which is likely not required. ***
- //
- // Why is it here? To ensure:
- // 1. the element is able to have focus and selection.
- // 2. if element was to flash render it has minimal visual impact.
- // 3. less flakyness with selection and copying which **might** occur if
- // the textarea element is not visible.
- //
- // The likelihood is the element won't even render, not even a flash,
- // so some of these are just precautions. However in IE the element
- // is visible whilst the popup box asking the user for permission for
- // the web page to copy to the clipboard.
- //
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
- // Place in top-left corner of screen regardless of scroll position.
- textArea.style.position = 'fixed';
- textArea.style.top = 0;
- textArea.style.left = 0;
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
- // Ensure it has a small width and height. Setting to 1px / 1em
- // doesn't work as this gives a negative w/h on some browsers.
- textArea.style.width = '2em';
- textArea.style.height = '2em';
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
- // We don't need padding, reducing the size if it does flash render.
- textArea.style.padding = 0;
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
- // Clean up any borders.
- textArea.style.border = 'none';
- textArea.style.outline = 'none';
- textArea.style.boxShadow = 'none';
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
- // Avoid flash of white box if rendered for any reason.
- textArea.style.background = 'transparent';
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
- textArea.value = text;
+ textArea.value = text;
- document.body.appendChild(textArea);
+ document.body.appendChild(textArea);
- textArea.select();
+ textArea.select();
- try {
- document.execCommand('copy');
- } catch (err) {
- console.log('Oops, unable to copy');
- }
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
- document.body.removeChild(textArea);
+ document.body.removeChild(textArea);
}
function isNumeric(val) {
- return !isNaN(val);
+ return !isNaN(val);
}
let throttle$1 = throttle_1;
@@ -815,936 +825,985 @@ let throttle$1 = throttle_1;
let debounce$2 = debounce_1;
function promisify(fn, context = null) {
- return (...args) => {
- return new Promise(resolve => {
- setTimeout(() => {
- const out = fn.apply(context, args);
- resolve(out);
- }, 0);
- });
- };
+ return (...args) => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ const out = fn.apply(context, args);
+ resolve(out);
+ }, 0);
+ });
+ };
}
function linkProperties(target, source, properties) {
- const props = properties.reduce((acc, prop) => {
- acc[prop] = {
- get() {
- return source[prop];
- }
- };
- return acc;
- }, {});
- Object.defineProperties(target, props);
+ const props = properties.reduce((acc, prop) => {
+ acc[prop] = {
+ get() {
+ return source[prop];
+ }
+ };
+ return acc;
+ }, {});
+ Object.defineProperties(target, props);
}
class DataManager {
- constructor(options) {
- this.options = options;
- this.sortRows = promisify(this.sortRows, this);
- this.switchColumn = promisify(this.switchColumn, this);
- this.removeColumn = promisify(this.removeColumn, this);
- this.filterRows = promisify(this.filterRows, this);
- }
-
- init(data) {
- if (!data) {
- data = this.options.data;
+ constructor(options) {
+ this.options = options;
+ this.sortRows = promisify(this.sortRows, this);
+ this.switchColumn = promisify(this.switchColumn, this);
+ this.removeColumn = promisify(this.removeColumn, this);
+ this.filterRows = promisify(this.filterRows, this);
}
- this.data = data;
-
- this.rowCount = 0;
- this.columns = [];
- this.rows = [];
-
- this.prepareColumns();
- this.prepareRows();
-
- this.prepareNumericColumns();
- }
-
- // computed property
- get currentSort() {
- const col = this.columns.find(col => col.sortOrder !== 'none');
- return col || {
- colIndex: -1,
- sortOrder: 'none'
- };
- }
-
- prepareColumns() {
- this.columns = [];
- this.validateColumns();
- this.prepareDefaultColumns();
- this.prepareHeader();
- }
-
- prepareDefaultColumns() {
- if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
- const cell = {
- id: '_checkbox',
- content: this.getCheckboxHTML(),
- editable: false,
- resizable: false,
- sortable: false,
- focusable: false,
- dropdown: false,
- width: 25
- };
- this.columns.push(cell);
- }
-
- if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
- let cell = {
- id: '_rowIndex',
- content: '',
- align: 'center',
- editable: false,
- resizable: false,
- focusable: false,
- dropdown: false
- };
-
- this.columns.push(cell);
- }
- }
-
- prepareRow(row, i) {
- const baseRowCell = {
- rowIndex: i
- };
-
- return row
- .map((cell, i) => this.prepareCell(cell, i))
- .map(cell => Object.assign({}, baseRowCell, cell));
- }
-
- prepareHeader() {
- let columns = this.columns.concat(this.options.columns);
- const baseCell = {
- isHeader: 1,
- editable: true,
- sortable: true,
- resizable: true,
- focusable: true,
- dropdown: true,
- width: null,
- format: (value) => {
- if (value === null || value === undefined) {
- return '';
- }
- return value + '';
- }
- };
-
- this.columns = columns
- .map((cell, i) => this.prepareCell(cell, i))
- .map(col => Object.assign({}, baseCell, col))
- .map(col => {
- col.id = col.id || col.content;
- return col;
- });
- }
-
- prepareCell(content, i) {
- const cell = {
- content: '',
- align: 'left',
- sortOrder: 'none',
- colIndex: i,
- column: this.columns[i]
- };
-
- if (content !== null && typeof content === 'object') {
- // passed as column/header
- Object.assign(cell, content);
- } else {
- cell.content = content;
- }
-
- return cell;
- }
-
- prepareNumericColumns() {
- const row0 = this.getRow(0);
- if (!row0) return;
- this.columns = this.columns.map((column, i) => {
-
- const cellValue = row0[i].content;
- if (!column.align && cellValue && isNumeric(cellValue)) {
- column.align = 'right';
- }
-
- return column;
- });
- }
-
- prepareRows() {
- this.validateData(this.data);
-
- this.rows = this.data.map((d, i) => {
- const index = this._getNextRowCount();
-
- let row = [];
-
- if (Array.isArray(d)) {
- // row is an array
- if (this.options.addCheckboxColumn) {
- row.push(this.getCheckboxHTML());
- }
- if (this.options.addSerialNoColumn) {
- row.push((index + 1) + '');
- }
- row = row.concat(d);
-
- while (row.length < this.columns.length) {
- row.push('');
+ init(data) {
+ if (!data) {
+ data = this.options.data;
}
- } else {
- // row is a dict
- for (let col of this.columns) {
- if (col.id === '_checkbox') {
- row.push(this.getCheckboxHTML());
- } else if (col.id === '_rowIndex') {
- row.push((index + 1) + '');
- } else {
- row.push(d[col.id]);
- }
+ this.data = data;
+
+ this.rowCount = 0;
+ this.columns = [];
+ this.rows = [];
+
+ this.prepareColumns();
+ this.prepareRows();
+
+ this.prepareNumericColumns();
+ }
+
+ // computed property
+ get currentSort() {
+ const col = this.columns.find(col => col.sortOrder !== 'none');
+ return col || {
+ colIndex: -1,
+ sortOrder: 'none'
+ };
+ }
+
+ prepareColumns() {
+ this.columns = [];
+ this.validateColumns();
+ this.prepareDefaultColumns();
+ this.prepareHeader();
+ }
+
+ prepareDefaultColumns() {
+ if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
+ const cell = {
+ id: '_checkbox',
+ content: this.getCheckboxHTML(),
+ editable: false,
+ resizable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 25
+ };
+ this.columns.push(cell);
}
- }
- return this.prepareRow(row, index);
- });
- }
+ if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
+ let cell = {
+ id: '_rowIndex',
+ content: '',
+ align: 'center',
+ editable: false,
+ resizable: false,
+ focusable: false,
+ dropdown: false
+ };
- validateColumns() {
- const columns = this.options.columns;
- if (!Array.isArray(columns)) {
- throw new DataError('`columns` must be an array');
+ this.columns.push(cell);
+ }
}
- columns.forEach((column, i) => {
- if (typeof column !== 'string' && typeof column !== 'object') {
- throw new DataError(`column "${i}" must be a string or an object`);
- }
- });
- }
+ prepareHeader() {
+ let columns = this.columns.concat(this.options.columns);
+ const baseCell = {
+ isHeader: 1,
+ editable: true,
+ sortable: true,
+ resizable: true,
+ focusable: true,
+ dropdown: true,
+ width: null,
+ format: (value) => {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return value + '';
+ }
+ };
- validateData(data) {
- if (Array.isArray(data) &&
- (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
- return true;
+ this.columns = columns
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(col => Object.assign({}, baseCell, col))
+ .map(col => {
+ col.id = col.id || col.content;
+ return col;
+ });
}
- throw new DataError('`data` must be an array of arrays or objects');
- }
- appendRows(rows) {
- this.validateData(rows);
+ prepareCell(content, i) {
+ const cell = {
+ content: '',
+ align: 'left',
+ sortOrder: 'none',
+ colIndex: i,
+ column: this.columns[i]
+ };
- this.rows = this.rows.concat(this.prepareRows(rows));
- }
-
- sortRows(colIndex, sortOrder = 'none') {
- colIndex = +colIndex;
-
- // reset sortOrder and update for colIndex
- this.getColumns()
- .map(col => {
- if (col.colIndex === colIndex) {
- col.sortOrder = sortOrder;
+ if (content !== null && typeof content === 'object') {
+ // passed as column/header
+ Object.assign(cell, content);
} else {
- col.sortOrder = 'none';
+ cell.content = content;
}
- });
- this._sortRows(colIndex, sortOrder);
- }
-
- _sortRows(colIndex, sortOrder) {
-
- if (this.currentSort.colIndex === colIndex) {
- // reverse the array if only sortOrder changed
- if (
- (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
- (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
- ) {
- this.reverseArray(this.rows);
- this.currentSort.sortOrder = sortOrder;
- return;
- }
+ return cell;
}
- this.rows.sort((a, b) => {
- const _aIndex = a[0].rowIndex;
- const _bIndex = b[0].rowIndex;
- const _a = a[colIndex].content;
- const _b = b[colIndex].content;
+ prepareNumericColumns() {
+ const row0 = this.getRow(0);
+ if (!row0) return;
+ this.columns = this.columns.map((column, i) => {
- if (sortOrder === 'none') {
- return _aIndex - _bIndex;
- } else if (sortOrder === 'asc') {
- if (_a < _b) return -1;
- if (_a > _b) return 1;
- if (_a === _b) return 0;
- } else if (sortOrder === 'desc') {
- if (_a < _b) return 1;
- if (_a > _b) return -1;
- if (_a === _b) return 0;
- }
- return 0;
- });
+ const cellValue = row0[i].content;
+ if (!column.align && cellValue && isNumeric(cellValue)) {
+ column.align = 'right';
+ }
- if (this.hasColumnById('_rowIndex')) {
- // update row index
- const srNoColIndex = this.getColumnIndexById('_rowIndex');
- this.rows = this.rows.map((row, index) => {
- return row.map(cell => {
- if (cell.colIndex === srNoColIndex) {
- cell.content = (index + 1) + '';
- }
- return cell;
+ return column;
});
- });
- }
- }
-
- reverseArray(array) {
- let left = null;
- let right = null;
- let length = array.length;
-
- for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
- const temporary = array[left];
-
- array[left] = array[right];
- array[right] = temporary;
- }
- }
-
- switchColumn(index1, index2) {
- // update columns
- const temp = this.columns[index1];
- this.columns[index1] = this.columns[index2];
- this.columns[index2] = temp;
-
- this.columns[index1].colIndex = index1;
- this.columns[index2].colIndex = index2;
-
- // update rows
- this.rows = this.rows.map(row => {
- const newCell1 = Object.assign({}, row[index1], { colIndex: index2 });
- const newCell2 = Object.assign({}, row[index2], { colIndex: index1 });
-
- let newRow = row.map(cell => {
- // make object copy
- return Object.assign({}, cell);
- });
-
- newRow[index2] = newCell1;
- newRow[index1] = newCell2;
-
- return newRow;
- });
- }
-
- removeColumn(index) {
- index = +index;
- const filter = cell => cell.colIndex !== index;
- const map = (cell, i) => Object.assign({}, cell, { colIndex: i });
- // update columns
- this.columns = this.columns
- .filter(filter)
- .map(map);
-
- // update rows
- this.rows = this.rows.map(row => {
- const newRow = row
- .filter(filter)
- .map(map);
-
- return newRow;
- });
- }
-
- updateRow(row, rowIndex) {
- if (row.length < this.columns.length) {
- if (this.hasColumnById('_rowIndex')) {
- const val = (rowIndex + 1) + '';
-
- row = [val].concat(row);
- }
-
- if (this.hasColumnById('_checkbox')) {
- const val = '';
-
- row = [val].concat(row);
- }
}
- const _row = this.prepareRow(row, rowIndex);
- const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
- this.rows[index] = _row;
+ prepareRows() {
+ this.validateData(this.data);
- return _row;
- }
+ this.rows = this.data.map((d, i) => {
+ const index = this._getNextRowCount();
- updateCell(colIndex, rowIndex, options) {
- let cell;
- if (typeof colIndex === 'object') {
- // cell object was passed,
- // must have colIndex, rowIndex
- cell = colIndex;
- colIndex = cell.colIndex;
- rowIndex = cell.rowIndex;
- // the object passed must be merged with original cell
- options = cell;
- }
- cell = this.getCell(colIndex, rowIndex);
+ let row = [];
- // mutate object directly
- for (let key in options) {
- const newVal = options[key];
- if (newVal !== undefined) {
- cell[key] = newVal;
- }
+ if (Array.isArray(d)) {
+ // row is an array
+ if (this.options.addCheckboxColumn) {
+ row.push(this.getCheckboxHTML());
+ }
+ if (this.options.addSerialNoColumn) {
+ row.push((index + 1) + '');
+ }
+ row = row.concat(d);
+
+ while (row.length < this.columns.length) {
+ row.push('');
+ }
+
+ } else {
+ // row is an object
+ for (let col of this.columns) {
+ if (col.id === '_checkbox') {
+ row.push(this.getCheckboxHTML());
+ } else if (col.id === '_rowIndex') {
+ row.push((index + 1) + '');
+ } else {
+ row.push(d[col.id]);
+ }
+ }
+ }
+
+ return this.prepareRow(row, {
+ rowIndex: index
+ });
+ });
}
- return cell;
- }
+ prepareRow(row, props) {
+ const baseRowCell = {
+ rowIndex: props.rowIndex
+ };
- updateColumn(colIndex, keyValPairs) {
- const column = this.getColumn(colIndex);
- for (let key in keyValPairs) {
- const newVal = keyValPairs[key];
- if (newVal !== undefined) {
- column[key] = newVal;
- }
- }
- return column;
- }
+ row = row
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(cell => Object.assign({}, baseRowCell, cell));
- filterRows(keyword, colIndex) {
- let rowsToHide = [];
- let rowsToShow = [];
- const cells = this.rows.map(row => row[colIndex]);
-
- cells.forEach(cell => {
- const hay = cell.content.toLowerCase();
- const needle = (keyword || '').toLowerCase();
-
- if (!needle || hay.includes(needle)) {
- rowsToShow.push(cell.rowIndex);
- } else {
- rowsToHide.push(cell.rowIndex);
- }
- });
-
- return {rowsToHide, rowsToShow};
- }
-
- getRowCount() {
- return this.rowCount;
- }
-
- _getNextRowCount() {
- const val = this.rowCount;
-
- this.rowCount++;
- return val;
- }
-
- getRows(start, end) {
- return this.rows.slice(start, end);
- }
-
- getColumns(skipStandardColumns) {
- let columns = this.columns;
-
- if (skipStandardColumns) {
- columns = columns.slice(this.getStandardColumnCount());
+ // monkey patched in array object
+ row.meta = props;
+ return row;
}
- return columns;
- }
+ validateColumns() {
+ const columns = this.options.columns;
+ if (!Array.isArray(columns)) {
+ throw new DataError('`columns` must be an array');
+ }
- getStandardColumnCount() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ columns.forEach((column, i) => {
+ if (typeof column !== 'string' && typeof column !== 'object') {
+ throw new DataError(`column "${i}" must be a string or an object`);
+ }
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ validateData(data) {
+ if (Array.isArray(data) &&
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
+ return true;
+ }
+ throw new DataError('`data` must be an array of arrays or objects');
}
- return 0;
- }
+ appendRows(rows) {
+ this.validateData(rows);
- getColumnCount(skipStandardColumns) {
- let val = this.columns.length;
-
- if (skipStandardColumns) {
- val = val - this.getStandardColumnCount();
+ this.rows = this.rows.concat(this.prepareRows(rows));
}
- return val;
- }
+ sortRows(colIndex, sortOrder = 'none') {
+ colIndex = +colIndex;
- getColumn(colIndex) {
- colIndex = +colIndex;
- return this.columns.find(col => col.colIndex === colIndex);
- }
+ // reset sortOrder and update for colIndex
+ this.getColumns()
+ .map(col => {
+ if (col.colIndex === colIndex) {
+ col.sortOrder = sortOrder;
+ } else {
+ col.sortOrder = 'none';
+ }
+ });
- getRow(rowIndex) {
- rowIndex = +rowIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex);
- }
+ this._sortRows(colIndex, sortOrder);
+ }
- getCell(colIndex, rowIndex) {
- rowIndex = +rowIndex;
- colIndex = +colIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
- }
+ _sortRows(colIndex, sortOrder) {
- get() {
- return {
- columns: this.columns,
- rows: this.rows
- };
- }
+ if (this.currentSort.colIndex === colIndex) {
+ // reverse the array if only sortOrder changed
+ if (
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
+ ) {
+ this.reverseArray(this.rows);
+ this.currentSort.sortOrder = sortOrder;
+ return;
+ }
+ }
- hasColumn(name) {
- return Boolean(this.columns.find(col => col.content === name));
- }
+ this.rows.sort((a, b) => {
+ const _aIndex = a[0].rowIndex;
+ const _bIndex = b[0].rowIndex;
+ const _a = a[colIndex].content;
+ const _b = b[colIndex].content;
- hasColumnById(id) {
- return Boolean(this.columns.find(col => col.id === id));
- }
+ if (sortOrder === 'none') {
+ return _aIndex - _bIndex;
+ } else if (sortOrder === 'asc') {
+ if (_a < _b) return -1;
+ if (_a > _b) return 1;
+ if (_a === _b) return 0;
+ } else if (sortOrder === 'desc') {
+ if (_a < _b) return 1;
+ if (_a > _b) return -1;
+ if (_a === _b) return 0;
+ }
+ return 0;
+ });
- getColumnIndex(name) {
- return this.columns.findIndex(col => col.content === name);
- }
+ if (this.hasColumnById('_rowIndex')) {
+ // update row index
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
+ this.rows.forEach((row, index) => {
+ row.forEach(cell => {
+ if (cell.colIndex === srNoColIndex) {
+ cell.content = (index + 1) + '';
+ }
+ });
+ });
+ }
+ }
- getColumnIndexById(id) {
- return this.columns.findIndex(col => col.id === id);
- }
+ reverseArray(array) {
+ let left = null;
+ let right = null;
+ let length = array.length;
- getCheckboxHTML() {
- return '';
- }
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
+ const temporary = array[left];
+
+ array[left] = array[right];
+ array[right] = temporary;
+ }
+ }
+
+ switchColumn(index1, index2) {
+ // update columns
+ const temp = this.columns[index1];
+ this.columns[index1] = this.columns[index2];
+ this.columns[index2] = temp;
+
+ this.columns[index1].colIndex = index1;
+ this.columns[index2].colIndex = index2;
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newCell1 = Object.assign({}, row[index1], {
+ colIndex: index2
+ });
+ const newCell2 = Object.assign({}, row[index2], {
+ colIndex: index1
+ });
+
+ let newRow = row.map(cell => {
+ // make object copy
+ return Object.assign({}, cell);
+ });
+
+ newRow[index2] = newCell1;
+ newRow[index1] = newCell2;
+
+ return newRow;
+ });
+ }
+
+ removeColumn(index) {
+ index = +index;
+ const filter = cell => cell.colIndex !== index;
+ const map = (cell, i) => Object.assign({}, cell, {
+ colIndex: i
+ });
+ // update columns
+ this.columns = this.columns
+ .filter(filter)
+ .map(map);
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newRow = row
+ .filter(filter)
+ .map(map);
+
+ return newRow;
+ });
+ }
+
+ updateRow(row, rowIndex) {
+ if (row.length < this.columns.length) {
+ if (this.hasColumnById('_rowIndex')) {
+ const val = (rowIndex + 1) + '';
+
+ row = [val].concat(row);
+ }
+
+ if (this.hasColumnById('_checkbox')) {
+ const val = '';
+
+ row = [val].concat(row);
+ }
+ }
+
+ const _row = this.prepareRow(row, rowIndex);
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
+ this.rows[index] = _row;
+
+ return _row;
+ }
+
+ updateCell(colIndex, rowIndex, options) {
+ let cell;
+ if (typeof colIndex === 'object') {
+ // cell object was passed,
+ // must have colIndex, rowIndex
+ cell = colIndex;
+ colIndex = cell.colIndex;
+ rowIndex = cell.rowIndex;
+ // the object passed must be merged with original cell
+ options = cell;
+ }
+ cell = this.getCell(colIndex, rowIndex);
+
+ // mutate object directly
+ for (let key in options) {
+ const newVal = options[key];
+ if (newVal !== undefined) {
+ cell[key] = newVal;
+ }
+ }
+
+ return cell;
+ }
+
+ updateColumn(colIndex, keyValPairs) {
+ const column = this.getColumn(colIndex);
+ for (let key in keyValPairs) {
+ const newVal = keyValPairs[key];
+ if (newVal !== undefined) {
+ column[key] = newVal;
+ }
+ }
+ return column;
+ }
+
+ filterRows(keyword, colIndex) {
+ let rowsToHide = [];
+ let rowsToShow = [];
+ const cells = this.rows.map(row => row[colIndex]);
+
+ cells.forEach(cell => {
+ const hay = cell.content.toLowerCase();
+ const needle = (keyword || '').toLowerCase();
+
+ if (!needle || hay.includes(needle)) {
+ rowsToShow.push(cell.rowIndex);
+ } else {
+ rowsToHide.push(cell.rowIndex);
+ }
+ });
+
+ return {
+ rowsToHide,
+ rowsToShow
+ };
+ }
+
+ getRowCount() {
+ return this.rowCount;
+ }
+
+ _getNextRowCount() {
+ const val = this.rowCount;
+
+ this.rowCount++;
+ return val;
+ }
+
+ getRows(start, end) {
+ return this.rows.slice(start, end);
+ }
+
+ getColumns(skipStandardColumns) {
+ let columns = this.columns;
+
+ if (skipStandardColumns) {
+ columns = columns.slice(this.getStandardColumnCount());
+ }
+
+ return columns;
+ }
+
+ getStandardColumnCount() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getColumnCount(skipStandardColumns) {
+ let val = this.columns.length;
+
+ if (skipStandardColumns) {
+ val = val - this.getStandardColumnCount();
+ }
+
+ return val;
+ }
+
+ getColumn(colIndex) {
+ colIndex = +colIndex;
+ return this.columns.find(col => col.colIndex === colIndex);
+ }
+
+ getRow(rowIndex) {
+ rowIndex = +rowIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex);
+ }
+
+ getCell(colIndex, rowIndex) {
+ rowIndex = +rowIndex;
+ colIndex = +colIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
+ }
+
+ get() {
+ return {
+ columns: this.columns,
+ rows: this.rows
+ };
+ }
+
+ hasColumn(name) {
+ return Boolean(this.columns.find(col => col.content === name));
+ }
+
+ hasColumnById(id) {
+ return Boolean(this.columns.find(col => col.id === id));
+ }
+
+ getColumnIndex(name) {
+ return this.columns.findIndex(col => col.content === name);
+ }
+
+ getColumnIndexById(id) {
+ return this.columns.findIndex(col => col.id === id);
+ }
+
+ getCheckboxHTML() {
+ return '';
+ }
}
// Custom Errors
class DataError extends TypeError {}
class ColumnManager {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options',
- 'fireEvent',
- 'header',
- 'datamanager',
- 'style',
- 'wrapper',
- 'rowmanager',
- 'bodyScrollable'
- ]);
+ linkProperties(this, this.instance, [
+ 'options',
+ 'fireEvent',
+ 'header',
+ 'datamanager',
+ 'style',
+ 'wrapper',
+ 'rowmanager',
+ 'bodyScrollable'
+ ]);
- this.bindEvents();
- getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
- }
+ this.bindEvents();
+ getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
+ }
- renderHeader() {
- this.header.innerHTML = '';
- this.refreshHeader();
- }
+ renderHeader() {
+ this.header.innerHTML = '';
+ this.refreshHeader();
+ }
- refreshHeader() {
- const columns = this.datamanager.getColumns();
+ refreshHeader() {
+ const columns = this.datamanager.getColumns();
- if (!$('.data-table-col', this.header)) {
- // insert html
+ if (!$('.data-table-col', this.header)) {
+ // insert html
- let html = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- if (this.options.enableInlineFilters) {
- html += this.rowmanager.getRowHTML(columns, { isFilter: 1 });
- }
+ let html = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ if (this.options.enableInlineFilters) {
+ html += this.rowmanager.getRowHTML(columns, {
+ isFilter: 1
+ });
+ }
- $('thead', this.header).innerHTML = html;
+ $('thead', this.header).innerHTML = html;
- this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
+ this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
- if (this.$filterRow) {
- // hide filter row immediately, so it doesn't disturb layout
- $.style(this.$filterRow, {
- display: 'none'
- });
- }
- } else {
- // refresh dom state
- const $cols = $.each('.data-table-col', this.header);
- if (columns.length < $cols.length) {
- // deleted column
- $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- return;
- }
+ if (this.$filterRow) {
+ // hide filter row immediately, so it doesn't disturb layout
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ }
+ } else {
+ // refresh dom state
+ const $cols = $.each('.data-table-col', this.header);
+ if (columns.length < $cols.length) {
+ // deleted column
+ $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ return;
+ }
- $cols.map(($col, i) => {
- const column = columns[i];
- // column sorted or order changed
- // update colIndex of each header cell
- $.data($col, {
- colIndex: column.colIndex
- });
+ $cols.map(($col, i) => {
+ const column = columns[i];
+ // column sorted or order changed
+ // update colIndex of each header cell
+ $.data($col, {
+ colIndex: column.colIndex
+ });
- // refresh sort indicator
- const sortIndicator = $('.sort-indicator', $col);
- if (sortIndicator) {
- sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ // refresh sort indicator
+ const sortIndicator = $('.sort-indicator', $col);
+ if (sortIndicator) {
+ sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ }
+ });
}
- });
+ // reset columnMap
+ this.$columnMap = [];
}
- // reset columnMap
- this.$columnMap = [];
- }
- bindEvents() {
- this.bindDropdown();
- this.bindResizeColumn();
- this.bindMoveColumn();
- this.bindFilter();
- }
-
- bindDropdown() {
- let $activeDropdown;
- $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
- const $dropdown = $.closest('.data-table-dropdown', $button);
-
- if (!$dropdown.classList.contains('is-active')) {
- deactivateDropdown();
- $dropdown.classList.add('is-active');
- $activeDropdown = $dropdown;
- } else {
- deactivateDropdown();
- }
- });
-
- $.on(document.body, 'click', (e) => {
- if (e.target.matches('.data-table-dropdown-toggle')) return;
- deactivateDropdown();
- });
-
- const dropdownItems = this.options.headerDropdown;
-
- $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
- const $col = $.closest('.data-table-col', $item);
- const { index } = $.data($item);
- const { colIndex } = $.data($col);
- let callback = dropdownItems[index].action;
-
- callback && callback.call(this.instance, this.getColumn(colIndex));
- });
-
- function deactivateDropdown(e) {
- $activeDropdown && $activeDropdown.classList.remove('is-active');
- $activeDropdown = null;
+ bindEvents() {
+ this.bindDropdown();
+ this.bindResizeColumn();
+ this.bindMoveColumn();
+ this.bindFilter();
}
- }
- bindResizeColumn() {
- let isDragging = false;
- let $resizingCell, startWidth, startX;
+ bindDropdown() {
+ let $activeDropdown;
+ $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
+ const $dropdown = $.closest('.data-table-dropdown', $button);
- $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
- document.body.classList.add('data-table-resize');
- const $cell = $handle.parentNode.parentNode;
- $resizingCell = $cell;
- const { colIndex } = $.data($resizingCell);
- const col = this.getColumn(colIndex);
-
- if (col && col.resizable === false) {
- return;
- }
-
- isDragging = true;
- startWidth = $.style($('.content', $resizingCell), 'width');
- startX = e.pageX;
- });
-
- $.on(document.body, 'mouseup', (e) => {
- document.body.classList.remove('data-table-resize');
- if (!$resizingCell) return;
- isDragging = false;
-
- const { colIndex } = $.data($resizingCell);
- this.setColumnWidth(colIndex);
- this.style.setBodyStyle();
- $resizingCell = null;
- });
-
- $.on(document.body, 'mousemove', (e) => {
- if (!isDragging) return;
- const finalWidth = startWidth + (e.pageX - startX);
- const { colIndex } = $.data($resizingCell);
-
- if (this.getColumnMinWidth(colIndex) > finalWidth) {
- // don't resize past minWidth
- return;
- }
- this.datamanager.updateColumn(colIndex, { width: finalWidth });
- this.setColumnHeaderWidth(colIndex);
- });
- }
-
- bindMoveColumn() {
- let initialized;
-
- const initialize = () => {
- if (initialized) {
- $.off(document.body, 'mousemove', initialize);
- return;
- }
- const ready = $('.data-table-col', this.header);
- if (!ready) return;
-
- const $parent = $('.data-table-row', this.header);
-
- this.sortable = Sortable.create($parent, {
- onEnd: (e) => {
- const { oldIndex, newIndex } = e;
- const $draggedCell = e.item;
- const { colIndex } = $.data($draggedCell);
- if (+colIndex === newIndex) return;
-
- this.switchColumn(oldIndex, newIndex);
- },
- preventOnFilter: false,
- filter: '.column-resizer, .data-table-dropdown',
- animation: 150
- });
- };
-
- $.on(document.body, 'mousemove', initialize);
- }
-
- bindSortColumn() {
-
- $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
- const $cell = span.closest('.data-table-col');
- let { colIndex, sortOrder } = $.data($cell);
- sortOrder = getDefault(sortOrder, 'none');
- const col = this.getColumn(colIndex);
-
- if (col && col.sortable === false) {
- return;
- }
-
- // reset sort indicator
- $('.sort-indicator', this.header).textContent = '';
- $.each('.data-table-col', this.header).map($cell => {
- $.data($cell, {
- sortOrder: 'none'
+ if (!$dropdown.classList.contains('is-active')) {
+ deactivateDropdown();
+ $dropdown.classList.add('is-active');
+ $activeDropdown = $dropdown;
+ } else {
+ deactivateDropdown();
+ }
});
- });
- let nextSortOrder, textContent;
- if (sortOrder === 'none') {
- nextSortOrder = 'asc';
- textContent = '▲';
- } else if (sortOrder === 'asc') {
- nextSortOrder = 'desc';
- textContent = '▼';
- } else if (sortOrder === 'desc') {
- nextSortOrder = 'none';
- textContent = '';
- }
-
- $.data($cell, {
- sortOrder: nextSortOrder
- });
- $('.sort-indicator', $cell).textContent = textContent;
-
- this.sortColumn(colIndex, nextSortOrder);
- });
- }
-
- sortColumn(colIndex, nextSortOrder) {
- this.instance.freeze();
- this.sortRows(colIndex, nextSortOrder)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onSortColumn', this.getColumn(colIndex));
- });
- }
-
- removeColumn(colIndex) {
- const removedCol = this.getColumn(colIndex);
- this.instance.freeze();
- this.datamanager.removeColumn(colIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onRemoveColumn', removedCol);
- });
- }
-
- switchColumn(oldIndex, newIndex) {
- this.instance.freeze();
- this.datamanager.switchColumn(oldIndex, newIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => {
- this.setColumnWidth(oldIndex);
- this.setColumnWidth(newIndex);
- this.instance.unfreeze();
- })
- .then(() => {
- this.fireEvent('onSwitchColumn',
- this.getColumn(oldIndex), this.getColumn(newIndex)
- );
- });
- }
-
- toggleFilter() {
- this.isFilterShown = this.isFilterShown || false;
-
- if (this.isFilterShown) {
- $.style(this.$filterRow, {
- display: 'none'
- });
- } else {
- $.style(this.$filterRow, {
- display: ''
- });
- }
-
- this.isFilterShown = !this.isFilterShown;
- this.style.setBodyStyle();
- }
-
- focusFilter(colIndex) {
- if (!this.isFilterShown) return;
-
- const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
- $filterInput.focus();
- }
-
- bindFilter() {
- if (!this.options.enableInlineFilters) return;
- const handler = e => {
- const $filterCell = $.closest('.data-table-col', e.target);
- const { colIndex } = $.data($filterCell);
- const keyword = e.target.value;
-
- this.datamanager.filterRows(keyword, colIndex)
- .then(({ rowsToHide, rowsToShow }) => {
- rowsToHide.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.add('hide');
- });
- rowsToShow.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.remove('hide');
- });
+ $.on(document.body, 'click', (e) => {
+ if (e.target.matches('.data-table-dropdown-toggle')) return;
+ deactivateDropdown();
});
- };
- $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300));
- }
- sortRows(colIndex, sortOrder) {
- return this.datamanager.sortRows(colIndex, sortOrder);
- }
+ const dropdownItems = this.options.headerDropdown;
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
+ $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
+ const $col = $.closest('.data-table-col', $item);
+ const {
+ index
+ } = $.data($item);
+ const {
+ colIndex
+ } = $.data($col);
+ let callback = dropdownItems[index].action;
- getColumns() {
- return this.datamanager.getColumns();
- }
+ callback && callback.call(this.instance, this.getColumn(colIndex));
+ });
- setColumnWidth(colIndex) {
- colIndex = +colIndex;
- this._columnWidthMap = this._columnWidthMap || [];
-
- const { width } = this.getColumn(colIndex);
-
- let index = this._columnWidthMap[colIndex];
- const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
- const styles = {
- width: width + 'px'
- };
-
- index = this.style.setStyle(selector, styles, index);
- this._columnWidthMap[colIndex] = index;
- }
-
- setColumnHeaderWidth(colIndex) {
- colIndex = +colIndex;
- this.$columnMap = this.$columnMap || [];
- const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
- const { width } = this.getColumn(colIndex);
-
- let $column = this.$columnMap[colIndex];
- if (!$column) {
- $column = this.header.querySelector(selector);
- this.$columnMap[colIndex] = $column;
+ function deactivateDropdown(e) {
+ $activeDropdown && $activeDropdown.classList.remove('is-active');
+ $activeDropdown = null;
+ }
}
- $column.style.width = width + 'px';
- }
+ bindResizeColumn() {
+ let isDragging = false;
+ let $resizingCell, startWidth, startX;
- getColumnMinWidth(colIndex) {
- colIndex = +colIndex;
- return this.getColumn(colIndex).minWidth || 24;
- }
+ $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
+ document.body.classList.add('data-table-resize');
+ const $cell = $handle.parentNode.parentNode;
+ $resizingCell = $cell;
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ const col = this.getColumn(colIndex);
- getFirstColumnIndex() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ if (col && col.resizable === false) {
+ return;
+ }
+
+ isDragging = true;
+ startWidth = $.style($('.content', $resizingCell), 'width');
+ startX = e.pageX;
+ });
+
+ $.on(document.body, 'mouseup', (e) => {
+ document.body.classList.remove('data-table-resize');
+ if (!$resizingCell) return;
+ isDragging = false;
+
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ this.setColumnWidth(colIndex);
+ this.style.setBodyStyle();
+ $resizingCell = null;
+ });
+
+ $.on(document.body, 'mousemove', (e) => {
+ if (!isDragging) return;
+ const finalWidth = startWidth + (e.pageX - startX);
+ const {
+ colIndex
+ } = $.data($resizingCell);
+
+ if (this.getColumnMinWidth(colIndex) > finalWidth) {
+ // don't resize past minWidth
+ return;
+ }
+ this.datamanager.updateColumn(colIndex, {
+ width: finalWidth
+ });
+ this.setColumnHeaderWidth(colIndex);
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ bindMoveColumn() {
+ let initialized;
+
+ const initialize = () => {
+ if (initialized) {
+ $.off(document.body, 'mousemove', initialize);
+ return;
+ }
+ const ready = $('.data-table-col', this.header);
+ if (!ready) return;
+
+ const $parent = $('.data-table-row', this.header);
+
+ this.sortable = Sortable.create($parent, {
+ onEnd: (e) => {
+ const {
+ oldIndex,
+ newIndex
+ } = e;
+ const $draggedCell = e.item;
+ const {
+ colIndex
+ } = $.data($draggedCell);
+ if (+colIndex === newIndex) return;
+
+ this.switchColumn(oldIndex, newIndex);
+ },
+ preventOnFilter: false,
+ filter: '.column-resizer, .data-table-dropdown',
+ animation: 150
+ });
+ };
+
+ $.on(document.body, 'mousemove', initialize);
}
- return 0;
- }
+ bindSortColumn() {
- getHeaderCell$(colIndex) {
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
+ const $cell = span.closest('.data-table-col');
+ let {
+ colIndex,
+ sortOrder
+ } = $.data($cell);
+ sortOrder = getDefault(sortOrder, 'none');
+ const col = this.getColumn(colIndex);
- getLastColumnIndex() {
- return this.datamanager.getColumnCount() - 1;
- }
+ if (col && col.sortable === false) {
+ return;
+ }
- getSerialColumnIndex() {
- const columns = this.datamanager.getColumns();
+ // reset sort indicator
+ $('.sort-indicator', this.header).textContent = '';
+ $.each('.data-table-col', this.header).map($cell => {
+ $.data($cell, {
+ sortOrder: 'none'
+ });
+ });
- return columns.findIndex(column => column.content.includes('Sr. No'));
- }
+ let nextSortOrder, textContent;
+ if (sortOrder === 'none') {
+ nextSortOrder = 'asc';
+ textContent = '▲';
+ } else if (sortOrder === 'asc') {
+ nextSortOrder = 'desc';
+ textContent = '▼';
+ } else if (sortOrder === 'desc') {
+ nextSortOrder = 'none';
+ textContent = '';
+ }
+
+ $.data($cell, {
+ sortOrder: nextSortOrder
+ });
+ $('.sort-indicator', $cell).textContent = textContent;
+
+ this.sortColumn(colIndex, nextSortOrder);
+ });
+ }
+
+ sortColumn(colIndex, nextSortOrder) {
+ this.instance.freeze();
+ this.sortRows(colIndex, nextSortOrder)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
+ });
+ }
+
+ removeColumn(colIndex) {
+ const removedCol = this.getColumn(colIndex);
+ this.instance.freeze();
+ this.datamanager.removeColumn(colIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onRemoveColumn', removedCol);
+ });
+ }
+
+ switchColumn(oldIndex, newIndex) {
+ this.instance.freeze();
+ this.datamanager.switchColumn(oldIndex, newIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => {
+ this.setColumnWidth(oldIndex);
+ this.setColumnWidth(newIndex);
+ this.instance.unfreeze();
+ })
+ .then(() => {
+ this.fireEvent('onSwitchColumn',
+ this.getColumn(oldIndex), this.getColumn(newIndex)
+ );
+ });
+ }
+
+ toggleFilter() {
+ this.isFilterShown = this.isFilterShown || false;
+
+ if (this.isFilterShown) {
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ } else {
+ $.style(this.$filterRow, {
+ display: ''
+ });
+ }
+
+ this.isFilterShown = !this.isFilterShown;
+ this.style.setBodyStyle();
+ }
+
+ focusFilter(colIndex) {
+ if (!this.isFilterShown) return;
+
+ const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
+ $filterInput.focus();
+ }
+
+ bindFilter() {
+ if (!this.options.enableInlineFilters) return;
+ const handler = e => {
+ const $filterCell = $.closest('.data-table-col', e.target);
+ const {
+ colIndex
+ } = $.data($filterCell);
+ const keyword = e.target.value;
+
+ this.datamanager.filterRows(keyword, colIndex)
+ .then(({
+ rowsToHide,
+ rowsToShow
+ }) => {
+ rowsToHide.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.add('hide');
+ });
+ rowsToShow.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.remove('hide');
+ });
+ });
+ };
+ $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300));
+ }
+
+ sortRows(colIndex, sortOrder) {
+ return this.datamanager.sortRows(colIndex, sortOrder);
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ setColumnWidth(colIndex) {
+ colIndex = +colIndex;
+ this._columnWidthMap = this._columnWidthMap || [];
+
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let index = this._columnWidthMap[colIndex];
+ const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
+ const styles = {
+ width: width + 'px'
+ };
+
+ index = this.style.setStyle(selector, styles, index);
+ this._columnWidthMap[colIndex] = index;
+ }
+
+ setColumnHeaderWidth(colIndex) {
+ colIndex = +colIndex;
+ this.$columnMap = this.$columnMap || [];
+ const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let $column = this.$columnMap[colIndex];
+ if (!$column) {
+ $column = this.header.querySelector(selector);
+ this.$columnMap[colIndex] = $column;
+ }
+
+ $column.style.width = width + 'px';
+ }
+
+ getColumnMinWidth(colIndex) {
+ colIndex = +colIndex;
+ return this.getColumn(colIndex).minWidth || 24;
+ }
+
+ getFirstColumnIndex() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getHeaderCell$(colIndex) {
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+
+ getLastColumnIndex() {
+ return this.datamanager.getColumnCount() - 1;
+ }
+
+ getSerialColumnIndex() {
+ const columns = this.datamanager.getColumns();
+
+ return columns.findIndex(column => column.content.includes('Sr. No'));
+ }
}
// eslint-disable-next-line
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
- // add dropdown buttons
- const dropdownItems = this.options.headerDropdown;
+ // add dropdown buttons
+ const dropdownItems = this.options.headerDropdown;
- return `${dropdownButton}
+ return `${dropdownButton}
${dropdownItems.map((d, i) => `
${d.label}
`).join('')}
@@ -1752,603 +1811,649 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
};
class CellManager {
- constructor(instance) {
- this.instance = instance;
- this.wrapper = this.instance.wrapper;
- this.options = this.instance.options;
- this.style = this.instance.style;
- this.bodyScrollable = this.instance.bodyScrollable;
- this.columnmanager = this.instance.columnmanager;
- this.rowmanager = this.instance.rowmanager;
- this.datamanager = this.instance.datamanager;
- this.keyboard = this.instance.keyboard;
+ constructor(instance) {
+ this.instance = instance;
+ this.wrapper = this.instance.wrapper;
+ this.options = this.instance.options;
+ this.style = this.instance.style;
+ this.bodyScrollable = this.instance.bodyScrollable;
+ this.columnmanager = this.instance.columnmanager;
+ this.rowmanager = this.instance.rowmanager;
+ this.datamanager = this.instance.datamanager;
+ this.keyboard = this.instance.keyboard;
- this.bindEvents();
- }
+ this.bindEvents();
+ }
- bindEvents() {
- this.bindFocusCell();
- this.bindEditCell();
- this.bindKeyboardSelection();
- this.bindCopyCellContents();
- this.bindMouseEvents();
- }
+ bindEvents() {
+ this.bindFocusCell();
+ this.bindEditCell();
+ this.bindKeyboardSelection();
+ this.bindCopyCellContents();
+ this.bindMouseEvents();
+ }
- bindFocusCell() {
- this.bindKeyboardNav();
- }
+ bindFocusCell() {
+ this.bindKeyboardNav();
+ }
- bindEditCell() {
- this.$editingCell = null;
+ bindEditCell() {
+ this.$editingCell = null;
- $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
- this.activateEditing(cell);
- });
+ $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
+ this.activateEditing(cell);
+ });
+
+ this.keyboard.on('enter', (e) => {
+ if (this.$focusedCell && !this.$editingCell) {
+ // enter keypress on focused cell
+ this.activateEditing(this.$focusedCell);
+ } else if (this.$editingCell) {
+ // enter keypress on editing cell
+ this.submitEditing();
+ this.deactivateEditing();
+ }
+ });
+ }
+
+ bindKeyboardNav() {
+ const focusCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+
+ if (direction === 'left' || direction === 'shift+tab') {
+ $cell = this.getLeftCell$($cell);
+ } else if (direction === 'right' || direction === 'tab') {
+ $cell = this.getRightCell$($cell);
+ } else if (direction === 'up') {
+ $cell = this.getAboveCell$($cell);
+ } else if (direction === 'down') {
+ $cell = this.getBelowCell$($cell);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ const focusLastCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ if (direction === 'left') {
+ $cell = this.getLeftMostCell$(rowIndex);
+ } else if (direction === 'right') {
+ $cell = this.getRightMostCell$(rowIndex);
+ } else if (direction === 'up') {
+ $cell = this.getTopMostCell$(colIndex);
+ } else if (direction === 'down') {
+ $cell = this.getBottomMostCell$(colIndex);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
+ direction => this.keyboard.on(direction, () => focusCell(direction))
+ );
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
+ );
+
+ this.keyboard.on('esc', () => {
+ this.deactivateEditing();
+ });
+
+ if (this.options.enableInlineFilters) {
+ this.keyboard.on('ctrl+f', (e) => {
+ const $cell = $.closest('.data-table-col', e.target);
+ let {
+ colIndex
+ } = $.data($cell);
+
+ this.activateFilter(colIndex);
+ return true;
+ });
+ }
+ }
+
+ bindKeyboardSelection() {
+ const getNextSelectionCursor = (direction) => {
+ let $selectionCursor = this.getSelectionCursor();
+
+ if (direction === 'left') {
+ $selectionCursor = this.getLeftCell$($selectionCursor);
+ } else if (direction === 'right') {
+ $selectionCursor = this.getRightCell$($selectionCursor);
+ } else if (direction === 'up') {
+ $selectionCursor = this.getAboveCell$($selectionCursor);
+ } else if (direction === 'down') {
+ $selectionCursor = this.getBelowCell$($selectionCursor);
+ }
+
+ return $selectionCursor;
+ };
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('shift+' + direction,
+ () => this.selectArea(getNextSelectionCursor(direction)))
+ );
+ }
+
+ bindCopyCellContents() {
+ this.keyboard.on('ctrl+c', () => {
+ this.copyCellContents(this.$focusedCell, this.$selectionCursor);
+ });
+ }
+
+ bindMouseEvents() {
+ let mouseDown = null;
+
+ $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
+ mouseDown = true;
+ this.focusCell($(e.delegatedTarget));
+ });
+
+ $.on(this.bodyScrollable, 'mouseup', () => {
+ mouseDown = false;
+ });
+
+ const selectArea = (e) => {
+ if (!mouseDown) return;
+ this.selectArea($(e.delegatedTarget));
+ };
+
+ $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50));
+ }
+
+ focusCell($cell, {
+ skipClearSelection = 0
+ } = {}) {
+ if (!$cell) return;
+
+ // don't focus if already editing cell
+ if ($cell === this.$editingCell) return;
+
+ const {
+ colIndex,
+ isHeader
+ } = $.data($cell);
+ if (isHeader) {
+ return;
+ }
+
+ const column = this.columnmanager.getColumn(colIndex);
+ if (column.focusable === false) {
+ return;
+ }
+
+ this.scrollToCell($cell);
- this.keyboard.on('enter', (e) => {
- if (this.$focusedCell && !this.$editingCell) {
- // enter keypress on focused cell
- this.activateEditing(this.$focusedCell);
- } else if (this.$editingCell) {
- // enter keypress on editing cell
- this.submitEditing();
this.deactivateEditing();
- }
- });
- }
+ if (!skipClearSelection) {
+ this.clearSelection();
+ }
- bindKeyboardNav() {
- const focusCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
+ if (this.$focusedCell) {
+ this.$focusedCell.classList.remove('selected');
+ }
- let $cell = this.$focusedCell;
+ this.$focusedCell = $cell;
+ $cell.classList.add('selected');
- if (direction === 'left' || direction === 'shift+tab') {
- $cell = this.getLeftCell$($cell);
- } else if (direction === 'right' || direction === 'tab') {
- $cell = this.getRightCell$($cell);
- } else if (direction === 'up') {
- $cell = this.getAboveCell$($cell);
- } else if (direction === 'down') {
- $cell = this.getBelowCell$($cell);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- const focusLastCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
-
- let $cell = this.$focusedCell;
- const { rowIndex, colIndex } = $.data($cell);
-
- if (direction === 'left') {
- $cell = this.getLeftMostCell$(rowIndex);
- } else if (direction === 'right') {
- $cell = this.getRightMostCell$(rowIndex);
- } else if (direction === 'up') {
- $cell = this.getTopMostCell$(colIndex);
- } else if (direction === 'down') {
- $cell = this.getBottomMostCell$(colIndex);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
- direction => this.keyboard.on(direction, () => focusCell(direction))
- );
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
- );
-
- this.keyboard.on('esc', () => {
- this.deactivateEditing();
- });
-
- if (this.options.enableInlineFilters) {
- this.keyboard.on('ctrl+f', (e) => {
- const $cell = $.closest('.data-table-col', e.target);
- let { colIndex } = $.data($cell);
-
- this.activateFilter(colIndex);
- return true;
- });
- }
- }
-
- bindKeyboardSelection() {
- const getNextSelectionCursor = (direction) => {
- let $selectionCursor = this.getSelectionCursor();
-
- if (direction === 'left') {
- $selectionCursor = this.getLeftCell$($selectionCursor);
- } else if (direction === 'right') {
- $selectionCursor = this.getRightCell$($selectionCursor);
- } else if (direction === 'up') {
- $selectionCursor = this.getAboveCell$($selectionCursor);
- } else if (direction === 'down') {
- $selectionCursor = this.getBelowCell$($selectionCursor);
- }
-
- return $selectionCursor;
- };
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('shift+' + direction,
- () => this.selectArea(getNextSelectionCursor(direction)))
- );
- }
-
- bindCopyCellContents() {
- this.keyboard.on('ctrl+c', () => {
- this.copyCellContents(this.$focusedCell, this.$selectionCursor);
- });
- }
-
- bindMouseEvents() {
- let mouseDown = null;
-
- $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
- mouseDown = true;
- this.focusCell($(e.delegatedTarget));
- });
-
- $.on(this.bodyScrollable, 'mouseup', () => {
- mouseDown = false;
- });
-
- const selectArea = (e) => {
- if (!mouseDown) return;
- this.selectArea($(e.delegatedTarget));
- };
-
- $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50));
- }
-
- focusCell($cell, { skipClearSelection = 0 } = {}) {
- if (!$cell) return;
-
- // don't focus if already editing cell
- if ($cell === this.$editingCell) return;
-
- const { colIndex, isHeader } = $.data($cell);
- if (isHeader) {
- return;
- }
-
- const column = this.columnmanager.getColumn(colIndex);
- if (column.focusable === false) {
- return;
- }
-
- this.scrollToCell($cell);
-
- this.deactivateEditing();
- if (!skipClearSelection) {
- this.clearSelection();
- }
-
- if (this.$focusedCell) {
- this.$focusedCell.classList.remove('selected');
- }
-
- this.$focusedCell = $cell;
- $cell.classList.add('selected');
-
- // so that keyboard nav works
- $cell.focus();
-
- this.highlightRowColumnHeader($cell);
- }
-
- highlightRowColumnHeader($cell) {
- const { colIndex, rowIndex } = $.data($cell);
- const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
- const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
- const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
-
- if (this.lastHeaders) {
- $.removeStyle(this.lastHeaders, 'backgroundColor');
- }
-
- const colHeader = $(colHeaderSelector, this.wrapper);
- const rowHeader = $(rowHeaderSelector, this.wrapper);
-
- $.style([colHeader, rowHeader], {
- backgroundColor: '#f5f7fa' // light-bg
- });
-
- this.lastHeaders = [colHeader, rowHeader];
- }
-
- selectAreaOnClusterChanged() {
- if (!(this.$focusedCell && this.$selectionCursor)) return;
- const { colIndex, rowIndex } = $.data(this.$selectionCursor);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell || $cell === this.$selectionCursor) return;
-
- // selectArea needs $focusedCell
- const fCell = $.data(this.$focusedCell);
- this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
-
- this.selectArea($cell);
- }
-
- focusCellOnClusterChanged() {
- if (!this.$focusedCell) return;
-
- const { colIndex, rowIndex } = $.data(this.$focusedCell);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell) return;
- // this function is called after selectAreaOnClusterChanged,
- // focusCell calls clearSelection which resets the area selection
- // so a flag to skip it
- this.focusCell($cell, { skipClearSelection: 1 });
- }
-
- selectArea($selectionCursor) {
- if (!this.$focusedCell) return;
-
- if (this._selectArea(this.$focusedCell, $selectionCursor)) {
- // valid selection
- this.$selectionCursor = $selectionCursor;
- }
- };
-
- _selectArea($cell1, $cell2) {
- if ($cell1 === $cell2) return false;
-
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return false;
-
- this.clearSelection();
- cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
- return true;
- }
-
- getCellsInRange($cell1, $cell2) {
- let colIndex1, rowIndex1, colIndex2, rowIndex2;
-
- if (typeof $cell1 === 'number') {
- [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
- } else
- if (typeof $cell1 === 'object') {
-
- if (!($cell1 && $cell2)) {
- return false;
- }
-
- const cell1 = $.data($cell1);
- const cell2 = $.data($cell2);
-
- colIndex1 = cell1.colIndex;
- rowIndex1 = cell1.rowIndex;
- colIndex2 = cell2.colIndex;
- rowIndex2 = cell2.rowIndex;
- }
-
- if (rowIndex1 > rowIndex2) {
- [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
- }
-
- if (colIndex1 > colIndex2) {
- [colIndex1, colIndex2] = [colIndex2, colIndex1];
- }
-
- if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
- return false;
- }
-
- let cells = [];
- let colIndex = colIndex1;
- let rowIndex = rowIndex1;
- let rowIndices = [];
-
- while (rowIndex <= rowIndex2) {
- rowIndices.push(rowIndex);
- rowIndex++;
- }
-
- rowIndices.map(rowIndex => {
- while (colIndex <= colIndex2) {
- cells.push([colIndex, rowIndex]);
- colIndex++;
- }
- colIndex = colIndex1;
- });
-
- return cells;
- }
-
- clearSelection() {
- $.each('.data-table-col.highlight', this.bodyScrollable)
- .map(cell => cell.classList.remove('highlight'));
-
- this.$selectionCursor = null;
- }
-
- getSelectionCursor() {
- return this.$selectionCursor || this.$focusedCell;
- }
-
- activateEditing($cell) {
- this.focusCell($cell);
- const { rowIndex, colIndex } = $.data($cell);
-
- const col = this.columnmanager.getColumn(colIndex);
- if (col && (col.editable === false || col.focusable === false)) {
- return;
- }
-
- const cell = this.getCell(colIndex, rowIndex);
- if (cell && cell.editable === false) {
- return;
- }
-
- if (this.$editingCell) {
- const { _rowIndex, _colIndex } = $.data(this.$editingCell);
-
- if (rowIndex === _rowIndex && colIndex === _colIndex) {
- // editing the same cell
- return;
- }
- }
-
- this.$editingCell = $cell;
- $cell.classList.add('editing');
-
- const $editCell = $('.edit-cell', $cell);
- $editCell.innerHTML = '';
-
- const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
-
- if (editor) {
- this.currentCellEditor = editor;
- // initialize editing input with cell value
- editor.initValue(cell.content, rowIndex, col);
- }
- }
-
- deactivateEditing() {
- // keep focus on the cell so that keyboard navigation works
- if (this.$focusedCell) this.$focusedCell.focus();
-
- if (!this.$editingCell) return;
- this.$editingCell.classList.remove('editing');
- this.$editingCell = null;
- }
-
- getEditor(colIndex, rowIndex, value, parent) {
- // debugger;
- const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
- if (obj && obj.setValue) return obj;
-
- // editing fallback
- const $input = $.create('input', {
- class: 'input-style',
- type: 'text',
- inside: parent
- });
-
- return {
- initValue(value) {
- $input.focus();
- $input.value = value;
- },
- getValue() {
- return $input.value;
- },
- setValue(value) {
- $input.value = value;
- }
- };
- }
-
- submitEditing() {
- if (!this.$editingCell) return;
- const $cell = this.$editingCell;
- const { rowIndex, colIndex } = $.data($cell);
- const col = this.datamanager.getColumn(colIndex);
-
- if ($cell) {
- const editor = this.currentCellEditor;
-
- if (editor) {
- const value = editor.getValue();
- const done = editor.setValue(value, rowIndex, col);
- const oldValue = this.getCell(colIndex, rowIndex).content;
-
- // update cell immediately
- this.updateCell(colIndex, rowIndex, value);
+ // so that keyboard nav works
$cell.focus();
- if (done && done.then) {
- // revert to oldValue if promise fails
- done.catch((e) => {
- console.log(e);
- this.updateCell(colIndex, rowIndex, oldValue);
- });
+ this.highlightRowColumnHeader($cell);
+ }
+
+ highlightRowColumnHeader($cell) {
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell);
+ const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
+ const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
+ const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
+
+ if (this.lastHeaders) {
+ $.removeStyle(this.lastHeaders, 'backgroundColor');
}
- }
+
+ const colHeader = $(colHeaderSelector, this.wrapper);
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
+
+ $.style([colHeader, rowHeader], {
+ backgroundColor: '#f5f7fa' // light-bg
+ });
+
+ this.lastHeaders = [colHeader, rowHeader];
}
- this.currentCellEditor = null;
- }
+ selectAreaOnClusterChanged() {
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$selectionCursor);
+ const $cell = this.getCell$(colIndex, rowIndex);
- copyCellContents($cell1, $cell2) {
- if (!$cell2 && $cell1) {
- // copy only focusedCell
- const { colIndex, rowIndex } = $.data($cell1);
- const cell = this.getCell(colIndex, rowIndex);
- copyTextToClipboard(cell.content);
- return;
+ if (!$cell || $cell === this.$selectionCursor) return;
+
+ // selectArea needs $focusedCell
+ const fCell = $.data(this.$focusedCell);
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
+
+ this.selectArea($cell);
}
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return;
+ focusCellOnClusterChanged() {
+ if (!this.$focusedCell) return;
- const values = cells
- // get cell objects
- .map(index => this.getCell(...index))
- // convert to array of rows
- .reduce((acc, curr) => {
- const rowIndex = curr.rowIndex;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$focusedCell);
+ const $cell = this.getCell$(colIndex, rowIndex);
- acc[rowIndex] = acc[rowIndex] || [];
- acc[rowIndex].push(curr.content);
-
- return acc;
- }, [])
- // join values by tab
- .map(row => row.join('\t'))
- // join rows by newline
- .join('\n');
-
- copyTextToClipboard(values);
- }
-
- activateFilter(colIndex) {
- this.columnmanager.toggleFilter();
- this.columnmanager.focusFilter(colIndex);
-
- if (!this.columnmanager.isFilterShown) {
- // put focus back on cell
- this.$focusedCell.focus();
+ if (!$cell) return;
+ // this function is called after selectAreaOnClusterChanged,
+ // focusCell calls clearSelection which resets the area selection
+ // so a flag to skip it
+ this.focusCell($cell, {
+ skipClearSelection: 1
+ });
}
- }
- updateCell(colIndex, rowIndex, value) {
- const cell = this.datamanager.updateCell(colIndex, rowIndex, {
- content: value
- });
- this.refreshCell(cell);
- }
+ selectArea($selectionCursor) {
+ if (!this.$focusedCell) return;
- refreshCell(cell) {
- const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
- $cell.innerHTML = this.getCellContent(cell);
- }
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
+ // valid selection
+ this.$selectionCursor = $selectionCursor;
+ }
+ };
- isStandardCell(colIndex) {
- // Standard cells are in Sr. No and Checkbox column
- return colIndex < this.columnmanager.getFirstColumnIndex();
- }
+ _selectArea($cell1, $cell2) {
+ if ($cell1 === $cell2) return false;
- getCell$(colIndex, rowIndex) {
- return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
- }
+ const cells = this.getCellsInRange($cell1, $cell2);
+ if (!cells) return false;
- getAboveCell$($cell) {
- const { colIndex } = $.data($cell);
- const $aboveRow = $cell.parentElement.previousElementSibling;
+ this.clearSelection();
+ cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
+ return true;
+ }
- return $(`[data-col-index="${colIndex}"]`, $aboveRow);
- }
+ getCellsInRange($cell1, $cell2) {
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
- getBelowCell$($cell) {
- const { colIndex } = $.data($cell);
- const $belowRow = $cell.parentElement.nextElementSibling;
+ if (typeof $cell1 === 'number') {
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
+ } else
+ if (typeof $cell1 === 'object') {
- return $(`[data-col-index="${colIndex}"]`, $belowRow);
- }
+ if (!($cell1 && $cell2)) {
+ return false;
+ }
- getLeftCell$($cell) {
- return $cell.previousElementSibling;
- }
+ const cell1 = $.data($cell1);
+ const cell2 = $.data($cell2);
- getRightCell$($cell) {
- return $cell.nextElementSibling;
- }
+ colIndex1 = cell1.colIndex;
+ rowIndex1 = cell1.rowIndex;
+ colIndex2 = cell2.colIndex;
+ rowIndex2 = cell2.rowIndex;
+ }
- getLeftMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
- }
+ if (rowIndex1 > rowIndex2) {
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
+ }
- getRightMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
- }
+ if (colIndex1 > colIndex2) {
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
+ }
- getTopMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
- }
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
+ return false;
+ }
- getBottomMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
- }
+ let cells = [];
+ let colIndex = colIndex1;
+ let rowIndex = rowIndex1;
+ let rowIndices = [];
- getCell(colIndex, rowIndex) {
- return this.instance.datamanager.getCell(colIndex, rowIndex);
- }
+ while (rowIndex <= rowIndex2) {
+ rowIndices.push(rowIndex);
+ rowIndex++;
+ }
- getCellAttr($cell) {
- return this.instance.getCellAttr($cell);
- }
+ rowIndices.map(rowIndex => {
+ while (colIndex <= colIndex2) {
+ cells.push([colIndex, rowIndex]);
+ colIndex++;
+ }
+ colIndex = colIndex1;
+ });
- getRowHeight() {
- return $.style($('.data-table-row', this.bodyScrollable), 'height');
- }
+ return cells;
+ }
- scrollToCell($cell) {
- if ($.inViewport($cell, this.bodyScrollable)) return false;
+ clearSelection() {
+ $.each('.data-table-col.highlight', this.bodyScrollable)
+ .map(cell => cell.classList.remove('highlight'));
- const { rowIndex } = $.data($cell);
- this.rowmanager.scrollToRow(rowIndex);
- return false;
- }
+ this.$selectionCursor = null;
+ }
- getRowCountPerPage() {
- return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
- }
+ getSelectionCursor() {
+ return this.$selectionCursor || this.$focusedCell;
+ }
- getCellHTML(cell) {
- const { rowIndex, colIndex, isHeader, isFilter } = cell;
- const dataAttr = makeDataAttributeString({
- rowIndex,
- colIndex,
- isHeader,
- isFilter
- });
+ activateEditing($cell) {
+ this.focusCell($cell);
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
- return `
+ const col = this.columnmanager.getColumn(colIndex);
+ if (col && (col.editable === false || col.focusable === false)) {
+ return;
+ }
+
+ const cell = this.getCell(colIndex, rowIndex);
+ if (cell && cell.editable === false) {
+ return;
+ }
+
+ if (this.$editingCell) {
+ const {
+ _rowIndex,
+ _colIndex
+ } = $.data(this.$editingCell);
+
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
+ // editing the same cell
+ return;
+ }
+ }
+
+ this.$editingCell = $cell;
+ $cell.classList.add('editing');
+
+ const $editCell = $('.edit-cell', $cell);
+ $editCell.innerHTML = '';
+
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
+
+ if (editor) {
+ this.currentCellEditor = editor;
+ // initialize editing input with cell value
+ editor.initValue(cell.content, rowIndex, col);
+ }
+ }
+
+ deactivateEditing() {
+ // keep focus on the cell so that keyboard navigation works
+ if (this.$focusedCell) this.$focusedCell.focus();
+
+ if (!this.$editingCell) return;
+ this.$editingCell.classList.remove('editing');
+ this.$editingCell = null;
+ }
+
+ getEditor(colIndex, rowIndex, value, parent) {
+ // debugger;
+ const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
+ if (obj && obj.setValue) return obj;
+
+ // editing fallback
+ const $input = $.create('input', {
+ class: 'input-style',
+ type: 'text',
+ inside: parent
+ });
+
+ return {
+ initValue(value) {
+ $input.focus();
+ $input.value = value;
+ },
+ getValue() {
+ return $input.value;
+ },
+ setValue(value) {
+ $input.value = value;
+ }
+ };
+ }
+
+ submitEditing() {
+ if (!this.$editingCell) return;
+ const $cell = this.$editingCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+ const col = this.datamanager.getColumn(colIndex);
+
+ if ($cell) {
+ const editor = this.currentCellEditor;
+
+ if (editor) {
+ const value = editor.getValue();
+ const done = editor.setValue(value, rowIndex, col);
+ const oldValue = this.getCell(colIndex, rowIndex).content;
+
+ // update cell immediately
+ this.updateCell(colIndex, rowIndex, value);
+ $cell.focus();
+
+ if (done && done.then) {
+ // revert to oldValue if promise fails
+ done.catch((e) => {
+ console.log(e);
+ this.updateCell(colIndex, rowIndex, oldValue);
+ });
+ }
+ }
+ }
+
+ this.currentCellEditor = null;
+ }
+
+ copyCellContents($cell1, $cell2) {
+ if (!$cell2 && $cell1) {
+ // copy only focusedCell
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell1);
+ const cell = this.getCell(colIndex, rowIndex);
+ copyTextToClipboard(cell.content);
+ return;
+ }
+ const cells = this.getCellsInRange($cell1, $cell2);
+
+ if (!cells) return;
+
+ const values = cells
+ // get cell objects
+ .map(index => this.getCell(...index))
+ // convert to array of rows
+ .reduce((acc, curr) => {
+ const rowIndex = curr.rowIndex;
+
+ acc[rowIndex] = acc[rowIndex] || [];
+ acc[rowIndex].push(curr.content);
+
+ return acc;
+ }, [])
+ // join values by tab
+ .map(row => row.join('\t'))
+ // join rows by newline
+ .join('\n');
+
+ copyTextToClipboard(values);
+ }
+
+ activateFilter(colIndex) {
+ this.columnmanager.toggleFilter();
+ this.columnmanager.focusFilter(colIndex);
+
+ if (!this.columnmanager.isFilterShown) {
+ // put focus back on cell
+ this.$focusedCell.focus();
+ }
+ }
+
+ updateCell(colIndex, rowIndex, value) {
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
+ content: value
+ });
+ this.refreshCell(cell);
+ }
+
+ refreshCell(cell) {
+ const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
+ $cell.innerHTML = this.getCellContent(cell);
+ }
+
+ isStandardCell(colIndex) {
+ // Standard cells are in Sr. No and Checkbox column
+ return colIndex < this.columnmanager.getFirstColumnIndex();
+ }
+
+ getCell$(colIndex, rowIndex) {
+ return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
+ }
+
+ getAboveCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $aboveRow = $cell.parentElement.previousElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $aboveRow);
+ }
+
+ getBelowCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $belowRow = $cell.parentElement.nextElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $belowRow);
+ }
+
+ getLeftCell$($cell) {
+ return $cell.previousElementSibling;
+ }
+
+ getRightCell$($cell) {
+ return $cell.nextElementSibling;
+ }
+
+ getLeftMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
+ }
+
+ getRightMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
+ }
+
+ getTopMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
+ }
+
+ getBottomMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getCellAttr($cell) {
+ return this.instance.getCellAttr($cell);
+ }
+
+ getRowHeight() {
+ return $.style($('.data-table-row', this.bodyScrollable), 'height');
+ }
+
+ scrollToCell($cell) {
+ if ($.inViewport($cell, this.bodyScrollable)) return false;
+
+ const {
+ rowIndex
+ } = $.data($cell);
+ this.rowmanager.scrollToRow(rowIndex);
+ return false;
+ }
+
+ getRowCountPerPage() {
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
+ }
+
+ getCellHTML(cell) {
+ const {
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ } = cell;
+ const dataAttr = makeDataAttributeString({
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ });
+
+ return `
${this.getCellContent(cell)}
|
`;
- }
-
- getCellContent(cell) {
- const { isHeader } = cell;
-
- const editable = !isHeader && cell.editable !== false;
- const editCellHTML = editable ? this.getEditCellHTML() : '';
-
- const sortable = isHeader && cell.sortable !== false;
- const sortIndicator = sortable ? '' : '';
-
- const resizable = isHeader && cell.resizable !== false;
- const resizeColumn = resizable ? '' : '';
-
- const hasDropdown = isHeader && cell.dropdown !== false;
- const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
-
- let contentHTML;
- if (cell.isHeader || cell.isFilter || !cell.column.format) {
- contentHTML = cell.content;
- } else {
- contentHTML = cell.column.format(cell.content, cell);
}
- return `
+ getCellContent(cell) {
+ const {
+ isHeader
+ } = cell;
+
+ const editable = !isHeader && cell.editable !== false;
+ const editCellHTML = editable ? this.getEditCellHTML() : '';
+
+ const sortable = isHeader && cell.sortable !== false;
+ const sortIndicator = sortable ? '' : '';
+
+ const resizable = isHeader && cell.resizable !== false;
+ const resizeColumn = resizable ? '' : '';
+
+ const hasDropdown = isHeader && cell.dropdown !== false;
+ const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
+
+ let contentHTML;
+ if (cell.isHeader || cell.isFilter || !cell.column.format) {
+ contentHTML = cell.content;
+ } else {
+ contentHTML = cell.column.format(cell.content, cell);
+ }
+
+ return `
${(contentHTML)}
${sortIndicator}
@@ -2357,704 +2462,719 @@ class CellManager {
${editCellHTML}
`;
- }
+ }
- getEditCellHTML() {
- return `
+ getEditCellHTML() {
+ return `
`;
- }
+ }
- cellSelector(colIndex, rowIndex) {
- return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
- }
+ cellSelector(colIndex, rowIndex) {
+ return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
+ }
}
class RowManager {
- constructor(instance) {
- this.instance = instance;
- this.options = this.instance.options;
- this.wrapper = this.instance.wrapper;
- this.bodyScrollable = this.instance.bodyScrollable;
+ constructor(instance) {
+ this.instance = instance;
+ this.options = this.instance.options;
+ this.wrapper = this.instance.wrapper;
+ this.bodyScrollable = this.instance.bodyScrollable;
- this.bindEvents();
- this.refreshRows = promisify(this.refreshRows, this);
- }
-
- get datamanager() {
- return this.instance.datamanager;
- }
-
- get cellmanager() {
- return this.instance.cellmanager;
- }
-
- bindEvents() {
- this.bindCheckbox();
- }
-
- bindCheckbox() {
- if (!this.options.addCheckboxColumn) return;
-
- // map of checked rows
- this.checkMap = [];
-
- $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
- const $cell = $checkbox.closest('.data-table-col');
- const { rowIndex, isHeader } = $.data($cell);
- const checked = $checkbox.checked;
-
- if (isHeader) {
- this.checkAll(checked);
- } else {
- this.checkRow(rowIndex, checked);
- }
- });
- }
-
- refreshRows() {
- this.instance.renderBody();
- this.instance.setDimensions();
- }
-
- refreshRow(row, rowIndex) {
- const _row = this.datamanager.updateRow(row, rowIndex);
-
- _row.forEach(cell => {
- this.cellmanager.refreshCell(cell);
- });
- }
-
- getCheckedRows() {
- if (!this.checkMap) {
- return [];
+ this.bindEvents();
+ this.refreshRows = promisify(this.refreshRows, this);
}
- return this.checkMap
- .map((c, rowIndex) => {
- if (c) {
- return rowIndex;
+ get datamanager() {
+ return this.instance.datamanager;
+ }
+
+ get cellmanager() {
+ return this.instance.cellmanager;
+ }
+
+ bindEvents() {
+ this.bindCheckbox();
+ }
+
+ bindCheckbox() {
+ if (!this.options.addCheckboxColumn) return;
+
+ // map of checked rows
+ this.checkMap = [];
+
+ $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
+ const $cell = $checkbox.closest('.data-table-col');
+ const {
+ rowIndex,
+ isHeader
+ } = $.data($cell);
+ const checked = $checkbox.checked;
+
+ if (isHeader) {
+ this.checkAll(checked);
+ } else {
+ this.checkRow(rowIndex, checked);
+ }
+ });
+ }
+
+ refreshRows() {
+ this.instance.renderBody();
+ this.instance.setDimensions();
+ }
+
+ refreshRow(row, rowIndex) {
+ const _row = this.datamanager.updateRow(row, rowIndex);
+
+ _row.forEach(cell => {
+ this.cellmanager.refreshCell(cell);
+ });
+ }
+
+ getCheckedRows() {
+ if (!this.checkMap) {
+ return [];
}
- return null;
- })
- .filter(c => {
- return c !== null || c !== undefined;
- });
- }
- highlightCheckedRows() {
- this.getCheckedRows()
- .map(rowIndex => this.checkRow(rowIndex, true));
- }
-
- checkRow(rowIndex, toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- this.checkMap[rowIndex] = value;
- // set checkbox value explicitly
- $.each(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`, this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight row
- this.highlightRow(rowIndex, toggle);
- }
-
- checkAll(toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- if (toggle) {
- this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
- } else {
- this.checkMap = [];
- }
- // set checkbox value
- $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight all
- this.highlightAll(toggle);
- }
-
- highlightRow(rowIndex, toggle = true) {
- const $row = this.getRow$(rowIndex);
- if (!$row) return;
-
- if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
- $row.classList.add('row-unhighlight');
- return;
+ return this.checkMap
+ .map((c, rowIndex) => {
+ if (c) {
+ return rowIndex;
+ }
+ return null;
+ })
+ .filter(c => {
+ return c !== null || c !== undefined;
+ });
}
- if (toggle && $row.classList.contains('row-unhighlight')) {
- $row.classList.remove('row-unhighlight');
+ highlightCheckedRows() {
+ this.getCheckedRows()
+ .map(rowIndex => this.checkRow(rowIndex, true));
}
- this._highlightedRows = this._highlightedRows || {};
-
- if (toggle) {
- $row.classList.add('row-highlight');
- this._highlightedRows[rowIndex] = $row;
- } else {
- $row.classList.remove('row-highlight');
- delete this._highlightedRows[rowIndex];
- }
- }
-
- highlightAll(toggle = true) {
- if (toggle) {
- this.bodyScrollable.classList.add('row-highlight-all');
- } else {
- this.bodyScrollable.classList.remove('row-highlight-all');
- for (const rowIndex in this._highlightedRows) {
- const $row = this._highlightedRows[rowIndex];
- $row.classList.remove('row-highlight');
- }
- this._highlightedRows = {};
- }
- }
-
- getRow$(rowIndex) {
- return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- }
-
- getTotalRows() {
- return this.datamanager.getRowCount();
- }
-
- getFirstRowIndex() {
- return 0;
- }
-
- getLastRowIndex() {
- return this.datamanager.getRowCount() - 1;
- }
-
- scrollToRow(rowIndex) {
- rowIndex = +rowIndex;
- this._lastScrollTo = this._lastScrollTo || 0;
- const $row = this.getRow$(rowIndex);
- if ($.inViewport($row, this.bodyScrollable)) return;
-
- const { height } = $row.getBoundingClientRect();
- const { top, bottom } = this.bodyScrollable.getBoundingClientRect();
- const rowsInView = Math.floor((bottom - top) / height);
-
- let offset = 0;
- if (rowIndex > this._lastScrollTo) {
- offset = height * ((rowIndex + 1) - rowsInView);
- } else {
- offset = height * ((rowIndex + 1) - 1);
+ checkRow(rowIndex, toggle) {
+ const value = toggle ? 1 : 0;
+ const selector = rowIndex =>
+ `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
+ // update internal map
+ this.checkMap[rowIndex] = value;
+ // set checkbox value explicitly
+ $.each(selector(rowIndex), this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight row
+ this.highlightRow(rowIndex, toggle);
}
- this._lastScrollTo = rowIndex;
- $.scrollTop(this.bodyScrollable, offset);
- }
+ checkAll(toggle) {
+ const value = toggle ? 1 : 0;
- getRowHTML(row, props) {
- const dataAttr = makeDataAttributeString(props);
-
- if (props.isFilter) {
- row = row.map(cell => (Object.assign(cell, {
- content: this.getFilterInput({ colIndex: cell.colIndex }),
- isFilter: 1,
- isHeader: undefined,
- editable: false
- })));
+ // update internal map
+ if (toggle) {
+ this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
+ } else {
+ this.checkMap = [];
+ }
+ // set checkbox value
+ $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight all
+ this.highlightAll(toggle);
}
- return `
-
- ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
-
- `;
- }
+ highlightRow(rowIndex, toggle = true) {
+ const $row = this.getRow$(rowIndex);
+ if (!$row) return;
- getFilterInput(props) {
- const dataAttr = makeDataAttributeString(props);
- return ``;
- }
+ if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
+ $row.classList.add('row-unhighlight');
+ return;
+ }
+
+ if (toggle && $row.classList.contains('row-unhighlight')) {
+ $row.classList.remove('row-unhighlight');
+ }
+
+ this._highlightedRows = this._highlightedRows || {};
+
+ if (toggle) {
+ $row.classList.add('row-highlight');
+ this._highlightedRows[rowIndex] = $row;
+ } else {
+ $row.classList.remove('row-highlight');
+ delete this._highlightedRows[rowIndex];
+ }
+ }
+
+ highlightAll(toggle = true) {
+ if (toggle) {
+ this.bodyScrollable.classList.add('row-highlight-all');
+ } else {
+ this.bodyScrollable.classList.remove('row-highlight-all');
+ for (const rowIndex in this._highlightedRows) {
+ const $row = this._highlightedRows[rowIndex];
+ $row.classList.remove('row-highlight');
+ }
+ this._highlightedRows = {};
+ }
+ }
+
+ getRow$(rowIndex) {
+ return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ }
+
+ getTotalRows() {
+ return this.datamanager.getRowCount();
+ }
+
+ getFirstRowIndex() {
+ return 0;
+ }
+
+ getLastRowIndex() {
+ return this.datamanager.getRowCount() - 1;
+ }
+
+ scrollToRow(rowIndex) {
+ rowIndex = +rowIndex;
+ this._lastScrollTo = this._lastScrollTo || 0;
+ const $row = this.getRow$(rowIndex);
+ if ($.inViewport($row, this.bodyScrollable)) return;
+
+ const {
+ height
+ } = $row.getBoundingClientRect();
+ const {
+ top,
+ bottom
+ } = this.bodyScrollable.getBoundingClientRect();
+ const rowsInView = Math.floor((bottom - top) / height);
+
+ let offset = 0;
+ if (rowIndex > this._lastScrollTo) {
+ offset = height * ((rowIndex + 1) - rowsInView);
+ } else {
+ offset = height * ((rowIndex + 1) - 1);
+ }
+
+ this._lastScrollTo = rowIndex;
+ $.scrollTop(this.bodyScrollable, offset);
+ }
+
+ getRowHTML(row, props) {
+ const dataAttr = makeDataAttributeString(props);
+
+ if (props.isFilter) {
+ row = row.map(cell => (Object.assign(cell, {
+ content: this.getFilterInput({
+ colIndex: cell.colIndex
+ }),
+ isFilter: 1,
+ isHeader: undefined,
+ editable: false
+ })));
+ }
+
+ return `
+
+ ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
+
+ `;
+ }
+
+ getFilterInput(props) {
+ const dataAttr = makeDataAttributeString(props);
+ return ``;
+ }
}
class BodyRenderer {
- constructor(instance) {
- this.instance = instance;
- this.options = instance.options;
- this.datamanager = instance.datamanager;
- this.rowmanager = instance.rowmanager;
- this.cellmanager = instance.cellmanager;
- this.bodyScrollable = instance.bodyScrollable;
- this.log = instance.log;
- this.appendRemainingData = promisify(this.appendRemainingData, this);
- }
-
- render() {
- if (this.options.enableClusterize) {
- this.renderBodyWithClusterize();
- } else {
- this.renderBodyHTML();
- }
- }
-
- renderBodyHTML() {
- const rows = this.datamanager.getRows();
-
- this.bodyScrollable.innerHTML = `
-
- `;
- this.instance.setDimensions();
- this.restoreState();
- }
-
- renderBodyWithClusterize() {
- // first page
- const rows = this.datamanager.getRows(0, 20);
- const initialData = this.getDataForClusterize(rows);
-
- if (!this.clusterize) {
- // empty body
- this.bodyScrollable.innerHTML = `
-
- `;
-
- // first 20 rows will appended
- // rest of them in nextTick
- this.clusterize = new Clusterize({
- rows: initialData,
- scrollElem: this.bodyScrollable,
- contentElem: $('tbody', this.bodyScrollable),
- callbacks: {
- clusterChanged: () => {
- this.restoreState();
- }
- },
- /* eslint-disable */
- no_data_text: this.options.noDataMessage,
- no_data_class: 'empty-state'
- /* eslint-enable */
- });
-
- // setDimensions requires atleast 1 row to exist in dom
- this.instance.setDimensions();
- } else {
- this.clusterize.update(initialData);
+ constructor(instance) {
+ this.instance = instance;
+ this.options = instance.options;
+ this.datamanager = instance.datamanager;
+ this.rowmanager = instance.rowmanager;
+ this.cellmanager = instance.cellmanager;
+ this.bodyScrollable = instance.bodyScrollable;
+ this.log = instance.log;
+ this.appendRemainingData = promisify(this.appendRemainingData, this);
}
- this.appendRemainingData();
- }
+ render() {
+ if (this.options.enableClusterize) {
+ this.renderBodyWithClusterize();
+ } else {
+ this.renderBodyHTML();
+ }
+ }
- restoreState() {
- this.rowmanager.highlightCheckedRows();
- this.cellmanager.selectAreaOnClusterChanged();
- this.cellmanager.focusCellOnClusterChanged();
- }
+ renderBodyHTML() {
+ const rows = this.datamanager.getRows();
- appendRemainingData() {
- const rows = this.datamanager.getRows(20);
- const data = this.getDataForClusterize(rows);
- this.clusterize.append(data);
- }
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML(rows)}
+
+ `;
+ this.instance.setDimensions();
+ this.restoreState();
+ }
- getDataForClusterize(rows) {
- return rows.map((row) => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex }));
- }
-}
+ renderBodyWithClusterize() {
+ // first page
+ const rows = this.datamanager.getRows(0, 20);
+ const initialData = this.getDataForClusterize(rows);
-function getBodyHTML(rows) {
- return `
-
- ${rows.map(row => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
-
- `;
+ if (!this.clusterize) {
+ // empty body
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML([])}
+
+ `;
+
+ // first 20 rows will appended
+ // rest of them in nextTick
+ this.clusterize = new Clusterize({
+ rows: initialData,
+ scrollElem: this.bodyScrollable,
+ contentElem: $('tbody', this.bodyScrollable),
+ callbacks: {
+ clusterChanged: () => {
+ this.restoreState();
+ }
+ },
+ /* eslint-disable */
+ no_data_text: this.options.noDataMessage,
+ no_data_class: 'empty-state'
+ /* eslint-enable */
+ });
+
+ // setDimensions requires atleast 1 row to exist in dom
+ this.instance.setDimensions();
+ } else {
+ this.clusterize.update(initialData);
+ }
+
+ this.appendRemainingData();
+ }
+
+ restoreState() {
+ this.rowmanager.highlightCheckedRows();
+ this.cellmanager.selectAreaOnClusterChanged();
+ this.cellmanager.focusCellOnClusterChanged();
+ }
+
+ appendRemainingData() {
+ const rows = this.datamanager.getRows(20);
+ const data = this.getDataForClusterize(rows);
+ this.clusterize.append(data);
+ }
+
+ getDataForClusterize(rows) {
+ return rows.map((row) => this.rowmanager.getRowHTML(row, row.meta));
+ }
+
+ getBodyHTML(rows) {
+ return `
+
+ ${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
+
+ `;
+ }
}
class Style {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options', 'datamanager', 'columnmanager',
- 'header', 'bodyScrollable', 'getColumn'
- ]);
+ linkProperties(this, this.instance, [
+ 'options', 'datamanager', 'columnmanager',
+ 'header', 'bodyScrollable', 'getColumn'
+ ]);
- this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
- instance.datatableWrapper.classList.add(this.scopeClass);
+ this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
+ instance.datatableWrapper.classList.add(this.scopeClass);
- const styleEl = document.createElement('style');
- instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
- this.styleEl = styleEl;
- this.styleSheet = styleEl.sheet;
+ const styleEl = document.createElement('style');
+ instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
+ this.styleEl = styleEl;
+ this.styleSheet = styleEl.sheet;
- this.bindResizeWindow();
- }
+ this.bindResizeWindow();
+ }
+
+ bindResizeWindow() {
+ if (this.options.layout === 'fluid') {
+ $.on(window, 'resize', throttle$1(() => {
+ this.distributeRemainingWidth();
+ this.refreshColumnWidth();
+ this.setBodyStyle();
+ }, 300));
+ }
+ }
+
+ destroy() {
+ this.styleEl.remove();
+ }
+
+ setStyle(selector, styleMap, index = -1) {
+ const styles = Object.keys(styleMap)
+ .map(prop => {
+ if (!prop.includes('-')) {
+ prop = camelCaseToDash(prop);
+ }
+ return `${prop}:${styleMap[prop]};`;
+ })
+ .join('');
+ let prefixedSelector = selector
+ .split(',')
+ .map(r => `.${this.scopeClass} ${r}`)
+ .join(',');
+
+ let ruleString = `${prefixedSelector} { ${styles} }`;
+
+ let _index = this.styleSheet.cssRules.length;
+ if (index !== -1) {
+ this.styleSheet.deleteRule(index);
+ _index = index;
+ }
+
+ this.styleSheet.insertRule(ruleString, _index);
+ return _index;
+ }
+
+ setDimensions() {
+ this.setHeaderStyle();
+
+ this.setupMinWidth();
+ this.setupNaturalColumnWidth();
+ this.setupColumnWidth();
- bindResizeWindow() {
- if (this.options.layout === 'fluid') {
- $.on(window, 'resize', throttle$1(() => {
this.distributeRemainingWidth();
- this.refreshColumnWidth();
+ this.setColumnStyle();
+ this.setDefaultCellHeight();
this.setBodyStyle();
- }, 300));
- }
- }
-
- destroy() {
- this.styleEl.remove();
- }
-
- setStyle(rule, styleMap, index = -1) {
- const styles = Object.keys(styleMap)
- .map(prop => {
- if (!prop.includes('-')) {
- prop = camelCaseToDash(prop);
- }
- return `${prop}:${styleMap[prop]};`;
- })
- .join('');
- let ruleString = `.${this.scopeClass} ${rule} { ${styles} }`;
-
- let _index = this.styleSheet.cssRules.length;
- if (index !== -1) {
- this.styleSheet.deleteRule(index);
- _index = index;
}
- this.styleSheet.insertRule(ruleString, _index);
- return _index;
- }
+ setHeaderStyle() {
+ if (this.options.layout === 'fluid') {
+ // setting width as 0 will ensure that the
+ // header doesn't take the available space
+ $.style(this.header, {
+ width: 0
+ });
+ }
- setDimensions() {
- this.setHeaderStyle();
+ $.style(this.header, {
+ margin: 0
+ });
- this.setupMinWidth();
- this.setupNaturalColumnWidth();
- this.setupColumnWidth();
+ // don't show resize cursor on nonResizable columns
+ const nonResizableColumnsSelector = this.datamanager.getColumns()
+ .filter(col => col.resizable === false)
+ .map(col => col.colIndex)
+ .map(i => `.data-table-header [data-col-index="${i}"]`)
+ .join();
- this.distributeRemainingWidth();
- this.setColumnStyle();
- this.setDefaultCellHeight();
- this.setBodyStyle();
- }
-
- setHeaderStyle() {
- if (this.options.layout === 'fluid') {
- // setting width as 0 will ensure that the
- // header doesn't take the available space
- $.style(this.header, {
- width: 0
- });
+ this.setStyle(nonResizableColumnsSelector, {
+ cursor: 'pointer'
+ });
}
- $.style(this.header, {
- margin: 0
- });
+ setupMinWidth() {
+ $.each('.data-table-col[data-is-header]', this.header).map(col => {
+ const width = $.style($('.content', col), 'width');
+ const {
+ colIndex
+ } = $.data(col);
+ const column = this.getColumn(colIndex);
- // don't show resize cursor on nonResizable columns
- const nonResizableColumnsSelector = this.datamanager.getColumns()
- .filter(col => col.resizable === false)
- .map(col => col.colIndex)
- .map(i => `.data-table-header [data-col-index="${i}"]`)
- .join();
-
- this.setStyle(nonResizableColumnsSelector, {
- cursor: 'pointer'
- });
- }
-
- setupMinWidth() {
- $.each('.data-table-col[data-is-header]', this.header).map(col => {
- const width = $.style($('.content', col), 'width');
- const {
- colIndex
- } = $.data(col);
- const column = this.getColumn(colIndex);
-
- if (!column.minWidth) {
- // only set this once
- column.minWidth = width;
- }
- });
- }
-
- setupNaturalColumnWidth() {
- if (!$('.data-table-row')) return;
-
- // set initial width as naturally calculated by table's first row
- $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
- const {
- colIndex
- } = $.data($cell);
- const column = this.datamanager.getColumn(colIndex);
-
- let naturalWidth = $.style($('.content', $cell), 'width');
-
- if (column.id === '_rowIndex') {
- // width based on rowCount
- const rowCount = this.datamanager.getRowCount();
- const digits = (rowCount + '').length;
- if (digits > 2) {
- naturalWidth = naturalWidth + ((digits - 2) * 8);
- }
- }
-
- column.naturalWidth = naturalWidth;
- });
- }
-
- setupColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- if (!column.width) {
- column.width = column.naturalWidth;
- }
- if (column.width < column.minWidth) {
- column.width = column.minWidth;
- }
- });
- }
-
- distributeRemainingWidth() {
- if (this.options.layout !== 'fluid') return;
-
- const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
- const headerWidth = $.style(this.header, 'width');
- const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
- const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
-
- resizableColumns.map(col => {
- const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
- let finalWidth = Math.floor(width + deltaWidth) - 2;
-
- this.datamanager.updateColumn(col.colIndex, {
- width: finalWidth
- });
- });
- }
-
- setDefaultCellHeight() {
- if (this.__cellHeightSet) return;
- const height = this.options.cellHeight || $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
- if (height) {
- this.setCellHeight(height);
- this.__cellHeightSet = true;
+ if (!column.minWidth) {
+ // only set this once
+ column.minWidth = width;
+ }
+ });
}
- }
- setCellHeight(height) {
- this.setStyle('.data-table-col .content', {
- height: height + 'px'
- });
- this.setStyle('.data-table-col .edit-cell', {
- height: height + 'px'
- });
- }
+ setupNaturalColumnWidth() {
+ if (!$('.data-table-row')) return;
- setColumnStyle() {
- // align columns
- this.datamanager.getColumns()
- .map(column => {
- // alignment
- if (['left', 'center', 'right'].includes(column.align)) {
- this.setStyle(`[data-col-index="${column.colIndex}"]`, {
- 'text-align': column.align
- });
+ // set initial width as naturally calculated by table's first row
+ $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
+ const {
+ colIndex
+ } = $.data($cell);
+ const column = this.datamanager.getColumn(colIndex);
+
+ let naturalWidth = $.style($('.content', $cell), 'width');
+
+ if (column.id === '_rowIndex') {
+ // width based on rowCount
+ const rowCount = this.datamanager.getRowCount();
+ const digits = (rowCount + '').length;
+ if (digits > 1) {
+ naturalWidth = naturalWidth + ((digits - 1) * 8);
+ }
+ }
+
+ column.naturalWidth = naturalWidth;
+ });
+ }
+
+ setupColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ if (!column.width) {
+ column.width = column.naturalWidth;
+ }
+ if (column.width < column.minWidth) {
+ column.width = column.minWidth;
+ }
+ });
+ }
+
+ distributeRemainingWidth() {
+ if (this.options.layout !== 'fluid') return;
+
+ const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
+ const headerWidth = $.style(this.header, 'width');
+ const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
+ const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
+
+ resizableColumns.map(col => {
+ const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
+ let finalWidth = Math.floor(width + deltaWidth) - 2;
+
+ this.datamanager.updateColumn(col.colIndex, {
+ width: finalWidth
+ });
+ });
+ }
+
+ setDefaultCellHeight() {
+ if (this.__cellHeightSet) return;
+ const height = this.options.cellHeight ||
+ $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
+ if (height) {
+ this.setCellHeight(height);
+ this.__cellHeightSet = true;
}
- // width
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- this.setBodyStyle();
- }
+ }
- refreshColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- }
+ setCellHeight(height) {
+ this.setStyle('.data-table-col .content', {
+ height: height + 'px'
+ });
+ this.setStyle('.data-table-col .edit-cell', {
+ height: height + 'px'
+ });
+ }
- setBodyStyle() {
- const width = $.style(this.header, 'width');
+ setColumnStyle() {
+ // align columns
+ this.datamanager.getColumns()
+ .map(column => {
+ // alignment
+ if (['left', 'center', 'right'].includes(column.align)) {
+ this.setStyle(`[data-col-index="${column.colIndex}"]`, {
+ 'text-align': column.align
+ });
+ }
+ // width
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ this.setBodyStyle();
+ }
- $.style(this.bodyScrollable, {
- width: width + 'px'
- });
+ refreshColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ }
- $.style(this.bodyScrollable, {
- marginTop: $.style(this.header, 'height') + 'px'
- });
+ setBodyStyle() {
+ const width = $.style(this.header, 'width');
- $.style($('table', this.bodyScrollable), {
- margin: 0
- });
- }
+ $.style(this.bodyScrollable, {
+ width: width + 'px'
+ });
- getColumnHeaderElement(colIndex) {
- colIndex = +colIndex;
- if (colIndex < 0) return null;
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.style(this.bodyScrollable, {
+ marginTop: $.style(this.header, 'height') + 'px'
+ });
+
+ $.style($('table', this.bodyScrollable), {
+ margin: 0
+ });
+ }
+
+ getColumnHeaderElement(colIndex) {
+ colIndex = +colIndex;
+ if (colIndex < 0) return null;
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
}
const KEYCODES = {
- 13: 'enter',
- 91: 'meta',
- 16: 'shift',
- 17: 'ctrl',
- 18: 'alt',
- 37: 'left',
- 38: 'up',
- 39: 'right',
- 40: 'down',
- 9: 'tab',
- 27: 'esc',
- 67: 'c',
- 70: 'f'
+ 13: 'enter',
+ 91: 'meta',
+ 16: 'shift',
+ 17: 'ctrl',
+ 18: 'alt',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 9: 'tab',
+ 27: 'esc',
+ 67: 'c',
+ 70: 'f'
};
class Keyboard {
- constructor(element) {
- this.listeners = {};
- $.on(element, 'keydown', this.handler.bind(this));
- }
-
- handler(e) {
- let key = KEYCODES[e.keyCode];
-
- if (e.shiftKey && key !== 'shift') {
- key = 'shift+' + key;
+ constructor(element) {
+ this.listeners = {};
+ $.on(element, 'keydown', this.handler.bind(this));
}
- if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
- key = 'ctrl+' + key;
- }
+ handler(e) {
+ let key = KEYCODES[e.keyCode];
- const listeners = this.listeners[key];
-
- if (listeners && listeners.length > 0) {
- for (let listener of listeners) {
- const preventBubbling = listener(e);
- if (preventBubbling === undefined || preventBubbling === true) {
- e.preventDefault();
+ if (e.shiftKey && key !== 'shift') {
+ key = 'shift+' + key;
+ }
+
+ if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
+ key = 'ctrl+' + key;
+ }
+
+ const listeners = this.listeners[key];
+
+ if (listeners && listeners.length > 0) {
+ for (let listener of listeners) {
+ const preventBubbling = listener(e);
+ if (preventBubbling === undefined || preventBubbling === true) {
+ e.preventDefault();
+ }
+ }
}
- }
}
- }
- on(key, listener) {
- const keys = key.split(',').map(k => k.trim());
+ on(key, listener) {
+ const keys = key.split(',').map(k => k.trim());
- keys.map(key => {
- this.listeners[key] = this.listeners[key] || [];
- this.listeners[key].push(listener);
- });
- }
+ keys.map(key => {
+ this.listeners[key] = this.listeners[key] || [];
+ this.listeners[key].push(listener);
+ });
+ }
}
var DEFAULT_OPTIONS = {
- columns: [],
- data: [],
- dropdownButton: '▼',
- headerDropdown: [
- {
- label: 'Sort Ascending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'asc');
- }
+ columns: [],
+ data: [],
+ dropdownButton: '▼',
+ headerDropdown: [
+ {
+ label: 'Sort Ascending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'asc');
+ }
+ },
+ {
+ label: 'Sort Descending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'desc');
+ }
+ },
+ {
+ label: 'Reset sorting',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'none');
+ }
+ },
+ {
+ label: 'Remove column',
+ action: function (column) {
+ this.removeColumn(column.colIndex);
+ }
+ }
+ ],
+ events: {
+ onRemoveColumn(column) {},
+ onSwitchColumn(column1, column2) {},
+ onSortColumn(column) {}
},
- {
- label: 'Sort Descending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'desc');
- }
+ sortIndicator: {
+ asc: '↑',
+ desc: '↓',
+ none: ''
},
- {
- label: 'Reset sorting',
- action: function (column) {
- this.sortColumn(column.colIndex, 'none');
- }
- },
- {
- label: 'Remove column',
- action: function (column) {
- this.removeColumn(column.colIndex);
- }
- }
- ],
- events: {
- onRemoveColumn(column) {},
- onSwitchColumn(column1, column2) {},
- onSortColumn(column) {}
- },
- sortIndicator: {
- asc: '↑',
- desc: '↓',
- none: ''
- },
- freezeMessage: '',
- getEditor: () => {},
- addSerialNoColumn: true,
- addCheckboxColumn: false,
- enableClusterize: true,
- enableLogs: false,
- layout: 'fixed', // fixed, fluid
- noDataMessage: 'No Data',
- cellHeight: null,
- enableInlineFilters: false
+ freezeMessage: '',
+ getEditor: () => {},
+ addSerialNoColumn: true,
+ addCheckboxColumn: false,
+ enableClusterize: true,
+ enableLogs: false,
+ layout: 'fixed', // fixed, fluid
+ noDataMessage: 'No Data',
+ cellHeight: null,
+ enableInlineFilters: false
};
class DataTable {
- constructor(wrapper, options) {
- DataTable.instances++;
+ constructor(wrapper, options) {
+ DataTable.instances++;
- if (typeof wrapper === 'string') {
- // css selector
- wrapper = document.querySelector(wrapper);
- }
- this.wrapper = wrapper;
- if (!(this.wrapper instanceof HTMLElement)) {
- throw new Error('Invalid argument given for `wrapper`');
+ if (typeof wrapper === 'string') {
+ // css selector
+ wrapper = document.querySelector(wrapper);
+ }
+ this.wrapper = wrapper;
+ if (!(this.wrapper instanceof HTMLElement)) {
+ throw new Error('Invalid argument given for `wrapper`');
+ }
+
+ this.options = Object.assign({}, DEFAULT_OPTIONS, options);
+ this.options.headerDropdown =
+ DEFAULT_OPTIONS.headerDropdown
+ .concat(options.headerDropdown || []);
+ // custom user events
+ this.events = Object.assign({}, DEFAULT_OPTIONS.events, options.events || {});
+ this.fireEvent = this.fireEvent.bind(this);
+
+ this.prepare();
+
+ this.style = new Style(this);
+ this.keyboard = new Keyboard(this.wrapper);
+ this.datamanager = new DataManager(this.options);
+ this.rowmanager = new RowManager(this);
+ this.columnmanager = new ColumnManager(this);
+ this.cellmanager = new CellManager(this);
+ this.bodyRenderer = new BodyRenderer(this);
+
+ if (this.options.data) {
+ this.refresh();
+ }
}
- this.options = Object.assign({}, DEFAULT_OPTIONS, options);
- this.options.headerDropdown =
- DEFAULT_OPTIONS.headerDropdown
- .concat(options.headerDropdown || []);
- // custom user events
- this.events = Object.assign(
- {}, DEFAULT_OPTIONS.events, options.events || {}
- );
- this.fireEvent = this.fireEvent.bind(this);
-
- this.prepare();
-
- this.style = new Style(this);
- this.keyboard = new Keyboard(this.wrapper);
- this.datamanager = new DataManager(this.options);
- this.rowmanager = new RowManager(this);
- this.columnmanager = new ColumnManager(this);
- this.cellmanager = new CellManager(this);
- this.bodyRenderer = new BodyRenderer(this);
-
- if (this.options.data) {
- this.refresh();
+ prepare() {
+ this.prepareDom();
+ this.unfreeze();
}
- }
- prepare() {
- this.prepareDom();
- this.unfreeze();
- }
-
- prepareDom() {
- this.wrapper.innerHTML = `
+ prepareDom() {
+ this.wrapper.innerHTML = `
@@ -3068,110 +3188,110 @@ class DataTable {
`;
- this.datatableWrapper = $('.data-table', this.wrapper);
- this.header = $('.data-table-header', this.wrapper);
- this.bodyScrollable = $('.body-scrollable', this.wrapper);
- this.freezeContainer = $('.freeze-container', this.wrapper);
- }
-
- refresh(data) {
- this.datamanager.init(data);
- this.render();
- this.setDimensions();
- }
-
- destroy() {
- this.wrapper.innerHTML = '';
- this.style.destroy();
- }
-
- appendRows(rows) {
- this.datamanager.appendRows(rows);
- this.rowmanager.refreshRows();
- }
-
- refreshRow(row, rowIndex) {
- this.rowmanager.refreshRow(row, rowIndex);
- }
-
- render() {
- this.renderHeader();
- this.renderBody();
- }
-
- renderHeader() {
- this.columnmanager.renderHeader();
- }
-
- renderBody() {
- this.bodyRenderer.render();
- }
-
- setDimensions() {
- this.style.setDimensions();
- }
-
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
-
- getColumns() {
- return this.datamanager.getColumns();
- }
-
- getRows() {
- return this.datamanager.getRows();
- }
-
- getCell(colIndex, rowIndex) {
- return this.datamanager.getCell(colIndex, rowIndex);
- }
-
- getColumnHeaderElement(colIndex) {
- return this.columnmanager.getColumnHeaderElement(colIndex);
- }
-
- getViewportHeight() {
- if (!this.viewportHeight) {
- this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ this.datatableWrapper = $('.data-table', this.wrapper);
+ this.header = $('.data-table-header', this.wrapper);
+ this.bodyScrollable = $('.body-scrollable', this.wrapper);
+ this.freezeContainer = $('.freeze-container', this.wrapper);
}
- return this.viewportHeight;
- }
-
- sortColumn(colIndex, sortOrder) {
- this.columnmanager.sortColumn(colIndex, sortOrder);
- }
-
- removeColumn(colIndex) {
- this.columnmanager.removeColumn(colIndex);
- }
-
- scrollToLastColumn() {
- this.datatableWrapper.scrollLeft = 9999;
- }
-
- freeze() {
- $.style(this.freezeContainer, {
- display: ''
- });
- }
-
- unfreeze() {
- $.style(this.freezeContainer, {
- display: 'none'
- });
- }
-
- fireEvent(eventName, ...args) {
- this.events[eventName].apply(this, args);
- }
-
- log() {
- if (this.options.enableLogs) {
- console.log.apply(console, arguments);
+ refresh(data) {
+ this.datamanager.init(data);
+ this.render();
+ this.setDimensions();
+ }
+
+ destroy() {
+ this.wrapper.innerHTML = '';
+ this.style.destroy();
+ }
+
+ appendRows(rows) {
+ this.datamanager.appendRows(rows);
+ this.rowmanager.refreshRows();
+ }
+
+ refreshRow(row, rowIndex) {
+ this.rowmanager.refreshRow(row, rowIndex);
+ }
+
+ render() {
+ this.renderHeader();
+ this.renderBody();
+ }
+
+ renderHeader() {
+ this.columnmanager.renderHeader();
+ }
+
+ renderBody() {
+ this.bodyRenderer.render();
+ }
+
+ setDimensions() {
+ this.style.setDimensions();
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ getRows() {
+ return this.datamanager.getRows();
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getColumnHeaderElement(colIndex) {
+ return this.columnmanager.getColumnHeaderElement(colIndex);
+ }
+
+ getViewportHeight() {
+ if (!this.viewportHeight) {
+ this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ }
+
+ return this.viewportHeight;
+ }
+
+ sortColumn(colIndex, sortOrder) {
+ this.columnmanager.sortColumn(colIndex, sortOrder);
+ }
+
+ removeColumn(colIndex) {
+ this.columnmanager.removeColumn(colIndex);
+ }
+
+ scrollToLastColumn() {
+ this.datatableWrapper.scrollLeft = 9999;
+ }
+
+ freeze() {
+ $.style(this.freezeContainer, {
+ display: ''
+ });
+ }
+
+ unfreeze() {
+ $.style(this.freezeContainer, {
+ display: 'none'
+ });
+ }
+
+ fireEvent(eventName, ...args) {
+ this.events[eventName].apply(this, args);
+ }
+
+ log() {
+ if (this.options.enableLogs) {
+ console.log.apply(console, arguments);
+ }
}
- }
}
DataTable.instances = 0;
diff --git a/docs/frappe-datatable.css b/docs/frappe-datatable.css
new file mode 100644
index 0000000..30dd637
--- /dev/null
+++ b/docs/frappe-datatable.css
@@ -0,0 +1,255 @@
+/* This file is processed by postcss */
+/* variables */
+
+.data-table {
+
+ /* styling */
+ width: 100%;
+ position: relative;
+ overflow: auto;
+}
+
+/* resets */
+
+.data-table *, .data-table *::after, .data-table *::before {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+
+.data-table button, .data-table input {
+ overflow: visible;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ margin: 0;
+ padding: 0;
+ }
+
+.data-table .input-style {
+ outline: none;
+ width: 100%;
+ border: none;
+ }
+
+.data-table *, .data-table *:focus {
+ outline: none;
+ border-radius: 0px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+
+.data-table table {
+ border-collapse: collapse;
+ }
+
+.data-table table td {
+ padding: 0;
+ border: 1px solid #d1d8dd;
+ }
+
+.data-table thead td {
+ border-bottom-width: 1px;
+ }
+
+.data-table .freeze-container {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -ms-flex-line-pack: center;
+ align-content: center;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ background-color: #f5f7fa;
+ opacity: 0.5;
+ font-size: 2em;
+ }
+
+.data-table .freeze-container span {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ }
+
+.data-table .trash-container {
+ position: absolute;
+ bottom: 0;
+ left: 30%;
+ right: 30%;
+ height: 70px;
+ background: palevioletred;
+ opacity: 0.5;
+ }
+
+.data-table .hide {
+ display: none;
+ }
+
+.body-scrollable {
+ max-height: 500px;
+ overflow: auto;
+ border-bottom: 1px solid #d1d8dd;
+}
+
+.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) {
+ background-color: #f5f7fa;
+ }
+
+.data-table-header {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: white;
+ font-weight: bold;
+}
+
+.data-table-header .content span:not(.column-resizer) {
+ cursor: pointer;
+ }
+
+.data-table-header .column-resizer {
+ display: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 4px;
+ width: 0.25rem;
+ height: 100%;
+ background-color: rgb(82, 146, 247);
+ cursor: col-resize;
+ }
+
+.data-table-header .data-table-dropdown {
+ position: absolute;
+ right: 10px;
+ display: -webkit-inline-box;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ vertical-align: top;
+ text-align: left;
+ }
+
+.data-table-header .data-table-dropdown.is-active .data-table-dropdown-list {
+ display: block;
+ }
+
+.data-table-header .data-table-dropdown.is-active .data-table-dropdown-toggle {
+ display: block;
+ }
+
+.data-table-header .data-table-dropdown-toggle {
+ display: none;
+ background-color: transparent;
+ border: none;
+ }
+
+.data-table-header .data-table-dropdown-list {
+ display: none;
+ font-weight: normal;
+
+ position: absolute;
+ min-width: 128px;
+ min-width: 8rem;
+ top: 100%;
+ right: 0;
+ z-index: 1;
+ background-color: white;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
+ box-shadow: 0 2px 3px rgba(10, 10, 10, .1), 0 0 0 1px rgba(10, 10, 10, .1);
+ padding-bottom: 8px;
+ padding-bottom: 0.5rem;
+ padding-top: 8px;
+ padding-top: 0.5rem;
+ }
+
+.data-table-header .data-table-dropdown-list> div {
+ padding: 8px 16px;
+ padding: 0.5rem 1rem;
+ }
+
+.data-table-header .data-table-dropdown-list> div:hover {
+ background-color: #f5f7fa;
+ }
+
+.data-table-header .data-table-col.remove-column {
+ background-color: #FD8B8B;
+ -webkit-transition: 300ms background-color ease-in-out;
+ transition: 300ms background-color ease-in-out;
+ }
+
+.data-table-header .data-table-col.sortable-chosen {
+ background-color: #f5f7fa;
+ }
+
+.data-table-col {
+ position: relative;
+}
+
+.data-table-col .content {
+ padding: 8px;
+ padding: 0.5rem;
+ border: 2px solid transparent;
+ }
+
+.data-table-col .content.ellipsis {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+.data-table-col .edit-cell {
+ display: none;
+ padding: 8px;
+ padding: 0.5rem;
+ background: #fff;
+ z-index: 1;
+ height: 100%;
+ }
+
+.data-table-col.selected .content {
+ border: 2px solid rgb(82, 146, 247);
+ }
+
+.data-table-col.editing .content {
+ display: none;
+ }
+
+.data-table-col.editing .edit-cell {
+ border: 2px solid rgb(82, 146, 247);
+ display: block;
+ }
+
+.data-table-col.highlight {
+ background-color: #f5f7fa;
+ }
+
+.data-table-col:hover .column-resizer {
+ display: inline-block;
+ }
+
+.data-table-col:hover .data-table-dropdown-toggle {
+ display: block;
+ }
+
+.data-table-row.row-highlight {
+ background-color: #f5f7fa;
+ }
+
+.noselect {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+body.data-table-resize {
+ cursor: col-resize;
+}
\ No newline at end of file
diff --git a/docs/frappe-datatable.js b/docs/frappe-datatable.js
new file mode 100644
index 0000000..fab99f7
--- /dev/null
+++ b/docs/frappe-datatable.js
@@ -0,0 +1,3332 @@
+var DataTable = (function (Sortable,Clusterize) {
+'use strict';
+
+Sortable = Sortable && Sortable.hasOwnProperty('default') ? Sortable['default'] : Sortable;
+Clusterize = Clusterize && Clusterize.hasOwnProperty('default') ? Clusterize['default'] : Clusterize;
+
+function $(expr, con) {
+ return typeof expr === 'string' ?
+ (con || document).querySelector(expr) :
+ expr || null;
+}
+
+$.each = (expr, con) => {
+ return typeof expr === 'string' ?
+ Array.from((con || document).querySelectorAll(expr)) :
+ expr || null;
+};
+
+$.create = (tag, o) => {
+ let element = document.createElement(tag);
+
+ for (let i in o) {
+ let val = o[i];
+
+ if (i === 'inside') {
+ $(val).appendChild(element);
+ } else
+ if (i === 'around') {
+ let ref = $(val);
+ ref.parentNode.insertBefore(element, ref);
+ element.appendChild(ref);
+ } else
+ if (i === 'styles') {
+ if (typeof val === 'object') {
+ Object.keys(val).map(prop => {
+ element.style[prop] = val[prop];
+ });
+ }
+ } else
+ if (i in element) {
+ element[i] = val;
+ } else {
+ element.setAttribute(i, val);
+ }
+ }
+
+ return element;
+};
+
+$.on = (element, event, selector, callback) => {
+ if (!callback) {
+ callback = selector;
+ $.bind(element, event, callback);
+ } else {
+ $.delegate(element, event, selector, callback);
+ }
+};
+
+$.off = (element, event, handler) => {
+ element.removeEventListener(event, handler);
+};
+
+$.bind = (element, event, callback) => {
+ event.split(/\s+/).forEach(function (event) {
+ element.addEventListener(event, callback);
+ });
+};
+
+$.delegate = (element, event, selector, callback) => {
+ element.addEventListener(event, function (e) {
+ const delegatedTarget = e.target.closest(selector);
+ if (delegatedTarget) {
+ e.delegatedTarget = delegatedTarget;
+ callback.call(this, e, delegatedTarget);
+ }
+ });
+};
+
+$.unbind = (element, o) => {
+ if (element) {
+ for (let event in o) {
+ let callback = o[event];
+
+ event.split(/\s+/).forEach(function (event) {
+ element.removeEventListener(event, callback);
+ });
+ }
+ }
+};
+
+$.fire = (target, type, properties) => {
+ let evt = document.createEvent('HTMLEvents');
+
+ evt.initEvent(type, true, true);
+
+ for (let j in properties) {
+ evt[j] = properties[j];
+ }
+
+ return target.dispatchEvent(evt);
+};
+
+$.data = (element, attrs) => { // eslint-disable-line
+ if (!attrs) {
+ return element.dataset;
+ }
+
+ for (const attr in attrs) {
+ element.dataset[attr] = attrs[attr];
+ }
+};
+
+$.style = (elements, styleMap) => { // eslint-disable-line
+
+ if (typeof styleMap === 'string') {
+ return $.getStyle(elements, styleMap);
+ }
+
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ elements.map(element => {
+ for (const prop in styleMap) {
+ element.style[prop] = styleMap[prop];
+ }
+ });
+};
+
+$.removeStyle = (elements, styleProps) => {
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ if (!Array.isArray(styleProps)) {
+ styleProps = [styleProps];
+ }
+
+ elements.map(element => {
+ for (const prop of styleProps) {
+ element.style[prop] = '';
+ }
+ });
+};
+
+$.getStyle = (element, prop) => {
+ let val = getComputedStyle(element)[prop];
+
+ if (['width', 'height'].includes(prop)) {
+ val = parseFloat(val);
+ }
+
+ return val;
+};
+
+$.closest = (selector, element) => {
+ if (!element) return null;
+
+ if (element.matches(selector)) {
+ return element;
+ }
+
+ return $.closest(selector, element.parentNode);
+};
+
+$.inViewport = (el, parentEl) => {
+ const {
+ top,
+ left,
+ bottom,
+ right
+ } = el.getBoundingClientRect();
+ const {
+ top: pTop,
+ left: pLeft,
+ bottom: pBottom,
+ right: pRight
+ } = parentEl.getBoundingClientRect();
+
+ return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
+};
+
+$.scrollTop = function scrollTop(element, pixels) {
+ requestAnimationFrame(() => {
+ element.scrollTop = pixels;
+ });
+};
+
+/**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+ var type = typeof value;
+ return value != null && (type == 'object' || type == 'function');
+}
+
+var isObject_1 = isObject;
+
+var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+/** Detect free variable `global` from Node.js. */
+var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
+
+var _freeGlobal = freeGlobal;
+
+/** Detect free variable `self`. */
+var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+/** Used as a reference to the global object. */
+var root = _freeGlobal || freeSelf || Function('return this')();
+
+var _root = root;
+
+/**
+ * Gets the timestamp of the number of milliseconds that have elapsed since
+ * the Unix epoch (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Date
+ * @returns {number} Returns the timestamp.
+ * @example
+ *
+ * _.defer(function(stamp) {
+ * console.log(_.now() - stamp);
+ * }, _.now());
+ * // => Logs the number of milliseconds it took for the deferred invocation.
+ */
+var now = function() {
+ return _root.Date.now();
+};
+
+var now_1 = now;
+
+/** Built-in value references. */
+var Symbol = _root.Symbol;
+
+var _Symbol = Symbol;
+
+/** Used for built-in method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var nativeObjectToString = objectProto.toString;
+
+/** Built-in value references. */
+var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined;
+
+/**
+ * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the raw `toStringTag`.
+ */
+function getRawTag(value) {
+ var isOwn = hasOwnProperty.call(value, symToStringTag),
+ tag = value[symToStringTag];
+
+ try {
+ value[symToStringTag] = undefined;
+ var unmasked = true;
+ } catch (e) {}
+
+ var result = nativeObjectToString.call(value);
+ if (unmasked) {
+ if (isOwn) {
+ value[symToStringTag] = tag;
+ } else {
+ delete value[symToStringTag];
+ }
+ }
+ return result;
+}
+
+var _getRawTag = getRawTag;
+
+/** Used for built-in method references. */
+var objectProto$1 = Object.prototype;
+
+/**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var nativeObjectToString$1 = objectProto$1.toString;
+
+/**
+ * Converts `value` to a string using `Object.prototype.toString`.
+ *
+ * @private
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ */
+function objectToString(value) {
+ return nativeObjectToString$1.call(value);
+}
+
+var _objectToString = objectToString;
+
+/** `Object#toString` result references. */
+var nullTag = '[object Null]';
+var undefinedTag = '[object Undefined]';
+
+/** Built-in value references. */
+var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined;
+
+/**
+ * The base implementation of `getTag` without fallbacks for buggy environments.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+function baseGetTag(value) {
+ if (value == null) {
+ return value === undefined ? undefinedTag : nullTag;
+ }
+ return (symToStringTag$1 && symToStringTag$1 in Object(value))
+ ? _getRawTag(value)
+ : _objectToString(value);
+}
+
+var _baseGetTag = baseGetTag;
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+ return value != null && typeof value == 'object';
+}
+
+var isObjectLike_1 = isObjectLike;
+
+/** `Object#toString` result references. */
+var symbolTag = '[object Symbol]';
+
+/**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+function isSymbol(value) {
+ return typeof value == 'symbol' ||
+ (isObjectLike_1(value) && _baseGetTag(value) == symbolTag);
+}
+
+var isSymbol_1 = isSymbol;
+
+/** Used as references for various `Number` constants. */
+var NAN = 0 / 0;
+
+/** Used to match leading and trailing whitespace. */
+var reTrim = /^\s+|\s+$/g;
+
+/** Used to detect bad signed hexadecimal string values. */
+var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+/** Used to detect binary string values. */
+var reIsBinary = /^0b[01]+$/i;
+
+/** Used to detect octal string values. */
+var reIsOctal = /^0o[0-7]+$/i;
+
+/** Built-in method references without a dependency on `root`. */
+var freeParseInt = parseInt;
+
+/**
+ * Converts `value` to a number.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ * @example
+ *
+ * _.toNumber(3.2);
+ * // => 3.2
+ *
+ * _.toNumber(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toNumber(Infinity);
+ * // => Infinity
+ *
+ * _.toNumber('3.2');
+ * // => 3.2
+ */
+function toNumber(value) {
+ if (typeof value == 'number') {
+ return value;
+ }
+ if (isSymbol_1(value)) {
+ return NAN;
+ }
+ if (isObject_1(value)) {
+ var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+ value = isObject_1(other) ? (other + '') : other;
+ }
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
+ }
+ value = value.replace(reTrim, '');
+ var isBinary = reIsBinary.test(value);
+ return (isBinary || reIsOctal.test(value))
+ ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+ : (reIsBadHex.test(value) ? NAN : +value);
+}
+
+var toNumber_1 = toNumber;
+
+/** Error message constants. */
+var FUNC_ERROR_TEXT = 'Expected a function';
+
+/* Built-in method references for those with the same name as other `lodash` methods. */
+var nativeMax = Math.max;
+var nativeMin = Math.min;
+
+/**
+ * Creates a debounced function that delays invoking `func` until after `wait`
+ * milliseconds have elapsed since the last time the debounced function was
+ * invoked. The debounced function comes with a `cancel` method to cancel
+ * delayed `func` invocations and a `flush` method to immediately invoke them.
+ * Provide `options` to indicate whether `func` should be invoked on the
+ * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+ * with the last arguments provided to the debounced function. Subsequent
+ * calls to the debounced function return the result of the last `func`
+ * invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the debounced function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.debounce` and `_.throttle`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to debounce.
+ * @param {number} [wait=0] The number of milliseconds to delay.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=false]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {number} [options.maxWait]
+ * The maximum time `func` is allowed to be delayed before it's invoked.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // Avoid costly calculations while the window size is in flux.
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+ *
+ * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+ * jQuery(element).on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * }));
+ *
+ * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+ * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+ * var source = new EventSource('/stream');
+ * jQuery(source).on('message', debounced);
+ *
+ * // Cancel the trailing debounced invocation.
+ * jQuery(window).on('popstate', debounced.cancel);
+ */
+function debounce(func, wait, options) {
+ var lastArgs,
+ lastThis,
+ maxWait,
+ result,
+ timerId,
+ lastCallTime,
+ lastInvokeTime = 0,
+ leading = false,
+ maxing = false,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ wait = toNumber_1(wait) || 0;
+ if (isObject_1(options)) {
+ leading = !!options.leading;
+ maxing = 'maxWait' in options;
+ maxWait = maxing ? nativeMax(toNumber_1(options.maxWait) || 0, wait) : maxWait;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+
+ function invokeFunc(time) {
+ var args = lastArgs,
+ thisArg = lastThis;
+
+ lastArgs = lastThis = undefined;
+ lastInvokeTime = time;
+ result = func.apply(thisArg, args);
+ return result;
+ }
+
+ function leadingEdge(time) {
+ // Reset any `maxWait` timer.
+ lastInvokeTime = time;
+ // Start the timer for the trailing edge.
+ timerId = setTimeout(timerExpired, wait);
+ // Invoke the leading edge.
+ return leading ? invokeFunc(time) : result;
+ }
+
+ function remainingWait(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime,
+ timeWaiting = wait - timeSinceLastCall;
+
+ return maxing
+ ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
+ : timeWaiting;
+ }
+
+ function shouldInvoke(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime;
+
+ // Either this is the first call, activity has stopped and we're at the
+ // trailing edge, the system time has gone backwards and we're treating
+ // it as the trailing edge, or we've hit the `maxWait` limit.
+ return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
+ (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+ }
+
+ function timerExpired() {
+ var time = now_1();
+ if (shouldInvoke(time)) {
+ return trailingEdge(time);
+ }
+ // Restart the timer.
+ timerId = setTimeout(timerExpired, remainingWait(time));
+ }
+
+ function trailingEdge(time) {
+ timerId = undefined;
+
+ // Only invoke if we have `lastArgs` which means `func` has been
+ // debounced at least once.
+ if (trailing && lastArgs) {
+ return invokeFunc(time);
+ }
+ lastArgs = lastThis = undefined;
+ return result;
+ }
+
+ function cancel() {
+ if (timerId !== undefined) {
+ clearTimeout(timerId);
+ }
+ lastInvokeTime = 0;
+ lastArgs = lastCallTime = lastThis = timerId = undefined;
+ }
+
+ function flush() {
+ return timerId === undefined ? result : trailingEdge(now_1());
+ }
+
+ function debounced() {
+ var time = now_1(),
+ isInvoking = shouldInvoke(time);
+
+ lastArgs = arguments;
+ lastThis = this;
+ lastCallTime = time;
+
+ if (isInvoking) {
+ if (timerId === undefined) {
+ return leadingEdge(lastCallTime);
+ }
+ if (maxing) {
+ // Handle invocations in a tight loop.
+ timerId = setTimeout(timerExpired, wait);
+ return invokeFunc(lastCallTime);
+ }
+ }
+ if (timerId === undefined) {
+ timerId = setTimeout(timerExpired, wait);
+ }
+ return result;
+ }
+ debounced.cancel = cancel;
+ debounced.flush = flush;
+ return debounced;
+}
+
+var debounce_1 = debounce;
+
+/** Error message constants. */
+var FUNC_ERROR_TEXT$1 = 'Expected a function';
+
+/**
+ * Creates a throttled function that only invokes `func` at most once per
+ * every `wait` milliseconds. The throttled function comes with a `cancel`
+ * method to cancel delayed `func` invocations and a `flush` method to
+ * immediately invoke them. Provide `options` to indicate whether `func`
+ * should be invoked on the leading and/or trailing edge of the `wait`
+ * timeout. The `func` is invoked with the last arguments provided to the
+ * throttled function. Subsequent calls to the throttled function return the
+ * result of the last `func` invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the throttled function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.throttle` and `_.debounce`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to throttle.
+ * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=true]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // Avoid excessively updating the position while scrolling.
+ * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+ *
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+ * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+ * jQuery(element).on('click', throttled);
+ *
+ * // Cancel the trailing throttled invocation.
+ * jQuery(window).on('popstate', throttled.cancel);
+ */
+function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT$1);
+ }
+ if (isObject_1(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+ return debounce_1(func, wait, {
+ 'leading': leading,
+ 'maxWait': wait,
+ 'trailing': trailing
+ });
+}
+
+var throttle_1 = throttle;
+
+function camelCaseToDash(str) {
+ return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
+}
+
+function makeDataAttributeString(props) {
+ const keys = Object.keys(props);
+
+ return keys
+ .map((key) => {
+ const _key = camelCaseToDash(key);
+ const val = props[key];
+
+ if (val === undefined) return '';
+ return `data-${_key}="${val}" `;
+ })
+ .join('')
+ .trim();
+}
+
+function getDefault(a, b) {
+ return a !== undefined ? a : b;
+}
+
+
+
+
+
+
+
+
+
+
+
+function copyTextToClipboard(text) {
+ // https://stackoverflow.com/a/30810322/5353542
+ var textArea = document.createElement('textarea');
+
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
+
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
+
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
+
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
+
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
+
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
+
+ textArea.value = text;
+
+ document.body.appendChild(textArea);
+
+ textArea.select();
+
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
+
+ document.body.removeChild(textArea);
+}
+
+function isNumeric(val) {
+ return !isNaN(val);
+}
+
+let throttle$1 = throttle_1;
+
+let debounce$2 = debounce_1;
+
+function promisify(fn, context = null) {
+ return (...args) => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ const out = fn.apply(context, args);
+ resolve(out);
+ }, 0);
+ });
+ };
+}
+
+
+
+function linkProperties(target, source, properties) {
+ const props = properties.reduce((acc, prop) => {
+ acc[prop] = {
+ get() {
+ return source[prop];
+ }
+ };
+ return acc;
+ }, {});
+ Object.defineProperties(target, props);
+}
+
+class DataManager {
+ constructor(options) {
+ this.options = options;
+ this.sortRows = promisify(this.sortRows, this);
+ this.switchColumn = promisify(this.switchColumn, this);
+ this.removeColumn = promisify(this.removeColumn, this);
+ this.filterRows = promisify(this.filterRows, this);
+ }
+
+ init(data) {
+ if (!data) {
+ data = this.options.data;
+ }
+
+ this.data = data;
+
+ this.rowCount = 0;
+ this.columns = [];
+ this.rows = [];
+
+ this.prepareColumns();
+ this.prepareRows();
+
+ this.prepareNumericColumns();
+ }
+
+ // computed property
+ get currentSort() {
+ const col = this.columns.find(col => col.sortOrder !== 'none');
+ return col || {
+ colIndex: -1,
+ sortOrder: 'none'
+ };
+ }
+
+ prepareColumns() {
+ this.columns = [];
+ this.validateColumns();
+ this.prepareDefaultColumns();
+ this.prepareHeader();
+ }
+
+ prepareDefaultColumns() {
+ if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
+ const cell = {
+ id: '_checkbox',
+ content: this.getCheckboxHTML(),
+ editable: false,
+ resizable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 25
+ };
+ this.columns.push(cell);
+ }
+
+ if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
+ let cell = {
+ id: '_rowIndex',
+ content: '',
+ align: 'center',
+ editable: false,
+ resizable: false,
+ focusable: false,
+ dropdown: false
+ };
+
+ this.columns.push(cell);
+ }
+ }
+
+ prepareHeader() {
+ let columns = this.columns.concat(this.options.columns);
+ const baseCell = {
+ isHeader: 1,
+ editable: true,
+ sortable: true,
+ resizable: true,
+ focusable: true,
+ dropdown: true,
+ width: null,
+ format: (value) => {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return value + '';
+ }
+ };
+
+ this.columns = columns
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(col => Object.assign({}, baseCell, col))
+ .map(col => {
+ col.id = col.id || col.content;
+ return col;
+ });
+ }
+
+ prepareCell(content, i) {
+ const cell = {
+ content: '',
+ align: 'left',
+ sortOrder: 'none',
+ colIndex: i,
+ column: this.columns[i]
+ };
+
+ if (content !== null && typeof content === 'object') {
+ // passed as column/header
+ Object.assign(cell, content);
+ } else {
+ cell.content = content;
+ }
+
+ return cell;
+ }
+
+ prepareNumericColumns() {
+ const row0 = this.getRow(0);
+ if (!row0) return;
+ this.columns = this.columns.map((column, i) => {
+
+ const cellValue = row0[i].content;
+ if (!column.align && cellValue && isNumeric(cellValue)) {
+ column.align = 'right';
+ }
+
+ return column;
+ });
+ }
+
+ prepareRows() {
+ this.validateData(this.data);
+
+ this.rows = this.data.map((d, i) => {
+ const index = this._getNextRowCount();
+
+ let row = [];
+
+ if (Array.isArray(d)) {
+ // row is an array
+ if (this.options.addCheckboxColumn) {
+ row.push(this.getCheckboxHTML());
+ }
+ if (this.options.addSerialNoColumn) {
+ row.push((index + 1) + '');
+ }
+ row = row.concat(d);
+
+ while (row.length < this.columns.length) {
+ row.push('');
+ }
+
+ } else {
+ // row is an object
+ for (let col of this.columns) {
+ if (col.id === '_checkbox') {
+ row.push(this.getCheckboxHTML());
+ } else if (col.id === '_rowIndex') {
+ row.push((index + 1) + '');
+ } else {
+ row.push(d[col.id]);
+ }
+ }
+ }
+
+ return this.prepareRow(row, {
+ rowIndex: index
+ });
+ });
+ }
+
+ prepareRow(row, props) {
+ const baseRowCell = {
+ rowIndex: props.rowIndex
+ };
+
+ row = row
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(cell => Object.assign({}, baseRowCell, cell));
+
+ // monkey patched in array object
+ row.meta = props;
+ return row;
+ }
+
+ validateColumns() {
+ const columns = this.options.columns;
+ if (!Array.isArray(columns)) {
+ throw new DataError('`columns` must be an array');
+ }
+
+ columns.forEach((column, i) => {
+ if (typeof column !== 'string' && typeof column !== 'object') {
+ throw new DataError(`column "${i}" must be a string or an object`);
+ }
+ });
+ }
+
+ validateData(data) {
+ if (Array.isArray(data) &&
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
+ return true;
+ }
+ throw new DataError('`data` must be an array of arrays or objects');
+ }
+
+ appendRows(rows) {
+ this.validateData(rows);
+
+ this.rows = this.rows.concat(this.prepareRows(rows));
+ }
+
+ sortRows(colIndex, sortOrder = 'none') {
+ colIndex = +colIndex;
+
+ // reset sortOrder and update for colIndex
+ this.getColumns()
+ .map(col => {
+ if (col.colIndex === colIndex) {
+ col.sortOrder = sortOrder;
+ } else {
+ col.sortOrder = 'none';
+ }
+ });
+
+ this._sortRows(colIndex, sortOrder);
+ }
+
+ _sortRows(colIndex, sortOrder) {
+
+ if (this.currentSort.colIndex === colIndex) {
+ // reverse the array if only sortOrder changed
+ if (
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
+ ) {
+ this.reverseArray(this.rows);
+ this.currentSort.sortOrder = sortOrder;
+ return;
+ }
+ }
+
+ this.rows.sort((a, b) => {
+ const _aIndex = a[0].rowIndex;
+ const _bIndex = b[0].rowIndex;
+ const _a = a[colIndex].content;
+ const _b = b[colIndex].content;
+
+ if (sortOrder === 'none') {
+ return _aIndex - _bIndex;
+ } else if (sortOrder === 'asc') {
+ if (_a < _b) return -1;
+ if (_a > _b) return 1;
+ if (_a === _b) return 0;
+ } else if (sortOrder === 'desc') {
+ if (_a < _b) return 1;
+ if (_a > _b) return -1;
+ if (_a === _b) return 0;
+ }
+ return 0;
+ });
+
+ if (this.hasColumnById('_rowIndex')) {
+ // update row index
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
+ this.rows.forEach((row, index) => {
+ row.forEach(cell => {
+ if (cell.colIndex === srNoColIndex) {
+ cell.content = (index + 1) + '';
+ }
+ });
+ });
+ }
+ }
+
+ reverseArray(array) {
+ let left = null;
+ let right = null;
+ let length = array.length;
+
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
+ const temporary = array[left];
+
+ array[left] = array[right];
+ array[right] = temporary;
+ }
+ }
+
+ switchColumn(index1, index2) {
+ // update columns
+ const temp = this.columns[index1];
+ this.columns[index1] = this.columns[index2];
+ this.columns[index2] = temp;
+
+ this.columns[index1].colIndex = index1;
+ this.columns[index2].colIndex = index2;
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newCell1 = Object.assign({}, row[index1], {
+ colIndex: index2
+ });
+ const newCell2 = Object.assign({}, row[index2], {
+ colIndex: index1
+ });
+
+ let newRow = row.map(cell => {
+ // make object copy
+ return Object.assign({}, cell);
+ });
+
+ newRow[index2] = newCell1;
+ newRow[index1] = newCell2;
+
+ return newRow;
+ });
+ }
+
+ removeColumn(index) {
+ index = +index;
+ const filter = cell => cell.colIndex !== index;
+ const map = (cell, i) => Object.assign({}, cell, {
+ colIndex: i
+ });
+ // update columns
+ this.columns = this.columns
+ .filter(filter)
+ .map(map);
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newRow = row
+ .filter(filter)
+ .map(map);
+
+ return newRow;
+ });
+ }
+
+ updateRow(row, rowIndex) {
+ if (row.length < this.columns.length) {
+ if (this.hasColumnById('_rowIndex')) {
+ const val = (rowIndex + 1) + '';
+
+ row = [val].concat(row);
+ }
+
+ if (this.hasColumnById('_checkbox')) {
+ const val = '';
+
+ row = [val].concat(row);
+ }
+ }
+
+ const _row = this.prepareRow(row, rowIndex);
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
+ this.rows[index] = _row;
+
+ return _row;
+ }
+
+ updateCell(colIndex, rowIndex, options) {
+ let cell;
+ if (typeof colIndex === 'object') {
+ // cell object was passed,
+ // must have colIndex, rowIndex
+ cell = colIndex;
+ colIndex = cell.colIndex;
+ rowIndex = cell.rowIndex;
+ // the object passed must be merged with original cell
+ options = cell;
+ }
+ cell = this.getCell(colIndex, rowIndex);
+
+ // mutate object directly
+ for (let key in options) {
+ const newVal = options[key];
+ if (newVal !== undefined) {
+ cell[key] = newVal;
+ }
+ }
+
+ return cell;
+ }
+
+ updateColumn(colIndex, keyValPairs) {
+ const column = this.getColumn(colIndex);
+ for (let key in keyValPairs) {
+ const newVal = keyValPairs[key];
+ if (newVal !== undefined) {
+ column[key] = newVal;
+ }
+ }
+ return column;
+ }
+
+ filterRows(keyword, colIndex) {
+ let rowsToHide = [];
+ let rowsToShow = [];
+ const cells = this.rows.map(row => row[colIndex]);
+
+ cells.forEach(cell => {
+ const hay = cell.content.toLowerCase();
+ const needle = (keyword || '').toLowerCase();
+
+ if (!needle || hay.includes(needle)) {
+ rowsToShow.push(cell.rowIndex);
+ } else {
+ rowsToHide.push(cell.rowIndex);
+ }
+ });
+
+ return {
+ rowsToHide,
+ rowsToShow
+ };
+ }
+
+ getRowCount() {
+ return this.rowCount;
+ }
+
+ _getNextRowCount() {
+ const val = this.rowCount;
+
+ this.rowCount++;
+ return val;
+ }
+
+ getRows(start, end) {
+ return this.rows.slice(start, end);
+ }
+
+ getColumns(skipStandardColumns) {
+ let columns = this.columns;
+
+ if (skipStandardColumns) {
+ columns = columns.slice(this.getStandardColumnCount());
+ }
+
+ return columns;
+ }
+
+ getStandardColumnCount() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getColumnCount(skipStandardColumns) {
+ let val = this.columns.length;
+
+ if (skipStandardColumns) {
+ val = val - this.getStandardColumnCount();
+ }
+
+ return val;
+ }
+
+ getColumn(colIndex) {
+ colIndex = +colIndex;
+ return this.columns.find(col => col.colIndex === colIndex);
+ }
+
+ getRow(rowIndex) {
+ rowIndex = +rowIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex);
+ }
+
+ getCell(colIndex, rowIndex) {
+ rowIndex = +rowIndex;
+ colIndex = +colIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
+ }
+
+ get() {
+ return {
+ columns: this.columns,
+ rows: this.rows
+ };
+ }
+
+ hasColumn(name) {
+ return Boolean(this.columns.find(col => col.content === name));
+ }
+
+ hasColumnById(id) {
+ return Boolean(this.columns.find(col => col.id === id));
+ }
+
+ getColumnIndex(name) {
+ return this.columns.findIndex(col => col.content === name);
+ }
+
+ getColumnIndexById(id) {
+ return this.columns.findIndex(col => col.id === id);
+ }
+
+ getCheckboxHTML() {
+ return '';
+ }
+}
+
+// Custom Errors
+class DataError extends TypeError {}
+
+class ColumnManager {
+ constructor(instance) {
+ this.instance = instance;
+
+ linkProperties(this, this.instance, [
+ 'options',
+ 'fireEvent',
+ 'header',
+ 'datamanager',
+ 'style',
+ 'wrapper',
+ 'rowmanager',
+ 'bodyScrollable'
+ ]);
+
+ this.bindEvents();
+ getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
+ }
+
+ renderHeader() {
+ this.header.innerHTML = '';
+ this.refreshHeader();
+ }
+
+ refreshHeader() {
+ const columns = this.datamanager.getColumns();
+
+ if (!$('.data-table-col', this.header)) {
+ // insert html
+
+ let html = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ if (this.options.enableInlineFilters) {
+ html += this.rowmanager.getRowHTML(columns, {
+ isFilter: 1
+ });
+ }
+
+ $('thead', this.header).innerHTML = html;
+
+ this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
+
+ if (this.$filterRow) {
+ // hide filter row immediately, so it doesn't disturb layout
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ }
+ } else {
+ // refresh dom state
+ const $cols = $.each('.data-table-col', this.header);
+ if (columns.length < $cols.length) {
+ // deleted column
+ $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ return;
+ }
+
+ $cols.map(($col, i) => {
+ const column = columns[i];
+ // column sorted or order changed
+ // update colIndex of each header cell
+ $.data($col, {
+ colIndex: column.colIndex
+ });
+
+ // refresh sort indicator
+ const sortIndicator = $('.sort-indicator', $col);
+ if (sortIndicator) {
+ sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ }
+ });
+ }
+ // reset columnMap
+ this.$columnMap = [];
+ }
+
+ bindEvents() {
+ this.bindDropdown();
+ this.bindResizeColumn();
+ this.bindMoveColumn();
+ this.bindFilter();
+ }
+
+ bindDropdown() {
+ let $activeDropdown;
+ $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
+ const $dropdown = $.closest('.data-table-dropdown', $button);
+
+ if (!$dropdown.classList.contains('is-active')) {
+ deactivateDropdown();
+ $dropdown.classList.add('is-active');
+ $activeDropdown = $dropdown;
+ } else {
+ deactivateDropdown();
+ }
+ });
+
+ $.on(document.body, 'click', (e) => {
+ if (e.target.matches('.data-table-dropdown-toggle')) return;
+ deactivateDropdown();
+ });
+
+ const dropdownItems = this.options.headerDropdown;
+
+ $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
+ const $col = $.closest('.data-table-col', $item);
+ const {
+ index
+ } = $.data($item);
+ const {
+ colIndex
+ } = $.data($col);
+ let callback = dropdownItems[index].action;
+
+ callback && callback.call(this.instance, this.getColumn(colIndex));
+ });
+
+ function deactivateDropdown(e) {
+ $activeDropdown && $activeDropdown.classList.remove('is-active');
+ $activeDropdown = null;
+ }
+ }
+
+ bindResizeColumn() {
+ let isDragging = false;
+ let $resizingCell, startWidth, startX;
+
+ $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
+ document.body.classList.add('data-table-resize');
+ const $cell = $handle.parentNode.parentNode;
+ $resizingCell = $cell;
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ const col = this.getColumn(colIndex);
+
+ if (col && col.resizable === false) {
+ return;
+ }
+
+ isDragging = true;
+ startWidth = $.style($('.content', $resizingCell), 'width');
+ startX = e.pageX;
+ });
+
+ $.on(document.body, 'mouseup', (e) => {
+ document.body.classList.remove('data-table-resize');
+ if (!$resizingCell) return;
+ isDragging = false;
+
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ this.setColumnWidth(colIndex);
+ this.style.setBodyStyle();
+ $resizingCell = null;
+ });
+
+ $.on(document.body, 'mousemove', (e) => {
+ if (!isDragging) return;
+ const finalWidth = startWidth + (e.pageX - startX);
+ const {
+ colIndex
+ } = $.data($resizingCell);
+
+ if (this.getColumnMinWidth(colIndex) > finalWidth) {
+ // don't resize past minWidth
+ return;
+ }
+ this.datamanager.updateColumn(colIndex, {
+ width: finalWidth
+ });
+ this.setColumnHeaderWidth(colIndex);
+ });
+ }
+
+ bindMoveColumn() {
+ let initialized;
+
+ const initialize = () => {
+ if (initialized) {
+ $.off(document.body, 'mousemove', initialize);
+ return;
+ }
+ const ready = $('.data-table-col', this.header);
+ if (!ready) return;
+
+ const $parent = $('.data-table-row', this.header);
+
+ this.sortable = Sortable.create($parent, {
+ onEnd: (e) => {
+ const {
+ oldIndex,
+ newIndex
+ } = e;
+ const $draggedCell = e.item;
+ const {
+ colIndex
+ } = $.data($draggedCell);
+ if (+colIndex === newIndex) return;
+
+ this.switchColumn(oldIndex, newIndex);
+ },
+ preventOnFilter: false,
+ filter: '.column-resizer, .data-table-dropdown',
+ animation: 150
+ });
+ };
+
+ $.on(document.body, 'mousemove', initialize);
+ }
+
+ bindSortColumn() {
+
+ $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
+ const $cell = span.closest('.data-table-col');
+ let {
+ colIndex,
+ sortOrder
+ } = $.data($cell);
+ sortOrder = getDefault(sortOrder, 'none');
+ const col = this.getColumn(colIndex);
+
+ if (col && col.sortable === false) {
+ return;
+ }
+
+ // reset sort indicator
+ $('.sort-indicator', this.header).textContent = '';
+ $.each('.data-table-col', this.header).map($cell => {
+ $.data($cell, {
+ sortOrder: 'none'
+ });
+ });
+
+ let nextSortOrder, textContent;
+ if (sortOrder === 'none') {
+ nextSortOrder = 'asc';
+ textContent = '▲';
+ } else if (sortOrder === 'asc') {
+ nextSortOrder = 'desc';
+ textContent = '▼';
+ } else if (sortOrder === 'desc') {
+ nextSortOrder = 'none';
+ textContent = '';
+ }
+
+ $.data($cell, {
+ sortOrder: nextSortOrder
+ });
+ $('.sort-indicator', $cell).textContent = textContent;
+
+ this.sortColumn(colIndex, nextSortOrder);
+ });
+ }
+
+ sortColumn(colIndex, nextSortOrder) {
+ this.instance.freeze();
+ this.sortRows(colIndex, nextSortOrder)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
+ });
+ }
+
+ removeColumn(colIndex) {
+ const removedCol = this.getColumn(colIndex);
+ this.instance.freeze();
+ this.datamanager.removeColumn(colIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onRemoveColumn', removedCol);
+ });
+ }
+
+ switchColumn(oldIndex, newIndex) {
+ this.instance.freeze();
+ this.datamanager.switchColumn(oldIndex, newIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => {
+ this.setColumnWidth(oldIndex);
+ this.setColumnWidth(newIndex);
+ this.instance.unfreeze();
+ })
+ .then(() => {
+ this.fireEvent('onSwitchColumn',
+ this.getColumn(oldIndex), this.getColumn(newIndex)
+ );
+ });
+ }
+
+ toggleFilter() {
+ this.isFilterShown = this.isFilterShown || false;
+
+ if (this.isFilterShown) {
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ } else {
+ $.style(this.$filterRow, {
+ display: ''
+ });
+ }
+
+ this.isFilterShown = !this.isFilterShown;
+ this.style.setBodyStyle();
+ }
+
+ focusFilter(colIndex) {
+ if (!this.isFilterShown) return;
+
+ const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
+ $filterInput.focus();
+ }
+
+ bindFilter() {
+ if (!this.options.enableInlineFilters) return;
+ const handler = e => {
+ const $filterCell = $.closest('.data-table-col', e.target);
+ const {
+ colIndex
+ } = $.data($filterCell);
+ const keyword = e.target.value;
+
+ this.datamanager.filterRows(keyword, colIndex)
+ .then(({
+ rowsToHide,
+ rowsToShow
+ }) => {
+ rowsToHide.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.add('hide');
+ });
+ rowsToShow.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.remove('hide');
+ });
+ });
+ };
+ $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300));
+ }
+
+ sortRows(colIndex, sortOrder) {
+ return this.datamanager.sortRows(colIndex, sortOrder);
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ setColumnWidth(colIndex) {
+ colIndex = +colIndex;
+ this._columnWidthMap = this._columnWidthMap || [];
+
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let index = this._columnWidthMap[colIndex];
+ const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
+ const styles = {
+ width: width + 'px'
+ };
+
+ index = this.style.setStyle(selector, styles, index);
+ this._columnWidthMap[colIndex] = index;
+ }
+
+ setColumnHeaderWidth(colIndex) {
+ colIndex = +colIndex;
+ this.$columnMap = this.$columnMap || [];
+ const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let $column = this.$columnMap[colIndex];
+ if (!$column) {
+ $column = this.header.querySelector(selector);
+ this.$columnMap[colIndex] = $column;
+ }
+
+ $column.style.width = width + 'px';
+ }
+
+ getColumnMinWidth(colIndex) {
+ colIndex = +colIndex;
+ return this.getColumn(colIndex).minWidth || 24;
+ }
+
+ getFirstColumnIndex() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getHeaderCell$(colIndex) {
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+
+ getLastColumnIndex() {
+ return this.datamanager.getColumnCount() - 1;
+ }
+
+ getSerialColumnIndex() {
+ const columns = this.datamanager.getColumns();
+
+ return columns.findIndex(column => column.content.includes('Sr. No'));
+ }
+}
+
+// eslint-disable-next-line
+var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
+ // add dropdown buttons
+ const dropdownItems = this.options.headerDropdown;
+
+ return `${dropdownButton}
+
+ ${dropdownItems.map((d, i) => `
${d.label}
`).join('')}
+
+ `;
+};
+
+class CellManager {
+ constructor(instance) {
+ this.instance = instance;
+ this.wrapper = this.instance.wrapper;
+ this.options = this.instance.options;
+ this.style = this.instance.style;
+ this.bodyScrollable = this.instance.bodyScrollable;
+ this.columnmanager = this.instance.columnmanager;
+ this.rowmanager = this.instance.rowmanager;
+ this.datamanager = this.instance.datamanager;
+ this.keyboard = this.instance.keyboard;
+
+ this.bindEvents();
+ }
+
+ bindEvents() {
+ this.bindFocusCell();
+ this.bindEditCell();
+ this.bindKeyboardSelection();
+ this.bindCopyCellContents();
+ this.bindMouseEvents();
+ }
+
+ bindFocusCell() {
+ this.bindKeyboardNav();
+ }
+
+ bindEditCell() {
+ this.$editingCell = null;
+
+ $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
+ this.activateEditing(cell);
+ });
+
+ this.keyboard.on('enter', (e) => {
+ if (this.$focusedCell && !this.$editingCell) {
+ // enter keypress on focused cell
+ this.activateEditing(this.$focusedCell);
+ } else if (this.$editingCell) {
+ // enter keypress on editing cell
+ this.submitEditing();
+ this.deactivateEditing();
+ }
+ });
+ }
+
+ bindKeyboardNav() {
+ const focusCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+
+ if (direction === 'left' || direction === 'shift+tab') {
+ $cell = this.getLeftCell$($cell);
+ } else if (direction === 'right' || direction === 'tab') {
+ $cell = this.getRightCell$($cell);
+ } else if (direction === 'up') {
+ $cell = this.getAboveCell$($cell);
+ } else if (direction === 'down') {
+ $cell = this.getBelowCell$($cell);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ const focusLastCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ if (direction === 'left') {
+ $cell = this.getLeftMostCell$(rowIndex);
+ } else if (direction === 'right') {
+ $cell = this.getRightMostCell$(rowIndex);
+ } else if (direction === 'up') {
+ $cell = this.getTopMostCell$(colIndex);
+ } else if (direction === 'down') {
+ $cell = this.getBottomMostCell$(colIndex);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
+ direction => this.keyboard.on(direction, () => focusCell(direction))
+ );
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
+ );
+
+ this.keyboard.on('esc', () => {
+ this.deactivateEditing();
+ });
+
+ if (this.options.enableInlineFilters) {
+ this.keyboard.on('ctrl+f', (e) => {
+ const $cell = $.closest('.data-table-col', e.target);
+ let {
+ colIndex
+ } = $.data($cell);
+
+ this.activateFilter(colIndex);
+ return true;
+ });
+ }
+ }
+
+ bindKeyboardSelection() {
+ const getNextSelectionCursor = (direction) => {
+ let $selectionCursor = this.getSelectionCursor();
+
+ if (direction === 'left') {
+ $selectionCursor = this.getLeftCell$($selectionCursor);
+ } else if (direction === 'right') {
+ $selectionCursor = this.getRightCell$($selectionCursor);
+ } else if (direction === 'up') {
+ $selectionCursor = this.getAboveCell$($selectionCursor);
+ } else if (direction === 'down') {
+ $selectionCursor = this.getBelowCell$($selectionCursor);
+ }
+
+ return $selectionCursor;
+ };
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('shift+' + direction,
+ () => this.selectArea(getNextSelectionCursor(direction)))
+ );
+ }
+
+ bindCopyCellContents() {
+ this.keyboard.on('ctrl+c', () => {
+ this.copyCellContents(this.$focusedCell, this.$selectionCursor);
+ });
+ }
+
+ bindMouseEvents() {
+ let mouseDown = null;
+
+ $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
+ mouseDown = true;
+ this.focusCell($(e.delegatedTarget));
+ });
+
+ $.on(this.bodyScrollable, 'mouseup', () => {
+ mouseDown = false;
+ });
+
+ const selectArea = (e) => {
+ if (!mouseDown) return;
+ this.selectArea($(e.delegatedTarget));
+ };
+
+ $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50));
+ }
+
+ focusCell($cell, {
+ skipClearSelection = 0
+ } = {}) {
+ if (!$cell) return;
+
+ // don't focus if already editing cell
+ if ($cell === this.$editingCell) return;
+
+ const {
+ colIndex,
+ isHeader
+ } = $.data($cell);
+ if (isHeader) {
+ return;
+ }
+
+ const column = this.columnmanager.getColumn(colIndex);
+ if (column.focusable === false) {
+ return;
+ }
+
+ this.scrollToCell($cell);
+
+ this.deactivateEditing();
+ if (!skipClearSelection) {
+ this.clearSelection();
+ }
+
+ if (this.$focusedCell) {
+ this.$focusedCell.classList.remove('selected');
+ }
+
+ this.$focusedCell = $cell;
+ $cell.classList.add('selected');
+
+ // so that keyboard nav works
+ $cell.focus();
+
+ this.highlightRowColumnHeader($cell);
+ }
+
+ highlightRowColumnHeader($cell) {
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell);
+ const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
+ const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
+ const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
+
+ if (this.lastHeaders) {
+ $.removeStyle(this.lastHeaders, 'backgroundColor');
+ }
+
+ const colHeader = $(colHeaderSelector, this.wrapper);
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
+
+ $.style([colHeader, rowHeader], {
+ backgroundColor: '#f5f7fa' // light-bg
+ });
+
+ this.lastHeaders = [colHeader, rowHeader];
+ }
+
+ selectAreaOnClusterChanged() {
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$selectionCursor);
+ const $cell = this.getCell$(colIndex, rowIndex);
+
+ if (!$cell || $cell === this.$selectionCursor) return;
+
+ // selectArea needs $focusedCell
+ const fCell = $.data(this.$focusedCell);
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
+
+ this.selectArea($cell);
+ }
+
+ focusCellOnClusterChanged() {
+ if (!this.$focusedCell) return;
+
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$focusedCell);
+ const $cell = this.getCell$(colIndex, rowIndex);
+
+ if (!$cell) return;
+ // this function is called after selectAreaOnClusterChanged,
+ // focusCell calls clearSelection which resets the area selection
+ // so a flag to skip it
+ this.focusCell($cell, {
+ skipClearSelection: 1
+ });
+ }
+
+ selectArea($selectionCursor) {
+ if (!this.$focusedCell) return;
+
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
+ // valid selection
+ this.$selectionCursor = $selectionCursor;
+ }
+ };
+
+ _selectArea($cell1, $cell2) {
+ if ($cell1 === $cell2) return false;
+
+ const cells = this.getCellsInRange($cell1, $cell2);
+ if (!cells) return false;
+
+ this.clearSelection();
+ cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
+ return true;
+ }
+
+ getCellsInRange($cell1, $cell2) {
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
+
+ if (typeof $cell1 === 'number') {
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
+ } else
+ if (typeof $cell1 === 'object') {
+
+ if (!($cell1 && $cell2)) {
+ return false;
+ }
+
+ const cell1 = $.data($cell1);
+ const cell2 = $.data($cell2);
+
+ colIndex1 = cell1.colIndex;
+ rowIndex1 = cell1.rowIndex;
+ colIndex2 = cell2.colIndex;
+ rowIndex2 = cell2.rowIndex;
+ }
+
+ if (rowIndex1 > rowIndex2) {
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
+ }
+
+ if (colIndex1 > colIndex2) {
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
+ }
+
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
+ return false;
+ }
+
+ let cells = [];
+ let colIndex = colIndex1;
+ let rowIndex = rowIndex1;
+ let rowIndices = [];
+
+ while (rowIndex <= rowIndex2) {
+ rowIndices.push(rowIndex);
+ rowIndex++;
+ }
+
+ rowIndices.map(rowIndex => {
+ while (colIndex <= colIndex2) {
+ cells.push([colIndex, rowIndex]);
+ colIndex++;
+ }
+ colIndex = colIndex1;
+ });
+
+ return cells;
+ }
+
+ clearSelection() {
+ $.each('.data-table-col.highlight', this.bodyScrollable)
+ .map(cell => cell.classList.remove('highlight'));
+
+ this.$selectionCursor = null;
+ }
+
+ getSelectionCursor() {
+ return this.$selectionCursor || this.$focusedCell;
+ }
+
+ activateEditing($cell) {
+ this.focusCell($cell);
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ const col = this.columnmanager.getColumn(colIndex);
+ if (col && (col.editable === false || col.focusable === false)) {
+ return;
+ }
+
+ const cell = this.getCell(colIndex, rowIndex);
+ if (cell && cell.editable === false) {
+ return;
+ }
+
+ if (this.$editingCell) {
+ const {
+ _rowIndex,
+ _colIndex
+ } = $.data(this.$editingCell);
+
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
+ // editing the same cell
+ return;
+ }
+ }
+
+ this.$editingCell = $cell;
+ $cell.classList.add('editing');
+
+ const $editCell = $('.edit-cell', $cell);
+ $editCell.innerHTML = '';
+
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
+
+ if (editor) {
+ this.currentCellEditor = editor;
+ // initialize editing input with cell value
+ editor.initValue(cell.content, rowIndex, col);
+ }
+ }
+
+ deactivateEditing() {
+ // keep focus on the cell so that keyboard navigation works
+ if (this.$focusedCell) this.$focusedCell.focus();
+
+ if (!this.$editingCell) return;
+ this.$editingCell.classList.remove('editing');
+ this.$editingCell = null;
+ }
+
+ getEditor(colIndex, rowIndex, value, parent) {
+ // debugger;
+ const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
+ if (obj && obj.setValue) return obj;
+
+ // editing fallback
+ const $input = $.create('input', {
+ class: 'input-style',
+ type: 'text',
+ inside: parent
+ });
+
+ return {
+ initValue(value) {
+ $input.focus();
+ $input.value = value;
+ },
+ getValue() {
+ return $input.value;
+ },
+ setValue(value) {
+ $input.value = value;
+ }
+ };
+ }
+
+ submitEditing() {
+ if (!this.$editingCell) return;
+ const $cell = this.$editingCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+ const col = this.datamanager.getColumn(colIndex);
+
+ if ($cell) {
+ const editor = this.currentCellEditor;
+
+ if (editor) {
+ const value = editor.getValue();
+ const done = editor.setValue(value, rowIndex, col);
+ const oldValue = this.getCell(colIndex, rowIndex).content;
+
+ // update cell immediately
+ this.updateCell(colIndex, rowIndex, value);
+ $cell.focus();
+
+ if (done && done.then) {
+ // revert to oldValue if promise fails
+ done.catch((e) => {
+ console.log(e);
+ this.updateCell(colIndex, rowIndex, oldValue);
+ });
+ }
+ }
+ }
+
+ this.currentCellEditor = null;
+ }
+
+ copyCellContents($cell1, $cell2) {
+ if (!$cell2 && $cell1) {
+ // copy only focusedCell
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell1);
+ const cell = this.getCell(colIndex, rowIndex);
+ copyTextToClipboard(cell.content);
+ return;
+ }
+ const cells = this.getCellsInRange($cell1, $cell2);
+
+ if (!cells) return;
+
+ const values = cells
+ // get cell objects
+ .map(index => this.getCell(...index))
+ // convert to array of rows
+ .reduce((acc, curr) => {
+ const rowIndex = curr.rowIndex;
+
+ acc[rowIndex] = acc[rowIndex] || [];
+ acc[rowIndex].push(curr.content);
+
+ return acc;
+ }, [])
+ // join values by tab
+ .map(row => row.join('\t'))
+ // join rows by newline
+ .join('\n');
+
+ copyTextToClipboard(values);
+ }
+
+ activateFilter(colIndex) {
+ this.columnmanager.toggleFilter();
+ this.columnmanager.focusFilter(colIndex);
+
+ if (!this.columnmanager.isFilterShown) {
+ // put focus back on cell
+ this.$focusedCell.focus();
+ }
+ }
+
+ updateCell(colIndex, rowIndex, value) {
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
+ content: value
+ });
+ this.refreshCell(cell);
+ }
+
+ refreshCell(cell) {
+ const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
+ $cell.innerHTML = this.getCellContent(cell);
+ }
+
+ isStandardCell(colIndex) {
+ // Standard cells are in Sr. No and Checkbox column
+ return colIndex < this.columnmanager.getFirstColumnIndex();
+ }
+
+ getCell$(colIndex, rowIndex) {
+ return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
+ }
+
+ getAboveCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $aboveRow = $cell.parentElement.previousElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $aboveRow);
+ }
+
+ getBelowCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $belowRow = $cell.parentElement.nextElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $belowRow);
+ }
+
+ getLeftCell$($cell) {
+ return $cell.previousElementSibling;
+ }
+
+ getRightCell$($cell) {
+ return $cell.nextElementSibling;
+ }
+
+ getLeftMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
+ }
+
+ getRightMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
+ }
+
+ getTopMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
+ }
+
+ getBottomMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getCellAttr($cell) {
+ return this.instance.getCellAttr($cell);
+ }
+
+ getRowHeight() {
+ return $.style($('.data-table-row', this.bodyScrollable), 'height');
+ }
+
+ scrollToCell($cell) {
+ if ($.inViewport($cell, this.bodyScrollable)) return false;
+
+ const {
+ rowIndex
+ } = $.data($cell);
+ this.rowmanager.scrollToRow(rowIndex);
+ return false;
+ }
+
+ getRowCountPerPage() {
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
+ }
+
+ getCellHTML(cell) {
+ const {
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ } = cell;
+ const dataAttr = makeDataAttributeString({
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ });
+
+ return `
+
+ ${this.getCellContent(cell)}
+ |
+ `;
+ }
+
+ getCellContent(cell) {
+ const {
+ isHeader
+ } = cell;
+
+ const editable = !isHeader && cell.editable !== false;
+ const editCellHTML = editable ? this.getEditCellHTML() : '';
+
+ const sortable = isHeader && cell.sortable !== false;
+ const sortIndicator = sortable ? '' : '';
+
+ const resizable = isHeader && cell.resizable !== false;
+ const resizeColumn = resizable ? '' : '';
+
+ const hasDropdown = isHeader && cell.dropdown !== false;
+ const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
+
+ let contentHTML;
+ if (cell.isHeader || cell.isFilter || !cell.column.format) {
+ contentHTML = cell.content;
+ } else {
+ contentHTML = cell.column.format(cell.content, cell);
+ }
+
+ return `
+
+ ${(contentHTML)}
+ ${sortIndicator}
+ ${resizeColumn}
+ ${dropdown}
+
+ ${editCellHTML}
+ `;
+ }
+
+ getEditCellHTML() {
+ return `
+
+ `;
+ }
+
+ cellSelector(colIndex, rowIndex) {
+ return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
+ }
+}
+
+class RowManager {
+ constructor(instance) {
+ this.instance = instance;
+ this.options = this.instance.options;
+ this.wrapper = this.instance.wrapper;
+ this.bodyScrollable = this.instance.bodyScrollable;
+
+ this.bindEvents();
+ this.refreshRows = promisify(this.refreshRows, this);
+ }
+
+ get datamanager() {
+ return this.instance.datamanager;
+ }
+
+ get cellmanager() {
+ return this.instance.cellmanager;
+ }
+
+ bindEvents() {
+ this.bindCheckbox();
+ }
+
+ bindCheckbox() {
+ if (!this.options.addCheckboxColumn) return;
+
+ // map of checked rows
+ this.checkMap = [];
+
+ $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
+ const $cell = $checkbox.closest('.data-table-col');
+ const {
+ rowIndex,
+ isHeader
+ } = $.data($cell);
+ const checked = $checkbox.checked;
+
+ if (isHeader) {
+ this.checkAll(checked);
+ } else {
+ this.checkRow(rowIndex, checked);
+ }
+ });
+ }
+
+ refreshRows() {
+ this.instance.renderBody();
+ this.instance.setDimensions();
+ }
+
+ refreshRow(row, rowIndex) {
+ const _row = this.datamanager.updateRow(row, rowIndex);
+
+ _row.forEach(cell => {
+ this.cellmanager.refreshCell(cell);
+ });
+ }
+
+ getCheckedRows() {
+ if (!this.checkMap) {
+ return [];
+ }
+
+ return this.checkMap
+ .map((c, rowIndex) => {
+ if (c) {
+ return rowIndex;
+ }
+ return null;
+ })
+ .filter(c => {
+ return c !== null || c !== undefined;
+ });
+ }
+
+ highlightCheckedRows() {
+ this.getCheckedRows()
+ .map(rowIndex => this.checkRow(rowIndex, true));
+ }
+
+ checkRow(rowIndex, toggle) {
+ const value = toggle ? 1 : 0;
+ const selector = rowIndex =>
+ `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
+ // update internal map
+ this.checkMap[rowIndex] = value;
+ // set checkbox value explicitly
+ $.each(selector(rowIndex), this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight row
+ this.highlightRow(rowIndex, toggle);
+ }
+
+ checkAll(toggle) {
+ const value = toggle ? 1 : 0;
+
+ // update internal map
+ if (toggle) {
+ this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
+ } else {
+ this.checkMap = [];
+ }
+ // set checkbox value
+ $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight all
+ this.highlightAll(toggle);
+ }
+
+ highlightRow(rowIndex, toggle = true) {
+ const $row = this.getRow$(rowIndex);
+ if (!$row) return;
+
+ if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
+ $row.classList.add('row-unhighlight');
+ return;
+ }
+
+ if (toggle && $row.classList.contains('row-unhighlight')) {
+ $row.classList.remove('row-unhighlight');
+ }
+
+ this._highlightedRows = this._highlightedRows || {};
+
+ if (toggle) {
+ $row.classList.add('row-highlight');
+ this._highlightedRows[rowIndex] = $row;
+ } else {
+ $row.classList.remove('row-highlight');
+ delete this._highlightedRows[rowIndex];
+ }
+ }
+
+ highlightAll(toggle = true) {
+ if (toggle) {
+ this.bodyScrollable.classList.add('row-highlight-all');
+ } else {
+ this.bodyScrollable.classList.remove('row-highlight-all');
+ for (const rowIndex in this._highlightedRows) {
+ const $row = this._highlightedRows[rowIndex];
+ $row.classList.remove('row-highlight');
+ }
+ this._highlightedRows = {};
+ }
+ }
+
+ getRow$(rowIndex) {
+ return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ }
+
+ getTotalRows() {
+ return this.datamanager.getRowCount();
+ }
+
+ getFirstRowIndex() {
+ return 0;
+ }
+
+ getLastRowIndex() {
+ return this.datamanager.getRowCount() - 1;
+ }
+
+ scrollToRow(rowIndex) {
+ rowIndex = +rowIndex;
+ this._lastScrollTo = this._lastScrollTo || 0;
+ const $row = this.getRow$(rowIndex);
+ if ($.inViewport($row, this.bodyScrollable)) return;
+
+ const {
+ height
+ } = $row.getBoundingClientRect();
+ const {
+ top,
+ bottom
+ } = this.bodyScrollable.getBoundingClientRect();
+ const rowsInView = Math.floor((bottom - top) / height);
+
+ let offset = 0;
+ if (rowIndex > this._lastScrollTo) {
+ offset = height * ((rowIndex + 1) - rowsInView);
+ } else {
+ offset = height * ((rowIndex + 1) - 1);
+ }
+
+ this._lastScrollTo = rowIndex;
+ $.scrollTop(this.bodyScrollable, offset);
+ }
+
+ getRowHTML(row, props) {
+ const dataAttr = makeDataAttributeString(props);
+
+ if (props.isFilter) {
+ row = row.map(cell => (Object.assign(cell, {
+ content: this.getFilterInput({
+ colIndex: cell.colIndex
+ }),
+ isFilter: 1,
+ isHeader: undefined,
+ editable: false
+ })));
+ }
+
+ return `
+
+ ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
+
+ `;
+ }
+
+ getFilterInput(props) {
+ const dataAttr = makeDataAttributeString(props);
+ return ``;
+ }
+}
+
+class BodyRenderer {
+ constructor(instance) {
+ this.instance = instance;
+ this.options = instance.options;
+ this.datamanager = instance.datamanager;
+ this.rowmanager = instance.rowmanager;
+ this.cellmanager = instance.cellmanager;
+ this.bodyScrollable = instance.bodyScrollable;
+ this.log = instance.log;
+ this.appendRemainingData = promisify(this.appendRemainingData, this);
+ }
+
+ render() {
+ if (this.options.enableClusterize) {
+ this.renderBodyWithClusterize();
+ } else {
+ this.renderBodyHTML();
+ }
+ }
+
+ renderBodyHTML() {
+ const rows = this.datamanager.getRows();
+
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML(rows)}
+
+ `;
+ this.instance.setDimensions();
+ this.restoreState();
+ }
+
+ renderBodyWithClusterize() {
+ // first page
+ const rows = this.datamanager.getRows(0, 20);
+ const initialData = this.getDataForClusterize(rows);
+
+ if (!this.clusterize) {
+ // empty body
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML([])}
+
+ `;
+
+ // first 20 rows will appended
+ // rest of them in nextTick
+ this.clusterize = new Clusterize({
+ rows: initialData,
+ scrollElem: this.bodyScrollable,
+ contentElem: $('tbody', this.bodyScrollable),
+ callbacks: {
+ clusterChanged: () => {
+ this.restoreState();
+ }
+ },
+ /* eslint-disable */
+ no_data_text: this.options.noDataMessage,
+ no_data_class: 'empty-state'
+ /* eslint-enable */
+ });
+
+ // setDimensions requires atleast 1 row to exist in dom
+ this.instance.setDimensions();
+ } else {
+ this.clusterize.update(initialData);
+ }
+
+ this.appendRemainingData();
+ }
+
+ restoreState() {
+ this.rowmanager.highlightCheckedRows();
+ this.cellmanager.selectAreaOnClusterChanged();
+ this.cellmanager.focusCellOnClusterChanged();
+ }
+
+ appendRemainingData() {
+ const rows = this.datamanager.getRows(20);
+ const data = this.getDataForClusterize(rows);
+ this.clusterize.append(data);
+ }
+
+ getDataForClusterize(rows) {
+ return rows.map((row) => this.rowmanager.getRowHTML(row, row.meta));
+ }
+
+ getBodyHTML(rows) {
+ return `
+
+ ${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
+
+ `;
+ }
+}
+
+class Style {
+ constructor(instance) {
+ this.instance = instance;
+
+ linkProperties(this, this.instance, [
+ 'options', 'datamanager', 'columnmanager',
+ 'header', 'bodyScrollable', 'getColumn'
+ ]);
+
+ this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
+ instance.datatableWrapper.classList.add(this.scopeClass);
+
+ const styleEl = document.createElement('style');
+ instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
+ this.styleEl = styleEl;
+ this.styleSheet = styleEl.sheet;
+
+ this.bindResizeWindow();
+ }
+
+ bindResizeWindow() {
+ if (this.options.layout === 'fluid') {
+ $.on(window, 'resize', throttle$1(() => {
+ this.distributeRemainingWidth();
+ this.refreshColumnWidth();
+ this.setBodyStyle();
+ }, 300));
+ }
+ }
+
+ destroy() {
+ this.styleEl.remove();
+ }
+
+ setStyle(selector, styleMap, index = -1) {
+ const styles = Object.keys(styleMap)
+ .map(prop => {
+ if (!prop.includes('-')) {
+ prop = camelCaseToDash(prop);
+ }
+ return `${prop}:${styleMap[prop]};`;
+ })
+ .join('');
+ let prefixedSelector = selector
+ .split(',')
+ .map(r => `.${this.scopeClass} ${r}`)
+ .join(',');
+
+ let ruleString = `${prefixedSelector} { ${styles} }`;
+
+ let _index = this.styleSheet.cssRules.length;
+ if (index !== -1) {
+ this.styleSheet.deleteRule(index);
+ _index = index;
+ }
+
+ this.styleSheet.insertRule(ruleString, _index);
+ return _index;
+ }
+
+ setDimensions() {
+ this.setHeaderStyle();
+
+ this.setupMinWidth();
+ this.setupNaturalColumnWidth();
+ this.setupColumnWidth();
+
+ this.distributeRemainingWidth();
+ this.setColumnStyle();
+ this.setDefaultCellHeight();
+ this.setBodyStyle();
+ }
+
+ setHeaderStyle() {
+ if (this.options.layout === 'fluid') {
+ // setting width as 0 will ensure that the
+ // header doesn't take the available space
+ $.style(this.header, {
+ width: 0
+ });
+ }
+
+ $.style(this.header, {
+ margin: 0
+ });
+
+ // don't show resize cursor on nonResizable columns
+ const nonResizableColumnsSelector = this.datamanager.getColumns()
+ .filter(col => col.resizable === false)
+ .map(col => col.colIndex)
+ .map(i => `.data-table-header [data-col-index="${i}"]`)
+ .join();
+
+ this.setStyle(nonResizableColumnsSelector, {
+ cursor: 'pointer'
+ });
+ }
+
+ setupMinWidth() {
+ $.each('.data-table-col[data-is-header]', this.header).map(col => {
+ const width = $.style($('.content', col), 'width');
+ const {
+ colIndex
+ } = $.data(col);
+ const column = this.getColumn(colIndex);
+
+ if (!column.minWidth) {
+ // only set this once
+ column.minWidth = width;
+ }
+ });
+ }
+
+ setupNaturalColumnWidth() {
+ if (!$('.data-table-row')) return;
+
+ // set initial width as naturally calculated by table's first row
+ $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
+ const {
+ colIndex
+ } = $.data($cell);
+ const column = this.datamanager.getColumn(colIndex);
+
+ let naturalWidth = $.style($('.content', $cell), 'width');
+
+ if (column.id === '_rowIndex') {
+ // width based on rowCount
+ const rowCount = this.datamanager.getRowCount();
+ const digits = (rowCount + '').length;
+ if (digits > 1) {
+ naturalWidth = naturalWidth + ((digits - 1) * 8);
+ }
+ }
+
+ column.naturalWidth = naturalWidth;
+ });
+ }
+
+ setupColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ if (!column.width) {
+ column.width = column.naturalWidth;
+ }
+ if (column.width < column.minWidth) {
+ column.width = column.minWidth;
+ }
+ });
+ }
+
+ distributeRemainingWidth() {
+ if (this.options.layout !== 'fluid') return;
+
+ const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
+ const headerWidth = $.style(this.header, 'width');
+ const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
+ const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
+
+ resizableColumns.map(col => {
+ const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
+ let finalWidth = Math.floor(width + deltaWidth) - 2;
+
+ this.datamanager.updateColumn(col.colIndex, {
+ width: finalWidth
+ });
+ });
+ }
+
+ setDefaultCellHeight() {
+ if (this.__cellHeightSet) return;
+ const height = this.options.cellHeight ||
+ $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
+ if (height) {
+ this.setCellHeight(height);
+ this.__cellHeightSet = true;
+ }
+ }
+
+ setCellHeight(height) {
+ this.setStyle('.data-table-col .content', {
+ height: height + 'px'
+ });
+ this.setStyle('.data-table-col .edit-cell', {
+ height: height + 'px'
+ });
+ }
+
+ setColumnStyle() {
+ // align columns
+ this.datamanager.getColumns()
+ .map(column => {
+ // alignment
+ if (['left', 'center', 'right'].includes(column.align)) {
+ this.setStyle(`[data-col-index="${column.colIndex}"]`, {
+ 'text-align': column.align
+ });
+ }
+ // width
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ this.setBodyStyle();
+ }
+
+ refreshColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ }
+
+ setBodyStyle() {
+ const width = $.style(this.header, 'width');
+
+ $.style(this.bodyScrollable, {
+ width: width + 'px'
+ });
+
+ $.style(this.bodyScrollable, {
+ marginTop: $.style(this.header, 'height') + 'px'
+ });
+
+ $.style($('table', this.bodyScrollable), {
+ margin: 0
+ });
+ }
+
+ getColumnHeaderElement(colIndex) {
+ colIndex = +colIndex;
+ if (colIndex < 0) return null;
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+}
+
+const KEYCODES = {
+ 13: 'enter',
+ 91: 'meta',
+ 16: 'shift',
+ 17: 'ctrl',
+ 18: 'alt',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 9: 'tab',
+ 27: 'esc',
+ 67: 'c',
+ 70: 'f'
+};
+
+class Keyboard {
+ constructor(element) {
+ this.listeners = {};
+ $.on(element, 'keydown', this.handler.bind(this));
+ }
+
+ handler(e) {
+ let key = KEYCODES[e.keyCode];
+
+ if (e.shiftKey && key !== 'shift') {
+ key = 'shift+' + key;
+ }
+
+ if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
+ key = 'ctrl+' + key;
+ }
+
+ const listeners = this.listeners[key];
+
+ if (listeners && listeners.length > 0) {
+ for (let listener of listeners) {
+ const preventBubbling = listener(e);
+ if (preventBubbling === undefined || preventBubbling === true) {
+ e.preventDefault();
+ }
+ }
+ }
+ }
+
+ on(key, listener) {
+ const keys = key.split(',').map(k => k.trim());
+
+ keys.map(key => {
+ this.listeners[key] = this.listeners[key] || [];
+ this.listeners[key].push(listener);
+ });
+ }
+}
+
+var DEFAULT_OPTIONS = {
+ columns: [],
+ data: [],
+ dropdownButton: '▼',
+ headerDropdown: [
+ {
+ label: 'Sort Ascending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'asc');
+ }
+ },
+ {
+ label: 'Sort Descending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'desc');
+ }
+ },
+ {
+ label: 'Reset sorting',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'none');
+ }
+ },
+ {
+ label: 'Remove column',
+ action: function (column) {
+ this.removeColumn(column.colIndex);
+ }
+ }
+ ],
+ events: {
+ onRemoveColumn(column) {},
+ onSwitchColumn(column1, column2) {},
+ onSortColumn(column) {}
+ },
+ sortIndicator: {
+ asc: '↑',
+ desc: '↓',
+ none: ''
+ },
+ freezeMessage: '',
+ getEditor: () => {},
+ addSerialNoColumn: true,
+ addCheckboxColumn: false,
+ enableClusterize: true,
+ enableLogs: false,
+ layout: 'fixed', // fixed, fluid
+ noDataMessage: 'No Data',
+ cellHeight: null,
+ enableInlineFilters: false
+};
+
+class DataTable {
+ constructor(wrapper, options) {
+ DataTable.instances++;
+
+ if (typeof wrapper === 'string') {
+ // css selector
+ wrapper = document.querySelector(wrapper);
+ }
+ this.wrapper = wrapper;
+ if (!(this.wrapper instanceof HTMLElement)) {
+ throw new Error('Invalid argument given for `wrapper`');
+ }
+
+ this.options = Object.assign({}, DEFAULT_OPTIONS, options);
+ this.options.headerDropdown =
+ DEFAULT_OPTIONS.headerDropdown
+ .concat(options.headerDropdown || []);
+ // custom user events
+ this.events = Object.assign({}, DEFAULT_OPTIONS.events, options.events || {});
+ this.fireEvent = this.fireEvent.bind(this);
+
+ this.prepare();
+
+ this.style = new Style(this);
+ this.keyboard = new Keyboard(this.wrapper);
+ this.datamanager = new DataManager(this.options);
+ this.rowmanager = new RowManager(this);
+ this.columnmanager = new ColumnManager(this);
+ this.cellmanager = new CellManager(this);
+ this.bodyRenderer = new BodyRenderer(this);
+
+ if (this.options.data) {
+ this.refresh();
+ }
+ }
+
+ prepare() {
+ this.prepareDom();
+ this.unfreeze();
+ }
+
+ prepareDom() {
+ this.wrapper.innerHTML = `
+
+
+
+
+
+ ${this.options.freezeMessage}
+
+
+
+ `;
+
+ this.datatableWrapper = $('.data-table', this.wrapper);
+ this.header = $('.data-table-header', this.wrapper);
+ this.bodyScrollable = $('.body-scrollable', this.wrapper);
+ this.freezeContainer = $('.freeze-container', this.wrapper);
+ }
+
+ refresh(data) {
+ this.datamanager.init(data);
+ this.render();
+ this.setDimensions();
+ }
+
+ destroy() {
+ this.wrapper.innerHTML = '';
+ this.style.destroy();
+ }
+
+ appendRows(rows) {
+ this.datamanager.appendRows(rows);
+ this.rowmanager.refreshRows();
+ }
+
+ refreshRow(row, rowIndex) {
+ this.rowmanager.refreshRow(row, rowIndex);
+ }
+
+ render() {
+ this.renderHeader();
+ this.renderBody();
+ }
+
+ renderHeader() {
+ this.columnmanager.renderHeader();
+ }
+
+ renderBody() {
+ this.bodyRenderer.render();
+ }
+
+ setDimensions() {
+ this.style.setDimensions();
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ getRows() {
+ return this.datamanager.getRows();
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getColumnHeaderElement(colIndex) {
+ return this.columnmanager.getColumnHeaderElement(colIndex);
+ }
+
+ getViewportHeight() {
+ if (!this.viewportHeight) {
+ this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ }
+
+ return this.viewportHeight;
+ }
+
+ sortColumn(colIndex, sortOrder) {
+ this.columnmanager.sortColumn(colIndex, sortOrder);
+ }
+
+ removeColumn(colIndex) {
+ this.columnmanager.removeColumn(colIndex);
+ }
+
+ scrollToLastColumn() {
+ this.datatableWrapper.scrollLeft = 9999;
+ }
+
+ freeze() {
+ $.style(this.freezeContainer, {
+ display: ''
+ });
+ }
+
+ unfreeze() {
+ $.style(this.freezeContainer, {
+ display: 'none'
+ });
+ }
+
+ fireEvent(eventName, ...args) {
+ this.events[eventName].apply(this, args);
+ }
+
+ log() {
+ if (this.options.enableLogs) {
+ console.log.apply(console, arguments);
+ }
+ }
+}
+
+DataTable.instances = 0;
+
+var name = "frappe-datatable";
+var version = "0.0.2";
+var description = "A modern datatable library for the web";
+var main = "dist/frappe-datatable.cjs.js";
+var scripts = {"start":"npm run dev","build":"rollup -c","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js","test:watch":"mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js"};
+var devDependencies = {"chai":"3.5.0","cssnano":"^3.10.0","deepmerge":"^2.0.1","eslint":"3.19.0","eslint-loader":"1.7.1","mocha":"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","precss":"^3.1.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify":"^3.0.0"};
+var repository = {"type":"git","url":"https://github.com/frappe/datatable.git"};
+var keywords = ["datatable","data","grid","table"];
+var author = "Faris Ansari";
+var license = "MIT";
+var bugs = {"url":"https://github.com/frappe/datatable/issues"};
+var homepage = "https://frappe.github.io/datatable";
+var dependencies = {"clusterize.js":"^0.18.0","lodash":"^4.17.5","sortablejs":"^1.7.0"};
+var packageJson = {
+ name: name,
+ version: version,
+ description: description,
+ main: main,
+ scripts: scripts,
+ devDependencies: devDependencies,
+ repository: repository,
+ keywords: keywords,
+ author: author,
+ license: license,
+ bugs: bugs,
+ homepage: homepage,
+ dependencies: dependencies
+};
+
+DataTable.__version__ = packageJson.version;
+
+return DataTable;
+
+}(Sortable,Clusterize));
diff --git a/docs/index.css b/docs/index.css
new file mode 100644
index 0000000..786b20d
--- /dev/null
+++ b/docs/index.css
@@ -0,0 +1,40 @@
+*, *::after, *::before {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+html, body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ font-size: 14px;
+}
+
+h1 {
+ font-size: 3rem;
+ margin: 1rem 0;
+}
+
+h2 {
+ font-size: 2.5rem;
+}
+
+.showcase {
+ margin: 0 auto;
+ padding: 4rem;
+ text-align: center;
+}
+
+.showcase p {
+ font-size: 2rem;
+ margin: 1rem 0;
+}
+
+.datatable-1, .datatable-2 {
+ width: 735px;
+ margin: 0 auto;
+ margin-top: 2rem;
+}
+
+.datatable-2 {
+ width: 934px;
+}
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..aef8ca7
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Frappe DataTable - A simple, modern datatable library for the web
+
+
+
+
+
+ Frappe DataTable
+ A simple, modern and interactive datatable for the web
+
+
+
+ Tree Structure
+ Show tree structured rows in your table
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/index.js b/docs/index.js
new file mode 100644
index 0000000..372b4af
--- /dev/null
+++ b/docs/index.js
@@ -0,0 +1,691 @@
+/* global DataTable */
+/* eslint-disable no-unused-vars */
+
+const {
+ columns,
+ data
+} = getSampleData();
+
+let datatable1 = new DataTable('.datatable-1', {
+ columns,
+ data
+});
+
+let datatable2 = new DataTable('.datatable-2', Object.assign(getTreeData(), {
+ enableInlineFilters: true
+}));
+
+function getSampleData(multiplier) {
+ let columns = ['Name', 'Position', 'Office', 'Extn.', 'Start Date', 'Salary'];
+ let data = [
+ ['Tiger Nixon', 'System Architect', 'Edinburgh', '5421', '2011/04/25', '$320,800'],
+ ['Garrett Winters', 'Accountant', 'Tokyo', '8422', '2011/07/25', '$170,750'],
+ ['Ashton Cox', 'Junior Technical Author', 'San Francisco', '1562', '2009/01/12', '$86,000'],
+ ['Cedric Kelly', 'Senior Javascript Developer', 'Edinburgh', '6224', '2012/03/29', '$433,060'],
+ ['Airi Satou', 'Accountant', 'Tokyo', '5407', '2008/11/28', '$162,700'],
+ ['Brielle Williamson', 'Integration Specialist', 'New York', '4804', '2012/12/02', '$372,000'],
+ ['Herrod Chandler', 'Sales Assistant', 'San Francisco', '9608', '2012/08/06', '$137,500'],
+ ['Rhona Davidson', 'Integration Specialist', 'Tokyo', '6200', '2010/10/14', '$327,900'],
+ ['Colleen Hurst', 'Javascript Developer', 'San Francisco', '2360', '2009/09/15', '$205,500'],
+ ['Sonya Frost', 'Software Engineer', 'Edinburgh', '1667', '2008/12/13', '$103,600'],
+ ['Jena Gaines', 'Office Manager', 'London', '3814', '2008/12/19', '$90,560'],
+ ['Quinn Flynn', 'Support Lead', 'Edinburgh', '9497', '2013/03/03', '$342,000'],
+ ['Charde Marshall', 'Regional Director', 'San Francisco', '6741', '2008/10/16', '$470,600'],
+ ['Haley Kennedy', 'Senior Marketing Designer', 'London', '3597', '2012/12/18', '$313,500'],
+ ['Tatyana Fitzpatrick', 'Regional Director', 'London', '1965', '2010/03/17', '$385,750'],
+ ['Michael Silva', 'Marketing Designer', 'London', '1581', '2012/11/27', '$198,500'],
+ ['Paul Byrd', 'Chief Financial Officer (CFO)', 'New York', '3059', '2010/06/09', '$725,000'],
+ ['Gloria Little', 'Systems Administrator', 'New York', '1721', '2009/04/10', '$237,500'],
+ ['Bradley Greer', 'Software Engineer', 'London', '2558', '2012/10/13', '$132,000'],
+ ['Dai Rios', 'Personnel Lead', 'Edinburgh', '2290', '2012/09/26', '$217,500'],
+ ['Jenette Caldwell', 'Development Lead', 'New York', '1937', '2011/09/03', '$345,000'],
+ ['Yuri Berry', 'Chief Marketing Officer (CMO)', 'New York', '6154', '2009/06/25', '$675,000'],
+ ['Caesar Vance', 'Pre-Sales Support', 'New York', '8330', '2011/12/12', '$106,450'],
+ ['Doris Wilder', 'Sales Assistant', 'Sidney', '3023', '2010/09/20', '$85,600'],
+ ['Angelica Ramos', 'Chief Executive Officer (CEO)', 'London', '5797', '2009/10/09', '$1,200,000'],
+ ['Gavin Joyce', 'Developer', 'Edinburgh', '8822', '2010/12/22', '$92,575'],
+ ['Jennifer Chang', 'Regional Director', 'Singapore', '9239', '2010/11/14', '$357,650'],
+ ['Brenden Wagner', 'Software Engineer', 'San Francisco', '1314', '2011/06/07', '$206,850'],
+ ['Fiona Green', 'Chief Operating Officer (COO)', 'San Francisco', '2947', '2010/03/11', '$850,000'],
+ ['Shou Itou', 'Regional Marketing', 'Tokyo', '8899', '2011/08/14', '$163,000'],
+ ['Michelle House', 'Integration Specialist', 'Sidney', '2769', '2011/06/02', '$95,400'],
+ ['Suki Burks', 'Developer', 'London', '6832', '2009/10/22', '$114,500'],
+ ['Prescott Bartlett', 'Technical Author', 'London', '3606', '2011/05/07', '$145,000'],
+ ['Gavin Cortez', 'Team Leader', 'San Francisco', '2860', '2008/10/26', '$235,500'],
+ ['Martena Mccray', 'Post-Sales support', 'Edinburgh', '8240', '2011/03/09', '$324,050'],
+ ['Unity Butler', 'Marketing Designer', 'San Francisco', '5384', '2009/12/09', '$85,675'],
+ ['Howard Hatfield', 'Office Manager', 'San Francisco', '7031', '2008/12/16', '$164,500'],
+ ['Hope Fuentes', 'Secretary', 'San Francisco', '6318', '2010/02/12', '$109,850'],
+ ['Vivian Harrell', 'Financial Controller', 'San Francisco', '9422', '2009/02/14', '$452,500'],
+ ['Timothy Mooney', 'Office Manager', 'London', '7580', '2008/12/11', '$136,200'],
+ ['Jackson Bradshaw', 'Director', 'New York', '1042', '2008/09/26', '$645,750'],
+ ['Olivia Liang', 'Support Engineer', 'Singapore', '2120', '2011/02/03', '$234,500'],
+ ['Bruno Nash', 'Software Engineer', 'London', '6222', '2011/05/03', '$163,500'],
+ ['Sakura Yamamoto', 'Support Engineer', 'Tokyo', '9383', '2009/08/19', '$139,575'],
+ ['Thor Walton', 'Developer', 'New York', '8327', '2013/08/11', '$98,540'],
+ ['Finn Camacho', 'Support Engineer', 'San Francisco', '2927', '2009/07/07', '$87,500'],
+ ['Serge Baldwin', 'Data Coordinator', 'Singapore', '8352', '2012/04/09', '$138,575'],
+ ['Zenaida Frank', 'Software Engineer', 'New York', '7439', '2010/01/04', '$125,250'],
+ ['Zorita Serrano', 'Software Engineer', 'San Francisco', '4389', '2012/06/01', '$115,000'],
+ ['Jennifer Acosta', 'Junior Javascript Developer', 'Edinburgh', '3431', '2013/02/01', '$75,650'],
+ ['Cara Stevens', 'Sales Assistant', 'New York', '3990', '2011/12/06', '$145,600'],
+ ['Hermione Butler', 'Regional Director', 'London', '1016', '2011/03/21', '$356,250'],
+ ['Lael Greer', 'Systems Administrator', 'London', '6733', '2009/02/27', '$103,500'],
+ ['Jonas Alexander', 'Developer', 'San Francisco', '8196', '2010/07/14', '$86,500'],
+ ['Shad Decker', 'Regional Director', 'Edinburgh', '6373', '2008/11/13', '$183,000'],
+ ['Michael Bruce', 'Javascript Developer', 'Singapore', '5384', '2011/06/27', '$183,000'],
+ ['Donna Snider', 'Customer Support', 'New York', '4226', '2011/01/25', '$112,000']
+ ];
+
+ if (multiplier) {
+ Array.from(new Array(multiplier - 1)).forEach(d => {
+ data = data.concat(data);
+ });
+ }
+
+ return {
+ columns,
+ data
+ };
+}
+
+function getTreeData() {
+ return {
+ columns: [{
+ 'id': 'account',
+ 'content': 'Account'
+ }, {
+ 'id': 'opening_debit',
+ 'content': 'Opening (Dr)'
+ }, {
+ 'id': 'opening_credit',
+ 'content': 'Opening (Cr)'
+ }, {
+ 'id': 'debit',
+ 'content': 'Debit'
+ }, {
+ 'id': 'credit',
+ 'content': 'Credit'
+ }, {
+ 'id': 'closing_debit',
+ 'content': 'Closing (Dr)'
+ }, {
+ 'id': 'closing_credit',
+ 'content': 'Closing (Cr)'
+ }, {
+ 'id': 'currency',
+ 'content': 'Currency',
+ 'hidden': 1
+ }],
+ data: [{
+ 'account_name': 'Application of Funds (Assets)',
+ 'account': 'Application of Funds (Assets) - GTPL',
+ 'parent_account': null,
+ 'indent': 0,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 12023729.54,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 12023729.54,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Current Assets',
+ 'account': 'Current Assets - GTPL',
+ 'parent_account': 'Application of Funds (Assets) - GTPL',
+ 'indent': 1,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 13960649.54,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 13960649.54,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Accounts Receivable',
+ 'account': 'Accounts Receivable - GTPL',
+ 'parent_account': 'Current Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 742790.474,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 742790.474,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Debtors',
+ 'account': 'Debtors - GTPL',
+ 'parent_account': 'Accounts Receivable - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 742790.474,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 742790.474,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Bank Accounts',
+ 'account': 'Bank Accounts - GTPL',
+ 'parent_account': 'Current Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 280676.822,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 280676.822,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Corporation Bank',
+ 'account': 'Corporation Bank - GTPL',
+ 'parent_account': 'Bank Accounts - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 290676.822,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 290676.822,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'HDFC Bank',
+ 'account': 'HDFC Bank - GTPL',
+ 'parent_account': 'Bank Accounts - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 10000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 10000.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Cash In Hand',
+ 'account': 'Cash In Hand - GTPL',
+ 'parent_account': 'Current Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 229904.494,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 229904.494,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Cash',
+ 'account': 'Cash - GTPL',
+ 'parent_account': 'Cash In Hand - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 229904.494,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 229904.494,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Stock Assets',
+ 'account': 'Stock Assets - GTPL',
+ 'parent_account': 'Current Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 12707277.75,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 12707277.75,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'All Warehouses',
+ 'account': 'All Warehouses - GTPL',
+ 'parent_account': 'Stock Assets - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 12707277.75,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 12707277.75,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Finished Goods',
+ 'account': 'Finished Goods - GTPL',
+ 'parent_account': 'All Warehouses - GTPL',
+ 'indent': 4,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 87320.3,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 87320.3,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Retail Stores',
+ 'account': 'Retail Stores - GTPL',
+ 'parent_account': 'All Warehouses - GTPL',
+ 'indent': 4,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 4540590.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 4540590.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Bandra Store',
+ 'account': 'Bandra Store - GTPL',
+ 'parent_account': 'Retail Stores - GTPL',
+ 'indent': 5,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 3246800.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 3246800.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Central Warehouse',
+ 'account': 'Central Warehouse - GTPL',
+ 'parent_account': 'Retail Stores - GTPL',
+ 'indent': 5,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 1236790.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 1236790.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Lower Parel Store',
+ 'account': 'Lower Parel Store - GTPL',
+ 'parent_account': 'Retail Stores - GTPL',
+ 'indent': 5,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 57000.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 57000.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Stores',
+ 'account': 'Stores - GTPL',
+ 'parent_account': 'All Warehouses - GTPL',
+ 'indent': 4,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 8016525.27,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 8016525.27,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Work In Progress',
+ 'account': 'Work In Progress - GTPL',
+ 'parent_account': 'All Warehouses - GTPL',
+ 'indent': 4,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 62842.18,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 62842.18,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Fixed Assets',
+ 'account': 'Fixed Assets - GTPL',
+ 'parent_account': 'Application of Funds (Assets) - GTPL',
+ 'indent': 1,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 19920.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 19920.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Electronic Equipments',
+ 'account': 'Electronic Equipments - GTPL',
+ 'parent_account': 'Fixed Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 80.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 80.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Furnitures and Fixtures',
+ 'account': 'Furnitures and Fixtures - GTPL',
+ 'parent_account': 'Fixed Assets - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 20000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 20000.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Temporary Accounts',
+ 'account': 'Temporary Accounts - GTPL',
+ 'parent_account': 'Application of Funds (Assets) - GTPL',
+ 'indent': 1,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 1917000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 1917000.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Temporary Opening',
+ 'account': 'Temporary Opening - GTPL',
+ 'parent_account': 'Temporary Accounts - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 1917000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 1917000.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Source of Funds (Liabilities)',
+ 'account': 'Source of Funds (Liabilities) - GTPL',
+ 'parent_account': null,
+ 'indent': 0,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 2371628.002,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 2371628.002,
+ 'has_value': true
+ }, {
+ 'account_name': 'Current Liabilities',
+ 'account': 'Current Liabilities - GTPL',
+ 'parent_account': 'Source of Funds (Liabilities) - GTPL',
+ 'indent': 1,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 2371628.002,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 2371628.002,
+ 'has_value': true
+ }, {
+ 'account_name': 'Accounts Payable',
+ 'account': 'Accounts Payable - GTPL',
+ 'parent_account': 'Current Liabilities - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 368311.85,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 368311.85,
+ 'has_value': true
+ }, {
+ 'account_name': 'Creditors',
+ 'account': 'Creditors - GTPL',
+ 'parent_account': 'Accounts Payable - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 194871.85,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 194871.85,
+ 'has_value': true
+ }, {
+ 'account_name': 'Salary Payable',
+ 'account': 'Salary Payable - GTPL',
+ 'parent_account': 'Accounts Payable - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 173440.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 173440.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Duties and Taxes',
+ 'account': 'Duties and Taxes - GTPL',
+ 'parent_account': 'Current Liabilities - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 150146.822,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 150146.822,
+ 'has_value': true
+ }, {
+ 'account_name': 'CGST',
+ 'account': 'CGST - GTPL',
+ 'parent_account': 'Duties and Taxes - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 51479.591,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 51479.591,
+ 'has_value': true
+ }, {
+ 'account_name': 'IGST',
+ 'account': 'IGST - GTPL',
+ 'parent_account': 'Duties and Taxes - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 1944.0,
+ 'opening_credit': 0.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 1944.0,
+ 'closing_credit': 0.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'SGST',
+ 'account': 'SGST - GTPL',
+ 'parent_account': 'Duties and Taxes - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 97711.231,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 97711.231,
+ 'has_value': true
+ }, {
+ 'account_name': 'UGST',
+ 'account': 'UGST - GTPL',
+ 'parent_account': 'Duties and Taxes - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 2900.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 2900.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Stock Liabilities',
+ 'account': 'Stock Liabilities - GTPL',
+ 'parent_account': 'Current Liabilities - GTPL',
+ 'indent': 2,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 1853169.33,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 1853169.33,
+ 'has_value': true
+ }, {
+ 'account_name': 'Stock Received But Not Billed',
+ 'account': 'Stock Received But Not Billed - GTPL',
+ 'parent_account': 'Stock Liabilities - GTPL',
+ 'indent': 3,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 1853169.33,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 1853169.33,
+ 'has_value': true
+ }, {
+ 'account_name': 'Equity',
+ 'account': 'Equity - GTPL',
+ 'parent_account': null,
+ 'indent': 0,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 10000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 10000.0,
+ 'has_value': true
+ }, {
+ 'account_name': 'Capital Stock',
+ 'account': 'Capital Stock - GTPL',
+ 'parent_account': 'Equity - GTPL',
+ 'indent': 1,
+ 'from_date': '2018-04-01',
+ 'to_date': '2019-03-31',
+ 'currency': 'INR',
+ 'opening_debit': 0.0,
+ 'opening_credit': 10000.0,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 0.0,
+ 'closing_credit': 10000.0,
+ 'has_value': true
+ }, {}, {
+ 'account': 'Total',
+ 'account_name': 'Total',
+ 'warn_if_negative': true,
+ 'opening_debit': 32260956.43,
+ 'opening_credit': 22618854.891999997,
+ 'debit': 0.0,
+ 'credit': 0.0,
+ 'closing_debit': 32260956.43,
+ 'closing_credit': 22618854.891999997,
+ 'parent_account': null,
+ 'indent': 0,
+ 'has_value': true,
+ 'currency': 'INR'
+ }]
+ };
+}
diff --git a/rollup.config.js b/rollup.config.js
index 5aacb6b..4e618b3 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -8,34 +8,45 @@ import cssnext from 'postcss-cssnext';
// import cssnano from 'cssnano';
const dev = {
- input: 'src/index.js',
- output: {
- file: 'dist/frappe-datatable.js',
- format: 'iife',
- name: 'DataTable',
- globals: {
- sortablejs: 'Sortable',
- 'clusterize.js': 'Clusterize'
- }
- },
- plugins: [
- json(),
- nodeResolve(),
- commonjs(),
- postcss({
- extract: 'dist/frappe-datatable.css',
- plugins: [
- nested(),
- cssnext()
- ]
- })
- ],
- external: ['sortablejs', 'clusterize.js']
+ input: 'src/index.js',
+ output: [{
+ file: 'dist/frappe-datatable.js',
+ format: 'iife',
+ name: 'DataTable',
+ globals: {
+ sortablejs: 'Sortable',
+ 'clusterize.js': 'Clusterize'
+ }
+ }, {
+ file: 'docs/frappe-datatable.js',
+ format: 'iife',
+ name: 'DataTable',
+ globals: {
+ sortablejs: 'Sortable',
+ 'clusterize.js': 'Clusterize'
+ }
+ }],
+ plugins: [
+ json(),
+ nodeResolve(),
+ commonjs(),
+ postcss({
+ extract: ['dist/frappe-datatable.css', 'docs/frappe-datatable.css'],
+ plugins: [
+ nested(),
+ cssnext()
+ ]
+ })
+ ],
+ external: ['sortablejs', 'clusterize.js']
};
-export default [dev, Object.assign({}, dev, {
- output: {
- format: 'cjs',
- file: 'dist/frappe-datatable.cjs.js'
- }
-})];
+export default [
+ dev,
+ Object.assign({}, dev, {
+ output: {
+ format: 'cjs',
+ file: 'dist/frappe-datatable.cjs.js'
+ }
+ })
+];
diff --git a/src/body-renderer.js b/src/body-renderer.js
index 5c1cb5e..17e8435 100644
--- a/src/body-renderer.js
+++ b/src/body-renderer.js
@@ -1,99 +1,101 @@
import $ from './dom';
import Clusterize from 'clusterize.js';
-import { promisify } from './utils';
+import {
+ promisify
+} from './utils';
export default class BodyRenderer {
- constructor(instance) {
- this.instance = instance;
- this.options = instance.options;
- this.datamanager = instance.datamanager;
- this.rowmanager = instance.rowmanager;
- this.cellmanager = instance.cellmanager;
- this.bodyScrollable = instance.bodyScrollable;
- this.log = instance.log;
- this.appendRemainingData = promisify(this.appendRemainingData, this);
- }
-
- render() {
- if (this.options.enableClusterize) {
- this.renderBodyWithClusterize();
- } else {
- this.renderBodyHTML();
- }
- }
-
- renderBodyHTML() {
- const rows = this.datamanager.getRows();
-
- this.bodyScrollable.innerHTML = `
-
- `;
- this.instance.setDimensions();
- this.restoreState();
- }
-
- renderBodyWithClusterize() {
- // first page
- const rows = this.datamanager.getRows(0, 20);
- const initialData = this.getDataForClusterize(rows);
-
- if (!this.clusterize) {
- // empty body
- this.bodyScrollable.innerHTML = `
-
- `;
-
- // first 20 rows will appended
- // rest of them in nextTick
- this.clusterize = new Clusterize({
- rows: initialData,
- scrollElem: this.bodyScrollable,
- contentElem: $('tbody', this.bodyScrollable),
- callbacks: {
- clusterChanged: () => {
- this.restoreState();
- }
- },
- /* eslint-disable */
- no_data_text: this.options.noDataMessage,
- no_data_class: 'empty-state'
- /* eslint-enable */
- });
-
- // setDimensions requires atleast 1 row to exist in dom
- this.instance.setDimensions();
- } else {
- this.clusterize.update(initialData);
+ constructor(instance) {
+ this.instance = instance;
+ this.options = instance.options;
+ this.datamanager = instance.datamanager;
+ this.rowmanager = instance.rowmanager;
+ this.cellmanager = instance.cellmanager;
+ this.bodyScrollable = instance.bodyScrollable;
+ this.log = instance.log;
+ this.appendRemainingData = promisify(this.appendRemainingData, this);
}
- this.appendRemainingData();
- }
+ render() {
+ if (this.options.enableClusterize) {
+ this.renderBodyWithClusterize();
+ } else {
+ this.renderBodyHTML();
+ }
+ }
- restoreState() {
- this.rowmanager.highlightCheckedRows();
- this.cellmanager.selectAreaOnClusterChanged();
- this.cellmanager.focusCellOnClusterChanged();
- }
+ renderBodyHTML() {
+ const rows = this.datamanager.getRows();
- appendRemainingData() {
- const rows = this.datamanager.getRows(20);
- const data = this.getDataForClusterize(rows);
- this.clusterize.append(data);
- }
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML(rows)}
+
+ `;
+ this.instance.setDimensions();
+ this.restoreState();
+ }
- getDataForClusterize(rows) {
- return rows.map((row) => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex }));
- }
+ renderBodyWithClusterize() {
+ // first page
+ const rows = this.datamanager.getRows(0, 20);
+ const initialData = this.getDataForClusterize(rows);
+
+ if (!this.clusterize) {
+ // empty body
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML([])}
+
+ `;
+
+ // first 20 rows will appended
+ // rest of them in nextTick
+ this.clusterize = new Clusterize({
+ rows: initialData,
+ scrollElem: this.bodyScrollable,
+ contentElem: $('tbody', this.bodyScrollable),
+ callbacks: {
+ clusterChanged: () => {
+ this.restoreState();
+ }
+ },
+ /* eslint-disable */
+ no_data_text: this.options.noDataMessage,
+ no_data_class: 'empty-state'
+ /* eslint-enable */
+ });
+
+ // setDimensions requires atleast 1 row to exist in dom
+ this.instance.setDimensions();
+ } else {
+ this.clusterize.update(initialData);
+ }
+
+ this.appendRemainingData();
+ }
+
+ restoreState() {
+ this.rowmanager.highlightCheckedRows();
+ this.cellmanager.selectAreaOnClusterChanged();
+ this.cellmanager.focusCellOnClusterChanged();
+ }
+
+ appendRemainingData() {
+ const rows = this.datamanager.getRows(20);
+ const data = this.getDataForClusterize(rows);
+ this.clusterize.append(data);
+ }
+
+ getDataForClusterize(rows) {
+ return rows.map((row) => this.rowmanager.getRowHTML(row, row.meta));
+ }
+
+ getBodyHTML(rows) {
+ return `
+
+ ${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
+
+ `;
+ }
};
-
-export function getBodyHTML(rows) {
- return `
-
- ${rows.map(row => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
-
- `;
-}
diff --git a/src/cellmanager.js b/src/cellmanager.js
index 12645e2..b8ea6c3 100644
--- a/src/cellmanager.js
+++ b/src/cellmanager.js
@@ -1,609 +1,657 @@
import {
- copyTextToClipboard,
- makeDataAttributeString,
- throttle
+ copyTextToClipboard,
+ makeDataAttributeString,
+ throttle
} from './utils';
import $ from './dom';
-import { getDropdownHTML } from './columnmanager';
+import {
+ getDropdownHTML
+} from './columnmanager';
export default class CellManager {
- constructor(instance) {
- this.instance = instance;
- this.wrapper = this.instance.wrapper;
- this.options = this.instance.options;
- this.style = this.instance.style;
- this.bodyScrollable = this.instance.bodyScrollable;
- this.columnmanager = this.instance.columnmanager;
- this.rowmanager = this.instance.rowmanager;
- this.datamanager = this.instance.datamanager;
- this.keyboard = this.instance.keyboard;
+ constructor(instance) {
+ this.instance = instance;
+ this.wrapper = this.instance.wrapper;
+ this.options = this.instance.options;
+ this.style = this.instance.style;
+ this.bodyScrollable = this.instance.bodyScrollable;
+ this.columnmanager = this.instance.columnmanager;
+ this.rowmanager = this.instance.rowmanager;
+ this.datamanager = this.instance.datamanager;
+ this.keyboard = this.instance.keyboard;
- this.bindEvents();
- }
+ this.bindEvents();
+ }
- bindEvents() {
- this.bindFocusCell();
- this.bindEditCell();
- this.bindKeyboardSelection();
- this.bindCopyCellContents();
- this.bindMouseEvents();
- }
+ bindEvents() {
+ this.bindFocusCell();
+ this.bindEditCell();
+ this.bindKeyboardSelection();
+ this.bindCopyCellContents();
+ this.bindMouseEvents();
+ }
- bindFocusCell() {
- this.bindKeyboardNav();
- }
+ bindFocusCell() {
+ this.bindKeyboardNav();
+ }
- bindEditCell() {
- this.$editingCell = null;
+ bindEditCell() {
+ this.$editingCell = null;
- $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
- this.activateEditing(cell);
- });
+ $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
+ this.activateEditing(cell);
+ });
+
+ this.keyboard.on('enter', (e) => {
+ if (this.$focusedCell && !this.$editingCell) {
+ // enter keypress on focused cell
+ this.activateEditing(this.$focusedCell);
+ } else if (this.$editingCell) {
+ // enter keypress on editing cell
+ this.submitEditing();
+ this.deactivateEditing();
+ }
+ });
+ }
+
+ bindKeyboardNav() {
+ const focusCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+
+ if (direction === 'left' || direction === 'shift+tab') {
+ $cell = this.getLeftCell$($cell);
+ } else if (direction === 'right' || direction === 'tab') {
+ $cell = this.getRightCell$($cell);
+ } else if (direction === 'up') {
+ $cell = this.getAboveCell$($cell);
+ } else if (direction === 'down') {
+ $cell = this.getBelowCell$($cell);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ const focusLastCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ if (direction === 'left') {
+ $cell = this.getLeftMostCell$(rowIndex);
+ } else if (direction === 'right') {
+ $cell = this.getRightMostCell$(rowIndex);
+ } else if (direction === 'up') {
+ $cell = this.getTopMostCell$(colIndex);
+ } else if (direction === 'down') {
+ $cell = this.getBottomMostCell$(colIndex);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
+ direction => this.keyboard.on(direction, () => focusCell(direction))
+ );
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
+ );
+
+ this.keyboard.on('esc', () => {
+ this.deactivateEditing();
+ });
+
+ if (this.options.enableInlineFilters) {
+ this.keyboard.on('ctrl+f', (e) => {
+ const $cell = $.closest('.data-table-col', e.target);
+ let {
+ colIndex
+ } = $.data($cell);
+
+ this.activateFilter(colIndex);
+ return true;
+ });
+ }
+ }
+
+ bindKeyboardSelection() {
+ const getNextSelectionCursor = (direction) => {
+ let $selectionCursor = this.getSelectionCursor();
+
+ if (direction === 'left') {
+ $selectionCursor = this.getLeftCell$($selectionCursor);
+ } else if (direction === 'right') {
+ $selectionCursor = this.getRightCell$($selectionCursor);
+ } else if (direction === 'up') {
+ $selectionCursor = this.getAboveCell$($selectionCursor);
+ } else if (direction === 'down') {
+ $selectionCursor = this.getBelowCell$($selectionCursor);
+ }
+
+ return $selectionCursor;
+ };
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('shift+' + direction,
+ () => this.selectArea(getNextSelectionCursor(direction)))
+ );
+ }
+
+ bindCopyCellContents() {
+ this.keyboard.on('ctrl+c', () => {
+ this.copyCellContents(this.$focusedCell, this.$selectionCursor);
+ });
+ }
+
+ bindMouseEvents() {
+ let mouseDown = null;
+
+ $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
+ mouseDown = true;
+ this.focusCell($(e.delegatedTarget));
+ });
+
+ $.on(this.bodyScrollable, 'mouseup', () => {
+ mouseDown = false;
+ });
+
+ const selectArea = (e) => {
+ if (!mouseDown) return;
+ this.selectArea($(e.delegatedTarget));
+ };
+
+ $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle(selectArea, 50));
+ }
+
+ focusCell($cell, {
+ skipClearSelection = 0
+ } = {}) {
+ if (!$cell) return;
+
+ // don't focus if already editing cell
+ if ($cell === this.$editingCell) return;
+
+ const {
+ colIndex,
+ isHeader
+ } = $.data($cell);
+ if (isHeader) {
+ return;
+ }
+
+ const column = this.columnmanager.getColumn(colIndex);
+ if (column.focusable === false) {
+ return;
+ }
+
+ this.scrollToCell($cell);
- this.keyboard.on('enter', (e) => {
- if (this.$focusedCell && !this.$editingCell) {
- // enter keypress on focused cell
- this.activateEditing(this.$focusedCell);
- } else if (this.$editingCell) {
- // enter keypress on editing cell
- this.submitEditing();
this.deactivateEditing();
- }
- });
- }
+ if (!skipClearSelection) {
+ this.clearSelection();
+ }
- bindKeyboardNav() {
- const focusCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
+ if (this.$focusedCell) {
+ this.$focusedCell.classList.remove('selected');
+ }
- let $cell = this.$focusedCell;
+ this.$focusedCell = $cell;
+ $cell.classList.add('selected');
- if (direction === 'left' || direction === 'shift+tab') {
- $cell = this.getLeftCell$($cell);
- } else if (direction === 'right' || direction === 'tab') {
- $cell = this.getRightCell$($cell);
- } else if (direction === 'up') {
- $cell = this.getAboveCell$($cell);
- } else if (direction === 'down') {
- $cell = this.getBelowCell$($cell);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- const focusLastCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
-
- let $cell = this.$focusedCell;
- const { rowIndex, colIndex } = $.data($cell);
-
- if (direction === 'left') {
- $cell = this.getLeftMostCell$(rowIndex);
- } else if (direction === 'right') {
- $cell = this.getRightMostCell$(rowIndex);
- } else if (direction === 'up') {
- $cell = this.getTopMostCell$(colIndex);
- } else if (direction === 'down') {
- $cell = this.getBottomMostCell$(colIndex);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
- direction => this.keyboard.on(direction, () => focusCell(direction))
- );
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
- );
-
- this.keyboard.on('esc', () => {
- this.deactivateEditing();
- });
-
- if (this.options.enableInlineFilters) {
- this.keyboard.on('ctrl+f', (e) => {
- const $cell = $.closest('.data-table-col', e.target);
- let { colIndex } = $.data($cell);
-
- this.activateFilter(colIndex);
- return true;
- });
- }
- }
-
- bindKeyboardSelection() {
- const getNextSelectionCursor = (direction) => {
- let $selectionCursor = this.getSelectionCursor();
-
- if (direction === 'left') {
- $selectionCursor = this.getLeftCell$($selectionCursor);
- } else if (direction === 'right') {
- $selectionCursor = this.getRightCell$($selectionCursor);
- } else if (direction === 'up') {
- $selectionCursor = this.getAboveCell$($selectionCursor);
- } else if (direction === 'down') {
- $selectionCursor = this.getBelowCell$($selectionCursor);
- }
-
- return $selectionCursor;
- };
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('shift+' + direction,
- () => this.selectArea(getNextSelectionCursor(direction)))
- );
- }
-
- bindCopyCellContents() {
- this.keyboard.on('ctrl+c', () => {
- this.copyCellContents(this.$focusedCell, this.$selectionCursor);
- });
- }
-
- bindMouseEvents() {
- let mouseDown = null;
-
- $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
- mouseDown = true;
- this.focusCell($(e.delegatedTarget));
- });
-
- $.on(this.bodyScrollable, 'mouseup', () => {
- mouseDown = false;
- });
-
- const selectArea = (e) => {
- if (!mouseDown) return;
- this.selectArea($(e.delegatedTarget));
- };
-
- $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle(selectArea, 50));
- }
-
- focusCell($cell, { skipClearSelection = 0 } = {}) {
- if (!$cell) return;
-
- // don't focus if already editing cell
- if ($cell === this.$editingCell) return;
-
- const { colIndex, isHeader } = $.data($cell);
- if (isHeader) {
- return;
- }
-
- const column = this.columnmanager.getColumn(colIndex);
- if (column.focusable === false) {
- return;
- }
-
- this.scrollToCell($cell);
-
- this.deactivateEditing();
- if (!skipClearSelection) {
- this.clearSelection();
- }
-
- if (this.$focusedCell) {
- this.$focusedCell.classList.remove('selected');
- }
-
- this.$focusedCell = $cell;
- $cell.classList.add('selected');
-
- // so that keyboard nav works
- $cell.focus();
-
- this.highlightRowColumnHeader($cell);
- }
-
- highlightRowColumnHeader($cell) {
- const { colIndex, rowIndex } = $.data($cell);
- const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
- const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
- const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
-
- if (this.lastHeaders) {
- $.removeStyle(this.lastHeaders, 'backgroundColor');
- }
-
- const colHeader = $(colHeaderSelector, this.wrapper);
- const rowHeader = $(rowHeaderSelector, this.wrapper);
-
- $.style([colHeader, rowHeader], {
- backgroundColor: '#f5f7fa' // light-bg
- });
-
- this.lastHeaders = [colHeader, rowHeader];
- }
-
- selectAreaOnClusterChanged() {
- if (!(this.$focusedCell && this.$selectionCursor)) return;
- const { colIndex, rowIndex } = $.data(this.$selectionCursor);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell || $cell === this.$selectionCursor) return;
-
- // selectArea needs $focusedCell
- const fCell = $.data(this.$focusedCell);
- this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
-
- this.selectArea($cell);
- }
-
- focusCellOnClusterChanged() {
- if (!this.$focusedCell) return;
-
- const { colIndex, rowIndex } = $.data(this.$focusedCell);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell) return;
- // this function is called after selectAreaOnClusterChanged,
- // focusCell calls clearSelection which resets the area selection
- // so a flag to skip it
- this.focusCell($cell, { skipClearSelection: 1 });
- }
-
- selectArea($selectionCursor) {
- if (!this.$focusedCell) return;
-
- if (this._selectArea(this.$focusedCell, $selectionCursor)) {
- // valid selection
- this.$selectionCursor = $selectionCursor;
- }
- };
-
- _selectArea($cell1, $cell2) {
- if ($cell1 === $cell2) return false;
-
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return false;
-
- this.clearSelection();
- cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
- return true;
- }
-
- getCellsInRange($cell1, $cell2) {
- let colIndex1, rowIndex1, colIndex2, rowIndex2;
-
- if (typeof $cell1 === 'number') {
- [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
- } else
- if (typeof $cell1 === 'object') {
-
- if (!($cell1 && $cell2)) {
- return false;
- }
-
- const cell1 = $.data($cell1);
- const cell2 = $.data($cell2);
-
- colIndex1 = cell1.colIndex;
- rowIndex1 = cell1.rowIndex;
- colIndex2 = cell2.colIndex;
- rowIndex2 = cell2.rowIndex;
- }
-
- if (rowIndex1 > rowIndex2) {
- [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
- }
-
- if (colIndex1 > colIndex2) {
- [colIndex1, colIndex2] = [colIndex2, colIndex1];
- }
-
- if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
- return false;
- }
-
- let cells = [];
- let colIndex = colIndex1;
- let rowIndex = rowIndex1;
- let rowIndices = [];
-
- while (rowIndex <= rowIndex2) {
- rowIndices.push(rowIndex);
- rowIndex++;
- }
-
- rowIndices.map(rowIndex => {
- while (colIndex <= colIndex2) {
- cells.push([colIndex, rowIndex]);
- colIndex++;
- }
- colIndex = colIndex1;
- });
-
- return cells;
- }
-
- clearSelection() {
- $.each('.data-table-col.highlight', this.bodyScrollable)
- .map(cell => cell.classList.remove('highlight'));
-
- this.$selectionCursor = null;
- }
-
- getSelectionCursor() {
- return this.$selectionCursor || this.$focusedCell;
- }
-
- activateEditing($cell) {
- this.focusCell($cell);
- const { rowIndex, colIndex } = $.data($cell);
-
- const col = this.columnmanager.getColumn(colIndex);
- if (col && (col.editable === false || col.focusable === false)) {
- return;
- }
-
- const cell = this.getCell(colIndex, rowIndex);
- if (cell && cell.editable === false) {
- return;
- }
-
- if (this.$editingCell) {
- const { _rowIndex, _colIndex } = $.data(this.$editingCell);
-
- if (rowIndex === _rowIndex && colIndex === _colIndex) {
- // editing the same cell
- return;
- }
- }
-
- this.$editingCell = $cell;
- $cell.classList.add('editing');
-
- const $editCell = $('.edit-cell', $cell);
- $editCell.innerHTML = '';
-
- const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
-
- if (editor) {
- this.currentCellEditor = editor;
- // initialize editing input with cell value
- editor.initValue(cell.content, rowIndex, col);
- }
- }
-
- deactivateEditing() {
- // keep focus on the cell so that keyboard navigation works
- if (this.$focusedCell) this.$focusedCell.focus();
-
- if (!this.$editingCell) return;
- this.$editingCell.classList.remove('editing');
- this.$editingCell = null;
- }
-
- getEditor(colIndex, rowIndex, value, parent) {
- // debugger;
- const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
- if (obj && obj.setValue) return obj;
-
- // editing fallback
- const $input = $.create('input', {
- class: 'input-style',
- type: 'text',
- inside: parent
- });
-
- return {
- initValue(value) {
- $input.focus();
- $input.value = value;
- },
- getValue() {
- return $input.value;
- },
- setValue(value) {
- $input.value = value;
- }
- };
- }
-
- submitEditing() {
- if (!this.$editingCell) return;
- const $cell = this.$editingCell;
- const { rowIndex, colIndex } = $.data($cell);
- const col = this.datamanager.getColumn(colIndex);
-
- if ($cell) {
- const editor = this.currentCellEditor;
-
- if (editor) {
- const value = editor.getValue();
- const done = editor.setValue(value, rowIndex, col);
- const oldValue = this.getCell(colIndex, rowIndex).content;
-
- // update cell immediately
- this.updateCell(colIndex, rowIndex, value);
+ // so that keyboard nav works
$cell.focus();
- if (done && done.then) {
- // revert to oldValue if promise fails
- done.catch((e) => {
- console.log(e);
- this.updateCell(colIndex, rowIndex, oldValue);
- });
+ this.highlightRowColumnHeader($cell);
+ }
+
+ highlightRowColumnHeader($cell) {
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell);
+ const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
+ const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
+ const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
+
+ if (this.lastHeaders) {
+ $.removeStyle(this.lastHeaders, 'backgroundColor');
}
- }
+
+ const colHeader = $(colHeaderSelector, this.wrapper);
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
+
+ $.style([colHeader, rowHeader], {
+ backgroundColor: '#f5f7fa' // light-bg
+ });
+
+ this.lastHeaders = [colHeader, rowHeader];
}
- this.currentCellEditor = null;
- }
+ selectAreaOnClusterChanged() {
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$selectionCursor);
+ const $cell = this.getCell$(colIndex, rowIndex);
- copyCellContents($cell1, $cell2) {
- if (!$cell2 && $cell1) {
- // copy only focusedCell
- const { colIndex, rowIndex } = $.data($cell1);
- const cell = this.getCell(colIndex, rowIndex);
- copyTextToClipboard(cell.content);
- return;
+ if (!$cell || $cell === this.$selectionCursor) return;
+
+ // selectArea needs $focusedCell
+ const fCell = $.data(this.$focusedCell);
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
+
+ this.selectArea($cell);
}
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return;
+ focusCellOnClusterChanged() {
+ if (!this.$focusedCell) return;
- const values = cells
- // get cell objects
- .map(index => this.getCell(...index))
- // convert to array of rows
- .reduce((acc, curr) => {
- const rowIndex = curr.rowIndex;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$focusedCell);
+ const $cell = this.getCell$(colIndex, rowIndex);
- acc[rowIndex] = acc[rowIndex] || [];
- acc[rowIndex].push(curr.content);
-
- return acc;
- }, [])
- // join values by tab
- .map(row => row.join('\t'))
- // join rows by newline
- .join('\n');
-
- copyTextToClipboard(values);
- }
-
- activateFilter(colIndex) {
- this.columnmanager.toggleFilter();
- this.columnmanager.focusFilter(colIndex);
-
- if (!this.columnmanager.isFilterShown) {
- // put focus back on cell
- this.$focusedCell.focus();
+ if (!$cell) return;
+ // this function is called after selectAreaOnClusterChanged,
+ // focusCell calls clearSelection which resets the area selection
+ // so a flag to skip it
+ this.focusCell($cell, {
+ skipClearSelection: 1
+ });
}
- }
- updateCell(colIndex, rowIndex, value) {
- const cell = this.datamanager.updateCell(colIndex, rowIndex, {
- content: value
- });
- this.refreshCell(cell);
- }
+ selectArea($selectionCursor) {
+ if (!this.$focusedCell) return;
- refreshCell(cell) {
- const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
- $cell.innerHTML = this.getCellContent(cell);
- }
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
+ // valid selection
+ this.$selectionCursor = $selectionCursor;
+ }
+ };
- isStandardCell(colIndex) {
- // Standard cells are in Sr. No and Checkbox column
- return colIndex < this.columnmanager.getFirstColumnIndex();
- }
+ _selectArea($cell1, $cell2) {
+ if ($cell1 === $cell2) return false;
- getCell$(colIndex, rowIndex) {
- return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
- }
+ const cells = this.getCellsInRange($cell1, $cell2);
+ if (!cells) return false;
- getAboveCell$($cell) {
- const { colIndex } = $.data($cell);
- const $aboveRow = $cell.parentElement.previousElementSibling;
+ this.clearSelection();
+ cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
+ return true;
+ }
- return $(`[data-col-index="${colIndex}"]`, $aboveRow);
- }
+ getCellsInRange($cell1, $cell2) {
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
- getBelowCell$($cell) {
- const { colIndex } = $.data($cell);
- const $belowRow = $cell.parentElement.nextElementSibling;
+ if (typeof $cell1 === 'number') {
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
+ } else
+ if (typeof $cell1 === 'object') {
- return $(`[data-col-index="${colIndex}"]`, $belowRow);
- }
+ if (!($cell1 && $cell2)) {
+ return false;
+ }
- getLeftCell$($cell) {
- return $cell.previousElementSibling;
- }
+ const cell1 = $.data($cell1);
+ const cell2 = $.data($cell2);
- getRightCell$($cell) {
- return $cell.nextElementSibling;
- }
+ colIndex1 = cell1.colIndex;
+ rowIndex1 = cell1.rowIndex;
+ colIndex2 = cell2.colIndex;
+ rowIndex2 = cell2.rowIndex;
+ }
- getLeftMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
- }
+ if (rowIndex1 > rowIndex2) {
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
+ }
- getRightMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
- }
+ if (colIndex1 > colIndex2) {
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
+ }
- getTopMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
- }
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
+ return false;
+ }
- getBottomMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
- }
+ let cells = [];
+ let colIndex = colIndex1;
+ let rowIndex = rowIndex1;
+ let rowIndices = [];
- getCell(colIndex, rowIndex) {
- return this.instance.datamanager.getCell(colIndex, rowIndex);
- }
+ while (rowIndex <= rowIndex2) {
+ rowIndices.push(rowIndex);
+ rowIndex++;
+ }
- getCellAttr($cell) {
- return this.instance.getCellAttr($cell);
- }
+ rowIndices.map(rowIndex => {
+ while (colIndex <= colIndex2) {
+ cells.push([colIndex, rowIndex]);
+ colIndex++;
+ }
+ colIndex = colIndex1;
+ });
- getRowHeight() {
- return $.style($('.data-table-row', this.bodyScrollable), 'height');
- }
+ return cells;
+ }
- scrollToCell($cell) {
- if ($.inViewport($cell, this.bodyScrollable)) return false;
+ clearSelection() {
+ $.each('.data-table-col.highlight', this.bodyScrollable)
+ .map(cell => cell.classList.remove('highlight'));
- const { rowIndex } = $.data($cell);
- this.rowmanager.scrollToRow(rowIndex);
- return false;
- }
+ this.$selectionCursor = null;
+ }
- getRowCountPerPage() {
- return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
- }
+ getSelectionCursor() {
+ return this.$selectionCursor || this.$focusedCell;
+ }
- getCellHTML(cell) {
- const { rowIndex, colIndex, isHeader, isFilter } = cell;
- const dataAttr = makeDataAttributeString({
- rowIndex,
- colIndex,
- isHeader,
- isFilter
- });
+ activateEditing($cell) {
+ this.focusCell($cell);
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
- return `
+ const col = this.columnmanager.getColumn(colIndex);
+ if (col && (col.editable === false || col.focusable === false)) {
+ return;
+ }
+
+ const cell = this.getCell(colIndex, rowIndex);
+ if (cell && cell.editable === false) {
+ return;
+ }
+
+ if (this.$editingCell) {
+ const {
+ _rowIndex,
+ _colIndex
+ } = $.data(this.$editingCell);
+
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
+ // editing the same cell
+ return;
+ }
+ }
+
+ this.$editingCell = $cell;
+ $cell.classList.add('editing');
+
+ const $editCell = $('.edit-cell', $cell);
+ $editCell.innerHTML = '';
+
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
+
+ if (editor) {
+ this.currentCellEditor = editor;
+ // initialize editing input with cell value
+ editor.initValue(cell.content, rowIndex, col);
+ }
+ }
+
+ deactivateEditing() {
+ // keep focus on the cell so that keyboard navigation works
+ if (this.$focusedCell) this.$focusedCell.focus();
+
+ if (!this.$editingCell) return;
+ this.$editingCell.classList.remove('editing');
+ this.$editingCell = null;
+ }
+
+ getEditor(colIndex, rowIndex, value, parent) {
+ // debugger;
+ const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
+ if (obj && obj.setValue) return obj;
+
+ // editing fallback
+ const $input = $.create('input', {
+ class: 'input-style',
+ type: 'text',
+ inside: parent
+ });
+
+ return {
+ initValue(value) {
+ $input.focus();
+ $input.value = value;
+ },
+ getValue() {
+ return $input.value;
+ },
+ setValue(value) {
+ $input.value = value;
+ }
+ };
+ }
+
+ submitEditing() {
+ if (!this.$editingCell) return;
+ const $cell = this.$editingCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+ const col = this.datamanager.getColumn(colIndex);
+
+ if ($cell) {
+ const editor = this.currentCellEditor;
+
+ if (editor) {
+ const value = editor.getValue();
+ const done = editor.setValue(value, rowIndex, col);
+ const oldValue = this.getCell(colIndex, rowIndex).content;
+
+ // update cell immediately
+ this.updateCell(colIndex, rowIndex, value);
+ $cell.focus();
+
+ if (done && done.then) {
+ // revert to oldValue if promise fails
+ done.catch((e) => {
+ console.log(e);
+ this.updateCell(colIndex, rowIndex, oldValue);
+ });
+ }
+ }
+ }
+
+ this.currentCellEditor = null;
+ }
+
+ copyCellContents($cell1, $cell2) {
+ if (!$cell2 && $cell1) {
+ // copy only focusedCell
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell1);
+ const cell = this.getCell(colIndex, rowIndex);
+ copyTextToClipboard(cell.content);
+ return;
+ }
+ const cells = this.getCellsInRange($cell1, $cell2);
+
+ if (!cells) return;
+
+ const values = cells
+ // get cell objects
+ .map(index => this.getCell(...index))
+ // convert to array of rows
+ .reduce((acc, curr) => {
+ const rowIndex = curr.rowIndex;
+
+ acc[rowIndex] = acc[rowIndex] || [];
+ acc[rowIndex].push(curr.content);
+
+ return acc;
+ }, [])
+ // join values by tab
+ .map(row => row.join('\t'))
+ // join rows by newline
+ .join('\n');
+
+ copyTextToClipboard(values);
+ }
+
+ activateFilter(colIndex) {
+ this.columnmanager.toggleFilter();
+ this.columnmanager.focusFilter(colIndex);
+
+ if (!this.columnmanager.isFilterShown) {
+ // put focus back on cell
+ this.$focusedCell.focus();
+ }
+ }
+
+ updateCell(colIndex, rowIndex, value) {
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
+ content: value
+ });
+ this.refreshCell(cell);
+ }
+
+ refreshCell(cell) {
+ const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
+ $cell.innerHTML = this.getCellContent(cell);
+ }
+
+ isStandardCell(colIndex) {
+ // Standard cells are in Sr. No and Checkbox column
+ return colIndex < this.columnmanager.getFirstColumnIndex();
+ }
+
+ getCell$(colIndex, rowIndex) {
+ return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
+ }
+
+ getAboveCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $aboveRow = $cell.parentElement.previousElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $aboveRow);
+ }
+
+ getBelowCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $belowRow = $cell.parentElement.nextElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $belowRow);
+ }
+
+ getLeftCell$($cell) {
+ return $cell.previousElementSibling;
+ }
+
+ getRightCell$($cell) {
+ return $cell.nextElementSibling;
+ }
+
+ getLeftMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
+ }
+
+ getRightMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
+ }
+
+ getTopMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
+ }
+
+ getBottomMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getCellAttr($cell) {
+ return this.instance.getCellAttr($cell);
+ }
+
+ getRowHeight() {
+ return $.style($('.data-table-row', this.bodyScrollable), 'height');
+ }
+
+ scrollToCell($cell) {
+ if ($.inViewport($cell, this.bodyScrollable)) return false;
+
+ const {
+ rowIndex
+ } = $.data($cell);
+ this.rowmanager.scrollToRow(rowIndex);
+ return false;
+ }
+
+ getRowCountPerPage() {
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
+ }
+
+ getCellHTML(cell) {
+ const {
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ } = cell;
+ const dataAttr = makeDataAttributeString({
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ });
+
+ return `
${this.getCellContent(cell)}
|
`;
- }
-
- getCellContent(cell) {
- const { isHeader } = cell;
-
- const editable = !isHeader && cell.editable !== false;
- const editCellHTML = editable ? this.getEditCellHTML() : '';
-
- const sortable = isHeader && cell.sortable !== false;
- const sortIndicator = sortable ? '' : '';
-
- const resizable = isHeader && cell.resizable !== false;
- const resizeColumn = resizable ? '' : '';
-
- const hasDropdown = isHeader && cell.dropdown !== false;
- const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
-
- let contentHTML;
- if (cell.isHeader || cell.isFilter || !cell.column.format) {
- contentHTML = cell.content;
- } else {
- contentHTML = cell.column.format(cell.content, cell);
}
- return `
+ getCellContent(cell) {
+ const {
+ isHeader
+ } = cell;
+
+ const editable = !isHeader && cell.editable !== false;
+ const editCellHTML = editable ? this.getEditCellHTML() : '';
+
+ const sortable = isHeader && cell.sortable !== false;
+ const sortIndicator = sortable ? '' : '';
+
+ const resizable = isHeader && cell.resizable !== false;
+ const resizeColumn = resizable ? '' : '';
+
+ const hasDropdown = isHeader && cell.dropdown !== false;
+ const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
+
+ let contentHTML;
+ if (cell.isHeader || cell.isFilter || !cell.column.format) {
+ contentHTML = cell.content;
+ } else {
+ contentHTML = cell.column.format(cell.content, cell);
+ }
+
+ return `
${(contentHTML)}
${sortIndicator}
@@ -612,15 +660,15 @@ export default class CellManager {
${editCellHTML}
`;
- }
+ }
- getEditCellHTML() {
- return `
+ getEditCellHTML() {
+ return `
`;
- }
+ }
- cellSelector(colIndex, rowIndex) {
- return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
- }
+ cellSelector(colIndex, rowIndex) {
+ return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
+ }
}
diff --git a/src/columnmanager.js b/src/columnmanager.js
index e67951d..6f4191a 100644
--- a/src/columnmanager.js
+++ b/src/columnmanager.js
@@ -1,413 +1,452 @@
import $ from './dom';
import Sortable from 'sortablejs';
-import { getDefault, linkProperties, debounce } from './utils';
+import {
+ getDefault,
+ linkProperties,
+ debounce
+} from './utils';
export default class ColumnManager {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options',
- 'fireEvent',
- 'header',
- 'datamanager',
- 'style',
- 'wrapper',
- 'rowmanager',
- 'bodyScrollable'
- ]);
+ linkProperties(this, this.instance, [
+ 'options',
+ 'fireEvent',
+ 'header',
+ 'datamanager',
+ 'style',
+ 'wrapper',
+ 'rowmanager',
+ 'bodyScrollable'
+ ]);
- this.bindEvents();
- getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
- }
+ this.bindEvents();
+ getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
+ }
- renderHeader() {
- this.header.innerHTML = '';
- this.refreshHeader();
- }
+ renderHeader() {
+ this.header.innerHTML = '';
+ this.refreshHeader();
+ }
- refreshHeader() {
- const columns = this.datamanager.getColumns();
+ refreshHeader() {
+ const columns = this.datamanager.getColumns();
- if (!$('.data-table-col', this.header)) {
- // insert html
+ if (!$('.data-table-col', this.header)) {
+ // insert html
- let html = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- if (this.options.enableInlineFilters) {
- html += this.rowmanager.getRowHTML(columns, { isFilter: 1 });
- }
+ let html = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ if (this.options.enableInlineFilters) {
+ html += this.rowmanager.getRowHTML(columns, {
+ isFilter: 1
+ });
+ }
- $('thead', this.header).innerHTML = html;
+ $('thead', this.header).innerHTML = html;
- this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
+ this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
- if (this.$filterRow) {
- // hide filter row immediately, so it doesn't disturb layout
- $.style(this.$filterRow, {
- display: 'none'
- });
- }
- } else {
- // refresh dom state
- const $cols = $.each('.data-table-col', this.header);
- if (columns.length < $cols.length) {
- // deleted column
- $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- return;
- }
+ if (this.$filterRow) {
+ // hide filter row immediately, so it doesn't disturb layout
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ }
+ } else {
+ // refresh dom state
+ const $cols = $.each('.data-table-col', this.header);
+ if (columns.length < $cols.length) {
+ // deleted column
+ $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ return;
+ }
- $cols.map(($col, i) => {
- const column = columns[i];
- // column sorted or order changed
- // update colIndex of each header cell
- $.data($col, {
- colIndex: column.colIndex
- });
+ $cols.map(($col, i) => {
+ const column = columns[i];
+ // column sorted or order changed
+ // update colIndex of each header cell
+ $.data($col, {
+ colIndex: column.colIndex
+ });
- // refresh sort indicator
- const sortIndicator = $('.sort-indicator', $col);
- if (sortIndicator) {
- sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ // refresh sort indicator
+ const sortIndicator = $('.sort-indicator', $col);
+ if (sortIndicator) {
+ sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ }
+ });
}
- });
+ // reset columnMap
+ this.$columnMap = [];
}
- // reset columnMap
- this.$columnMap = [];
- }
- bindEvents() {
- this.bindDropdown();
- this.bindResizeColumn();
- this.bindMoveColumn();
- this.bindFilter();
- }
-
- bindDropdown() {
- let $activeDropdown;
- $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
- const $dropdown = $.closest('.data-table-dropdown', $button);
-
- if (!$dropdown.classList.contains('is-active')) {
- deactivateDropdown();
- $dropdown.classList.add('is-active');
- $activeDropdown = $dropdown;
- } else {
- deactivateDropdown();
- }
- });
-
- $.on(document.body, 'click', (e) => {
- if (e.target.matches('.data-table-dropdown-toggle')) return;
- deactivateDropdown();
- });
-
- const dropdownItems = this.options.headerDropdown;
-
- $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
- const $col = $.closest('.data-table-col', $item);
- const { index } = $.data($item);
- const { colIndex } = $.data($col);
- let callback = dropdownItems[index].action;
-
- callback && callback.call(this.instance, this.getColumn(colIndex));
- });
-
- function deactivateDropdown(e) {
- $activeDropdown && $activeDropdown.classList.remove('is-active');
- $activeDropdown = null;
+ bindEvents() {
+ this.bindDropdown();
+ this.bindResizeColumn();
+ this.bindMoveColumn();
+ this.bindFilter();
}
- }
- bindResizeColumn() {
- let isDragging = false;
- let $resizingCell, startWidth, startX;
+ bindDropdown() {
+ let $activeDropdown;
+ $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
+ const $dropdown = $.closest('.data-table-dropdown', $button);
- $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
- document.body.classList.add('data-table-resize');
- const $cell = $handle.parentNode.parentNode;
- $resizingCell = $cell;
- const { colIndex } = $.data($resizingCell);
- const col = this.getColumn(colIndex);
-
- if (col && col.resizable === false) {
- return;
- }
-
- isDragging = true;
- startWidth = $.style($('.content', $resizingCell), 'width');
- startX = e.pageX;
- });
-
- $.on(document.body, 'mouseup', (e) => {
- document.body.classList.remove('data-table-resize');
- if (!$resizingCell) return;
- isDragging = false;
-
- const { colIndex } = $.data($resizingCell);
- this.setColumnWidth(colIndex);
- this.style.setBodyStyle();
- $resizingCell = null;
- });
-
- $.on(document.body, 'mousemove', (e) => {
- if (!isDragging) return;
- const finalWidth = startWidth + (e.pageX - startX);
- const { colIndex } = $.data($resizingCell);
-
- if (this.getColumnMinWidth(colIndex) > finalWidth) {
- // don't resize past minWidth
- return;
- }
- this.datamanager.updateColumn(colIndex, { width: finalWidth });
- this.setColumnHeaderWidth(colIndex);
- });
- }
-
- bindMoveColumn() {
- let initialized;
-
- const initialize = () => {
- if (initialized) {
- $.off(document.body, 'mousemove', initialize);
- return;
- }
- const ready = $('.data-table-col', this.header);
- if (!ready) return;
-
- const $parent = $('.data-table-row', this.header);
-
- this.sortable = Sortable.create($parent, {
- onEnd: (e) => {
- const { oldIndex, newIndex } = e;
- const $draggedCell = e.item;
- const { colIndex } = $.data($draggedCell);
- if (+colIndex === newIndex) return;
-
- this.switchColumn(oldIndex, newIndex);
- },
- preventOnFilter: false,
- filter: '.column-resizer, .data-table-dropdown',
- animation: 150
- });
- };
-
- $.on(document.body, 'mousemove', initialize);
- }
-
- bindSortColumn() {
-
- $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
- const $cell = span.closest('.data-table-col');
- let { colIndex, sortOrder } = $.data($cell);
- sortOrder = getDefault(sortOrder, 'none');
- const col = this.getColumn(colIndex);
-
- if (col && col.sortable === false) {
- return;
- }
-
- // reset sort indicator
- $('.sort-indicator', this.header).textContent = '';
- $.each('.data-table-col', this.header).map($cell => {
- $.data($cell, {
- sortOrder: 'none'
+ if (!$dropdown.classList.contains('is-active')) {
+ deactivateDropdown();
+ $dropdown.classList.add('is-active');
+ $activeDropdown = $dropdown;
+ } else {
+ deactivateDropdown();
+ }
});
- });
- let nextSortOrder, textContent;
- if (sortOrder === 'none') {
- nextSortOrder = 'asc';
- textContent = '▲';
- } else if (sortOrder === 'asc') {
- nextSortOrder = 'desc';
- textContent = '▼';
- } else if (sortOrder === 'desc') {
- nextSortOrder = 'none';
- textContent = '';
- }
-
- $.data($cell, {
- sortOrder: nextSortOrder
- });
- $('.sort-indicator', $cell).textContent = textContent;
-
- this.sortColumn(colIndex, nextSortOrder);
- });
- }
-
- sortColumn(colIndex, nextSortOrder) {
- this.instance.freeze();
- this.sortRows(colIndex, nextSortOrder)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onSortColumn', this.getColumn(colIndex));
- });
- }
-
- removeColumn(colIndex) {
- const removedCol = this.getColumn(colIndex);
- this.instance.freeze();
- this.datamanager.removeColumn(colIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onRemoveColumn', removedCol);
- });
- }
-
- switchColumn(oldIndex, newIndex) {
- this.instance.freeze();
- this.datamanager.switchColumn(oldIndex, newIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => {
- this.setColumnWidth(oldIndex);
- this.setColumnWidth(newIndex);
- this.instance.unfreeze();
- })
- .then(() => {
- this.fireEvent('onSwitchColumn',
- this.getColumn(oldIndex), this.getColumn(newIndex)
- );
- });
- }
-
- toggleFilter() {
- this.isFilterShown = this.isFilterShown || false;
-
- if (this.isFilterShown) {
- $.style(this.$filterRow, {
- display: 'none'
- });
- } else {
- $.style(this.$filterRow, {
- display: ''
- });
- }
-
- this.isFilterShown = !this.isFilterShown;
- this.style.setBodyStyle();
- }
-
- focusFilter(colIndex) {
- if (!this.isFilterShown) return;
-
- const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
- $filterInput.focus();
- }
-
- bindFilter() {
- if (!this.options.enableInlineFilters) return;
- const handler = e => {
- const $filterCell = $.closest('.data-table-col', e.target);
- const { colIndex } = $.data($filterCell);
- const keyword = e.target.value;
-
- this.datamanager.filterRows(keyword, colIndex)
- .then(({ rowsToHide, rowsToShow }) => {
- rowsToHide.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.add('hide');
- });
- rowsToShow.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.remove('hide');
- });
+ $.on(document.body, 'click', (e) => {
+ if (e.target.matches('.data-table-dropdown-toggle')) return;
+ deactivateDropdown();
});
- };
- $.on(this.header, 'keydown', '.data-table-filter', debounce(handler, 300));
- }
- sortRows(colIndex, sortOrder) {
- return this.datamanager.sortRows(colIndex, sortOrder);
- }
+ const dropdownItems = this.options.headerDropdown;
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
+ $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
+ const $col = $.closest('.data-table-col', $item);
+ const {
+ index
+ } = $.data($item);
+ const {
+ colIndex
+ } = $.data($col);
+ let callback = dropdownItems[index].action;
- getColumns() {
- return this.datamanager.getColumns();
- }
+ callback && callback.call(this.instance, this.getColumn(colIndex));
+ });
- setColumnWidth(colIndex) {
- colIndex = +colIndex;
- this._columnWidthMap = this._columnWidthMap || [];
-
- const { width } = this.getColumn(colIndex);
-
- let index = this._columnWidthMap[colIndex];
- const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
- const styles = {
- width: width + 'px'
- };
-
- index = this.style.setStyle(selector, styles, index);
- this._columnWidthMap[colIndex] = index;
- }
-
- setColumnHeaderWidth(colIndex) {
- colIndex = +colIndex;
- this.$columnMap = this.$columnMap || [];
- const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
- const { width } = this.getColumn(colIndex);
-
- let $column = this.$columnMap[colIndex];
- if (!$column) {
- $column = this.header.querySelector(selector);
- this.$columnMap[colIndex] = $column;
+ function deactivateDropdown(e) {
+ $activeDropdown && $activeDropdown.classList.remove('is-active');
+ $activeDropdown = null;
+ }
}
- $column.style.width = width + 'px';
- }
+ bindResizeColumn() {
+ let isDragging = false;
+ let $resizingCell, startWidth, startX;
- getColumnMinWidth(colIndex) {
- colIndex = +colIndex;
- return this.getColumn(colIndex).minWidth || 24;
- }
+ $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
+ document.body.classList.add('data-table-resize');
+ const $cell = $handle.parentNode.parentNode;
+ $resizingCell = $cell;
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ const col = this.getColumn(colIndex);
- getFirstColumnIndex() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ if (col && col.resizable === false) {
+ return;
+ }
+
+ isDragging = true;
+ startWidth = $.style($('.content', $resizingCell), 'width');
+ startX = e.pageX;
+ });
+
+ $.on(document.body, 'mouseup', (e) => {
+ document.body.classList.remove('data-table-resize');
+ if (!$resizingCell) return;
+ isDragging = false;
+
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ this.setColumnWidth(colIndex);
+ this.style.setBodyStyle();
+ $resizingCell = null;
+ });
+
+ $.on(document.body, 'mousemove', (e) => {
+ if (!isDragging) return;
+ const finalWidth = startWidth + (e.pageX - startX);
+ const {
+ colIndex
+ } = $.data($resizingCell);
+
+ if (this.getColumnMinWidth(colIndex) > finalWidth) {
+ // don't resize past minWidth
+ return;
+ }
+ this.datamanager.updateColumn(colIndex, {
+ width: finalWidth
+ });
+ this.setColumnHeaderWidth(colIndex);
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ bindMoveColumn() {
+ let initialized;
+
+ const initialize = () => {
+ if (initialized) {
+ $.off(document.body, 'mousemove', initialize);
+ return;
+ }
+ const ready = $('.data-table-col', this.header);
+ if (!ready) return;
+
+ const $parent = $('.data-table-row', this.header);
+
+ this.sortable = Sortable.create($parent, {
+ onEnd: (e) => {
+ const {
+ oldIndex,
+ newIndex
+ } = e;
+ const $draggedCell = e.item;
+ const {
+ colIndex
+ } = $.data($draggedCell);
+ if (+colIndex === newIndex) return;
+
+ this.switchColumn(oldIndex, newIndex);
+ },
+ preventOnFilter: false,
+ filter: '.column-resizer, .data-table-dropdown',
+ animation: 150
+ });
+ };
+
+ $.on(document.body, 'mousemove', initialize);
}
- return 0;
- }
+ bindSortColumn() {
- getHeaderCell$(colIndex) {
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
+ const $cell = span.closest('.data-table-col');
+ let {
+ colIndex,
+ sortOrder
+ } = $.data($cell);
+ sortOrder = getDefault(sortOrder, 'none');
+ const col = this.getColumn(colIndex);
- getLastColumnIndex() {
- return this.datamanager.getColumnCount() - 1;
- }
+ if (col && col.sortable === false) {
+ return;
+ }
- getSerialColumnIndex() {
- const columns = this.datamanager.getColumns();
+ // reset sort indicator
+ $('.sort-indicator', this.header).textContent = '';
+ $.each('.data-table-col', this.header).map($cell => {
+ $.data($cell, {
+ sortOrder: 'none'
+ });
+ });
- return columns.findIndex(column => column.content.includes('Sr. No'));
- }
+ let nextSortOrder, textContent;
+ if (sortOrder === 'none') {
+ nextSortOrder = 'asc';
+ textContent = '▲';
+ } else if (sortOrder === 'asc') {
+ nextSortOrder = 'desc';
+ textContent = '▼';
+ } else if (sortOrder === 'desc') {
+ nextSortOrder = 'none';
+ textContent = '';
+ }
+
+ $.data($cell, {
+ sortOrder: nextSortOrder
+ });
+ $('.sort-indicator', $cell).textContent = textContent;
+
+ this.sortColumn(colIndex, nextSortOrder);
+ });
+ }
+
+ sortColumn(colIndex, nextSortOrder) {
+ this.instance.freeze();
+ this.sortRows(colIndex, nextSortOrder)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
+ });
+ }
+
+ removeColumn(colIndex) {
+ const removedCol = this.getColumn(colIndex);
+ this.instance.freeze();
+ this.datamanager.removeColumn(colIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onRemoveColumn', removedCol);
+ });
+ }
+
+ switchColumn(oldIndex, newIndex) {
+ this.instance.freeze();
+ this.datamanager.switchColumn(oldIndex, newIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => {
+ this.setColumnWidth(oldIndex);
+ this.setColumnWidth(newIndex);
+ this.instance.unfreeze();
+ })
+ .then(() => {
+ this.fireEvent('onSwitchColumn',
+ this.getColumn(oldIndex), this.getColumn(newIndex)
+ );
+ });
+ }
+
+ toggleFilter() {
+ this.isFilterShown = this.isFilterShown || false;
+
+ if (this.isFilterShown) {
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ } else {
+ $.style(this.$filterRow, {
+ display: ''
+ });
+ }
+
+ this.isFilterShown = !this.isFilterShown;
+ this.style.setBodyStyle();
+ }
+
+ focusFilter(colIndex) {
+ if (!this.isFilterShown) return;
+
+ const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
+ $filterInput.focus();
+ }
+
+ bindFilter() {
+ if (!this.options.enableInlineFilters) return;
+ const handler = e => {
+ const $filterCell = $.closest('.data-table-col', e.target);
+ const {
+ colIndex
+ } = $.data($filterCell);
+ const keyword = e.target.value;
+
+ this.datamanager.filterRows(keyword, colIndex)
+ .then(({
+ rowsToHide,
+ rowsToShow
+ }) => {
+ rowsToHide.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.add('hide');
+ });
+ rowsToShow.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.remove('hide');
+ });
+ });
+ };
+ $.on(this.header, 'keydown', '.data-table-filter', debounce(handler, 300));
+ }
+
+ sortRows(colIndex, sortOrder) {
+ return this.datamanager.sortRows(colIndex, sortOrder);
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ setColumnWidth(colIndex) {
+ colIndex = +colIndex;
+ this._columnWidthMap = this._columnWidthMap || [];
+
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let index = this._columnWidthMap[colIndex];
+ const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
+ const styles = {
+ width: width + 'px'
+ };
+
+ index = this.style.setStyle(selector, styles, index);
+ this._columnWidthMap[colIndex] = index;
+ }
+
+ setColumnHeaderWidth(colIndex) {
+ colIndex = +colIndex;
+ this.$columnMap = this.$columnMap || [];
+ const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let $column = this.$columnMap[colIndex];
+ if (!$column) {
+ $column = this.header.querySelector(selector);
+ this.$columnMap[colIndex] = $column;
+ }
+
+ $column.style.width = width + 'px';
+ }
+
+ getColumnMinWidth(colIndex) {
+ colIndex = +colIndex;
+ return this.getColumn(colIndex).minWidth || 24;
+ }
+
+ getFirstColumnIndex() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getHeaderCell$(colIndex) {
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+
+ getLastColumnIndex() {
+ return this.datamanager.getColumnCount() - 1;
+ }
+
+ getSerialColumnIndex() {
+ const columns = this.datamanager.getColumns();
+
+ return columns.findIndex(column => column.content.includes('Sr. No'));
+ }
}
// eslint-disable-next-line
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
- // add dropdown buttons
- const dropdownItems = this.options.headerDropdown;
+ // add dropdown buttons
+ const dropdownItems = this.options.headerDropdown;
- return `${dropdownButton}
+ return `${dropdownButton}
${dropdownItems.map((d, i) => `
${d.label}
`).join('')}
@@ -415,5 +454,5 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
};
export {
- getDropdownHTML
+ getDropdownHTML
};
diff --git a/src/datamanager.js b/src/datamanager.js
index 4c6993e..3569118 100644
--- a/src/datamanager.js
+++ b/src/datamanager.js
@@ -1,500 +1,517 @@
-import { isNumeric, promisify } from './utils';
+import {
+ isNumeric,
+ promisify
+} from './utils';
export default class DataManager {
- constructor(options) {
- this.options = options;
- this.sortRows = promisify(this.sortRows, this);
- this.switchColumn = promisify(this.switchColumn, this);
- this.removeColumn = promisify(this.removeColumn, this);
- this.filterRows = promisify(this.filterRows, this);
- }
-
- init(data) {
- if (!data) {
- data = this.options.data;
+ constructor(options) {
+ this.options = options;
+ this.sortRows = promisify(this.sortRows, this);
+ this.switchColumn = promisify(this.switchColumn, this);
+ this.removeColumn = promisify(this.removeColumn, this);
+ this.filterRows = promisify(this.filterRows, this);
}
- this.data = data;
-
- this.rowCount = 0;
- this.columns = [];
- this.rows = [];
-
- this.prepareColumns();
- this.prepareRows();
-
- this.prepareNumericColumns();
- }
-
- // computed property
- get currentSort() {
- const col = this.columns.find(col => col.sortOrder !== 'none');
- return col || {
- colIndex: -1,
- sortOrder: 'none'
- };
- }
-
- prepareColumns() {
- this.columns = [];
- this.validateColumns();
- this.prepareDefaultColumns();
- this.prepareHeader();
- }
-
- prepareDefaultColumns() {
- if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
- const cell = {
- id: '_checkbox',
- content: this.getCheckboxHTML(),
- editable: false,
- resizable: false,
- sortable: false,
- focusable: false,
- dropdown: false,
- width: 25
- };
- this.columns.push(cell);
- }
-
- if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
- let cell = {
- id: '_rowIndex',
- content: '',
- align: 'center',
- editable: false,
- resizable: false,
- focusable: false,
- dropdown: false
- };
-
- this.columns.push(cell);
- }
- }
-
- prepareRow(row, i) {
- const baseRowCell = {
- rowIndex: i
- };
-
- return row
- .map((cell, i) => this.prepareCell(cell, i))
- .map(cell => Object.assign({}, baseRowCell, cell));
- }
-
- prepareHeader() {
- let columns = this.columns.concat(this.options.columns);
- const baseCell = {
- isHeader: 1,
- editable: true,
- sortable: true,
- resizable: true,
- focusable: true,
- dropdown: true,
- width: null,
- format: (value) => {
- if (value === null || value === undefined) {
- return '';
- }
- return value + '';
- }
- };
-
- this.columns = columns
- .map((cell, i) => this.prepareCell(cell, i))
- .map(col => Object.assign({}, baseCell, col))
- .map(col => {
- col.id = col.id || col.content;
- return col;
- });
- }
-
- prepareCell(content, i) {
- const cell = {
- content: '',
- align: 'left',
- sortOrder: 'none',
- colIndex: i,
- column: this.columns[i]
- };
-
- if (content !== null && typeof content === 'object') {
- // passed as column/header
- Object.assign(cell, content);
- } else {
- cell.content = content;
- }
-
- return cell;
- }
-
- prepareNumericColumns() {
- const row0 = this.getRow(0);
- if (!row0) return;
- this.columns = this.columns.map((column, i) => {
-
- const cellValue = row0[i].content;
- if (!column.align && cellValue && isNumeric(cellValue)) {
- column.align = 'right';
- }
-
- return column;
- });
- }
-
- prepareRows() {
- this.validateData(this.data);
-
- this.rows = this.data.map((d, i) => {
- const index = this._getNextRowCount();
-
- let row = [];
-
- if (Array.isArray(d)) {
- // row is an array
- if (this.options.addCheckboxColumn) {
- row.push(this.getCheckboxHTML());
- }
- if (this.options.addSerialNoColumn) {
- row.push((index + 1) + '');
- }
- row = row.concat(d);
-
- while (row.length < this.columns.length) {
- row.push('');
+ init(data) {
+ if (!data) {
+ data = this.options.data;
}
- } else {
- // row is a dict
- for (let col of this.columns) {
- if (col.id === '_checkbox') {
- row.push(this.getCheckboxHTML());
- } else if (col.id === '_rowIndex') {
- row.push((index + 1) + '');
- } else {
- row.push(d[col.id]);
- }
+ this.data = data;
+
+ this.rowCount = 0;
+ this.columns = [];
+ this.rows = [];
+
+ this.prepareColumns();
+ this.prepareRows();
+
+ this.prepareNumericColumns();
+ }
+
+ // computed property
+ get currentSort() {
+ const col = this.columns.find(col => col.sortOrder !== 'none');
+ return col || {
+ colIndex: -1,
+ sortOrder: 'none'
+ };
+ }
+
+ prepareColumns() {
+ this.columns = [];
+ this.validateColumns();
+ this.prepareDefaultColumns();
+ this.prepareHeader();
+ }
+
+ prepareDefaultColumns() {
+ if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
+ const cell = {
+ id: '_checkbox',
+ content: this.getCheckboxHTML(),
+ editable: false,
+ resizable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 25
+ };
+ this.columns.push(cell);
}
- }
- return this.prepareRow(row, index);
- });
- }
+ if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
+ let cell = {
+ id: '_rowIndex',
+ content: '',
+ align: 'center',
+ editable: false,
+ resizable: false,
+ focusable: false,
+ dropdown: false
+ };
- validateColumns() {
- const columns = this.options.columns;
- if (!Array.isArray(columns)) {
- throw new DataError('`columns` must be an array');
+ this.columns.push(cell);
+ }
}
- columns.forEach((column, i) => {
- if (typeof column !== 'string' && typeof column !== 'object') {
- throw new DataError(`column "${i}" must be a string or an object`);
- }
- });
- }
+ prepareHeader() {
+ let columns = this.columns.concat(this.options.columns);
+ const baseCell = {
+ isHeader: 1,
+ editable: true,
+ sortable: true,
+ resizable: true,
+ focusable: true,
+ dropdown: true,
+ width: null,
+ format: (value) => {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return value + '';
+ }
+ };
- validateData(data) {
- if (Array.isArray(data) &&
- (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
- return true;
+ this.columns = columns
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(col => Object.assign({}, baseCell, col))
+ .map(col => {
+ col.id = col.id || col.content;
+ return col;
+ });
}
- throw new DataError('`data` must be an array of arrays or objects');
- }
- appendRows(rows) {
- this.validateData(rows);
+ prepareCell(content, i) {
+ const cell = {
+ content: '',
+ align: 'left',
+ sortOrder: 'none',
+ colIndex: i,
+ column: this.columns[i]
+ };
- this.rows = this.rows.concat(this.prepareRows(rows));
- }
-
- sortRows(colIndex, sortOrder = 'none') {
- colIndex = +colIndex;
-
- // reset sortOrder and update for colIndex
- this.getColumns()
- .map(col => {
- if (col.colIndex === colIndex) {
- col.sortOrder = sortOrder;
+ if (content !== null && typeof content === 'object') {
+ // passed as column/header
+ Object.assign(cell, content);
} else {
- col.sortOrder = 'none';
+ cell.content = content;
}
- });
- this._sortRows(colIndex, sortOrder);
- }
-
- _sortRows(colIndex, sortOrder) {
-
- if (this.currentSort.colIndex === colIndex) {
- // reverse the array if only sortOrder changed
- if (
- (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
- (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
- ) {
- this.reverseArray(this.rows);
- this.currentSort.sortOrder = sortOrder;
- return;
- }
+ return cell;
}
- this.rows.sort((a, b) => {
- const _aIndex = a[0].rowIndex;
- const _bIndex = b[0].rowIndex;
- const _a = a[colIndex].content;
- const _b = b[colIndex].content;
+ prepareNumericColumns() {
+ const row0 = this.getRow(0);
+ if (!row0) return;
+ this.columns = this.columns.map((column, i) => {
- if (sortOrder === 'none') {
- return _aIndex - _bIndex;
- } else if (sortOrder === 'asc') {
- if (_a < _b) return -1;
- if (_a > _b) return 1;
- if (_a === _b) return 0;
- } else if (sortOrder === 'desc') {
- if (_a < _b) return 1;
- if (_a > _b) return -1;
- if (_a === _b) return 0;
- }
- return 0;
- });
+ const cellValue = row0[i].content;
+ if (!column.align && cellValue && isNumeric(cellValue)) {
+ column.align = 'right';
+ }
- if (this.hasColumnById('_rowIndex')) {
- // update row index
- const srNoColIndex = this.getColumnIndexById('_rowIndex');
- this.rows = this.rows.map((row, index) => {
- return row.map(cell => {
- if (cell.colIndex === srNoColIndex) {
- cell.content = (index + 1) + '';
- }
- return cell;
+ return column;
});
- });
- }
- }
-
- reverseArray(array) {
- let left = null;
- let right = null;
- let length = array.length;
-
- for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
- const temporary = array[left];
-
- array[left] = array[right];
- array[right] = temporary;
- }
- }
-
- switchColumn(index1, index2) {
- // update columns
- const temp = this.columns[index1];
- this.columns[index1] = this.columns[index2];
- this.columns[index2] = temp;
-
- this.columns[index1].colIndex = index1;
- this.columns[index2].colIndex = index2;
-
- // update rows
- this.rows = this.rows.map(row => {
- const newCell1 = Object.assign({}, row[index1], { colIndex: index2 });
- const newCell2 = Object.assign({}, row[index2], { colIndex: index1 });
-
- let newRow = row.map(cell => {
- // make object copy
- return Object.assign({}, cell);
- });
-
- newRow[index2] = newCell1;
- newRow[index1] = newCell2;
-
- return newRow;
- });
- }
-
- removeColumn(index) {
- index = +index;
- const filter = cell => cell.colIndex !== index;
- const map = (cell, i) => Object.assign({}, cell, { colIndex: i });
- // update columns
- this.columns = this.columns
- .filter(filter)
- .map(map);
-
- // update rows
- this.rows = this.rows.map(row => {
- const newRow = row
- .filter(filter)
- .map(map);
-
- return newRow;
- });
- }
-
- updateRow(row, rowIndex) {
- if (row.length < this.columns.length) {
- if (this.hasColumnById('_rowIndex')) {
- const val = (rowIndex + 1) + '';
-
- row = [val].concat(row);
- }
-
- if (this.hasColumnById('_checkbox')) {
- const val = '';
-
- row = [val].concat(row);
- }
}
- const _row = this.prepareRow(row, rowIndex);
- const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
- this.rows[index] = _row;
+ prepareRows() {
+ this.validateData(this.data);
- return _row;
- }
+ this.rows = this.data.map((d, i) => {
+ const index = this._getNextRowCount();
- updateCell(colIndex, rowIndex, options) {
- let cell;
- if (typeof colIndex === 'object') {
- // cell object was passed,
- // must have colIndex, rowIndex
- cell = colIndex;
- colIndex = cell.colIndex;
- rowIndex = cell.rowIndex;
- // the object passed must be merged with original cell
- options = cell;
- }
- cell = this.getCell(colIndex, rowIndex);
+ let row = [];
- // mutate object directly
- for (let key in options) {
- const newVal = options[key];
- if (newVal !== undefined) {
- cell[key] = newVal;
- }
+ if (Array.isArray(d)) {
+ // row is an array
+ if (this.options.addCheckboxColumn) {
+ row.push(this.getCheckboxHTML());
+ }
+ if (this.options.addSerialNoColumn) {
+ row.push((index + 1) + '');
+ }
+ row = row.concat(d);
+
+ while (row.length < this.columns.length) {
+ row.push('');
+ }
+
+ } else {
+ // row is an object
+ for (let col of this.columns) {
+ if (col.id === '_checkbox') {
+ row.push(this.getCheckboxHTML());
+ } else if (col.id === '_rowIndex') {
+ row.push((index + 1) + '');
+ } else {
+ row.push(d[col.id]);
+ }
+ }
+ }
+
+ return this.prepareRow(row, {
+ rowIndex: index
+ });
+ });
}
- return cell;
- }
+ prepareRow(row, props) {
+ const baseRowCell = {
+ rowIndex: props.rowIndex
+ };
- updateColumn(colIndex, keyValPairs) {
- const column = this.getColumn(colIndex);
- for (let key in keyValPairs) {
- const newVal = keyValPairs[key];
- if (newVal !== undefined) {
- column[key] = newVal;
- }
- }
- return column;
- }
+ row = row
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(cell => Object.assign({}, baseRowCell, cell));
- filterRows(keyword, colIndex) {
- let rowsToHide = [];
- let rowsToShow = [];
- const cells = this.rows.map(row => row[colIndex]);
-
- cells.forEach(cell => {
- const hay = cell.content.toLowerCase();
- const needle = (keyword || '').toLowerCase();
-
- if (!needle || hay.includes(needle)) {
- rowsToShow.push(cell.rowIndex);
- } else {
- rowsToHide.push(cell.rowIndex);
- }
- });
-
- return {rowsToHide, rowsToShow};
- }
-
- getRowCount() {
- return this.rowCount;
- }
-
- _getNextRowCount() {
- const val = this.rowCount;
-
- this.rowCount++;
- return val;
- }
-
- getRows(start, end) {
- return this.rows.slice(start, end);
- }
-
- getColumns(skipStandardColumns) {
- let columns = this.columns;
-
- if (skipStandardColumns) {
- columns = columns.slice(this.getStandardColumnCount());
+ // monkey patched in array object
+ row.meta = props;
+ return row;
}
- return columns;
- }
+ validateColumns() {
+ const columns = this.options.columns;
+ if (!Array.isArray(columns)) {
+ throw new DataError('`columns` must be an array');
+ }
- getStandardColumnCount() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ columns.forEach((column, i) => {
+ if (typeof column !== 'string' && typeof column !== 'object') {
+ throw new DataError(`column "${i}" must be a string or an object`);
+ }
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ validateData(data) {
+ if (Array.isArray(data) &&
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
+ return true;
+ }
+ throw new DataError('`data` must be an array of arrays or objects');
}
- return 0;
- }
+ appendRows(rows) {
+ this.validateData(rows);
- getColumnCount(skipStandardColumns) {
- let val = this.columns.length;
-
- if (skipStandardColumns) {
- val = val - this.getStandardColumnCount();
+ this.rows = this.rows.concat(this.prepareRows(rows));
}
- return val;
- }
+ sortRows(colIndex, sortOrder = 'none') {
+ colIndex = +colIndex;
- getColumn(colIndex) {
- colIndex = +colIndex;
- return this.columns.find(col => col.colIndex === colIndex);
- }
+ // reset sortOrder and update for colIndex
+ this.getColumns()
+ .map(col => {
+ if (col.colIndex === colIndex) {
+ col.sortOrder = sortOrder;
+ } else {
+ col.sortOrder = 'none';
+ }
+ });
- getRow(rowIndex) {
- rowIndex = +rowIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex);
- }
+ this._sortRows(colIndex, sortOrder);
+ }
- getCell(colIndex, rowIndex) {
- rowIndex = +rowIndex;
- colIndex = +colIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
- }
+ _sortRows(colIndex, sortOrder) {
- get() {
- return {
- columns: this.columns,
- rows: this.rows
- };
- }
+ if (this.currentSort.colIndex === colIndex) {
+ // reverse the array if only sortOrder changed
+ if (
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
+ ) {
+ this.reverseArray(this.rows);
+ this.currentSort.sortOrder = sortOrder;
+ return;
+ }
+ }
- hasColumn(name) {
- return Boolean(this.columns.find(col => col.content === name));
- }
+ this.rows.sort((a, b) => {
+ const _aIndex = a[0].rowIndex;
+ const _bIndex = b[0].rowIndex;
+ const _a = a[colIndex].content;
+ const _b = b[colIndex].content;
- hasColumnById(id) {
- return Boolean(this.columns.find(col => col.id === id));
- }
+ if (sortOrder === 'none') {
+ return _aIndex - _bIndex;
+ } else if (sortOrder === 'asc') {
+ if (_a < _b) return -1;
+ if (_a > _b) return 1;
+ if (_a === _b) return 0;
+ } else if (sortOrder === 'desc') {
+ if (_a < _b) return 1;
+ if (_a > _b) return -1;
+ if (_a === _b) return 0;
+ }
+ return 0;
+ });
- getColumnIndex(name) {
- return this.columns.findIndex(col => col.content === name);
- }
+ if (this.hasColumnById('_rowIndex')) {
+ // update row index
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
+ this.rows.forEach((row, index) => {
+ row.forEach(cell => {
+ if (cell.colIndex === srNoColIndex) {
+ cell.content = (index + 1) + '';
+ }
+ });
+ });
+ }
+ }
- getColumnIndexById(id) {
- return this.columns.findIndex(col => col.id === id);
- }
+ reverseArray(array) {
+ let left = null;
+ let right = null;
+ let length = array.length;
- getCheckboxHTML() {
- return '';
- }
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
+ const temporary = array[left];
+
+ array[left] = array[right];
+ array[right] = temporary;
+ }
+ }
+
+ switchColumn(index1, index2) {
+ // update columns
+ const temp = this.columns[index1];
+ this.columns[index1] = this.columns[index2];
+ this.columns[index2] = temp;
+
+ this.columns[index1].colIndex = index1;
+ this.columns[index2].colIndex = index2;
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newCell1 = Object.assign({}, row[index1], {
+ colIndex: index2
+ });
+ const newCell2 = Object.assign({}, row[index2], {
+ colIndex: index1
+ });
+
+ let newRow = row.map(cell => {
+ // make object copy
+ return Object.assign({}, cell);
+ });
+
+ newRow[index2] = newCell1;
+ newRow[index1] = newCell2;
+
+ return newRow;
+ });
+ }
+
+ removeColumn(index) {
+ index = +index;
+ const filter = cell => cell.colIndex !== index;
+ const map = (cell, i) => Object.assign({}, cell, {
+ colIndex: i
+ });
+ // update columns
+ this.columns = this.columns
+ .filter(filter)
+ .map(map);
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newRow = row
+ .filter(filter)
+ .map(map);
+
+ return newRow;
+ });
+ }
+
+ updateRow(row, rowIndex) {
+ if (row.length < this.columns.length) {
+ if (this.hasColumnById('_rowIndex')) {
+ const val = (rowIndex + 1) + '';
+
+ row = [val].concat(row);
+ }
+
+ if (this.hasColumnById('_checkbox')) {
+ const val = '';
+
+ row = [val].concat(row);
+ }
+ }
+
+ const _row = this.prepareRow(row, rowIndex);
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
+ this.rows[index] = _row;
+
+ return _row;
+ }
+
+ updateCell(colIndex, rowIndex, options) {
+ let cell;
+ if (typeof colIndex === 'object') {
+ // cell object was passed,
+ // must have colIndex, rowIndex
+ cell = colIndex;
+ colIndex = cell.colIndex;
+ rowIndex = cell.rowIndex;
+ // the object passed must be merged with original cell
+ options = cell;
+ }
+ cell = this.getCell(colIndex, rowIndex);
+
+ // mutate object directly
+ for (let key in options) {
+ const newVal = options[key];
+ if (newVal !== undefined) {
+ cell[key] = newVal;
+ }
+ }
+
+ return cell;
+ }
+
+ updateColumn(colIndex, keyValPairs) {
+ const column = this.getColumn(colIndex);
+ for (let key in keyValPairs) {
+ const newVal = keyValPairs[key];
+ if (newVal !== undefined) {
+ column[key] = newVal;
+ }
+ }
+ return column;
+ }
+
+ filterRows(keyword, colIndex) {
+ let rowsToHide = [];
+ let rowsToShow = [];
+ const cells = this.rows.map(row => row[colIndex]);
+
+ cells.forEach(cell => {
+ const hay = cell.content.toLowerCase();
+ const needle = (keyword || '').toLowerCase();
+
+ if (!needle || hay.includes(needle)) {
+ rowsToShow.push(cell.rowIndex);
+ } else {
+ rowsToHide.push(cell.rowIndex);
+ }
+ });
+
+ return {
+ rowsToHide,
+ rowsToShow
+ };
+ }
+
+ getRowCount() {
+ return this.rowCount;
+ }
+
+ _getNextRowCount() {
+ const val = this.rowCount;
+
+ this.rowCount++;
+ return val;
+ }
+
+ getRows(start, end) {
+ return this.rows.slice(start, end);
+ }
+
+ getColumns(skipStandardColumns) {
+ let columns = this.columns;
+
+ if (skipStandardColumns) {
+ columns = columns.slice(this.getStandardColumnCount());
+ }
+
+ return columns;
+ }
+
+ getStandardColumnCount() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getColumnCount(skipStandardColumns) {
+ let val = this.columns.length;
+
+ if (skipStandardColumns) {
+ val = val - this.getStandardColumnCount();
+ }
+
+ return val;
+ }
+
+ getColumn(colIndex) {
+ colIndex = +colIndex;
+ return this.columns.find(col => col.colIndex === colIndex);
+ }
+
+ getRow(rowIndex) {
+ rowIndex = +rowIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex);
+ }
+
+ getCell(colIndex, rowIndex) {
+ rowIndex = +rowIndex;
+ colIndex = +colIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
+ }
+
+ get() {
+ return {
+ columns: this.columns,
+ rows: this.rows
+ };
+ }
+
+ hasColumn(name) {
+ return Boolean(this.columns.find(col => col.content === name));
+ }
+
+ hasColumnById(id) {
+ return Boolean(this.columns.find(col => col.id === id));
+ }
+
+ getColumnIndex(name) {
+ return this.columns.findIndex(col => col.content === name);
+ }
+
+ getColumnIndexById(id) {
+ return this.columns.findIndex(col => col.id === id);
+ }
+
+ getCheckboxHTML() {
+ return '';
+ }
}
// Custom Errors
diff --git a/src/datatable.js b/src/datatable.js
index 89d6aec..33bad20 100644
--- a/src/datatable.js
+++ b/src/datatable.js
@@ -10,50 +10,48 @@ import DEFAULT_OPTIONS from './defaults';
import './style.css';
class DataTable {
- constructor(wrapper, options) {
- DataTable.instances++;
+ constructor(wrapper, options) {
+ DataTable.instances++;
- if (typeof wrapper === 'string') {
- // css selector
- wrapper = document.querySelector(wrapper);
- }
- this.wrapper = wrapper;
- if (!(this.wrapper instanceof HTMLElement)) {
- throw new Error('Invalid argument given for `wrapper`');
+ if (typeof wrapper === 'string') {
+ // css selector
+ wrapper = document.querySelector(wrapper);
+ }
+ this.wrapper = wrapper;
+ if (!(this.wrapper instanceof HTMLElement)) {
+ throw new Error('Invalid argument given for `wrapper`');
+ }
+
+ this.options = Object.assign({}, DEFAULT_OPTIONS, options);
+ this.options.headerDropdown =
+ DEFAULT_OPTIONS.headerDropdown
+ .concat(options.headerDropdown || []);
+ // custom user events
+ this.events = Object.assign({}, DEFAULT_OPTIONS.events, options.events || {});
+ this.fireEvent = this.fireEvent.bind(this);
+
+ this.prepare();
+
+ this.style = new Style(this);
+ this.keyboard = new Keyboard(this.wrapper);
+ this.datamanager = new DataManager(this.options);
+ this.rowmanager = new RowManager(this);
+ this.columnmanager = new ColumnManager(this);
+ this.cellmanager = new CellManager(this);
+ this.bodyRenderer = new BodyRenderer(this);
+
+ if (this.options.data) {
+ this.refresh();
+ }
}
- this.options = Object.assign({}, DEFAULT_OPTIONS, options);
- this.options.headerDropdown =
- DEFAULT_OPTIONS.headerDropdown
- .concat(options.headerDropdown || []);
- // custom user events
- this.events = Object.assign(
- {}, DEFAULT_OPTIONS.events, options.events || {}
- );
- this.fireEvent = this.fireEvent.bind(this);
-
- this.prepare();
-
- this.style = new Style(this);
- this.keyboard = new Keyboard(this.wrapper);
- this.datamanager = new DataManager(this.options);
- this.rowmanager = new RowManager(this);
- this.columnmanager = new ColumnManager(this);
- this.cellmanager = new CellManager(this);
- this.bodyRenderer = new BodyRenderer(this);
-
- if (this.options.data) {
- this.refresh();
+ prepare() {
+ this.prepareDom();
+ this.unfreeze();
}
- }
- prepare() {
- this.prepareDom();
- this.unfreeze();
- }
-
- prepareDom() {
- this.wrapper.innerHTML = `
+ prepareDom() {
+ this.wrapper.innerHTML = `
@@ -67,110 +65,110 @@ class DataTable {
`;
- this.datatableWrapper = $('.data-table', this.wrapper);
- this.header = $('.data-table-header', this.wrapper);
- this.bodyScrollable = $('.body-scrollable', this.wrapper);
- this.freezeContainer = $('.freeze-container', this.wrapper);
- }
-
- refresh(data) {
- this.datamanager.init(data);
- this.render();
- this.setDimensions();
- }
-
- destroy() {
- this.wrapper.innerHTML = '';
- this.style.destroy();
- }
-
- appendRows(rows) {
- this.datamanager.appendRows(rows);
- this.rowmanager.refreshRows();
- }
-
- refreshRow(row, rowIndex) {
- this.rowmanager.refreshRow(row, rowIndex);
- }
-
- render() {
- this.renderHeader();
- this.renderBody();
- }
-
- renderHeader() {
- this.columnmanager.renderHeader();
- }
-
- renderBody() {
- this.bodyRenderer.render();
- }
-
- setDimensions() {
- this.style.setDimensions();
- }
-
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
-
- getColumns() {
- return this.datamanager.getColumns();
- }
-
- getRows() {
- return this.datamanager.getRows();
- }
-
- getCell(colIndex, rowIndex) {
- return this.datamanager.getCell(colIndex, rowIndex);
- }
-
- getColumnHeaderElement(colIndex) {
- return this.columnmanager.getColumnHeaderElement(colIndex);
- }
-
- getViewportHeight() {
- if (!this.viewportHeight) {
- this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ this.datatableWrapper = $('.data-table', this.wrapper);
+ this.header = $('.data-table-header', this.wrapper);
+ this.bodyScrollable = $('.body-scrollable', this.wrapper);
+ this.freezeContainer = $('.freeze-container', this.wrapper);
}
- return this.viewportHeight;
- }
-
- sortColumn(colIndex, sortOrder) {
- this.columnmanager.sortColumn(colIndex, sortOrder);
- }
-
- removeColumn(colIndex) {
- this.columnmanager.removeColumn(colIndex);
- }
-
- scrollToLastColumn() {
- this.datatableWrapper.scrollLeft = 9999;
- }
-
- freeze() {
- $.style(this.freezeContainer, {
- display: ''
- });
- }
-
- unfreeze() {
- $.style(this.freezeContainer, {
- display: 'none'
- });
- }
-
- fireEvent(eventName, ...args) {
- this.events[eventName].apply(this, args);
- }
-
- log() {
- if (this.options.enableLogs) {
- console.log.apply(console, arguments);
+ refresh(data) {
+ this.datamanager.init(data);
+ this.render();
+ this.setDimensions();
+ }
+
+ destroy() {
+ this.wrapper.innerHTML = '';
+ this.style.destroy();
+ }
+
+ appendRows(rows) {
+ this.datamanager.appendRows(rows);
+ this.rowmanager.refreshRows();
+ }
+
+ refreshRow(row, rowIndex) {
+ this.rowmanager.refreshRow(row, rowIndex);
+ }
+
+ render() {
+ this.renderHeader();
+ this.renderBody();
+ }
+
+ renderHeader() {
+ this.columnmanager.renderHeader();
+ }
+
+ renderBody() {
+ this.bodyRenderer.render();
+ }
+
+ setDimensions() {
+ this.style.setDimensions();
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ getRows() {
+ return this.datamanager.getRows();
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getColumnHeaderElement(colIndex) {
+ return this.columnmanager.getColumnHeaderElement(colIndex);
+ }
+
+ getViewportHeight() {
+ if (!this.viewportHeight) {
+ this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ }
+
+ return this.viewportHeight;
+ }
+
+ sortColumn(colIndex, sortOrder) {
+ this.columnmanager.sortColumn(colIndex, sortOrder);
+ }
+
+ removeColumn(colIndex) {
+ this.columnmanager.removeColumn(colIndex);
+ }
+
+ scrollToLastColumn() {
+ this.datatableWrapper.scrollLeft = 9999;
+ }
+
+ freeze() {
+ $.style(this.freezeContainer, {
+ display: ''
+ });
+ }
+
+ unfreeze() {
+ $.style(this.freezeContainer, {
+ display: 'none'
+ });
+ }
+
+ fireEvent(eventName, ...args) {
+ this.events[eventName].apply(this, args);
+ }
+
+ log() {
+ if (this.options.enableLogs) {
+ console.log.apply(console, arguments);
+ }
}
- }
}
DataTable.instances = 0;
diff --git a/src/defaults.js b/src/defaults.js
index f306723..30f228e 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -1,51 +1,51 @@
export default {
- columns: [],
- data: [],
- dropdownButton: '▼',
- headerDropdown: [
- {
- label: 'Sort Ascending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'asc');
- }
+ columns: [],
+ data: [],
+ dropdownButton: '▼',
+ headerDropdown: [
+ {
+ label: 'Sort Ascending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'asc');
+ }
+ },
+ {
+ label: 'Sort Descending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'desc');
+ }
+ },
+ {
+ label: 'Reset sorting',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'none');
+ }
+ },
+ {
+ label: 'Remove column',
+ action: function (column) {
+ this.removeColumn(column.colIndex);
+ }
+ }
+ ],
+ events: {
+ onRemoveColumn(column) {},
+ onSwitchColumn(column1, column2) {},
+ onSortColumn(column) {}
},
- {
- label: 'Sort Descending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'desc');
- }
+ sortIndicator: {
+ asc: '↑',
+ desc: '↓',
+ none: ''
},
- {
- label: 'Reset sorting',
- action: function (column) {
- this.sortColumn(column.colIndex, 'none');
- }
- },
- {
- label: 'Remove column',
- action: function (column) {
- this.removeColumn(column.colIndex);
- }
- }
- ],
- events: {
- onRemoveColumn(column) {},
- onSwitchColumn(column1, column2) {},
- onSortColumn(column) {}
- },
- sortIndicator: {
- asc: '↑',
- desc: '↓',
- none: ''
- },
- freezeMessage: '',
- getEditor: () => {},
- addSerialNoColumn: true,
- addCheckboxColumn: false,
- enableClusterize: true,
- enableLogs: false,
- layout: 'fixed', // fixed, fluid
- noDataMessage: 'No Data',
- cellHeight: null,
- enableInlineFilters: false
+ freezeMessage: '',
+ getEditor: () => {},
+ addSerialNoColumn: true,
+ addCheckboxColumn: false,
+ enableClusterize: true,
+ enableLogs: false,
+ layout: 'fixed', // fixed, fluid
+ noDataMessage: 'No Data',
+ cellHeight: null,
+ enableInlineFilters: false
};
diff --git a/src/dom.js b/src/dom.js
index 29b2cf5..f930a90 100644
--- a/src/dom.js
+++ b/src/dom.js
@@ -1,172 +1,181 @@
-
export default function $(expr, con) {
- return typeof expr === 'string' ?
- (con || document).querySelector(expr) :
- expr || null;
+ return typeof expr === 'string' ?
+ (con || document).querySelector(expr) :
+ expr || null;
}
$.each = (expr, con) => {
- return typeof expr === 'string' ?
- Array.from((con || document).querySelectorAll(expr)) :
- expr || null;
+ return typeof expr === 'string' ?
+ Array.from((con || document).querySelectorAll(expr)) :
+ expr || null;
};
$.create = (tag, o) => {
- let element = document.createElement(tag);
+ let element = document.createElement(tag);
- for (let i in o) {
- let val = o[i];
+ for (let i in o) {
+ let val = o[i];
- if (i === 'inside') {
- $(val).appendChild(element);
- } else
- if (i === 'around') {
- let ref = $(val);
- ref.parentNode.insertBefore(element, ref);
- element.appendChild(ref);
- } else
- if (i === 'styles') {
- if (typeof val === 'object') {
- Object.keys(val).map(prop => {
- element.style[prop] = val[prop];
- });
- }
+ if (i === 'inside') {
+ $(val).appendChild(element);
} else
- if (i in element) {
+ if (i === 'around') {
+ let ref = $(val);
+ ref.parentNode.insertBefore(element, ref);
+ element.appendChild(ref);
+ } else
+ if (i === 'styles') {
+ if (typeof val === 'object') {
+ Object.keys(val).map(prop => {
+ element.style[prop] = val[prop];
+ });
+ }
+ } else
+ if (i in element) {
element[i] = val;
- } else {
+ } else {
element.setAttribute(i, val);
- }
- }
+ }
+ }
- return element;
+ return element;
};
$.on = (element, event, selector, callback) => {
- if (!callback) {
- callback = selector;
- $.bind(element, event, callback);
- } else {
- $.delegate(element, event, selector, callback);
- }
+ if (!callback) {
+ callback = selector;
+ $.bind(element, event, callback);
+ } else {
+ $.delegate(element, event, selector, callback);
+ }
};
$.off = (element, event, handler) => {
- element.removeEventListener(event, handler);
+ element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
- event.split(/\s+/).forEach(function (event) {
- element.addEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.addEventListener(event, callback);
+ });
};
$.delegate = (element, event, selector, callback) => {
- element.addEventListener(event, function (e) {
- const delegatedTarget = e.target.closest(selector);
- if (delegatedTarget) {
- e.delegatedTarget = delegatedTarget;
- callback.call(this, e, delegatedTarget);
- }
- });
+ element.addEventListener(event, function (e) {
+ const delegatedTarget = e.target.closest(selector);
+ if (delegatedTarget) {
+ e.delegatedTarget = delegatedTarget;
+ callback.call(this, e, delegatedTarget);
+ }
+ });
};
$.unbind = (element, o) => {
- if (element) {
- for (let event in o) {
- let callback = o[event];
+ if (element) {
+ for (let event in o) {
+ let callback = o[event];
- event.split(/\s+/).forEach(function (event) {
- element.removeEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.removeEventListener(event, callback);
+ });
+ }
}
- }
};
$.fire = (target, type, properties) => {
- let evt = document.createEvent('HTMLEvents');
+ let evt = document.createEvent('HTMLEvents');
- evt.initEvent(type, true, true);
+ evt.initEvent(type, true, true);
- for (let j in properties) {
- evt[j] = properties[j];
- }
+ for (let j in properties) {
+ evt[j] = properties[j];
+ }
- return target.dispatchEvent(evt);
+ return target.dispatchEvent(evt);
};
$.data = (element, attrs) => { // eslint-disable-line
- if (!attrs) {
- return element.dataset;
- }
+ if (!attrs) {
+ return element.dataset;
+ }
- for (const attr in attrs) {
- element.dataset[attr] = attrs[attr];
- }
+ for (const attr in attrs) {
+ element.dataset[attr] = attrs[attr];
+ }
};
$.style = (elements, styleMap) => { // eslint-disable-line
- if (typeof styleMap === 'string') {
- return $.getStyle(elements, styleMap);
- }
-
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- elements.map(element => {
- for (const prop in styleMap) {
- element.style[prop] = styleMap[prop];
+ if (typeof styleMap === 'string') {
+ return $.getStyle(elements, styleMap);
}
- });
+
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ elements.map(element => {
+ for (const prop in styleMap) {
+ element.style[prop] = styleMap[prop];
+ }
+ });
};
$.removeStyle = (elements, styleProps) => {
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- if (!Array.isArray(styleProps)) {
- styleProps = [styleProps];
- }
-
- elements.map(element => {
- for (const prop of styleProps) {
- element.style[prop] = '';
+ if (!Array.isArray(elements)) {
+ elements = [elements];
}
- });
+
+ if (!Array.isArray(styleProps)) {
+ styleProps = [styleProps];
+ }
+
+ elements.map(element => {
+ for (const prop of styleProps) {
+ element.style[prop] = '';
+ }
+ });
};
$.getStyle = (element, prop) => {
- let val = getComputedStyle(element)[prop];
+ let val = getComputedStyle(element)[prop];
- if (['width', 'height'].includes(prop)) {
- val = parseFloat(val);
- }
+ if (['width', 'height'].includes(prop)) {
+ val = parseFloat(val);
+ }
- return val;
+ return val;
};
$.closest = (selector, element) => {
- if (!element) return null;
+ if (!element) return null;
- if (element.matches(selector)) {
- return element;
- }
+ if (element.matches(selector)) {
+ return element;
+ }
- return $.closest(selector, element.parentNode);
+ return $.closest(selector, element.parentNode);
};
$.inViewport = (el, parentEl) => {
- const { top, left, bottom, right } = el.getBoundingClientRect();
- const { top: pTop, left: pLeft, bottom: pBottom, right: pRight } = parentEl.getBoundingClientRect();
+ const {
+ top,
+ left,
+ bottom,
+ right
+ } = el.getBoundingClientRect();
+ const {
+ top: pTop,
+ left: pLeft,
+ bottom: pBottom,
+ right: pRight
+ } = parentEl.getBoundingClientRect();
- return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
+ return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
};
$.scrollTop = function scrollTop(element, pixels) {
- requestAnimationFrame(() => {
- element.scrollTop = pixels;
- });
+ requestAnimationFrame(() => {
+ element.scrollTop = pixels;
+ });
};
diff --git a/src/keyboard.js b/src/keyboard.js
index abd9e96..77f2cbe 100644
--- a/src/keyboard.js
+++ b/src/keyboard.js
@@ -1,56 +1,56 @@
import $ from './dom';
const KEYCODES = {
- 13: 'enter',
- 91: 'meta',
- 16: 'shift',
- 17: 'ctrl',
- 18: 'alt',
- 37: 'left',
- 38: 'up',
- 39: 'right',
- 40: 'down',
- 9: 'tab',
- 27: 'esc',
- 67: 'c',
- 70: 'f'
+ 13: 'enter',
+ 91: 'meta',
+ 16: 'shift',
+ 17: 'ctrl',
+ 18: 'alt',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 9: 'tab',
+ 27: 'esc',
+ 67: 'c',
+ 70: 'f'
};
export default class Keyboard {
- constructor(element) {
- this.listeners = {};
- $.on(element, 'keydown', this.handler.bind(this));
- }
-
- handler(e) {
- let key = KEYCODES[e.keyCode];
-
- if (e.shiftKey && key !== 'shift') {
- key = 'shift+' + key;
+ constructor(element) {
+ this.listeners = {};
+ $.on(element, 'keydown', this.handler.bind(this));
}
- if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
- key = 'ctrl+' + key;
- }
+ handler(e) {
+ let key = KEYCODES[e.keyCode];
- const listeners = this.listeners[key];
-
- if (listeners && listeners.length > 0) {
- for (let listener of listeners) {
- const preventBubbling = listener(e);
- if (preventBubbling === undefined || preventBubbling === true) {
- e.preventDefault();
+ if (e.shiftKey && key !== 'shift') {
+ key = 'shift+' + key;
+ }
+
+ if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
+ key = 'ctrl+' + key;
+ }
+
+ const listeners = this.listeners[key];
+
+ if (listeners && listeners.length > 0) {
+ for (let listener of listeners) {
+ const preventBubbling = listener(e);
+ if (preventBubbling === undefined || preventBubbling === true) {
+ e.preventDefault();
+ }
+ }
}
- }
}
- }
- on(key, listener) {
- const keys = key.split(',').map(k => k.trim());
+ on(key, listener) {
+ const keys = key.split(',').map(k => k.trim());
- keys.map(key => {
- this.listeners[key] = this.listeners[key] || [];
- this.listeners[key].push(listener);
- });
- }
+ keys.map(key => {
+ this.listeners[key] = this.listeners[key] || [];
+ this.listeners[key].push(listener);
+ });
+ }
}
diff --git a/src/performance.js b/src/performance.js
deleted file mode 100644
index 9dcd0de..0000000
--- a/src/performance.js
+++ /dev/null
@@ -1,14 +0,0 @@
-class Performance {
- start() {
- this._start = window.performance.now();
- }
-
- end() {
- this._end = window.performance.now();
- console.log(this._end - this._start);
- }
-}
-
-let perf = new Performance();
-
-export default perf;
diff --git a/src/rowmanager.js b/src/rowmanager.js
index f601045..28fb68d 100644
--- a/src/rowmanager.js
+++ b/src/rowmanager.js
@@ -1,210 +1,224 @@
import $ from './dom';
-import { makeDataAttributeString, promisify } from './utils';
+import {
+ makeDataAttributeString,
+ promisify
+} from './utils';
export default class RowManager {
- constructor(instance) {
- this.instance = instance;
- this.options = this.instance.options;
- this.wrapper = this.instance.wrapper;
- this.bodyScrollable = this.instance.bodyScrollable;
+ constructor(instance) {
+ this.instance = instance;
+ this.options = this.instance.options;
+ this.wrapper = this.instance.wrapper;
+ this.bodyScrollable = this.instance.bodyScrollable;
- this.bindEvents();
- this.refreshRows = promisify(this.refreshRows, this);
- }
-
- get datamanager() {
- return this.instance.datamanager;
- }
-
- get cellmanager() {
- return this.instance.cellmanager;
- }
-
- bindEvents() {
- this.bindCheckbox();
- }
-
- bindCheckbox() {
- if (!this.options.addCheckboxColumn) return;
-
- // map of checked rows
- this.checkMap = [];
-
- $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
- const $cell = $checkbox.closest('.data-table-col');
- const { rowIndex, isHeader } = $.data($cell);
- const checked = $checkbox.checked;
-
- if (isHeader) {
- this.checkAll(checked);
- } else {
- this.checkRow(rowIndex, checked);
- }
- });
- }
-
- refreshRows() {
- this.instance.renderBody();
- this.instance.setDimensions();
- }
-
- refreshRow(row, rowIndex) {
- const _row = this.datamanager.updateRow(row, rowIndex);
-
- _row.forEach(cell => {
- this.cellmanager.refreshCell(cell);
- });
- }
-
- getCheckedRows() {
- if (!this.checkMap) {
- return [];
+ this.bindEvents();
+ this.refreshRows = promisify(this.refreshRows, this);
}
- return this.checkMap
- .map((c, rowIndex) => {
- if (c) {
- return rowIndex;
+ get datamanager() {
+ return this.instance.datamanager;
+ }
+
+ get cellmanager() {
+ return this.instance.cellmanager;
+ }
+
+ bindEvents() {
+ this.bindCheckbox();
+ }
+
+ bindCheckbox() {
+ if (!this.options.addCheckboxColumn) return;
+
+ // map of checked rows
+ this.checkMap = [];
+
+ $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
+ const $cell = $checkbox.closest('.data-table-col');
+ const {
+ rowIndex,
+ isHeader
+ } = $.data($cell);
+ const checked = $checkbox.checked;
+
+ if (isHeader) {
+ this.checkAll(checked);
+ } else {
+ this.checkRow(rowIndex, checked);
+ }
+ });
+ }
+
+ refreshRows() {
+ this.instance.renderBody();
+ this.instance.setDimensions();
+ }
+
+ refreshRow(row, rowIndex) {
+ const _row = this.datamanager.updateRow(row, rowIndex);
+
+ _row.forEach(cell => {
+ this.cellmanager.refreshCell(cell);
+ });
+ }
+
+ getCheckedRows() {
+ if (!this.checkMap) {
+ return [];
}
- return null;
- })
- .filter(c => {
- return c !== null || c !== undefined;
- });
- }
- highlightCheckedRows() {
- this.getCheckedRows()
- .map(rowIndex => this.checkRow(rowIndex, true));
- }
-
- checkRow(rowIndex, toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- this.checkMap[rowIndex] = value;
- // set checkbox value explicitly
- $.each(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`, this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight row
- this.highlightRow(rowIndex, toggle);
- }
-
- checkAll(toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- if (toggle) {
- this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
- } else {
- this.checkMap = [];
- }
- // set checkbox value
- $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight all
- this.highlightAll(toggle);
- }
-
- highlightRow(rowIndex, toggle = true) {
- const $row = this.getRow$(rowIndex);
- if (!$row) return;
-
- if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
- $row.classList.add('row-unhighlight');
- return;
+ return this.checkMap
+ .map((c, rowIndex) => {
+ if (c) {
+ return rowIndex;
+ }
+ return null;
+ })
+ .filter(c => {
+ return c !== null || c !== undefined;
+ });
}
- if (toggle && $row.classList.contains('row-unhighlight')) {
- $row.classList.remove('row-unhighlight');
+ highlightCheckedRows() {
+ this.getCheckedRows()
+ .map(rowIndex => this.checkRow(rowIndex, true));
}
- this._highlightedRows = this._highlightedRows || {};
-
- if (toggle) {
- $row.classList.add('row-highlight');
- this._highlightedRows[rowIndex] = $row;
- } else {
- $row.classList.remove('row-highlight');
- delete this._highlightedRows[rowIndex];
- }
- }
-
- highlightAll(toggle = true) {
- if (toggle) {
- this.bodyScrollable.classList.add('row-highlight-all');
- } else {
- this.bodyScrollable.classList.remove('row-highlight-all');
- for (const rowIndex in this._highlightedRows) {
- const $row = this._highlightedRows[rowIndex];
- $row.classList.remove('row-highlight');
- }
- this._highlightedRows = {};
- }
- }
-
- getRow$(rowIndex) {
- return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- }
-
- getTotalRows() {
- return this.datamanager.getRowCount();
- }
-
- getFirstRowIndex() {
- return 0;
- }
-
- getLastRowIndex() {
- return this.datamanager.getRowCount() - 1;
- }
-
- scrollToRow(rowIndex) {
- rowIndex = +rowIndex;
- this._lastScrollTo = this._lastScrollTo || 0;
- const $row = this.getRow$(rowIndex);
- if ($.inViewport($row, this.bodyScrollable)) return;
-
- const { height } = $row.getBoundingClientRect();
- const { top, bottom } = this.bodyScrollable.getBoundingClientRect();
- const rowsInView = Math.floor((bottom - top) / height);
-
- let offset = 0;
- if (rowIndex > this._lastScrollTo) {
- offset = height * ((rowIndex + 1) - rowsInView);
- } else {
- offset = height * ((rowIndex + 1) - 1);
+ checkRow(rowIndex, toggle) {
+ const value = toggle ? 1 : 0;
+ const selector = rowIndex =>
+ `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
+ // update internal map
+ this.checkMap[rowIndex] = value;
+ // set checkbox value explicitly
+ $.each(selector(rowIndex), this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight row
+ this.highlightRow(rowIndex, toggle);
}
- this._lastScrollTo = rowIndex;
- $.scrollTop(this.bodyScrollable, offset);
- }
+ checkAll(toggle) {
+ const value = toggle ? 1 : 0;
- getRowHTML(row, props) {
- const dataAttr = makeDataAttributeString(props);
-
- if (props.isFilter) {
- row = row.map(cell => (Object.assign(cell, {
- content: this.getFilterInput({ colIndex: cell.colIndex }),
- isFilter: 1,
- isHeader: undefined,
- editable: false
- })));
+ // update internal map
+ if (toggle) {
+ this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
+ } else {
+ this.checkMap = [];
+ }
+ // set checkbox value
+ $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight all
+ this.highlightAll(toggle);
}
- return `
-
- ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
-
- `;
- }
+ highlightRow(rowIndex, toggle = true) {
+ const $row = this.getRow$(rowIndex);
+ if (!$row) return;
- getFilterInput(props) {
- const dataAttr = makeDataAttributeString(props);
- return ``;
- }
+ if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
+ $row.classList.add('row-unhighlight');
+ return;
+ }
+
+ if (toggle && $row.classList.contains('row-unhighlight')) {
+ $row.classList.remove('row-unhighlight');
+ }
+
+ this._highlightedRows = this._highlightedRows || {};
+
+ if (toggle) {
+ $row.classList.add('row-highlight');
+ this._highlightedRows[rowIndex] = $row;
+ } else {
+ $row.classList.remove('row-highlight');
+ delete this._highlightedRows[rowIndex];
+ }
+ }
+
+ highlightAll(toggle = true) {
+ if (toggle) {
+ this.bodyScrollable.classList.add('row-highlight-all');
+ } else {
+ this.bodyScrollable.classList.remove('row-highlight-all');
+ for (const rowIndex in this._highlightedRows) {
+ const $row = this._highlightedRows[rowIndex];
+ $row.classList.remove('row-highlight');
+ }
+ this._highlightedRows = {};
+ }
+ }
+
+ getRow$(rowIndex) {
+ return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ }
+
+ getTotalRows() {
+ return this.datamanager.getRowCount();
+ }
+
+ getFirstRowIndex() {
+ return 0;
+ }
+
+ getLastRowIndex() {
+ return this.datamanager.getRowCount() - 1;
+ }
+
+ scrollToRow(rowIndex) {
+ rowIndex = +rowIndex;
+ this._lastScrollTo = this._lastScrollTo || 0;
+ const $row = this.getRow$(rowIndex);
+ if ($.inViewport($row, this.bodyScrollable)) return;
+
+ const {
+ height
+ } = $row.getBoundingClientRect();
+ const {
+ top,
+ bottom
+ } = this.bodyScrollable.getBoundingClientRect();
+ const rowsInView = Math.floor((bottom - top) / height);
+
+ let offset = 0;
+ if (rowIndex > this._lastScrollTo) {
+ offset = height * ((rowIndex + 1) - rowsInView);
+ } else {
+ offset = height * ((rowIndex + 1) - 1);
+ }
+
+ this._lastScrollTo = rowIndex;
+ $.scrollTop(this.bodyScrollable, offset);
+ }
+
+ getRowHTML(row, props) {
+ const dataAttr = makeDataAttributeString(props);
+
+ if (props.isFilter) {
+ row = row.map(cell => (Object.assign(cell, {
+ content: this.getFilterInput({
+ colIndex: cell.colIndex
+ }),
+ isFilter: 1,
+ isHeader: undefined,
+ editable: false
+ })));
+ }
+
+ return `
+
+ ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
+
+ `;
+ }
+
+ getFilterInput(props) {
+ const dataAttr = makeDataAttributeString(props);
+ return ``;
+ }
}
diff --git a/src/style.css b/src/style.css
index 9ed0c29..9cd2d1a 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,3 +1,4 @@
+/* This file is processed by postcss */
/* variables */
:root {
diff --git a/src/style.js b/src/style.js
index 714eb49..0e29d32 100644
--- a/src/style.js
+++ b/src/style.js
@@ -1,235 +1,241 @@
import $ from './dom';
import {
- camelCaseToDash,
- linkProperties,
- throttle
+ camelCaseToDash,
+ linkProperties,
+ throttle
} from './utils';
export default class Style {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options', 'datamanager', 'columnmanager',
- 'header', 'bodyScrollable', 'getColumn'
- ]);
+ linkProperties(this, this.instance, [
+ 'options', 'datamanager', 'columnmanager',
+ 'header', 'bodyScrollable', 'getColumn'
+ ]);
- this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
- instance.datatableWrapper.classList.add(this.scopeClass);
+ this.scopeClass = 'datatable-instance-' + instance.constructor.instances;
+ instance.datatableWrapper.classList.add(this.scopeClass);
- const styleEl = document.createElement('style');
- instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
- this.styleEl = styleEl;
- this.styleSheet = styleEl.sheet;
+ const styleEl = document.createElement('style');
+ instance.wrapper.insertBefore(styleEl, instance.datatableWrapper);
+ this.styleEl = styleEl;
+ this.styleSheet = styleEl.sheet;
- this.bindResizeWindow();
- }
+ this.bindResizeWindow();
+ }
+
+ bindResizeWindow() {
+ if (this.options.layout === 'fluid') {
+ $.on(window, 'resize', throttle(() => {
+ this.distributeRemainingWidth();
+ this.refreshColumnWidth();
+ this.setBodyStyle();
+ }, 300));
+ }
+ }
+
+ destroy() {
+ this.styleEl.remove();
+ }
+
+ setStyle(selector, styleMap, index = -1) {
+ const styles = Object.keys(styleMap)
+ .map(prop => {
+ if (!prop.includes('-')) {
+ prop = camelCaseToDash(prop);
+ }
+ return `${prop}:${styleMap[prop]};`;
+ })
+ .join('');
+ let prefixedSelector = selector
+ .split(',')
+ .map(r => `.${this.scopeClass} ${r}`)
+ .join(',');
+
+ let ruleString = `${prefixedSelector} { ${styles} }`;
+
+ let _index = this.styleSheet.cssRules.length;
+ if (index !== -1) {
+ this.styleSheet.deleteRule(index);
+ _index = index;
+ }
+
+ this.styleSheet.insertRule(ruleString, _index);
+ return _index;
+ }
+
+ setDimensions() {
+ this.setHeaderStyle();
+
+ this.setupMinWidth();
+ this.setupNaturalColumnWidth();
+ this.setupColumnWidth();
- bindResizeWindow() {
- if (this.options.layout === 'fluid') {
- $.on(window, 'resize', throttle(() => {
this.distributeRemainingWidth();
- this.refreshColumnWidth();
+ this.setColumnStyle();
+ this.setDefaultCellHeight();
this.setBodyStyle();
- }, 300));
- }
- }
-
- destroy() {
- this.styleEl.remove();
- }
-
- setStyle(rule, styleMap, index = -1) {
- const styles = Object.keys(styleMap)
- .map(prop => {
- if (!prop.includes('-')) {
- prop = camelCaseToDash(prop);
- }
- return `${prop}:${styleMap[prop]};`;
- })
- .join('');
- let ruleString = `.${this.scopeClass} ${rule} { ${styles} }`;
-
- let _index = this.styleSheet.cssRules.length;
- if (index !== -1) {
- this.styleSheet.deleteRule(index);
- _index = index;
}
- this.styleSheet.insertRule(ruleString, _index);
- return _index;
- }
+ setHeaderStyle() {
+ if (this.options.layout === 'fluid') {
+ // setting width as 0 will ensure that the
+ // header doesn't take the available space
+ $.style(this.header, {
+ width: 0
+ });
+ }
- setDimensions() {
- this.setHeaderStyle();
+ $.style(this.header, {
+ margin: 0
+ });
- this.setupMinWidth();
- this.setupNaturalColumnWidth();
- this.setupColumnWidth();
+ // don't show resize cursor on nonResizable columns
+ const nonResizableColumnsSelector = this.datamanager.getColumns()
+ .filter(col => col.resizable === false)
+ .map(col => col.colIndex)
+ .map(i => `.data-table-header [data-col-index="${i}"]`)
+ .join();
- this.distributeRemainingWidth();
- this.setColumnStyle();
- this.setDefaultCellHeight();
- this.setBodyStyle();
- }
-
- setHeaderStyle() {
- if (this.options.layout === 'fluid') {
- // setting width as 0 will ensure that the
- // header doesn't take the available space
- $.style(this.header, {
- width: 0
- });
+ this.setStyle(nonResizableColumnsSelector, {
+ cursor: 'pointer'
+ });
}
- $.style(this.header, {
- margin: 0
- });
+ setupMinWidth() {
+ $.each('.data-table-col[data-is-header]', this.header).map(col => {
+ const width = $.style($('.content', col), 'width');
+ const {
+ colIndex
+ } = $.data(col);
+ const column = this.getColumn(colIndex);
- // don't show resize cursor on nonResizable columns
- const nonResizableColumnsSelector = this.datamanager.getColumns()
- .filter(col => col.resizable === false)
- .map(col => col.colIndex)
- .map(i => `.data-table-header [data-col-index="${i}"]`)
- .join();
-
- this.setStyle(nonResizableColumnsSelector, {
- cursor: 'pointer'
- });
- }
-
- setupMinWidth() {
- $.each('.data-table-col[data-is-header]', this.header).map(col => {
- const width = $.style($('.content', col), 'width');
- const {
- colIndex
- } = $.data(col);
- const column = this.getColumn(colIndex);
-
- if (!column.minWidth) {
- // only set this once
- column.minWidth = width;
- }
- });
- }
-
- setupNaturalColumnWidth() {
- if (!$('.data-table-row')) return;
-
- // set initial width as naturally calculated by table's first row
- $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
- const {
- colIndex
- } = $.data($cell);
- const column = this.datamanager.getColumn(colIndex);
-
- let naturalWidth = $.style($('.content', $cell), 'width');
-
- if (column.id === '_rowIndex') {
- // width based on rowCount
- const rowCount = this.datamanager.getRowCount();
- const digits = (rowCount + '').length;
- if (digits > 2) {
- naturalWidth = naturalWidth + ((digits - 2) * 8);
- }
- }
-
- column.naturalWidth = naturalWidth;
- });
- }
-
- setupColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- if (!column.width) {
- column.width = column.naturalWidth;
- }
- if (column.width < column.minWidth) {
- column.width = column.minWidth;
- }
- });
- }
-
- distributeRemainingWidth() {
- if (this.options.layout !== 'fluid') return;
-
- const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
- const headerWidth = $.style(this.header, 'width');
- const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
- const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
-
- resizableColumns.map(col => {
- const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
- let finalWidth = Math.floor(width + deltaWidth) - 2;
-
- this.datamanager.updateColumn(col.colIndex, {
- width: finalWidth
- });
- });
- }
-
- setDefaultCellHeight() {
- if (this.__cellHeightSet) return;
- const height = this.options.cellHeight || $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
- if (height) {
- this.setCellHeight(height);
- this.__cellHeightSet = true;
+ if (!column.minWidth) {
+ // only set this once
+ column.minWidth = width;
+ }
+ });
}
- }
- setCellHeight(height) {
- this.setStyle('.data-table-col .content', {
- height: height + 'px'
- });
- this.setStyle('.data-table-col .edit-cell', {
- height: height + 'px'
- });
- }
+ setupNaturalColumnWidth() {
+ if (!$('.data-table-row')) return;
- setColumnStyle() {
- // align columns
- this.datamanager.getColumns()
- .map(column => {
- // alignment
- if (['left', 'center', 'right'].includes(column.align)) {
- this.setStyle(`[data-col-index="${column.colIndex}"]`, {
- 'text-align': column.align
- });
+ // set initial width as naturally calculated by table's first row
+ $.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
+ const {
+ colIndex
+ } = $.data($cell);
+ const column = this.datamanager.getColumn(colIndex);
+
+ let naturalWidth = $.style($('.content', $cell), 'width');
+
+ if (column.id === '_rowIndex') {
+ // width based on rowCount
+ const rowCount = this.datamanager.getRowCount();
+ const digits = (rowCount + '').length;
+ if (digits > 1) {
+ naturalWidth = naturalWidth + ((digits - 1) * 8);
+ }
+ }
+
+ column.naturalWidth = naturalWidth;
+ });
+ }
+
+ setupColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ if (!column.width) {
+ column.width = column.naturalWidth;
+ }
+ if (column.width < column.minWidth) {
+ column.width = column.minWidth;
+ }
+ });
+ }
+
+ distributeRemainingWidth() {
+ if (this.options.layout !== 'fluid') return;
+
+ const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
+ const headerWidth = $.style(this.header, 'width');
+ const resizableColumns = this.datamanager.getColumns().filter(col => col.resizable);
+ const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
+
+ resizableColumns.map(col => {
+ const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
+ let finalWidth = Math.floor(width + deltaWidth) - 2;
+
+ this.datamanager.updateColumn(col.colIndex, {
+ width: finalWidth
+ });
+ });
+ }
+
+ setDefaultCellHeight() {
+ if (this.__cellHeightSet) return;
+ const height = this.options.cellHeight ||
+ $.style($('.data-table-col', this.instance.datatableWrapper), 'height');
+ if (height) {
+ this.setCellHeight(height);
+ this.__cellHeightSet = true;
}
- // width
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- this.setBodyStyle();
- }
+ }
- refreshColumnWidth() {
- this.datamanager.getColumns()
- .map(column => {
- this.columnmanager.setColumnHeaderWidth(column.colIndex);
- this.columnmanager.setColumnWidth(column.colIndex);
- });
- }
+ setCellHeight(height) {
+ this.setStyle('.data-table-col .content', {
+ height: height + 'px'
+ });
+ this.setStyle('.data-table-col .edit-cell', {
+ height: height + 'px'
+ });
+ }
- setBodyStyle() {
- const width = $.style(this.header, 'width');
+ setColumnStyle() {
+ // align columns
+ this.datamanager.getColumns()
+ .map(column => {
+ // alignment
+ if (['left', 'center', 'right'].includes(column.align)) {
+ this.setStyle(`[data-col-index="${column.colIndex}"]`, {
+ 'text-align': column.align
+ });
+ }
+ // width
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ this.setBodyStyle();
+ }
- $.style(this.bodyScrollable, {
- width: width + 'px'
- });
+ refreshColumnWidth() {
+ this.datamanager.getColumns()
+ .map(column => {
+ this.columnmanager.setColumnHeaderWidth(column.colIndex);
+ this.columnmanager.setColumnWidth(column.colIndex);
+ });
+ }
- $.style(this.bodyScrollable, {
- marginTop: $.style(this.header, 'height') + 'px'
- });
+ setBodyStyle() {
+ const width = $.style(this.header, 'width');
- $.style($('table', this.bodyScrollable), {
- margin: 0
- });
- }
+ $.style(this.bodyScrollable, {
+ width: width + 'px'
+ });
- getColumnHeaderElement(colIndex) {
- colIndex = +colIndex;
- if (colIndex < 0) return null;
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.style(this.bodyScrollable, {
+ marginTop: $.style(this.header, 'height') + 'px'
+ });
+
+ $.style($('table', this.bodyScrollable), {
+ margin: 0
+ });
+ }
+
+ getColumnHeaderElement(colIndex) {
+ colIndex = +colIndex;
+ if (colIndex < 0) return null;
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
}
diff --git a/src/utils.js b/src/utils.js
index e2d2be5..e2969a1 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -2,153 +2,153 @@ import _throttle from 'lodash/throttle';
import _debounce from 'lodash/debounce';
export function camelCaseToDash(str) {
- return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
+ return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
}
export function makeDataAttributeString(props) {
- const keys = Object.keys(props);
+ const keys = Object.keys(props);
- return keys
- .map((key) => {
- const _key = camelCaseToDash(key);
- const val = props[key];
+ return keys
+ .map((key) => {
+ const _key = camelCaseToDash(key);
+ const val = props[key];
- if (val === undefined) return '';
- return `data-${_key}="${val}" `;
- })
- .join('')
- .trim();
+ if (val === undefined) return '';
+ return `data-${_key}="${val}" `;
+ })
+ .join('')
+ .trim();
}
export function getDefault(a, b) {
- return a !== undefined ? a : b;
+ return a !== undefined ? a : b;
}
export function escapeRegExp(str) {
- // https://stackoverflow.com/a/6969486
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
+ // https://stackoverflow.com/a/6969486
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
export function getCSSString(styleMap) {
- let style = '';
+ let style = '';
- for (const prop in styleMap) {
- if (styleMap.hasOwnProperty(prop)) {
- style += `${prop}: ${styleMap[prop]}; `;
+ for (const prop in styleMap) {
+ if (styleMap.hasOwnProperty(prop)) {
+ style += `${prop}: ${styleMap[prop]}; `;
+ }
}
- }
- return style.trim();
+ return style.trim();
}
export function getCSSRuleBlock(rule, styleMap) {
- return `${rule} { ${getCSSString(styleMap)} }`;
+ return `${rule} { ${getCSSString(styleMap)} }`;
}
export function buildCSSRule(rule, styleMap, cssRulesString = '') {
- // build css rules efficiently,
- // append new rule if doesnt exist,
- // update existing ones
+ // build css rules efficiently,
+ // append new rule if doesnt exist,
+ // update existing ones
- const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
- const rulePattern = new RegExp(rulePatternStr, 'g');
+ const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
+ const rulePattern = new RegExp(rulePatternStr, 'g');
- if (cssRulesString && cssRulesString.match(rulePattern)) {
- for (const property in styleMap) {
- const value = styleMap[property];
- const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
+ if (cssRulesString && cssRulesString.match(rulePattern)) {
+ for (const property in styleMap) {
+ const value = styleMap[property];
+ const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
- cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
- if (propertyStr.match(propPattern)) {
- // property exists, replace value with new value
- propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
- return `${property}: ${value};`;
- });
+ cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
+ if (propertyStr.match(propPattern)) {
+ // property exists, replace value with new value
+ propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
+ return `${property}: ${value};`;
+ });
+ }
+ propertyStr = propertyStr.trim();
+
+ const replacer =
+ `${rule} { ${propertyStr} }`;
+
+ return replacer;
+ });
}
- propertyStr = propertyStr.trim();
- const replacer =
- `${rule} { ${propertyStr} }`;
-
- return replacer;
- });
+ return cssRulesString;
}
-
- return cssRulesString;
- }
- // no match, append new rule block
- return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
+ // no match, append new rule block
+ return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
}
export function removeCSSRule(rule, cssRulesString = '') {
- const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
- const rulePattern = new RegExp(rulePatternStr, 'g');
- let output = cssRulesString;
+ const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
+ const rulePattern = new RegExp(rulePatternStr, 'g');
+ let output = cssRulesString;
- if (cssRulesString && cssRulesString.match(rulePattern)) {
- output = cssRulesString.replace(rulePattern, '');
- }
+ if (cssRulesString && cssRulesString.match(rulePattern)) {
+ output = cssRulesString.replace(rulePattern, '');
+ }
- return output.trim();
+ return output.trim();
}
export function copyTextToClipboard(text) {
- // https://stackoverflow.com/a/30810322/5353542
- var textArea = document.createElement('textarea');
+ // https://stackoverflow.com/a/30810322/5353542
+ var textArea = document.createElement('textarea');
- //
- // *** This styling is an extra step which is likely not required. ***
- //
- // Why is it here? To ensure:
- // 1. the element is able to have focus and selection.
- // 2. if element was to flash render it has minimal visual impact.
- // 3. less flakyness with selection and copying which **might** occur if
- // the textarea element is not visible.
- //
- // The likelihood is the element won't even render, not even a flash,
- // so some of these are just precautions. However in IE the element
- // is visible whilst the popup box asking the user for permission for
- // the web page to copy to the clipboard.
- //
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
- // Place in top-left corner of screen regardless of scroll position.
- textArea.style.position = 'fixed';
- textArea.style.top = 0;
- textArea.style.left = 0;
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
- // Ensure it has a small width and height. Setting to 1px / 1em
- // doesn't work as this gives a negative w/h on some browsers.
- textArea.style.width = '2em';
- textArea.style.height = '2em';
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
- // We don't need padding, reducing the size if it does flash render.
- textArea.style.padding = 0;
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
- // Clean up any borders.
- textArea.style.border = 'none';
- textArea.style.outline = 'none';
- textArea.style.boxShadow = 'none';
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
- // Avoid flash of white box if rendered for any reason.
- textArea.style.background = 'transparent';
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
- textArea.value = text;
+ textArea.value = text;
- document.body.appendChild(textArea);
+ document.body.appendChild(textArea);
- textArea.select();
+ textArea.select();
- try {
- document.execCommand('copy');
- } catch (err) {
- console.log('Oops, unable to copy');
- }
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
- document.body.removeChild(textArea);
+ document.body.removeChild(textArea);
}
export function isNumeric(val) {
- return !isNaN(val);
+ return !isNaN(val);
}
export let throttle = _throttle;
@@ -156,30 +156,30 @@ export let throttle = _throttle;
export let debounce = _debounce;
export function promisify(fn, context = null) {
- return (...args) => {
- return new Promise(resolve => {
- setTimeout(() => {
- const out = fn.apply(context, args);
- resolve(out);
- }, 0);
- });
- };
+ return (...args) => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ const out = fn.apply(context, args);
+ resolve(out);
+ }, 0);
+ });
+ };
};
export function chainPromises(promises) {
- return promises.reduce(
- (prev, cur) => prev.then(cur), Promise.resolve()
- );
+ return promises.reduce(
+ (prev, cur) => prev.then(cur), Promise.resolve()
+ );
};
export function linkProperties(target, source, properties) {
- const props = properties.reduce((acc, prop) => {
- acc[prop] = {
- get() {
- return source[prop];
- }
- };
- return acc;
- }, {});
- Object.defineProperties(target, props);
+ const props = properties.reduce((acc, prop) => {
+ acc[prop] = {
+ get() {
+ return source[prop];
+ }
+ };
+ return acc;
+ }, {});
+ Object.defineProperties(target, props);
};