How to bypass immutable proxy objects when using an external and old jquery library in LWC

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

Answer

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

Attribution
Source : Link , Question Author : ghiscoding , Answer Author : Christian Szandor Knapp

Leave a Comment