Компоненты Q.Palette
Виджет

Виджет

Пререквизиты

Для разработки виджетов и использования их совместно с QWidget требуется версия пакета @diasoft/qpalette-visual не ниже 3.3.6.

Разработка виджетов

Виджеты разрабатываются аналогично подходу, описанному в статье Лукап в разделе "Разработка виджета для лукапа".

В данной статье предполагается, что виджет представлен модулемTaskProcessingWidgetModuleи компонентомTaskProcessingWidgetComponent.

Для того, чтобы разрабатываемый модуль стал виджетом, необходимо его унаследовать от QWidgetModuleAbstract из @diasoft/qpalette-visual и определить геттер widgetConfig, возвращающий структуру QWidgetConfig с информацией о виджете, как в примере ниже:

import {QWidgetConfig, QWidgetManager, QWidgetModule, QWidgetModuleAbstract} from '@diasoft/qpalette-visual';
 
@NgModule({
  declarations: [SomeWidgetComponent],
  exports: [SomeWidgetComponent],
  imports: [
    // ...
    QWidgetModule
  ],
  bootstrap: []
})
export class SomeWidgetModule extends QWidgetModuleAbstract {
  get widgetConfig(): QWidgetConfig {
    return {
      component: SomeWidgetComponent,
      elementName: 'some-selector'
    };
  }
 
  constructor(
    protected injector: Injector,
    protected widgetManager: QWidgetManager
  ) {
    super(injector, widgetManager);
  }
}

Необходимо:

  • В component указать компонент виджета.
  • В elementName указать имя компонента (селектор).

Все остальные шаги по сборке и деплою - такие же, как и в статье Лукап.

Но стоит обратить внимание, что существуют различия меджу виджетами:

  • <q-widget> - подключает обычные виджеты, а именно любые куски кода, обернутые в веб-компонент, у которых есть инпуты и аутпуты.
  • <q-lookup> - подключает виджеты лукапа, т.е. функционал поиска, обернутый в веб-компонент, со строго определенным программным интерфейсом.

Подключение виджета в целевом продукте

Для начала, убедитесь в том, что конфигурация приложения содержит

{
  // ...
  "widgets": {
    "bundleUrl": "/api/$service/$component/widgets/$widget/main.js"
  }
}

В AppModule вашего проекта импортируется

import {QWidgetModule} from '@diasoft/qpalette-visual';
import {QCoreService} from '@diasoft/qpalette-core';
 
@NgModule({
  imports: [
    // ...
    QWidgetModule.forRoot(QCoreService.config.widgets)
  ]
})
export class AppModule {
  // ...
}

Для подключения виджета, необходимо импортировать модуль QWidgetModule из @diasoft/qpalette-visual в модуль, в котором он будет вызываться:

import {QWidgetModule} from '@diasoft/qpalette-visual';
 
@NgModule({
  imports: [
    // ...
    QWidgetModule
  ]
})
export class SomeModule {
}

Затем использовать q-widget для загрузки разработанного виджета, как в примере ниже:

Параметры q-widget:

  • source - адрес виджета в виде строки формата service:component:widget (или относительный путь до бандла виджета в случае с библиотечным UI, см. следующий раздел). Будет преобразован в URL-адрес: /<service>/<component>/widgets/<widget>/main.js
  • inputs - набор input-параметров виджета (ключ-значение). Реализует интерфейс QWidgetInputs.
  • outputs - обработчики событий виджета. Реализует интерфейс QWidgetOutputs.
  • widgetLoaded - событие, срабатывающее при загрузке виджета (не возвращает никаких данных).

Ниже приведён пример кода компонента, шаблон которого приведён ранее:

import {QWidgetInputs, QWidgetOutputs} from '@diasoft/qpalette-visual';
 
@Component({
  // ...
})
export class SomeComponent implements OnInit {
  source = 'service:component:widget';
 
  widgetInputs: QWidgetInputs = {
    actions: ['action-one', 'action-two'],
    cancelAllowed: false
  };
 
  widgetEventHandlers: QWidgetOutputs = {
    taskActionTriggered: this.onWidgetActionSelected.bind(this),
    cancel: this.onWidgetCancel.bind(this),
  };
 
  onWidgetActionSelected(event: any): void {
    console.log(event.detail);
  }
 
  onWidgetCancel(event: any): void {
    console.log(event.detail);
  }
 
  widgetLoaded(event: any): void {
    //do something
  }
}

