From 3df8a00efcc2f11f6dfb159f65f4387b85f36043 Mon Sep 17 00:00:00 2001 From: "Marko B. Ludolph" Date: Wed, 25 Jan 2023 15:10:29 +0100 Subject: [PATCH 01/26] Drop jQuery requirement --- examples/example-column-group.html | 64 +- ...example-composite-editor-item-details.html | 6 +- ...example-composite-editor-modal-dialog.html | 2 +- examples/example-explicit-initialization.html | 2 +- examples/example1-simple.html | 28 +- examples/example2-formatters.html | 6 +- examples/example3-editing.html | 6 +- examples/example3b-editing-with-undo.html | 3 - .../example5-collapsing-treeGrid-sort.html | 50 +- examples/example5-collapsing.html | 209 +- plugins/slick.cellmenu.js | 3 + plugins/slick.contextmenu.js | 6 + slick.compositeeditor.js | 37 +- slick.core.js | 318 ++- slick.dataview.js | 32 +- slick.editors.js | 395 +-- slick.formatters.js | 20 +- slick.grid.js | 2309 +++++++++-------- slick.groupitemmetadataprovider.js | 10 +- slick.interactions.js | 14 +- 20 files changed, 1899 insertions(+), 1621 deletions(-) diff --git a/examples/example-column-group.html b/examples/example-column-group.html index 8feabe59..04851404 100644 --- a/examples/example-column-group.html +++ b/examples/example-column-group.html @@ -94,46 +94,44 @@

View Source:

