Виджет
Пререквизиты
Для разработки виджетов и использования их совместно с 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-адрес.
Пример
- Мы открываем страницу приложения /customer-search-widget, на которой расположен виджет.
- При загрузке виджет пытается сопоставить текущий адрес /customer-search-widgetс теми, которые указаны в настройках роутера этого виджета.
- Виджет не находит такого адреса, пишет в консоль 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 меняется:
- 
Виджет при загрузке ищет в настройках роутера уже не адрес /customer-search-widget, а ту часть, которая идёт после него. Поскольку в нашем случае эта часть пуста, то виджет ищет в настройках роутера пустой адрес ('') - и находит его:
- 
Виджет выводит компонент 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.
См. также
Подробнее можно почитать в статьях:
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base (opens in a new tab)
- https://angular.io/guide/router#html5-urls-and-the--base-href (opens in a new tab)