bryntum / support

An issues-only repository for the Bryntum project management component suite which includes powerful Grid, Scheduler, Calendar, Kanban Task Board and Gantt chart components all built in pure JS / CSS / TypeScript
https://www.bryntum.com
53 stars 6 forks source link

[SALESFORCE] RowExpander doesn't expand records in Salesforce #9746

Closed chuckn0rris closed 1 day ago

chuckn0rris commented 1 month ago

RowExpander doesn't work in Salesforce because of security restrictions

Forum post

Hi,

/* globals bryntum : true */
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import GRID from '@salesforce/resourceUrl/bryntum_grid';
import { columns, data } from './data';

export default class Grid_component extends LightningElement {
    renderedCallback() {
        if (this.bryntumInitialized) {
            return;
        }
        this.bryntumInitialized = true;

        Promise.all([
            loadScript(this, GRID + '/grid.lwc.module.js'),
            loadStyle(this, GRID + '/grid.stockholm.css'),
        ])
            .then(() => {
                this.createGrid();
            });
    }

    createGrid() {
        window.grid = new bryntum.grid.Grid({
            features : {
                regionResize : true,

                group : {
                    field : 'skills.skill'
                },

                rowExpander : {
                    // When configuring different Widgets for different Grid regions, the different widget configuration objects
                    // needs to be wrapped in an object with the region names as properties
                    widget : {
                        // This is the widget configuration for the left region
                        locked : {
                            type  : 'container',
                            cls   : 'action-panel',
                            items : [
                                {
                                    type    : 'button',
                                    text    : 'Add skill',
                                    width   : '100%',
                                    onClick : ({ source }) => {
                                        const
                                            { rowExpander } = source.up('grid').features,
                                            // With the 'getExpandedRecord' function you can get the expanded record that the
                                            // current widget belongs to
                                            record          = rowExpander.getExpandedRecord(source.owner),
                                            { skills }      = record,
                                            id             = skills.last?.id || 0;

                                        skills.add({
                                            id       : id + 1,
                                            skill    : 'New skill',
                                            level    : 1,
                                            active   : true,
                                            verified : false
                                        });
                                    }
                                },
                                {
                                    type    : 'button',
                                    text    : 'Clear skills',
                                    width   : '100%',
                                    onClick : ({ source }) => {
                                        const record = source.up('grid').features.rowExpander.getExpandedRecord(source.owner);

                                        record.skills.removeAll();
                                    }
                                },
                                {
                                    type    : 'button',
                                    text    : 'Deactivate',
                                    width   : '100%',
                                    onClick : ({ source }) => {
                                        // This button toggles the "Active" value of the expanded record
                                        const record = source.up('grid').features.rowExpander.getExpandedRecord(source.owner);

                                        record.active = !record.active;
                                    },
                                    listeners : {
                                        paint({ source }) {
                                            // Set the Activate/Deactivate button's text upon expanding the row
                                            const
                                                button         = this,
                                                grid           = source.up('grid'),
                                                expandedRecord = grid.features.rowExpander.getExpandedRecord(source.owner);

                                            if (!expandedRecord.active) {
                                                button.text = 'Activate';
                                            }

                                            // When record changes (either from clicking the button, or editing the outer grid)
                                            // Update the Activate/Deactivate button's text
                                            grid.store.on({
                                                update({ record }) {
                                                    if (expandedRecord === record) {
                                                        button.text = record.active ? 'Deactivate' : 'Activate';
                                                    }
                                                },
                                                // To remove this listener when the row collapses
                                                thisObj : button
                                            });
                                        }
                                    }
                                }
                            ]
                        },
                        // This is the widget configuration for the right region
                        normal : {
                            type           : 'grid',
                            cls            : 'skills-grid',
                            // The dataField config needs to be placed inside the widget config object when there is multiple
                            // regions and widgets
                            dataField      : 'skills',
                            autoHeight     : true,
                            minHeight      : null,
                            emptyText      : 'No skills assigned',
                            fillLastColumn : false,
                            columns        : [
                                { field : 'id', text : 'No.', type : 'number', width : 100, align : 'center' },
                                { field : 'skill', text : 'Skill', flex : 1, maxWidth : 400 },
                                { field : 'level', text : 'Level', type : 'number', width : 100, align : 'center' },
                                { field : 'verified', text : 'Verified', type : 'check', width : 100 }
                            ]
                        }
                    }
                }
            },

            columns : [
                { field : 'employeeId', text : 'Employee no.', type : 'number', width : 110, region : 'locked', align : 'center', renderer : ({ value, record }) => record.isPhantom ? '' : value },
                { field : 'name', text : 'Name', width : 150, region : 'locked' },
                { field : 'city', text : 'City', width : 170, region : 'normal' },
                { field : 'age', text : 'Age', type : 'number', width : 120, region : 'normal', align : 'center' },
                { field : 'start', text : 'Start', type : 'date', width : 180, region : 'normal' },
                {
                    field      : 'email',
                    text       : 'Email',
                    width      : 270,
                    region     : 'normal',
                    htmlEncode : false,
                    renderer   : ({ value }) =>  {
                        return bryntum.grid.StringHelper.xss`<i class="b-fa b-fa-envelope"></i><a href="mailto:${value}">${value}</a>`;
                    }
                },
                { field : 'active', text : 'Active', type : 'check', width : 120, region : 'normal' },
                { field : 'notes', text : 'Notes', width : 1000, region : 'normal' }
            ],

            appendTo: this.template.querySelector('.container'),
            store : {
                fields : [
                    { name : 'skills', type : 'store', storeClass : window.bryntum.grid.Store, modelClass : window.bryntum.grid.GridRowModel }, 'active', 'notes'
                ]
            }
        });

        const data = window.bryntum.grid.StringHelper.safeJsonParse('[{"skills":[],"_groupValue":"Angular","skill":"Angular"},{"id":1,"skills":[{"id":1,"skill":"Angular","level":3,"verified":true},{"id":2,"skill":"BASIC","level":3,"verified":false},{"id":3,"skill":"Vue","level":1,"verified":true},{"id":4,"skill":"JavaScript","level":1,"verified":true}],"title":"Row 0","name":"Don A Taylor","firstName":"Don","surName":"Taylor","city":"Montreal","team":"Paris Tigers","age":30,"food":"Salad","color":"Black","score":880,"rank":99,"start":"2019-02-06T00:00:00+03:00","finish":"2019-02-21T00:00:00+03:00","time":"2020-01-01T02:10:00+03:00","percent":13,"done":false,"rating":2,"active":true,"relatedTo":2,"notes":"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.","employeeId":1,"email":"don.a.taylor@example.com"},{"id":"2_link_819172dd-4ed0-4e9b-89ed-65533c10e71b","skills":[{"id":1,"skill":"PASCAL","level":2,"verified":true},{"id":2,"skill":"Java","level":3,"verified":true},{"id":3,"skill":"Angular","level":2,"verified":true},{"id":4,"skill":"PHP","level":2,"verified":true}],"title":"Row 1","name":"Jane B Scott","firstName":"Jane","surName":"Scott","city":"Stockholm","team":"Barcelona Ducks","age":32,"food":"Burger","color":"Orange","score":640,"rank":23,"start":"2019-01-23T00:00:00+03:00","finish":"2019-01-30T00:00:00+03:00","time":"2020-01-01T20:00:00+03:00","percent":81,"done":false,"rating":2,"active":false,"relatedTo":2,"notes":"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","employeeId":2,"email":"jane.b.scott@example.com"},{"skills":[],"_groupValue":"BASIC","skill":"BASIC"},{"id":"1_link_8369bf4b-3d53-4d87-953c-436ca12c4f03","skills":[{"id":1,"skill":"Angular","level":3,"verified":true},{"id":2,"skill":"BASIC","level":3,"verified":false},{"id":3,"skill":"Vue","level":1,"verified":true},{"id":4,"skill":"JavaScript","level":1,"verified":true}],"title":"Row 0","name":"Don A Taylor","firstName":"Don","surName":"Taylor","city":"Montreal","team":"Paris Tigers","age":30,"food":"Salad","color":"Black","score":880,"rank":99,"start":"2019-02-06T00:00:00+03:00","finish":"2019-02-21T00:00:00+03:00","time":"2020-01-01T02:10:00+03:00","percent":13,"done":false,"rating":2,"active":true,"relatedTo":2,"notes":"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.","employeeId":1,"email":"don.a.taylor@example.com"},{"id":"3_link_2162cfdf-e110-4798-8c6b-87fa46e53916","skills":[{"id":1,"skill":"JavaScript","level":3,"verified":true},{"id":2,"skill":"Vue","level":2,"verified":false},{"id":3,"skill":"BASIC","level":2,"verified":false}],"title":"Row 2","name":"Mike C Ewans","firstName":"Mike","surName":"Ewans","city":"Montreal","team":"Washington Dogs","age":15,"food":"Carbonara","color":"Teal","score":260,"rank":14,"start":"2019-02-07T00:00:00+03:00","finish":"2019-02-10T00:00:00+03:00","time":"2020-01-01T02:55:00+03:00","percent":11,"done":false,"rating":3,"active":true,"relatedTo":2,"notes":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?","employeeId":3,"email":"mike.c.ewans@example.com"},{"skills":[],"_groupValue":"Java","skill":"Java"},{"id":"2_link_07c629f5-bd70-4bea-af84-53fd4fa3a743","skills":[{"id":1,"skill":"PASCAL","level":2,"verified":true},{"id":2,"skill":"Java","level":3,"verified":true},{"id":3,"skill":"Angular","level":2,"verified":true},{"id":4,"skill":"PHP","level":2,"verified":true}],"title":"Row 1","name":"Jane B Scott","firstName":"Jane","surName":"Scott","city":"Stockholm","team":"Barcelona Ducks","age":32,"food":"Burger","color":"Orange","score":640,"rank":23,"start":"2019-01-23T00:00:00+03:00","finish":"2019-01-30T00:00:00+03:00","time":"2020-01-01T20:00:00+03:00","percent":81,"done":false,"rating":2,"active":false,"relatedTo":2,"notes":"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","employeeId":2,"email":"jane.b.scott@example.com"},{"skills":[],"_groupValue":"JavaScript","skill":"JavaScript"},{"id":"1_link_f752fd2c-756b-44e9-a1dc-d9c9a6ce91b7","skills":[{"id":1,"skill":"Angular","level":3,"verified":true},{"id":2,"skill":"BASIC","level":3,"verified":false},{"id":3,"skill":"Vue","level":1,"verified":true},{"id":4,"skill":"JavaScript","level":1,"verified":true}],"title":"Row 0","name":"Don A Taylor","firstName":"Don","surName":"Taylor","city":"Montreal","team":"Paris Tigers","age":30,"food":"Salad","color":"Black","score":880,"rank":99,"start":"2019-02-06T00:00:00+03:00","finish":"2019-02-21T00:00:00+03:00","time":"2020-01-01T02:10:00+03:00","percent":13,"done":false,"rating":2,"active":true,"relatedTo":2,"notes":"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.","employeeId":1,"email":"don.a.taylor@example.com"},{"id":3,"skills":[{"id":1,"skill":"JavaScript","level":3,"verified":true},{"id":2,"skill":"Vue","level":2,"verified":false},{"id":3,"skill":"BASIC","level":2,"verified":false}],"title":"Row 2","name":"Mike C Ewans","firstName":"Mike","surName":"Ewans","city":"Montreal","team":"Washington Dogs","age":15,"food":"Carbonara","color":"Teal","score":260,"rank":14,"start":"2019-02-07T00:00:00+03:00","finish":"2019-02-10T00:00:00+03:00","time":"2020-01-01T02:55:00+03:00","percent":11,"done":false,"rating":3,"active":true,"relatedTo":2,"notes":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?","employeeId":3,"email":"mike.c.ewans@example.com"},{"skills":[],"_groupValue":"PASCAL","skill":"PASCAL"},{"id":2,"skills":[{"id":1,"skill":"PASCAL","level":2,"verified":true},{"id":2,"skill":"Java","level":3,"verified":true},{"id":3,"skill":"Angular","level":2,"verified":true},{"id":4,"skill":"PHP","level":2,"verified":true}],"title":"Row 1","name":"Jane B Scott","firstName":"Jane","surName":"Scott","city":"Stockholm","team":"Barcelona Ducks","age":32,"food":"Burger","color":"Orange","score":640,"rank":23,"start":"2019-01-23T00:00:00+03:00","finish":"2019-01-30T00:00:00+03:00","time":"2020-01-01T20:00:00+03:00","percent":81,"done":false,"rating":2,"active":false,"relatedTo":2,"notes":"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","employeeId":2,"email":"jane.b.scott@example.com"},{"skills":[],"_groupValue":"PHP","skill":"PHP"},{"id":"2_link_7b635a7c-b7fb-4983-8887-4f0529a4777f","skills":[{"id":1,"skill":"PASCAL","level":2,"verified":true},{"id":2,"skill":"Java","level":3,"verified":true},{"id":3,"skill":"Angular","level":2,"verified":true},{"id":4,"skill":"PHP","level":2,"verified":true}],"title":"Row 1","name":"Jane B Scott","firstName":"Jane","surName":"Scott","city":"Stockholm","team":"Barcelona Ducks","age":32,"food":"Burger","color":"Orange","score":640,"rank":23,"start":"2019-01-23T00:00:00+03:00","finish":"2019-01-30T00:00:00+03:00","time":"2020-01-01T20:00:00+03:00","percent":81,"done":false,"rating":2,"active":false,"relatedTo":2,"notes":"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","employeeId":2,"email":"jane.b.scott@example.com"},{"skills":[],"_groupValue":"Vue","skill":"Vue"},{"id":"1_link_bef7c540-c8aa-4258-8bf8-44b8ca5f7cc5","skills":[{"id":1,"skill":"Angular","level":3,"verified":true},{"id":2,"skill":"BASIC","level":3,"verified":false},{"id":3,"skill":"Vue","level":1,"verified":true},{"id":4,"skill":"JavaScript","level":1,"verified":true}],"title":"Row 0","name":"Don A Taylor","firstName":"Don","surName":"Taylor","city":"Montreal","team":"Paris Tigers","age":30,"food":"Salad","color":"Black","score":880,"rank":99,"start":"2019-02-06T00:00:00+03:00","finish":"2019-02-21T00:00:00+03:00","time":"2020-01-01T02:10:00+03:00","percent":13,"done":false,"rating":2,"active":true,"relatedTo":2,"notes":"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.","employeeId":1,"email":"don.a.taylor@example.com"},{"id":"3_link_ff35202f-8556-4898-9416-977de6fca0d7","skills":[{"id":1,"skill":"JavaScript","level":3,"verified":true},{"id":2,"skill":"Vue","level":2,"verified":false},{"id":3,"skill":"BASIC","level":2,"verified":false}],"title":"Row 2","name":"Mike C Ewans","firstName":"Mike","surName":"Ewans","city":"Montreal","team":"Washington Dogs","age":15,"food":"Carbonara","color":"Teal","score":260,"rank":14,"start":"2019-02-07T00:00:00+03:00","finish":"2019-02-10T00:00:00+03:00","time":"2020-01-01T02:55:00+03:00","percent":11,"done":false,"rating":3,"active":true,"relatedTo":2,"notes":"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?","employeeId":3,"email":"mike.c.ewans@example.com"}]');
        window.grid.store.data = data;

        window.grid.features.rowExpander.expand(grid.store.getAt(7));
    }
JockeLindberg commented 2 weeks ago

attachShadow is prohibited in LWC non LWS. Fix for this issue will be to update docs with information about this limitations.