5

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?

SlickGrid Immutable Proxy Error

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??

5
  • Not to sound like yet another detractor, but LWC's own datatable is powerful enough and caters most things really well: developer.salesforce.com/docs/component-library/bundle/… AND it looks native to SF LEX
    – zaitsman
    Commented Feb 21, 2020 at 12:18
  • That doesn't help with my question, but to answer your question, the SF datatable is good for basic grids but falls short when in need for a complex grid. For example there's no column pinning, grid menu, data grouping, multi-column sort, multi-column filters and a few other things... Maybe one day it will have it all but for now it's still just a basic grid and lacks feature we require for some complex pages of our project
    – ghiscoding
    Commented Feb 21, 2020 at 14:27
  • Unfortunately lightning-container is yet to be available for lwc. Commented Feb 21, 2020 at 18:20
  • oh that seems interesting and possibly helpful, thanks for pointing that out and hopefully will be available in the near future.
    – ghiscoding
    Commented Feb 21, 2020 at 18:50
  • The source code for lightning datatable is soon to be available at github.com/salesforce/base-components-recipes Commented Feb 21, 2020 at 21:14

1 Answer 1

3

No, there are none to my knowledge.

In addition, any such workarounds would bypass Salesforce's security and could break anytime because Salesforce might fix such a hole without notice.

Enterprise Secure JavaScript Components co-existing on the same page is the main feature of Locker Service. See also this question

1
  • ok thanks, I think the only other option I have would be to create my component in a separate JS file and call that when I want to use it. Not as good as having a real LWC component, but I think that would be the only option I see so far. Doing it with an external JS and loading it in an LWC component when I need it would be in 1 component and hence be the parent and not the child.
    – ghiscoding
    Commented Feb 21, 2020 at 21:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .