diff --git a/vite-demo/src/example4.html b/vite-demo/src/example4.html index 5e80ae16..8c2fe1ff 100644 --- a/vite-demo/src/example4.html +++ b/vite-demo/src/example4.html @@ -24,7 +24,10 @@



- +

Load Data

+ + +

Demonstrates:

diff --git a/vite-demo/src/example4.js b/vite-demo/src/example4.js index be3f536d..432a6c3f 100644 --- a/vite-demo/src/example4.js +++ b/vite-demo/src/example4.js @@ -11,279 +11,275 @@ import { Utils, } from 'slickgrid'; -import Example4 from './example4.html?raw'; +import example4Html from './example4.html?raw'; import './example4.scss' -/** render Example HTML code */ -export function render() { - return Example4; -} - -/** initialize Example JS code */ -export function init() { - let dataView; - let grid; - let data = []; - let columns = [ - { id: "sel", name: "#", field: "num", behavior: "select", cssClass: "cell-selection", width: 45, cannotTriggerInsert: true, resizable: false, selectable: false, excludeFromColumnPicker: true }, - { id: "title", name: "Title", field: "title", width: 110, minWidth: 100, cssClass: "cell-title", editor: Editors.LongText, validator: requiredFieldValidator, sortable: true }, - { id: "duration", name: "Duration", field: "duration", editor: Editors.Text, sortable: true }, - { id: "%", defaultSortAsc: false, name: "% Complete", field: "percentComplete", width: 95, formatter: Formatters.PercentCompleteBar, editor: Editors.PercentComplete, sortable: true }, - { id: "start", name: "Start", field: "start", minWidth: 60, editor: Editors.Flatpickr, sortable: true }, - { id: "finish", name: "Finish", field: "finish", minWidth: 60, editor: Editors.Flatpickr, sortable: true }, - { id: "effort-driven", name: "Effort Driven", width: 120, minWidth: 20, cssClass: "cell-effort-driven", field: "effortDriven", formatter: Formatters.Checkbox, editor: Editors.Checkbox, cannotTriggerInsert: true, sortable: true } - ]; - - let options = { - columnPicker: { - columnTitle: "Columns", - hideForceFitButton: false, - hideSyncResizeButton: false, - forceFitTitle: "Force fit columns", - syncResizeTitle: "Synchronous resize", - }, - gridMenu: { - iconCssClass: "sgi sgi-menu sgi-17px", - columnTitle: "Columns", - hideForceFitButton: false, - hideSyncResizeButton: false, - forceFitTitle: "Force fit columns", - syncResizeTitle: "Synchronous resize", - }, - editable: true, - enableAddRow: true, - enableCellNavigation: true, - asyncEditorLoading: true, - forceFitColumns: false, - topPanelHeight: 35, - rowHeight: 28 - }; - - let sortcol = "title"; - let sortdir = 1; - let percentCompleteThreshold = 0; - let searchString = ""; - - function toggleTheme(theme) { - const gridElm = document.querySelector('#myGrid'); - - if (theme === 'alpine') { - changeCSS('../dist/styles/css/slick.grid.css', '../dist/styles/css/slick-alpine-theme.css'); - changeCSS('examples.css', '../dist/styles/css/example-demo.css'); - changeCSS('examples-unicode-icons.css', '../dist/styles/css/slick-icons.css'); - gridElm.classList.add('alpine-theme'); - gridElm.classList.remove('classic-theme'); - } else { - changeCSS('../dist/styles/css/slick-alpine-theme.css', '../dist/styles/css/slick.grid.css'); - changeCSS('../dist/styles/css/example-demo.css', 'examples.css'); - changeCSS('../dist/styles/css/slick-icons.css', 'examples-unicode-icons.css'); - gridElm.classList.add('classic-theme'); - gridElm.classList.remove('alpine-theme'); - } +export class Example4 { + dataView; + grid; + data; + columns; + options; + sortcol = 'title'; + sortdir = 1; + percentCompleteThreshold = 0; + searchString = ''; + + /** render Example HTML code */ + render() { + return example4Html; } - function changeCSS(prevFilePath, newFilePath) { - let headerIndex = 0; - let previousCssElm = document.getElementsByTagName("head")[0].querySelector(`link[href="${prevFilePath}"]`); - if (previousCssElm) { - previousCssElm.setAttribute('href', newFilePath); - } - } + /** initialize Example JS code */ + init() { + this.dataView; + this.grid; + this.data = []; + this.columns = [ + { id: "sel", name: "#", field: "num", behavior: "select", cssClass: "cell-selection", width: 45, cannotTriggerInsert: true, resizable: false, selectable: false, excludeFromColumnPicker: true }, + { id: "title", name: "Title", field: "title", width: 110, minWidth: 100, cssClass: "cell-title", editor: Editors.LongText, validator: this.requiredFieldValidator, sortable: true }, + { id: "duration", name: "Duration", field: "duration", editor: Editors.Text, sortable: true }, + { id: "%", defaultSortAsc: false, name: "% Complete", field: "percentComplete", width: 95, formatter: Formatters.PercentCompleteBar, editor: Editors.PercentComplete, sortable: true }, + { id: "start", name: "Start", field: "start", minWidth: 60, editor: Editors.Flatpickr, sortable: true }, + { id: "finish", name: "Finish", field: "finish", minWidth: 60, editor: Editors.Flatpickr, sortable: true }, + { id: "effort-driven", name: "Effort Driven", width: 120, minWidth: 20, cssClass: "cell-effort-driven", field: "effortDriven", formatter: Formatters.Checkbox, editor: Editors.Checkbox, cannotTriggerInsert: true, sortable: true } + ]; + + this.options = { + columnPicker: { + columnTitle: "Columns", + hideForceFitButton: false, + hideSyncResizeButton: false, + forceFitTitle: "Force fit columns", + syncResizeTitle: "Synchronous resize", + }, + gridMenu: { + iconCssClass: "sgi sgi-menu sgi-17px", + columnTitle: "Columns", + hideForceFitButton: false, + hideSyncResizeButton: false, + forceFitTitle: "Force fit columns", + syncResizeTitle: "Synchronous resize", + }, + editable: true, + enableAddRow: true, + enableCellNavigation: true, + asyncEditorLoading: true, + forceFitColumns: false, + topPanelHeight: 35, + rowHeight: 28 + }; + + // get some mocked data + this.data = this.getData(50000); + + // instantiate SlickGrid and SlickDataView + this.dataView = new SlickDataView({ inlineFilters: true }); + this.grid = new SlickGrid("#myGrid", this.dataView, this.columns, this.options); + this.grid.setSelectionModel(new SlickRowSelectionModel()); + + // instantiate a few Controls + new SlickGridPager(this.dataView, this.grid, "#pager"); + new SlickColumnPicker(this.columns, this.grid, this.options); + new SlickGridMenu(this.columns, this.grid, this.options); + + // move the filter panel defined in a hidden div into grid top panel + let topPanel = this.grid.getTopPanel(); + const topPanelLeftElm = document.querySelector("#inlineFilterPanel"); + topPanel.appendChild(topPanelLeftElm); + topPanelLeftElm.style.display = 'block'; + + this.grid.onCellChange.subscribe((e, args) => { + this.dataView.updateItem(args.item.id, args.item); + }); - function requiredFieldValidator(value) { - if (value == null || value == undefined || !value.length) { - return { valid: false, msg: 'This is a required field' }; - } else if (!/^(task\s\d+)*$/i.test(value)) { - return { valid: false, msg: 'Your title is invalid, it must start with "Task" followed by a number.' }; - } - else { - return { valid: true, msg: null }; - } - } + this.grid.onAddNewRow.subscribe((e, args) => { + let item = { "num": this.data.length, "id": "new_" + (Math.round(Math.random() * 10000)), "title": "New task", "duration": "1 day", "percentComplete": 0, "start": "01/01/2009", "finish": "01/01/2009", "effortDriven": false }; + Utils.extend(item, args.item); + this.dataView.addItem(item); + }); - function percentCompleteSort(a, b) { - return a["percentComplete"] - b["percentComplete"]; - } + this.grid.onKeyDown.subscribe(function (e) { + // select all rows on ctrl-a + if (e.which != 65 || !e.ctrlKey) { + return false; + } - function comparer(a, b) { - let x = a[sortcol], y = b[sortcol]; - return (x == y ? 0 : (x > y ? 1 : -1)); - } + let rows = []; + for (let i = 0; i < this.dataView.getLength(); i++) { + rows.push(i); + } - function toggleFilterRow() { - grid.setTopPanelVisibility(!grid.getOptions().showTopPanel); - } + this.grid.setSelectedRows(rows); + e.preventDefault(); + }); - // prepare the data - for (let i = 0; i < 50000; i++) { - let d = (data[i] = {}); - - d["id"] = "id_" + i; - d["num"] = i; - 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); - } + this.grid.onSort.subscribe((e, args) => { + this.sortdir = args.sortAsc ? 1 : -1; + this.sortcol = args.sortCol.field; - // instantiate SlickGrid and SlickDataView - dataView = new SlickDataView({ inlineFilters: true }); - grid = new SlickGrid("#myGrid", dataView, columns, options); - grid.setSelectionModel(new SlickRowSelectionModel()); - - let pager = new SlickGridPager(dataView, grid, "#pager"); - let columnpicker = new SlickColumnPicker(columns, grid, options); - let gridMenu = new SlickGridMenu(columns, grid, options); - - // move the filter panel defined in a hidden div into grid top panel - let topPanel = grid.getTopPanel(); - const topPanelLeftElm = document.querySelector("#inlineFilterPanel"); - topPanel.appendChild(topPanelLeftElm); - topPanelLeftElm.style.display = 'block'; - - grid.onCellChange.subscribe((e, args) => { - dataView.updateItem(args.item.id, args.item); - }); - - grid.onAddNewRow.subscribe((e, args) => { - let item = { "num": data.length, "id": "new_" + (Math.round(Math.random() * 10000)), "title": "New task", "duration": "1 day", "percentComplete": 0, "start": "01/01/2009", "finish": "01/01/2009", "effortDriven": false }; - Utils.extend(item, args.item); - dataView.addItem(item); - }); - - grid.onKeyDown.subscribe(function (e) { - // select all rows on ctrl-a - if (e.which != 65 || !e.ctrlKey) { - return false; - } + // using native sort with comparer + this.dataView.sort(this.comparer.bind(this), args.sortAsc); + }); - let rows = []; - for (let i = 0; i < dataView.getLength(); i++) { - rows.push(i); - } + this.grid.onBeforeEditCell.subscribe((e, args) => { + if (args.item.title === 'Task 18') { + return false; + } + }); - grid.setSelectedRows(rows); - e.preventDefault(); - }); + // wire up model events to drive the grid + // !! both this.dataView.onRowCountChanged and this.dataView.onRowsChanged MUST be wired to correctly update the grid + // see Issue#91 + this.dataView.onRowCountChanged.subscribe((e, args) => { + this.grid.updateRowCount(); + this.grid.render(); + }); - grid.onSort.subscribe((e, args) => { - sortdir = args.sortAsc ? 1 : -1; - sortcol = args.sortCol.field; + this.dataView.onRowsChanged.subscribe((e, args) => { + this.grid.invalidateRows(args.rows); + this.grid.render(); + }); - // using native sort with comparer - dataView.sort(comparer, args.sortAsc); - }); + this.dataView.onPagingInfoChanged.subscribe((e, pagingInfo) => { + this.grid.updatePagingStatusFromView(pagingInfo); + // show the pagingInfo but remove the dataView from the object, just for the Cypress E2E test + delete pagingInfo.dataView; + console.log('on After Paging Info Changed - New Paging:: ', pagingInfo); + }); - grid.onBeforeEditCell.subscribe((e, args) => { - if (args.item.title === 'Task 18') { - return false; - } - }); - - // wire up model events to drive the grid - // !! both dataView.onRowCountChanged and dataView.onRowsChanged MUST be wired to correctly update the grid - // see Issue#91 - dataView.onRowCountChanged.subscribe((e, args) => { - grid.updateRowCount(); - grid.render(); - }); - - dataView.onRowsChanged.subscribe((e, args) => { - grid.invalidateRows(args.rows); - grid.render(); - }); - - dataView.onPagingInfoChanged.subscribe((e, pagingInfo) => { - grid.updatePagingStatusFromView(pagingInfo); - // show the pagingInfo but remove the dataView from the object, just for the Cypress E2E test - delete pagingInfo.dataView; - console.log('on After Paging Info Changed - New Paging:: ', pagingInfo); - }); - - dataView.onBeforePagingInfoChanged.subscribe((e, previousPagingInfo) => { - // show the previous pagingInfo but remove the dataView from the object, just for the Cypress E2E test - delete previousPagingInfo.dataView; - console.log('on Before Paging Info Changed - Previous Paging:: ', previousPagingInfo); - }); - - let h_runfilters = null; - - // wire up the slider to apply the filter to the model - let slider = document.getElementById("pcSlider"); - let slider2 = document.getElementById("pcSlider2"); - let sliderCallback = function () { - SlickGlobalEditorLock.cancelCurrentEdit(); - - if (percentCompleteThreshold != this.value) { - window.clearTimeout(h_runfilters); - h_runfilters = window.setTimeout(updateFilter, 10); - percentCompleteThreshold = this.value; + this.dataView.onBeforePagingInfoChanged.subscribe((e, previousPagingInfo) => { + // show the previous pagingInfo but remove the dataView from the object, just for the Cypress E2E test + delete previousPagingInfo.dataView; + console.log('on Before Paging Info Changed - Previous Paging:: ', previousPagingInfo); + }); + + let h_runfilters = null; + + // wire up the slider to apply the filter to the model + let slider = document.getElementById("pcSlider"); + let slider2 = document.getElementById("pcSlider2"); + let sliderCallback = (e) => { + const value = e.target.value; + SlickGlobalEditorLock.cancelCurrentEdit(); + if (this.percentCompleteThreshold != value) { + window.clearTimeout(h_runfilters); + h_runfilters = window.setTimeout(this.updateFilter(), 10); + this.percentCompleteThreshold = value; + } } - } - slider.oninput = sliderCallback; - slider2.oninput = sliderCallback; + slider.oninput = sliderCallback.bind(this); + slider2.oninput = sliderCallback.bind(this); + + // wire up the search textbox to apply the filter to the model + document.querySelectorAll("#txtSearch,#txtSearch2").forEach(elm => elm.addEventListener('keyup', (e) => { + SlickGlobalEditorLock.cancelCurrentEdit(); - // wire up the search textbox to apply the filter to the model - document.querySelectorAll("#txtSearch,#txtSearch2").forEach(elm => elm.addEventListener('keyup', (e) => { - SlickGlobalEditorLock.cancelCurrentEdit(); + // clear on Esc + if (e.which == 27) e.target.value = ''; - // clear on Esc - if (e.which == 27) e.target.value = ''; + this.searchString = (e.target.value || '').trim(); + this.updateFilter(); + this.dataView.refresh(); + })); - searchString = (e.target.value || '').trim(); - updateFilter(); - dataView.refresh(); - })); + document.querySelector("#btnSelectRows").addEventListener('click', () => { + if (!SlickGlobalEditorLock.commitCurrentEdit()) { + return; + } + let rows = []; - function updateFilter() { - dataView.setFilterArgs({ - percentCompleteThreshold, - searchString + for (let i = 0; i < 10 && i < this.dataView.getLength(); i++) { + rows.push(i); + } + this.grid.setSelectedRows(rows); }); - dataView.refresh(); + + // initialize the model after all the events have been hooked up + this.dataView.beginUpdate(); + this.dataView.setItems(this.data); + this.dataView.setFilterArgs({ + percentCompleteThreshold: this.percentCompleteThreshold, + searchString: this.searchString + }); + // `inlineFilters` can be a regular function or an ES6 arrow function like below + // this.dataView.setFilter((item, args) => item['percentComplete'] > args.percentCompleteThreshold); + this.dataView.setFilter((item, args) => { + if (item['percentComplete'] < args.percentCompleteThreshold) { + return false; + } + + const searchString = args.searchString.toLowerCase(); + if (args.searchString != '' && !item['title'].toLowerCase().includes(searchString)) { + return false; + } + + return true; + }); + this.dataView.endUpdate(); + + // if you don't want the items that are not visible (due to being filtered out + // or being on a different page) to stay selected, pass 'false' to the second arg + this.dataView.syncGridSelection(this.grid, true); + + document.querySelector('.sgi-search').addEventListener('click', this.toggleFilterRow.bind(this)); + document.querySelector('[data-test="add-50k-rows-btn"]').addEventListener('click', () => this.setData(this.getData(50000))); + document.querySelector('[data-test="add-500k-rows-btn"]').addEventListener('click', () => this.setData(this.getData(500000))); + document.querySelector('[data-test="add-1M-rows-btn"]').addEventListener('click', () => this.setData(this.getData(1000000))); } - document.querySelector("#btnSelectRows").addEventListener('click', () => { - if (!SlickGlobalEditorLock.commitCurrentEdit()) { - return; - } - let rows = []; + comparer(a, b) { + let x = a[this.sortcol], y = b[this.sortcol]; + return (x == y ? 0 : (x > y ? 1 : -1)); + } - for (let i = 0; i < 10 && i < dataView.getLength(); i++) { - rows.push(i); - } - grid.setSelectedRows(rows); - }); - - // initialize the model after all the events have been hooked up - dataView.beginUpdate(); - dataView.setItems(data); - dataView.setFilterArgs({ - percentCompleteThreshold, - searchString - }); - // `inlineFilters` can be a regular function or an ES6 arrow function like below - // dataView.setFilter((item, args) => item['percentComplete'] > args.percentCompleteThreshold); - dataView.setFilter((item, args) => { - if (item['percentComplete'] < args.percentCompleteThreshold) { - return false; - } + setData(data) { + this.dataView.setItems(data); + } - const searchString = args.searchString.toLowerCase(); - if (args.searchString != '' && !item['title'].toLowerCase().includes(searchString)) { - return false; + getData(count) { + // prepare the data + let tmpData = []; + for (let i = 0; i < count; i++) { + let d = (tmpData[i] = {}); + + d["id"] = "id_" + i; + d["num"] = i; + 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); } + return tmpData; + } - return true; - }); - dataView.endUpdate(); + percentCompleteSort(a, b) { + return a["percentComplete"] - b["percentComplete"]; + } - // if you don't want the items that are not visible (due to being filtered out - // or being on a different page) to stay selected, pass 'false' to the second arg - dataView.syncGridSelection(grid, true); + requiredFieldValidator(value) { + if (value == null || value == undefined || !value.length) { + return { valid: false, msg: 'This is a required field' }; + } else if (!/^(task\s\d+)*$/i.test(value)) { + return { valid: false, msg: 'Your title is invalid, it must start with "Task" followed by a number.' }; + } + else { + return { valid: true, msg: null }; + } + } - document.querySelector('.sgi-search').addEventListener('click', toggleFilterRow); + toggleFilterRow() { + this.grid.setTopPanelVisibility(!this.grid.getOptions().showTopPanel); + } + + updateFilter() { + this.dataView.setFilterArgs({ + percentCompleteThreshold: this.percentCompleteThreshold, + searchString: this.searchString + }); + this.dataView.refresh(); + } } diff --git a/vite-demo/src/main.js b/vite-demo/src/main.js index ff4b3948..1f301363 100644 --- a/vite-demo/src/main.js +++ b/vite-demo/src/main.js @@ -2,12 +2,13 @@ import 'flatpickr'; import 'flatpickr/dist/flatpickr.css'; import Sortable from 'sortablejs'; -import { init, render } from './example4.js'; +import { Example4 } from './example4.js'; import './style.scss'; // assign SortableJS to the Window object window.Sortable = Sortable; // load example html, assign it to App and then init the JS code -document.querySelector('#app').innerHTML = render(); -init(); \ No newline at end of file +const demo4 = new Example4(); +document.querySelector('#app').innerHTML = demo4.render(); +demo4.init(); \ No newline at end of file