Please note I'm fairly new to LWC and even SalesForce (like barely 2 weeks into it).
I'm trying to use SlickGrid with LWC, note that this jQuery lib was created over 10 years ago, and I got that working and displaying data nicely. However I'm having a few issues in regards to what are supposed to be POJO (Plain Old Javascript Object) in the lib but being transformed into immutable Proxy objects. Are there any ways to somehow bypass that or disable that in certain sections? SlickGrid uses object pointer references in a few areas and it's totally normal in that lib to set some object properties by using the object reference but LWC seems to be converting every objects in Proxy objects. Are there any ways to have LWC not do that in the external lib?
To show a bit of code that I got working, I use my new component with this template
<template>
<div class="demo-container">
<c-slickgrid grid-class="myGrid" columns={columnDefinitions} options={gridOptions} records={dataset}>
</c-slickgrid>
</div>
</template>
and on the ViewModel side, I use it with this (notice the editor: Editors.text
, it's a JS class not instantiated, SlickGrid will instantiate it when required on the fly)
import { LightningElement, track } from 'lwc';
import { FieldType } from './models/fieldType';
import { Editors } from 'c/editors';
export default class MyNewComponent extends LightningElement {
@track columnDefinitions;
@track gridOptions;
@track dataset;
slickgridInitialized = false;
renderedCallback() {
if (this.slickgridInitialized) {
return;
}
this.slickgridInitialized = true;
this.initializeGrid();
this.dataset = this.loadData(500);
}
initializeGrid() {
this.columnDefinitions = [
{
id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string,
editor: Editors.longText,
},
{ id: 'duration', name: 'Duration', field: 'duration', sortable: true, type: FieldType.string },
{
id: 'percentComplete', name: '% Complete', field: 'percentComplete', sortable: true, type: FieldType.number,
editor: Editors.text,
},
{ id: 'start', name: 'Start', field: 'start', sortable: true },
{ id: 'finish', name: 'Finish', field: 'finish', sortable: true },
{ id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', sortable: true },
];
this.gridOptions = {
autoCommitEdit: true,
editable: true,
enableAutoSizeColumns: true,
enableAutoResize: true,
enableCellNavigation: true,
enableSorting: true,
};
}
loadData(count) {
const dataset = [];
for (let i = 0; i < count; i++) {
dataset[i] = {
id: 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)
};
}
return dataset;
}
}
Then in my slickgrid component, I build and create the grid with
export default class Slickgrid extends LightningElement {
@api columns;
@api options;
@api records;
async renderedCallback() {
if (this._slickgridInitialized) {
return;
}
this.gridClassName = `slickgrid-container ${this.gridClass}`;
this._slickgridInitialized = true;
await loadStyle(this, `${slickgrid_bundle}/slickgrid-theme-salesforce.css`);
// load all SlickGrid CSS and Scripts here
this.initialization();
this.loadData(this._dataset);
}
initialization() {
this._gridOptions = this.convertProxyObjectToPojo(this.mergeGridOptions(this._gridOptions));
const gridElm = this.template.querySelector(`.${this.gridClass}`);
this._dataView = this.convertProxyObjectToPojo(new Slick.Data.DataView());
this.columnDefinitions = this.convertProxyObjectToPojo(this._columnDefinitions);
const grid = new Slick.Grid(gridElm, this._dataView, this.columnDefinitions, this._gridOptions);
}
convertProxyObjectToPojo(proxyObj) {
return _.cloneDeep(proxyObj);
}
}
I first had some issues with the editor: Editors.longText
but I found that if I use Lodash cloneDeep
method, I can transform a proxy object into a POJO (with this.columnDefinitions = this.convertProxyObjectToPojo(this._columnDefinitions);
). Using this method works for some areas of the code but not everywhere. The main problem I'm facing is especially with the Editors in SlickGrid, they are defined in a way that they uses pointer references in their code but LWC makes all the objects in SlickGrid as Proxy and immutable objects and it totally blocks SlickGrid typical usage of object pointers. SlickGrid uses pointer references in a few areas and it's normal for them but does that mean I can't use SlickGrid at all in LWC? That would be a shame if that's the case because I created a wrapper in Angular (Angular-Slickgrid) without any issues and it totally rocks with TypeScript, I was trying to do the same kind of wrapper with LWC but I'm blocked.
The error shown below happens because in SlickGrid we update the object property by reference with this piece of code (pulled from this GitHub)
// item is pointer reference
this.applyValue = function (item, newValue) {
item[args.column.field] = newValue;
};
but LWC complains it's immutable and throws the error shown below. I tried to use a spread operator, that gets rid of the error but causes other problems since this creates a new object and SlickGrid now has 2 objects representing 1 item... so that doesn't work either
this.applyValue = function (item, newValue) {
item = { ...item, [args.column.field]: newValue };
};
I also tried this to do with with Object.defineProperty
like below but that too fails however with a slightly different error
Object.defineProperty(item, fieldName, { writable: true, configurable: true, value: newValue });
Ideally what I'd like to do is to tell SalesForce that hey this external library, don't touch it with Proxy objects and don't make it immutable, let me handle that myself. How can I do that? Is that possible? Any alternatives?
I think this LWC Dev Guide Objects Passed to Components Are Read-Only explains why but in my case, I really need to let it mutate. Can I somehow bypass that so I can get back to work?
EDIT
I can confirm that the issue is because of the Child <c-slickgrid>
tries to modify the object property and that is not allowed. I've put everything in 1 LWC and I don't have the error, but this won't be helpful in my case since I wouldn't be able to use it as a child <c-slickgrid>
like I want. So again, are there any ways to bypass this??