Skip to content

Commit

Permalink
chore: remove Remote Example & replace with Import from CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Jan 19, 2025
1 parent 341a1a8 commit c998507
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 325 deletions.
87 changes: 47 additions & 40 deletions packages/demo/src/examples/slickgrid/example17.html
Original file line number Diff line number Diff line change
@@ -1,46 +1,53 @@
<h2>
${title}
<span class="float-end">
<a style="font-size: 18px"
target="_blank"
<div class="container-fluid">
<h2>
Example 17: Dynamically Create Grid from CSV / Excel import
<span class="float-end">
<a style="font-size: 18px" target="_blank"
href="https://github.com/ghiscoding/aurelia-slickgrid/blob/master/packages/demo/src/examples/slickgrid/example17.ts">
<span class="mdi mdi-link-variant"></span> code
</a>
</span>
</h2>
<div class="subtitle"
innerhtml.bind="subTitle"></div>
<span class="mdi mdi-link-variant"></span> code
</a>
</span>
<button
class="ms-2 btn btn-outline-secondary btn-sm btn-icon"
type="button"
data-test="toggle-subtitle"
click.trigger="toggleSubTitle()"
>
<span class="mdi mdi-information-outline" title="Toggle example sub-title details"></span>
</button>
</h2>

<hr>
<div if.bind="showSubTitle" class="subtitle">
Allow creating a grid dynamically by importing an external CSV or Excel file. This script demo will read the CSV file and will
consider the first row as the column header and create the column definitions accordingly, while the next few rows will be
considered the dataset. Note that this example is demoing a CSV file import but in your application you could easily implemnt
an Excel file uploading.
</div>

<div class="col-md-6"
style="margin-bottom: 15px">
<label>Octopart Catalog Search <small>(throttle search to 1.5sec)</small></label>
<input type="text"
class="form-control"
value.bind="search & throttle:1500">
</div>
<div>A default CSV file can be download <a id="template-dl" href.bind="templateUrl">here</a>.</div>

<div class="alert alert-danger col-md-7" role="alert">
<strong>Note:</strong>
this demo no longer displays any results because the WebAPI Key to connect and query the <b>Octopart Component
Search</b> is no longer valid. However the concept remains valid, which is to use your own Custom DataView
instead of the default SlickGrid DataView used by this library.
</div>
<div class="d-flex mt-5 align-items-end">
<div class="file-upload">
<label for="formFile" class="form-label">Choose a CSV file…</label>
<input class="form-control" type="file" data-test="file-upload-input" value.bind="uploadFileRef" change.trigger="handleFileImport($event)" />
</div>
<span class="mx-3">or</span>
<div>
<button id="uploadBtn" data-test="static-data-btn" class="btn btn-outline-secondary" click.trigger="handleDefaultCsv">
Use default CSV data
</button>
&nbsp;/
<button class="btn btn-outline-danger btn-sm ms-2" click.trigger="destroyGrid()">Destroy Grid</button>
</div>
</div>

<div class="alert alert-warning col-md-6"
role="alert"
if.bind="loading">
<i class="mdi mdi-sync mdi-spin"></i>
<span>Loading...</span>
</div>
<hr />

<aurelia-slickgrid grid-id="grid1"
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset"
custom-data-view.bind="customDataView"
on-aurelia-grid-created.trigger="aureliaGridReady($event.detail)"
on-viewport-changed.trigger="onViewportChanged()"
on-sort.trigger="onSort($event.detail.eventData, $event.detail.args)">
</aurelia-slickgrid>
<aurelia-slickgrid
if="value.bind: gridCreated; cache: false"
grid-id="grid17"
column-definitions.bind="columnDefinitions"
grid-options.bind="gridOptions"
dataset.bind="dataset">
</aurelia-slickgrid>
</div>
211 changes: 83 additions & 128 deletions packages/demo/src/examples/slickgrid/example17.ts
Original file line number Diff line number Diff line change
@@ -1,147 +1,102 @@
// import 'slickgrid/slick.remotemodel'; // SlickGrid Remote Plugin
import { bindable, BindingMode } from 'aurelia';
import { type Column, type GridOption, toCamelCase } from 'aurelia-slickgrid';
import { ExcelExportService } from '@slickgrid-universal/excel-export';

