From 00b8000d495e6529dd171922ad60caa848c10a8c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 18 May 2018 12:23:19 +0530 Subject: [PATCH] v0.0.6 --- dist/frappe-datatable.cjs.js | 101 +- dist/frappe-datatable.css | 5 + dist/frappe-datatable.js | 6673 ++++++++++++------------- dist/frappe-datatable.min.css | 2 +- dist/frappe-datatable.min.js | 2 +- docs/assets/css/frappe-datatable.css | 434 +- docs/assets/js/frappe-datatable.js | 6742 +++++++++++++------------- package.json | 2 +- 8 files changed, 7041 insertions(+), 6920 deletions(-) diff --git a/dist/frappe-datatable.cjs.js b/dist/frappe-datatable.cjs.js index faa601a..79f2eef 100644 --- a/dist/frappe-datatable.cjs.js +++ b/dist/frappe-datatable.cjs.js @@ -242,10 +242,12 @@ 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. */ @@ -253,16 +255,34 @@ 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. */ @@ -291,11 +311,10 @@ function getRawTag(value) { try { value[symToStringTag] = undefined; - var unmasked = true; } catch (e) {} var result = nativeObjectToString.call(value); - if (unmasked) { + { if (isOwn) { value[symToStringTag] = tag; } else { @@ -330,8 +349,9 @@ function objectToString(value) { var _objectToString = objectToString; -var nullTag = '[object Null]'; -var undefinedTag = '[object Undefined]'; +/** `Object#toString` result references. */ +var nullTag = '[object Null]', + undefinedTag = '[object Undefined]'; /** Built-in value references. */ var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined; @@ -384,6 +404,7 @@ function isObjectLike(value) { var isObjectLike_1 = isObjectLike; +/** `Object#toString` result references. */ var symbolTag = '[object Symbol]'; /** @@ -410,6 +431,7 @@ function isSymbol(value) { var isSymbol_1 = isSymbol; +/** Used as references for various `Number` constants. */ var NAN = 0 / 0; /** Used to match leading and trailing whitespace. */ @@ -473,11 +495,12 @@ function toNumber(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; +var nativeMax = Math.max, + nativeMin = Math.min; /** * Creates a debounced function that delays invoking `func` until after `wait` @@ -659,6 +682,7 @@ function debounce(func, wait, options) { var debounce_1 = debounce; +/** Error message constants. */ var FUNC_ERROR_TEXT$1 = 'Expected a function'; /** @@ -805,7 +829,7 @@ function isNumeric(val) { let throttle$1 = throttle_1; -let debounce$2 = debounce_1; +let debounce$1 = debounce_1; function nextTick(fn, context = null) { return (...args) => { @@ -818,7 +842,6 @@ function nextTick(fn, context = null) { }); }; } - function linkProperties(target, source, properties) { const props = properties.reduce((acc, prop) => { acc[prop] = { @@ -830,7 +853,6 @@ function linkProperties(target, source, properties) { }, {}); Object.defineProperties(target, props); } - function isSet(val) { return val !== undefined || val !== null; } @@ -1226,7 +1248,7 @@ class DataManager { } } - const _row = this.prepareRow(row, rowIndex); + const _row = this.prepareRow(row, {rowIndex}); const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex); this.rows[index] = _row; @@ -1606,6 +1628,22 @@ class CellManager { this.instance.showToastMessage(message, 2); } }); + + if (this.options.pasteFromClipboard) { + this.keyboard.on('ctrl+v', (e) => { + // hack + // https://stackoverflow.com/a/2177059/5353542 + this.instance.pasteTarget.focus(); + + setTimeout(() => { + const data = this.instance.pasteTarget.value; + this.instance.pasteTarget.value = ''; + this.pasteContentInCell(data); + }, 10); + + return false; + }); + } } bindMouseEvents() { @@ -1994,6 +2032,30 @@ class CellManager { return rows.reduce((total, row) => total + row.length, 0); } + pasteContentInCell(data) { + if (!this.$focusedCell) return; + + const matrix = data + .split('\n') + .map(row => row.split('\t')) + .filter(row => row.length && row.every(it => it)); + + let { colIndex, rowIndex } = $.data(this.$focusedCell); + + let focusedCell = { + colIndex: +colIndex, + rowIndex: +rowIndex + }; + + matrix.forEach((row, i) => { + let rowIndex = i + focusedCell.rowIndex; + row.forEach((cell, j) => { + let colIndex = j + focusedCell.colIndex; + this.updateCell(colIndex, rowIndex, cell); + }); + }); + } + activateFilter(colIndex) { this.columnmanager.toggleFilter(); this.columnmanager.focusFilter(colIndex); @@ -2513,7 +2575,7 @@ class ColumnManager { this.rowmanager.showRows(rowsToShow); }); }; - $.on(this.header, 'keydown', '.dt-filter', debounce$2(handler, 300)); + $.on(this.header, 'keydown', '.dt-filter', debounce$1(handler, 300)); } sortRows(colIndex, sortOrder) { @@ -3299,7 +3361,8 @@ const KEYCODES = { 9: 'tab', 27: 'esc', 67: 'c', - 70: 'f' + 70: 'f', + 86: 'v' }; class Keyboard { @@ -3394,7 +3457,8 @@ var DEFAULT_OPTIONS = { inlineFilters: false, treeView: false, checkedRowStatus: true, - dynamicRowHeight: false + dynamicRowHeight: false, + pasteFromClipboard: false }; class DataTable { @@ -3434,8 +3498,11 @@ class DataTable { this.options || {}, options ); - this.options.headerDropdown - .push(...(options.headerDropdown || [])); + options.headerDropdown = options.headerDropdown || []; + this.options.headerDropdown = [ + ...DEFAULT_OPTIONS.headerDropdown, + ...options.headerDropdown + ]; // custom user events this.events = Object.assign( @@ -3464,6 +3531,7 @@ class DataTable {
+ `; @@ -3472,6 +3540,7 @@ class DataTable { this.bodyScrollable = $('.dt-scrollable', this.wrapper); this.freezeContainer = $('.dt-freeze', this.wrapper); this.toastMessage = $('.dt-toast', this.wrapper); + this.pasteTarget = $('.dt-paste-target', this.wrapper); } refresh(data, columns) { @@ -3589,7 +3658,7 @@ class DataTable { DataTable.instances = 0; var name = "frappe-datatable"; -var version = "0.0.4"; +var version = "0.0.5"; var description = "A modern datatable library for the web"; var main = "dist/frappe-datatable.cjs.js"; var scripts = {"start":"yarn run dev","build":"rollup -c","production":"rollup -c --production","build:docs":"rollup -c --docs","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"}; diff --git a/dist/frappe-datatable.css b/dist/frappe-datatable.css index c4253e8..edffe35 100644 --- a/dist/frappe-datatable.css +++ b/dist/frappe-datatable.css @@ -247,6 +247,11 @@ transform: translateY(-50%); } +.dt-paste-target { + position: fixed; + left: -999em; +} + body.dt-resize { cursor: col-resize; } diff --git a/dist/frappe-datatable.js b/dist/frappe-datatable.js index 60b8f48..5f1ec6f 100644 --- a/dist/frappe-datatable.js +++ b/dist/frappe-datatable.js @@ -1,2195 +1,2257 @@ var DataTable = (function (Sortable,Clusterize) { -'use strict'; + 'use strict'; -Sortable = Sortable && Sortable.hasOwnProperty('default') ? Sortable['default'] : Sortable; -Clusterize = Clusterize && Clusterize.hasOwnProperty('default') ? Clusterize['default'] : Clusterize; + 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; -} + 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; -}; + $.each = (expr, con) => { + return typeof expr === 'string' ? + Array.from((con || document).querySelectorAll(expr)) : + expr || null; + }; -$.create = (tag, o) => { - let element = document.createElement(tag); + $.create = (tag, o) => { + 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 === '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); } - } 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; - }); -}; - -$.scrollbarWidth = function scrollbarWidth() { - // Create the measurement node - const scrollDiv = document.createElement('div'); - $.style(scrollDiv, { - width: '100px', - height: '100px', - overflow: 'scroll', - position: 'absolute', - top: '-9999px' - }); - document.body.appendChild(scrollDiv); - - // Get the scrollbar width - const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - - // Delete the DIV - document.body.removeChild(scrollDiv); - - return scrollbarWidth; -}; - -/** - * 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 : {}; - -var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; - -var _freeGlobal = freeGlobal; - -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; - -var now = function() { - return _root.Date.now(); -}; - -var now_1 = now; - -var Symbol = _root.Symbol; - -var _Symbol = Symbol; - -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; - -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; - -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; - -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; - -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; - -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 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 nextTick(fn, context = null) { - return (...args) => { - return new Promise(resolve => { - const execute = () => { - const out = fn.apply(context, args); - resolve(out); - }; - setTimeout(execute); + }; + + $.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); }); }; -} -function linkProperties(target, source, properties) { - const props = properties.reduce((acc, prop) => { - acc[prop] = { - get() { - return source[prop]; - } - }; - return acc; - }, {}); - Object.defineProperties(target, props); -} - -function isSet(val) { - return val !== undefined || val !== null; -} - -function notSet(val) { - return !isSet(val); -} - -function isNumber(val) { - return !isNaN(val); -} - -function ensureArray(val) { - if (!Array.isArray(val)) { - return [val]; - } - return val; -} - -class DataManager { - constructor(options) { - this.options = options; - this.sortRows = nextTick(this.sortRows, this); - this.switchColumn = nextTick(this.switchColumn, this); - this.removeColumn = nextTick(this.removeColumn, this); - this.filterRows = nextTick(this.filterRows, this); - } - - init(data, columns) { - if (!data) { - data = this.options.data; - } - if (columns) { - this.options.columns = columns; - } - - this.data = data; - - this.rowCount = 0; - this.columns = []; - this.rows = []; - - this.prepareColumns(); - this.prepareRows(); - this.prepareTreeRows(); - this.prepareRowView(); - - 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.checkboxColumn && !this.hasColumnById('_checkbox')) { - const cell = { - id: '_checkbox', - content: this.getCheckboxHTML(), - editable: false, - resizable: false, - sortable: false, - focusable: false, - dropdown: false, - width: 32 - }; - this.columns.push(cell); - } - - if (this.options.serialNoColumn && !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.content = col.content || col.name || ''; - col.id = col.id || col.content; - return col; - }); - } - - prepareCell(content, i) { - const cell = { - content: '', - 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 = []; - let meta = { - rowIndex: index - }; - - if (Array.isArray(d)) { - // row is an array - if (this.options.checkboxColumn) { - row.push(this.getCheckboxHTML()); - } - if (this.options.serialNoColumn) { - 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]); - } - } - - meta.indent = d.indent || 0; - } - - return this.prepareRow(row, meta); - }); - } - - prepareTreeRows() { - this.rows.forEach((row, i) => { - if (isNumber(row.meta.indent)) { - // if (i === 36) debugger; - const nextRow = this.getRow(i + 1); - row.meta.isLeaf = !nextRow || - notSet(nextRow.meta.indent) || - nextRow.meta.indent <= row.meta.indent; + $.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); } }); - } + }; - prepareRowView() { - // This is order in which rows will be rendered in the table. - // When sorting happens, only this.rowViewOrder will change - // and not the original this.rows - this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); - } + $.unbind = (element, o) => { + if (element) { + for (let event in o) { + let callback = o[event]; - prepareRow(row, meta) { - const baseRowCell = { - rowIndex: meta.rowIndex, - indent: meta.indent - }; + event.split(/\s+/).forEach(function (event) { + element.removeEventListener(event, callback); + }); + } + } + }; - row = row - .map((cell, i) => this.prepareCell(cell, i)) - .map(cell => Object.assign({}, baseRowCell, cell)); + $.fire = (target, type, properties) => { + let evt = document.createEvent('HTMLEvents'); - // monkey patched in array object - row.meta = meta; - return row; - } + evt.initEvent(type, true, true); - validateColumns() { - const columns = this.options.columns; - if (!Array.isArray(columns)) { - throw new DataError('`columns` must be an array'); + for (let j in properties) { + evt[j] = properties[j]; } - columns.forEach((column, i) => { - if (typeof column !== 'string' && typeof column !== 'object') { - throw new DataError(`column "${i}" must be a string or an object`); + 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]; } }); - } + }; - 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.push(...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.rowViewOrder); - this.currentSort.sortOrder = sortOrder; - return; - } + $.removeStyle = (elements, styleProps) => { + if (!Array.isArray(elements)) { + elements = [elements]; } - this.rowViewOrder.sort((a, b) => { - const aIndex = a; - const bIndex = b; - const aContent = this.getCell(colIndex, a).content; - const bContent = this.getCell(colIndex, b).content; - - if (sortOrder === 'none') { - return aIndex - bIndex; - } else if (sortOrder === 'asc') { - if (aContent < bContent) return -1; - if (aContent > bContent) return 1; - if (aContent === bContent) return 0; - } else if (sortOrder === 'desc') { - if (aContent < bContent) return 1; - if (aContent > bContent) return -1; - if (aContent === bContent) return 0; - } - return 0; - }); - - if (this.hasColumnById('_rowIndex')) { - // update row index - const srNoColIndex = this.getColumnIndexById('_rowIndex'); - this.rows.forEach((row, index) => { - const viewIndex = this.rowViewOrder.indexOf(index); - const cell = row[srNoColIndex]; - cell.content = (viewIndex + 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.forEach(row => { - const newCell1 = Object.assign({}, row[index1], { - colIndex: index2 - }); - const newCell2 = Object.assign({}, row[index2], { - colIndex: index1 - }); - - row[index2] = newCell1; - row[index1] = newCell2; - }); - } - - 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.forEach(row => { - // remove cell - row.splice(index, 1); - // update colIndex - row.forEach((cell, i) => { - cell.colIndex = i; - }); - }); - } - - 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); - } + if (!Array.isArray(styleProps)) { + styleProps = [styleProps]; } - 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 = String(cell.content || '').toLowerCase(); - const needle = (keyword || '').toLowerCase(); - - if (!needle || hay.includes(needle)) { - rowsToShow.push(cell.rowIndex); - } else { - rowsToHide.push(cell.rowIndex); + elements.map(element => { + for (const prop of styleProps) { + element.style[prop] = ''; } }); + }; - this._filteredRows = rowsToShow; + $.getStyle = (element, prop) => { + let val = getComputedStyle(element)[prop]; - return { - rowsToHide, - rowsToShow - }; - } - - getFilteredRowIndices() { - return this._filteredRows || this.rows.map(row => row.meta.rowIndex); - } - - getRowCount() { - return this.rowCount; - } - - _getNextRowCount() { - const val = this.rowCount; - - this.rowCount++; - return val; - } - - getRows(start, end) { - return this.rows.slice(start, end); - } - - getRowsForView(start, end) { - const rows = this.rowViewOrder.map(i => this.rows[i]); - return rows.slice(start, end); - } - - getColumns(skipStandardColumns) { - let columns = this.columns; - - if (skipStandardColumns) { - columns = columns.slice(this.getStandardColumnCount()); - } - - return columns; - } - - getStandardColumnCount() { - if (this.options.checkboxColumn && this.options.serialNoColumn) { - return 2; - } - - if (this.options.checkboxColumn || this.options.serialNoColumn) { - return 1; - } - - return 0; - } - - getColumnCount(skipStandardColumns) { - let val = this.columns.length; - - if (skipStandardColumns) { - val = val - this.getStandardColumnCount(); + if (['width', 'height'].includes(prop)) { + val = parseFloat(val); } return val; - } + }; - getColumn(colIndex) { - colIndex = +colIndex; + $.closest = (selector, element) => { + if (!element) return null; - if (colIndex < 0) { - // negative indexes - colIndex = this.columns.length + colIndex; + if (element.matches(selector)) { + return element; } - return this.columns.find(col => col.colIndex === colIndex); - } + return $.closest(selector, element.parentNode); + }; - getColumnById(id) { - return this.columns.find(col => col.id === id); - } + $.inViewport = (el, parentEl) => { + const { + top, + left, + bottom, + right + } = el.getBoundingClientRect(); + const { + top: pTop, + left: pLeft, + bottom: pBottom, + right: pRight + } = parentEl.getBoundingClientRect(); - getRow(rowIndex) { - rowIndex = +rowIndex; - return this.rows[rowIndex]; - } + return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight; + }; - getCell(colIndex, rowIndex) { - rowIndex = +rowIndex; - colIndex = +colIndex; - return this.getRow(rowIndex)[colIndex]; - } + $.scrollTop = function scrollTop(element, pixels) { + requestAnimationFrame(() => { + element.scrollTop = pixels; + }); + }; - getChildren(parentRowIndex) { - parentRowIndex = +parentRowIndex; - const parentIndent = this.getRow(parentRowIndex).meta.indent; - const out = []; + $.scrollbarWidth = function scrollbarWidth() { + // Create the measurement node + const scrollDiv = document.createElement('div'); + $.style(scrollDiv, { + width: '100px', + height: '100px', + overflow: 'scroll', + position: 'absolute', + top: '-9999px' + }); + document.body.appendChild(scrollDiv); - for (let i = parentRowIndex + 1; i < this.rowCount; i++) { - const row = this.getRow(i); - if (isNaN(row.meta.indent)) continue; + // Get the scrollbar width + const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - if (row.meta.indent > parentIndent) { - out.push(i); - } + // Delete the DIV + document.body.removeChild(scrollDiv); - if (row.meta.indent === parentIndent) { - break; - } - } - - return out; - } - - getImmediateChildren(parentRowIndex) { - parentRowIndex = +parentRowIndex; - const parentIndent = this.getRow(parentRowIndex).meta.indent; - const out = []; - const childIndent = parentIndent + 1; - - for (let i = parentRowIndex + 1; i < this.rowCount; i++) { - const row = this.getRow(i); - if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; - - if (row.meta.indent === childIndent) { - out.push(i); - } - - if (row.meta.indent === parentIndent) { - break; - } - } - - return out; - } - - get() { - return { - columns: this.columns, - rows: this.rows - }; - } + return scrollbarWidth; + }; /** - * Returns the original data which was passed - * based on rowIndex - * @param {Number} rowIndex - * @returns Array|Object - * @memberof DataManager + * 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 */ - getData(rowIndex) { - return this.data[rowIndex]; + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); } - hasColumn(name) { - return Boolean(this.columns.find(col => col.content === name)); + 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; + } catch (e) {} + + var result = nativeObjectToString.call(value); + { + if (isOwn) { + value[symToStringTag] = tag; + } else { + delete value[symToStringTag]; + } + } + return result; } - hasColumnById(id) { - return Boolean(this.columns.find(col => col.id === id)); + 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); } - getColumnIndex(name) { - return this.columns.findIndex(col => col.content === name); + var _objectToString = objectToString; + + /** `Object#toString` result references. */ + var nullTag = '[object Null]', + 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); } - getColumnIndexById(id) { - return this.columns.findIndex(col => col.id === id); + 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'; } - getCheckboxHTML() { - return ''; - } -} + var isObjectLike_1 = isObjectLike; -// Custom Errors -class DataError extends TypeError {} + /** `Object#toString` result references. */ + var symbolTag = '[object Symbol]'; -class CellManager { - constructor(instance) { - this.instance = instance; - linkProperties(this, this.instance, [ - 'wrapper', - 'options', - 'style', - 'bodyScrollable', - 'columnmanager', - 'rowmanager', - 'datamanager', - 'keyboard' - ]); - - this.bindEvents(); + /** + * 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); } - bindEvents() { - this.bindFocusCell(); - this.bindEditCell(); - this.bindKeyboardSelection(); - this.bindCopyCellContents(); - this.bindMouseEvents(); - this.bindTreeEvents(); + 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); } - bindFocusCell() { - this.bindKeyboardNav(); + 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, + 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; } - bindEditCell() { - this.$editingCell = null; + var debounce_1 = debounce; - $.on(this.bodyScrollable, 'dblclick', '.dt-cell', (e, cell) => { - this.activateEditing(cell); - }); + /** Error message constants. */ + var FUNC_ERROR_TEXT$1 = 'Expected a function'; - this.keyboard.on('enter', () => { - 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(); - } - }); + /** + * 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 + }); } - bindKeyboardNav() { - const focusCell = (direction) => { - if (!this.$focusedCell || this.$editingCell) { - return false; - } + var throttle_1 = throttle; - let $cell = this.$focusedCell; + function camelCaseToDash(str) { + return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`); + } - 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); - } + function makeDataAttributeString(props) { + const keys = Object.keys(props); - this.focusCell($cell); - return true; + return keys + .map((key) => { + const _key = camelCaseToDash(key); + const val = props[key]; + + if (val === undefined) return ''; + return `data-${_key}="${val}" `; + }) + .join('') + .trim(); + } + + 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$1 = debounce_1; + + function nextTick(fn, context = null) { + return (...args) => { + return new Promise(resolve => { + const execute = () => { + const out = fn.apply(context, args); + resolve(out); + }; + setTimeout(execute); + }); }; + } + function linkProperties(target, source, properties) { + const props = properties.reduce((acc, prop) => { + acc[prop] = { + get() { + return source[prop]; + } + }; + return acc; + }, {}); + Object.defineProperties(target, props); + } + function isSet(val) { + return val !== undefined || val !== null; + } - const focusLastCell = (direction) => { - if (!this.$focusedCell || this.$editingCell) { + function notSet(val) { + return !isSet(val); + } + + function isNumber(val) { + return !isNaN(val); + } + + function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; + } + + class DataManager { + constructor(options) { + this.options = options; + this.sortRows = nextTick(this.sortRows, this); + this.switchColumn = nextTick(this.switchColumn, this); + this.removeColumn = nextTick(this.removeColumn, this); + this.filterRows = nextTick(this.filterRows, this); + } + + init(data, columns) { + if (!data) { + data = this.options.data; + } + if (columns) { + this.options.columns = columns; + } + + this.data = data; + + this.rowCount = 0; + this.columns = []; + this.rows = []; + + this.prepareColumns(); + this.prepareRows(); + this.prepareTreeRows(); + this.prepareRowView(); + + 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.checkboxColumn && !this.hasColumnById('_checkbox')) { + const cell = { + id: '_checkbox', + content: this.getCheckboxHTML(), + editable: false, + resizable: false, + sortable: false, + focusable: false, + dropdown: false, + width: 32 + }; + this.columns.push(cell); + } + + if (this.options.serialNoColumn && !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.content = col.content || col.name || ''; + col.id = col.id || col.content; + return col; + }); + } + + prepareCell(content, i) { + const cell = { + content: '', + 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 = []; + let meta = { + rowIndex: index + }; + + if (Array.isArray(d)) { + // row is an array + if (this.options.checkboxColumn) { + row.push(this.getCheckboxHTML()); + } + if (this.options.serialNoColumn) { + 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]); + } + } + + meta.indent = d.indent || 0; + } + + return this.prepareRow(row, meta); + }); + } + + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; + } + }); + } + + prepareRowView() { + // This is order in which rows will be rendered in the table. + // When sorting happens, only this.rowViewOrder will change + // and not the original this.rows + this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); + } + + prepareRow(row, meta) { + const baseRowCell = { + rowIndex: meta.rowIndex, + indent: meta.indent + }; + + row = row + .map((cell, i) => this.prepareCell(cell, i)) + .map(cell => Object.assign({}, baseRowCell, cell)); + + // monkey patched in array object + row.meta = meta; + 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.push(...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.rowViewOrder); + this.currentSort.sortOrder = sortOrder; + return; + } + } + + this.rowViewOrder.sort((a, b) => { + const aIndex = a; + const bIndex = b; + const aContent = this.getCell(colIndex, a).content; + const bContent = this.getCell(colIndex, b).content; + + if (sortOrder === 'none') { + return aIndex - bIndex; + } else if (sortOrder === 'asc') { + if (aContent < bContent) return -1; + if (aContent > bContent) return 1; + if (aContent === bContent) return 0; + } else if (sortOrder === 'desc') { + if (aContent < bContent) return 1; + if (aContent > bContent) return -1; + if (aContent === bContent) return 0; + } + return 0; + }); + + if (this.hasColumnById('_rowIndex')) { + // update row index + const srNoColIndex = this.getColumnIndexById('_rowIndex'); + this.rows.forEach((row, index) => { + const viewIndex = this.rowViewOrder.indexOf(index); + const cell = row[srNoColIndex]; + cell.content = (viewIndex + 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.forEach(row => { + const newCell1 = Object.assign({}, row[index1], { + colIndex: index2 + }); + const newCell2 = Object.assign({}, row[index2], { + colIndex: index1 + }); + + row[index2] = newCell1; + row[index1] = newCell2; + }); + } + + 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.forEach(row => { + // remove cell + row.splice(index, 1); + // update colIndex + row.forEach((cell, i) => { + cell.colIndex = i; + }); + }); + } + + 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 = String(cell.content || '').toLowerCase(); + const needle = (keyword || '').toLowerCase(); + + if (!needle || hay.includes(needle)) { + rowsToShow.push(cell.rowIndex); + } else { + rowsToHide.push(cell.rowIndex); + } + }); + + this._filteredRows = rowsToShow; + + return { + rowsToHide, + rowsToShow + }; + } + + getFilteredRowIndices() { + return this._filteredRows || this.rows.map(row => row.meta.rowIndex); + } + + getRowCount() { + return this.rowCount; + } + + _getNextRowCount() { + const val = this.rowCount; + + this.rowCount++; + return val; + } + + getRows(start, end) { + return this.rows.slice(start, end); + } + + getRowsForView(start, end) { + const rows = this.rowViewOrder.map(i => this.rows[i]); + return rows.slice(start, end); + } + + getColumns(skipStandardColumns) { + let columns = this.columns; + + if (skipStandardColumns) { + columns = columns.slice(this.getStandardColumnCount()); + } + + return columns; + } + + getStandardColumnCount() { + if (this.options.checkboxColumn && this.options.serialNoColumn) { + return 2; + } + + if (this.options.checkboxColumn || this.options.serialNoColumn) { + return 1; + } + + return 0; + } + + getColumnCount(skipStandardColumns) { + let val = this.columns.length; + + if (skipStandardColumns) { + val = val - this.getStandardColumnCount(); + } + + return val; + } + + getColumn(colIndex) { + colIndex = +colIndex; + + if (colIndex < 0) { + // negative indexes + colIndex = this.columns.length + colIndex; + } + + return this.columns.find(col => col.colIndex === colIndex); + } + + getColumnById(id) { + return this.columns.find(col => col.id === id); + } + + getRow(rowIndex) { + rowIndex = +rowIndex; + return this.rows[rowIndex]; + } + + getCell(colIndex, rowIndex) { + rowIndex = +rowIndex; + colIndex = +colIndex; + return this.getRow(rowIndex)[colIndex]; + } + + getChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; + + if (row.meta.indent > parentIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + get() { + return { + columns: this.columns, + rows: this.rows + }; + } + + /** + * Returns the original data which was passed + * based on rowIndex + * @param {Number} rowIndex + * @returns Array|Object + * @memberof DataManager + */ + getData(rowIndex) { + return this.data[rowIndex]; + } + + 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 CellManager { + constructor(instance) { + this.instance = instance; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); + + this.bindEvents(); + } + + bindEvents() { + this.bindFocusCell(); + this.bindEditCell(); + this.bindKeyboardSelection(); + this.bindCopyCellContents(); + this.bindMouseEvents(); + this.bindTreeEvents(); + } + + bindFocusCell() { + this.bindKeyboardNav(); + } + + bindEditCell() { + this.$editingCell = null; + + $.on(this.bodyScrollable, 'dblclick', '.dt-cell', (e, cell) => { + this.activateEditing(cell); + }); + + this.keyboard.on('enter', () => { + 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.inlineFilters) { + this.keyboard.on('ctrl+f', (e) => { + const $cell = $.closest('.dt-cell', e.target); + const { 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', () => { + const noOfCellsCopied = this.copyCellContents(this.$focusedCell, this.$selectionCursor); + const message = `${noOfCellsCopied} cell${noOfCellsCopied > 1 ? 's' : ''} copied`; + if (noOfCellsCopied) { + this.instance.showToastMessage(message, 2); + } + }); + + if (this.options.pasteFromClipboard) { + this.keyboard.on('ctrl+v', (e) => { + // hack + // https://stackoverflow.com/a/2177059/5353542 + this.instance.pasteTarget.focus(); + + setTimeout(() => { + const data = this.instance.pasteTarget.value; + this.instance.pasteTarget.value = ''; + this.pasteContentInCell(data); + }, 10); + + return false; + }); + } + } + + bindMouseEvents() { + let mouseDown = null; + + $.on(this.bodyScrollable, 'mousedown', '.dt-cell', (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', '.dt-cell', throttle$1(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.dt-tree-node__toggle', (e, $toggle) => { + const $cell = $.closest('.dt-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('dt-cell--tree-close')) { + this.rowmanager.openSingleNode(rowIndex); + } else { + this.rowmanager.closeSingleNode(rowIndex); + } + }); + } + + 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('dt-cell--focus'); + } + + this.$focusedCell = $cell; + $cell.classList.add('dt-cell--focus'); + + // so that keyboard nav works + $cell.focus(); + + this.highlightRowColumnHeader($cell); + } + + highlightRowColumnHeader($cell) { + const { + colIndex, + rowIndex + } = $.data($cell); + + const srNoColIndex = this.datamanager.getColumnIndexById('_rowIndex'); + const colHeaderSelector = `.dt-cell--header-${colIndex}`; + const rowHeaderSelector = `.dt-cell--${srNoColIndex}-${rowIndex}`; + + if (this.lastHeaders) { + this.lastHeaders.forEach(header => header.classList.remove('dt-cell--highlight')); + } + + const colHeader = $(colHeaderSelector, this.wrapper); + const rowHeader = $(rowHeaderSelector, this.wrapper); + + this.lastHeaders = [colHeader, rowHeader]; + this.lastHeaders.forEach(header => header.classList.add('dt-cell--highlight')); + } + + 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(); + this._selectedCells = cells.map(index => this.getCell$(...index)); + requestAnimationFrame(() => { + this._selectedCells.map($cell => $cell.classList.add('dt-cell--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 $cell = this.$focusedCell; + const cells = []; + let colIndex = colIndex1; + let rowIndex = rowIndex1; + const rowIndices = []; + + while (rowIndex <= rowIndex2) { + rowIndices.push(rowIndex); + rowIndex += 1; + } + + rowIndices.map((rowIndex) => { + while (colIndex <= colIndex2) { + cells.push([colIndex, rowIndex]); + colIndex++; + } + colIndex = colIndex1; + }); + + return cells; + } + + clearSelection() { + (this._selectedCells || []) + .forEach($cell => $cell.classList.remove('dt-cell--highlight')); + + this._selectedCells = []; + this.$selectionCursor = null; + } + + getSelectionCursor() { + return this.$selectionCursor || this.$focusedCell; + } + + activateEditing($cell) { + this.focusCell($cell); 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.inlineFilters) { - this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.dt-cell', e.target); - const { 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', () => { - const noOfCellsCopied = this.copyCellContents(this.$focusedCell, this.$selectionCursor); - const message = `${noOfCellsCopied} cell${noOfCellsCopied > 1 ? 's' : ''} copied`; - if (noOfCellsCopied) { - this.instance.showToastMessage(message, 2); - } - }); - } - - bindMouseEvents() { - let mouseDown = null; - - $.on(this.bodyScrollable, 'mousedown', '.dt-cell', (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', '.dt-cell', throttle$1(selectArea, 50)); - } - - bindTreeEvents() { - $.on(this.bodyScrollable, 'click', '.dt-tree-node__toggle', (e, $toggle) => { - const $cell = $.closest('.dt-cell', $toggle); - const { rowIndex } = $.data($cell); - - if ($cell.classList.contains('dt-cell--tree-close')) { - this.rowmanager.openSingleNode(rowIndex); - } else { - this.rowmanager.closeSingleNode(rowIndex); - } - }); - } - - 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('dt-cell--focus'); - } - - this.$focusedCell = $cell; - $cell.classList.add('dt-cell--focus'); - - // so that keyboard nav works - $cell.focus(); - - this.highlightRowColumnHeader($cell); - } - - highlightRowColumnHeader($cell) { - const { - colIndex, - rowIndex - } = $.data($cell); - - const srNoColIndex = this.datamanager.getColumnIndexById('_rowIndex'); - const colHeaderSelector = `.dt-cell--header-${colIndex}`; - const rowHeaderSelector = `.dt-cell--${srNoColIndex}-${rowIndex}`; - - if (this.lastHeaders) { - this.lastHeaders.forEach(header => header.classList.remove('dt-cell--highlight')); - } - - const colHeader = $(colHeaderSelector, this.wrapper); - const rowHeader = $(rowHeaderSelector, this.wrapper); - - this.lastHeaders = [colHeader, rowHeader]; - this.lastHeaders.forEach(header => header.classList.add('dt-cell--highlight')); - } - - 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(); - this._selectedCells = cells.map(index => this.getCell$(...index)); - requestAnimationFrame(() => { - this._selectedCells.map($cell => $cell.classList.add('dt-cell--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; - } - - const cells = []; - let colIndex = colIndex1; - let rowIndex = rowIndex1; - const rowIndices = []; - - while (rowIndex <= rowIndex2) { - rowIndices.push(rowIndex); - rowIndex += 1; - } - - rowIndices.map((rowIndex) => { - while (colIndex <= colIndex2) { - cells.push([colIndex, rowIndex]); - colIndex++; - } - colIndex = colIndex1; - }); - - return cells; - } - - clearSelection() { - (this._selectedCells || []) - .forEach($cell => $cell.classList.remove('dt-cell--highlight')); - - this._selectedCells = []; - 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 + const col = this.columnmanager.getColumn(colIndex); + if (col && (col.editable === false || col.focusable === false)) { return; } - } - this.$editingCell = $cell; - $cell.classList.add('dt-cell--editing'); - - const $editCell = $('.dt-cell__edit', $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('dt-cell--editing'); - this.$editingCell = null; - } - - getEditor(colIndex, rowIndex, value, parent) { - const column = this.datamanager.getColumn(colIndex); - const row = this.datamanager.getRow(rowIndex); - const data = this.datamanager.getData(rowIndex); - let editor = this.options.getEditor ? - this.options.getEditor(colIndex, rowIndex, value, parent, column, row, data) : - this.getDefaultEditor(parent); - - if (editor === false) { - // explicitly returned false - return false; - } - if (editor === undefined) { - // didn't return editor, fallback to default - editor = this.getDefaultEditor(parent); - } - - return editor; - } - - getDefaultEditor(parent) { - const $input = $.create('input', { - class: 'dt-input', - type: 'text', - inside: parent - }); - - return { - initValue(value) { - $input.focus(); - $input.value = value; - }, - getValue() { - return $input.value; - }, - setValue(value) { - $input.value = value; + const cell = this.getCell(colIndex, rowIndex); + if (cell && cell.editable === false) { + return; } - }; - } - submitEditing() { - if (!this.$editingCell) return; - const $cell = this.$editingCell; - const { - rowIndex, - colIndex - } = $.data($cell); - const col = this.datamanager.getColumn(colIndex); + if (this.$editingCell) { + const { + _rowIndex, + _colIndex + } = $.data(this.$editingCell); - 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); - }); + if (rowIndex === _rowIndex && colIndex === _colIndex) { + // editing the same cell + return; } } + + this.$editingCell = $cell; + $cell.classList.add('dt-cell--editing'); + + const $editCell = $('.dt-cell__edit', $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); + } } - this.currentCellEditor = null; - } + 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('dt-cell--editing'); + this.$editingCell = null; + } + + getEditor(colIndex, rowIndex, value, parent) { + const column = this.datamanager.getColumn(colIndex); + const row = this.datamanager.getRow(rowIndex); + const data = this.datamanager.getData(rowIndex); + let editor = this.options.getEditor ? + this.options.getEditor(colIndex, rowIndex, value, parent, column, row, data) : + this.getDefaultEditor(parent); + + if (editor === false) { + // explicitly returned false + return false; + } + if (editor === undefined) { + // didn't return editor, fallback to default + editor = this.getDefaultEditor(parent); + } + + return editor; + } + + getDefaultEditor(parent) { + const $input = $.create('input', { + class: 'dt-input', + 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 1; + } + const cells = this.getCellsInRange($cell1, $cell2); + + if (!cells) return 0; + + const rows = 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; + }, []); + + const values = rows + // join values by tab + .map(row => row.join('\t')) + // join rows by newline + .join('\n'); + + copyTextToClipboard(values); + + // return no of cells copied + return rows.reduce((total, row) => total + row.length, 0); + } + + pasteContentInCell(data) { + if (!this.$focusedCell) return; + + const matrix = data + .split('\n') + .map(row => row.split('\t')) + .filter(row => row.length && row.every(it => it)); + + let { colIndex, rowIndex } = $.data(this.$focusedCell); + + let focusedCell = { + colIndex: +colIndex, + rowIndex: +rowIndex + }; + + matrix.forEach((row, i) => { + let rowIndex = i + focusedCell.rowIndex; + row.forEach((cell, j) => { + let colIndex = j + focusedCell.colIndex; + this.updateCell(colIndex, rowIndex, cell); + }); + }); + } + + 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.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + $cell.innerHTML = this.getCellContent(cell); + } + + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('dt-cell--tree-close'); + } + } + + isStandardCell(colIndex) { + // Standard cells are in Sr. No and Checkbox column + return colIndex < this.columnmanager.getFirstColumnIndex(); + } + + getCell$(colIndex, rowIndex) { + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); + } + + getAboveCell$($cell) { + const { + colIndex + } = $.data($cell); + + let $aboveRow = $cell.parentElement.previousElementSibling; + while ($aboveRow && $aboveRow.classList.contains('dt-row--hide')) { + $aboveRow = $aboveRow.previousElementSibling; + } + + if (!$aboveRow) return $cell; + return $(`.dt-cell--col-${colIndex}`, $aboveRow); + } + + getBelowCell$($cell) { + const { + colIndex + } = $.data($cell); + + let $belowRow = $cell.parentElement.nextElementSibling; + while ($belowRow && $belowRow.classList.contains('dt-row--hide')) { + $belowRow = $belowRow.nextElementSibling; + } + + if (!$belowRow) return $cell; + return $(`.dt-cell--col-${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); + } + + getRowHeight() { + return $.style($('.dt-row', this.bodyScrollable), 'height'); + } + + scrollToCell($cell) { + if ($.inViewport($cell, this.bodyScrollable)) return false; - copyCellContents($cell1, $cell2) { - if (!$cell2 && $cell1) { - // copy only focusedCell const { - colIndex, rowIndex - } = $.data($cell1); - const cell = this.getCell(colIndex, rowIndex); - copyTextToClipboard(cell.content); - return 1; - } - const cells = this.getCellsInRange($cell1, $cell2); - - if (!cells) return 0; - - const rows = 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; - }, []); - - const values = rows - // join values by tab - .map(row => row.join('\t')) - // join rows by newline - .join('\n'); - - copyTextToClipboard(values); - - // return no of cells copied - return rows.reduce((total, row) => total + row.length, 0); - } - - 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.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); - $cell.innerHTML = this.getCellContent(cell); - } - - toggleTreeButton(rowIndex, flag) { - const colIndex = this.columnmanager.getFirstColumnIndex(); - const $cell = this.getCell$(colIndex, rowIndex); - if ($cell) { - $cell.classList[flag ? 'remove' : 'add']('dt-cell--tree-close'); - } - } - - isStandardCell(colIndex) { - // Standard cells are in Sr. No and Checkbox column - return colIndex < this.columnmanager.getFirstColumnIndex(); - } - - getCell$(colIndex, rowIndex) { - return $(this.selector(colIndex, rowIndex), this.bodyScrollable); - } - - getAboveCell$($cell) { - const { - colIndex - } = $.data($cell); - - let $aboveRow = $cell.parentElement.previousElementSibling; - while ($aboveRow && $aboveRow.classList.contains('dt-row--hide')) { - $aboveRow = $aboveRow.previousElementSibling; + } = $.data($cell); + this.rowmanager.scrollToRow(rowIndex); + return false; } - if (!$aboveRow) return $cell; - return $(`.dt-cell--col-${colIndex}`, $aboveRow); - } - - getBelowCell$($cell) { - const { - colIndex - } = $.data($cell); - - let $belowRow = $cell.parentElement.nextElementSibling; - while ($belowRow && $belowRow.classList.contains('dt-row--hide')) { - $belowRow = $belowRow.nextElementSibling; + getRowCountPerPage() { + return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight()); } - if (!$belowRow) return $cell; - return $(`.dt-cell--col-${colIndex}`, $belowRow); - } + getCellHTML(cell) { + const { + rowIndex, + colIndex, + isHeader, + isFilter + } = cell; + const dataAttr = makeDataAttributeString({ + rowIndex, + colIndex, + isHeader, + isFilter + }); - getLeftCell$($cell) { - return $cell.previousElementSibling; - } + const isBodyCell = !(isHeader || isFilter); - getRightCell$($cell) { - return $cell.nextElementSibling; - } + const className = [ + 'dt-cell', + 'dt-cell--col-' + colIndex, + isBodyCell ? `dt-cell--${colIndex}-${rowIndex}` : '', + isBodyCell ? 'dt-cell--row-' + rowIndex : '', + isHeader ? 'dt-cell--header' : '', + isHeader ? `dt-cell--header-${colIndex}` : '', + isFilter ? 'dt-cell--filter' : '' + ].join(' '); - 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); - } - - getRowHeight() { - return $.style($('.dt-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 - }); - - const isBodyCell = !(isHeader || isFilter); - - const className = [ - 'dt-cell', - 'dt-cell--col-' + colIndex, - isBodyCell ? `dt-cell--${colIndex}-${rowIndex}` : '', - isBodyCell ? 'dt-cell--row-' + rowIndex : '', - isHeader ? 'dt-cell--header' : '', - isHeader ? `dt-cell--header-${colIndex}` : '', - isFilter ? 'dt-cell--filter' : '' - ].join(' '); - - return ` + return ` ${this.getCellContent(cell)} `; - } - - getCellContent(cell) { - const { - isHeader, - isFilter, - colIndex - } = cell; - - const editable = !isHeader && cell.editable !== false; - const editCellHTML = editable ? this.getEditCellHTML(colIndex) : ''; - - 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 ? this.columnmanager.getDropdownHTML() : ''; - - const customFormatter = cell.format || (cell.column && cell.column.format) || null; - - let contentHTML; - if (isHeader || isFilter || !customFormatter) { - contentHTML = cell.content; - } else { - const row = this.datamanager.getRow(cell.rowIndex); - const data = this.datamanager.getData(cell.rowIndex); - contentHTML = customFormatter(cell.content, row, cell.column, data); } - if (this.options.treeView && !(isHeader || isFilter) && cell.indent !== undefined) { - const nextRow = this.datamanager.getRow(cell.rowIndex + 1); - const addToggle = nextRow && nextRow.meta.indent > cell.indent; + getCellContent(cell) { + const { + isHeader, + isFilter, + colIndex + } = cell; - // Add toggle and indent in the first column - const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; - if (firstColumnIndex === cell.colIndex) { - const padding = ((cell.indent || 0) + 1) * 1.5; - const toggleHTML = addToggle ? - `` : ''; - contentHTML = ` - ${toggleHTML}${contentHTML}`; + const editable = !isHeader && cell.editable !== false; + const editCellHTML = editable ? this.getEditCellHTML(colIndex) : ''; + + 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 ? this.columnmanager.getDropdownHTML() : ''; + + const customFormatter = cell.format || (cell.column && cell.column.format) || null; + + let contentHTML; + if (isHeader || isFilter || !customFormatter) { + contentHTML = cell.content; + } else { + const row = this.datamanager.getRow(cell.rowIndex); + const data = this.datamanager.getData(cell.rowIndex); + contentHTML = customFormatter(cell.content, row, cell.column, data); } - } - const className = [ - 'dt-cell__content', - isHeader ? `dt-cell__content--header-${colIndex}` : `dt-cell__content--col-${colIndex}` - ].join(' '); + if (this.options.treeView && !(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; - return ` + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? + `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + + const className = [ + 'dt-cell__content', + isHeader ? `dt-cell__content--header-${colIndex}` : `dt-cell__content--col-${colIndex}` + ].join(' '); + + return `
${contentHTML} ${sortIndicator} @@ -2198,396 +2260,396 @@ class CellManager {
${editCellHTML} `; + } + + getEditCellHTML(colIndex) { + return `
`; + } + + selector(colIndex, rowIndex) { + return `.dt-cell--${colIndex}-${rowIndex}`; + } } - getEditCellHTML(colIndex) { - return `
`; - } + class ColumnManager { + constructor(instance) { + this.instance = instance; - selector(colIndex, rowIndex) { - return `.dt-cell--${colIndex}-${rowIndex}`; - } -} + linkProperties(this, this.instance, [ + 'options', + 'fireEvent', + 'header', + 'datamanager', + 'style', + 'wrapper', + 'rowmanager', + 'bodyScrollable' + ]); -class ColumnManager { - constructor(instance) { - this.instance = instance; + this.bindEvents(); + } - linkProperties(this, this.instance, [ - 'options', - 'fireEvent', - 'header', - 'datamanager', - 'style', - 'wrapper', - 'rowmanager', - 'bodyScrollable' - ]); + renderHeader() { + this.header.innerHTML = ''; + this.refreshHeader(); + } - this.bindEvents(); - } + refreshHeader() { + const columns = this.datamanager.getColumns(); + const $cols = $.each('.dt-cell--header', this.header); - renderHeader() { - this.header.innerHTML = ''; - this.refreshHeader(); - } + const refreshHTML = + // first init + !$('.dt-cell', this.header) || + // deleted column + columns.length < $cols.length; - refreshHeader() { - const columns = this.datamanager.getColumns(); - const $cols = $.each('.dt-cell--header', this.header); + if (refreshHTML) { + // refresh html + $('thead', this.header).innerHTML = this.getHeaderHTML(columns); - const refreshHTML = - // first init - !$('.dt-cell', this.header) || - // deleted column - columns.length < $cols.length; + this.$filterRow = $('.dt-row[data-is-filter]', this.header); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } + } else { + // update data-attributes + $cols.map(($col, i) => { + const column = columns[i]; + // column sorted or order changed + // update colIndex of each header cell + $.data($col, { + colIndex: column.colIndex + }); - if (refreshHTML) { - // refresh html - $('thead', this.header).innerHTML = this.getHeaderHTML(columns); - - this.$filterRow = $('.dt-row[data-is-filter]', this.header); - if (this.$filterRow) { - $.style(this.$filterRow, { display: 'none' }); - } - } else { - // update data-attributes - $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 = []; + } - // refresh sort indicator - const sortIndicator = $('.sort-indicator', $col); - if (sortIndicator) { - sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder]; + getHeaderHTML(columns) { + let html = this.rowmanager.getRowHTML(columns, { + isHeader: 1 + }); + if (this.options.inlineFilters) { + html += this.rowmanager.getRowHTML(columns, { + isFilter: 1 + }); + } + return html; + } + + bindEvents() { + this.bindDropdown(); + this.bindResizeColumn(); + this.bindMoveColumn(); + this.bindFilter(); + } + + bindDropdown() { + let $activeDropdown; + let activeClass = 'dt-dropdown--active'; + let toggleClass = '.dt-dropdown__toggle'; + + $.on(this.header, 'click', toggleClass, (e, $button) => { + const $dropdown = $.closest('.dt-dropdown', $button); + + if (!$dropdown.classList.contains(activeClass)) { + deactivateDropdown(); + $dropdown.classList.add(activeClass); + $activeDropdown = $dropdown; + } else { + deactivateDropdown(); } }); - } - // reset columnMap - this.$columnMap = []; - } - getHeaderHTML(columns) { - let html = this.rowmanager.getRowHTML(columns, { - isHeader: 1 - }); - if (this.options.inlineFilters) { - html += this.rowmanager.getRowHTML(columns, { - isFilter: 1 - }); - } - return html; - } - - bindEvents() { - this.bindDropdown(); - this.bindResizeColumn(); - this.bindMoveColumn(); - this.bindFilter(); - } - - bindDropdown() { - let $activeDropdown; - let activeClass = 'dt-dropdown--active'; - let toggleClass = '.dt-dropdown__toggle'; - - $.on(this.header, 'click', toggleClass, (e, $button) => { - const $dropdown = $.closest('.dt-dropdown', $button); - - if (!$dropdown.classList.contains(activeClass)) { - deactivateDropdown(); - $dropdown.classList.add(activeClass); - $activeDropdown = $dropdown; - } else { + $.on(document.body, 'click', (e) => { + if (e.target.matches(toggleClass)) return; deactivateDropdown(); + }); + + const dropdownItems = this.options.headerDropdown; + + $.on(this.header, 'click', '.dt-dropdown__list-item', (e, $item) => { + const $col = $.closest('.dt-cell', $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(activeClass); + $activeDropdown = null; } - }); - - $.on(document.body, 'click', (e) => { - if (e.target.matches(toggleClass)) return; - deactivateDropdown(); - }); - - const dropdownItems = this.options.headerDropdown; - - $.on(this.header, 'click', '.dt-dropdown__list-item', (e, $item) => { - const $col = $.closest('.dt-cell', $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(activeClass); - $activeDropdown = null; - } - } - - bindResizeColumn() { - let isDragging = false; - let $resizingCell, startWidth, startX; - - $.on(this.header, 'mousedown', '.dt-cell .dt-cell__resize-handle', (e, $handle) => { - document.body.classList.add('dt-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($('.dt-cell__content', $resizingCell), 'width'); - startX = e.pageX; - }); - - $.on(document.body, 'mouseup', (e) => { - document.body.classList.remove('dt-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 = $('.dt-cell', this.header); - if (!ready) return; - - const $parent = $('.dt-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: '.dt-cell__resize-handle, .dt-dropdown', - chosenClass: 'dt-cell--dragging', - animation: 150 - }); - }; - - $.on(document.body, 'mousemove', initialize); - } - - 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(flag) { - let showFilter; - if (flag === undefined) { - showFilter = !this.isFilterShown; - } else { - showFilter = flag; } - if (showFilter) { - $.style(this.$filterRow, { display: '' }); - } else { - $.style(this.$filterRow, { display: 'none' }); - } + bindResizeColumn() { + let isDragging = false; + let $resizingCell, startWidth, startX; - this.isFilterShown = showFilter; - this.style.setBodyStyle(); - } + $.on(this.header, 'mousedown', '.dt-cell .dt-cell__resize-handle', (e, $handle) => { + document.body.classList.add('dt-resize'); + const $cell = $handle.parentNode.parentNode; + $resizingCell = $cell; + const { + colIndex + } = $.data($resizingCell); + const col = this.getColumn(colIndex); - focusFilter(colIndex) { - if (!this.isFilterShown) return; + if (col && col.resizable === false) { + return; + } - const $filterInput = $(`[data-col-index="${colIndex}"] .dt-filter`, this.$filterRow); - $filterInput.focus(); - } + isDragging = true; + startWidth = $.style($('.dt-cell__content', $resizingCell), 'width'); + startX = e.pageX; + }); - bindFilter() { - if (!this.options.inlineFilters) return; - const handler = e => { - const $filterCell = $.closest('.dt-cell', e.target); - const { - colIndex - } = $.data($filterCell); - const keyword = e.target.value; + $.on(document.body, 'mouseup', (e) => { + document.body.classList.remove('dt-resize'); + if (!$resizingCell) return; + isDragging = false; - this.datamanager.filterRows(keyword, colIndex) - .then(({ - rowsToHide, - rowsToShow - }) => { - this.rowmanager.hideRows(rowsToHide); - this.rowmanager.showRows(rowsToShow); + 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 }); - }; - $.on(this.header, 'keydown', '.dt-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, width) { - colIndex = +colIndex; - this._columnWidthMap = this._columnWidthMap || []; - - let columnWidth = width || this.getColumn(colIndex).width; - - let index = this._columnWidthMap[colIndex]; - const selector = [ - `.dt-cell__content--col-${colIndex}`, - `.dt-cell__edit--col-${colIndex}` - ].join(', '); - - const styles = { - width: columnWidth + 'px' - }; - - index = this.style.setStyle(selector, styles, index); - - if (index !== undefined) { - this._columnWidthMap[colIndex] = index; - } - } - - setColumnHeaderWidth(colIndex) { - colIndex = +colIndex; - this.$columnMap = this.$columnMap || []; - const selector = `.dt-cell__content--header-${colIndex}`; - const { - width - } = this.getColumn(colIndex); - - let $column = this.$columnMap[colIndex]; - if (!$column) { - $column = this.header.querySelector(selector); - this.$columnMap[colIndex] = $column; + this.setColumnHeaderWidth(colIndex); + }); } - $column.style.width = width + 'px'; - } + bindMoveColumn() { + let initialized; - getColumnMinWidth(colIndex) { - colIndex = +colIndex; - return this.getColumn(colIndex).minWidth || 24; - } + const initialize = () => { + if (initialized) { + $.off(document.body, 'mousemove', initialize); + return; + } + const ready = $('.dt-cell', this.header); + if (!ready) return; - getFirstColumnIndex() { - return this.datamanager.getColumnIndexById('_rowIndex') + 1; - } + const $parent = $('.dt-row', this.header); - getHeaderCell$(colIndex) { - return $(`.dt-cell--header-${colIndex}`, 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; - getLastColumnIndex() { - return this.datamanager.getColumnCount() - 1; - } + this.switchColumn(oldIndex, newIndex); + }, + preventOnFilter: false, + filter: '.dt-cell__resize-handle, .dt-dropdown', + chosenClass: 'dt-cell--dragging', + animation: 150 + }); + }; - getDropdownHTML() { - const { dropdownButton, headerDropdown: dropdownItems } = this.options; + $.on(document.body, 'mousemove', initialize); + } - return ` + 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(flag) { + let showFilter; + if (flag === undefined) { + showFilter = !this.isFilterShown; + } else { + showFilter = flag; + } + + if (showFilter) { + $.style(this.$filterRow, { display: '' }); + } else { + $.style(this.$filterRow, { display: 'none' }); + } + + this.isFilterShown = showFilter; + this.style.setBodyStyle(); + } + + focusFilter(colIndex) { + if (!this.isFilterShown) return; + + const $filterInput = $(`[data-col-index="${colIndex}"] .dt-filter`, this.$filterRow); + $filterInput.focus(); + } + + bindFilter() { + if (!this.options.inlineFilters) return; + const handler = e => { + const $filterCell = $.closest('.dt-cell', e.target); + const { + colIndex + } = $.data($filterCell); + const keyword = e.target.value; + + this.datamanager.filterRows(keyword, colIndex) + .then(({ + rowsToHide, + rowsToShow + }) => { + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); + }); + }; + $.on(this.header, 'keydown', '.dt-filter', debounce$1(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, width) { + colIndex = +colIndex; + this._columnWidthMap = this._columnWidthMap || []; + + let columnWidth = width || this.getColumn(colIndex).width; + + let index = this._columnWidthMap[colIndex]; + const selector = [ + `.dt-cell__content--col-${colIndex}`, + `.dt-cell__edit--col-${colIndex}` + ].join(', '); + + const styles = { + width: columnWidth + 'px' + }; + + index = this.style.setStyle(selector, styles, index); + + if (index !== undefined) { + this._columnWidthMap[colIndex] = index; + } + } + + setColumnHeaderWidth(colIndex) { + colIndex = +colIndex; + this.$columnMap = this.$columnMap || []; + const selector = `.dt-cell__content--header-${colIndex}`; + 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() { + return this.datamanager.getColumnIndexById('_rowIndex') + 1; + } + + getHeaderCell$(colIndex) { + return $(`.dt-cell--header-${colIndex}`, this.header); + } + + getLastColumnIndex() { + return this.datamanager.getColumnCount() - 1; + } + + getDropdownHTML() { + const { dropdownButton, headerDropdown: dropdownItems } = this.options; + + return `
${dropdownButton}
@@ -2597,861 +2659,866 @@ class ColumnManager {
`; - } -} - -class RowManager { - constructor(instance) { - this.instance = instance; - linkProperties(this, this.instance, [ - 'options', - 'fireEvent', - 'wrapper', - 'bodyScrollable', - 'bodyRenderer' - ]); - - this.bindEvents(); - this.refreshRows = nextTick(this.refreshRows, this); + } } - get datamanager() { - return this.instance.datamanager; - } + class RowManager { + constructor(instance) { + this.instance = instance; + linkProperties(this, this.instance, [ + 'options', + 'fireEvent', + 'wrapper', + 'bodyScrollable', + 'bodyRenderer' + ]); - get cellmanager() { - return this.instance.cellmanager; - } - - bindEvents() { - this.bindCheckbox(); - } - - bindCheckbox() { - if (!this.options.checkboxColumn) return; - - // map of checked rows - this.checkMap = []; - - $.on(this.wrapper, 'click', '.dt-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { - const $cell = $checkbox.closest('.dt-cell'); - 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 = nextTick(this.refreshRows, this); } - let out = []; - for (let rowIndex in this.checkMap) { - const checked = this.checkMap[rowIndex]; - if (checked === 1) { - out.push(rowIndex); - } + get datamanager() { + return this.instance.datamanager; } - return out; - } + get cellmanager() { + return this.instance.cellmanager; + } - highlightCheckedRows() { - this.getCheckedRows() - .map(rowIndex => this.checkRow(rowIndex, true)); - } + bindEvents() { + this.bindCheckbox(); + } - checkRow(rowIndex, toggle) { - const value = toggle ? 1 : 0; - const selector = rowIndex => - `.dt-cell[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.showCheckStatus(); - this.fireEvent('onCheckRow', this.datamanager.getRow(rowIndex)); - } + bindCheckbox() { + if (!this.options.checkboxColumn) return; - checkAll(toggle) { - const value = toggle ? 1 : 0; - - // update internal map - if (toggle) { - this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value); - } else { + // map of checked rows this.checkMap = []; - } - // set checkbox value - $.each('.dt-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) - .map(input => { - input.checked = toggle; + + $.on(this.wrapper, 'click', '.dt-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.dt-cell'); + const { + rowIndex, + isHeader + } = $.data($cell); + const checked = $checkbox.checked; + + if (isHeader) { + this.checkAll(checked); + } else { + this.checkRow(rowIndex, checked); + } }); - // highlight all - this.highlightAll(toggle); - this.showCheckStatus(); - } - - showCheckStatus() { - if (!this.options.checkedRowStatus) return; - const checkedRows = this.getCheckedRows(); - const count = checkedRows.length; - if (count > 0) { - this.bodyRenderer.showToastMessage(`${count} row${count > 1 ? 's' : ''} selected`); - } else { - this.bodyRenderer.clearToastMessage(); - } - } - - highlightRow(rowIndex, toggle = true) { - const $row = this.getRow$(rowIndex); - if (!$row) return; - - if (!toggle && this.bodyScrollable.classList.contains('dt-scrollable--highlight-all')) { - $row.classList.add('dt-row--unhighlight'); - return; } - if (toggle && $row.classList.contains('dt-row--unhighlight')) { - $row.classList.remove('dt-row--unhighlight'); + refreshRows() { + this.instance.renderBody(); + this.instance.setDimensions(); } - this._highlightedRows = this._highlightedRows || {}; + refreshRow(row, rowIndex) { + const _row = this.datamanager.updateRow(row, rowIndex); - if (toggle) { - $row.classList.add('dt-row--highlight'); - this._highlightedRows[rowIndex] = $row; - } else { - $row.classList.remove('dt-row--highlight'); - delete this._highlightedRows[rowIndex]; + _row.forEach(cell => { + this.cellmanager.refreshCell(cell); + }); } - } - highlightAll(toggle = true) { - if (toggle) { - this.bodyScrollable.classList.add('dt-scrollable--highlight-all'); - } else { - this.bodyScrollable.classList.remove('dt-scrollable--highlight-all'); - for (const rowIndex in this._highlightedRows) { - const $row = this._highlightedRows[rowIndex]; - $row.classList.remove('dt-row--highlight'); + getCheckedRows() { + if (!this.checkMap) { + return []; } - this._highlightedRows = {}; + + let out = []; + for (let rowIndex in this.checkMap) { + const checked = this.checkMap[rowIndex]; + if (checked === 1) { + out.push(rowIndex); + } + } + + return out; } - } - hideRows(rowIndices) { - rowIndices = ensureArray(rowIndices); - rowIndices.map(rowIndex => { - const $tr = this.getRow$(rowIndex); - $tr.classList.add('dt-row--hide'); - }); - } + highlightCheckedRows() { + this.getCheckedRows() + .map(rowIndex => this.checkRow(rowIndex, true)); + } - showRows(rowIndices) { - rowIndices = ensureArray(rowIndices); - rowIndices.map(rowIndex => { - const $tr = this.getRow$(rowIndex); - $tr.classList.remove('dt-row--hide'); - }); - } + checkRow(rowIndex, toggle) { + const value = toggle ? 1 : 0; + const selector = rowIndex => + `.dt-cell[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.showCheckStatus(); + this.fireEvent('onCheckRow', this.datamanager.getRow(rowIndex)); + } - openSingleNode(rowIndex) { - const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); - this.showRows(rowsToShow); - this.cellmanager.toggleTreeButton(rowIndex, true); - } + checkAll(toggle) { + const value = toggle ? 1 : 0; - closeSingleNode(rowIndex) { - const children = this.datamanager.getImmediateChildren(rowIndex); - children.forEach(childIndex => { - const row = this.datamanager.getRow(childIndex); - if (row.meta.isLeaf) { - // close - this.hideRows(childIndex); - this.cellmanager.toggleTreeButton(childIndex, false); + // update internal map + if (toggle) { + this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value); } else { - this.closeSingleNode(childIndex); - this.hideRows(childIndex); + this.checkMap = []; } - }); - this.cellmanager.toggleTreeButton(rowIndex, false); - } - - getRow$(rowIndex) { - return $(this.selector(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); + // set checkbox value + $.each('.dt-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + .map(input => { + input.checked = toggle; + }); + // highlight all + this.highlightAll(toggle); + this.showCheckStatus(); } - 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 - }))); + showCheckStatus() { + if (!this.options.checkedRowStatus) return; + const checkedRows = this.getCheckedRows(); + const count = checkedRows.length; + if (count > 0) { + this.bodyRenderer.showToastMessage(`${count} row${count > 1 ? 's' : ''} selected`); + } else { + this.bodyRenderer.clearToastMessage(); + } } - return ` + highlightRow(rowIndex, toggle = true) { + const $row = this.getRow$(rowIndex); + if (!$row) return; + + if (!toggle && this.bodyScrollable.classList.contains('dt-scrollable--highlight-all')) { + $row.classList.add('dt-row--unhighlight'); + return; + } + + if (toggle && $row.classList.contains('dt-row--unhighlight')) { + $row.classList.remove('dt-row--unhighlight'); + } + + this._highlightedRows = this._highlightedRows || {}; + + if (toggle) { + $row.classList.add('dt-row--highlight'); + this._highlightedRows[rowIndex] = $row; + } else { + $row.classList.remove('dt-row--highlight'); + delete this._highlightedRows[rowIndex]; + } + } + + highlightAll(toggle = true) { + if (toggle) { + this.bodyScrollable.classList.add('dt-scrollable--highlight-all'); + } else { + this.bodyScrollable.classList.remove('dt-scrollable--highlight-all'); + for (const rowIndex in this._highlightedRows) { + const $row = this._highlightedRows[rowIndex]; + $row.classList.remove('dt-row--highlight'); + } + this._highlightedRows = {}; + } + } + + hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('dt-row--hide'); + }); + } + + showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('dt-row--hide'); + }); + } + + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); + this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); + } + + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); + } + + getRow$(rowIndex) { + return $(this.selector(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 ``; - } + getFilterInput(props) { + const dataAttr = makeDataAttributeString(props); + return ``; + } - selector(rowIndex) { - return `.dt-row[data-row-index="${rowIndex}"]`; - } -} - -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 = nextTick(this.appendRemainingData, this); - } - - render() { - if (this.options.clusterize) { - this.renderBodyWithClusterize(); - } else { - this.renderBodyHTML(); + selector(rowIndex) { + return `.dt-row[data-row-index="${rowIndex}"]`; } } - renderBodyHTML() { - const rows = this.datamanager.getRowsForView(); - - this.bodyScrollable.innerHTML = this.getBodyHTML(rows); - this.instance.setDimensions(); - this.restoreState(); - } - - renderBodyWithClusterize() { - // first page - const rows = this.datamanager.getRowsForView(0, 20); - let initialData = this.getDataForClusterize(rows); - - if (initialData.length === 0) { - initialData = [this.getNoDataHTML()]; + 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 = nextTick(this.appendRemainingData, this); } - if (!this.clusterize) { - // empty body - this.bodyScrollable.innerHTML = this.getBodyHTML([]); + render() { + if (this.options.clusterize) { + this.renderBodyWithClusterize(); + } else { + this.renderBodyHTML(); + } + } - // 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 */ - show_no_data_row: false, - /* eslint-enable */ - }); + renderBodyHTML() { + const rows = this.datamanager.getRowsForView(); - // setDimensions requires atleast 1 row to exist in dom + this.bodyScrollable.innerHTML = this.getBodyHTML(rows); this.instance.setDimensions(); - } else { - this.clusterize.update(initialData); + this.restoreState(); } - this.appendRemainingData(); - } + renderBodyWithClusterize() { + // first page + const rows = this.datamanager.getRowsForView(0, 20); + let initialData = this.getDataForClusterize(rows); - restoreState() { - this.rowmanager.highlightCheckedRows(); - this.cellmanager.selectAreaOnClusterChanged(); - this.cellmanager.focusCellOnClusterChanged(); - } + if (initialData.length === 0) { + initialData = [this.getNoDataHTML()]; + } - appendRemainingData() { - const rows = this.datamanager.getRowsForView(20); - const data = this.getDataForClusterize(rows); - this.clusterize.append(data); - } + if (!this.clusterize) { + // empty body + this.bodyScrollable.innerHTML = this.getBodyHTML([]); - showToastMessage(message, hideAfter) { - this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message); + // 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 */ + show_no_data_row: false, + /* eslint-enable */ + }); - if (hideAfter) { - setTimeout(() => { - this.clearToastMessage(); - }, hideAfter * 1000); + // setDimensions requires atleast 1 row to exist in dom + this.instance.setDimensions(); + } else { + this.clusterize.update(initialData); + } + + this.appendRemainingData(); } - } - clearToastMessage() { - this.instance.toastMessage.innerHTML = ''; - } + restoreState() { + this.rowmanager.highlightCheckedRows(); + this.cellmanager.selectAreaOnClusterChanged(); + this.cellmanager.focusCellOnClusterChanged(); + } - getDataForClusterize(rows) { - return rows.map(row => this.rowmanager.getRowHTML(row, row.meta)); - } + appendRemainingData() { + const rows = this.datamanager.getRowsForView(20); + const data = this.getDataForClusterize(rows); + this.clusterize.append(data); + } - getBodyHTML(rows) { - return ` + showToastMessage(message, hideAfter) { + this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message); + + if (hideAfter) { + setTimeout(() => { + this.clearToastMessage(); + }, hideAfter * 1000); + } + } + + clearToastMessage() { + this.instance.toastMessage.innerHTML = ''; + } + + 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('')}
`; - } + } - getNoDataHTML() { - return `
${this.options.noDataMessage}
`; - } + getNoDataHTML() { + return `
${this.options.noDataMessage}
`; + } - getToastMessageHTML(message) { - return `${message}`; - } -} - -class Style { - constructor(instance) { - this.instance = instance; - - linkProperties(this, this.instance, [ - 'options', 'datamanager', 'columnmanager', - 'header', 'bodyScrollable', 'datatableWrapper', - 'getColumn' - ]); - - this.scopeClass = 'dt-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.bindResizeWindow(); - } - - get stylesheet() { - return this.styleEl.sheet; - } - - bindResizeWindow() { - if (this.options.layout === 'fluid') { - $.on(window, 'resize', throttle$1(() => { - this.distributeRemainingWidth(); - this.refreshColumnWidth(); - this.compensateScrollbarWidth(); - this.setBodyStyle(); - }, 300)); + getToastMessageHTML(message) { + return `${message}`; } } - destroy() { - this.styleEl.remove(); - } + class Style { + constructor(instance) { + this.instance = instance; - 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(','); + linkProperties(this, this.instance, [ + 'options', 'datamanager', 'columnmanager', + 'header', 'bodyScrollable', 'datatableWrapper', + 'getColumn' + ]); - let ruleString = `${prefixedSelector} { ${styles} }`; + this.scopeClass = 'dt-instance-' + instance.constructor.instances; + instance.datatableWrapper.classList.add(this.scopeClass); - if (!this.stylesheet) return; + const styleEl = document.createElement('style'); + instance.wrapper.insertBefore(styleEl, instance.datatableWrapper); + this.styleEl = styleEl; - let _index = this.stylesheet.cssRules.length; - if (index !== -1) { - this.stylesheet.deleteRule(index); - _index = index; + this.bindResizeWindow(); } - this.stylesheet.insertRule(ruleString, _index); - return _index; // eslint-disable-line - } - - setDimensions() { - this.setHeaderStyle(); - - this.setupMinWidth(); - this.setupNaturalColumnWidth(); - this.setupColumnWidth(); - this.distributeRemainingWidth(); - this.setColumnStyle(); - this.compensateScrollbarWidth(); - - 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 - }); + get stylesheet() { + return this.styleEl.sheet; } - $.style(this.header, { - margin: 0 - }); - } - - setupMinWidth() { - $.each('.dt-cell[data-is-header]', this.header).map(col => { - const { colIndex } = $.data(col); - const column = this.getColumn(colIndex); - - if (!column.minWidth) { - const width = $.style($('.dt-cell__content', col), 'width'); - // only set this once - column.minWidth = width; + bindResizeWindow() { + if (this.options.layout === 'fluid') { + $.on(window, 'resize', throttle$1(() => { + this.distributeRemainingWidth(); + this.refreshColumnWidth(); + this.compensateScrollbarWidth(); + this.setBodyStyle(); + }, 300)); } - }); - } + } - setupNaturalColumnWidth() { - if (!$('.dt-row')) return; + destroy() { + this.styleEl.remove(); + } - // set initial width as naturally calculated by table's first row - $.each('.dt-row[data-row-index="0"] .dt-cell', this.bodyScrollable).map($cell => { - const { - colIndex - } = $.data($cell); - const column = this.datamanager.getColumn(colIndex); - - let naturalWidth = $.style($('.dt-cell__content', $cell), 'width'); - - if (column.id === '_rowIndex') { - naturalWidth = this.getRowIndexColumnWidth(naturalWidth); - column.width = naturalWidth; - } - - column.naturalWidth = naturalWidth; - }); - } - - setupColumnWidth() { - if (this.options.layout === 'ratio') { - let totalWidth = $.style(this.datatableWrapper, 'width'); - - if (this.options.serialNoColumn) { - const rowIndexColumn = this.datamanager.getColumnById('_rowIndex'); - totalWidth = totalWidth - rowIndexColumn.width - 1; - } - - if (this.options.checkboxColumn) { - const rowIndexColumn = this.datamanager.getColumnById('_checkbox'); - totalWidth = totalWidth - rowIndexColumn.width - 1; - } - - const totalParts = this.datamanager.getColumns() - .map(column => { - if (column.id === '_rowIndex' || column.id === '_checkbox') { - return 0; + setStyle(selector, styleMap, index = -1) { + const styles = Object.keys(styleMap) + .map(prop => { + if (!prop.includes('-')) { + prop = camelCaseToDash(prop); } - if (!column.width) { - column.width = 1; - } - column.ratioWidth = parseInt(column.width, 10); - return column.ratioWidth; + return `${prop}:${styleMap[prop]};`; }) - .reduce((a, c) => a + c); + .join(''); + let prefixedSelector = selector + .split(',') + .map(r => `.${this.scopeClass} ${r}`) + .join(','); - const onePart = totalWidth / totalParts; + let ruleString = `${prefixedSelector} { ${styles} }`; - this.datamanager.getColumns() - .map(column => { - if (column.id === '_rowIndex' || column.id === '_checkbox') return; - column.width = Math.floor(onePart * column.ratioWidth) - 1; - }); - } else { - this.datamanager.getColumns() - .map(column => { - if (!column.width) { - column.width = column.naturalWidth; - } - if (column.width < column.minWidth) { - column.width = column.minWidth; - } - }); + if (!this.stylesheet) return; + + let _index = this.stylesheet.cssRules.length; + if (index !== -1) { + this.stylesheet.deleteRule(index); + _index = index; + } + + this.stylesheet.insertRule(ruleString, _index); + return _index; // eslint-disable-line } - } - compensateScrollbarWidth() { - requestAnimationFrame(() => { - const scrollbarWidth = $.scrollbarWidth(); - const lastCol = this.datamanager.getColumn(-1); - const width = lastCol.width - scrollbarWidth; - this.columnmanager.setColumnWidth(lastCol.colIndex, width); - }); - } + setDimensions() { + this.setHeaderStyle(); - distributeRemainingWidth() { - if (this.options.layout !== 'fluid') return; + this.setupMinWidth(); + this.setupNaturalColumnWidth(); + this.setupColumnWidth(); + this.distributeRemainingWidth(); + this.setColumnStyle(); + this.compensateScrollbarWidth(); - 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; + this.setDefaultCellHeight(); + this.setBodyStyle(); + } - resizableColumns.map(col => { - const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width'); - let finalWidth = Math.floor(width + deltaWidth) - 2; + 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.datamanager.updateColumn(col.colIndex, { - width: finalWidth + $.style(this.header, { + margin: 0 }); - }); - } - - setDefaultCellHeight() { - if (this.options.dynamicRowHeight) return; - if (this.__cellHeightSet) return; - const $firstCell = $('.dt-cell[data-is-header]', this.instance.header); - if (!$firstCell) return; - - const height = this.options.cellHeight || $.style($firstCell, 'height'); - if (height) { - this.setCellHeight(height); - this.__cellHeightSet = true; } - } - setCellHeight(height) { - this.setStyle('.dt-cell__content, .dt-cell__edit', { - height: height + 'px' - }); - } + setupMinWidth() { + $.each('.dt-cell[data-is-header]', this.header).map(col => { + const { colIndex } = $.data(col); + const column = this.getColumn(colIndex); - setColumnStyle() { - // align columns - this.datamanager.getColumns() - .map(column => { - // alignment - if (['left', 'center', 'right'].includes(column.align)) { - this.setStyle(`.dt-cell--col-${column.colIndex}`, { - 'text-align': column.align + if (!column.minWidth) { + const width = $.style($('.dt-cell__content', col), 'width'); + // only set this once + column.minWidth = width; + } + }); + } + + setupNaturalColumnWidth() { + if (!$('.dt-row')) return; + + // set initial width as naturally calculated by table's first row + $.each('.dt-row[data-row-index="0"] .dt-cell', this.bodyScrollable).map($cell => { + const { + colIndex + } = $.data($cell); + const column = this.datamanager.getColumn(colIndex); + + let naturalWidth = $.style($('.dt-cell__content', $cell), 'width'); + + if (column.id === '_rowIndex') { + naturalWidth = this.getRowIndexColumnWidth(naturalWidth); + column.width = naturalWidth; + } + + column.naturalWidth = naturalWidth; + }); + } + + setupColumnWidth() { + if (this.options.layout === 'ratio') { + let totalWidth = $.style(this.datatableWrapper, 'width'); + + if (this.options.serialNoColumn) { + const rowIndexColumn = this.datamanager.getColumnById('_rowIndex'); + totalWidth = totalWidth - rowIndexColumn.width - 1; + } + + if (this.options.checkboxColumn) { + const rowIndexColumn = this.datamanager.getColumnById('_checkbox'); + totalWidth = totalWidth - rowIndexColumn.width - 1; + } + + const totalParts = this.datamanager.getColumns() + .map(column => { + if (column.id === '_rowIndex' || column.id === '_checkbox') { + return 0; + } + if (!column.width) { + column.width = 1; + } + column.ratioWidth = parseInt(column.width, 10); + return column.ratioWidth; + }) + .reduce((a, c) => a + c); + + const onePart = totalWidth / totalParts; + + this.datamanager.getColumns() + .map(column => { + if (column.id === '_rowIndex' || column.id === '_checkbox') return; + column.width = Math.floor(onePart * column.ratioWidth) - 1; + }); + } else { + this.datamanager.getColumns() + .map(column => { + if (!column.width) { + column.width = column.naturalWidth; + } + if (column.width < column.minWidth) { + column.width = column.minWidth; + } + }); + } + } + + compensateScrollbarWidth() { + requestAnimationFrame(() => { + const scrollbarWidth = $.scrollbarWidth(); + const lastCol = this.datamanager.getColumn(-1); + const width = lastCol.width - scrollbarWidth; + this.columnmanager.setColumnWidth(lastCol.colIndex, width); + }); + } + + 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.options.dynamicRowHeight) return; + if (this.__cellHeightSet) return; + const $firstCell = $('.dt-cell[data-is-header]', this.instance.header); + if (!$firstCell) return; + + const height = this.options.cellHeight || $.style($firstCell, 'height'); + if (height) { + this.setCellHeight(height); + this.__cellHeightSet = true; + } + } + + setCellHeight(height) { + this.setStyle('.dt-cell__content, .dt-cell__edit', { + height: height + 'px' + }); + } + + setColumnStyle() { + // align columns + this.datamanager.getColumns() + .map(column => { + // alignment + if (['left', 'center', 'right'].includes(column.align)) { + this.setStyle(`.dt-cell--col-${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() { + requestAnimationFrame(() => { + const width = $.style(this.header, 'width'); + + $.style(this.bodyScrollable, { + width: width + 'px' + }); + + const $body = $('.dt-body', this.bodyScrollable); + + if ($body) { + $.style($body, { + height: '0px' }); } - // 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() { - requestAnimationFrame(() => { - const width = $.style(this.header, 'width'); - - $.style(this.bodyScrollable, { - width: width + 'px' - }); - - const $body = $('.dt-body', this.bodyScrollable); - - if ($body) { - $.style($body, { - height: '0px' + $.style(this.bodyScrollable, { + marginTop: $.style(this.header, 'height') + 'px' }); + + $.style($('table', this.bodyScrollable), { + margin: 0, + width: '100%' + }); + }); + } + + getColumnHeaderElement(colIndex) { + colIndex = +colIndex; + if (colIndex < 0) return null; + return $(`.dt-cell[data-col-index="${colIndex}"]`, this.header); + } + + getRowIndexColumnWidth(baseWidth) { + this._rowIndexColumnWidthMap = this._rowIndexColumnWidthMap || {}; + const rowCount = this.datamanager.getRowCount(); + const digits = (rowCount + '').length; + + if (!this._rowIndexColumnWidthMap[digits]) { + // add 8px for each unit + this._rowIndexColumnWidthMap[digits] = baseWidth + ((digits - 1) * 8); } - $.style(this.bodyScrollable, { - marginTop: $.style(this.header, 'height') + 'px' - }); - - $.style($('table', this.bodyScrollable), { - margin: 0, - width: '100%' - }); - }); + return this._rowIndexColumnWidthMap[digits]; + } } - getColumnHeaderElement(colIndex) { - colIndex = +colIndex; - if (colIndex < 0) return null; - return $(`.dt-cell[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', + 86: 'v' + }; - getRowIndexColumnWidth(baseWidth) { - this._rowIndexColumnWidthMap = this._rowIndexColumnWidthMap || {}; - const rowCount = this.datamanager.getRowCount(); - const digits = (rowCount + '').length; - - if (!this._rowIndexColumnWidthMap[digits]) { - // add 8px for each unit - this._rowIndexColumnWidthMap[digits] = baseWidth + ((digits - 1) * 8); + class Keyboard { + constructor(element) { + this.listeners = {}; + $.on(element, 'keydown', this.handler.bind(this)); } - return this._rowIndexColumnWidthMap[digits]; - } -} + handler(e) { + let key = KEYCODES[e.keyCode]; -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' -}; + if (e.shiftKey && key !== 'shift') { + key = 'shift+' + key; + } -class Keyboard { - 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 (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(); + 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); + }); + } } - 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'); + 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) {}, + onCheckRow(row) {} }, - { - 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'); + freezeMessage: '', + getEditor: null, + serialNoColumn: true, + checkboxColumn: false, + clusterize: true, + logs: false, + layout: 'fixed', // fixed, fluid, ratio + noDataMessage: 'No Data', + cellHeight: null, + inlineFilters: false, + treeView: false, + checkedRowStatus: true, + dynamicRowHeight: false, + pasteFromClipboard: false + }; + + class DataTable { + constructor(wrapper, options) { + DataTable.instances++; + + if (typeof wrapper === 'string') { + // css selector + wrapper = document.querySelector(wrapper); } - }, - { - label: 'Remove column', - action: function (column) { - this.removeColumn(column.colIndex); + this.wrapper = wrapper; + if (!(this.wrapper instanceof HTMLElement)) { + throw new Error('Invalid argument given for `wrapper`'); + } + + this.buildOptions(options); + 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(); } } - ], - events: { - onRemoveColumn(column) {}, - onSwitchColumn(column1, column2) {}, - onSortColumn(column) {}, - onCheckRow(row) {} - }, - sortIndicator: { - asc: '↑', - desc: '↓', - none: '' - }, - freezeMessage: '', - getEditor: null, - serialNoColumn: true, - checkboxColumn: false, - clusterize: true, - logs: false, - layout: 'fixed', // fixed, fluid, ratio - noDataMessage: 'No Data', - cellHeight: null, - inlineFilters: false, - treeView: false, - checkedRowStatus: true, - dynamicRowHeight: false -}; -class DataTable { - constructor(wrapper, options) { - DataTable.instances++; + buildOptions(options) { + this.options = this.options || {}; - 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, + this.options || {}, options + ); + + options.headerDropdown = options.headerDropdown || []; + this.options.headerDropdown = [ + ...DEFAULT_OPTIONS.headerDropdown, + ...options.headerDropdown + ]; + + // custom user events + this.events = Object.assign( + {}, DEFAULT_OPTIONS.events, + this.options.events || {}, + options.events || {} + ); + this.fireEvent = this.fireEvent.bind(this); } - this.buildOptions(options); - 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(); } - } - buildOptions(options) { - this.options = this.options || {}; - - this.options = Object.assign( - {}, DEFAULT_OPTIONS, - this.options || {}, options - ); - - this.options.headerDropdown - .push(...(options.headerDropdown || [])); - - // custom user events - this.events = Object.assign( - {}, DEFAULT_OPTIONS.events, - this.options.events || {}, - options.events || {} - ); - this.fireEvent = this.fireEvent.bind(this); - } - - prepare() { - this.prepareDom(); - this.unfreeze(); - } - - prepareDom() { - this.wrapper.innerHTML = ` + prepareDom() { + this.wrapper.innerHTML = `
@@ -3463,161 +3530,163 @@ class DataTable {
+ `; - this.datatableWrapper = $('.datatable', this.wrapper); - this.header = $('.dt-header', this.wrapper); - this.bodyScrollable = $('.dt-scrollable', this.wrapper); - this.freezeContainer = $('.dt-freeze', this.wrapper); - this.toastMessage = $('.dt-toast', this.wrapper); - } - - refresh(data, columns) { - this.datamanager.init(data, columns); - 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(); - } - - showToastMessage(message, hideAfter) { - this.bodyRenderer.showToastMessage(message, hideAfter); - } - - clearToastMessage() { - this.bodyRenderer.clearToastMessage(); - } - - 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 = $('.datatable', this.wrapper); + this.header = $('.dt-header', this.wrapper); + this.bodyScrollable = $('.dt-scrollable', this.wrapper); + this.freezeContainer = $('.dt-freeze', this.wrapper); + this.toastMessage = $('.dt-toast', this.wrapper); + this.pasteTarget = $('.dt-paste-target', this.wrapper); } - return this.viewportHeight; - } + refresh(data, columns) { + this.datamanager.init(data, columns); + this.render(); + this.setDimensions(); + } - sortColumn(colIndex, sortOrder) { - this.columnmanager.sortColumn(colIndex, sortOrder); - } + destroy() { + this.wrapper.innerHTML = ''; + this.style.destroy(); + } - removeColumn(colIndex) { - this.columnmanager.removeColumn(colIndex); - } + appendRows(rows) { + this.datamanager.appendRows(rows); + this.rowmanager.refreshRows(); + } - scrollToLastColumn() { - this.datatableWrapper.scrollLeft = 9999; - } + refreshRow(row, rowIndex) { + this.rowmanager.refreshRow(row, rowIndex); + } - freeze() { - $.style(this.freezeContainer, { - display: '' - }); - } + render() { + this.renderHeader(); + this.renderBody(); + } - unfreeze() { - $.style(this.freezeContainer, { - display: 'none' - }); - } + renderHeader() { + this.columnmanager.renderHeader(); + } - updateOptions(options) { - this.buildOptions(options); - } + renderBody() { + this.bodyRenderer.render(); + } - fireEvent(eventName, ...args) { - this.events[eventName].apply(this, args); - } + setDimensions() { + this.style.setDimensions(); + } - log() { - if (this.options.logs) { - console.log.apply(console, arguments); + showToastMessage(message, hideAfter) { + this.bodyRenderer.showToastMessage(message, hideAfter); + } + + clearToastMessage() { + this.bodyRenderer.clearToastMessage(); + } + + 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' + }); + } + + updateOptions(options) { + this.buildOptions(options); + } + + fireEvent(eventName, ...args) { + this.events[eventName].apply(this, args); + } + + log() { + if (this.options.logs) { + console.log.apply(console, arguments); + } } } -} -DataTable.instances = 0; + DataTable.instances = 0; -var name = "frappe-datatable"; -var version = "0.0.4"; -var description = "A modern datatable library for the web"; -var main = "dist/frappe-datatable.cjs.js"; -var scripts = {"start":"yarn run dev","build":"rollup -c","production":"rollup -c --production","build:docs":"rollup -c --docs","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"}; -var devDependencies = {"chai":"3.5.0","deepmerge":"^2.0.1","eslint-config-airbnb":"^16.1.0","eslint-config-airbnb-base":"^12.1.0","eslint-plugin-import":"^2.11.0","mocha":"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-eslint":"^4.0.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify-es":"^0.0.1"}; -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 -}; + var name = "frappe-datatable"; + var version = "0.0.5"; + var description = "A modern datatable library for the web"; + var main = "dist/frappe-datatable.cjs.js"; + var scripts = {"start":"yarn run dev","build":"rollup -c","production":"rollup -c --production","build:docs":"rollup -c --docs","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"}; + var devDependencies = {"chai":"3.5.0","deepmerge":"^2.0.1","eslint-config-airbnb":"^16.1.0","eslint-config-airbnb-base":"^12.1.0","eslint-plugin-import":"^2.11.0","mocha":"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-eslint":"^4.0.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify-es":"^0.0.1"}; + 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; + DataTable.__version__ = packageJson.version; -return DataTable; + return DataTable; }(Sortable,Clusterize)); diff --git a/dist/frappe-datatable.min.css b/dist/frappe-datatable.min.css index d87dd06..acf2da2 100644 --- a/dist/frappe-datatable.min.css +++ b/dist/frappe-datatable.min.css @@ -1 +1 @@ -.data-table{position:relative;overflow:auto}.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:0;-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:.5;font-size:2em}.data-table .freeze-container span{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.data-table .hide{display:none}.data-table .toast-message{position:absolute;bottom:16px;bottom:1rem;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.data-table .toast-message span{display:inline-block;background-color:rgba(0,0,0,.8);color:#dfe2e5;border-radius:3px;padding:8px 16px;padding:.5rem 1rem}.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}.body-scrollable .no-data td{text-align:center;padding:8px;padding:.5rem}.data-table-header{position:absolute;top:0;left:0;background-color:#fff;font-weight:700}.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:.25rem;height:100%;background-color:#5292f7;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,.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:400;position:absolute;min-width:128px;min-width:8rem;top:100%;right:0;z-index:1;background-color:#fff;border-radius:3px;-webkit-box-shadow:0 2px 3px hsla(0,0%,4%,.1),0 0 0 1px hsla(0,0%,4%,.1);box-shadow:0 2px 3px hsla(0,0%,4%,.1),0 0 0 1px hsla(0,0%,4%,.1);padding-bottom:8px;padding-bottom:.5rem;padding-top:8px;padding-top:.5rem}.data-table-header .data-table-dropdown-list>div{padding:8px 16px;padding:.5rem 1rem}.data-table-header .data-table-dropdown-list>div:hover{background-color:#f5f7fa}.data-table-header .data-table-cell.remove-column{background-color:#fd8b8b;-webkit-transition:background-color .3s ease-in-out;transition:background-color .3s ease-in-out}.data-table-header .data-table-cell.sortable-chosen{background-color:#f5f7fa}.data-table-cell{position:relative}.data-table-cell .content{padding:8px;padding:.5rem;border:2px solid transparent;height:100%}.data-table-cell .content.ellipsis{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.data-table-cell .edit-cell{display:none;padding:8px;padding:.5rem;background-color:#fff;z-index:1;height:100%}.data-table-cell.selected .content{border:2px solid #5292f7}.data-table-cell.editing .content{display:none}.data-table-cell.editing .edit-cell{border:2px solid #ffa00a;border-style:dashed;display:block}.data-table-cell.highlight{background-color:#f5f7fa}.data-table-cell:hover .column-resizer{display:inline-block}.data-table-cell:hover .data-table-dropdown-toggle{display:block}.data-table-cell .tree-node{display:inline-block;position:relative}.data-table-cell .toggle{display:inline-block;position:absolute;padding:0 4px;cursor:pointer}.data-table-cell .toggle:before{content:"▼"}.data-table-cell.tree-close .toggle:before{content:"►"}.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 +.datatable *,.datatable :after,.datatable :before{-webkit-box-sizing:border-box;box-sizing:border-box}.datatable{position:relative;overflow:auto}.dt-header{border-bottom:1px solid #d1d8dd;position:absolute;top:0;left:0;background-color:#fff}.dt-body,.dt-header{border-collapse:collapse}.dt-scrollable{max-height:40vw;overflow:auto;border-bottom:1px solid #d1d8dd}.dt-scrollable--highlight-all{background-color:#fffce7}.dt-scrollable__no-data{text-align:center;padding:16px;padding:1rem;border-left:1px solid #d1d8dd;border-right:1px solid #d1d8dd}.dt-row--highlight{background-color:#fffce7}.dt-row--unhighlight{background-color:#fff}.dt-row--hide{display:none}.dt-cell{border:1px solid #d1d8dd;position:relative;outline:none;padding:0}.dt-cell__content{border:2px solid transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.dt-cell__content,.dt-cell__edit{padding:8px;padding:.5rem;height:100%}.dt-cell__edit{display:none;background-color:#fff;border:2px solid #ffa00a;z-index:1}.dt-cell__resize-handle{opacity:0;position:absolute;right:-3px;top:0;width:5px;height:100%;cursor:col-resize;z-index:1}.dt-cell--editing .dt-cell__content{display:none}.dt-cell--editing .dt-cell__edit{display:block}.dt-cell--focus .dt-cell__content{border-color:#5292f7}.dt-cell--dragging,.dt-cell--highlight{background-color:#f5f7fa}.dt-cell--header .dt-cell__content{padding-right:16px;padding-right:1rem;font-weight:700}.dt-cell--header:hover .dt-dropdown__toggle{opacity:1}.dt-cell--tree-close .dt-tree-node__toggle:before{content:"►"}.dt-dropdown{position:absolute;right:10px;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:top;text-align:left;font-weight:400;cursor:pointer}.dt-dropdown__toggle{opacity:0}.dt-dropdown__list{display:none;position:absolute;min-width:128px;min-width:8rem;top:100%;right:0;z-index:1;background-color:#fff;border-radius:3px;padding:8px 0;padding:.5rem 0;-webkit-box-shadow:0 2px 3px hsla(0,0%,4%,.1),0 0 0 1px hsla(0,0%,4%,.1);box-shadow:0 2px 3px hsla(0,0%,4%,.1),0 0 0 1px hsla(0,0%,4%,.1)}.dt-dropdown__list-item{padding:8px 16px;padding:.5rem 1rem}.dt-dropdown__list-item:hover{background-color:#f5f7fa}.dt-dropdown--active .dt-dropdown__list{display:block}.dt-tree-node{display:inline-block;position:relative}.dt-tree-node__toggle{display:inline-block;position:absolute;font-size:10px;padding:0 4px;cursor:pointer}.dt-tree-node__toggle:before{content:"▼"}.dt-toast{position:absolute;bottom:16px;bottom:1rem;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.dt-toast__message{display:inline-block;background-color:rgba(0,0,0,.8);color:#dfe2e5;border-radius:3px;padding:8px 16px;padding:.5rem 1rem}.dt-input{outline:none;width:100%;border:none;overflow:visible;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;padding:0}.dt-freeze{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:.5;font-size:2em}.dt-freeze__message{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.dt-paste-target{position:fixed;left:-999em}body.dt-resize{cursor:col-resize} \ No newline at end of file diff --git a/dist/frappe-datatable.min.js b/dist/frappe-datatable.min.js index f821db2..dc3ea17 100644 --- a/dist/frappe-datatable.min.js +++ b/dist/frappe-datatable.min.js @@ -1 +1 @@ -var DataTable=function(t,e){"use strict";function n(t,e){return"string"==typeof t?(e||document).querySelector(t):t||null}function i(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function o(t){var e=T.call(t,L),n=t[L];try{t[L]=void 0;var i=!0}catch(t){}var o=H.call(t);return i&&(e?t[L]=n:delete t[L]),o}function s(t){return W.call(t)}function r(t){return null==t?void 0===t?z:O:A&&A in Object(t)?_(t):j(t)}function a(t){return null!=t&&"object"==typeof t}function l(t){return"symbol"==typeof t||F(t)&&B(t)==D}function c(t){if("number"==typeof t)return t;if(N(t))return V;if(I(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=I(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(q,"");var n=P.test(t);return n||X.test(t)?U(t.slice(2),n?2:8):K.test(t)?V:+t}function h(t,e,n){function i(e){var n=d,i=f;return d=f=void 0,b=e,g=t.apply(i,n)}function o(t){return b=t,p=setTimeout(a,e),C?i(t):g}function s(t){var n=t-b,i=e-(t-w);return y?Q(i,m-n):i}function r(t){var n=t-w,i=t-b;return void 0===w||n>=e||n<0||y&&i>=m}function a(){var t=E();if(r(t))return l(t);p=setTimeout(a,s(t))}function l(t){return p=void 0,x&&d?i(t):(d=f=void 0,g)}function c(){void 0!==p&&clearTimeout(p),b=0,d=w=f=p=void 0}function h(){return void 0===p?g:l(E())}function u(){var t=E(),n=r(t);if(d=arguments,f=this,w=t,n){if(void 0===p)return o(w);if(y)return p=setTimeout(a,e),i(w)}return void 0===p&&(p=setTimeout(a,e)),g}var d,f,m,g,p,w,b=0,C=!1,y=!1,x=!0;if("function"!=typeof t)throw new TypeError(G);return e=Z(e)||0,I(n)&&(C=!!n.leading,m=(y="maxWait"in n)?J(Z(n.maxWait)||0,e):m,x="trailing"in n?!!n.trailing:x),u.cancel=c,u.flush=h,u}function u(t,e,n){var i=!0,o=!0;if("function"!=typeof t)throw new TypeError(tt);return I(n)&&(i="leading"in n?!!n.leading:i,o="trailing"in n?!!n.trailing:o),Y(t,e,{leading:i,maxWait:e,trailing:o})}function d(t){return t.replace(/([A-Z])/g,t=>`-${t[0].toLowerCase()}`)}function f(t){return Object.keys(t).map(e=>{const n=d(e);const i=t[e];if(void 0===i)return"";return`data-${n}="${i}" `}).join("").trim()}function m(t){var e=document.createElement("textarea");e.style.position="fixed",e.style.top=0,e.style.left=0,e.style.width="2em",e.style.height="2em",e.style.padding=0,e.style.border="none",e.style.outline="none",e.style.boxShadow="none",e.style.background="transparent",e.value=t,document.body.appendChild(e),e.select();try{document.execCommand("copy")}catch(t){console.log("Oops, unable to copy")}document.body.removeChild(e)}function g(t){return!isNaN(t)}function p(t,e=null){return(...n)=>new Promise(i=>{const o=()=>{const o=t.apply(e,n);i(o)};window.setImmediate?setImmediate(o):window.requestAnimationFrame?requestAnimationFrame(o):setTimeout(o)})}function w(t,e,n){const i=n.reduce((t,n)=>{t[n]={get(){return e[n]}};return t},{});Object.defineProperties(t,i)}function b(t){return void 0!==t||null!==t}function C(t){return!b(t)}function y(t){return!isNaN(t)}function x(t){return Array.isArray(t)?t:[t]}t=t&&t.hasOwnProperty("default")?t.default:t,e=e&&e.hasOwnProperty("default")?e.default:e,n.each=((t,e)=>"string"==typeof t?Array.from((e||document).querySelectorAll(t)):t||null),n.create=((t,e)=>{let i=document.createElement(t);for(let t in e){let o=e[t];if("inside"===t)n(o).appendChild(i);else if("around"===t){let t=n(o);t.parentNode.insertBefore(i,t),i.appendChild(t)}else"styles"===t?"object"==typeof o&&Object.keys(o).map(t=>{i.style[t]=o[t]}):t in i?i[t]=o:i.setAttribute(t,o)}return i}),n.on=((t,e,i,o)=>{o?n.delegate(t,e,i,o):(o=i,n.bind(t,e,o))}),n.off=((t,e,n)=>{t.removeEventListener(e,n)}),n.bind=((t,e,n)=>{e.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}),n.delegate=((t,e,n,i)=>{t.addEventListener(e,function(t){const e=t.target.closest(n);e&&(t.delegatedTarget=e,i.call(this,t,e))})}),n.unbind=((t,e)=>{if(t)for(let n in e){let i=e[n];n.split(/\s+/).forEach(function(e){t.removeEventListener(e,i)})}}),n.fire=((t,e,n)=>{let i=document.createEvent("HTMLEvents");i.initEvent(e,!0,!0);for(let t in n)i[t]=n[t];return t.dispatchEvent(i)}),n.data=((t,e)=>{if(!e)return t.dataset;for(const n in e)t.dataset[n]=e[n]}),n.style=((t,e)=>{if("string"==typeof e)return n.getStyle(t,e);Array.isArray(t)||(t=[t]);t.map(t=>{for(const n in e)t.style[n]=e[n]})}),n.removeStyle=((t,e)=>{Array.isArray(t)||(t=[t]);Array.isArray(e)||(e=[e]);t.map(t=>{for(const n of e)t.style[n]=""})}),n.getStyle=((t,e)=>{let n=getComputedStyle(t)[e];["width","height"].includes(e)&&(n=parseFloat(n));return n}),n.closest=((t,e)=>{if(!e)return null;if(e.matches(t))return e;return n.closest(t,e.parentNode)}),n.inViewport=((t,e)=>{const{top:top,left:left,bottom:bottom,right:right}=t.getBoundingClientRect();const{top:pTop,left:pLeft,bottom:pBottom,right:pRight}=e.getBoundingClientRect();return top>=pTop&&left>=pLeft&&bottom<=pBottom&&right<=pRight}),n.scrollTop=function(t,e){requestAnimationFrame(()=>{t.scrollTop=e})},n.scrollbarWidth=function(){const t=document.createElement("div");n.style(t,{width:"100px",height:"100px",overflow:"scroll",position:"absolute",top:"-9999px"}),document.body.appendChild(t);const e=t.offsetWidth-t.clientWidth;return document.body.removeChild(t),e};var I=i,v="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},R="object"==typeof v&&v&&v.Object===Object&&v,S="object"==typeof self&&self&&self.Object===Object&&self,$=R||S||Function("return this")(),E=function(){return $.Date.now()},k=$.Symbol,M=Object.prototype,T=M.hasOwnProperty,H=M.toString,L=k?k.toStringTag:void 0,_=o,W=Object.prototype.toString,j=s,O="[object Null]",z="[object Undefined]",A=k?k.toStringTag:void 0,B=r,F=a,D="[object Symbol]",N=l,V=NaN,q=/^\s+|\s+$/g,K=/^[-+]0x[0-9a-f]+$/i,P=/^0b[01]+$/i,X=/^0o[0-7]+$/i,U=parseInt,Z=c,G="Expected a function",J=Math.max,Q=Math.min,Y=h,tt="Expected a function";let et=u,nt=Y;class it{constructor(t){this.options=t,this.sortRows=p(this.sortRows,this),this.switchColumn=p(this.switchColumn,this),this.removeColumn=p(this.removeColumn,this),this.filterRows=p(this.filterRows,this)}init(t,e){t||(t=this.options.data),e&&(this.options.columns=e),this.data=t,this.rowCount=0,this.columns=[],this.rows=[],this.prepareColumns(),this.prepareRows(),this.prepareTreeRows(),this.prepareRowView(),this.prepareNumericColumns()}get currentSort(){return this.columns.find(t=>"none"!==t.sortOrder)||{colIndex:-1,sortOrder:"none"}}prepareColumns(){this.columns=[],this.validateColumns(),this.prepareDefaultColumns(),this.prepareHeader()}prepareDefaultColumns(){if(this.options.checkboxColumn&&!this.hasColumnById("_checkbox")){const t={id:"_checkbox",content:this.getCheckboxHTML(),editable:!1,resizable:!1,sortable:!1,focusable:!1,dropdown:!1,width:32};this.columns.push(t)}if(this.options.serialNoColumn&&!this.hasColumnById("_rowIndex")){let t={id:"_rowIndex",content:"",align:"center",editable:!1,resizable:!1,focusable:!1,dropdown:!1};this.columns.push(t)}}prepareHeader(){let t=this.columns.concat(this.options.columns);const e={isHeader:1,editable:!0,sortable:!0,resizable:!0,focusable:!0,dropdown:!0,width:null,format:t=>{if(null===t||void 0===t)return"";return t+""}};this.columns=t.map((t,e)=>this.prepareCell(t,e)).map(t=>Object.assign({},e,t)).map(t=>{t.content=t.content||t.name||"";t.id=t.id||t.content;return t})}prepareCell(t,e){const n={content:"",sortOrder:"none",colIndex:e,column:this.columns[e]};return null!==t&&"object"==typeof t?Object.assign(n,t):n.content=t,n}prepareNumericColumns(){const t=this.getRow(0);t&&(this.columns=this.columns.map((e,n)=>{const i=t[n].content;!e.align&&i&&g(i)&&(e.align="right");return e}))}prepareRows(){this.validateData(this.data),this.rows=this.data.map((t,e)=>{const n=this._getNextRowCount();let i=[];let o={rowIndex:n};if(Array.isArray(t))for(this.options.checkboxColumn&&i.push(this.getCheckboxHTML()),this.options.serialNoColumn&&i.push(n+1+""),i=i.concat(t);i.length{if(y(t.meta.indent)){const n=this.getRow(e+1);t.meta.isLeaf=!n||C(n.meta.indent)||n.meta.indent<=t.meta.indent}})}prepareRowView(){this.rowViewOrder=this.rows.map(t=>t.meta.rowIndex)}prepareRow(t,e){const n={rowIndex:e.rowIndex,indent:e.indent};return t=t.map((t,e)=>this.prepareCell(t,e)).map(t=>Object.assign({},n,t)),t.meta=e,t}validateColumns(){const t=this.options.columns;if(!Array.isArray(t))throw new ot("`columns` must be an array");t.forEach((t,e)=>{if("string"!=typeof t&&"object"!=typeof t)throw new ot(`column "${e}" must be a string or an object`)})}validateData(t){if(Array.isArray(t)&&(0===t.length||Array.isArray(t[0])||"object"==typeof t[0]))return!0;throw new ot("`data` must be an array of arrays or objects")}appendRows(t){this.validateData(t),this.rows.push(...this.prepareRows(t))}sortRows(t,e="none"){t=+t,this.getColumns().map(n=>{n.colIndex===t?n.sortOrder=e:n.sortOrder="none"}),this._sortRows(t,e)}_sortRows(t,e){if(this.currentSort.colIndex===t&&("asc"===this.currentSort.sortOrder&&"desc"===e||"desc"===this.currentSort.sortOrder&&"asc"===e))return this.reverseArray(this.rowViewOrder),void(this.currentSort.sortOrder=e);if(this.rowViewOrder.sort((n,i)=>{const o=n;const s=i;const r=this.getCell(t,n).content;const a=this.getCell(t,i).content;if("none"===e)return o-s;if("asc"===e){if(ra)return 1;if(r===a)return 0}else if("desc"===e){if(ra)return-1;if(r===a)return 0}return 0}),this.hasColumnById("_rowIndex")){const t=this.getColumnIndexById("_rowIndex");this.rows.forEach((e,n)=>{const i=this.rowViewOrder.indexOf(n);const o=e[t];o.content=i+1+""})}}reverseArray(t){let e=null,n=null;for(e=0,n=t.length-1;e{const i=Object.assign({},n[t],{colIndex:e});const o=Object.assign({},n[e],{colIndex:t});n[e]=i;n[t]=o})}removeColumn(t){t=+t;const e=e=>e.colIndex!==t,n=(t,e)=>Object.assign({},t,{colIndex:e});this.columns=this.columns.filter(e).map(n),this.rows.forEach(e=>{e.splice(t,1);e.forEach((t,e)=>{t.colIndex=e})})}updateRow(t,e){if(t.lengtht[0].rowIndex===e);return this.rows[i]=n,n}updateCell(t,e,n){let i;"object"==typeof t&&(t=(i=t).colIndex,e=i.rowIndex,n=i),i=this.getCell(t,e);for(let t in n){const e=n[t];void 0!==e&&(i[t]=e)}return i}updateColumn(t,e){const n=this.getColumn(t);for(let t in e){const i=e[t];void 0!==i&&(n[t]=i)}return n}filterRows(t,e){let n=[],i=[];return this.rows.map(t=>t[e]).forEach(e=>{const o=String(e.content||"").toLowerCase();const s=(t||"").toLowerCase();!s||o.includes(s)?i.push(e.rowIndex):n.push(e.rowIndex)}),this._filteredRows=i,{rowsToHide:n,rowsToShow:i}}getFilteredRowIndices(){return this._filteredRows||this.rows.map(t=>t.meta.rowIndex)}getRowCount(){return this.rowCount}_getNextRowCount(){const t=this.rowCount;return this.rowCount++,t}getRows(t,e){return this.rows.slice(t,e)}getRowsForView(t,e){return this.rowViewOrder.map(t=>this.rows[t]).slice(t,e)}getColumns(t){let e=this.columns;return t&&(e=e.slice(this.getStandardColumnCount())),e}getStandardColumnCount(){return this.options.checkboxColumn&&this.options.serialNoColumn?2:this.options.checkboxColumn||this.options.serialNoColumn?1:0}getColumnCount(t){let e=this.columns.length;return t&&(e-=this.getStandardColumnCount()),e}getColumn(t){return(t=+t)<0&&(t=this.columns.length+t),this.columns.find(e=>e.colIndex===t)}getColumnById(t){return this.columns.find(e=>e.id===t)}getRow(t){return t=+t,this.rows[t]}getCell(t,e){return e=+e,t=+t,this.getRow(e)[t]}getChildren(t){t=+t;const e=this.getRow(t).meta.indent,n=[];for(let i=t+1;ie&&n.push(i),t.meta.indent===e))break}return n}getImmediateChildren(t){t=+t;const e=this.getRow(t).meta.indent,n=[],i=e+1;for(let o=t+1;oi)&&(t.meta.indent===i&&n.push(o),t.meta.indent===e))break}return n}get(){return{columns:this.columns,rows:this.rows}}getData(t){return this.data[t]}hasColumn(t){return Boolean(this.columns.find(e=>e.content===t))}hasColumnById(t){return Boolean(this.columns.find(e=>e.id===t))}getColumnIndex(t){return this.columns.findIndex(e=>e.content===t)}getColumnIndexById(t){return this.columns.findIndex(e=>e.id===t)}getCheckboxHTML(){return''}}class ot extends TypeError{}class st{constructor(t){this.instance=t,w(this,this.instance,["options","fireEvent","header","datamanager","style","wrapper","rowmanager","bodyScrollable"]),this.bindEvents(),rt=rt.bind(this,this.options.dropdownButton)}renderHeader(){this.header.innerHTML="",this.refreshHeader()}refreshHeader(){const t=this.datamanager.getColumns(),e=n.each(".data-table-cell[data-is-header]",this.header);!n(".data-table-cell",this.header)||t.length{const o=t[i];n.data(e,{colIndex:o.colIndex});const s=n(".sort-indicator",e);s&&(s.innerHTML=this.options.sortIndicator[o.sortOrder])}),this.$columnMap=[]}getHeaderHTML(t){let e=this.rowmanager.getRowHTML(t,{isHeader:1});return this.options.inlineFilters&&(e+=this.rowmanager.getRowHTML(t,{isFilter:1})),e}bindEvents(){this.bindDropdown(),this.bindResizeColumn(),this.bindMoveColumn(),this.bindFilter()}bindDropdown(){function t(t){e&&e.classList.remove("is-active"),e=null}let e;n.on(this.header,"click",".data-table-dropdown-toggle",(i,o)=>{const s=n.closest(".data-table-dropdown",o);s.classList.contains("is-active")?t():(t(),s.classList.add("is-active"),e=s)}),n.on(document.body,"click",e=>{if(e.target.matches(".data-table-dropdown-toggle"))return;t()});const i=this.options.headerDropdown;n.on(this.header,"click",".data-table-dropdown-list > div",(t,e)=>{const o=n.closest(".data-table-cell",e);const{index:index}=n.data(e);const{colIndex:colIndex}=n.data(o);let s=i[index].action;s&&s.call(this.instance,this.getColumn(colIndex))})}bindResizeColumn(){let t,e,i,o=!1;n.on(this.header,"mousedown",".data-table-cell .column-resizer",(s,r)=>{document.body.classList.add("data-table-resize");const a=r.parentNode.parentNode;t=a;const{colIndex:colIndex}=n.data(t);const l=this.getColumn(colIndex);if(l&&!1===l.resizable)return;o=!0;e=n.style(n(".content",t),"width");i=s.pageX}),n.on(document.body,"mouseup",e=>{document.body.classList.remove("data-table-resize");if(!t)return;o=!1;const{colIndex:colIndex}=n.data(t);this.setColumnWidth(colIndex);this.style.setBodyStyle();t=null}),n.on(document.body,"mousemove",s=>{if(!o)return;const r=e+(s.pageX-i);const{colIndex:colIndex}=n.data(t);if(this.getColumnMinWidth(colIndex)>r)return;this.datamanager.updateColumn(colIndex,{width:r});this.setColumnHeaderWidth(colIndex)})}bindMoveColumn(){const e=()=>{const e=n(".data-table-cell",this.header);if(!e)return;const i=n(".data-table-row",this.header);this.sortable=t.create(i,{onEnd:t=>{const{oldIndex:oldIndex,newIndex:newIndex}=t;const e=t.item;const{colIndex:colIndex}=n.data(e);if(+colIndex===newIndex)return;this.switchColumn(oldIndex,newIndex)},preventOnFilter:!1,filter:".column-resizer, .data-table-dropdown",animation:150})};n.on(document.body,"mousemove",e)}bindSortColumn(){n.on(this.header,"click",".data-table-cell .column-title",(t,e)=>{const i=e.closest(".data-table-cell");let{colIndex:colIndex,sortOrder:sortOrder="none"}=n.data(i);const o=this.getColumn(colIndex);if(o&&!1===o.sortable)return;n(".sort-indicator",this.header).textContent="";n.each(".data-table-cell",this.header).map(t=>{n.data(t,{sortOrder:"none"})});let s,r;"none"===sortOrder?(s="asc",r="▲"):"asc"===sortOrder?(s="desc",r="▼"):"desc"===sortOrder&&(s="none",r="");n.data(i,{sortOrder:s});n(".sort-indicator",i).textContent=r;this.sortColumn(colIndex,s)})}sortColumn(t,e){this.instance.freeze(),this.sortRows(t,e).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>this.instance.unfreeze()).then(()=>{this.fireEvent("onSortColumn",this.getColumn(t))})}removeColumn(t){const e=this.getColumn(t);this.instance.freeze(),this.datamanager.removeColumn(t).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>this.instance.unfreeze()).then(()=>{this.fireEvent("onRemoveColumn",e)})}switchColumn(t,e){this.instance.freeze(),this.datamanager.switchColumn(t,e).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>{this.setColumnWidth(t);this.setColumnWidth(e);this.instance.unfreeze()}).then(()=>{this.fireEvent("onSwitchColumn",this.getColumn(t),this.getColumn(e))})}toggleFilter(t){let e;(e=void 0===t?!this.isFilterShown:t)?n.style(this.$filterRow,{display:""}):n.style(this.$filterRow,{display:"none"}),this.isFilterShown=e,this.style.setBodyStyle()}focusFilter(t){this.isFilterShown&&n(`[data-col-index="${t}"] .data-table-filter`,this.$filterRow).focus()}bindFilter(){if(this.options.inlineFilters){const t=t=>{const e=n.closest(".data-table-cell",t.target);const{colIndex:colIndex}=n.data(e);const i=t.target.value;this.datamanager.filterRows(i,colIndex).then(({rowsToHide:rowsToHide,rowsToShow:rowsToShow})=>{this.rowmanager.hideRows(rowsToHide);this.rowmanager.showRows(rowsToShow)})};n.on(this.header,"keydown",".data-table-filter",nt(t,300))}}sortRows(t,e){return this.datamanager.sortRows(t,e)}getColumn(t){return this.datamanager.getColumn(t)}getColumns(){return this.datamanager.getColumns()}setColumnWidth(t,e){t=+t,this._columnWidthMap=this._columnWidthMap||[];let n=e||this.getColumn(t).width,i=this._columnWidthMap[t];const o=`[data-col-index="${t}"] .content, [data-col-index="${t}"] .edit-cell`,s={width:n+"px"};void 0!==(i=this.style.setStyle(o,s,i))&&(this._columnWidthMap[t]=i)}setColumnHeaderWidth(t){t=+t,this.$columnMap=this.$columnMap||[];const e=`.data-table-header [data-col-index="${t}"] .content`,{width:width}=this.getColumn(t);let n=this.$columnMap[t];n||(n=this.header.querySelector(e),this.$columnMap[t]=n),n.style.width=width+"px"}getColumnMinWidth(t){return t=+t,this.getColumn(t).minWidth||24}getFirstColumnIndex(){return this.datamanager.getColumnIndexById("_rowIndex")+1}getHeaderCell$(t){return n(`.data-table-cell[data-col-index="${t}"]`,this.header)}getLastColumnIndex(){return this.datamanager.getColumnCount()-1}getSerialColumnIndex(){return this.datamanager.getColumns().findIndex(t=>t.content.includes("Sr. No"))}}var rt=function(t="v"){return`
${t}
\n
\n ${this.options.headerDropdown.map((t,e)=>`
${t.label}
`).join("")}\n
\n `};class at{constructor(t){this.instance=t,w(this,this.instance,["wrapper","options","style","bodyScrollable","columnmanager","rowmanager","datamanager","keyboard"]),this.bindEvents()}bindEvents(){this.bindFocusCell(),this.bindEditCell(),this.bindKeyboardSelection(),this.bindCopyCellContents(),this.bindMouseEvents(),this.bindTreeEvents()}bindFocusCell(){this.bindKeyboardNav()}bindEditCell(){this.$editingCell=null,n.on(this.bodyScrollable,"dblclick",".data-table-cell",(t,e)=>{this.activateEditing(e)}),this.keyboard.on("enter",t=>{this.$focusedCell&&!this.$editingCell?this.activateEditing(this.$focusedCell):this.$editingCell&&(this.submitEditing(),this.deactivateEditing())})}bindKeyboardNav(){const t=t=>{if(!this.$focusedCell||this.$editingCell)return!1;let e=this.$focusedCell;"left"===t||"shift+tab"===t?e=this.getLeftCell$(e):"right"===t||"tab"===t?e=this.getRightCell$(e):"up"===t?e=this.getAboveCell$(e):"down"===t&&(e=this.getBelowCell$(e));this.focusCell(e);return!0},e=t=>{if(!this.$focusedCell||this.$editingCell)return!1;let e=this.$focusedCell;const{rowIndex:rowIndex,colIndex:colIndex}=n.data(e);"left"===t?e=this.getLeftMostCell$(rowIndex):"right"===t?e=this.getRightMostCell$(rowIndex):"up"===t?e=this.getTopMostCell$(colIndex):"down"===t&&(e=this.getBottomMostCell$(colIndex));this.focusCell(e);return!0};["left","right","up","down","tab","shift+tab"].map(e=>this.keyboard.on(e,()=>t(e))),["left","right","up","down"].map(t=>this.keyboard.on("ctrl+"+t,()=>e(t))),this.keyboard.on("esc",()=>{this.deactivateEditing()}),this.options.inlineFilters&&this.keyboard.on("ctrl+f",t=>{const e=n.closest(".data-table-cell",t.target);let{colIndex:colIndex}=n.data(e);this.activateFilter(colIndex);return!0})}bindKeyboardSelection(){const t=t=>{let e=this.getSelectionCursor();"left"===t?e=this.getLeftCell$(e):"right"===t?e=this.getRightCell$(e):"up"===t?e=this.getAboveCell$(e):"down"===t&&(e=this.getBelowCell$(e));return e};["left","right","up","down"].map(e=>this.keyboard.on("shift+"+e,()=>this.selectArea(t(e))))}bindCopyCellContents(){this.keyboard.on("ctrl+c",()=>{this.copyCellContents(this.$focusedCell,this.$selectionCursor)})}bindMouseEvents(){let t=null;n.on(this.bodyScrollable,"mousedown",".data-table-cell",e=>{t=!0;this.focusCell(n(e.delegatedTarget))}),n.on(this.bodyScrollable,"mouseup",()=>{t=!1});const e=e=>{if(!t)return;this.selectArea(n(e.delegatedTarget))};n.on(this.bodyScrollable,"mousemove",".data-table-cell",et(e,50))}bindTreeEvents(){n.on(this.bodyScrollable,"click",".toggle",(t,e)=>{const i=n.closest(".data-table-cell",e);const{rowIndex:rowIndex}=n.data(i);i.classList.contains("tree-close")?this.rowmanager.openSingleNode(rowIndex):this.rowmanager.closeSingleNode(rowIndex)})}focusCell(t,{skipClearSelection:skipClearSelection=0}={}){if(t&&t!==this.$editingCell){const{colIndex:colIndex,isHeader:isHeader}=n.data(t);isHeader||!1!==this.columnmanager.getColumn(colIndex).focusable&&(this.scrollToCell(t),this.deactivateEditing(),skipClearSelection||this.clearSelection(),this.$focusedCell&&this.$focusedCell.classList.remove("selected"),this.$focusedCell=t,t.classList.add("selected"),t.focus(),this.highlightRowColumnHeader(t))}}highlightRowColumnHeader(t){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(t),e=`.data-table-header .data-table-cell[data-col-index="${colIndex}"]`,i=`.data-table-cell[data-row-index="${rowIndex}"][data-col-index="${this.datamanager.getColumnIndexById("_rowIndex")}"]`;this.lastHeaders&&n.removeStyle(this.lastHeaders,"backgroundColor");const o=n(e,this.wrapper),s=n(i,this.wrapper);n.style([o,s],{backgroundColor:"#f5f7fa"}),this.lastHeaders=[o,s]}selectAreaOnClusterChanged(){if(this.$focusedCell&&this.$selectionCursor){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(this.$selectionCursor),t=this.getCell$(colIndex,rowIndex);if(t&&t!==this.$selectionCursor){const e=n.data(this.$focusedCell);this.$focusedCell=this.getCell$(e.colIndex,e.rowIndex),this.selectArea(t)}}}focusCellOnClusterChanged(){if(this.$focusedCell){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(this.$focusedCell),t=this.getCell$(colIndex,rowIndex);t&&this.focusCell(t,{skipClearSelection:1})}}selectArea(t){this.$focusedCell&&this._selectArea(this.$focusedCell,t)&&(this.$selectionCursor=t)}_selectArea(t,e){if(t===e)return!1;const n=this.getCellsInRange(t,e);return!!n&&(this.clearSelection(),n.map(t=>this.getCell$(...t)).map(t=>t.classList.add("highlight")),!0)}getCellsInRange(t,e){let i,o,s,r;if("number"==typeof t)[i,o,s,r]=arguments;else if("object"==typeof t){if(!t||!e)return!1;const a=n.data(t),l=n.data(e);i=a.colIndex,o=a.rowIndex,s=l.colIndex,r=l.rowIndex}if(o>r&&([o,r]=[r,o]),i>s&&([i,s]=[s,i]),this.isStandardCell(i)||this.isStandardCell(s))return!1;let a=[],l=i,c=o,h=[];for(;c<=r;)h.push(c),c++;return h.map(t=>{for(;l<=s;)a.push([l,t]),l++;l=i}),a}clearSelection(){n.each(".data-table-cell.highlight",this.bodyScrollable).map(t=>t.classList.remove("highlight")),this.$selectionCursor=null}getSelectionCursor(){return this.$selectionCursor||this.$focusedCell}activateEditing(t){this.focusCell(t);const{rowIndex:rowIndex,colIndex:colIndex}=n.data(t),e=this.columnmanager.getColumn(colIndex);if(!e||!1!==e.editable&&!1!==e.focusable){const i=this.getCell(colIndex,rowIndex);if(!i||!1!==i.editable){if(this.$editingCell){const{_rowIndex:_rowIndex,_colIndex:_colIndex}=n.data(this.$editingCell);if(rowIndex===_rowIndex&&colIndex===_colIndex)return}this.$editingCell=t,t.classList.add("editing");const o=n(".edit-cell",t);o.innerHTML="";const s=this.getEditor(colIndex,rowIndex,i.content,o);s&&(this.currentCellEditor=s,s.initValue(i.content,rowIndex,e))}}}deactivateEditing(){this.$focusedCell&&this.$focusedCell.focus(),this.$editingCell&&(this.$editingCell.classList.remove("editing"),this.$editingCell=null)}getEditor(t,e,n,i){const o=this.datamanager.getColumn(t),s=this.datamanager.getRow(e),r=this.datamanager.getData(e);let a=this.options.getEditor?this.options.getEditor(t,e,n,i,o,s,r):this.getDefaultEditor(i);return!1!==a&&(void 0===a&&(a=this.getDefaultEditor(i)),a)}getDefaultEditor(t){const e=n.create("input",{class:"input-style",type:"text",inside:t});return{initValue(t){e.focus(),e.value=t},getValue(){return e.value},setValue(t){e.value=t}}}submitEditing(){if(this.$editingCell){const t=this.$editingCell,{rowIndex:rowIndex,colIndex:colIndex}=n.data(t),e=this.datamanager.getColumn(colIndex);if(t){const n=this.currentCellEditor;if(n){const i=n.getValue(),o=n.setValue(i,rowIndex,e),s=this.getCell(colIndex,rowIndex).content;this.updateCell(colIndex,rowIndex,i),t.focus(),o&&o.then&&o.catch(t=>{console.log(t);this.updateCell(colIndex,rowIndex,s)})}}this.currentCellEditor=null}}copyCellContents(t,e){if(!e&&t){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(t),e=this.getCell(colIndex,rowIndex);return void m(e.content)}const i=this.getCellsInRange(t,e);i&&m(i.map(t=>this.getCell(...t)).reduce((t,e)=>{const n=e.rowIndex;t[n]=t[n]||[];t[n].push(e.content);return t},[]).map(t=>t.join("\t")).join("\n"))}activateFilter(t){this.columnmanager.toggleFilter(),this.columnmanager.focusFilter(t),this.columnmanager.isFilterShown||this.$focusedCell.focus()}updateCell(t,e,n){const i=this.datamanager.updateCell(t,e,{content:n});this.refreshCell(i)}refreshCell(t){n(this.selector(t.colIndex,t.rowIndex),this.bodyScrollable).innerHTML=this.getCellContent(t)}toggleTreeButton(t,e){const n=this.columnmanager.getFirstColumnIndex(),i=this.getCell$(n,t);i&&i.classList[e?"remove":"add"]("tree-close")}isStandardCell(t){return t\n ${this.getCellContent(t)}\n \n `}getCellContent(t){const{isHeader:isHeader,isFilter:isFilter}=t,e=!isHeader&&!1!==t.editable?this.getEditCellHTML():"",n=isHeader&&!1!==t.sortable?'':"",i=isHeader&&!1!==t.resizable?'':"",o=isHeader&&!1!==t.dropdown?`
${rt()}
`:"",s=t.format||t.column&&t.column.format||null;let r;if(isHeader||isFilter||!s)r=t.content;else{const e=this.datamanager.getRow(t.rowIndex),n=this.datamanager.getData(t.rowIndex);r=s(t.content,e,t.column,n)}if(this.options.treeView&&!isHeader&&!isFilter&&void 0!==t.indent){const e=this.datamanager.getRow(t.rowIndex+1),n=e&&e.meta.indent>t.indent,i=this.datamanager.getColumnIndexById("_rowIndex")+1;if(i===t.colIndex){const e=1.5*((t.indent||0)+1),i=n?``:"";r=`\n ${i}${r}`}}return`\n
\n ${r}\n ${n}\n ${i}\n ${o}\n
\n ${e}\n `}getEditCellHTML(){return`\n
\n `}selector(t,e){return`.data-table-cell[data-col-index="${t}"][data-row-index="${e}"]`}}class lt{constructor(t){this.instance=t,w(this,this.instance,["options","fireEvent","wrapper","bodyScrollable","bodyRenderer"]),this.bindEvents(),this.refreshRows=p(this.refreshRows,this)}get datamanager(){return this.instance.datamanager}get cellmanager(){return this.instance.cellmanager}bindEvents(){this.bindCheckbox()}bindCheckbox(){this.options.checkboxColumn&&(this.checkMap=[],n.on(this.wrapper,"click",'.data-table-cell[data-col-index="0"] [type="checkbox"]',(t,e)=>{const i=e.closest(".data-table-cell");const{rowIndex:rowIndex,isHeader:isHeader}=n.data(i);const o=e.checked;isHeader?this.checkAll(o):this.checkRow(rowIndex,o)}))}refreshRows(){this.instance.renderBody(),this.instance.setDimensions()}refreshRow(t,e){this.datamanager.updateRow(t,e).forEach(t=>{this.cellmanager.refreshCell(t)})}getCheckedRows(){if(!this.checkMap)return[];let t=[];for(let e in this.checkMap){const n=this.checkMap[e];1===n&&t.push(e)}return t}highlightCheckedRows(){this.getCheckedRows().map(t=>this.checkRow(t,!0))}checkRow(t,e){const i=e?1:0,o=t=>`.data-table-cell[data-row-index="${t}"][data-col-index="0"] [type="checkbox"]`;this.checkMap[t]=i,n.each(o(t),this.bodyScrollable).map(t=>{t.checked=e}),this.highlightRow(t,e),this.showCheckStatus(),this.fireEvent("onCheckRow",this.datamanager.getRow(t))}checkAll(t){const e=t?1:0;this.checkMap=t?Array.from(Array(this.getTotalRows())).map(t=>e):[],n.each('.data-table-cell[data-col-index="0"] [type="checkbox"]',this.bodyScrollable).map(e=>{e.checked=t}),this.highlightAll(t),this.showCheckStatus()}showCheckStatus(){if(this.options.checkedRowStatus){const t=this.getCheckedRows().length;t>0?this.bodyRenderer.showToastMessage(`${t} row${t>1?"s":""} selected`):this.bodyRenderer.clearToastMessage()}}highlightRow(t,e=!0){const n=this.getRow$(t);if(n){if(!e&&this.bodyScrollable.classList.contains("row-highlight-all"))return void n.classList.add("row-unhighlight");e&&n.classList.contains("row-unhighlight")&&n.classList.remove("row-unhighlight"),this._highlightedRows=this._highlightedRows||{},e?(n.classList.add("row-highlight"),this._highlightedRows[t]=n):(n.classList.remove("row-highlight"),delete this._highlightedRows[t])}}highlightAll(t=!0){if(t)this.bodyScrollable.classList.add("row-highlight-all");else{this.bodyScrollable.classList.remove("row-highlight-all");for(const t in this._highlightedRows){const e=this._highlightedRows[t];e.classList.remove("row-highlight")}this._highlightedRows={}}}hideRows(t){(t=x(t)).map(t=>{const e=this.getRow$(t);e.classList.add("hide")})}showRows(t){(t=x(t)).map(t=>{const e=this.getRow$(t);e.classList.remove("hide")})}openSingleNode(t){const e=this.datamanager.getImmediateChildren(t);this.showRows(e),this.cellmanager.toggleTreeButton(t,!0)}closeSingleNode(t){this.datamanager.getImmediateChildren(t).forEach(t=>{const e=this.datamanager.getRow(t);e.meta.isLeaf?(this.hideRows(t),this.cellmanager.toggleTreeButton(t,!1)):(this.closeSingleNode(t),this.hideRows(t))}),this.cellmanager.toggleTreeButton(t,!1)}getRow$(t){return n(this.selector(t),this.bodyScrollable)}getTotalRows(){return this.datamanager.getRowCount()}getFirstRowIndex(){return 0}getLastRowIndex(){return this.datamanager.getRowCount()-1}scrollToRow(t){t=+t,this._lastScrollTo=this._lastScrollTo||0;const e=this.getRow$(t);if(!n.inViewport(e,this.bodyScrollable)){const{height:height}=e.getBoundingClientRect(),{top:top,bottom:bottom}=this.bodyScrollable.getBoundingClientRect(),i=Math.floor((bottom-top)/height);let o=0;o=t>this._lastScrollTo?height*(t+1-i):height*(t+1-1),this._lastScrollTo=t,n.scrollTop(this.bodyScrollable,o)}}getRowHTML(t,e){const n=f(e);return e.isFilter&&(t=t.map(t=>Object.assign({},t,{content:this.getFilterInput({colIndex:t.colIndex}),isFilter:1,isHeader:void 0,editable:!1}))),`\n \n ${t.map(t=>this.cellmanager.getCellHTML(t)).join("")}\n \n `}getFilterInput(t){return``}selector(t){return`.data-table-row[data-row-index="${t}"]`}}class ct{constructor(t){this.instance=t,this.options=t.options,this.datamanager=t.datamanager,this.rowmanager=t.rowmanager,this.cellmanager=t.cellmanager,this.bodyScrollable=t.bodyScrollable,this.log=t.log,this.appendRemainingData=p(this.appendRemainingData,this)}render(){this.options.clusterize?this.renderBodyWithClusterize():this.renderBodyHTML()}renderBodyHTML(){const t=this.datamanager.getRowsForView();this.bodyScrollable.innerHTML=`\n \n ${this.getBodyHTML(t)}\n
\n `,this.instance.setDimensions(),this.restoreState()}renderBodyWithClusterize(){const t=this.datamanager.getRowsForView(0,20);let i=this.getDataForClusterize(t);0===i.length&&(i=[`${this.options.noDataMessage}`]),this.clusterize?this.clusterize.update(i):(this.bodyScrollable.innerHTML=`\n \n ${this.getBodyHTML([])}\n
\n `,this.clusterize=new e({rows:i,scrollElem:this.bodyScrollable,contentElem:n("tbody",this.bodyScrollable),callbacks:{clusterChanged:()=>{this.restoreState()}},show_no_data_row:!1}),this.instance.setDimensions()),this.appendRemainingData()}restoreState(){this.rowmanager.highlightCheckedRows(),this.cellmanager.selectAreaOnClusterChanged(),this.cellmanager.focusCellOnClusterChanged()}appendRemainingData(){const t=this.datamanager.getRowsForView(20),e=this.getDataForClusterize(t);this.clusterize.append(e)}showToastMessage(t){this.instance.toastMessage.innerHTML=`${t}`}clearToastMessage(){this.instance.toastMessage.innerHTML=""}getDataForClusterize(t){return t.map(t=>this.rowmanager.getRowHTML(t,t.meta))}getBodyHTML(t){return`\n \n ${t.map(t=>this.rowmanager.getRowHTML(t,t.meta)).join("")}\n \n `}}class ht{constructor(t){this.instance=t,w(this,this.instance,["options","datamanager","columnmanager","header","bodyScrollable","datatableWrapper","getColumn"]),this.scopeClass="datatable-instance-"+t.constructor.instances,t.datatableWrapper.classList.add(this.scopeClass);const e=document.createElement("style");t.wrapper.insertBefore(e,t.datatableWrapper),this.styleEl=e,this.bindResizeWindow()}get stylesheet(){return this.styleEl.sheet}bindResizeWindow(){"fluid"===this.options.layout&&n.on(window,"resize",et(()=>{this.distributeRemainingWidth();this.refreshColumnWidth();this.compensateScrollbarWidth();this.setBodyStyle()},300))}destroy(){this.styleEl.remove()}setStyle(t,e,n=-1){const i=Object.keys(e).map(t=>{t.includes("-")||(t=d(t));return`${t}:${e[t]};`}).join("");let o=`${t.split(",").map(t=>`.${this.scopeClass} ${t}`).join(",")} { ${i} }`;if(this.stylesheet){let t=this.stylesheet.cssRules.length;return-1!==n&&(this.stylesheet.deleteRule(n),t=n),this.stylesheet.insertRule(o,t),t}}setDimensions(){this.setHeaderStyle(),this.setupMinWidth(),this.setupNaturalColumnWidth(),this.setupColumnWidth(),this.distributeRemainingWidth(),this.setColumnStyle(),this.compensateScrollbarWidth(),this.setDefaultCellHeight(),this.setBodyStyle()}setHeaderStyle(){"fluid"===this.options.layout&&n.style(this.header,{width:0}),n.style(this.header,{margin:0});const t=this.datamanager.getColumns().filter(t=>!1===t.resizable).map(t=>t.colIndex).map(t=>`.data-table-header [data-col-index="${t}"]`).join();this.setStyle(t,{cursor:"pointer"})}setupMinWidth(){n.each(".data-table-cell[data-is-header]",this.header).map(t=>{const e=n.style(n(".content",t),"width");const{colIndex:colIndex}=n.data(t);const i=this.getColumn(colIndex);i.minWidth||(i.minWidth=e)})}setupNaturalColumnWidth(){n(".data-table-row")&&n.each('.data-table-row[data-row-index="0"] .data-table-cell',this.bodyScrollable).map(t=>{const{colIndex:colIndex}=n.data(t);const e=this.datamanager.getColumn(colIndex);let i=n.style(n(".content",t),"width");"_rowIndex"===e.id&&(i=this.getRowIndexColumnWidth(i),e.width=i);e.naturalWidth=i})}setupColumnWidth(){if("ratio"===this.options.layout){let t=n.style(this.datatableWrapper,"width");if(this.options.serialNoColumn){const e=this.datamanager.getColumnById("_rowIndex");t=t-e.width-1}if(this.options.checkboxColumn){const e=this.datamanager.getColumnById("_checkbox");t=t-e.width-1}const e=this.datamanager.getColumns().map(t=>{if("_rowIndex"===t.id||"_checkbox"===t.id)return 0;t.width||(t.width=1);t.ratioWidth=parseInt(t.width,10);return t.ratioWidth}).reduce((t,e)=>t+e),i=t/e;this.datamanager.getColumns().map(t=>{if("_rowIndex"===t.id||"_checkbox"===t.id)return;t.width=Math.floor(i*t.ratioWidth)-1})}else this.datamanager.getColumns().map(t=>{t.width||(t.width=t.naturalWidth);t.widtht.resizable),o=(t-e)/i.length;i.map(t=>{const e=n.style(this.getColumnHeaderElement(t.colIndex),"width");let i=Math.floor(e+o)-2;this.datamanager.updateColumn(t.colIndex,{width:i})})}}setDefaultCellHeight(){if(!this.options.dynamicRowHeight&&!this.__cellHeightSet){const t=n(".data-table-cell[data-is-header]",this.instance.header);if(t){const e=this.options.cellHeight||n.style(t,"height");e&&(this.setCellHeight(e),this.__cellHeightSet=!0)}}}setCellHeight(t){this.setStyle(".data-table-cell .content",{height:t+"px"}),this.setStyle(".data-table-cell .edit-cell",{height:t+"px"})}setColumnStyle(){this.datamanager.getColumns().map(t=>{["left","center","right"].includes(t.align)&&this.setStyle(`[data-col-index="${t.colIndex}"]`,{"text-align":t.align});this.columnmanager.setColumnHeaderWidth(t.colIndex);this.columnmanager.setColumnWidth(t.colIndex)}),this.setBodyStyle()}refreshColumnWidth(){this.datamanager.getColumns().map(t=>{this.columnmanager.setColumnHeaderWidth(t.colIndex);this.columnmanager.setColumnWidth(t.colIndex)})}setBodyStyle(){const t=n.style(this.header,"width");n.style(this.bodyScrollable,{width:t+"px"});const e=n(".data-table-body",this.bodyScrollable);e&&n.style(e,{height:"0px"}),n.style(this.bodyScrollable,{marginTop:n.style(this.header,"height")+"px"}),n.style(n("table",this.bodyScrollable),{margin:0,width:"100%"})}getColumnHeaderElement(t){return t=+t,t<0?null:n(`.data-table-cell[data-col-index="${t}"]`,this.header)}getRowIndexColumnWidth(t){this._rowIndexColumnWidthMap=this._rowIndexColumnWidthMap||{};const e=(this.datamanager.getRowCount()+"").length;return this._rowIndexColumnWidthMap[e]||(this._rowIndexColumnWidthMap[e]=t+8*(e-1)),this._rowIndexColumnWidthMap[e]}}const ut={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 dt{constructor(t){this.listeners={},n.on(t,"keydown",this.handler.bind(this))}handler(t){let e=ut[t.keyCode];t.shiftKey&&"shift"!==e&&(e="shift+"+e),(t.ctrlKey&&"ctrl"!==e||t.metaKey&&"meta"!==e)&&(e="ctrl+"+e);const n=this.listeners[e];if(n&&n.length>0)for(let e of n){const n=e(t);void 0!==n&&!0!==n||t.preventDefault()}}on(t,e){t.split(",").map(t=>t.trim()).map(t=>{this.listeners[t]=this.listeners[t]||[];this.listeners[t].push(e)})}}var ft={columns:[],data:[],dropdownButton:"▼",headerDropdown:[{label:"Sort Ascending",action:function(t){this.sortColumn(t.colIndex,"asc")}},{label:"Sort Descending",action:function(t){this.sortColumn(t.colIndex,"desc")}},{label:"Reset sorting",action:function(t){this.sortColumn(t.colIndex,"none")}},{label:"Remove column",action:function(t){this.removeColumn(t.colIndex)}}],events:{onRemoveColumn(t){},onSwitchColumn(t,e){},onSortColumn(t){},onCheckRow(t){}},sortIndicator:{asc:"↑",desc:"↓",none:""},freezeMessage:"",getEditor:null,serialNoColumn:!0,checkboxColumn:!1,clusterize:!0,logs:!1,layout:"fixed",noDataMessage:"No Data",cellHeight:null,inlineFilters:!1,treeView:!1,checkedRowStatus:!0,dynamicRowHeight:!1};class mt{constructor(t,e){if(mt.instances++,"string"==typeof t&&(t=document.querySelector(t)),this.wrapper=t,!(this.wrapper instanceof HTMLElement))throw new Error("Invalid argument given for `wrapper`");this.buildOptions(e),this.prepare(),this.style=new ht(this),this.keyboard=new dt(this.wrapper),this.datamanager=new it(this.options),this.rowmanager=new lt(this),this.columnmanager=new st(this),this.cellmanager=new at(this),this.bodyRenderer=new ct(this),this.options.data&&this.refresh()}buildOptions(t){this.options=this.options||{},this.options=Object.assign({},ft,this.options||{},t),this.options.headerDropdown.push(...t.headerDropdown||[]),this.events=Object.assign({},ft.events,this.options.events||{},t.events||{}),this.fireEvent=this.fireEvent.bind(this)}prepare(){this.prepareDom(),this.unfreeze()}prepareDom(){this.wrapper.innerHTML=`\n
\n \n
\n
\n
\n
\n ${this.options.freezeMessage}\n
\n \n
\n
\n `,this.datatableWrapper=n(".data-table",this.wrapper),this.header=n(".data-table-header",this.wrapper),this.bodyScrollable=n(".body-scrollable",this.wrapper),this.freezeContainer=n(".freeze-container",this.wrapper),this.toastMessage=n(".toast-message",this.wrapper)}refresh(t,e){this.datamanager.init(t,e),this.render(),this.setDimensions()}destroy(){this.wrapper.innerHTML="",this.style.destroy()}appendRows(t){this.datamanager.appendRows(t),this.rowmanager.refreshRows()}refreshRow(t,e){this.rowmanager.refreshRow(t,e)}render(){this.renderHeader(),this.renderBody()}renderHeader(){this.columnmanager.renderHeader()}renderBody(){this.bodyRenderer.render()}setDimensions(){this.style.setDimensions()}showToastMessage(t){this.bodyRenderer.showToastMessage(t)}clearToastMessage(){this.bodyRenderer.clearToastMessage()}getColumn(t){return this.datamanager.getColumn(t)}getColumns(){return this.datamanager.getColumns()}getRows(){return this.datamanager.getRows()}getCell(t,e){return this.datamanager.getCell(t,e)}getColumnHeaderElement(t){return this.columnmanager.getColumnHeaderElement(t)}getViewportHeight(){return this.viewportHeight||(this.viewportHeight=n.style(this.bodyScrollable,"height")),this.viewportHeight}sortColumn(t,e){this.columnmanager.sortColumn(t,e)}removeColumn(t){this.columnmanager.removeColumn(t)}scrollToLastColumn(){this.datatableWrapper.scrollLeft=9999}freeze(){n.style(this.freezeContainer,{display:""})}unfreeze(){n.style(this.freezeContainer,{display:"none"})}updateOptions(t){this.buildOptions(t)}fireEvent(t,...e){this.events[t].apply(this,e)}log(){this.options.logs&&console.log.apply(console,arguments)}}mt.instances=0;var gt={name:"frappe-datatable",version:"0.0.4",description:"A modern datatable library for the web",main:"dist/frappe-datatable.cjs.js",scripts:{start:"yarn run dev",build:"rollup -c",production:"rollup -c --production","build:docs":"rollup -c --docs",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"},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","rollup-plugin-uglify-es":"^0.0.1"},repository:{type:"git",url:"https://github.com/frappe/datatable.git"},keywords:["datatable","data","grid","table"],author:"Faris Ansari",license:"MIT",bugs:{url:"https://github.com/frappe/datatable/issues"},homepage:"https://frappe.github.io/datatable",dependencies:{"clusterize.js":"^0.18.0",lodash:"^4.17.5",sortablejs:"^1.7.0"}};return mt.__version__=gt.version,mt}(Sortable,Clusterize); +var DataTable=function(t,e){"use strict";function n(t,e){return"string"==typeof t?(e||document).querySelector(t):t||null}function i(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function o(t){var e=M.call(t,H),n=t[H];try{t[H]=void 0}catch(t){}var i=k.call(t);return e?t[H]=n:delete t[H],i}function s(t){return W.call(t)}function r(t){return null==t?void 0===t?A:O:F&&F in Object(t)?L(t):j(t)}function l(t){return null!=t&&"object"==typeof t}function c(t){return"symbol"==typeof t||B(t)&&z(t)==D}function a(t){if("number"==typeof t)return t;if(N(t))return V;if(I(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=I(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(q,"");var n=P.test(t);return n||X.test(t)?U(t.slice(2),n?2:8):K.test(t)?V:+t}function h(t,e,n){function i(e){var n=d,i=f;return d=f=void 0,b=e,m=t.apply(i,n)}function o(t){return b=t,p=setTimeout(l,e),C?i(t):m}function s(t){var n=t-b,i=e-(t-w);return y?Q(i,g-n):i}function r(t){var n=t-w,i=t-b;return void 0===w||n>=e||n<0||y&&i>=g}function l(){var t=$();if(r(t))return c(t);p=setTimeout(l,s(t))}function c(t){return p=void 0,x&&d?i(t):(d=f=void 0,m)}function a(){void 0!==p&&clearTimeout(p),b=0,d=w=f=p=void 0}function h(){return void 0===p?m:c($())}function u(){var t=$(),n=r(t);if(d=arguments,f=this,w=t,n){if(void 0===p)return o(w);if(y)return p=setTimeout(l,e),i(w)}return void 0===p&&(p=setTimeout(l,e)),m}var d,f,g,m,p,w,b=0,C=!1,y=!1,x=!0;if("function"!=typeof t)throw new TypeError(G);return e=Z(e)||0,I(n)&&(C=!!n.leading,g=(y="maxWait"in n)?J(Z(n.maxWait)||0,e):g,x="trailing"in n?!!n.trailing:x),u.cancel=a,u.flush=h,u}function u(t,e,n){var i=!0,o=!0;if("function"!=typeof t)throw new TypeError(tt);return I(n)&&(i="leading"in n?!!n.leading:i,o="trailing"in n?!!n.trailing:o),Y(t,e,{leading:i,maxWait:e,trailing:o})}function d(t){return t.replace(/([A-Z])/g,t=>`-${t[0].toLowerCase()}`)}function f(t){return Object.keys(t).map(e=>{const n=d(e);const i=t[e];if(void 0===i)return"";return`data-${n}="${i}" `}).join("").trim()}function g(t){var e=document.createElement("textarea");e.style.position="fixed",e.style.top=0,e.style.left=0,e.style.width="2em",e.style.height="2em",e.style.padding=0,e.style.border="none",e.style.outline="none",e.style.boxShadow="none",e.style.background="transparent",e.value=t,document.body.appendChild(e),e.select();try{document.execCommand("copy")}catch(t){console.log("Oops, unable to copy")}document.body.removeChild(e)}function m(t){return!isNaN(t)}function p(t,e=null){return(...n)=>new Promise(i=>{const o=()=>{const o=t.apply(e,n);i(o)};setTimeout(o)})}function w(t,e,n){const i=n.reduce((t,n)=>{t[n]={get(){return e[n]}};return t},{});Object.defineProperties(t,i)}function b(t){return void 0!==t||null!==t}function C(t){return!b(t)}function y(t){return!isNaN(t)}function x(t){return Array.isArray(t)?t:[t]}t=t&&t.hasOwnProperty("default")?t.default:t,e=e&&e.hasOwnProperty("default")?e.default:e,n.each=((t,e)=>"string"==typeof t?Array.from((e||document).querySelectorAll(t)):t||null),n.create=((t,e)=>{let i=document.createElement(t);for(let t in e){let o=e[t];if("inside"===t)n(o).appendChild(i);else if("around"===t){let t=n(o);t.parentNode.insertBefore(i,t),i.appendChild(t)}else"styles"===t?"object"==typeof o&&Object.keys(o).map(t=>{i.style[t]=o[t]}):t in i?i[t]=o:i.setAttribute(t,o)}return i}),n.on=((t,e,i,o)=>{o?n.delegate(t,e,i,o):(o=i,n.bind(t,e,o))}),n.off=((t,e,n)=>{t.removeEventListener(e,n)}),n.bind=((t,e,n)=>{e.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}),n.delegate=((t,e,n,i)=>{t.addEventListener(e,function(t){const e=t.target.closest(n);e&&(t.delegatedTarget=e,i.call(this,t,e))})}),n.unbind=((t,e)=>{if(t)for(let n in e){let i=e[n];n.split(/\s+/).forEach(function(e){t.removeEventListener(e,i)})}}),n.fire=((t,e,n)=>{let i=document.createEvent("HTMLEvents");i.initEvent(e,!0,!0);for(let t in n)i[t]=n[t];return t.dispatchEvent(i)}),n.data=((t,e)=>{if(!e)return t.dataset;for(const n in e)t.dataset[n]=e[n]}),n.style=((t,e)=>{if("string"==typeof e)return n.getStyle(t,e);Array.isArray(t)||(t=[t]);t.map(t=>{for(const n in e)t.style[n]=e[n]})}),n.removeStyle=((t,e)=>{Array.isArray(t)||(t=[t]);Array.isArray(e)||(e=[e]);t.map(t=>{for(const n of e)t.style[n]=""})}),n.getStyle=((t,e)=>{let n=getComputedStyle(t)[e];["width","height"].includes(e)&&(n=parseFloat(n));return n}),n.closest=((t,e)=>{if(!e)return null;if(e.matches(t))return e;return n.closest(t,e.parentNode)}),n.inViewport=((t,e)=>{const{top:top,left:left,bottom:bottom,right:right}=t.getBoundingClientRect();const{top:pTop,left:pLeft,bottom:pBottom,right:pRight}=e.getBoundingClientRect();return top>=pTop&&left>=pLeft&&bottom<=pBottom&&right<=pRight}),n.scrollTop=function(t,e){requestAnimationFrame(()=>{t.scrollTop=e})},n.scrollbarWidth=function(){const t=document.createElement("div");n.style(t,{width:"100px",height:"100px",overflow:"scroll",position:"absolute",top:"-9999px"}),document.body.appendChild(t);const e=t.offsetWidth-t.clientWidth;return document.body.removeChild(t),e};var I=i,v="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},R="object"==typeof v&&v&&v.Object===Object&&v,S="object"==typeof self&&self&&self.Object===Object&&self,_=R||S||Function("return this")(),$=function(){return _.Date.now()},T=_.Symbol,E=Object.prototype,M=E.hasOwnProperty,k=E.toString,H=T?T.toStringTag:void 0,L=o,W=Object.prototype.toString,j=s,O="[object Null]",A="[object Undefined]",F=T?T.toStringTag:void 0,z=r,B=l,D="[object Symbol]",N=c,V=NaN,q=/^\s+|\s+$/g,K=/^[-+]0x[0-9a-f]+$/i,P=/^0b[01]+$/i,X=/^0o[0-7]+$/i,U=parseInt,Z=a,G="Expected a function",J=Math.max,Q=Math.min,Y=h,tt="Expected a function";let et=u,nt=Y;class it{constructor(t){this.options=t,this.sortRows=p(this.sortRows,this),this.switchColumn=p(this.switchColumn,this),this.removeColumn=p(this.removeColumn,this),this.filterRows=p(this.filterRows,this)}init(t,e){t||(t=this.options.data),e&&(this.options.columns=e),this.data=t,this.rowCount=0,this.columns=[],this.rows=[],this.prepareColumns(),this.prepareRows(),this.prepareTreeRows(),this.prepareRowView(),this.prepareNumericColumns()}get currentSort(){return this.columns.find(t=>"none"!==t.sortOrder)||{colIndex:-1,sortOrder:"none"}}prepareColumns(){this.columns=[],this.validateColumns(),this.prepareDefaultColumns(),this.prepareHeader()}prepareDefaultColumns(){if(this.options.checkboxColumn&&!this.hasColumnById("_checkbox")){const t={id:"_checkbox",content:this.getCheckboxHTML(),editable:!1,resizable:!1,sortable:!1,focusable:!1,dropdown:!1,width:32};this.columns.push(t)}if(this.options.serialNoColumn&&!this.hasColumnById("_rowIndex")){let t={id:"_rowIndex",content:"",align:"center",editable:!1,resizable:!1,focusable:!1,dropdown:!1};this.columns.push(t)}}prepareHeader(){let t=this.columns.concat(this.options.columns);const e={isHeader:1,editable:!0,sortable:!0,resizable:!0,focusable:!0,dropdown:!0,width:null,format:t=>{if(null===t||void 0===t)return"";return t+""}};this.columns=t.map((t,e)=>this.prepareCell(t,e)).map(t=>Object.assign({},e,t)).map(t=>{t.content=t.content||t.name||"";t.id=t.id||t.content;return t})}prepareCell(t,e){const n={content:"",sortOrder:"none",colIndex:e,column:this.columns[e]};return null!==t&&"object"==typeof t?Object.assign(n,t):n.content=t,n}prepareNumericColumns(){const t=this.getRow(0);t&&(this.columns=this.columns.map((e,n)=>{const i=t[n].content;!e.align&&i&&m(i)&&(e.align="right");return e}))}prepareRows(){this.validateData(this.data),this.rows=this.data.map((t,e)=>{const n=this._getNextRowCount();let i=[];let o={rowIndex:n};if(Array.isArray(t))for(this.options.checkboxColumn&&i.push(this.getCheckboxHTML()),this.options.serialNoColumn&&i.push(n+1+""),i=i.concat(t);i.length{if(y(t.meta.indent)){const n=this.getRow(e+1);t.meta.isLeaf=!n||C(n.meta.indent)||n.meta.indent<=t.meta.indent}})}prepareRowView(){this.rowViewOrder=this.rows.map(t=>t.meta.rowIndex)}prepareRow(t,e){const n={rowIndex:e.rowIndex,indent:e.indent};return t=t.map((t,e)=>this.prepareCell(t,e)).map(t=>Object.assign({},n,t)),t.meta=e,t}validateColumns(){const t=this.options.columns;if(!Array.isArray(t))throw new ot("`columns` must be an array");t.forEach((t,e)=>{if("string"!=typeof t&&"object"!=typeof t)throw new ot(`column "${e}" must be a string or an object`)})}validateData(t){if(Array.isArray(t)&&(0===t.length||Array.isArray(t[0])||"object"==typeof t[0]))return!0;throw new ot("`data` must be an array of arrays or objects")}appendRows(t){this.validateData(t),this.rows.push(...this.prepareRows(t))}sortRows(t,e="none"){t=+t,this.getColumns().map(n=>{n.colIndex===t?n.sortOrder=e:n.sortOrder="none"}),this._sortRows(t,e)}_sortRows(t,e){if(this.currentSort.colIndex===t&&("asc"===this.currentSort.sortOrder&&"desc"===e||"desc"===this.currentSort.sortOrder&&"asc"===e))return this.reverseArray(this.rowViewOrder),void(this.currentSort.sortOrder=e);if(this.rowViewOrder.sort((n,i)=>{const o=n;const s=i;const r=this.getCell(t,n).content;const l=this.getCell(t,i).content;if("none"===e)return o-s;if("asc"===e){if(rl)return 1;if(r===l)return 0}else if("desc"===e){if(rl)return-1;if(r===l)return 0}return 0}),this.hasColumnById("_rowIndex")){const t=this.getColumnIndexById("_rowIndex");this.rows.forEach((e,n)=>{const i=this.rowViewOrder.indexOf(n);const o=e[t];o.content=i+1+""})}}reverseArray(t){let e=null,n=null;for(e=0,n=t.length-1;e{const i=Object.assign({},n[t],{colIndex:e});const o=Object.assign({},n[e],{colIndex:t});n[e]=i;n[t]=o})}removeColumn(t){t=+t;const e=e=>e.colIndex!==t,n=(t,e)=>Object.assign({},t,{colIndex:e});this.columns=this.columns.filter(e).map(n),this.rows.forEach(e=>{e.splice(t,1);e.forEach((t,e)=>{t.colIndex=e})})}updateRow(t,e){if(t.lengtht[0].rowIndex===e);return this.rows[i]=n,n}updateCell(t,e,n){let i;"object"==typeof t&&(t=(i=t).colIndex,e=i.rowIndex,n=i),i=this.getCell(t,e);for(let t in n){const e=n[t];void 0!==e&&(i[t]=e)}return i}updateColumn(t,e){const n=this.getColumn(t);for(let t in e){const i=e[t];void 0!==i&&(n[t]=i)}return n}filterRows(t,e){let n=[],i=[];return this.rows.map(t=>t[e]).forEach(e=>{const o=String(e.content||"").toLowerCase();const s=(t||"").toLowerCase();!s||o.includes(s)?i.push(e.rowIndex):n.push(e.rowIndex)}),this._filteredRows=i,{rowsToHide:n,rowsToShow:i}}getFilteredRowIndices(){return this._filteredRows||this.rows.map(t=>t.meta.rowIndex)}getRowCount(){return this.rowCount}_getNextRowCount(){const t=this.rowCount;return this.rowCount++,t}getRows(t,e){return this.rows.slice(t,e)}getRowsForView(t,e){return this.rowViewOrder.map(t=>this.rows[t]).slice(t,e)}getColumns(t){let e=this.columns;return t&&(e=e.slice(this.getStandardColumnCount())),e}getStandardColumnCount(){return this.options.checkboxColumn&&this.options.serialNoColumn?2:this.options.checkboxColumn||this.options.serialNoColumn?1:0}getColumnCount(t){let e=this.columns.length;return t&&(e-=this.getStandardColumnCount()),e}getColumn(t){return(t=+t)<0&&(t=this.columns.length+t),this.columns.find(e=>e.colIndex===t)}getColumnById(t){return this.columns.find(e=>e.id===t)}getRow(t){return t=+t,this.rows[t]}getCell(t,e){return e=+e,t=+t,this.getRow(e)[t]}getChildren(t){t=+t;const e=this.getRow(t).meta.indent,n=[];for(let i=t+1;ie&&n.push(i),t.meta.indent===e))break}return n}getImmediateChildren(t){t=+t;const e=this.getRow(t).meta.indent,n=[],i=e+1;for(let o=t+1;oi)&&(t.meta.indent===i&&n.push(o),t.meta.indent===e))break}return n}get(){return{columns:this.columns,rows:this.rows}}getData(t){return this.data[t]}hasColumn(t){return Boolean(this.columns.find(e=>e.content===t))}hasColumnById(t){return Boolean(this.columns.find(e=>e.id===t))}getColumnIndex(t){return this.columns.findIndex(e=>e.content===t)}getColumnIndexById(t){return this.columns.findIndex(e=>e.id===t)}getCheckboxHTML(){return''}}class ot extends TypeError{}class st{constructor(t){this.instance=t,w(this,this.instance,["wrapper","options","style","bodyScrollable","columnmanager","rowmanager","datamanager","keyboard"]),this.bindEvents()}bindEvents(){this.bindFocusCell(),this.bindEditCell(),this.bindKeyboardSelection(),this.bindCopyCellContents(),this.bindMouseEvents(),this.bindTreeEvents()}bindFocusCell(){this.bindKeyboardNav()}bindEditCell(){this.$editingCell=null,n.on(this.bodyScrollable,"dblclick",".dt-cell",(t,e)=>{this.activateEditing(e)}),this.keyboard.on("enter",()=>{this.$focusedCell&&!this.$editingCell?this.activateEditing(this.$focusedCell):this.$editingCell&&(this.submitEditing(),this.deactivateEditing())})}bindKeyboardNav(){const t=t=>{if(!this.$focusedCell||this.$editingCell)return!1;let e=this.$focusedCell;"left"===t||"shift+tab"===t?e=this.getLeftCell$(e):"right"===t||"tab"===t?e=this.getRightCell$(e):"up"===t?e=this.getAboveCell$(e):"down"===t&&(e=this.getBelowCell$(e));this.focusCell(e);return!0},e=t=>{if(!this.$focusedCell||this.$editingCell)return!1;let e=this.$focusedCell;const{rowIndex:rowIndex,colIndex:colIndex}=n.data(e);"left"===t?e=this.getLeftMostCell$(rowIndex):"right"===t?e=this.getRightMostCell$(rowIndex):"up"===t?e=this.getTopMostCell$(colIndex):"down"===t&&(e=this.getBottomMostCell$(colIndex));this.focusCell(e);return!0};["left","right","up","down","tab","shift+tab"].map(e=>this.keyboard.on(e,()=>t(e))),["left","right","up","down"].map(t=>this.keyboard.on(`ctrl+${t}`,()=>e(t))),this.keyboard.on("esc",()=>{this.deactivateEditing()}),this.options.inlineFilters&&this.keyboard.on("ctrl+f",t=>{const e=n.closest(".dt-cell",t.target);const{colIndex:colIndex}=n.data(e);this.activateFilter(colIndex);return!0})}bindKeyboardSelection(){const t=t=>{let e=this.getSelectionCursor();"left"===t?e=this.getLeftCell$(e):"right"===t?e=this.getRightCell$(e):"up"===t?e=this.getAboveCell$(e):"down"===t&&(e=this.getBelowCell$(e));return e};["left","right","up","down"].map(e=>this.keyboard.on(`shift+${e}`,()=>this.selectArea(t(e))))}bindCopyCellContents(){this.keyboard.on("ctrl+c",()=>{const t=this.copyCellContents(this.$focusedCell,this.$selectionCursor);const e=`${t} cell${t>1?"s":""} copied`;t&&this.instance.showToastMessage(e,2)}),this.options.pasteFromClipboard&&this.keyboard.on("ctrl+v",t=>{this.instance.pasteTarget.focus();setTimeout(()=>{const t=this.instance.pasteTarget.value;this.instance.pasteTarget.value="";this.pasteContentInCell(t)},10);return!1})}bindMouseEvents(){let t=null;n.on(this.bodyScrollable,"mousedown",".dt-cell",e=>{t=!0;this.focusCell(n(e.delegatedTarget))}),n.on(this.bodyScrollable,"mouseup",()=>{t=!1});const e=e=>{if(!t)return;this.selectArea(n(e.delegatedTarget))};n.on(this.bodyScrollable,"mousemove",".dt-cell",et(e,50))}bindTreeEvents(){n.on(this.bodyScrollable,"click",".dt-tree-node__toggle",(t,e)=>{const i=n.closest(".dt-cell",e);const{rowIndex:rowIndex}=n.data(i);i.classList.contains("dt-cell--tree-close")?this.rowmanager.openSingleNode(rowIndex):this.rowmanager.closeSingleNode(rowIndex)})}focusCell(t,{skipClearSelection:skipClearSelection=0}={}){if(t&&t!==this.$editingCell){const{colIndex:colIndex,isHeader:isHeader}=n.data(t);isHeader||!1!==this.columnmanager.getColumn(colIndex).focusable&&(this.scrollToCell(t),this.deactivateEditing(),skipClearSelection||this.clearSelection(),this.$focusedCell&&this.$focusedCell.classList.remove("dt-cell--focus"),this.$focusedCell=t,t.classList.add("dt-cell--focus"),t.focus(),this.highlightRowColumnHeader(t))}}highlightRowColumnHeader(t){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(t),e=`.dt-cell--header-${colIndex}`,i=`.dt-cell--${this.datamanager.getColumnIndexById("_rowIndex")}-${rowIndex}`;this.lastHeaders&&this.lastHeaders.forEach(t=>t.classList.remove("dt-cell--highlight"));const o=n(e,this.wrapper),s=n(i,this.wrapper);this.lastHeaders=[o,s],this.lastHeaders.forEach(t=>t.classList.add("dt-cell--highlight"))}selectAreaOnClusterChanged(){if(this.$focusedCell&&this.$selectionCursor){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(this.$selectionCursor),t=this.getCell$(colIndex,rowIndex);if(t&&t!==this.$selectionCursor){const e=n.data(this.$focusedCell);this.$focusedCell=this.getCell$(e.colIndex,e.rowIndex),this.selectArea(t)}}}focusCellOnClusterChanged(){if(this.$focusedCell){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(this.$focusedCell),t=this.getCell$(colIndex,rowIndex);t&&this.focusCell(t,{skipClearSelection:1})}}selectArea(t){this.$focusedCell&&this._selectArea(this.$focusedCell,t)&&(this.$selectionCursor=t)}_selectArea(t,e){if(t===e)return!1;const n=this.getCellsInRange(t,e);return!!n&&(this.clearSelection(),this._selectedCells=n.map(t=>this.getCell$(...t)),requestAnimationFrame(()=>{this._selectedCells.map(t=>t.classList.add("dt-cell--highlight"))}),!0)}getCellsInRange(t,e){let i,o,s,r;if("number"==typeof t)[i,o,s,r]=arguments;else if("object"==typeof t){if(!t||!e)return!1;const l=n.data(t),c=n.data(e);i=+l.colIndex,o=+l.rowIndex,s=+c.colIndex,r=+c.rowIndex}if(o>r&&([o,r]=[r,o]),i>s&&([i,s]=[s,i]),this.isStandardCell(i)||this.isStandardCell(s))return!1;const l=[];let c=i,a=o;const h=[];for(;a<=r;)h.push(a),a+=1;return h.map(t=>{for(;c<=s;)l.push([c,t]),c++;c=i}),l}clearSelection(){(this._selectedCells||[]).forEach(t=>t.classList.remove("dt-cell--highlight")),this._selectedCells=[],this.$selectionCursor=null}getSelectionCursor(){return this.$selectionCursor||this.$focusedCell}activateEditing(t){this.focusCell(t);const{rowIndex:rowIndex,colIndex:colIndex}=n.data(t),e=this.columnmanager.getColumn(colIndex);if(!e||!1!==e.editable&&!1!==e.focusable){const i=this.getCell(colIndex,rowIndex);if(!i||!1!==i.editable){if(this.$editingCell){const{_rowIndex:_rowIndex,_colIndex:_colIndex}=n.data(this.$editingCell);if(rowIndex===_rowIndex&&colIndex===_colIndex)return}this.$editingCell=t,t.classList.add("dt-cell--editing");const o=n(".dt-cell__edit",t);o.innerHTML="";const s=this.getEditor(colIndex,rowIndex,i.content,o);s&&(this.currentCellEditor=s,s.initValue(i.content,rowIndex,e))}}}deactivateEditing(){this.$focusedCell&&this.$focusedCell.focus(),this.$editingCell&&(this.$editingCell.classList.remove("dt-cell--editing"),this.$editingCell=null)}getEditor(t,e,n,i){const o=this.datamanager.getColumn(t),s=this.datamanager.getRow(e),r=this.datamanager.getData(e);let l=this.options.getEditor?this.options.getEditor(t,e,n,i,o,s,r):this.getDefaultEditor(i);return!1!==l&&(void 0===l&&(l=this.getDefaultEditor(i)),l)}getDefaultEditor(t){const e=n.create("input",{class:"dt-input",type:"text",inside:t});return{initValue(t){e.focus(),e.value=t},getValue(){return e.value},setValue(t){e.value=t}}}submitEditing(){if(this.$editingCell){const t=this.$editingCell,{rowIndex:rowIndex,colIndex:colIndex}=n.data(t),e=this.datamanager.getColumn(colIndex);if(t){const n=this.currentCellEditor;if(n){const i=n.getValue(),o=n.setValue(i,rowIndex,e),s=this.getCell(colIndex,rowIndex).content;this.updateCell(colIndex,rowIndex,i),t.focus(),o&&o.then&&o.catch(t=>{console.log(t);this.updateCell(colIndex,rowIndex,s)})}}this.currentCellEditor=null}}copyCellContents(t,e){if(!e&&t){const{colIndex:colIndex,rowIndex:rowIndex}=n.data(t),e=this.getCell(colIndex,rowIndex);return g(e.content),1}const i=this.getCellsInRange(t,e);if(!i)return 0;const o=i.map(t=>this.getCell(...t)).reduce((t,e)=>{const n=e.rowIndex;t[n]=t[n]||[];t[n].push(e.content);return t},[]);return g(o.map(t=>t.join("\t")).join("\n")),o.reduce((t,e)=>t+e.length,0)}pasteContentInCell(t){if(this.$focusedCell){const e=t.split("\n").map(t=>t.split("\t")).filter(t=>t.length&&t.every(t=>t));let{colIndex:colIndex,rowIndex:rowIndex}=n.data(this.$focusedCell),i={colIndex:+colIndex,rowIndex:+rowIndex};e.forEach((t,e)=>{let n=e+i.rowIndex;t.forEach((t,e)=>{let o=e+i.colIndex;this.updateCell(o,n,t)})})}}activateFilter(t){this.columnmanager.toggleFilter(),this.columnmanager.focusFilter(t),this.columnmanager.isFilterShown||this.$focusedCell.focus()}updateCell(t,e,n){const i=this.datamanager.updateCell(t,e,{content:n});this.refreshCell(i)}refreshCell(t){n(this.selector(t.colIndex,t.rowIndex),this.bodyScrollable).innerHTML=this.getCellContent(t)}toggleTreeButton(t,e){const n=this.columnmanager.getFirstColumnIndex(),i=this.getCell$(n,t);i&&i.classList[e?"remove":"add"]("dt-cell--tree-close")}isStandardCell(t){return t\n ${this.getCellContent(t)}\n \n `}getCellContent(t){const{isHeader:isHeader,isFilter:isFilter,colIndex:colIndex}=t,e=!isHeader&&!1!==t.editable?this.getEditCellHTML(colIndex):"",n=isHeader&&!1!==t.sortable?'':"",i=isHeader&&!1!==t.resizable?'':"",o=isHeader&&!1!==t.dropdown?this.columnmanager.getDropdownHTML():"",s=t.format||t.column&&t.column.format||null;let r;if(isHeader||isFilter||!s)r=t.content;else{const e=this.datamanager.getRow(t.rowIndex),n=this.datamanager.getData(t.rowIndex);r=s(t.content,e,t.column,n)}if(this.options.treeView&&!isHeader&&!isFilter&&void 0!==t.indent){const e=this.datamanager.getRow(t.rowIndex+1),n=e&&e.meta.indent>t.indent,i=this.datamanager.getColumnIndexById("_rowIndex")+1;if(i===t.colIndex){const e=1.5*((t.indent||0)+1),i=n?``:"";r=`\n ${i}${r}`}}return`\n
\n ${r}\n ${n}\n ${i}\n ${o}\n
\n ${e}\n `}getEditCellHTML(t){return`
`}selector(t,e){return`.dt-cell--${t}-${e}`}}class rt{constructor(t){this.instance=t,w(this,this.instance,["options","fireEvent","header","datamanager","style","wrapper","rowmanager","bodyScrollable"]),this.bindEvents()}renderHeader(){this.header.innerHTML="",this.refreshHeader()}refreshHeader(){const t=this.datamanager.getColumns(),e=n.each(".dt-cell--header",this.header);!n(".dt-cell",this.header)||t.length{const o=t[i];n.data(e,{colIndex:o.colIndex});const s=n(".sort-indicator",e);s&&(s.innerHTML=this.options.sortIndicator[o.sortOrder])}),this.$columnMap=[]}getHeaderHTML(t){let e=this.rowmanager.getRowHTML(t,{isHeader:1});return this.options.inlineFilters&&(e+=this.rowmanager.getRowHTML(t,{isFilter:1})),e}bindEvents(){this.bindDropdown(),this.bindResizeColumn(),this.bindMoveColumn(),this.bindFilter()}bindDropdown(){function t(t){e&&e.classList.remove(i),e=null}let e,i="dt-dropdown--active";n.on(this.header,"click",".dt-dropdown__toggle",(o,s)=>{const r=n.closest(".dt-dropdown",s);r.classList.contains(i)?t():(t(),r.classList.add(i),e=r)}),n.on(document.body,"click",e=>{if(e.target.matches(".dt-dropdown__toggle"))return;t()});const o=this.options.headerDropdown;n.on(this.header,"click",".dt-dropdown__list-item",(t,e)=>{const i=n.closest(".dt-cell",e);const{index:index}=n.data(e);const{colIndex:colIndex}=n.data(i);let s=o[index].action;s&&s.call(this.instance,this.getColumn(colIndex))})}bindResizeColumn(){let t,e,i,o=!1;n.on(this.header,"mousedown",".dt-cell .dt-cell__resize-handle",(s,r)=>{document.body.classList.add("dt-resize");const l=r.parentNode.parentNode;t=l;const{colIndex:colIndex}=n.data(t);const c=this.getColumn(colIndex);if(c&&!1===c.resizable)return;o=!0;e=n.style(n(".dt-cell__content",t),"width");i=s.pageX}),n.on(document.body,"mouseup",e=>{document.body.classList.remove("dt-resize");if(!t)return;o=!1;const{colIndex:colIndex}=n.data(t);this.setColumnWidth(colIndex);this.style.setBodyStyle();t=null}),n.on(document.body,"mousemove",s=>{if(!o)return;const r=e+(s.pageX-i);const{colIndex:colIndex}=n.data(t);if(this.getColumnMinWidth(colIndex)>r)return;this.datamanager.updateColumn(colIndex,{width:r});this.setColumnHeaderWidth(colIndex)})}bindMoveColumn(){const e=()=>{const e=n(".dt-cell",this.header);if(!e)return;const i=n(".dt-row",this.header);this.sortable=t.create(i,{onEnd:t=>{const{oldIndex:oldIndex,newIndex:newIndex}=t;const e=t.item;const{colIndex:colIndex}=n.data(e);if(+colIndex===newIndex)return;this.switchColumn(oldIndex,newIndex)},preventOnFilter:!1,filter:".dt-cell__resize-handle, .dt-dropdown",chosenClass:"dt-cell--dragging",animation:150})};n.on(document.body,"mousemove",e)}sortColumn(t,e){this.instance.freeze(),this.sortRows(t,e).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>this.instance.unfreeze()).then(()=>{this.fireEvent("onSortColumn",this.getColumn(t))})}removeColumn(t){const e=this.getColumn(t);this.instance.freeze(),this.datamanager.removeColumn(t).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>this.instance.unfreeze()).then(()=>{this.fireEvent("onRemoveColumn",e)})}switchColumn(t,e){this.instance.freeze(),this.datamanager.switchColumn(t,e).then(()=>{this.refreshHeader();return this.rowmanager.refreshRows()}).then(()=>{this.setColumnWidth(t);this.setColumnWidth(e);this.instance.unfreeze()}).then(()=>{this.fireEvent("onSwitchColumn",this.getColumn(t),this.getColumn(e))})}toggleFilter(t){let e;(e=void 0===t?!this.isFilterShown:t)?n.style(this.$filterRow,{display:""}):n.style(this.$filterRow,{display:"none"}),this.isFilterShown=e,this.style.setBodyStyle()}focusFilter(t){this.isFilterShown&&n(`[data-col-index="${t}"] .dt-filter`,this.$filterRow).focus()}bindFilter(){if(this.options.inlineFilters){const t=t=>{const e=n.closest(".dt-cell",t.target);const{colIndex:colIndex}=n.data(e);const i=t.target.value;this.datamanager.filterRows(i,colIndex).then(({rowsToHide:rowsToHide,rowsToShow:rowsToShow})=>{this.rowmanager.hideRows(rowsToHide);this.rowmanager.showRows(rowsToShow)})};n.on(this.header,"keydown",".dt-filter",nt(t,300))}}sortRows(t,e){return this.datamanager.sortRows(t,e)}getColumn(t){return this.datamanager.getColumn(t)}getColumns(){return this.datamanager.getColumns()}setColumnWidth(t,e){t=+t,this._columnWidthMap=this._columnWidthMap||[];let n=e||this.getColumn(t).width,i=this._columnWidthMap[t];const o=[`.dt-cell__content--col-${t}`,`.dt-cell__edit--col-${t}`].join(", "),s={width:n+"px"};void 0!==(i=this.style.setStyle(o,s,i))&&(this._columnWidthMap[t]=i)}setColumnHeaderWidth(t){t=+t,this.$columnMap=this.$columnMap||[];const e=`.dt-cell__content--header-${t}`,{width:width}=this.getColumn(t);let n=this.$columnMap[t];n||(n=this.header.querySelector(e),this.$columnMap[t]=n),n.style.width=width+"px"}getColumnMinWidth(t){return t=+t,this.getColumn(t).minWidth||24}getFirstColumnIndex(){return this.datamanager.getColumnIndexById("_rowIndex")+1}getHeaderCell$(t){return n(`.dt-cell--header-${t}`,this.header)}getLastColumnIndex(){return this.datamanager.getColumnCount()-1}getDropdownHTML(){const{dropdownButton:dropdownButton,headerDropdown:dropdownItems}=this.options;return`\n
\n
${dropdownButton}
\n
\n ${dropdownItems.map((t,e)=>`\n
${t.label}
\n `).join("")}\n
\n
\n `}}class lt{constructor(t){this.instance=t,w(this,this.instance,["options","fireEvent","wrapper","bodyScrollable","bodyRenderer"]),this.bindEvents(),this.refreshRows=p(this.refreshRows,this)}get datamanager(){return this.instance.datamanager}get cellmanager(){return this.instance.cellmanager}bindEvents(){this.bindCheckbox()}bindCheckbox(){this.options.checkboxColumn&&(this.checkMap=[],n.on(this.wrapper,"click",'.dt-cell[data-col-index="0"] [type="checkbox"]',(t,e)=>{const i=e.closest(".dt-cell");const{rowIndex:rowIndex,isHeader:isHeader}=n.data(i);const o=e.checked;isHeader?this.checkAll(o):this.checkRow(rowIndex,o)}))}refreshRows(){this.instance.renderBody(),this.instance.setDimensions()}refreshRow(t,e){this.datamanager.updateRow(t,e).forEach(t=>{this.cellmanager.refreshCell(t)})}getCheckedRows(){if(!this.checkMap)return[];let t=[];for(let e in this.checkMap){const n=this.checkMap[e];1===n&&t.push(e)}return t}highlightCheckedRows(){this.getCheckedRows().map(t=>this.checkRow(t,!0))}checkRow(t,e){const i=e?1:0,o=t=>`.dt-cell[data-row-index="${t}"][data-col-index="0"] [type="checkbox"]`;this.checkMap[t]=i,n.each(o(t),this.bodyScrollable).map(t=>{t.checked=e}),this.highlightRow(t,e),this.showCheckStatus(),this.fireEvent("onCheckRow",this.datamanager.getRow(t))}checkAll(t){const e=t?1:0;this.checkMap=t?Array.from(Array(this.getTotalRows())).map(t=>e):[],n.each('.dt-cell[data-col-index="0"] [type="checkbox"]',this.bodyScrollable).map(e=>{e.checked=t}),this.highlightAll(t),this.showCheckStatus()}showCheckStatus(){if(this.options.checkedRowStatus){const t=this.getCheckedRows().length;t>0?this.bodyRenderer.showToastMessage(`${t} row${t>1?"s":""} selected`):this.bodyRenderer.clearToastMessage()}}highlightRow(t,e=!0){const n=this.getRow$(t);if(n){if(!e&&this.bodyScrollable.classList.contains("dt-scrollable--highlight-all"))return void n.classList.add("dt-row--unhighlight");e&&n.classList.contains("dt-row--unhighlight")&&n.classList.remove("dt-row--unhighlight"),this._highlightedRows=this._highlightedRows||{},e?(n.classList.add("dt-row--highlight"),this._highlightedRows[t]=n):(n.classList.remove("dt-row--highlight"),delete this._highlightedRows[t])}}highlightAll(t=!0){if(t)this.bodyScrollable.classList.add("dt-scrollable--highlight-all");else{this.bodyScrollable.classList.remove("dt-scrollable--highlight-all");for(const t in this._highlightedRows){const e=this._highlightedRows[t];e.classList.remove("dt-row--highlight")}this._highlightedRows={}}}hideRows(t){(t=x(t)).map(t=>{const e=this.getRow$(t);e.classList.add("dt-row--hide")})}showRows(t){(t=x(t)).map(t=>{const e=this.getRow$(t);e.classList.remove("dt-row--hide")})}openSingleNode(t){const e=this.datamanager.getImmediateChildren(t);this.showRows(e),this.cellmanager.toggleTreeButton(t,!0)}closeSingleNode(t){this.datamanager.getImmediateChildren(t).forEach(t=>{const e=this.datamanager.getRow(t);e.meta.isLeaf?(this.hideRows(t),this.cellmanager.toggleTreeButton(t,!1)):(this.closeSingleNode(t),this.hideRows(t))}),this.cellmanager.toggleTreeButton(t,!1)}getRow$(t){return n(this.selector(t),this.bodyScrollable)}getTotalRows(){return this.datamanager.getRowCount()}getFirstRowIndex(){return 0}getLastRowIndex(){return this.datamanager.getRowCount()-1}scrollToRow(t){t=+t,this._lastScrollTo=this._lastScrollTo||0;const e=this.getRow$(t);if(!n.inViewport(e,this.bodyScrollable)){const{height:height}=e.getBoundingClientRect(),{top:top,bottom:bottom}=this.bodyScrollable.getBoundingClientRect(),i=Math.floor((bottom-top)/height);let o=0;o=t>this._lastScrollTo?height*(t+1-i):height*(t+1-1),this._lastScrollTo=t,n.scrollTop(this.bodyScrollable,o)}}getRowHTML(t,e){const n=f(e);return e.isFilter&&(t=t.map(t=>Object.assign({},t,{content:this.getFilterInput({colIndex:t.colIndex}),isFilter:1,isHeader:void 0,editable:!1}))),`\n \n ${t.map(t=>this.cellmanager.getCellHTML(t)).join("")}\n \n `}getFilterInput(t){return``}selector(t){return`.dt-row[data-row-index="${t}"]`}}class ct{constructor(t){this.instance=t,this.options=t.options,this.datamanager=t.datamanager,this.rowmanager=t.rowmanager,this.cellmanager=t.cellmanager,this.bodyScrollable=t.bodyScrollable,this.log=t.log,this.appendRemainingData=p(this.appendRemainingData,this)}render(){this.options.clusterize?this.renderBodyWithClusterize():this.renderBodyHTML()}renderBodyHTML(){const t=this.datamanager.getRowsForView();this.bodyScrollable.innerHTML=this.getBodyHTML(t),this.instance.setDimensions(),this.restoreState()}renderBodyWithClusterize(){const t=this.datamanager.getRowsForView(0,20);let i=this.getDataForClusterize(t);0===i.length&&(i=[this.getNoDataHTML()]),this.clusterize?this.clusterize.update(i):(this.bodyScrollable.innerHTML=this.getBodyHTML([]),this.clusterize=new e({rows:i,scrollElem:this.bodyScrollable,contentElem:n("tbody",this.bodyScrollable),callbacks:{clusterChanged:()=>this.restoreState()},show_no_data_row:!1}),this.instance.setDimensions()),this.appendRemainingData()}restoreState(){this.rowmanager.highlightCheckedRows(),this.cellmanager.selectAreaOnClusterChanged(),this.cellmanager.focusCellOnClusterChanged()}appendRemainingData(){const t=this.datamanager.getRowsForView(20),e=this.getDataForClusterize(t);this.clusterize.append(e)}showToastMessage(t,e){this.instance.toastMessage.innerHTML=this.getToastMessageHTML(t),e&&setTimeout(()=>{this.clearToastMessage()},1e3*e)}clearToastMessage(){this.instance.toastMessage.innerHTML=""}getDataForClusterize(t){return t.map(t=>this.rowmanager.getRowHTML(t,t.meta))}getBodyHTML(t){return`\n \n \n ${t.map(t=>this.rowmanager.getRowHTML(t,t.meta)).join("")}\n \n
\n `}getNoDataHTML(){return`
${this.options.noDataMessage}
`}getToastMessageHTML(t){return`${t}`}}class at{constructor(t){this.instance=t,w(this,this.instance,["options","datamanager","columnmanager","header","bodyScrollable","datatableWrapper","getColumn"]),this.scopeClass="dt-instance-"+t.constructor.instances,t.datatableWrapper.classList.add(this.scopeClass);const e=document.createElement("style");t.wrapper.insertBefore(e,t.datatableWrapper),this.styleEl=e,this.bindResizeWindow()}get stylesheet(){return this.styleEl.sheet}bindResizeWindow(){"fluid"===this.options.layout&&n.on(window,"resize",et(()=>{this.distributeRemainingWidth();this.refreshColumnWidth();this.compensateScrollbarWidth();this.setBodyStyle()},300))}destroy(){this.styleEl.remove()}setStyle(t,e,n=-1){const i=Object.keys(e).map(t=>{t.includes("-")||(t=d(t));return`${t}:${e[t]};`}).join("");let o=`${t.split(",").map(t=>`.${this.scopeClass} ${t}`).join(",")} { ${i} }`;if(this.stylesheet){let t=this.stylesheet.cssRules.length;return-1!==n&&(this.stylesheet.deleteRule(n),t=n),this.stylesheet.insertRule(o,t),t}}setDimensions(){this.setHeaderStyle(),this.setupMinWidth(),this.setupNaturalColumnWidth(),this.setupColumnWidth(),this.distributeRemainingWidth(),this.setColumnStyle(),this.compensateScrollbarWidth(),this.setDefaultCellHeight(),this.setBodyStyle()}setHeaderStyle(){"fluid"===this.options.layout&&n.style(this.header,{width:0}),n.style(this.header,{margin:0})}setupMinWidth(){n.each(".dt-cell[data-is-header]",this.header).map(t=>{const{colIndex:colIndex}=n.data(t);const e=this.getColumn(colIndex);if(!e.minWidth){const i=n.style(n(".dt-cell__content",t),"width");e.minWidth=i}})}setupNaturalColumnWidth(){n(".dt-row")&&n.each('.dt-row[data-row-index="0"] .dt-cell',this.bodyScrollable).map(t=>{const{colIndex:colIndex}=n.data(t);const e=this.datamanager.getColumn(colIndex);let i=n.style(n(".dt-cell__content",t),"width");"_rowIndex"===e.id&&(i=this.getRowIndexColumnWidth(i),e.width=i);e.naturalWidth=i})}setupColumnWidth(){if("ratio"===this.options.layout){let t=n.style(this.datatableWrapper,"width");if(this.options.serialNoColumn){const e=this.datamanager.getColumnById("_rowIndex");t=t-e.width-1}if(this.options.checkboxColumn){const e=this.datamanager.getColumnById("_checkbox");t=t-e.width-1}const e=this.datamanager.getColumns().map(t=>{if("_rowIndex"===t.id||"_checkbox"===t.id)return 0;t.width||(t.width=1);t.ratioWidth=parseInt(t.width,10);return t.ratioWidth}).reduce((t,e)=>t+e),i=t/e;this.datamanager.getColumns().map(t=>{if("_rowIndex"===t.id||"_checkbox"===t.id)return;t.width=Math.floor(i*t.ratioWidth)-1})}else this.datamanager.getColumns().map(t=>{t.width||(t.width=t.naturalWidth);t.width{const t=n.scrollbarWidth();const e=this.datamanager.getColumn(-1);const i=e.width-t;this.columnmanager.setColumnWidth(e.colIndex,i)})}distributeRemainingWidth(){if("fluid"===this.options.layout){const t=n.style(this.instance.datatableWrapper,"width"),e=n.style(this.header,"width"),i=this.datamanager.getColumns().filter(t=>t.resizable),o=(t-e)/i.length;i.map(t=>{const e=n.style(this.getColumnHeaderElement(t.colIndex),"width");let i=Math.floor(e+o)-2;this.datamanager.updateColumn(t.colIndex,{width:i})})}}setDefaultCellHeight(){if(!this.options.dynamicRowHeight&&!this.__cellHeightSet){const t=n(".dt-cell[data-is-header]",this.instance.header);if(t){const e=this.options.cellHeight||n.style(t,"height");e&&(this.setCellHeight(e),this.__cellHeightSet=!0)}}}setCellHeight(t){this.setStyle(".dt-cell__content, .dt-cell__edit",{height:t+"px"})}setColumnStyle(){this.datamanager.getColumns().map(t=>{["left","center","right"].includes(t.align)&&this.setStyle(`.dt-cell--col-${t.colIndex}`,{"text-align":t.align});this.columnmanager.setColumnHeaderWidth(t.colIndex);this.columnmanager.setColumnWidth(t.colIndex)}),this.setBodyStyle()}refreshColumnWidth(){this.datamanager.getColumns().map(t=>{this.columnmanager.setColumnHeaderWidth(t.colIndex);this.columnmanager.setColumnWidth(t.colIndex)})}setBodyStyle(){requestAnimationFrame(()=>{const t=n.style(this.header,"width");n.style(this.bodyScrollable,{width:t+"px"});const e=n(".dt-body",this.bodyScrollable);e&&n.style(e,{height:"0px"});n.style(this.bodyScrollable,{marginTop:n.style(this.header,"height")+"px"});n.style(n("table",this.bodyScrollable),{margin:0,width:"100%"})})}getColumnHeaderElement(t){return t=+t,t<0?null:n(`.dt-cell[data-col-index="${t}"]`,this.header)}getRowIndexColumnWidth(t){this._rowIndexColumnWidthMap=this._rowIndexColumnWidthMap||{};const e=(this.datamanager.getRowCount()+"").length;return this._rowIndexColumnWidthMap[e]||(this._rowIndexColumnWidthMap[e]=t+8*(e-1)),this._rowIndexColumnWidthMap[e]}}const ht={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",86:"v"};class ut{constructor(t){this.listeners={},n.on(t,"keydown",this.handler.bind(this))}handler(t){let e=ht[t.keyCode];t.shiftKey&&"shift"!==e&&(e="shift+"+e),(t.ctrlKey&&"ctrl"!==e||t.metaKey&&"meta"!==e)&&(e="ctrl+"+e);const n=this.listeners[e];if(n&&n.length>0)for(let e of n){const n=e(t);void 0!==n&&!0!==n||t.preventDefault()}}on(t,e){t.split(",").map(t=>t.trim()).map(t=>{this.listeners[t]=this.listeners[t]||[];this.listeners[t].push(e)})}}var dt={columns:[],data:[],dropdownButton:"▼",headerDropdown:[{label:"Sort Ascending",action:function(t){this.sortColumn(t.colIndex,"asc")}},{label:"Sort Descending",action:function(t){this.sortColumn(t.colIndex,"desc")}},{label:"Reset sorting",action:function(t){this.sortColumn(t.colIndex,"none")}},{label:"Remove column",action:function(t){this.removeColumn(t.colIndex)}}],events:{onRemoveColumn(t){},onSwitchColumn(t,e){},onSortColumn(t){},onCheckRow(t){}},sortIndicator:{asc:"↑",desc:"↓",none:""},freezeMessage:"",getEditor:null,serialNoColumn:!0,checkboxColumn:!1,clusterize:!0,logs:!1,layout:"fixed",noDataMessage:"No Data",cellHeight:null,inlineFilters:!1,treeView:!1,checkedRowStatus:!0,dynamicRowHeight:!1,pasteFromClipboard:!1};class ft{constructor(t,e){if(ft.instances++,"string"==typeof t&&(t=document.querySelector(t)),this.wrapper=t,!(this.wrapper instanceof HTMLElement))throw new Error("Invalid argument given for `wrapper`");this.buildOptions(e),this.prepare(),this.style=new at(this),this.keyboard=new ut(this.wrapper),this.datamanager=new it(this.options),this.rowmanager=new lt(this),this.columnmanager=new rt(this),this.cellmanager=new st(this),this.bodyRenderer=new ct(this),this.options.data&&this.refresh()}buildOptions(t){this.options=this.options||{},this.options=Object.assign({},dt,this.options||{},t),t.headerDropdown=t.headerDropdown||[],this.options.headerDropdown=[...dt.headerDropdown,...t.headerDropdown],this.events=Object.assign({},dt.events,this.options.events||{},t.events||{}),this.fireEvent=this.fireEvent.bind(this)}prepare(){this.prepareDom(),this.unfreeze()}prepareDom(){this.wrapper.innerHTML=`\n
\n \n
\n
\n
\n
\n \n ${this.options.freezeMessage}\n \n
\n
\n \n
\n `,this.datatableWrapper=n(".datatable",this.wrapper),this.header=n(".dt-header",this.wrapper),this.bodyScrollable=n(".dt-scrollable",this.wrapper),this.freezeContainer=n(".dt-freeze",this.wrapper),this.toastMessage=n(".dt-toast",this.wrapper),this.pasteTarget=n(".dt-paste-target",this.wrapper)}refresh(t,e){this.datamanager.init(t,e),this.render(),this.setDimensions()}destroy(){this.wrapper.innerHTML="",this.style.destroy()}appendRows(t){this.datamanager.appendRows(t),this.rowmanager.refreshRows()}refreshRow(t,e){this.rowmanager.refreshRow(t,e)}render(){this.renderHeader(),this.renderBody()}renderHeader(){this.columnmanager.renderHeader()}renderBody(){this.bodyRenderer.render()}setDimensions(){this.style.setDimensions()}showToastMessage(t,e){this.bodyRenderer.showToastMessage(t,e)}clearToastMessage(){this.bodyRenderer.clearToastMessage()}getColumn(t){return this.datamanager.getColumn(t)}getColumns(){return this.datamanager.getColumns()}getRows(){return this.datamanager.getRows()}getCell(t,e){return this.datamanager.getCell(t,e)}getColumnHeaderElement(t){return this.columnmanager.getColumnHeaderElement(t)}getViewportHeight(){return this.viewportHeight||(this.viewportHeight=n.style(this.bodyScrollable,"height")),this.viewportHeight}sortColumn(t,e){this.columnmanager.sortColumn(t,e)}removeColumn(t){this.columnmanager.removeColumn(t)}scrollToLastColumn(){this.datatableWrapper.scrollLeft=9999}freeze(){n.style(this.freezeContainer,{display:""})}unfreeze(){n.style(this.freezeContainer,{display:"none"})}updateOptions(t){this.buildOptions(t)}fireEvent(t,...e){this.events[t].apply(this,e)}log(){this.options.logs&&console.log.apply(console,arguments)}}ft.instances=0;var gt={name:"frappe-datatable",version:"0.0.5",description:"A modern datatable library for the web",main:"dist/frappe-datatable.cjs.js",scripts:{start:"yarn run dev",build:"rollup -c",production:"rollup -c --production","build:docs":"rollup -c --docs",dev:"rollup -c -w",test:"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"},devDependencies:{chai:"3.5.0",deepmerge:"^2.0.1","eslint-config-airbnb":"^16.1.0","eslint-config-airbnb-base":"^12.1.0","eslint-plugin-import":"^2.11.0",mocha:"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-eslint":"^4.0.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify-es":"^0.0.1"},repository:{type:"git",url:"https://github.com/frappe/datatable.git"},keywords:["datatable","data","grid","table"],author:"Faris Ansari",license:"MIT",bugs:{url:"https://github.com/frappe/datatable/issues"},homepage:"https://frappe.github.io/datatable",dependencies:{"clusterize.js":"^0.18.0",lodash:"^4.17.5",sortablejs:"^1.7.0"}};return ft.__version__=gt.version,ft}(Sortable,Clusterize); diff --git a/docs/assets/css/frappe-datatable.css b/docs/assets/css/frappe-datatable.css index dd6fcf3..edffe35 100644 --- a/docs/assets/css/frappe-datatable.css +++ b/docs/assets/css/frappe-datatable.css @@ -1,170 +1,151 @@ -/* This file is processed by postcss */ -/* variables */ - -.data-table { - - /* styling */ - position: relative; - overflow: auto; -} - -/* resets */ - -.data-table *, .data-table *::after, .data-table *::before { +.datatable *, .datatable *::after, .datatable *::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; - } +.datatable { + position: relative; + overflow: auto; +} -.data-table .input-style { - outline: none; - width: 100%; - border: none; - } +.dt-header { + border-collapse: collapse; + border-bottom: 1px solid #d1d8dd; -.data-table *, .data-table *:focus { - outline: none; - border-radius: 0px; - -webkit-box-shadow: none; - box-shadow: none; - } + position: absolute; + top: 0; + left: 0; + background-color: #fff; +} -.data-table table { - border-collapse: collapse; - } +.dt-body { + 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 .hide { - display: none; - } - -.data-table .toast-message { - position: absolute; - bottom: 16px; - bottom: 1rem; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); - } - -.data-table .toast-message span { - display: inline-block; - background-color: rgba(0, 0, 0, .8); - color: #dfe2e5; - border-radius: 3px; - padding: 8px 16px; - padding: 0.5rem 1rem; - } - -.body-scrollable { - max-height: 500px; +.dt-scrollable { + max-height: 40vw; overflow: auto; border-bottom: 1px solid #d1d8dd; } -.body-scrollable.row-highlight-all .data-table-row:not(.row-unhighlight) { +.dt-scrollable--highlight-all { + background-color: #fffce7; + } + +.dt-scrollable__no-data { + text-align: center; + padding: 16px; + padding: 1rem; + border-left: 1px solid #d1d8dd; + border-right: 1px solid #d1d8dd; + } + +.dt-row--highlight { + background-color: #fffce7; + } + +.dt-row--unhighlight { + background-color: #fff; + } + +.dt-row--hide { + display: none; + } + +.dt-cell { + border: 1px solid #d1d8dd; + position: relative; + outline: none; + padding: 0; +} + +.dt-cell__content { + padding: 8px; + padding: 0.5rem; + border: 2px solid transparent; + height: 100%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + +.dt-cell__edit { + display: none; + padding: 8px; + padding: 0.5rem; + background-color: #fff; + border: 2px solid rgb(255, 160, 10); + z-index: 1; + height: 100%; + } + +.dt-cell__resize-handle { + opacity: 0; + position: absolute; + right: -3px; + top: 0; + width: 5px; + height: 100%; + cursor: col-resize; + z-index: 1; + } + +.dt-cell--editing .dt-cell__content { + display: none; + } + +.dt-cell--editing .dt-cell__edit { + display: block; + } + +.dt-cell--focus .dt-cell__content { + border-color: rgb(82, 146, 247); + } + +.dt-cell--highlight { background-color: #f5f7fa; } -.body-scrollable .no-data td { - text-align: center; - padding: 8px; - padding: 0.5rem; +.dt-cell--dragging { + background-color: #f5f7fa; } -.data-table-header { +.dt-cell--header .dt-cell__content { + padding-right: 16px; + padding-right: 1rem; + font-weight: bold; + } + +.dt-cell--header:hover .dt-dropdown__toggle { + opacity: 1; + } + +.dt-cell--tree-close .dt-tree-node__toggle:before { + content: '►'; + } + +.dt-dropdown { position: absolute; - top: 0; - left: 0; - background-color: white; - font-weight: bold; + right: 10px; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: top; + text-align: left; + font-weight: normal; + cursor: pointer; } -.data-table-header .content span:not(.column-resizer) { - cursor: pointer; +.dt-dropdown__toggle { + opacity: 0; } -.data-table-header .column-resizer { +.dt-dropdown__list { 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; @@ -172,118 +153,105 @@ top: 100%; right: 0; z-index: 1; - background-color: white; + background-color: #fff; border-radius: 3px; + padding: 8px 0; + padding: 0.5rem 0; -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; +.dt-dropdown__list-item { + padding: 8px 16px; + padding: 0.5rem 1rem; + } + +.dt-dropdown__list-item:hover { + background-color: #f5f7fa; } -.data-table-header .data-table-dropdown-list> div:hover { - background-color: #f5f7fa; - } - -.data-table-header .data-table-cell.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-cell.sortable-chosen { - background-color: #f5f7fa; - } - -.data-table-cell { - position: relative; -} - -.data-table-cell .content { - padding: 8px; - padding: 0.5rem; - border: 2px solid transparent; - height: 100%; - } - -.data-table-cell .content.ellipsis { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - -.data-table-cell .edit-cell { - display: none; - padding: 8px; - padding: 0.5rem; - background-color: #fff; - z-index: 1; - height: 100%; - } - -.data-table-cell.selected .content { - border: 2px solid rgb(82, 146, 247); - } - -.data-table-cell.editing .content { - display: none; - } - -.data-table-cell.editing .edit-cell { - border: 2px solid rgb(255, 160, 10); - display: block; - } - -.data-table-cell.highlight { - background-color: #f5f7fa; - } - -.data-table-cell:hover .column-resizer { - display: inline-block; - } - -.data-table-cell:hover .data-table-dropdown-toggle { +.dt-dropdown--active .dt-dropdown__list { display: block; } -.data-table-cell .tree-node { - display: inline-block; - position: relative; - } +.dt-tree-node { + display: inline-block; + position: relative; +} -.data-table-cell .toggle { +.dt-tree-node__toggle { display: inline-block; position: absolute; + font-size: 10px; padding: 0 4px; cursor: pointer; } -.data-table-cell .toggle:before { +.dt-tree-node__toggle:before { content: '▼'; } -.data-table-cell.tree-close .toggle:before { - content: '►'; +.dt-toast { + position: absolute; + bottom: 16px; + bottom: 1rem; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); +} + +.dt-toast__message { + display: inline-block; + background-color: rgba(0, 0, 0, .8); + color: #dfe2e5; + border-radius: 3px; + padding: 8px 16px; + padding: 0.5rem 1rem; } -.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; +.dt-input { + outline: none; + width: 100%; + border: none; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; + margin: 0; + padding: 0; } -body.data-table-resize { +.dt-freeze { + 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; +} + +.dt-freeze__message { + position: absolute; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + } + +.dt-paste-target { + position: fixed; + left: -999em; +} + +body.dt-resize { cursor: col-resize; } diff --git a/docs/assets/js/frappe-datatable.js b/docs/assets/js/frappe-datatable.js index c0a4056..5f1ec6f 100644 --- a/docs/assets/js/frappe-datatable.js +++ b/docs/assets/js/frappe-datatable.js @@ -1,2638 +1,2258 @@ var DataTable = (function (Sortable,Clusterize) { -'use strict'; + 'use strict'; -Sortable = Sortable && Sortable.hasOwnProperty('default') ? Sortable['default'] : Sortable; -Clusterize = Clusterize && Clusterize.hasOwnProperty('default') ? Clusterize['default'] : Clusterize; + 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); - } + function $(expr, con) { + return typeof expr === 'string' ? + (con || document).querySelector(expr) : + expr || null; } - 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; - }); -}; - -$.scrollbarWidth = function scrollbarWidth() { - // Create the measurement node - const scrollDiv = document.createElement('div'); - $.style(scrollDiv, { - width: '100px', - height: '100px', - overflow: 'scroll', - position: 'absolute', - top: '-9999px' - }); - document.body.appendChild(scrollDiv); - - // Get the scrollbar width - const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - - // Delete the DIV - document.body.removeChild(scrollDiv); - - return scrollbarWidth; -}; - -/** - * 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 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 nextTick(fn, context = null) { - return (...args) => { - return new Promise(resolve => { - const execute = () => { - const out = fn.apply(context, args); - resolve(out); - }; - - if (window.setImmediate) { - setImmediate(execute); - } else if (window.requestAnimationFrame) { - requestAnimationFrame(execute); + $.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 { - setTimeout(execute); + 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); } }); }; -} -function linkProperties(target, source, properties) { - const props = properties.reduce((acc, prop) => { - acc[prop] = { - get() { - return source[prop]; + $.unbind = (element, o) => { + if (element) { + for (let event in o) { + let callback = o[event]; + + event.split(/\s+/).forEach(function (event) { + element.removeEventListener(event, callback); + }); } - }; - return acc; - }, {}); - Object.defineProperties(target, props); -} - -function isSet(val) { - return val !== undefined || val !== null; -} - -function notSet(val) { - return !isSet(val); -} - -function isNumber(val) { - return !isNaN(val); -} - -function ensureArray(val) { - if (!Array.isArray(val)) { - return [val]; - } - return val; -} - -class DataManager { - constructor(options) { - this.options = options; - this.sortRows = nextTick(this.sortRows, this); - this.switchColumn = nextTick(this.switchColumn, this); - this.removeColumn = nextTick(this.removeColumn, this); - this.filterRows = nextTick(this.filterRows, this); - } - - init(data, columns) { - if (!data) { - data = this.options.data; } - if (columns) { - this.options.columns = columns; + }; + + $.fire = (target, type, properties) => { + let evt = document.createEvent('HTMLEvents'); + + evt.initEvent(type, true, true); + + for (let j in properties) { + evt[j] = properties[j]; } - this.data = data; + return target.dispatchEvent(evt); + }; - this.rowCount = 0; - this.columns = []; - this.rows = []; - - this.prepareColumns(); - this.prepareRows(); - this.prepareTreeRows(); - this.prepareRowView(); - - 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.checkboxColumn && !this.hasColumnById('_checkbox')) { - const cell = { - id: '_checkbox', - content: this.getCheckboxHTML(), - editable: false, - resizable: false, - sortable: false, - focusable: false, - dropdown: false, - width: 32 - }; - this.columns.push(cell); + $.data = (element, attrs) => { // eslint-disable-line + if (!attrs) { + return element.dataset; } - if (this.options.serialNoColumn && !this.hasColumnById('_rowIndex')) { - let cell = { - id: '_rowIndex', - content: '', - align: 'center', - editable: false, - resizable: false, - focusable: false, - dropdown: false - }; - - this.columns.push(cell); + for (const attr in attrs) { + element.dataset[attr] = attrs[attr]; } - } + }; - 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 + ''; - } - }; + $.style = (elements, styleMap) => { // eslint-disable-line - this.columns = columns - .map((cell, i) => this.prepareCell(cell, i)) - .map(col => Object.assign({}, baseCell, col)) - .map(col => { - col.content = col.content || col.name || ''; - col.id = col.id || col.content; - return col; - }); - } - - prepareCell(content, i) { - const cell = { - content: '', - 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; + if (typeof styleMap === 'string') { + return $.getStyle(elements, styleMap); } - return cell; - } + if (!Array.isArray(elements)) { + elements = [elements]; + } - 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 = []; - let meta = { - rowIndex: index - }; - - if (Array.isArray(d)) { - // row is an array - if (this.options.checkboxColumn) { - row.push(this.getCheckboxHTML()); - } - if (this.options.serialNoColumn) { - 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]); - } - } - - meta.indent = d.indent || 0; - } - - return this.prepareRow(row, meta); - }); - } - - prepareTreeRows() { - this.rows.forEach((row, i) => { - if (isNumber(row.meta.indent)) { - // if (i === 36) debugger; - const nextRow = this.getRow(i + 1); - row.meta.isLeaf = !nextRow || - notSet(nextRow.meta.indent) || - nextRow.meta.indent <= row.meta.indent; + elements.map(element => { + for (const prop in styleMap) { + element.style[prop] = styleMap[prop]; } }); - } + }; - prepareRowView() { - // This is order in which rows will be rendered in the table. - // When sorting happens, only this.rowViewOrder will change - // and not the original this.rows - this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); - } - - prepareRow(row, meta) { - const baseRowCell = { - rowIndex: meta.rowIndex, - indent: meta.indent - }; - - row = row - .map((cell, i) => this.prepareCell(cell, i)) - .map(cell => Object.assign({}, baseRowCell, cell)); - - // monkey patched in array object - row.meta = meta; - return row; - } - - validateColumns() { - const columns = this.options.columns; - if (!Array.isArray(columns)) { - throw new DataError('`columns` must be an array'); + $.removeStyle = (elements, styleProps) => { + if (!Array.isArray(elements)) { + elements = [elements]; } - columns.forEach((column, i) => { - if (typeof column !== 'string' && typeof column !== 'object') { - throw new DataError(`column "${i}" must be a string or an object`); + if (!Array.isArray(styleProps)) { + styleProps = [styleProps]; + } + + elements.map(element => { + for (const prop of styleProps) { + element.style[prop] = ''; } }); - } + }; - 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'); - } + $.getStyle = (element, prop) => { + let val = getComputedStyle(element)[prop]; - appendRows(rows) { - this.validateData(rows); - - this.rows.push(...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.rowViewOrder); - this.currentSort.sortOrder = sortOrder; - return; - } - } - - this.rowViewOrder.sort((a, b) => { - const aIndex = a; - const bIndex = b; - const aContent = this.getCell(colIndex, a).content; - const bContent = this.getCell(colIndex, b).content; - - if (sortOrder === 'none') { - return aIndex - bIndex; - } else if (sortOrder === 'asc') { - if (aContent < bContent) return -1; - if (aContent > bContent) return 1; - if (aContent === bContent) return 0; - } else if (sortOrder === 'desc') { - if (aContent < bContent) return 1; - if (aContent > bContent) return -1; - if (aContent === bContent) return 0; - } - return 0; - }); - - if (this.hasColumnById('_rowIndex')) { - // update row index - const srNoColIndex = this.getColumnIndexById('_rowIndex'); - this.rows.forEach((row, index) => { - const viewIndex = this.rowViewOrder.indexOf(index); - const cell = row[srNoColIndex]; - cell.content = (viewIndex + 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.forEach(row => { - const newCell1 = Object.assign({}, row[index1], { - colIndex: index2 - }); - const newCell2 = Object.assign({}, row[index2], { - colIndex: index1 - }); - - row[index2] = newCell1; - row[index1] = newCell2; - }); - } - - 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.forEach(row => { - // remove cell - row.splice(index, 1); - // update colIndex - row.forEach((cell, i) => { - cell.colIndex = i; - }); - }); - } - - 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 = String(cell.content || '').toLowerCase(); - const needle = (keyword || '').toLowerCase(); - - if (!needle || hay.includes(needle)) { - rowsToShow.push(cell.rowIndex); - } else { - rowsToHide.push(cell.rowIndex); - } - }); - - this._filteredRows = rowsToShow; - - return { - rowsToHide, - rowsToShow - }; - } - - getFilteredRowIndices() { - return this._filteredRows || this.rows.map(row => row.meta.rowIndex); - } - - getRowCount() { - return this.rowCount; - } - - _getNextRowCount() { - const val = this.rowCount; - - this.rowCount++; - return val; - } - - getRows(start, end) { - return this.rows.slice(start, end); - } - - getRowsForView(start, end) { - const rows = this.rowViewOrder.map(i => this.rows[i]); - return rows.slice(start, end); - } - - getColumns(skipStandardColumns) { - let columns = this.columns; - - if (skipStandardColumns) { - columns = columns.slice(this.getStandardColumnCount()); - } - - return columns; - } - - getStandardColumnCount() { - if (this.options.checkboxColumn && this.options.serialNoColumn) { - return 2; - } - - if (this.options.checkboxColumn || this.options.serialNoColumn) { - return 1; - } - - return 0; - } - - getColumnCount(skipStandardColumns) { - let val = this.columns.length; - - if (skipStandardColumns) { - val = val - this.getStandardColumnCount(); + if (['width', 'height'].includes(prop)) { + val = parseFloat(val); } return val; - } + }; - getColumn(colIndex) { - colIndex = +colIndex; + $.closest = (selector, element) => { + if (!element) return null; - if (colIndex < 0) { - // negative indexes - colIndex = this.columns.length + colIndex; + if (element.matches(selector)) { + return element; } - return this.columns.find(col => col.colIndex === colIndex); - } + return $.closest(selector, element.parentNode); + }; - getColumnById(id) { - return this.columns.find(col => col.id === id); - } + $.inViewport = (el, parentEl) => { + const { + top, + left, + bottom, + right + } = el.getBoundingClientRect(); + const { + top: pTop, + left: pLeft, + bottom: pBottom, + right: pRight + } = parentEl.getBoundingClientRect(); - getRow(rowIndex) { - rowIndex = +rowIndex; - return this.rows[rowIndex]; - } + return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight; + }; - getCell(colIndex, rowIndex) { - rowIndex = +rowIndex; - colIndex = +colIndex; - return this.getRow(rowIndex)[colIndex]; - } + $.scrollTop = function scrollTop(element, pixels) { + requestAnimationFrame(() => { + element.scrollTop = pixels; + }); + }; - getChildren(parentRowIndex) { - parentRowIndex = +parentRowIndex; - const parentIndent = this.getRow(parentRowIndex).meta.indent; - const out = []; + $.scrollbarWidth = function scrollbarWidth() { + // Create the measurement node + const scrollDiv = document.createElement('div'); + $.style(scrollDiv, { + width: '100px', + height: '100px', + overflow: 'scroll', + position: 'absolute', + top: '-9999px' + }); + document.body.appendChild(scrollDiv); - for (let i = parentRowIndex + 1; i < this.rowCount; i++) { - const row = this.getRow(i); - if (isNaN(row.meta.indent)) continue; + // Get the scrollbar width + const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; - if (row.meta.indent > parentIndent) { - out.push(i); - } + // Delete the DIV + document.body.removeChild(scrollDiv); - if (row.meta.indent === parentIndent) { - break; - } - } - - return out; - } - - getImmediateChildren(parentRowIndex) { - parentRowIndex = +parentRowIndex; - const parentIndent = this.getRow(parentRowIndex).meta.indent; - const out = []; - const childIndent = parentIndent + 1; - - for (let i = parentRowIndex + 1; i < this.rowCount; i++) { - const row = this.getRow(i); - if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; - - if (row.meta.indent === childIndent) { - out.push(i); - } - - if (row.meta.indent === parentIndent) { - break; - } - } - - return out; - } - - get() { - return { - columns: this.columns, - rows: this.rows - }; - } + return scrollbarWidth; + }; /** - * Returns the original data which was passed - * based on rowIndex - * @param {Number} rowIndex - * @returns Array|Object - * @memberof DataManager + * 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 */ - getData(rowIndex) { - return this.data[rowIndex]; + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); } - hasColumn(name) { - return Boolean(this.columns.find(col => col.content === name)); - } + var isObject_1 = isObject; - hasColumnById(id) { - return Boolean(this.columns.find(col => col.id === id)); - } + var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - getColumnIndex(name) { - return this.columns.findIndex(col => col.content === name); - } + /** Detect free variable `global` from Node.js. */ + var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; - getColumnIndexById(id) { - return this.columns.findIndex(col => col.id === id); - } + var _freeGlobal = freeGlobal; - getCheckboxHTML() { - return ''; - } -} + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; -// Custom Errors -class DataError extends TypeError {} + /** Used as a reference to the global object. */ + var root = _freeGlobal || freeSelf || Function('return this')(); -class ColumnManager { - constructor(instance) { - this.instance = instance; + var _root = root; - linkProperties(this, this.instance, [ - 'options', - 'fireEvent', - 'header', - 'datamanager', - 'style', - 'wrapper', - 'rowmanager', - 'bodyScrollable' - ]); + /** + * 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(); + }; - this.bindEvents(); - getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton); - } + var now_1 = now; - renderHeader() { - this.header.innerHTML = ''; - this.refreshHeader(); - } + /** Built-in value references. */ + var Symbol = _root.Symbol; - refreshHeader() { - const columns = this.datamanager.getColumns(); - const $cols = $.each('.data-table-cell[data-is-header]', this.header); + var _Symbol = Symbol; - const refreshHTML = - // first init - !$('.data-table-cell', this.header) || - // deleted column - columns.length < $cols.length; + /** Used for built-in method references. */ + var objectProto = Object.prototype; - if (refreshHTML) { - // refresh html - $('thead', this.header).innerHTML = this.getHeaderHTML(columns); + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; - this.$filterRow = $('.data-table-row[data-is-filter]', this.header); - if (this.$filterRow) { - $.style(this.$filterRow, { display: 'none' }); - } + /** + * 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; + } catch (e) {} + + var result = nativeObjectToString.call(value); + { + if (isOwn) { + value[symToStringTag] = tag; } else { - // update data-attributes - $cols.map(($col, i) => { - const column = columns[i]; - // column sorted or order changed - // update colIndex of each header cell - $.data($col, { - colIndex: column.colIndex - }); + delete value[symToStringTag]; + } + } + return result; + } - // refresh sort indicator - const sortIndicator = $('.sort-indicator', $col); - if (sortIndicator) { - sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder]; + 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]', + 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, + 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 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$1 = debounce_1; + + function nextTick(fn, context = null) { + return (...args) => { + return new Promise(resolve => { + const execute = () => { + const out = fn.apply(context, args); + resolve(out); + }; + setTimeout(execute); + }); + }; + } + function linkProperties(target, source, properties) { + const props = properties.reduce((acc, prop) => { + acc[prop] = { + get() { + return source[prop]; + } + }; + return acc; + }, {}); + Object.defineProperties(target, props); + } + function isSet(val) { + return val !== undefined || val !== null; + } + + function notSet(val) { + return !isSet(val); + } + + function isNumber(val) { + return !isNaN(val); + } + + function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; + } + + class DataManager { + constructor(options) { + this.options = options; + this.sortRows = nextTick(this.sortRows, this); + this.switchColumn = nextTick(this.switchColumn, this); + this.removeColumn = nextTick(this.removeColumn, this); + this.filterRows = nextTick(this.filterRows, this); + } + + init(data, columns) { + if (!data) { + data = this.options.data; + } + if (columns) { + this.options.columns = columns; + } + + this.data = data; + + this.rowCount = 0; + this.columns = []; + this.rows = []; + + this.prepareColumns(); + this.prepareRows(); + this.prepareTreeRows(); + this.prepareRowView(); + + 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.checkboxColumn && !this.hasColumnById('_checkbox')) { + const cell = { + id: '_checkbox', + content: this.getCheckboxHTML(), + editable: false, + resizable: false, + sortable: false, + focusable: false, + dropdown: false, + width: 32 + }; + this.columns.push(cell); + } + + if (this.options.serialNoColumn && !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.content = col.content || col.name || ''; + col.id = col.id || col.content; + return col; + }); + } + + prepareCell(content, i) { + const cell = { + content: '', + 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 = []; + let meta = { + rowIndex: index + }; + + if (Array.isArray(d)) { + // row is an array + if (this.options.checkboxColumn) { + row.push(this.getCheckboxHTML()); + } + if (this.options.serialNoColumn) { + 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]); + } + } + + meta.indent = d.indent || 0; + } + + return this.prepareRow(row, meta); + }); + } + + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; } }); } - // reset columnMap - this.$columnMap = []; - } - getHeaderHTML(columns) { - let html = this.rowmanager.getRowHTML(columns, { - isHeader: 1 - }); - if (this.options.inlineFilters) { - html += this.rowmanager.getRowHTML(columns, { - isFilter: 1 + prepareRowView() { + // This is order in which rows will be rendered in the table. + // When sorting happens, only this.rowViewOrder will change + // and not the original this.rows + this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); + } + + prepareRow(row, meta) { + const baseRowCell = { + rowIndex: meta.rowIndex, + indent: meta.indent + }; + + row = row + .map((cell, i) => this.prepareCell(cell, i)) + .map(cell => Object.assign({}, baseRowCell, cell)); + + // monkey patched in array object + row.meta = meta; + 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`); + } }); } - return html; - } - 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(); + validateData(data) { + if (Array.isArray(data) && + (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) { + return true; } - }); - - $.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-cell', $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; + throw new DataError('`data` must be an array of arrays or objects'); } - } - bindResizeColumn() { - let isDragging = false; - let $resizingCell, startWidth, startX; + appendRows(rows) { + this.validateData(rows); - $.on(this.header, 'mousedown', '.data-table-cell .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); + this.rows.push(...this.prepareRows(rows)); + } - if (col && col.resizable === false) { - return; + 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.rowViewOrder); + this.currentSort.sortOrder = sortOrder; + return; + } } - isDragging = true; - startWidth = $.style($('.content', $resizingCell), 'width'); - startX = e.pageX; - }); + this.rowViewOrder.sort((a, b) => { + const aIndex = a; + const bIndex = b; + const aContent = this.getCell(colIndex, a).content; + const bContent = this.getCell(colIndex, b).content; - $.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 + if (sortOrder === 'none') { + return aIndex - bIndex; + } else if (sortOrder === 'asc') { + if (aContent < bContent) return -1; + if (aContent > bContent) return 1; + if (aContent === bContent) return 0; + } else if (sortOrder === 'desc') { + if (aContent < bContent) return 1; + if (aContent > bContent) return -1; + if (aContent === bContent) return 0; + } + return 0; }); - this.setColumnHeaderWidth(colIndex); - }); - } - bindMoveColumn() { - let initialized; - - const initialize = () => { - if (initialized) { - $.off(document.body, 'mousemove', initialize); - return; + if (this.hasColumnById('_rowIndex')) { + // update row index + const srNoColIndex = this.getColumnIndexById('_rowIndex'); + this.rows.forEach((row, index) => { + const viewIndex = this.rowViewOrder.indexOf(index); + const cell = row[srNoColIndex]; + cell.content = (viewIndex + 1) + ''; + }); } - const ready = $('.data-table-cell', this.header); - if (!ready) return; + } - const $parent = $('.data-table-row', this.header); + reverseArray(array) { + let left = null; + let right = null; + let length = array.length; - this.sortable = Sortable.create($parent, { - onEnd: (e) => { - const { - oldIndex, - newIndex - } = e; - const $draggedCell = e.item; - const { - colIndex - } = $.data($draggedCell); - if (+colIndex === newIndex) return; + for (left = 0, right = length - 1; left < right; left += 1, right -= 1) { + const temporary = array[left]; - this.switchColumn(oldIndex, newIndex); - }, - preventOnFilter: false, - filter: '.column-resizer, .data-table-dropdown', - animation: 150 + 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.forEach(row => { + const newCell1 = Object.assign({}, row[index1], { + colIndex: index2 + }); + const newCell2 = Object.assign({}, row[index2], { + colIndex: index1 + }); + + row[index2] = newCell1; + row[index1] = newCell2; }); - }; + } - $.on(document.body, 'mousemove', initialize); - } + 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); - bindSortColumn() { - - $.on(this.header, 'click', '.data-table-cell .column-title', (e, span) => { - const $cell = span.closest('.data-table-cell'); - let { - colIndex, - sortOrder = 'none' - } = $.data($cell); - const col = this.getColumn(colIndex); - - if (col && col.sortable === false) { - return; - } - - // reset sort indicator - $('.sort-indicator', this.header).textContent = ''; - $.each('.data-table-cell', this.header).map($cell => { - $.data($cell, { - sortOrder: 'none' + // update rows + this.rows.forEach(row => { + // remove cell + row.splice(index, 1); + // update colIndex + row.forEach((cell, i) => { + cell.colIndex = i; }); }); + } - let nextSortOrder, textContent; - if (sortOrder === 'none') { - nextSortOrder = 'asc'; - textContent = '▲'; - } else if (sortOrder === 'asc') { - nextSortOrder = 'desc'; - textContent = '▼'; - } else if (sortOrder === 'desc') { - nextSortOrder = 'none'; - textContent = ''; + 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); + } } - $.data($cell, { - sortOrder: nextSortOrder - }); - $('.sort-indicator', $cell).textContent = textContent; + const _row = this.prepareRow(row, {rowIndex}); + const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex); + this.rows[index] = _row; - 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(flag) { - let showFilter; - if (flag === undefined) { - showFilter = !this.isFilterShown; - } else { - showFilter = flag; + return _row; } - if (showFilter) { - $.style(this.$filterRow, { display: '' }); - } else { - $.style(this.$filterRow, { display: 'none' }); + 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; } - this.isFilterShown = showFilter; - this.style.setBodyStyle(); - } + 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; + } - focusFilter(colIndex) { - if (!this.isFilterShown) return; + filterRows(keyword, colIndex) { + let rowsToHide = []; + let rowsToShow = []; + const cells = this.rows.map(row => row[colIndex]); - const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow); - $filterInput.focus(); - } + cells.forEach(cell => { + const hay = String(cell.content || '').toLowerCase(); + const needle = (keyword || '').toLowerCase(); - bindFilter() { - if (!this.options.inlineFilters) return; - const handler = e => { - const $filterCell = $.closest('.data-table-cell', e.target); - const { - colIndex - } = $.data($filterCell); - const keyword = e.target.value; + if (!needle || hay.includes(needle)) { + rowsToShow.push(cell.rowIndex); + } else { + rowsToHide.push(cell.rowIndex); + } + }); - this.datamanager.filterRows(keyword, colIndex) - .then(({ - rowsToHide, - rowsToShow - }) => { - this.rowmanager.hideRows(rowsToHide); - this.rowmanager.showRows(rowsToShow); - }); - }; - $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300)); - } + this._filteredRows = rowsToShow; - sortRows(colIndex, sortOrder) { - return this.datamanager.sortRows(colIndex, sortOrder); - } + return { + rowsToHide, + rowsToShow + }; + } - getColumn(colIndex) { - return this.datamanager.getColumn(colIndex); - } + getFilteredRowIndices() { + return this._filteredRows || this.rows.map(row => row.meta.rowIndex); + } - getColumns() { - return this.datamanager.getColumns(); - } + getRowCount() { + return this.rowCount; + } - setColumnWidth(colIndex, width) { - colIndex = +colIndex; - this._columnWidthMap = this._columnWidthMap || []; + _getNextRowCount() { + const val = this.rowCount; - let columnWidth = width || this.getColumn(colIndex).width; + this.rowCount++; + return val; + } - let index = this._columnWidthMap[colIndex]; - const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`; - const styles = { - width: columnWidth + 'px' - }; + getRows(start, end) { + return this.rows.slice(start, end); + } - index = this.style.setStyle(selector, styles, index); + getRowsForView(start, end) { + const rows = this.rowViewOrder.map(i => this.rows[i]); + return rows.slice(start, end); + } - if (index !== undefined) { - this._columnWidthMap[colIndex] = index; + getColumns(skipStandardColumns) { + let columns = this.columns; + + if (skipStandardColumns) { + columns = columns.slice(this.getStandardColumnCount()); + } + + return columns; + } + + getStandardColumnCount() { + if (this.options.checkboxColumn && this.options.serialNoColumn) { + return 2; + } + + if (this.options.checkboxColumn || this.options.serialNoColumn) { + return 1; + } + + return 0; + } + + getColumnCount(skipStandardColumns) { + let val = this.columns.length; + + if (skipStandardColumns) { + val = val - this.getStandardColumnCount(); + } + + return val; + } + + getColumn(colIndex) { + colIndex = +colIndex; + + if (colIndex < 0) { + // negative indexes + colIndex = this.columns.length + colIndex; + } + + return this.columns.find(col => col.colIndex === colIndex); + } + + getColumnById(id) { + return this.columns.find(col => col.id === id); + } + + getRow(rowIndex) { + rowIndex = +rowIndex; + return this.rows[rowIndex]; + } + + getCell(colIndex, rowIndex) { + rowIndex = +rowIndex; + colIndex = +colIndex; + return this.getRow(rowIndex)[colIndex]; + } + + getChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; + + if (row.meta.indent > parentIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + get() { + return { + columns: this.columns, + rows: this.rows + }; + } + + /** + * Returns the original data which was passed + * based on rowIndex + * @param {Number} rowIndex + * @returns Array|Object + * @memberof DataManager + */ + getData(rowIndex) { + return this.data[rowIndex]; + } + + 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 ''; } } - setColumnHeaderWidth(colIndex) { - colIndex = +colIndex; - this.$columnMap = this.$columnMap || []; - const selector = `.data-table-header [data-col-index="${colIndex}"] .content`; - const { - width - } = this.getColumn(colIndex); + // Custom Errors + class DataError extends TypeError {} - let $column = this.$columnMap[colIndex]; - if (!$column) { - $column = this.header.querySelector(selector); - this.$columnMap[colIndex] = $column; + class CellManager { + constructor(instance) { + this.instance = instance; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); + + this.bindEvents(); } - $column.style.width = width + 'px'; - } + bindEvents() { + this.bindFocusCell(); + this.bindEditCell(); + this.bindKeyboardSelection(); + this.bindCopyCellContents(); + this.bindMouseEvents(); + this.bindTreeEvents(); + } - getColumnMinWidth(colIndex) { - colIndex = +colIndex; - return this.getColumn(colIndex).minWidth || 24; - } + bindFocusCell() { + this.bindKeyboardNav(); + } - getFirstColumnIndex() { - return this.datamanager.getColumnIndexById('_rowIndex') + 1; - } + bindEditCell() { + this.$editingCell = null; - getHeaderCell$(colIndex) { - return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); - } + $.on(this.bodyScrollable, 'dblclick', '.dt-cell', (e, cell) => { + this.activateEditing(cell); + }); - getLastColumnIndex() { - return this.datamanager.getColumnCount() - 1; - } + this.keyboard.on('enter', () => { + 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(); + } + }); + } - getSerialColumnIndex() { - const columns = this.datamanager.getColumns(); + bindKeyboardNav() { + const focusCell = (direction) => { + if (!this.$focusedCell || this.$editingCell) { + return false; + } - return columns.findIndex(column => column.content.includes('Sr. No')); - } -} + let $cell = this.$focusedCell; -// eslint-disable-next-line -var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') { - // add dropdown buttons - const dropdownItems = this.options.headerDropdown; + 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); + } - return `
${dropdownButton}
-
- ${dropdownItems.map((d, i) => `
${d.label}
`).join('')} -
- `; -}; + this.focusCell($cell); + return true; + }; -class CellManager { - constructor(instance) { - this.instance = instance; - linkProperties(this, this.instance, [ - 'wrapper', - 'options', - 'style', - 'bodyScrollable', - 'columnmanager', - 'rowmanager', - 'datamanager', - 'keyboard' - ]); + const focusLastCell = (direction) => { + if (!this.$focusedCell || this.$editingCell) { + return false; + } - this.bindEvents(); - } + let $cell = this.$focusedCell; + const { + rowIndex, + colIndex + } = $.data($cell); - bindEvents() { - this.bindFocusCell(); - this.bindEditCell(); - this.bindKeyboardSelection(); - this.bindCopyCellContents(); - this.bindMouseEvents(); - this.bindTreeEvents(); - } + 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); + } - bindFocusCell() { - this.bindKeyboardNav(); - } + this.focusCell($cell); + return true; + }; - bindEditCell() { - this.$editingCell = null; + ['left', 'right', 'up', 'down', 'tab', 'shift+tab'] + .map(direction => this.keyboard.on(direction, () => focusCell(direction))); - $.on(this.bodyScrollable, 'dblclick', '.data-table-cell', (e, cell) => { - this.activateEditing(cell); - }); + ['left', 'right', 'up', 'down'] + .map(direction => this.keyboard.on(`ctrl+${direction}`, () => focusLastCell(direction))); - 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.keyboard.on('esc', () => { this.deactivateEditing(); - } - }); - } + }); - bindKeyboardNav() { - const focusCell = (direction) => { - if (!this.$focusedCell || this.$editingCell) { - return false; + if (this.options.inlineFilters) { + this.keyboard.on('ctrl+f', (e) => { + const $cell = $.closest('.dt-cell', e.target); + const { 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', () => { + const noOfCellsCopied = this.copyCellContents(this.$focusedCell, this.$selectionCursor); + const message = `${noOfCellsCopied} cell${noOfCellsCopied > 1 ? 's' : ''} copied`; + if (noOfCellsCopied) { + this.instance.showToastMessage(message, 2); + } + }); + + if (this.options.pasteFromClipboard) { + this.keyboard.on('ctrl+v', (e) => { + // hack + // https://stackoverflow.com/a/2177059/5353542 + this.instance.pasteTarget.focus(); + + setTimeout(() => { + const data = this.instance.pasteTarget.value; + this.instance.pasteTarget.value = ''; + this.pasteContentInCell(data); + }, 10); + + return false; + }); + } + } + + bindMouseEvents() { + let mouseDown = null; + + $.on(this.bodyScrollable, 'mousedown', '.dt-cell', (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', '.dt-cell', throttle$1(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.dt-tree-node__toggle', (e, $toggle) => { + const $cell = $.closest('.dt-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('dt-cell--tree-close')) { + this.rowmanager.openSingleNode(rowIndex); + } else { + this.rowmanager.closeSingleNode(rowIndex); + } + }); + } + + 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; } - 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); + const column = this.columnmanager.getColumn(colIndex); + if (column.focusable === false) { + return; } - this.focusCell($cell); + this.scrollToCell($cell); + + this.deactivateEditing(); + if (!skipClearSelection) { + this.clearSelection(); + } + + if (this.$focusedCell) { + this.$focusedCell.classList.remove('dt-cell--focus'); + } + + this.$focusedCell = $cell; + $cell.classList.add('dt-cell--focus'); + + // so that keyboard nav works + $cell.focus(); + + this.highlightRowColumnHeader($cell); + } + + highlightRowColumnHeader($cell) { + const { + colIndex, + rowIndex + } = $.data($cell); + + const srNoColIndex = this.datamanager.getColumnIndexById('_rowIndex'); + const colHeaderSelector = `.dt-cell--header-${colIndex}`; + const rowHeaderSelector = `.dt-cell--${srNoColIndex}-${rowIndex}`; + + if (this.lastHeaders) { + this.lastHeaders.forEach(header => header.classList.remove('dt-cell--highlight')); + } + + const colHeader = $(colHeaderSelector, this.wrapper); + const rowHeader = $(rowHeaderSelector, this.wrapper); + + this.lastHeaders = [colHeader, rowHeader]; + this.lastHeaders.forEach(header => header.classList.add('dt-cell--highlight')); + } + + 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(); + this._selectedCells = cells.map(index => this.getCell$(...index)); + requestAnimationFrame(() => { + this._selectedCells.map($cell => $cell.classList.add('dt-cell--highlight')); + }); return true; - }; + } - const focusLastCell = (direction) => { - if (!this.$focusedCell || this.$editingCell) { + 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 $cell = this.$focusedCell; + const cells = []; + let colIndex = colIndex1; + let rowIndex = rowIndex1; + const rowIndices = []; + + while (rowIndex <= rowIndex2) { + rowIndices.push(rowIndex); + rowIndex += 1; + } + + rowIndices.map((rowIndex) => { + while (colIndex <= colIndex2) { + cells.push([colIndex, rowIndex]); + colIndex++; + } + colIndex = colIndex1; + }); + + return cells; + } + + clearSelection() { + (this._selectedCells || []) + .forEach($cell => $cell.classList.remove('dt-cell--highlight')); + + this._selectedCells = []; + this.$selectionCursor = null; + } + + getSelectionCursor() { + return this.$selectionCursor || this.$focusedCell; + } + + activateEditing($cell) { + this.focusCell($cell); 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.inlineFilters) { - this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.data-table-cell', 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-cell', (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-cell', throttle$1(selectArea, 50)); - } - - bindTreeEvents() { - $.on(this.bodyScrollable, 'click', '.toggle', (e, $toggle) => { - const $cell = $.closest('.data-table-cell', $toggle); - const { rowIndex } = $.data($cell); - - if ($cell.classList.contains('tree-close')) { - this.rowmanager.openSingleNode(rowIndex); - } else { - this.rowmanager.closeSingleNode(rowIndex); - } - }); - } - - 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-cell[data-col-index="${colIndex}"]`; - const rowHeaderSelector = `.data-table-cell[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-cell.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 + const col = this.columnmanager.getColumn(colIndex); + if (col && (col.editable === false || col.focusable === false)) { 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) { - const column = this.datamanager.getColumn(colIndex); - const row = this.datamanager.getRow(rowIndex); - const data = this.datamanager.getData(rowIndex); - let editor = this.options.getEditor ? - this.options.getEditor(colIndex, rowIndex, value, parent, column, row, data) : - this.getDefaultEditor(parent); - - if (editor === false) { - // explicitly returned false - return false; - } - if (editor === undefined) { - // didn't return editor, fallback to default - editor = this.getDefaultEditor(parent); - } - - return editor; - } - - getDefaultEditor(parent) { - 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; + const cell = this.getCell(colIndex, rowIndex); + if (cell && cell.editable === false) { + return; } - }; - } - submitEditing() { - if (!this.$editingCell) return; - const $cell = this.$editingCell; - const { - rowIndex, - colIndex - } = $.data($cell); - const col = this.datamanager.getColumn(colIndex); + if (this.$editingCell) { + const { + _rowIndex, + _colIndex + } = $.data(this.$editingCell); - 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); - }); + if (rowIndex === _rowIndex && colIndex === _colIndex) { + // editing the same cell + return; } } + + this.$editingCell = $cell; + $cell.classList.add('dt-cell--editing'); + + const $editCell = $('.dt-cell__edit', $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); + } } - this.currentCellEditor = null; - } + 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('dt-cell--editing'); + this.$editingCell = null; + } + + getEditor(colIndex, rowIndex, value, parent) { + const column = this.datamanager.getColumn(colIndex); + const row = this.datamanager.getRow(rowIndex); + const data = this.datamanager.getData(rowIndex); + let editor = this.options.getEditor ? + this.options.getEditor(colIndex, rowIndex, value, parent, column, row, data) : + this.getDefaultEditor(parent); + + if (editor === false) { + // explicitly returned false + return false; + } + if (editor === undefined) { + // didn't return editor, fallback to default + editor = this.getDefaultEditor(parent); + } + + return editor; + } + + getDefaultEditor(parent) { + const $input = $.create('input', { + class: 'dt-input', + 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 1; + } + const cells = this.getCellsInRange($cell1, $cell2); + + if (!cells) return 0; + + const rows = 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; + }, []); + + const values = rows + // join values by tab + .map(row => row.join('\t')) + // join rows by newline + .join('\n'); + + copyTextToClipboard(values); + + // return no of cells copied + return rows.reduce((total, row) => total + row.length, 0); + } + + pasteContentInCell(data) { + if (!this.$focusedCell) return; + + const matrix = data + .split('\n') + .map(row => row.split('\t')) + .filter(row => row.length && row.every(it => it)); + + let { colIndex, rowIndex } = $.data(this.$focusedCell); + + let focusedCell = { + colIndex: +colIndex, + rowIndex: +rowIndex + }; + + matrix.forEach((row, i) => { + let rowIndex = i + focusedCell.rowIndex; + row.forEach((cell, j) => { + let colIndex = j + focusedCell.colIndex; + this.updateCell(colIndex, rowIndex, cell); + }); + }); + } + + 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.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + $cell.innerHTML = this.getCellContent(cell); + } + + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('dt-cell--tree-close'); + } + } + + isStandardCell(colIndex) { + // Standard cells are in Sr. No and Checkbox column + return colIndex < this.columnmanager.getFirstColumnIndex(); + } + + getCell$(colIndex, rowIndex) { + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); + } + + getAboveCell$($cell) { + const { + colIndex + } = $.data($cell); + + let $aboveRow = $cell.parentElement.previousElementSibling; + while ($aboveRow && $aboveRow.classList.contains('dt-row--hide')) { + $aboveRow = $aboveRow.previousElementSibling; + } + + if (!$aboveRow) return $cell; + return $(`.dt-cell--col-${colIndex}`, $aboveRow); + } + + getBelowCell$($cell) { + const { + colIndex + } = $.data($cell); + + let $belowRow = $cell.parentElement.nextElementSibling; + while ($belowRow && $belowRow.classList.contains('dt-row--hide')) { + $belowRow = $belowRow.nextElementSibling; + } + + if (!$belowRow) return $cell; + return $(`.dt-cell--col-${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); + } + + getRowHeight() { + return $.style($('.dt-row', this.bodyScrollable), 'height'); + } + + scrollToCell($cell) { + if ($.inViewport($cell, this.bodyScrollable)) return false; - 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.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); - $cell.innerHTML = this.getCellContent(cell); - } - - toggleTreeButton(rowIndex, flag) { - const colIndex = this.columnmanager.getFirstColumnIndex(); - const $cell = this.getCell$(colIndex, rowIndex); - if ($cell) { - $cell.classList[flag ? 'remove' : 'add']('tree-close'); - } - } - - isStandardCell(colIndex) { - // Standard cells are in Sr. No and Checkbox column - return colIndex < this.columnmanager.getFirstColumnIndex(); - } - - getCell$(colIndex, rowIndex) { - return $(this.selector(colIndex, rowIndex), this.bodyScrollable); - } - - getAboveCell$($cell) { - const { - colIndex - } = $.data($cell); - - let $aboveRow = $cell.parentElement.previousElementSibling; - while ($aboveRow && $aboveRow.classList.contains('hide')) { - $aboveRow = $aboveRow.previousElementSibling; + } = $.data($cell); + this.rowmanager.scrollToRow(rowIndex); + return false; } - if (!$aboveRow) return $cell; - return $(`[data-col-index="${colIndex}"]`, $aboveRow); - } - - getBelowCell$($cell) { - const { - colIndex - } = $.data($cell); - - let $belowRow = $cell.parentElement.nextElementSibling; - while ($belowRow && $belowRow.classList.contains('hide')) { - $belowRow = $belowRow.nextElementSibling; + getRowCountPerPage() { + return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight()); } - if (!$belowRow) return $cell; - return $(`[data-col-index="${colIndex}"]`, $belowRow); - } + getCellHTML(cell) { + const { + rowIndex, + colIndex, + isHeader, + isFilter + } = cell; + const dataAttr = makeDataAttributeString({ + rowIndex, + colIndex, + isHeader, + isFilter + }); - getLeftCell$($cell) { - return $cell.previousElementSibling; - } + const isBodyCell = !(isHeader || isFilter); - getRightCell$($cell) { - return $cell.nextElementSibling; - } + const className = [ + 'dt-cell', + 'dt-cell--col-' + colIndex, + isBodyCell ? `dt-cell--${colIndex}-${rowIndex}` : '', + isBodyCell ? 'dt-cell--row-' + rowIndex : '', + isHeader ? 'dt-cell--header' : '', + isHeader ? `dt-cell--header-${colIndex}` : '', + isFilter ? 'dt-cell--filter' : '' + ].join(' '); - 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 ` - + return ` + ${this.getCellContent(cell)} `; - } - - getCellContent(cell) { - const { - isHeader, - isFilter - } = 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()}
` : ''; - - const customFormatter = cell.format || (cell.column && cell.column.format) || null; - - let contentHTML; - if (isHeader || isFilter || !customFormatter) { - contentHTML = cell.content; - } else { - const row = this.datamanager.getRow(cell.rowIndex); - const data = this.datamanager.getData(cell.rowIndex); - contentHTML = customFormatter(cell.content, row, cell.column, data); } - if (this.options.treeView && !(isHeader || isFilter) && cell.indent !== undefined) { - const nextRow = this.datamanager.getRow(cell.rowIndex + 1); - const addToggle = nextRow && nextRow.meta.indent > cell.indent; + getCellContent(cell) { + const { + isHeader, + isFilter, + colIndex + } = cell; - // Add toggle and indent in the first column - const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; - if (firstColumnIndex === cell.colIndex) { - const padding = ((cell.indent || 0) + 1) * 1.5; - const toggleHTML = addToggle ? `` : ''; - contentHTML = ` - ${toggleHTML}${contentHTML}`; + const editable = !isHeader && cell.editable !== false; + const editCellHTML = editable ? this.getEditCellHTML(colIndex) : ''; + + 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 ? this.columnmanager.getDropdownHTML() : ''; + + const customFormatter = cell.format || (cell.column && cell.column.format) || null; + + let contentHTML; + if (isHeader || isFilter || !customFormatter) { + contentHTML = cell.content; + } else { + const row = this.datamanager.getRow(cell.rowIndex); + const data = this.datamanager.getData(cell.rowIndex); + contentHTML = customFormatter(cell.content, row, cell.column, data); } - } - return ` -
+ if (this.options.treeView && !(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; + + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? + `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + + const className = [ + 'dt-cell__content', + isHeader ? `dt-cell__content--header-${colIndex}` : `dt-cell__content--col-${colIndex}` + ].join(' '); + + return ` +
${contentHTML} ${sortIndicator} ${resizeColumn} @@ -2640,1043 +2260,1433 @@ class CellManager {
${editCellHTML} `; + } + + getEditCellHTML(colIndex) { + return `
`; + } + + selector(colIndex, rowIndex) { + return `.dt-cell--${colIndex}-${rowIndex}`; + } } - getEditCellHTML() { - return ` -
- `; - } + class ColumnManager { + constructor(instance) { + this.instance = instance; - selector(colIndex, rowIndex) { - return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; - } -} + linkProperties(this, this.instance, [ + 'options', + 'fireEvent', + 'header', + 'datamanager', + 'style', + 'wrapper', + 'rowmanager', + 'bodyScrollable' + ]); -class RowManager { - constructor(instance) { - this.instance = instance; - linkProperties(this, this.instance, [ - 'options', - 'fireEvent', - 'wrapper', - 'bodyScrollable', - 'bodyRenderer' - ]); + this.bindEvents(); + } - this.bindEvents(); - this.refreshRows = nextTick(this.refreshRows, this); - } + renderHeader() { + this.header.innerHTML = ''; + this.refreshHeader(); + } - get datamanager() { - return this.instance.datamanager; - } + refreshHeader() { + const columns = this.datamanager.getColumns(); + const $cols = $.each('.dt-cell--header', this.header); - get cellmanager() { - return this.instance.cellmanager; - } + const refreshHTML = + // first init + !$('.dt-cell', this.header) || + // deleted column + columns.length < $cols.length; - bindEvents() { - this.bindCheckbox(); - } + if (refreshHTML) { + // refresh html + $('thead', this.header).innerHTML = this.getHeaderHTML(columns); - bindCheckbox() { - if (!this.options.checkboxColumn) return; + this.$filterRow = $('.dt-row[data-is-filter]', this.header); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } + } else { + // update data-attributes + $cols.map(($col, i) => { + const column = columns[i]; + // column sorted or order changed + // update colIndex of each header cell + $.data($col, { + colIndex: column.colIndex + }); - // map of checked rows - this.checkMap = []; + // refresh sort indicator + const sortIndicator = $('.sort-indicator', $col); + if (sortIndicator) { + sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder]; + } + }); + } + // reset columnMap + this.$columnMap = []; + } - $.on(this.wrapper, 'click', '.data-table-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { - const $cell = $checkbox.closest('.data-table-cell'); + getHeaderHTML(columns) { + let html = this.rowmanager.getRowHTML(columns, { + isHeader: 1 + }); + if (this.options.inlineFilters) { + html += this.rowmanager.getRowHTML(columns, { + isFilter: 1 + }); + } + return html; + } + + bindEvents() { + this.bindDropdown(); + this.bindResizeColumn(); + this.bindMoveColumn(); + this.bindFilter(); + } + + bindDropdown() { + let $activeDropdown; + let activeClass = 'dt-dropdown--active'; + let toggleClass = '.dt-dropdown__toggle'; + + $.on(this.header, 'click', toggleClass, (e, $button) => { + const $dropdown = $.closest('.dt-dropdown', $button); + + if (!$dropdown.classList.contains(activeClass)) { + deactivateDropdown(); + $dropdown.classList.add(activeClass); + $activeDropdown = $dropdown; + } else { + deactivateDropdown(); + } + }); + + $.on(document.body, 'click', (e) => { + if (e.target.matches(toggleClass)) return; + deactivateDropdown(); + }); + + const dropdownItems = this.options.headerDropdown; + + $.on(this.header, 'click', '.dt-dropdown__list-item', (e, $item) => { + const $col = $.closest('.dt-cell', $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(activeClass); + $activeDropdown = null; + } + } + + bindResizeColumn() { + let isDragging = false; + let $resizingCell, startWidth, startX; + + $.on(this.header, 'mousedown', '.dt-cell .dt-cell__resize-handle', (e, $handle) => { + document.body.classList.add('dt-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($('.dt-cell__content', $resizingCell), 'width'); + startX = e.pageX; + }); + + $.on(document.body, 'mouseup', (e) => { + document.body.classList.remove('dt-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 = $('.dt-cell', this.header); + if (!ready) return; + + const $parent = $('.dt-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: '.dt-cell__resize-handle, .dt-dropdown', + chosenClass: 'dt-cell--dragging', + animation: 150 + }); + }; + + $.on(document.body, 'mousemove', initialize); + } + + 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(flag) { + let showFilter; + if (flag === undefined) { + showFilter = !this.isFilterShown; + } else { + showFilter = flag; + } + + if (showFilter) { + $.style(this.$filterRow, { display: '' }); + } else { + $.style(this.$filterRow, { display: 'none' }); + } + + this.isFilterShown = showFilter; + this.style.setBodyStyle(); + } + + focusFilter(colIndex) { + if (!this.isFilterShown) return; + + const $filterInput = $(`[data-col-index="${colIndex}"] .dt-filter`, this.$filterRow); + $filterInput.focus(); + } + + bindFilter() { + if (!this.options.inlineFilters) return; + const handler = e => { + const $filterCell = $.closest('.dt-cell', e.target); + const { + colIndex + } = $.data($filterCell); + const keyword = e.target.value; + + this.datamanager.filterRows(keyword, colIndex) + .then(({ + rowsToHide, + rowsToShow + }) => { + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); + }); + }; + $.on(this.header, 'keydown', '.dt-filter', debounce$1(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, width) { + colIndex = +colIndex; + this._columnWidthMap = this._columnWidthMap || []; + + let columnWidth = width || this.getColumn(colIndex).width; + + let index = this._columnWidthMap[colIndex]; + const selector = [ + `.dt-cell__content--col-${colIndex}`, + `.dt-cell__edit--col-${colIndex}` + ].join(', '); + + const styles = { + width: columnWidth + 'px' + }; + + index = this.style.setStyle(selector, styles, index); + + if (index !== undefined) { + this._columnWidthMap[colIndex] = index; + } + } + + setColumnHeaderWidth(colIndex) { + colIndex = +colIndex; + this.$columnMap = this.$columnMap || []; + const selector = `.dt-cell__content--header-${colIndex}`; const { - rowIndex, - isHeader - } = $.data($cell); - const checked = $checkbox.checked; + width + } = this.getColumn(colIndex); - if (isHeader) { - this.checkAll(checked); - } else { - this.checkRow(rowIndex, checked); + let $column = this.$columnMap[colIndex]; + if (!$column) { + $column = this.header.querySelector(selector); + this.$columnMap[colIndex] = $column; } - }); - } - 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 []; + $column.style.width = width + 'px'; } - let out = []; - for (let rowIndex in this.checkMap) { - const checked = this.checkMap[rowIndex]; - if (checked === 1) { - out.push(rowIndex); - } + getColumnMinWidth(colIndex) { + colIndex = +colIndex; + return this.getColumn(colIndex).minWidth || 24; } - return out; + getFirstColumnIndex() { + return this.datamanager.getColumnIndexById('_rowIndex') + 1; + } + + getHeaderCell$(colIndex) { + return $(`.dt-cell--header-${colIndex}`, this.header); + } + + getLastColumnIndex() { + return this.datamanager.getColumnCount() - 1; + } + + getDropdownHTML() { + const { dropdownButton, headerDropdown: dropdownItems } = this.options; + + return ` +
+
${dropdownButton}
+
+ ${dropdownItems.map((d, i) => ` +
${d.label}
+ `).join('')} +
+
+ `; + } } - highlightCheckedRows() { - this.getCheckedRows() - .map(rowIndex => this.checkRow(rowIndex, true)); - } + class RowManager { + constructor(instance) { + this.instance = instance; + linkProperties(this, this.instance, [ + 'options', + 'fireEvent', + 'wrapper', + 'bodyScrollable', + 'bodyRenderer' + ]); - checkRow(rowIndex, toggle) { - const value = toggle ? 1 : 0; - const selector = rowIndex => - `.data-table-cell[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.showCheckStatus(); - this.fireEvent('onCheckRow', this.datamanager.getRow(rowIndex)); - } + this.bindEvents(); + this.refreshRows = nextTick(this.refreshRows, this); + } - checkAll(toggle) { - const value = toggle ? 1 : 0; + get datamanager() { + return this.instance.datamanager; + } - // update internal map - if (toggle) { - this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value); - } else { + get cellmanager() { + return this.instance.cellmanager; + } + + bindEvents() { + this.bindCheckbox(); + } + + bindCheckbox() { + if (!this.options.checkboxColumn) return; + + // map of checked rows this.checkMap = []; - } - // set checkbox value - $.each('.data-table-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) - .map(input => { - input.checked = toggle; + + $.on(this.wrapper, 'click', '.dt-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.dt-cell'); + const { + rowIndex, + isHeader + } = $.data($cell); + const checked = $checkbox.checked; + + if (isHeader) { + this.checkAll(checked); + } else { + this.checkRow(rowIndex, checked); + } }); - // highlight all - this.highlightAll(toggle); - this.showCheckStatus(); - } - - showCheckStatus() { - if (!this.options.checkedRowStatus) return; - const checkedRows = this.getCheckedRows(); - const count = checkedRows.length; - if (count > 0) { - this.bodyRenderer.showToastMessage(`${count} row${count > 1 ? 's' : ''} selected`); - } else { - this.bodyRenderer.clearToastMessage(); - } - } - - 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'); + refreshRows() { + this.instance.renderBody(); + this.instance.setDimensions(); } - this._highlightedRows = this._highlightedRows || {}; + refreshRow(row, rowIndex) { + const _row = this.datamanager.updateRow(row, rowIndex); - if (toggle) { - $row.classList.add('row-highlight'); - this._highlightedRows[rowIndex] = $row; - } else { - $row.classList.remove('row-highlight'); - delete this._highlightedRows[rowIndex]; + _row.forEach(cell => { + this.cellmanager.refreshCell(cell); + }); } - } - 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'); + getCheckedRows() { + if (!this.checkMap) { + return []; } - this._highlightedRows = {}; + + let out = []; + for (let rowIndex in this.checkMap) { + const checked = this.checkMap[rowIndex]; + if (checked === 1) { + out.push(rowIndex); + } + } + + return out; } - } - hideRows(rowIndices) { - rowIndices = ensureArray(rowIndices); - rowIndices.map(rowIndex => { - const $tr = this.getRow$(rowIndex); - $tr.classList.add('hide'); - }); - } + highlightCheckedRows() { + this.getCheckedRows() + .map(rowIndex => this.checkRow(rowIndex, true)); + } - showRows(rowIndices) { - rowIndices = ensureArray(rowIndices); - rowIndices.map(rowIndex => { - const $tr = this.getRow$(rowIndex); - $tr.classList.remove('hide'); - }); - } + checkRow(rowIndex, toggle) { + const value = toggle ? 1 : 0; + const selector = rowIndex => + `.dt-cell[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.showCheckStatus(); + this.fireEvent('onCheckRow', this.datamanager.getRow(rowIndex)); + } - openSingleNode(rowIndex) { - const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); - this.showRows(rowsToShow); - this.cellmanager.toggleTreeButton(rowIndex, true); - } + checkAll(toggle) { + const value = toggle ? 1 : 0; - closeSingleNode(rowIndex) { - const children = this.datamanager.getImmediateChildren(rowIndex); - children.forEach(childIndex => { - const row = this.datamanager.getRow(childIndex); - if (row.meta.isLeaf) { - // close - this.hideRows(childIndex); - this.cellmanager.toggleTreeButton(childIndex, false); + // update internal map + if (toggle) { + this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value); } else { - this.closeSingleNode(childIndex); - this.hideRows(childIndex); + this.checkMap = []; } - }); - this.cellmanager.toggleTreeButton(rowIndex, false); - } - - getRow$(rowIndex) { - return $(this.selector(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); + // set checkbox value + $.each('.dt-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + .map(input => { + input.checked = toggle; + }); + // highlight all + this.highlightAll(toggle); + this.showCheckStatus(); } - 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 - }))); + showCheckStatus() { + if (!this.options.checkedRowStatus) return; + const checkedRows = this.getCheckedRows(); + const count = checkedRows.length; + if (count > 0) { + this.bodyRenderer.showToastMessage(`${count} row${count > 1 ? 's' : ''} selected`); + } else { + this.bodyRenderer.clearToastMessage(); + } } - return ` - + highlightRow(rowIndex, toggle = true) { + const $row = this.getRow$(rowIndex); + if (!$row) return; + + if (!toggle && this.bodyScrollable.classList.contains('dt-scrollable--highlight-all')) { + $row.classList.add('dt-row--unhighlight'); + return; + } + + if (toggle && $row.classList.contains('dt-row--unhighlight')) { + $row.classList.remove('dt-row--unhighlight'); + } + + this._highlightedRows = this._highlightedRows || {}; + + if (toggle) { + $row.classList.add('dt-row--highlight'); + this._highlightedRows[rowIndex] = $row; + } else { + $row.classList.remove('dt-row--highlight'); + delete this._highlightedRows[rowIndex]; + } + } + + highlightAll(toggle = true) { + if (toggle) { + this.bodyScrollable.classList.add('dt-scrollable--highlight-all'); + } else { + this.bodyScrollable.classList.remove('dt-scrollable--highlight-all'); + for (const rowIndex in this._highlightedRows) { + const $row = this._highlightedRows[rowIndex]; + $row.classList.remove('dt-row--highlight'); + } + this._highlightedRows = {}; + } + } + + hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('dt-row--hide'); + }); + } + + showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('dt-row--hide'); + }); + } + + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); + this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); + } + + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); + } + + getRow$(rowIndex) { + return $(this.selector(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 ``; - } + getFilterInput(props) { + const dataAttr = makeDataAttributeString(props); + return ``; + } - selector(rowIndex) { - return `.data-table-row[data-row-index="${rowIndex}"]`; - } -} - -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 = nextTick(this.appendRemainingData, this); - } - - render() { - if (this.options.clusterize) { - this.renderBodyWithClusterize(); - } else { - this.renderBodyHTML(); + selector(rowIndex) { + return `.dt-row[data-row-index="${rowIndex}"]`; } } - renderBodyHTML() { - const rows = this.datamanager.getRowsForView(); + 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 = nextTick(this.appendRemainingData, this); + } - this.bodyScrollable.innerHTML = ` - - ${this.getBodyHTML(rows)} + render() { + if (this.options.clusterize) { + this.renderBodyWithClusterize(); + } else { + this.renderBodyHTML(); + } + } + + renderBodyHTML() { + const rows = this.datamanager.getRowsForView(); + + this.bodyScrollable.innerHTML = this.getBodyHTML(rows); + this.instance.setDimensions(); + this.restoreState(); + } + + renderBodyWithClusterize() { + // first page + const rows = this.datamanager.getRowsForView(0, 20); + let initialData = this.getDataForClusterize(rows); + + if (initialData.length === 0) { + initialData = [this.getNoDataHTML()]; + } + + 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 */ + show_no_data_row: false, + /* 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.getRowsForView(20); + const data = this.getDataForClusterize(rows); + this.clusterize.append(data); + } + + showToastMessage(message, hideAfter) { + this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message); + + if (hideAfter) { + setTimeout(() => { + this.clearToastMessage(); + }, hideAfter * 1000); + } + } + + clearToastMessage() { + this.instance.toastMessage.innerHTML = ''; + } + + 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('')} +
`; - this.instance.setDimensions(); - this.restoreState(); - } - - renderBodyWithClusterize() { - // first page - const rows = this.datamanager.getRowsForView(0, 20); - let initialData = this.getDataForClusterize(rows); - - if (initialData.length === 0) { - initialData = [`${this.options.noDataMessage}`]; } - if (!this.clusterize) { - // empty body - this.bodyScrollable.innerHTML = ` - - ${this.getBodyHTML([])} -
- `; + getNoDataHTML() { + return `
${this.options.noDataMessage}
`; + } - // 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(); + getToastMessageHTML(message) { + return `${message}`; + } + } + + class Style { + constructor(instance) { + this.instance = instance; + + linkProperties(this, this.instance, [ + 'options', 'datamanager', 'columnmanager', + 'header', 'bodyScrollable', 'datatableWrapper', + 'getColumn' + ]); + + this.scopeClass = 'dt-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.bindResizeWindow(); + } + + get stylesheet() { + return this.styleEl.sheet; + } + + bindResizeWindow() { + if (this.options.layout === 'fluid') { + $.on(window, 'resize', throttle$1(() => { + this.distributeRemainingWidth(); + this.refreshColumnWidth(); + this.compensateScrollbarWidth(); + 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); } - }, - /* eslint-disable */ - show_no_data_row: false, - /* 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.getRowsForView(20); - const data = this.getDataForClusterize(rows); - this.clusterize.append(data); - } - - showToastMessage(message) { - this.instance.toastMessage.innerHTML = `${message}`; - } - - clearToastMessage() { - this.instance.toastMessage.innerHTML = ''; - } - - 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', 'datatableWrapper', - '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.bindResizeWindow(); - } - - get stylesheet() { - return this.styleEl.sheet; - } - - bindResizeWindow() { - if (this.options.layout === 'fluid') { - $.on(window, 'resize', throttle$1(() => { - this.distributeRemainingWidth(); - this.refreshColumnWidth(); - this.compensateScrollbarWidth(); - 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} }`; - - if (!this.stylesheet) return; - - let _index = this.stylesheet.cssRules.length; - if (index !== -1) { - this.stylesheet.deleteRule(index); - _index = index; - } - - this.stylesheet.insertRule(ruleString, _index); - return _index; // eslint-disable-line - } - - setDimensions() { - this.setHeaderStyle(); - - this.setupMinWidth(); - this.setupNaturalColumnWidth(); - this.setupColumnWidth(); - this.distributeRemainingWidth(); - this.setColumnStyle(); - this.compensateScrollbarWidth(); - - 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-cell[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-cell', this.bodyScrollable).map($cell => { - const { - colIndex - } = $.data($cell); - const column = this.datamanager.getColumn(colIndex); - - let naturalWidth = $.style($('.content', $cell), 'width'); - - if (column.id === '_rowIndex') { - naturalWidth = this.getRowIndexColumnWidth(naturalWidth); - column.width = naturalWidth; - } - - column.naturalWidth = naturalWidth; - }); - } - - setupColumnWidth() { - if (this.options.layout === 'ratio') { - let totalWidth = $.style(this.datatableWrapper, 'width'); - - if (this.options.serialNoColumn) { - const rowIndexColumn = this.datamanager.getColumnById('_rowIndex'); - totalWidth = totalWidth - rowIndexColumn.width - 1; - } - - if (this.options.checkboxColumn) { - const rowIndexColumn = this.datamanager.getColumnById('_checkbox'); - totalWidth = totalWidth - rowIndexColumn.width - 1; - } - - const totalParts = this.datamanager.getColumns() - .map(column => { - if (column.id === '_rowIndex' || column.id === '_checkbox') { - return 0; - } - if (!column.width) { - column.width = 1; - } - column.ratioWidth = parseInt(column.width, 10); - return column.ratioWidth; + return `${prop}:${styleMap[prop]};`; }) - .reduce((a, c) => a + c); + .join(''); + let prefixedSelector = selector + .split(',') + .map(r => `.${this.scopeClass} ${r}`) + .join(','); - const onePart = totalWidth / totalParts; + let ruleString = `${prefixedSelector} { ${styles} }`; - this.datamanager.getColumns() - .map(column => { - if (column.id === '_rowIndex' || column.id === '_checkbox') return; - column.width = Math.floor(onePart * column.ratioWidth) - 1; - }); - } else { - this.datamanager.getColumns() - .map(column => { - if (!column.width) { - column.width = column.naturalWidth; - } - if (column.width < column.minWidth) { - column.width = column.minWidth; - } - }); + if (!this.stylesheet) return; + + let _index = this.stylesheet.cssRules.length; + if (index !== -1) { + this.stylesheet.deleteRule(index); + _index = index; + } + + this.stylesheet.insertRule(ruleString, _index); + return _index; // eslint-disable-line } - } - compensateScrollbarWidth() { - const scrollbarWidth = $.scrollbarWidth(); - const lastCol = this.datamanager.getColumn(-1); - const width = lastCol.width - scrollbarWidth; - this.columnmanager.setColumnWidth(lastCol.colIndex, width); - } + setDimensions() { + this.setHeaderStyle(); - distributeRemainingWidth() { - if (this.options.layout !== 'fluid') return; + this.setupMinWidth(); + this.setupNaturalColumnWidth(); + this.setupColumnWidth(); + this.distributeRemainingWidth(); + this.setColumnStyle(); + this.compensateScrollbarWidth(); - 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; + this.setDefaultCellHeight(); + this.setBodyStyle(); + } - resizableColumns.map(col => { - const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width'); - let finalWidth = Math.floor(width + deltaWidth) - 2; + 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.datamanager.updateColumn(col.colIndex, { - width: finalWidth + $.style(this.header, { + margin: 0 }); - }); - } - - setDefaultCellHeight() { - if (this.options.dynamicRowHeight) return; - if (this.__cellHeightSet) return; - const $firstCell = $('.data-table-cell[data-is-header]', this.instance.header); - if (!$firstCell) return; - - const height = this.options.cellHeight || $.style($firstCell, 'height'); - if (height) { - this.setCellHeight(height); - this.__cellHeightSet = true; } - } - setCellHeight(height) { - this.setStyle('.data-table-cell .content', { - height: height + 'px' - }); - this.setStyle('.data-table-cell .edit-cell', { - height: height + 'px' - }); - } + setupMinWidth() { + $.each('.dt-cell[data-is-header]', this.header).map(col => { + const { colIndex } = $.data(col); + const column = this.getColumn(colIndex); - 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 + if (!column.minWidth) { + const width = $.style($('.dt-cell__content', col), 'width'); + // only set this once + column.minWidth = width; + } + }); + } + + setupNaturalColumnWidth() { + if (!$('.dt-row')) return; + + // set initial width as naturally calculated by table's first row + $.each('.dt-row[data-row-index="0"] .dt-cell', this.bodyScrollable).map($cell => { + const { + colIndex + } = $.data($cell); + const column = this.datamanager.getColumn(colIndex); + + let naturalWidth = $.style($('.dt-cell__content', $cell), 'width'); + + if (column.id === '_rowIndex') { + naturalWidth = this.getRowIndexColumnWidth(naturalWidth); + column.width = naturalWidth; + } + + column.naturalWidth = naturalWidth; + }); + } + + setupColumnWidth() { + if (this.options.layout === 'ratio') { + let totalWidth = $.style(this.datatableWrapper, 'width'); + + if (this.options.serialNoColumn) { + const rowIndexColumn = this.datamanager.getColumnById('_rowIndex'); + totalWidth = totalWidth - rowIndexColumn.width - 1; + } + + if (this.options.checkboxColumn) { + const rowIndexColumn = this.datamanager.getColumnById('_checkbox'); + totalWidth = totalWidth - rowIndexColumn.width - 1; + } + + const totalParts = this.datamanager.getColumns() + .map(column => { + if (column.id === '_rowIndex' || column.id === '_checkbox') { + return 0; + } + if (!column.width) { + column.width = 1; + } + column.ratioWidth = parseInt(column.width, 10); + return column.ratioWidth; + }) + .reduce((a, c) => a + c); + + const onePart = totalWidth / totalParts; + + this.datamanager.getColumns() + .map(column => { + if (column.id === '_rowIndex' || column.id === '_checkbox') return; + column.width = Math.floor(onePart * column.ratioWidth) - 1; + }); + } else { + this.datamanager.getColumns() + .map(column => { + if (!column.width) { + column.width = column.naturalWidth; + } + if (column.width < column.minWidth) { + column.width = column.minWidth; + } + }); + } + } + + compensateScrollbarWidth() { + requestAnimationFrame(() => { + const scrollbarWidth = $.scrollbarWidth(); + const lastCol = this.datamanager.getColumn(-1); + const width = lastCol.width - scrollbarWidth; + this.columnmanager.setColumnWidth(lastCol.colIndex, width); + }); + } + + 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.options.dynamicRowHeight) return; + if (this.__cellHeightSet) return; + const $firstCell = $('.dt-cell[data-is-header]', this.instance.header); + if (!$firstCell) return; + + const height = this.options.cellHeight || $.style($firstCell, 'height'); + if (height) { + this.setCellHeight(height); + this.__cellHeightSet = true; + } + } + + setCellHeight(height) { + this.setStyle('.dt-cell__content, .dt-cell__edit', { + height: height + 'px' + }); + } + + setColumnStyle() { + // align columns + this.datamanager.getColumns() + .map(column => { + // alignment + if (['left', 'center', 'right'].includes(column.align)) { + this.setStyle(`.dt-cell--col-${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() { + requestAnimationFrame(() => { + const width = $.style(this.header, 'width'); + + $.style(this.bodyScrollable, { + width: width + 'px' + }); + + const $body = $('.dt-body', this.bodyScrollable); + + if ($body) { + $.style($body, { + height: '0px' }); } - // 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); - }); - } + $.style(this.bodyScrollable, { + marginTop: $.style(this.header, 'height') + 'px' + }); - setBodyStyle() { - const width = $.style(this.header, 'width'); - - $.style(this.bodyScrollable, { - width: width + 'px' - }); - - const $body = $('.data-table-body', this.bodyScrollable); - - if ($body) { - $.style($body, { - height: '0px' + $.style($('table', this.bodyScrollable), { + margin: 0, + width: '100%' + }); }); } - $.style(this.bodyScrollable, { - marginTop: $.style(this.header, 'height') + 'px' - }); - - $.style($('table', this.bodyScrollable), { - margin: 0, - width: '100%' - }); - } - - getColumnHeaderElement(colIndex) { - colIndex = +colIndex; - if (colIndex < 0) return null; - return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); - } - - getRowIndexColumnWidth(baseWidth) { - this._rowIndexColumnWidthMap = this._rowIndexColumnWidthMap || {}; - const rowCount = this.datamanager.getRowCount(); - const digits = (rowCount + '').length; - - if (!this._rowIndexColumnWidthMap[digits]) { - // add 8px for each unit - this._rowIndexColumnWidthMap[digits] = baseWidth + ((digits - 1) * 8); + getColumnHeaderElement(colIndex) { + colIndex = +colIndex; + if (colIndex < 0) return null; + return $(`.dt-cell[data-col-index="${colIndex}"]`, this.header); } - return this._rowIndexColumnWidthMap[digits]; - } -} + getRowIndexColumnWidth(baseWidth) { + this._rowIndexColumnWidthMap = this._rowIndexColumnWidthMap || {}; + const rowCount = this.datamanager.getRowCount(); + const digits = (rowCount + '').length; -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' -}; + if (!this._rowIndexColumnWidthMap[digits]) { + // add 8px for each unit + this._rowIndexColumnWidthMap[digits] = baseWidth + ((digits - 1) * 8); + } -class Keyboard { - constructor(element) { - this.listeners = {}; - $.on(element, 'keydown', this.handler.bind(this)); + return this._rowIndexColumnWidthMap[digits]; + } } - handler(e) { - let key = KEYCODES[e.keyCode]; + 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', + 86: 'v' + }; - if (e.shiftKey && key !== 'shift') { - key = 'shift+' + key; + class Keyboard { + 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 (e.shiftKey && key !== 'shift') { + key = 'shift+' + key; + } - if (listeners && listeners.length > 0) { - for (let listener of listeners) { - const preventBubbling = listener(e); - if (preventBubbling === undefined || preventBubbling === true) { - e.preventDefault(); + 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); + }); + } } - 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'); + 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) {}, + onCheckRow(row) {} }, - { - 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'); + freezeMessage: '', + getEditor: null, + serialNoColumn: true, + checkboxColumn: false, + clusterize: true, + logs: false, + layout: 'fixed', // fixed, fluid, ratio + noDataMessage: 'No Data', + cellHeight: null, + inlineFilters: false, + treeView: false, + checkedRowStatus: true, + dynamicRowHeight: false, + pasteFromClipboard: false + }; + + class DataTable { + constructor(wrapper, options) { + DataTable.instances++; + + if (typeof wrapper === 'string') { + // css selector + wrapper = document.querySelector(wrapper); } - }, - { - label: 'Remove column', - action: function (column) { - this.removeColumn(column.colIndex); + this.wrapper = wrapper; + if (!(this.wrapper instanceof HTMLElement)) { + throw new Error('Invalid argument given for `wrapper`'); + } + + this.buildOptions(options); + 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(); } } - ], - events: { - onRemoveColumn(column) {}, - onSwitchColumn(column1, column2) {}, - onSortColumn(column) {}, - onCheckRow(row) {} - }, - sortIndicator: { - asc: '↑', - desc: '↓', - none: '' - }, - freezeMessage: '', - getEditor: null, - serialNoColumn: true, - checkboxColumn: false, - clusterize: true, - logs: false, - layout: 'fixed', // fixed, fluid, ratio - noDataMessage: 'No Data', - cellHeight: null, - inlineFilters: false, - treeView: false, - checkedRowStatus: true, - dynamicRowHeight: false -}; -class DataTable { - constructor(wrapper, options) { - DataTable.instances++; + buildOptions(options) { + this.options = this.options || {}; - 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, + this.options || {}, options + ); + + options.headerDropdown = options.headerDropdown || []; + this.options.headerDropdown = [ + ...DEFAULT_OPTIONS.headerDropdown, + ...options.headerDropdown + ]; + + // custom user events + this.events = Object.assign( + {}, DEFAULT_OPTIONS.events, + this.options.events || {}, + options.events || {} + ); + this.fireEvent = this.fireEvent.bind(this); } - this.buildOptions(options); - 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(); } - } - buildOptions(options) { - this.options = this.options || {}; - - this.options = Object.assign( - {}, DEFAULT_OPTIONS, - this.options || {}, options - ); - - this.options.headerDropdown - .push(...(options.headerDropdown || [])); - - // custom user events - this.events = Object.assign( - {}, DEFAULT_OPTIONS.events, - this.options.events || {}, - options.events || {} - ); - this.fireEvent = this.fireEvent.bind(this); - } - - prepare() { - this.prepareDom(); - this.unfreeze(); - } - - prepareDom() { - this.wrapper.innerHTML = ` -
- + prepareDom() { + this.wrapper.innerHTML = ` +
+
-
+
-
- ${this.options.freezeMessage} +
+ + ${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); - this.toastMessage = $('.toast-message', this.wrapper); - } - - refresh(data, columns) { - this.datamanager.init(data, columns); - 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(); - } - - showToastMessage(message) { - this.bodyRenderer.showToastMessage(message); - } - - clearToastMessage() { - this.bodyRenderer.clearToastMessage(); - } - - 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 = $('.datatable', this.wrapper); + this.header = $('.dt-header', this.wrapper); + this.bodyScrollable = $('.dt-scrollable', this.wrapper); + this.freezeContainer = $('.dt-freeze', this.wrapper); + this.toastMessage = $('.dt-toast', this.wrapper); + this.pasteTarget = $('.dt-paste-target', this.wrapper); } - return this.viewportHeight; - } + refresh(data, columns) { + this.datamanager.init(data, columns); + this.render(); + this.setDimensions(); + } - sortColumn(colIndex, sortOrder) { - this.columnmanager.sortColumn(colIndex, sortOrder); - } + destroy() { + this.wrapper.innerHTML = ''; + this.style.destroy(); + } - removeColumn(colIndex) { - this.columnmanager.removeColumn(colIndex); - } + appendRows(rows) { + this.datamanager.appendRows(rows); + this.rowmanager.refreshRows(); + } - scrollToLastColumn() { - this.datatableWrapper.scrollLeft = 9999; - } + refreshRow(row, rowIndex) { + this.rowmanager.refreshRow(row, rowIndex); + } - freeze() { - $.style(this.freezeContainer, { - display: '' - }); - } + render() { + this.renderHeader(); + this.renderBody(); + } - unfreeze() { - $.style(this.freezeContainer, { - display: 'none' - }); - } + renderHeader() { + this.columnmanager.renderHeader(); + } - updateOptions(options) { - this.buildOptions(options); - } + renderBody() { + this.bodyRenderer.render(); + } - fireEvent(eventName, ...args) { - this.events[eventName].apply(this, args); - } + setDimensions() { + this.style.setDimensions(); + } - log() { - if (this.options.logs) { - console.log.apply(console, arguments); + showToastMessage(message, hideAfter) { + this.bodyRenderer.showToastMessage(message, hideAfter); + } + + clearToastMessage() { + this.bodyRenderer.clearToastMessage(); + } + + 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' + }); + } + + updateOptions(options) { + this.buildOptions(options); + } + + fireEvent(eventName, ...args) { + this.events[eventName].apply(this, args); + } + + log() { + if (this.options.logs) { + console.log.apply(console, arguments); + } } } -} -DataTable.instances = 0; + DataTable.instances = 0; -var name = "frappe-datatable"; -var version = "0.0.4"; -var description = "A modern datatable library for the web"; -var main = "dist/frappe-datatable.cjs.js"; -var scripts = {"start":"yarn run dev","build":"rollup -c","production":"rollup -c --production","build:docs":"rollup -c --docs","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"}; -var devDependencies = {"chai":"3.5.0","deepmerge":"^2.0.1","mocha":"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-eslint":"^4.0.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify-es":"^0.0.1"}; -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 -}; + var name = "frappe-datatable"; + var version = "0.0.5"; + var description = "A modern datatable library for the web"; + var main = "dist/frappe-datatable.cjs.js"; + var scripts = {"start":"yarn run dev","build":"rollup -c","production":"rollup -c --production","build:docs":"rollup -c --docs","dev":"rollup -c -w","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js"}; + var devDependencies = {"chai":"3.5.0","deepmerge":"^2.0.1","eslint-config-airbnb":"^16.1.0","eslint-config-airbnb-base":"^12.1.0","eslint-plugin-import":"^2.11.0","mocha":"3.3.0","postcss-cssnext":"^3.1.0","postcss-nested":"^3.0.0","rollup-plugin-commonjs":"^8.3.0","rollup-plugin-eslint":"^4.0.0","rollup-plugin-json":"^2.3.0","rollup-plugin-node-resolve":"^3.0.3","rollup-plugin-postcss":"^1.2.8","rollup-plugin-uglify-es":"^0.0.1"}; + 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; + DataTable.__version__ = packageJson.version; -return DataTable; + return DataTable; }(Sortable,Clusterize)); diff --git a/package.json b/package.json index 5fc9222..5c42ac6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "frappe-datatable", - "version": "0.0.5", + "version": "0.0.6", "description": "A modern datatable library for the web", "main": "dist/frappe-datatable.cjs.js", "scripts": {