ThingsBoard front-end project background image component development

Preface

ThingsBoard is currently the most popular open source IoT platform on Github (14.4k Star), which can realize the rapid development, management and expansion of IoT projects. It is the best choice for IoT platforms for small, medium and micro enterprises.

This article describes how to develop a background image widget in the ThingsBoard front-end project.

product demand

Recently, I received a request from a product manager to add a background image under the components in the TB dashboard to achieve an effect similar to the following:

The entire interface of the TB dashboard supports background image settings. A single component only supports background color settings and does not support background image settings. I originally thought that adding a background image setting function would solve the problem, but I was told that there may be two components that are shared. The requirement for a background image is a bit troublesome...

solution

After some deliberation, I finally found a reasonable solution, which is to develop a picture component that can be dragged to the bottom of other components, and set the background of the upper component to transparent to achieve the desired effect.

I have experience in developing a new component. The difficulty lies in dragging components below other components. This is currently not supported by TB.

You can see in the developer tools that the elements of the two components z-index: 1have the same style and are on the same level, so they cannot overlap.

Looking at the project code, we found that this function uses the grid layout plug-in: angular-gridster2, so check whether its official website supports the overlapping function.

  • github: https://github.com/tiberiuzuld/angular-gridster2(1.2k) Oops, the star is not low yet.
  • Official documentation:https://tiberiuzuld.github.io/angular-gridster2

The official document is in English. With my poor English reading level, I finally found this function: https://tiberiuzuld.github.io/angular-gridster2/multiLayer. Thanks to the official document for its considerate demo function.

Allow items to show in layers Key parameters: allowMultiLayer: allow items show in layers.

If you’re sure the plan is feasible, let’s get started!

Background image widget

Component advanced settings

First, I defined the background image component as a type of Cards component library, so I ui-ngx\src\app\modules\home\components\widget\lib\settings\cardscreated the component settings file image-widget-settings.component.htmland in the directory image-widget-settings.component.ts.

where image-widget-settings.component.htmlcode:

<section class="tb-widget-settings" [formGroup]="imageWidgetSettingsForm">

  <fieldset class="fields-group">
    <legend class="group-title" translate>widgets.image.settings</legend>
    <div fxLayout.xs="column" fxLayout="column" fxLayoutGap="8px">
      <!--上传图片-->
      <tb-image-input label="{
   
   { 'widgets.image.imageUrl' | translate }}"
                      formControlName="imageUrl">
      </tb-image-input>
      <!--是否为背景图片-->
      <mat-slide-toggle formControlName="isUnderLayer" class="slide-block">
        {
   
   { 'widgets.image.isUnderLayer' | translate }}
      </mat-slide-toggle>
    </div>
  </fieldset>

</section>

Use the FormGroup form control imageWidgetSettingsFormto store data, imageUrluse the data field to store background images, and use the TB tb-image-inputupload image component. Another data field isUnderLayeris used to store whether it is set as a background image, that is, whether it can be dragged below other components. It is mat-slide-toggleimplemented using the slider switch component of the UI plug-in Material.

image-widget-settings.component.tscode show as below:

import { Component } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';

@Component({
  selector: 'tb-image-widget-settings',
  templateUrl: './image-widget-settings.component.html',
  styleUrls: ['./../widget-settings.scss']
})
export class ImageWidgetSettingsComponent extends WidgetSettingsComponent {

  /*FormGroup表单*/
  imageWidgetSettingsForm: FormGroup;

  constructor(protected store: Store<AppState>,
              private fb: FormBuilder) {
    super(store);
  }

  protected settingsForm(): FormGroup {
    return this.imageWidgetSettingsForm;
  }

  /*初始化数据字段*/
  protected defaultSettings(): WidgetSettings {
    return {
      imageUrl: '',
      isUnderLayer: true,
    };
  }

  /*数据字段设置*/
  protected onSettingsSet(settings: WidgetSettings) {
    this.imageWidgetSettingsForm = this.fb.group({
      imageUrl: [settings.imageUrl, []],
      isUnderLayer: [settings.isUnderLayer, []]
    });
  }