import {
type AureliaGridInstance,
type Column,
type Formatter,
type GridOption,
SlickEventHandler,
} from 'aurelia-slickgrid';

const brandFormatter: Formatter = (_row, _cell, _value, _columnDef, dataContext) => {
return dataContext && dataContext.brand && dataContext.brand.name || '';
};

const mpnFormatter: Formatter = (_row, _cell, _value, _columnDef, dataContext) => {
let link = '';
if (dataContext && dataContext.octopart_url && dataContext.mpn) {
link = `<a href="${dataContext.octopart_url}" target="_blank">${dataContext.mpn}</a>`;
}
return link;
};
const sampleDataRoot = 'assets/data';

export class Example17 {
@bindable({ mode: BindingMode.twoWay }) search = '';
private _eventHandler: any = new SlickEventHandler();

title = 'Example 17: Octopart Catalog Search - Remote Model Plugin';
subTitle = `
This example demonstrates how to use "slick.remotemodel.js" or any Remote implementation through an external Remote Service
<ul>
<li>Your browser might block access to the Octopart query, if you get "block content" then just unblock it.</li>
<li>If the demo throws some errors, try again later (there's a limit per day).</li>
<li>
Uses <a href="https://github.com/6pac/SlickGrid/blob/master/slick.remotemodel.js" target="_blank">slick.remotemodel.js</a>
which is hooked up to load search results from Octopart, but can easily be extended
to support any JSONP-compatible backend that accepts paging parameters.
</li>
<li>
This demo implements a custom DataView, however please note that you are on your own to implement all necessary DataView methods
for Sorting, Filtering, etc...
</li>
<li>
Soure code for this example is available <a href="https://github.com/ghiscoding/aurelia-slickgrid/blob/master/doc/github-demo/src/examples/slickgrid/example17.ts" target="_blank">here</a>
</li>
</ul>
`;
aureliaGrid!: AureliaGridInstance;
columnDefinitions: Column[] = [];
customDataView: any;
dataset = [];
gridObj: any;
gridOptions!: GridOption;
loaderDataView: any;
loading = false; // spinner when loading data

constructor() {
// define the grid options & columns and then create the grid itself
this.defineGrid();
// this.loaderDataView = new Slick.Data.RemoteModel!();
// this.customDataView = this.loaderDataView && this.loaderDataView.data;
}

attached() {
this.hookAllLoaderEvents();

// set default search
// this.search = 'switch';
// this.loaderDataView.setSearch(this.search);
gridCreated = false;
showSubTitle = true;
dataset: any[] = [];
paginationPosition: 'bottom' | 'top' = 'top';
templateUrl = `${sampleDataRoot}/users.csv`;
uploadFileRef = '';

destroyGrid() {
this.gridCreated = false;
}

detaching() {
// unsubscribe all SlickGrid events
this._eventHandler.unsubscribeAll();
handleFileImport(event: any) {
const file: File = event.target.files[0];
if (file.name.endsWith('.csv')) {
const reader = new FileReader();
reader.onload = (e: any) => {
const content = e.target.result;
this.dynamicallyCreateGrid(content);
};
reader.readAsText(file);
} else {
alert('File must be a CSV file');
}
}

aureliaGridReady(aureliaGrid: AureliaGridInstance) {
this.aureliaGrid = aureliaGrid;
this.gridObj = aureliaGrid.slickGrid; // grid object
// this.loaderDataView.setSort('score', -1);
// this.gridObj.setSortColumn('score', false);

// simulate a delayed search to preload the first page
window.setTimeout(() => this.searchChanged(this.search), 100);
handleDefaultCsv() {
const staticDataCsv = `First Name,Last Name,Age,Type\nBob,Smith,33,Teacher\nJohn,Doe,20,Student\nJane,Doe,21,Student`;
this.dynamicallyCreateGrid(staticDataCsv);
this.uploadFileRef = '';
}

defineGrid() {
this.columnDefinitions = [
{ id: 'mpn', name: 'MPN', field: 'mpn', formatter: mpnFormatter, width: 100, sortable: true },
{ id: 'brand', name: 'Brand', field: 'brand.name', formatter: brandFormatter, width: 100, sortable: true },
{ id: 'short_description', name: 'Description', field: 'short_description', width: 520 },
];
dynamicallyCreateGrid(csvContent: string) {
// dispose of any previous grid before creating a new one
this.gridCreated = false;

const dataRows = csvContent?.split('\n');
const colDefs: Column[] = [];
const outputData: any[] = [];

// create column definitions
dataRows.forEach((dataRow, rowIndex) => {
const cellValues = dataRow.split(',');
const dataEntryObj: any = {};

if (rowIndex === 0) {
// the 1st row is considered to be the header titles, we can create the column definitions from it
for (const cellVal of cellValues) {
const camelFieldName = toCamelCase(cellVal);
colDefs.push({
id: camelFieldName,
name: cellVal,
field: camelFieldName,
filterable: true,
sortable: true,
});
}
} else {
// at this point all column defs were created and we can loop through them and
// we can now start adding data as an object and then simply push it to the dataset array
cellValues.forEach((cellVal, colIndex) => {
dataEntryObj[colDefs[colIndex].id] = cellVal;
});

// a unique "id" must be provided, if not found then use the row index and push it to the dataset
if ('id' in dataEntryObj) {
outputData.push(dataEntryObj);
} else {
outputData.push({ ...dataEntryObj, id: rowIndex });
}
}
});

this.gridOptions = {
enableAutoResize: true,
autoResize: {
container: '#demo-container',
rightPadding: 10
},
enableCellNavigation: true,
enableColumnReorder: false,
enableGridMenu: false,
multiColumnSort: false
gridHeight: 300,
gridWidth: 800,
enableFiltering: true,
enableExcelExport: true,
externalResources: [new ExcelExportService()],
headerRowHeight: 35,
rowHeight: 33,
};
}

hookAllLoaderEvents() {
if (this._eventHandler && this._eventHandler.subscribe && this.loaderDataView && this.loaderDataView.onDataLoading && this.loaderDataView.onDataLoaded) {
this._eventHandler.subscribe(this.loaderDataView.onDataLoading, () => this.loading = true);
this._eventHandler.subscribe(this.loaderDataView.onDataLoaded, (_e: Event, args: any) => {
if (args && this.gridObj && this.gridObj.invalidateRow && this.gridObj.updateRowCount && this.gridObj.render) {
for (let i = args.from; i <= args.to; i++) {
this.gridObj.invalidateRow(i);
}
this.gridObj.updateRowCount();
this.gridObj.render();
this.loading = false;
}
});
}
}

searchChanged(newValue: string) {
if (newValue && this.gridObj && this.gridObj.getViewport && this.loaderDataView && this.loaderDataView.ensureData && this.loaderDataView.setSearch) {
const vp = this.gridObj.getViewport();
this.loaderDataView.setSearch(newValue);
this.loaderDataView.ensureData(vp.top, vp.bottom);
}
}

onSort(_e: Event, args: any) {
if (this.gridObj && this.gridObj.getViewport && this.loaderDataView && this.loaderDataView.ensureData && this.loaderDataView.setSort) {
const vp = this.gridObj.getViewport();
if (args && args.sortCol && args.sortCol.field) {
this.loaderDataView.setSort(args.sortCol.field, args.sortAsc ? 1 : -1);
}
this.loaderDataView.ensureData(vp.top, vp.bottom);
}
this.dataset = outputData;
this.columnDefinitions = colDefs;
console.log(this.columnDefinitions, this.dataset)
this.gridCreated = true;
}

onViewportChanged() {
if (this.gridObj && this.gridObj.getViewport && this.loaderDataView && this.loaderDataView.ensureData) {
const vp = this.gridObj.getViewport();
this.loaderDataView.ensureData(vp.top, vp.bottom);
}
toggleSubTitle() {
this.showSubTitle = !this.showSubTitle;
const action = this.showSubTitle ? 'remove' : 'add';
document.querySelector('.subtitle')?.classList[action]('hidden');
}
}
53 changes: 0 additions & 53 deletions packages/demo/src/examples/slickgrid/example43.html

This file was deleted.

Loading

0 comments on commit c998507

Please sign in to comment.