Customize a Dashboard

Hello, I come once again to ask for help to the seniors, this time a big help.
I have no experience in the stack they use to create their components and style them. I want to build a dashboard that will show various states of a custom asset I created.

I’ve already found your custom components and they work for me, but I’m having trouble updating variables and objects that are arranged in the HTML created by the render() function.

go to the code:

This is my customElement basic implementation

@customElement("page-custom-dashboard")
export class pageCustomDashboard extends Page<AppStateKeyed>  {

    static get styles() {
        // language=CSS
        return css`
            :host {
                display: flex;
                align-items: start;
                flex-direction: column;
                width: 100%;
                --or-icon-fill: var(--or-app-color4);
            }
        `;
    }

    get name(): string {
        return "custom-dashboard";
    }

    constructor(store: EnhancedStore<AppStateKeyed>) {
        super(store);
    }

    protected render(): TemplateResult | void {
        const addAsset = html`
            <h1>Test: <b>Dashboard</b></h1>            
            <div>
                <or-mwc-input id="assetid-input" .type="${InputType.TEXT}" .value="${(this.idAsset && this.idAsset) ? this.idAsset : undefined}" .label="Asset ID" pattern="\\w+" required @keyup="${(ev: KeyboardEvent) => this.WriteAssetId((ev.target as OrMwcInput).currentValue)}" @or-mwc-input-changed="${(ev: OrInputChangedEvent) => this.WriteAssetId(ev.detail.value)}"></or-mwc-input>
                <or-mwc-input  id ="assetid-button" class="button" .type="${InputType.BUTTON}" label="Kiosk by ID" @click="${() => this.callAction('GAI')}"></or-mwc-input>
            </div>
            ${   
                this.assetArray.map((asset: Asset)=> html `
                <div>
                    <p>${asset.id}</p>
                    <p>${asset.name}</p>
                    <p>${asset.realm}</p>
                </div>
                `
            )}
        `
        return html `${addAsset}`
     
    }

    @property()
    protected idAsset: string;
    @property()
    protected assetArray: Array<Asset> ;

And here is the basic function that makes the API request and minimally handles the data.

 protected WriteAssetId(assetId: string) {
        this.idAsset = assetId;
    };

    callAction(button:string): void {
        switch (button) {
            case "GAQ":     
            // this.byAssetQuery();                
                break;
            case "GAI":
                this.GetbyAssetId();            
                break;
            case "DAI":
                this.RemoveAsset();
                break;
    
            default:
                break;
        }
    }

    async GetbyAssetId() {
        const response = await manager.rest.api.AssetResource.get(this.idAsset);        
        let success = response.status === 204 || response.status === 200;

       if (success) {            
        this.assetArray.push(response.data);
       }
    }

If there is already data in:

    @property()
    protected assetArray: Array<Asset> = [];

everything works fine, but if I put some of aditional data in assetArray variable the map doesn’t render it.

I know this is probably the basics of the paradigm in which the platform was developed, but I would like your help to see where I am going wrong and in what direction I should go to become more proficient in this reactive paradigm.

Finally, what my dashboard will do is show different icons according to the state of some attributes. For example: one closed barrier (false) will show its closed icon, when opened it will show the respective icon. This multiplied by an undetermined number of kiosks (that’s why I need the map to create those structures according to the number of inputs)

Any help is welcome :slight_smile:

Hi!

The UI is not rendered (not calling the render() function) since LitElement does not detect changes when pushing to an array. It checks whether the state has changed, sadly not the content of the state.
To keep it simple, you could either:

  • Request an update manually this.requestUpdate() after you change the array.
  • or reinstantiate the array instead of pushing it this.assetArray = [...this.assetArray, response.data].
  • or modify the change detection with LitElements’ hasChanged to for example a JSON string comparison.

.
Looking at your code, you can by the way leave out the @keyup="" and @click="" functions,
since @or-input-changed will do all the work on any or-mwc-input component.
:wink:

.
Something else worth mentioning is that I’m busy with significantally improving the Insights page by adding a drag-and-drop builder to it. This makes it possible to create custom dashboards through the Manager UI, with custom widgets like yours. It’s on the OpenRemote roadmap, and a first version should be available quite soon. Don’t know whether it perfectly fits the use case but I thought it was nice to share. :grin:

2 Likes

Thanks for the quick response Martin, I’ll try to fix the code as soon as possible and I’ll post feedback.

And yes, I am very interested in its implementation, today the insight is very focused on timeseries and I would like to monitor the current state as in the example of the barrier I mentioned above.

When you have news of your implementation let me know :slight_smile:

@martin.peeters everything works fine after changing the implementation as per your guidance. It was really misunderstanding on my part about how this works. Thank you very much :smiley:

1 Like

Hello @martin.peeters ,
I’m going to take advantage of the fact that you’re developing for the Insight page and ask one thing :slightly_smiling_face:, I’m not able to get all the assets by their type using AssetQuery.

As an alternative I am making a request through the Rest API

 const response = await manager.rest.api.AssetResource.get(idAsset); 

now I need this to be recursive, like for example in a SetInterval.

This is probably not the best way (but unfortunately I don’t know anything about it and I’m short on time to show a proof of concept) so I decided to continue with what I know.

So… when doing SetInterval the ID is created but the function is not called;

  @property()
    protected refreshData = setInterval(this.refresDataSet, 5000, this.assetArray);

    refresDataSet(aAsset:Array<Asset>) {
        if (aAsset.length > 0) {
            console.log("refreshDataSet");
            aAsset.forEach(asset =>{
                this.getByAssetId(asset.id);
            });
        }
    }

Some advice about it? How I could get my data every 5 seconds, for example?

Hi!

This approach is of course not great but should be fine for a proof of concept.
Solving this should be nothing more than regular TypeScript code, with code that runs every X seconds.
Literally everything would work here; setTimeout(..., 5000), setInterval(), et cetera.

But back to your issue. Is perhaps this.assetArray undefined, or of a length == 0?
This may be caused by the LitElement lifecycle, where some instantiations are ran after each other.

If so, maybe try updateComplete or a different function to wait until this.assetArray is set.

constructor() {
    this.updateComplete.then(() => {
        setInterval(this.refresDataSet, 5000, this.assetArray);
    })
}

.
If not, have you tried running different code on setInterval, just to test whether that works?
Because the code itself looks fine to me.

2 Likes