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

Using state provider in advanced angular demo throws exception #5853

Open bmblb opened 1 year ago

bmblb commented 1 year ago

Reported by client.

To reproduce on advanced angular demo:

  1. Setup state provider:
    
    // app/gantt/gantt.config.ts
    import { StateProvider } from '@bryntum/gantt';

const ganttConfig = { stateProvider : StateProvider.setup('local'), stateId : 'foo',

2. Use state provider in the app:

// app/gantt/gantt.component.html [stateId] = "ganttConfig.stateId" [stateProvider] = "ganttConfig.stateProvider"

3. Build the app and serve it:

npm run build serve -s dest/advanced


Run the app and see this exception:

Uncaught TypeError: Super constructor null of local is not a constructor


It points to this line in the code:
![image](https://user-images.githubusercontent.com/45400762/210074638-fe33cd58-808c-4dfe-a92c-86897c0b0e22.png)

Which appears to caused by the layout of StateProvider.js, where we extend class before we actually declare it:

Local = class extends StateStorage { constructor(stateProvider) { super(); this.prefix = stateProvider.prefix || ''; }

bmblb commented 1 year ago

Workaround is to define own storage:

// gantt.config.ts
import { StateProvider, StateStorage } from '@bryntum/gantt';

const
    empty = () => Object.create(null),
    getKeys = (prefix, pos = 0) => {
        const
            keys = [],
            count = localStorage.length;

        for (let key, i = 0; i < count; ++i) {
            key = localStorage.key(i);

            key.startsWith(prefix) && keys.push(key.slice(pos));
        }

        return keys;
    };

//@ts-ignore
export class LocalStorage extends StateStorage {
    prefix = '';

    constructor(stateProvider) {
        super();
        this.prefix = stateProvider.prefix || '';
    }

    get isLocal() {
        return true;
    }

    //@ts-ignore
    get data() {
        const
            data = empty(),
            keys = this.keys;

        for (const key of keys) {
            data[key] = this.getItem(key);
        }

        return data;
    }

    //@ts-ignore
    get keys() {
        return getKeys(this.prefix, this.prefix.length);
    }

    clear() {
        // It's important that we only clear our own StateProvider's keys, not all of localStorage. We get the
        // full keys not the suffixes since we're just going to call removeItem() with them...
        const keys = getKeys(this.prefix);

        for (const key of keys) {
            localStorage.removeItem(key);
        }
    }

    getItem(key) {
        const value = localStorage.getItem(this.prefix + key);

        // We handle the JSON translation at this layer because the Memory storage does not do any such pickling
        // of data but localStorage only handles strings
        return (value === null) ? value : JSON.parse(value);
    }

    removeItem(key) {
        return localStorage.removeItem(this.prefix + key);
    }

    setItem(key, value) {
        return localStorage.setItem(this.prefix + key, JSON.stringify(value));
    }
}

const ganttConfig = {
    //@ts-ignore
    stateProvider : StateProvider.setup(LocalStorage),
...