{id: "effort-driven", name: "Effort Driven", width: 80, minWidth: 20, maxWidth: 80, field: "effortDriven", columnGroup:"Analysis"} ]; - $(function () { - for (var i = 0; i < 50000; i++) { - var d = (data[i] = {}); + for (var i = 0; i < 50000; i++) { + var d = (data[i] = {}); - d["id"] = "id_" + i; - d["num"] = i; - d["title"] = "Task " + i; - d["duration"] = "5 days"; - d["start"] = "01/01/2009"; - d["finish"] = "01/05/2009"; - d["percentComplete"] = Math.round(Math.random() * 100); - d["effortDriven"] = (i % 5 == 0); - } + d["id"] = "id_" + i; + d["num"] = i; + d["title"] = "Task " + i; + d["duration"] = "5 days"; + d["start"] = "01/01/2009"; + d["finish"] = "01/05/2009"; + d["percentComplete"] = Math.round(Math.random() * 100); + d["effortDriven"] = (i % 5 == 0); + } - dataView = new Slick.Data.DataView(); - grid = new Slick.Grid("#myGrid", dataView, columns, options); + dataView = new Slick.Data.DataView(); + grid = new Slick.Grid("#myGrid", dataView, columns, options); - dataView.onRowCountChanged.subscribe(function (e, args) { - grid.updateRowCount(); - grid.render(); - }); + dataView.onRowCountChanged.subscribe(function (e, args) { + grid.updateRowCount(); + grid.render(); + }); - dataView.onRowsChanged.subscribe(function (e, args) { - grid.invalidateRows(args.rows); - grid.render(); - }); + dataView.onRowsChanged.subscribe(function (e, args) { + grid.invalidateRows(args.rows); + grid.render(); + }); - grid.init(); + grid.init(); - grid.onColumnsResized.subscribe(function (e, args) { - CreateAddlHeaderRow(); - }); - + grid.onColumnsResized.subscribe(function (e, args) { CreateAddlHeaderRow(); - - // Initialise data - dataView.beginUpdate(); - dataView.setItems(data); - dataView.endUpdate(); - }) + }); + + CreateAddlHeaderRow(); + + // Initialise data + dataView.beginUpdate(); + dataView.setItems(data); + dataView.endUpdate(); diff --git a/examples/example-composite-editor-item-details.html b/examples/example-composite-editor-item-details.html index 031b6a92..922f5118 100644 --- a/examples/example-composite-editor-item-details.html +++ b/examples/example-composite-editor-item-details.html @@ -157,11 +157,11 @@

View Source:

.appendTo("body"); $modal.keydown(function (e) { - if (e.which == $.ui.keyCode.ENTER) { + if (e.which == Slick.keyCode.ENTER) { grid.getEditController().commitCurrentEdit(); e.stopPropagation(); e.preventDefault(); - } else if (e.which == $.ui.keyCode.ESCAPE) { + } else if (e.which == Slick.keyCode.ESCAPE) { grid.getEditController().cancelCurrentEdit(); e.stopPropagation(); e.preventDefault(); @@ -178,7 +178,7 @@

View Source:

var containers = $.map(columns, function (c) { - return $modal.find("[data-editorid=" + c.id + "]"); + return $modal.find("[data-editorid=" + c.id + "]")[0]; }); var compositeEditor = new Slick.CompositeEditor( diff --git a/examples/example-composite-editor-modal-dialog.html b/examples/example-composite-editor-modal-dialog.html index 4d793fcb..d8d2656e 100644 --- a/examples/example-composite-editor-modal-dialog.html +++ b/examples/example-composite-editor-modal-dialog.html @@ -439,7 +439,7 @@

View Source:

}); var containers = $.map(modalColumns, function (c) { - return $modal.find("[data-editorid=" + c.id + "]"); + return $modal.find("[data-editorid=" + c.id + "]")[0]; }); var compositeEditor = new Slick.CompositeEditor( diff --git a/examples/example-explicit-initialization.html b/examples/example-explicit-initialization.html index 99cc2aa0..cb170b7a 100644 --- a/examples/example-explicit-initialization.html +++ b/examples/example-explicit-initialization.html @@ -72,7 +72,7 @@

View Source:

// create a detached container element var myGrid = $("
"); - grid = new Slick.Grid(myGrid, data, columns, options); + grid = new Slick.Grid(myGrid[0], data, columns, options); // ... later on, append the container to the DOM and initialize SlickGrid diff --git a/examples/example1-simple.html b/examples/example1-simple.html index b188a225..acdcc68d 100644 --- a/examples/example1-simple.html +++ b/examples/example1-simple.html @@ -26,8 +26,6 @@

View Source:

- - @@ -50,21 +48,19 @@

View Source:

enableColumnReorder: false }; - $(function () { - var data = []; - for (var i = 0; i < 500; i++) { - data[i] = { - title: "Task " + i, - duration: "5 days", - percentComplete: Math.round(Math.random() * 100), - start: "01/01/2009", - finish: "01/05/2009", - effortDriven: (i % 5 == 0) - }; - } + var data = []; + for (var i = 0; i < 500; i++) { + data[i] = { + title: "Task " + i, + duration: "5 days", + percentComplete: Math.round(Math.random() * 100), + start: "01/01/2009", + finish: "01/05/2009", + effortDriven: (i % 5 == 0) + }; + } - grid = new Slick.Grid("#myGrid", data, columns, options); - }) + grid = new Slick.Grid("#myGrid", data, columns, options); diff --git a/examples/example2-formatters.html b/examples/example2-formatters.html index 52561d71..b8d3943c 100644 --- a/examples/example2-formatters.html +++ b/examples/example2-formatters.html @@ -49,7 +49,6 @@

View Source:

- @@ -99,10 +98,10 @@

View Source:

var options = { editable: true, enableAddRow: false, - enableCellNavigation: true + enableCellNavigation: true, + createPreHeaderPanel: true }; - $(function () { for (var i = 0; i < 5; i++) { var d = (data[i] = {}); @@ -115,7 +114,6 @@

View Source:

} grid = new Slick.Grid("#myGrid", data, columns, options); - }) diff --git a/examples/example3-editing.html b/examples/example3-editing.html index fc60a7dd..944d6f9d 100644 --- a/examples/example3-editing.html +++ b/examples/example3-editing.html @@ -53,10 +53,10 @@

View Source:

- - - + + + diff --git a/examples/example3b-editing-with-undo.html b/examples/example3b-editing-with-undo.html index d5d07854..8f8c6b16 100644 --- a/examples/example3b-editing-with-undo.html +++ b/examples/example3b-editing-with-undo.html @@ -41,7 +41,6 @@

View Source:

- @@ -96,7 +95,6 @@

View Source:

} } - $(function () { for (var i = 0; i < 500; i++) { var d = (data[i] = {}); @@ -110,7 +108,6 @@

View Source:

} grid = new Slick.Grid("#myGrid", data, columns, options); - }) diff --git a/examples/example5-collapsing-treeGrid-sort.html b/examples/example5-collapsing-treeGrid-sort.html index bf1acbdd..cccb988d 100644 --- a/examples/example5-collapsing-treeGrid-sort.html +++ b/examples/example5-collapsing-treeGrid-sort.html @@ -65,8 +65,6 @@

View Source:

- - @@ -606,12 +604,12 @@

View Source:

"start": "01/01/2009", "finish": "01/01/2009", "effortDriven": false}; - $.extend(item, args.item); + Slick.Utils.extend(item, args.item); dataView.addItem(item); }); grid.onClick.subscribe(function (e, args) { - if ($(e.target).hasClass("toggle")) { + if (e.getNativeEvent().target.classList.contains("toggle")) { var item = dataView.getItem(args.row); if (item) { if (!item._collapsed) { @@ -657,7 +655,7 @@

View Source:

// wire up the search textbox to apply the filter to the model - $("#txtSearch").keyup(function (e) { + document.getElementById("txtSearch").addEventListener("keyup", function (e) { Slick.GlobalEditorLock.cancelCurrentEdit(); // clear on Esc @@ -670,33 +668,27 @@

View Source:

}) } -$(function () { - // $.ajaxSettings.async = false; - // $.getJSON("./data.json",function(content){ - // data = content; - // }); - // $.ajaxSettings.async = true; - - let indent = 0; - let parents = []; - - itemMap = groupBy(data, 'parentId'); - data = initTree(data); - - // initialize the model - dataView = new Slick.Data.DataView({ inlineFilters: true }); - dataView.beginUpdate(); - dataView.setItems(data); - dataView.setFilter(myFilter); - dataView.endUpdate(); - - // initialize the grid - grid = new Slick.Grid("#myGrid", dataView, columns, options); +let indent = 0; +let parents = []; + +itemMap = groupBy(data, 'parentId'); +data = initTree(data); + +// initialize the model +dataView = new Slick.Data.DataView({ inlineFilters: true }); +dataView.beginUpdate(); +dataView.setItems(data); +dataView.setFilter(myFilter); +dataView.endUpdate(); + + +// initialize the grid +grid = new Slick.Grid("#myGrid", dataView, columns, options); + +bindGridEvents(); - bindGridEvents(); -}) diff --git a/examples/example5-collapsing.html b/examples/example5-collapsing.html index 819ef932..44e85ce2 100644 --- a/examples/example5-collapsing.html +++ b/examples/example5-collapsing.html @@ -66,7 +66,6 @@

View Source:

- @@ -154,128 +153,126 @@

View Source:

return a["percentComplete"] - b["percentComplete"]; } -$(function () { - var indent = 0; - var parents = []; - - // prepare the data - for (var i = 0; i < 1000; i++) { - var d = (data[i] = {}); - var parent; - - if (Math.random() > 0.8 && i > 0) { - indent++; - parents.push(i - 1); - } else if (Math.random() < 0.3 && indent > 0) { - indent--; - parents.pop(); - } - - if (parents.length > 0) { - parent = parents[parents.length - 1]; - } else { - parent = null; - } +var indent = 0; +var parents = []; + +// prepare the data +for (var i = 0; i < 1000; i++) { + var d = (data[i] = {}); + var parent; + + if (Math.random() > 0.8 && i > 0) { + indent++; + parents.push(i - 1); + } else if (Math.random() < 0.3 && indent > 0) { + indent--; + parents.pop(); + } - d["id"] = "id_" + i; - d["indent"] = indent; - d["parent"] = parent; - d["title"] = "Task " + i; - d["duration"] = "5 days"; - d["percentComplete"] = Math.round(Math.random() * 100); - d["start"] = "01/01/2009"; - d["finish"] = "01/05/2009"; - d["effortDriven"] = (i % 5 == 0); + if (parents.length > 0) { + parent = parents[parents.length - 1]; + } else { + parent = null; } + d["id"] = "id_" + i; + d["indent"] = indent; + d["parent"] = parent; + d["title"] = "Task " + i; + d["duration"] = "5 days"; + d["percentComplete"] = Math.round(Math.random() * 100); + d["start"] = "01/01/2009"; + d["finish"] = "01/05/2009"; + d["effortDriven"] = (i % 5 == 0); +} + - // initialize the model - dataView = new Slick.Data.DataView({ inlineFilters: true }); - dataView.beginUpdate(); - dataView.setItems(data); - dataView.setFilter(myFilter); - dataView.endUpdate(); - - - // initialize the grid - grid = new Slick.Grid("#myGrid", dataView, columns, options); - - grid.onCellChange.subscribe(function (e, args) { - dataView.updateItem(args.item.id, args.item); - }); - - grid.onAddNewRow.subscribe(function (e, args) { - var item = { - "id": "new_" + (Math.round(Math.random() * 10000)), - "indent": 0, - "title": "New task", - "duration": "1 day", - "percentComplete": 0, - "start": "01/01/2009", - "finish": "01/01/2009", - "effortDriven": false}; - $.extend(item, args.item); - dataView.addItem(item); - }); - - grid.onClick.subscribe(function (e, args) { - if ($(e.target).hasClass("toggle")) { - var item = dataView.getItem(args.row); - if (item) { - if (!item._collapsed) { - item._collapsed = true; - } else { - item._collapsed = false; - } - - dataView.updateItem(item.id, item); +// initialize the model +dataView = new Slick.Data.DataView({ inlineFilters: true }); +dataView.beginUpdate(); +dataView.setItems(data); +dataView.setFilter(myFilter); +dataView.endUpdate(); + + +// initialize the grid +grid = new Slick.Grid("#myGrid", dataView, columns, options); + +grid.onCellChange.subscribe(function (e, args) { + dataView.updateItem(args.item.id, args.item); +}); + +grid.onAddNewRow.subscribe(function (e, args) { + var item = { + "id": "new_" + (Math.round(Math.random() * 10000)), + "indent": 0, + "title": "New task", + "duration": "1 day", + "percentComplete": 0, + "start": "01/01/2009", + "finish": "01/01/2009", + "effortDriven": false}; + Slick.Utils.extend(item, args.item); + dataView.addItem(item); +}); + +grid.onClick.subscribe(function (e, args) { + if (e.getNativeEvent().target.classList.contains("toggle")) { + var item = dataView.getItem(args.row); + if (item) { + if (!item._collapsed) { + item._collapsed = true; + } else { + item._collapsed = false; } - e.stopImmediatePropagation(); + + dataView.updateItem(item.id, item); } - }); + e.stopImmediatePropagation(); + } +}); - // wire up model events to drive the grid - dataView.onRowCountChanged.subscribe(function (e, args) { - grid.updateRowCount(); - grid.render(); - }); +// wire up model events to drive the grid +dataView.onRowCountChanged.subscribe(function (e, args) { + grid.updateRowCount(); + grid.render(); +}); - dataView.onRowsChanged.subscribe(function (e, args) { - grid.invalidateRows(args.rows); - grid.render(); - }); +dataView.onRowsChanged.subscribe(function (e, args) { + grid.invalidateRows(args.rows); + grid.render(); +}); - var h_runfilters = null; +var h_runfilters = null; - // wire up the slider to apply the filter to the model - var slider = document.getElementById("pcSlider"); - var sliderCallback = function() { - Slick.GlobalEditorLock.cancelCurrentEdit(); +// wire up the slider to apply the filter to the model +var slider = document.getElementById("pcSlider"); +var sliderCallback = function() { + Slick.GlobalEditorLock.cancelCurrentEdit(); - if (percentCompleteThreshold != this.value) { - window.clearTimeout(h_runfilters); - h_runfilters = window.setTimeout(dataView.refresh, 10); - percentCompleteThreshold = this.value; - } + if (percentCompleteThreshold != this.value) { + window.clearTimeout(h_runfilters); + h_runfilters = window.setTimeout(dataView.refresh, 10); + percentCompleteThreshold = this.value; } - - slider.oninput = sliderCallback; +} - // wire up the search textbox to apply the filter to the model - $("#txtSearch").keyup(function (e) { - Slick.GlobalEditorLock.cancelCurrentEdit(); +slider.oninput = sliderCallback; - // clear on Esc - if (e.which == 27) { - this.value = ""; - } +// wire up the search textbox to apply the filter to the model +document.getElementById("txtSearch").addEventListener("keyup", function (e) { + Slick.GlobalEditorLock.cancelCurrentEdit(); + + // clear on Esc + if (e.which == 27) { + this.value = ""; + } - searchString = this.value; - dataView.refresh(); - }) -}) + searchString = this.value; + dataView.refresh(); +}); diff --git a/plugins/slick.cellmenu.js b/plugins/slick.cellmenu.js index 6dd28b21..f8b024b5 100644 --- a/plugins/slick.cellmenu.js +++ b/plugins/slick.cellmenu.js @@ -364,6 +364,9 @@ } function handleCellClick(e, args) { + if(e instanceof Slick.EventData) + e = e.getNativeEvent(); + var cell = _grid.getCellFromEvent(e); var dataContext = _grid.getDataItem(cell.row); var columnDef = _grid.getColumns()[cell.cell]; diff --git a/plugins/slick.contextmenu.js b/plugins/slick.contextmenu.js index 1b92d126..eb0fba9e 100644 --- a/plugins/slick.contextmenu.js +++ b/plugins/slick.contextmenu.js @@ -200,6 +200,9 @@ } function createMenu(e) { + if(e instanceof Slick.EventData) + e = e.getNativeEvent(); + var targetEvent = e.touches ? e.touches[0] : e; var cell = _grid.getCellFromEvent(e); _currentCell = cell && cell.cell; @@ -346,6 +349,9 @@ } function handleOnContextMenu(e, args) { + if(e instanceof Slick.EventData) + e = e.getNativeEvent(); + e.preventDefault(); var cell = _grid.getCellFromEvent(e); diff --git a/slick.compositeeditor.js b/slick.compositeeditor.js index 71e0cb63..af388796 100644 --- a/slick.compositeeditor.js +++ b/slick.compositeeditor.js @@ -44,14 +44,14 @@ var firstInvalidEditor; - options = $.extend({}, defaultOptions, options); + options = Slick.Utils.extend({}, defaultOptions, options); function getContainerBox(i) { var c = containers[i]; - var offset = $(c).offset(); - var w = $(c).width() || 0; - var h = $(c).height() || 0; + var offset = Slick.Utils.offset(c); + var w = Slick.Utils.width(c); + var h = Slick.Utils.height(c); return { top: offset && offset.top, @@ -75,7 +75,7 @@ while (idx < columns.length) { if (columns[idx].editor) { var column = columns[idx]; - newArgs = $.extend({}, args); + newArgs = Slick.Utils.extend({}, args); newArgs.container = containers[idx]; newArgs.column = column; newArgs.position = getContainerBox(idx); @@ -170,12 +170,13 @@ while (idx < editors.length) { var columnDef = editors[idx].args && editors[idx].args.column || {}; if (columnDef) { - var $validationElm = $(".item-details-validation.editor-" + columnDef.id); - var $labelElm = $(".item-details-label.editor-" + columnDef.id); - var $editorElm = $("[data-editorid=" + columnDef.id + "]"); + var $validationElm = document.querySelector(".item-details-validation.editor-" + columnDef.id); + var $labelElm = document.querySelector(".item-details-label.editor-" + columnDef.id); + var $editorElm = document.querySelector("[data-editorid=" + columnDef.id + "]"); var validationMsgPrefix = options && options.validationMsgPrefix || ""; - if (!$targetElm || ($editorElm.has($targetElm).length > 0)) { + // ($editorElm.has($targetElm).length > 0) + if (!$targetElm || Slick.Utils.contains($editorElm, $targetElm)) { validationResults = editors[idx].validate(); if (!validationResults.valid) { @@ -188,14 +189,14 @@ }); if ($validationElm) { - $validationElm.text(validationMsgPrefix + validationResults.msg); - $labelElm.addClass("invalid"); - $editorElm.addClass("invalid"); + $validationElm.textContent = validationMsgPrefix + validationResults.msg; + $labelElm.classList.add("invalid"); + $editorElm.classList.add("invalid"); } } else if ($validationElm) { - $validationElm.text(""); - $editorElm.removeClass("invalid"); - $labelElm.removeClass("invalid"); + $validationElm.textContent = ""; + $editorElm.classList.remove("invalid"); + $labelElm.classList.remove("invalid"); } } $validationElm = null; @@ -255,9 +256,7 @@ } // exports - $.extend(true, window, { - Slick: { - CompositeEditor: CompositeEditor - } + Slick.Utils.extend(Slick, { + CompositeEditor: CompositeEditor }); })(jQuery); \ No newline at end of file diff --git a/slick.core.js b/slick.core.js index fc0aac35..12b8d400 100644 --- a/slick.core.js +++ b/slick.core.js @@ -4,23 +4,26 @@ * @namespace Slick */ -(function ($) { +(function (window) { /*** * An event object for passing data to event handlers and letting them control propagation. *

This is pretty much identical to how W3C and jQuery implement events.

* @class EventData * @constructor */ - function EventData() { + function EventData(event) { + + var nativeEvent = event; var isPropagationStopped = false; var isImmediatePropagationStopped = false; - + var isDefaultPrevented = false; /*** * Stops event from propagating up the DOM tree. * @method stopPropagation */ this.stopPropagation = function () { isPropagationStopped = true; + this.preventDefault(); }; /*** @@ -29,7 +32,7 @@ * @return {Boolean} */ this.isPropagationStopped = function () { - return isPropagationStopped; + return isPropagationStopped || isDefaultPrevented; }; /*** @@ -38,6 +41,7 @@ */ this.stopImmediatePropagation = function () { isImmediatePropagationStopped = true; + this.preventDefault(); }; /*** @@ -48,6 +52,16 @@ this.isImmediatePropagationStopped = function () { return isImmediatePropagationStopped; }; + + this.getNativeEvent = function() { + return nativeEvent; + } + + this.preventDefault = function() { + if(nativeEvent) + nativeEvent.preventDefault(); + isDefaultPrevented = true; + } } /*** @@ -96,7 +110,9 @@ * If not specified, the scope will be set to the Event instance. */ this.notify = function (args, e, scope) { - e = e || new EventData(); + + if(!(e instanceof EventData)) + e = new EventData(e); scope = scope || this; var returnValue; @@ -564,7 +580,7 @@ } function cloneTreeColumns() { - return $.extend(true, [], treeColumns); + return extend([], treeColumns); } init(); @@ -572,8 +588,10 @@ this.hasDepth = function () { for (var i in treeColumns) + { if (treeColumns[i].hasOwnProperty('columns')) return true; + } return false; }; @@ -675,10 +693,243 @@ function regexSanitizer(dirtyHtml) { return dirtyHtml.replace(/(\b)(on[a-z]+)(\s*)=|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script(>*)|(<)(\/*)(script|script defer)(.*)(>|>">)/gi, ''); } + + // With help from https://youmightnotneedjquery.com/ + function grep( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + } + + function offset(el) { + + box = el.getBoundingClientRect(); + docElem = document.documentElement; + return { + top: box.top + window.pageYOffset - docElem.clientTop, + left: box.left + window.pageXOffset - docElem.clientLeft + }; - // exports - $.extend(true, window, { - "Slick": { + } + + function width(el, value) + { + if (value === undefined) { + return el.getBoundingClientRect().width; + } + setStyleSize(el, "width", value); + } + + function height(el, value) + { + if (value === undefined) { + return el.getBoundingClientRect().height; + } + setStyleSize(el, "height", value); + } + + function setWidth(el, value) { + setStyleSize(el, "width", value); + } + + function setHeight(el, value) { + setStyleSize(el, "height", value); + } + + function setStyleSize(el, style, val) { + if (typeof val === 'function') val = val(); + if (typeof val === 'string') el.style[style] = val; + else el.style[style] = val + 'px'; + } + + function position(el) { + const {top, left} = el.getBoundingClientRect(); + const {marginTop, marginLeft} = getComputedStyle(el); + return { + top: top - parseInt(marginTop, 10), + left: left - parseInt(marginLeft, 10) + }; + } + + function contains(parent, child) { + parent.contains(child); + } + + function isHidden(el) + { + return el.offsetWidth === 0 && el.offsetHeight === 0; + } + + function parents(el, selector) { + const parents = []; + const visible = selector == ":visible"; + const hidden = selector == ":hidden"; + + while ((el = el.parentNode) && el !== document) { + + if(hidden) + { + if(isHidden(el)) + parents.push(el); + } + else if (visible) + { + if(!isHidden(el)) + parents.push(el); + } + else if (!selector || el.matches(selector)) + parents.push(el); + } + return parents; + } + + function template(html, parent) { + const template = document.createElement('template'); + template.innerHTML = html.trim(); + + const first = template.content.firstChild; + if(parent) + { + [].forEach.call(template.content.children, (child) => { + parent.appendChild(child); + }); + return first; + } + return first; + } + + function toFloat(value) { + var x = parseFloat(value) + if (isNaN(x)) { + return 0; + } + return x; + } + + function scrollLeft(el, value) { + el = getElement(el); + + if (value === undefined) { + return el.pageXOffset; + } else { + if (el === window || el.nodeType === 9) { + el.scrollTo(value, el.pageYOffset); + } else { + el.pageXOffset = value; + } + } + } + + function scrollTop(el, value) { + if (value === undefined) { + return el.pageYOffset; + } else { + if (el === window || el.nodeType === 9) { + el.scrollTo(el.pageXOffset, value); + } else { + el.pageYOffset = value; + } + } + } + + function show(el, type) + { + if(type) + el.style.display = type; + else + el.style.display = ""; + } + + function hide(el) + { + el.style.display = "none"; + } + + // jQuery's extend + var getProto = Object.getPrototypeOf; + var class2type = {}; + var toString = class2type.toString; + var hasOwn = class2type.hasOwnProperty; + var fnToString = hasOwn.toString; + var ObjectFunctionString = fnToString.call( Object ); + function isFunction( obj ) { + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + function isPlainObject( obj ) { + var proto, Ctor; + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + if ( !proto ) { + return true; + } + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + } + function extend() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = true; + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[ i ] || {}; + i++; + } + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + if ( i === length ) { + target = this; + i--; + } + for ( ; i < length; i++ ) { + if ( ( options = arguments[ i ] ) != null ) { + for ( name in options ) { + copy = options[ name ]; + if ( name === "__proto__" || target === copy ) { + continue; + } + if ( deep && copy && ( isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + target[ name ] = extend( deep, clone, copy ); + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + return target; + }; + + // exports + window.Slick = { "Event": Event, "EventData": EventData, "EventHandler": EventHandler, @@ -689,7 +940,49 @@ "GroupTotals": GroupTotals, "RegexSanitizer": regexSanitizer, "EditorLock": EditorLock, - + "Utils": + { + "extend": extend, + "grep": grep, + "offset": offset, + "height": height, + "setHeight": setHeight, + "width": width, + "setWidth": setWidth, + "setStyleSize": setStyleSize, + "contains": contains, + "template": template, + "toFloat": toFloat, + "position": position, + "parents": parents, + "scrollLeft": scrollLeft, + "scrollTop": scrollTop, + "show": show, + "hide": hide, + "storage": { + // https://stackoverflow.com/questions/29222027/vanilla-alternative-to-jquery-data-function-any-native-javascript-alternati + _storage: new WeakMap(), + put: function (element, key, obj) { + if (!this._storage.has(element)) { + this._storage.set(element, new Map()); + } + this._storage.get(element).set(key, obj); + }, + get: function (element, key) { + return this._storage.get(element).get(key); + }, + has: function (element, key) { + return this._storage.has(element) && this._storage.get(element).has(key); + }, + remove: function (element, key) { + var ret = this._storage.get(element).delete(key); + if (!this._storage.get(element).size === 0) { + this._storage.delete(element); + } + return ret; + } + } + }, /*** * A global singleton editor lock. * @class GlobalEditorLock @@ -757,7 +1050,4 @@ HTML: 'HTML' } } - }); -})(jQuery); - - +})(window); diff --git a/slick.dataview.js b/slick.dataview.js index 345dbc29..87b67851 100644 --- a/slick.dataview.js +++ b/slick.dataview.js @@ -1,4 +1,4 @@ -(function ($) { +(function () { /*** * A sample Model implementation. * Provides a filtered view of the underlying data. @@ -78,7 +78,7 @@ var onGroupExpanded = new Slick.Event(); var onGroupCollapsed = new Slick.Event(); - options = $.extend(true, {}, defaults, options); + options = Slick.Utils.extend({}, defaults, options); /*** * Begins a bached update of the items in the data view. @@ -330,7 +330,7 @@ groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo]; for (var i = 0; i < groupingInfos.length; i++) { - var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]); + var gi = groupingInfos[i] = Slick.Utils.extend({}, groupingInfoDefaults, groupingInfos[i]); gi.getterIsAFn = typeof gi.getter === "function"; // pre-compile accumulator loops @@ -1206,7 +1206,7 @@ return; } - var previousPagingInfo = $.extend(true, {}, getPagingInfo()); + var previousPagingInfo = Slick.Utils.extend({}, getPagingInfo()); var countBefore = rows.length; var totalRowsBefore = totalRows; @@ -1495,7 +1495,7 @@ this.onRowsOrCountChanged.subscribe(update); } - $.extend(this, { + Slick.Utils.extend(this, { // methods "beginUpdate": beginUpdate, "endUpdate": endUpdate, @@ -1685,18 +1685,16 @@ // TODO: merge common aggregators in one to prevent needles iterating // exports - $.extend(true, window, { - Slick: { - Data: { - DataView: DataView, - Aggregators: { - Avg: AvgAggregator, - Min: MinAggregator, - Max: MaxAggregator, - Sum: SumAggregator, - Count: CountAggregator - } + Slick.Utils.extend(Slick, { + Data: { + DataView: DataView, + Aggregators: { + Avg: AvgAggregator, + Min: MinAggregator, + Max: MaxAggregator, + Sum: SumAggregator, + Count: CountAggregator } } }); -})(jQuery); +})(); diff --git a/slick.editors.js b/slick.editors.js index ca6b404f..eee540c8 100644 --- a/slick.editors.js +++ b/slick.editors.js @@ -4,24 +4,26 @@ * @namespace Slick */ -(function ($) { +(function () { + + const u = Slick.Utils; + function TextEditor(args) { - var $input; + var input; var defaultValue; var scope = this; this.args = args; this.init = function () { var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; - $input = $("") - .appendTo(args.container) - .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) - .focus() - .select(); + input = u.template("", args.container); + input.addEventListener("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav); + input.focus(); + input.select(); // don't show Save/Cancel when it's a Composite Editor and also trigger a onCompositeEditorChange event when input changes if (args.compositeEditorOptions) { - $input.on("change", function () { + input.addEventListener("change", function () { var activeCell = args.grid.getActiveCell(); // when valid, we'll also apply the new value to the dataContext item object @@ -35,30 +37,30 @@ }; this.destroy = function () { - $input.remove(); + input.remove(); }; this.focus = function () { - $input.focus(); + input.focus(); }; this.getValue = function () { - return $input.val(); + return input.value; }; this.setValue = function (val) { - $input.val(val); + input.value = val; }; this.loadValue = function (item) { defaultValue = item[args.column.field] || ""; - $input.val(defaultValue); - $input[0].defaultValue = defaultValue; - $input.select(); + input.value = defaultValue; + input.defaultValue = defaultValue; + input.select(); }; this.serializeValue = function () { - return $input.val(); + return input.value; }; this.applyValue = function (item, state) { @@ -66,12 +68,12 @@ }; this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue); + return (!(input.value === "" && defaultValue == null)) && (input.value != defaultValue); }; this.validate = function () { if (args.column.validator) { - var validationResults = args.column.validator($input.val(), args); + var validationResults = args.column.validator(input.value, args); if (!validationResults.valid) { return validationResults; } @@ -87,22 +89,21 @@ } function IntegerEditor(args) { - var $input; + var input; var defaultValue; var scope = this; this.args = args; this.init = function () { var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; - $input = $("") - .appendTo(args.container) - .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) - .focus() - .select(); + input = u.template("", args.container); + input.addEventListener("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav); + input.focus() + input.select(); // trigger onCompositeEditorChange event when input changes and it's a Composite Editor if (args.compositeEditorOptions) { - $input.on("change", function () { + input.addEventListener("change", function () { var activeCell = args.grid.getActiveCell(); // when valid, we'll also apply the new value to the dataContext item object @@ -116,22 +117,22 @@ }; this.destroy = function () { - $input.remove(); + input.remove(); }; this.focus = function () { - $input.focus(); + input.focus(); }; this.loadValue = function (item) { defaultValue = item[args.column.field]; - $input.val(defaultValue); - $input[0].defaultValue = defaultValue; - $input.select(); + input.value = defaultValue; + input.defaultValue = defaultValue; + input.select(); }; this.serializeValue = function () { - return parseInt($input.val(), 10) || 0; + return parseInt(input.value, 10) || 0; }; this.applyValue = function (item, state) { @@ -139,11 +140,11 @@ }; this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue); + return (!(input.value === "" && defaultValue == null)) && (input.value != defaultValue); }; this.validate = function () { - if (isNaN($input.val())) { + if (isNaN(input.value)) { return { valid: false, msg: "Please enter a valid integer" @@ -151,7 +152,7 @@ } if (args.column.validator) { - var validationResults = args.column.validator($input.val(), args); + var validationResults = args.column.validator(input.value, args); if (!validationResults.valid) { return validationResults; } @@ -167,22 +168,21 @@ } function FloatEditor(args) { - var $input; + var input; var defaultValue; var scope = this; this.args = args; this.init = function () { var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; - $input = $("") - .appendTo(args.container) - .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav) - .focus() - .select(); + input = u.template("", args.container); + input.addEventListener("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav); + input.focus() + input.select(); // trigger onCompositeEditorChange event when input changes and it's a Composite Editor if (args.compositeEditorOptions) { - $input.on("change", function () { + input.addEventListener("change", function () { var activeCell = args.grid.getActiveCell(); // when valid, we'll also apply the new value to the dataContext item object @@ -196,11 +196,11 @@ }; this.destroy = function () { - $input.remove(); + input.remove(); }; this.focus = function () { - $input.focus(); + input.focus(); }; function getDecimalPlaces() { @@ -222,13 +222,13 @@ defaultValue = defaultValue.toFixed(decPlaces); } - $input.val(defaultValue); - $input[0].defaultValue = defaultValue; - $input.select(); + input.value = defaultValue; + input.defaultValue = defaultValue; + input.select(); }; this.serializeValue = function () { - var rtn = parseFloat($input.val()); + var rtn = parseFloat(input.value); if (FloatEditor.AllowEmptyValue) { if (!rtn && rtn !== 0) { rtn = ''; } } else { @@ -250,11 +250,11 @@ }; this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue); + return (!(input.value === "" && defaultValue == null)) && (input.value != defaultValue); }; this.validate = function () { - if (isNaN($input.val())) { + if (isNaN(input.value)) { return { valid: false, msg: "Please enter a valid number" @@ -262,7 +262,7 @@ } if (args.column.validator) { - var validationResults = args.column.validator($input.val(), args); + var validationResults = args.column.validator(input.value, args); if (!validationResults.valid) { return validationResults; } @@ -280,127 +280,22 @@ FloatEditor.DefaultDecimalPlaces = null; FloatEditor.AllowEmptyValue = false; - function DateEditor(args) { - var $input; - var defaultValue; - var scope = this; - var calendarOpen = false; - this.args = args; - - this.init = function () { - $input = $(""); - $input.appendTo(args.container); - $input.focus().select(); - $input.datepicker({ - showOn: "button", - buttonImageOnly: true, - beforeShow: function () { - calendarOpen = true; - }, - onClose: function () { - calendarOpen = false; - - // trigger onCompositeEditorChange event when input changes and it's a Composite Editor - if (args.compositeEditorOptions) { - var activeCell = args.grid.getActiveCell(); - - // when valid, we'll also apply the new value to the dataContext item object - if (scope.validate().valid) { - scope.applyValue(scope.args.item, scope.serializeValue()); - } - scope.applyValue(scope.args.compositeEditorOptions.formValues, scope.serializeValue()); - args.grid.onCompositeEditorChange.notify({ row: activeCell.row, cell: activeCell.cell, item: scope.args.item, column: scope.args.column, formValues: scope.args.compositeEditorOptions.formValues }); - } - } - }); - - $input.width($input.width() - (!args.compositeEditorOptions ? 18 : 28)); - }; - - this.destroy = function () { - $.datepicker.dpDiv.stop(true, true); - $input.datepicker("hide"); - $input.datepicker("destroy"); - $input.remove(); - }; - - this.show = function () { - if (calendarOpen) { - $.datepicker.dpDiv.stop(true, true).show(); - } - }; - - this.hide = function () { - if (calendarOpen) { - $.datepicker.dpDiv.stop(true, true).hide(); - } - }; - - this.position = function (position) { - if (!calendarOpen) { - return; - } - $.datepicker.dpDiv - .css("top", position.top + 30) - .css("left", position.left); - }; - - this.focus = function () { - $input.focus(); - }; - - this.loadValue = function (item) { - defaultValue = item[args.column.field]; - $input.val(defaultValue); - $input[0].defaultValue = defaultValue; - $input.select(); - }; - - this.serializeValue = function () { - return $input.val(); - }; - - this.applyValue = function (item, state) { - item[args.column.field] = state; - }; - - this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue); - }; - - this.validate = function () { - if (args.column.validator) { - var validationResults = args.column.validator($input.val(), args); - if (!validationResults.valid) { - return validationResults; - } - } - - return { - valid: true, - msg: null - }; - }; - - this.init(); - } - function FlatpickrEditor(args) { if (typeof flatpickr === 'undefined') { throw new Error('Flatpickr not loaded but required in SlickGrid.Editors, refer to Flatpickr documentation: https://flatpickr.js.org/getting-started/'); } - var $input; + var input; var defaultValue; var scope = this; this.args = args; var flatpickrInstance; this.init = function () { - $input = $(''); - $input.appendTo(args.container); - $input.focus().select(); - flatpickrInstance = $input.flatpickr({ + input = u.template('', args.container); + input.focus(); + input.select(); + flatpickrInstance = flatpickr(input, { closeOnSelect: true, allowInput: true, altInput: true, @@ -428,13 +323,13 @@ }, 50); } - $input.width($input.width() - (!args.compositeEditorOptions ? 18 : 28)); + u.width(input, u.width(input) - (!args.compositeEditorOptions ? 18 : 28)); }; this.destroy = function () { scope.hide(); flatpickrInstance.destroy(); - $input.remove(); + input.remove(); }; this.show = function () { @@ -450,19 +345,19 @@ }; this.focus = function () { - $input.focus(); + input.focus(); }; this.loadValue = function (item) { defaultValue = item[args.column.field]; - $input.val(defaultValue); - $input[0].defaultValue = defaultValue; - $input.select(); + input.value = defaultValue; + input.defaultValue = defaultValue; + input.select(); flatpickrInstance.setDate(defaultValue); }; this.serializeValue = function () { - return $input.val(); + return input.value; }; this.applyValue = function (item, state) { @@ -470,12 +365,12 @@ }; this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue); + return (!(input.value === "" && defaultValue == null)) && (input.value != defaultValue); }; this.validate = function () { if (args.column.validator) { - var validationResults = args.column.validator($input.val(), args); + var validationResults = args.column.validator(input.value, args); if (!validationResults.valid) { return validationResults; } @@ -491,19 +386,18 @@ } function YesNoSelectEditor(args) { - var $select; + var select; var defaultValue; var scope = this; this.args = args; this.init = function () { - $select = $(""); - $select.appendTo(args.container); - $select.focus(); + select = u.template("", args.container); + select.focus(); // trigger onCompositeEditorChange event when input changes and it's a Composite Editor if (args.compositeEditorOptions) { - $select.on("change", function () { + select.addEventListener("change", function () { var activeCell = args.grid.getActiveCell(); // when valid, we'll also apply the new value to the dataContext item object @@ -517,20 +411,20 @@ }; this.destroy = function () { - $select.remove(); + select.remove(); }; this.focus = function () { - $select.focus(); + select.focus(); }; this.loadValue = function (item) { - $select.val((defaultValue = item[args.column.field]) ? "yes" : "no"); - $select.select(); + select.value = ((defaultValue = item[args.column.field]) ? "yes" : "no"); + select.select(); }; this.serializeValue = function () { - return ($select.val() == "yes"); + return select.value == "yes"; }; this.applyValue = function (item, state) { @@ -538,7 +432,7 @@ }; this.isValueChanged = function () { - return ($select.val() != defaultValue); + return select.value != defaultValue; }; this.validate = function () { @@ -552,19 +446,18 @@ } function CheckboxEditor(args) { - var $select; + var select; var defaultValue; var scope = this; this.args = args; this.init = function () { - $select = $(""); - $select.appendTo(args.container); - $select.focus(); + select = u.template("", args.container); + select.focus(); // trigger onCompositeEditorChange event when input checkbox changes and it's a Composite Editor if (args.compositeEditorOptions) { - $select.on("change", function () { + select.addEventListener("change", function () { var activeCell = args.grid.getActiveCell(); // when valid, we'll also apply the new value to the dataContext item object @@ -578,28 +471,24 @@ }; this.destroy = function () { - $select.remove(); + select.remove(); }; this.focus = function () { - $select.focus(); + select.focus(); }; this.loadValue = function (item) { defaultValue = !!item[args.column.field]; if (defaultValue) { - $select.prop('checked', true); + select.checked = true; } else { - $select.prop('checked', false); + select.checked = false; } }; - this.preClick = function () { - $select.prop('checked', !$select.prop('checked')); - }; - this.serializeValue = function () { - return $select.prop('checked'); + return select.checked; }; this.applyValue = function (item, state) { @@ -621,13 +510,13 @@ } function PercentCompleteEditor(args) { - var $input, $picker; + var input, picker; var defaultValue; var scope = this; this.args = args; - var $slider; + var slider; var sliderInputHandler = function () { - $input.val(this.value); + input.value = this.value; } var sliderChangeHandler = function () { // trigger onCompositeEditorChange event when slider stops and it's a Composite Editor @@ -644,47 +533,49 @@ } this.init = function () { - $input = $(''); - $input.width($(args.container).innerWidth() - 25); - $input.appendTo(args.container); + input = u.template('', args.container); + u.width(input, args.container.clientWidth - 25); - $picker = $("
").appendTo(args.container); - $picker.append("
"); - $picker.find(".editor-percentcomplete-buttons").append('

'); - $input.focus().select(); + picker = u.template("
", args.container); + u.template("


", picker); + input.focus(); + input.select(); - $slider = $picker.find('input.editor-percentcomplete-slider'); - $slider.val(defaultValue); + slider = picker.querySelector('input.editor-percentcomplete-slider'); + slider.value = defaultValue; - $slider.on('input', sliderInputHandler); - $slider.on('change', sliderChangeHandler); + slider.addEventListener('input', sliderInputHandler); + slider.addEventListener('change', sliderChangeHandler); - $picker.find(".editor-percentcomplete-buttons button").on("click", function (e) { - $input.val($(this).attr("val")); - $slider.val($(this).attr("val")); + const buttons = picker.querySelectorAll(".editor-percentcomplete-buttons button"); + [].forEach.call(buttons, (button) => { + button.addEventListener("click", function (e) { + input.value = this.getAttribute("val"); + slider.value = this.getAttribute("val"); + }); }); }; this.destroy = function () { - $slider.off('input', sliderInputHandler); - $slider.off('change', sliderChangeHandler); - $input.remove(); - $picker.remove(); + slider.removeEventListener('input', sliderInputHandler); + slider.removeEventListener('change', sliderChangeHandler); + input.remove(); + picker.remove(); }; this.focus = function () { - $input.focus(); + input.focus(); }; this.loadValue = function (item) { defaultValue = item[args.column.field]; - $slider.val(defaultValue); - $input.val(defaultValue); - $input.select(); + slider.value = defaultValue; + input.value = defaultValue; + input.select(); }; this.serializeValue = function () { - return parseInt($input.val(), 10) || 0; + return parseInt(input.value, 10) || 0; }; this.applyValue = function (item, state) { @@ -692,11 +583,11 @@ }; this.isValueChanged = function () { - return (!($input.val() === "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue); + return (!(input.value === "" && defaultValue == null)) && ((parseInt(input.value, 10) || 0) != defaultValue); }; this.validate = function () { - if (isNaN(parseInt($input.val(), 10))) { + if (isNaN(parseInt(input.value, 10))) { return { valid: false, msg: "Please enter a valid positive number" @@ -718,7 +609,7 @@ * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. */ function LongTextEditor(args) { - var $input, $wrapper; + var input, wrapper; var defaultValue; var scope = this; this.args = args; @@ -726,22 +617,22 @@ this.init = function () { var compositeEditorOptions = args.compositeEditorOptions; var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys; - var $container = compositeEditorOptions ? args.container : $('body'); + var container = compositeEditorOptions ? args.container : document.body; - $wrapper = $("
") - .appendTo($container); + wrapper = u.template("
", container); if (compositeEditorOptions) { - $wrapper.css({ position: 'relative', padding: 0, border: 0 }); + wrapper.style.position = 'relative'; + u.setStyleSize(wrapper, "padding", 0); + u.setStyleSize(wrapper, "border", 0); } else { - $wrapper.css({ position: 'absolute' }); + wrapper.style.position = 'absolute'; } - $input = $("