Обратите внимание, что если в событии компонента передаются какие-то данные, то они будут содержаться в полеdetailсобытия.

Ниже приведён пример кода самого виджета, который принимает на вход массивactionsи отдаёт событияtaskActionTriggeredиcanceled:

import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
 
@Component({
  ...
})
export class TaskWidgetComponent implements OnInit {
  private cancellable = false;
 
  @Input() set actions(actions: string[]) {
    console.log('Доступные действия: ', actions);
  }
 
  @Input() set cancelAllowed(allowed: boolean) {
    console.log(`Отмена ${allowed ? 'доступна' : 'недоступна'}`);
 
    this.cancellable = allowed;
  }
 
  @Output() actionTriggered = new EventEmitter<string>();
  @Output() canceled = new EventEmitter<void>();
 
  ngOnInit(): void {
    this.actionTriggered.emit('action-one');
 
    if (this.cancellable) {
      this.canceled.emit();
    }
  }
}

Подключение виджета из библиотечного проекта

Обновитесь до Q.Palette 5.3.15 и выше для работы этого функционала.

Если вы подключили в свой проект библиотечный UI, который содержит виджеты, вы можете их также подключить, проделав следующие шаги.

Введите название вашего UI-сервиса, компонента, подключаемого библиотечного UI и виджета - и мы подставим их в инструкции ниже:

Имя сервиса прикладного UI * Имя компонента прикладного UI * Имя пакета библиотечного UI * Имя подключаемого виджета *

В файле angular.json найдите секцию projects.[project].architect.build.options.assets, где [project] - это название вашего проекта, в который вы хотите подключить лукап.

Добавьте в данную секцию следующий блок кода, где libraryWidgetForm.installedLibraryPackage - название подключенного библиотечного UI.

Теперь после каждой сборки проекта в папке assets/libraryWidgetForm.installedLibraryPackage будет находиться бандл самого библиотечного UI, а также все бандлы виджетов (как правило, находятся в папке assets/libraryWidgetForm.installedLibraryPackage/widgets).

Подключите лукап с указанием полного относительного пути к виджету в параметре source:

Обратите внимание, что адрес начинается с пути /api/libraryWidgetForm.service/libraryWidgetForm.component, который является путем к веб-компоненту, в который вы подключаете библиотечный UI. Подставьте вместо него адрес своего веб-компонента. Также обратите внимание на папку /widgets/libraryWidgetForm.widget: виджет может оказаться в другой папке по вине разработчика виджета - в этом случае поищите виджет вручную в папке /assets/libraryWidgetForm.installedLibraryPackage вашего компонента и подставьте правильный путь.

Ниже пример лукапа, подключающего виджет из библиотечного UI нашей документации:

APP_BASE_HREF

Зачем виджету нужен APP_BASE_HREF?

APP_BASE_HREFнужен в любых приложениях, у которых есть базовый url-адрес.

Пример

  1. Мы открываем страницу приложения /customer-search-widget, на которой расположен виджет.
  2. При загрузке виджет пытается сопоставить текущий адрес /customer-search-widget с теми, которые указаны в настройках роутера этого виджета.
  3. Виджет не находит такого адреса, пишет в консоль Error: Cannot match any routes. URL Segment: 'customer-search-widget' и редиректит в корень ("/").

Установка

Чтобы этого избежать, мы должны установить адрес/customer-search-widgetв качестве базового в приложении виджета. Это можно сделать установкой провайдераAPP_BASE_HREF:

{provide: APP_BASE_HREF, useValue: window.location.pathname}

В момент подгрузки виджета переменнаяwindow.location.pathnameбудет равна/customer-search-widget, что и станет базовым адресом виджета.

После установки

Теперь поведение, начиная с п. 2 меняется:

  1. Виджет при загрузке ищет в настройках роутера уже не адрес /customer-search-widget, а ту часть, которая идёт после него. Поскольку в нашем случае эта часть пуста, то виджет ищет в настройках роутера пустой адрес ('') - и находит его:

  2. Виджет выводит компонент SearchIdDocComponent.

    Точно такое же поведение при навигации по другим маршрутам относительно /customer-search-widget:

    • /customer-search-widget/search-full-name
    • /customer-search-widget/customer-main-info

Во всех этих случаях виджет будет теперь искать именно маршруты/search-full-nameи/customer-main-info, так как часть/customer-search-widgetустановлена в качестве базового URL.

См. также

Подробнее можно почитать в статьях:

Пример использования виджета с роутингом