  /*数据字段验证*/
  protected updateValidators(emitEvent: boolean) {
    this.imageWidgetSettingsForm.get('imageUrl').updateValueAndValidity({emitEvent});
    this.imageWidgetSettingsForm.get('isUnderLayer').updateValueAndValidity({emitEvent});
  }
}

There is not much code, so I just posted it all, defining the component selector tb-image-widget-settings, defining the FormGroup form imageWidgetSettingsForm, initializing the data fields , imageUrland isUnderLayerthe rest of the data setting and validation functions are written in the same way as other component settings.

Finally, remember to introduce the declaration and export of Class ImageWidgetSettingsComponentin the component settings module file .widget-settings.module.ts

import {
  ImageWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/image-widget-settings.component';

@NgModule({
  declarations: [
  ...
  ImageWidgetSettingsComponent
  ],
  exports: [
  ...
  ImageWidgetSettingsComponent
  ]

export class WidgetSettingsModule {
}

export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsComponent>} = {
  ...
  'tb-image-widget-settings': ImageWidgetSettingsComponent
};

Component function display

After creating the advanced setting function of the component, let's show the effect of the component.

Create ui-ngx\src\app\modules\home\components\widget\libthree files image-widget.component.html, image-widget.component.tsand image-widget.component.css.

The code is as image-widget.component.htmlfollows:

<div class="bg-img" style="background-image: url('{
   
   {settings.imageUrl}}')">
</div>

It's super simple, because it's just a background image effect display, and the path of the background image is imageUrl.

image-widget.component.csscode show as below:

:host {
  display: flex;
  width: 100%;
  height: 100%;
  .bg-img{
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }
}

Css is used to set the width and height of the component and the style of the background image, background-size: 100% 100%;so that the background image can be stretched along with the container to achieve the effect of displaying the entire image.

image-widget.component.tscode show as below:

import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';

interface ImageWidgetSettings {
  imageUrl: string;
  isUnderLayer: boolean;
}

@Component({
  selector: 'tb-image-widget',
  templateUrl: './image-widget.component.html',
  styleUrls: ['./image-widget.component.css']
})
export class ImageWidgetComponent extends PageComponent implements OnInit {

  settings: ImageWidgetSettings;

  @Input()
  ctx: WidgetContext;

  constructor(protected store: Store<AppState>,
              protected cd: ChangeDetectorRef) {
    super(store);
  }

  ngOnInit(): void {
    this.ctx.$scope.imageWidget = this;
    this.settings = this.ctx.settings;
  }
}

The settingsfields store imageUrlthe sum of the widget settings isUnderLayer.

Like the advanced settings file, Class needs to be declared and exported in ImageWidgetComponentthe component module file .widget-components.module.ts

import { ImageWidgetComponent } from '@home/components/widget/lib/image-widget.component';

@NgModule({
  declarations: [
  ...
  ImageWidgetComponent
  ],
  exports: [
  ...
  ImageWidgetComponent
  ]

export class WidgetComponentsModule {
}

Set component level

Ahem, knock on the blackboard, next is the core part, how to set the level of the background image component so that it can be dragged to the bottom of other components.

First find angular-gridster2the code for option settings. In dashboard.component.tsthe file, add hierarchical options allowMultiLayer:

ngOnInit(): void {
    this.dashboardWidgets.parentDashboard = this.parentDashboard;
    if (!this.dashboardTimewindow) {
      this.dashboardTimewindow = this.timeService.defaultTimewindow();
    }
    this.gridsterOpts = {
      gridType: GridType.ScrollVertical,
      keepFixedHeightInMobile: true,
      disableWarnings: false,
      disableAutoPositionOnConflict: false,
      pushItems: false,
      swap: false,
      maxRows: 100,
      minCols: this.columns ? this.columns : 24,
      maxCols: 3000,
      maxItemCols: 1000,
      maxItemRows: 1000,
      maxItemArea: 1000000,
      outerMargin: true,
      margin: isDefined(this.margin) ? this.margin : 10,
      minItemCols: 1,
      minItemRows: 1,
      defaultItemCols: 8,
      defaultItemRows: 6,
      
      <!--新增参数-->
      allowMultiLayer: true,
      baseLayerIndex: 1,
      defaultLayerIndex: 2,
      maxLayerIndex: 10,
      
      resizable: {enabled: this.isEdit},
      draggable: {enabled: this.isEdit},
      itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
      itemInitCallback: (item, itemComponent) => {
        (itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
      }
    };
  }

allowMultiLayer: trueSet to allow layered overlap, because other components must be above the background image component, so the default layer is defaultLayerIndexset 2. According to the official documentation, the final z-index should be baseLayerIndex + layerIndex, so the level of the final component is baseLayerIndex + defaultLayerIndex, that is, the level It is 3. Open the control tool to check and it is exactly 3.

Then the question comes. This is to set the parameters of the entire grid layout. How to set the level of a single component is not given in the official documentation API.

So, I thought of programming for ChatGPT:

oh? setZIndex(),I see! Fortunately, I have ChatGPT. It's really... it's nonsense. It doesn't work at all if I try it! Fortunately, I believe you so much... There is no such method at all, it's really a serious fabrication... Forget it, seeing as you have helped me so many times before, I won't argue with you this time...

It is better to rely on others than to rely on yourself. I continued to study the official documents and found that there is a view source code button in the upper right corner... Click to read the source code and finally found out how to set it up layerIndex.

It turns out that just setting the data layerIndexis enough. Find the loop body data source and print it out:

Sure enough, we found the grid unit parameters cols, rows, y, x. We only need to find the corresponding background image component and set layerIndexthe properties. So how do we find it? isUnderLayerBy the way, the judgment is added to the method body in dashboard.component.tsthe file through the advanced settings field updateWidgets().

private updateWidgets() {
    this.dashboardWidgets.setWidgets(this.widgets, this.widgetLayouts);
    this.dashboardWidgets.doCheck();
    
    <!--背景图片部件层级设置-->
    this.dashboardWidgets.dashboardWidgets.forEach((widget) => {
      if (widget.widgetContext.widget.config.settings?.isUnderLayer) {
        widget.layerIndex = 1;
      }
    });
  }

Because I set the default value for other components layerIndexto 2, and the background image component is below the other components, so set a smaller value of 1.

Final effect demonstration

First, we need to add the background image component to the component library, log in to the system administrator account [email protected] / sysadmin, and log in to the system administrator account because after adding it, it will be displayed as a system component package by default.

Open the widget library menu, open the Cards widget package, click Add new widget type -> Create new widget type -> Static widget in the lower right corner to initialize the background image widget:

  1. Set the widget title, such as "Image"
  2. Set HTML:<tb-image-widget [ctx]="ctx"></tb-image-widget>
  3. Clear JavaScript content
  4. The widget.settings-form-selector in widget.widget-settings is set totb-image-widget-settings

The 2nd item [ctx]="ctx"is a must for component value transfer and cannot be omitted; the 4th item is tb-image-widget-settingsthe component advanced setting selector and cannot be filled in incorrectly.

Adding the widget Okay, let's add this widget in the dashboard.

Upload an image in the advanced settings and set whether it is a background image to Yes. After saving, let's check the component elements z-index.

The background image component z-indexis set to 2, and the next step is to witness the miracle!

You're done, Nice~

Conclusion

Since the audience of TB is very small, it is normal if you have not studied TB and cannot understand this article - just skip it. TB related articles are more of a record of my working knowledge. If It would be even better if it can help a small group of people~

Okay, the above is all the content of the development of the background image component of the ThingsBoard front-end project. I hope it will be helpful to you. If you have any questions, you can contact me through my blog https://echeverra.cn or the WeChat public account echeverra .

Have you learned to "waste"?

(over)


The article was first published on my blog https://echeverra.cn/tb3 . It is an original article. Please indicate the source when reprinting.

Welcome to follow my WeChat public account echeverra and learn and progress together! Resources and benefits will be given away from time to time!


Guess you like

Origin blog.csdn.net/weixin_41394875/article/details/133338780