-
+
@@ -40,4 +39,4 @@
Example 03 - Tree View from Hierarchical Dataset
+
\ No newline at end of file
diff --git a/packages/vanilla-bundle-examples/src/examples/example06.scss b/packages/vanilla-bundle-examples/src/examples/example06.scss
index 06c39cd1e..d4db51552 100644
--- a/packages/vanilla-bundle-examples/src/examples/example06.scss
+++ b/packages/vanilla-bundle-examples/src/examples/example06.scss
@@ -6,6 +6,10 @@
text-align: center;
}
+.mdi-20px {
+ font-size: 19px;
+}
+
.toggle {
height: 20px;
width: 20px;
diff --git a/packages/vanilla-bundle-examples/src/examples/example06.ts b/packages/vanilla-bundle-examples/src/examples/example06.ts
index 5a78186d0..44d4c5a3d 100644
--- a/packages/vanilla-bundle-examples/src/examples/example06.ts
+++ b/packages/vanilla-bundle-examples/src/examples/example06.ts
@@ -1,13 +1,12 @@
-import { convertArrayFlatToHierarchical, Column, FieldType, GridOption, sortFlatArrayByHierarchy, convertArrayHierarchicalToFlat } from '@slickgrid-universal/common';
+import { convertArrayFlatToHierarchical, Column, FieldType, Filters, GridOption, sortFlatArrayByHierarchy, convertArrayHierarchicalToFlat, Formatters, Formatter, sortHierarchicalArray } from '@slickgrid-universal/common';
import { Slicker } from '@slickgrid-universal/vanilla-bundle';
-import './example05.scss';
+import './example06.scss';
-const NB_ITEMS = 20;
-
-export class Example5 {
+export class Example6 {
columnDefinitions: Column[];
gridOptions: GridOption;
- dataset: any[];
+ datasetFlat: any[];
+ datasetHierarchical = [];
dataViewObj: any;
gridObj: any;
slickgridLwc;
@@ -17,7 +16,7 @@ export class Example5 {
attached() {
this.initializeGrid();
- this.dataset = [];
+ this.datasetFlat = [];
const gridContainerElm = document.querySelector('.grid5');
gridContainerElm.addEventListener('onclick', this.handleOnClick.bind(this));
@@ -25,22 +24,22 @@ export class Example5 {
this.slickgridLwc = new Slicker.GridBundle(gridContainerElm, this.columnDefinitions, this.gridOptions);
this.dataViewObj = this.slickgridLwc.dataView;
this.dataViewObj.setFilter(this.myFilter.bind(this));
- // this.dataset = this.mockDataset();
- this.dataset = convertArrayHierarchicalToFlat(this.mockDataset(), { childPropName: 'children' });
- this.slickgridLwc.dataset = this.dataset;
+ this.datasetHierarchical = sortHierarchicalArray(this.mockDataset(), { sortByPropName: 'file' });
+ this.datasetFlat = convertArrayHierarchicalToFlat(this.datasetHierarchical, { childPropName: 'files' });
+ this.slickgridLwc.dataset = this.datasetFlat;
}
initializeGrid() {
this.columnDefinitions = [
- { id: 'title', name: 'Title', field: 'title', width: 220, cssClass: 'cell-title', filterable: true, formatter: this.treeFormatter.bind(this) },
- { id: 'duration', name: 'Duration', field: 'duration', minWidth: 90 },
- { id: '%', name: '% Complete', field: 'percentComplete', width: 120, resizable: false, formatter: Slicker.Formatters.percentCompleteBar },
- { id: 'start', name: 'Start', field: 'start', minWidth: 60 },
- { id: 'finish', name: 'Finish', field: 'finish', minWidth: 60 },
+ { id: 'file', name: 'Files', field: 'file', filterable: true, sortable: true, type: FieldType.string, width: 150, formatter: this.treeFormatter },
{
- id: 'effort-driven', name: 'Effort Driven', width: 80, minWidth: 20, maxWidth: 80, cssClass: 'cell-effort-driven', field: 'effortDriven',
- formatter: Slicker.Formatters.checkmarkMaterial, cannotTriggerInsert: true
- }
+ id: 'dateModified', name: 'Date Modified', field: 'dateModified', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, minWidth: 90,
+ // exportWithFormatter: true, filterable: true, filter: { model: Filters.compoundDate }
+ },
+ {
+ id: 'size', name: 'Size', field: 'size', sortable: true, minWidth: 90, formatter: (row, cell, value, columnDef, dataContext, grid) => isNaN(value) ? '' : `${value} MB`
+ // exportWithFormatter: true, filterable: true, filter: { model: Filters.compoundDate }
+ },
];
this.gridOptions = {
@@ -51,6 +50,10 @@ export class Example5 {
enableAutoResize: true,
headerRowHeight: 45,
rowHeight: 45,
+ enableTreeView: true,
+ treeViewOptions: {
+ fieldId: 'file',
+ }
};
}
@@ -58,113 +61,141 @@ export class Example5 {
this.slickgridLwc.dispose();
}
- searchTask(event: KeyboardEvent) {
+ searchFile(event: KeyboardEvent) {
this.searchString = (event.target as HTMLInputElement).value;
this.dataViewObj.refresh();
}
- treeFormatter(row, cell, value, columnDef, dataContext) {
- if (value == null || value === undefined || dataContext === undefined) { return ''; }
+ treeFormatter: Formatter = (row, cell, value, columnDef, dataContext, grid) => {
+ if (value === null || value === undefined || dataContext === undefined) {
+ return '';
+ }
+ const dataView = grid.getData();
+ const data = dataView.getItems();
+ const idx = dataView.getIdxById(dataContext.id);
+ const prefix = this.getFileIcon(value);
+
value = value.replace(/&/g, '&').replace(//g, '>');
- const spacer = `
`;
+ const spacer = `
`;
- if (dataContext['__hasChildren']) {
+ if (data[idx + 1] && data[idx + 1].__treeLevel > data[idx].__treeLevel) {
+ const folderPrefix = `
`;
if (dataContext.__collapsed) {
- return `${spacer}
${value}`;
+ return `${spacer}
${folderPrefix} ${prefix} ${value}`;
} else {
- return `${spacer}
${value}`;
+ return `${spacer}
${folderPrefix} ${prefix} ${value}`;
}
+ } else {
+ return `${spacer}
${prefix} ${value}`;
}
- return `${spacer}
${value}`;
}
- myFilter(item) {
- // if (item["percentComplete"] < percentCompleteThreshold) {
- // return false;
- // }
+ getFileIcon(value: string) {
+ let prefix = '';
+ if (value.includes('.pdf')) {
+ prefix = '
';
+ } else if (value.includes('.txt')) {
+ prefix = '
';
+ } else if (value.includes('.xls')) {
+ prefix = '
';
+ } else if (value.includes('.mp3')) {
+ prefix = '
';
+ }
+ return prefix;
+ }
- if (this.searchString !== '' && item['title'].indexOf(this.searchString) === -1) {
+ myFilter(item: any) {
+ const treeAssociatedField = this.gridOptions.treeViewOptions?.fieldId;
+ if (this.searchString !== '' && item[treeAssociatedField].indexOf(this.searchString) === -1) {
return false;
}
- if (item.parent != null) {
- let parent = this.dataset.find(itm => itm.id === item.parent);
+ if (item.__parentId !== null) {
+ let parent = this.datasetFlat.find(itm => itm.id === item.__parentId);
while (parent) {
- if (parent.__collapsed || /* (parent["percentComplete"] < percentCompleteThreshold) || */ (this.searchString !== '' && parent['title'].indexOf(this.searchString) === -1)) {
+ if (parent.__collapsed || (this.searchString !== '' && parent[treeAssociatedField].indexOf(this.searchString) === -1)) {
return false;
}
- const parentId = parent.parent !== null ? parent.parent : null;
- parent = this.dataset.find((itm2) => itm2.id === parentId);
+ const parentId = parent.__parentId !== null ? parent.__parentId : null;
+ parent = this.datasetFlat.find((itm2) => itm2.id === parentId);
}
}
return true;
}
+ findRecursive(array, predicate, childrenPropertyName) {
+ if (!childrenPropertyName) {
+ throw new Error('findRecursive requires parameter "childrenPropertyName"');
+ }
+ const initialFind = array.find(predicate);
+ const elementsWithChildren = array.filter(x => x[childrenPropertyName]);
+ if (initialFind) {
+ return initialFind;
+ } else if (elementsWithChildren.length) {
+ const childElements = [];
+ elementsWithChildren.forEach(x => {
+ childElements.push(...x[childrenPropertyName]);
+ });
+ return this.findRecursive(childElements, predicate, childrenPropertyName);
+ } else {
+ return undefined;
+ }
+ }
+
/**
* A simple method to add a new item inside the first group that we find.
* After adding the item, it will sort by parent/child recursively
*/
- addNewRow() {
- const newId = this.dataset.length;
- const newTreeLevel = 1;
+ addNewFile() {
+ const newId = this.datasetFlat.length + 100;
// find first parent object and add the new item as a child
- const childItemFound = this.dataset.find((item) => item.__treeLevel === newTreeLevel);
- const parentItemFound = this.dataViewObj.getItemByIdx(childItemFound.parent);
-
- const newItem = {
- id: newId,
- parent: parentItemFound.id,
- title: `Task ${newId}`,
- duration: '1 day',
- percentComplete: 0,
- start: '01/01/2009',
- finish: '01/01/2009',
- effortDriven: false
- };
- this.dataViewObj.addItem(newItem);
- this.gridObj.navigateBottom();
- this.dataset = this.dataViewObj.getItems();
- console.log('new item', newItem, 'parent', parentItemFound);
- console.warn(this.dataset)
- const resultSortedFlatDataset = sortFlatArrayByHierarchy(
- this.dataset,
- {
- parentPropName: 'parent',
- childPropName: 'children',
- direction: 'ASC',
- identifierPropName: 'id',
- sortByPropName: 'id',
- sortPropFieldType: FieldType.number,
+ const popItem = this.findRecursive(this.datasetHierarchical, x => x.file === 'pop', 'files');
+
+ if (popItem && Array.isArray(popItem.files)) {
+ popItem.files.push({
+ id: newId,
+ file: `pop${Math.round(Math.random() * 100)}.mp3`,
+ dateModified: new Date(),
+ size: Math.round(Math.random() * 100),
});
+ const sortedArray = sortHierarchicalArray(this.datasetHierarchical, { sortByPropName: 'file' });
+ this.datasetFlat = convertArrayHierarchicalToFlat(sortedArray, { childPropName: 'files' });
- // update dataset and re-render (invalidate) the grid
- this.slickgridLwc.dataset = resultSortedFlatDataset;
- this.dataset = resultSortedFlatDataset;
- this.gridObj.invalidate();
+ // update dataset and re-render (invalidate) the grid
+ this.slickgridLwc.dataset = this.datasetFlat;
+ this.gridObj.invalidate();
- // scroll to the new row
- const rowIndex = this.dataViewObj.getIdxById(newItem.id);
- this.gridObj.scrollRowIntoView(rowIndex, false);
+ // scroll to bottom of the grid
+ this.gridObj.navigateBottom();
+ }
}
collapseAll() {
- this.dataset.forEach((item) => item.__collapsed = true);
- this.slickgridLwc.dataset = this.dataset;
+ this.datasetFlat.forEach((item) => item.__collapsed = true);
+ this.slickgridLwc.dataset = this.datasetFlat;
this.gridObj.invalidate();
}
expandAll() {
- this.dataset.forEach((item) => item.__collapsed = false);
- this.slickgridLwc.dataset = this.dataset;
+ this.datasetFlat.forEach((item) => item.__collapsed = false);
+ this.slickgridLwc.dataset = this.datasetFlat;
this.gridObj.invalidate();
}
- recreateDataset() {
- const newDataset = this.mockDataset();
- this.slickgridLwc.dataset = newDataset;
- this.dataset = newDataset;
- this.gridObj.invalidate();
+ resort(inputFlatArray?: any[]) {
+ const datasetFlat = inputFlatArray || this.datasetFlat;
+
+ return sortFlatArrayByHierarchy(
+ datasetFlat,
+ {
+ parentPropName: '__parentId',
+ childPropName: 'files',
+ direction: 'ASC',
+ identifierPropName: 'id',
+ sortByPropName: 'id',
+ sortPropFieldType: FieldType.number,
+ });
}
handleOnClick(event: any) {
@@ -193,49 +224,41 @@ export class Example5 {
}
logExpandedStructure() {
- const explodedArray = convertArrayFlatToHierarchical(this.dataset, { parentPropName: 'parent', childPropName: 'children' });
- console.log('exploded array', explodedArray);
+ const explodedArray = convertArrayFlatToHierarchical(this.datasetFlat, { parentPropName: '__parentId', childPropName: 'files' });
+ console.log('exploded array', explodedArray/* , JSON.stringify(explodedArray, null, 2) */);
+
}
logFlatStructure() {
- const outputHierarchicalArray = convertArrayFlatToHierarchical(this.dataset, { parentPropName: 'parent', childPropName: 'children' });
- const outputFlatArray = convertArrayHierarchicalToFlat(outputHierarchicalArray, { childPropName: 'children' });
- console.log('flat array', outputFlatArray);
+ const outputHierarchicalArray = convertArrayFlatToHierarchical(this.datasetFlat, { parentPropName: '__parentId', childPropName: 'files' });
+ const outputFlatArray = convertArrayHierarchicalToFlat(outputHierarchicalArray, { childPropName: 'files' });
+ console.log('flat array', outputFlatArray/* , JSON.stringify(outputFlatArray, null, 2) */);
}
mockDataset() {
- let indent = 0;
- const parents = [];
- const data = [];
-
- // prepare the data
- for (let i = 0; i < NB_ITEMS; i++) {
- const d = (data[i] = {});
- let 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;
- }
-
- d['id'] = i;
- 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);
- }
- return data;
+ return [
+ {
+ id: 21, file: 'Documents', files: [
+ { id: 2, file: 'txt', files: [{ id: 3, file: 'todo.txt', dateModified: '2015-05-12T14:50:00', size: 0.7, }] },
+ {
+ id: 4, file: 'pdf', files: [
+ { id: 5, file: 'map.pdf', dateModified: '2015-05-21T10:22:00', size: 3.1, },
+ { id: 6, file: 'internet-bill.pdf', dateModified: '2015-05-12T14:50:00', size: 1.4, },
+ ]
+ },
+ { id: 7, file: 'xls', files: [{ id: 8, file: 'compilation.xls', dateModified: '2014-10-02T14:50:00', size: 2.3, }] },
+ { id: 9, file: 'misc', files: [{ id: 10, file: 'something.txt', dateModified: '2015-02-26T16:50:00', size: 0.4, }] },
+ ]
+ },
+ {
+ id: 11, file: 'Music', files: [{
+ id: 12, file: 'mp3', files: [
+ { id: 14, file: 'pop', files: [{ id: 15, file: 'theme.mp3', dateModified: '2015-03-01T17:05:00', size: 85, }] },
+ { id: 16, file: 'rock', files: [{ id: 17, file: 'soft.mp3', dateModified: '2015-05-13T13:50:00', size: 98, }] },
+ ]
+ }]
+ },
+ { id: 18, file: 'else.txt', dateModified: '2015-03-03T03:50:00', size: 90 },
+ ];
}
}
diff --git a/packages/vanilla-bundle-examples/src/main.ts b/packages/vanilla-bundle-examples/src/main.ts
index 1a9ecf7d7..2abdc51f6 100644
--- a/packages/vanilla-bundle-examples/src/main.ts
+++ b/packages/vanilla-bundle-examples/src/main.ts
@@ -1,5 +1,6 @@
import 'jquery';
import 'jquery-ui-dist/jquery-ui';
+import 'bulma/css/bulma.css';
import './styles.scss';
import { Renderer } from './renderer';
import * as SlickerModule from '@slickgrid-universal/vanilla-bundle';
diff --git a/packages/vanilla-bundle-examples/src/renderer.ts b/packages/vanilla-bundle-examples/src/renderer.ts
index d55726d1b..1173d0920 100644
--- a/packages/vanilla-bundle-examples/src/renderer.ts
+++ b/packages/vanilla-bundle-examples/src/renderer.ts
@@ -1,5 +1,3 @@
-import * as DOMPurify from 'dompurify';
-
export class Renderer {
view: any;
viewModel: any;
@@ -28,18 +26,18 @@ export class Renderer {
}
parseTemplate(viewTemplate: string) {
- return viewTemplate.replace(/([a-z]*){1}.(delegate)="(.*)"/gi, this.parseEventBinding.bind(this));
+ return viewTemplate.replace(/([a-z]*){1}.(delegate)="?(.*?)(\))/gi, this.parseEventBinding.bind(this));
}
- parseEventBinding(match: string, eventName: string, eventType: string, callbackFn: Function) {
+ parseEventBinding(match: string, eventName: string, eventType: string, callbackFn: string, lastChar: string) {
let output = '';
switch (eventType) {
case 'delegate':
- output = `${eventName.toLowerCase()}="window.${this.className}.${callbackFn}"`;
+ output = `${eventName.toLowerCase()}="window.${this.className.trim()}.${callbackFn.trim()}${lastChar}"`;
break;
}
- return DOMPurify.sanitize(output || '');
+ return (output || '');
}
render(html: string) {
diff --git a/packages/vanilla-bundle-examples/src/styles.scss b/packages/vanilla-bundle-examples/src/styles.scss
index 0f4817878..6de4c87b7 100644
--- a/packages/vanilla-bundle-examples/src/styles.scss
+++ b/packages/vanilla-bundle-examples/src/styles.scss
@@ -1,3 +1,2 @@
/* make sure to add the @import the SlickGrid Bootstrap Theme AFTER the variables changes */
@import '@slickgrid-universal/common/dist/styles/sass/slickgrid-theme-material.scss';
-// @import '@slickgrid-universal/common/dist/styles/css/slickgrid-theme-material.css';
diff --git a/packages/vanilla-bundle-examples/webpack.config.js b/packages/vanilla-bundle-examples/webpack.config.js
index 78355f71c..8cdb2ed72 100644
--- a/packages/vanilla-bundle-examples/webpack.config.js
+++ b/packages/vanilla-bundle-examples/webpack.config.js
@@ -1,4 +1,5 @@
const { ProvidePlugin } = require('webpack');
+const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
@@ -77,21 +78,23 @@ module.exports = ({ production } = {}, { extractCss, analyze, tests, hmr, port,
'window.jQuery': 'jquery',
'window.$': 'jquery',
}),
- ...when(!production, new HtmlWebpackPlugin({
+ new HtmlWebpackPlugin({
template: 'index.ejs',
metadata: {
// available in index.ejs //
title, baseUrl
}
- })),
- ...when(!production, new CopyWebpackPlugin([
+ }),
+ new CopyWebpackPlugin([
// { from: 'static', to: outDir, ignore: ['.*'] }, // ignore dot (hidden) files
{ from: `${srcDir}/favicon.ico`, to: 'favicon.ico' },
// { from: 'assets', to: 'assets' }
- ])),
+ ]),
...when(extractCss, new MiniCssExtractPlugin({ // updated to match the naming conventions for the js files
filename: production ? '[name].[contenthash].bundle.css' : '[name].[hash].bundle.css',
chunkFilename: production ? '[name].[contenthash].chunk.css' : '[name].[hash].chunk.css'
})),
+ // Note that the usage of following plugin cleans the webpack output directory before build.
+ new CleanWebpackPlugin(),
]
});