UNPKG

abc-charts

Version:

Widget render for using in 'ABC consulting' projects

392 lines (306 loc) 14.8 kB
# abc-charts The library for visualisation data via charts. ### Install ```npm npm i abc-charts --save ``` ### Exported modules - ```abc-charts``` - almost all the modules, except below - ```abc-charts/widgetFactory``` - factory to create widgets - ```abc-charts/widgetConfig``` - config to provide to the widget factory - ```abc-charts/dataProvider``` - data provider for GraphQL - ```abc-charts/constants``` - constants for using in selectors only - ```abc-charts/settings``` - widget settings, helping functions and controls - ```abc-charts/types``` - general types ### Usage Importing needed classes ```js import {WidgetConfig} from 'abc-charts/widgetConfig'; import {WidgetFactory} from 'abc-charts/widgetFactory'; // Фабрика import {DataProvider} from "abc-charts/dataProvider"; // Провайдер данных import {Constants} from "abc-charts/constants"; // Значения литеральных типов и перечислений import {SomeInterface} from 'abc-charts/interfaces'; import {SomeType} from 'abc-charts/types'; ``` If your intergration system does not includes "material design" or needed fonts you can import these from the "abc-charts". For example: ``` TypeScript: require('abc-charts/styles.css'); ``` ``` Less/Scss: @import '~abc-charts/styles.css'; ``` Running WidgetFactory to draw widget from template. Also you can use Promise for receiving signals about complete of loading widget or errors. *(Looking example below...)* Interface IChart has method for getting available variables for EventBus: ``` interface IChart { getVariables(): IWidgetVariables; ... } ``` where IWidgetVariables is ``` { <VAR_NAME>: { description?: string; } } ``` ###### *Full example with massive sending* *Service:* ``` class EventBusService { private eventBus: EventBus = null; constructor() { this.eventBus = new EventBus(); this.eventBus.useStateDeferredMerging = false; } getWrapper(): EventBusWrapper { return new EventBusWrapper(this.eventBus); } appendWidgetVariables(eventBusWrapper: EventBusWrapper, widgetVars: IWidgetVariables): void { eventBusWrapper.varAliases = [...Object.keys(widgetVars), '_cb'] .reduce((obj, key) => { obj[key] = {listen: key, trigger: key}; return obj; }, {}) } async sendMassive(eventBusWrapper: EventBusWrapper, widgetVars: IWidgetVariables, values: NameValue[]): Promise<void> { return new Promise(resolve => { let params: IEventData = { _cb: { func: resolve } }; values.forEach((v: NameValue) => { const regExp: RegExp = new RegExp(`^${v.name}`, 'g') params = { ...params, ...Object.keys(widgetVars) .filter(v => regExp.test(v)) .reduce((obj, key) => { obj[key] = v.value; return obj; }, {}) }; }); eventBusWrapper.triggerStateChange(params); } } } ``` *Main code:* ``` class Example { private widget: IChart = null; create() { const eventBusWrapper: EventBusWrapper = eventBusService.getWrapper(); this.widgetConfig = new WidgetConfig(); this.widgetConfig.apiUrl = <API URL>; this.widgetConfig.eventBus = eventBus; this.widgetConfig.templateId = <TEMPLATE ID>; this.widgetConfig.element = <HTMLElement>; this.widgetConfig.afterCreate = async (widget: IChart): Promise<void> => { // Function call after create before the first render const widgetVars: IWidgetVariables = widget.getVariables(); eventBusService.appendWidgetVariables(eventBusWrapper, widgetVars); // First init of widget variables await eventBusService.sendMassive(eventBusWrapper, widgetVars, [ {name: 'start date', value: DateHelper.yyyymmdd(new Date())}, {name: 'finish date', value: DateHelper.yyyymmdd(new Date())}, ]); } (new WidgetFactory).run(this.widgetConfig, <options?: WidgetOptions>).then( (widget: IChart) => this.widget = widget, (err) => <ERROR FUNCTION> ); } destroy() { if (this.widget) { this.widget.destroy(); // Unsubscribe this.widget = null } } } ``` #### Basic auth The authorization hash stored in the LocalStorage. The integration product must control authentication on its own and store it in LocalStorage using the “authToken” key. ``` localStorage.setItem('authToken', btoa('login:pass')) ``` #### Prevent logs You can hide logs specify WidgetOptions when creating the widget via WidgetFactory. ``` (new WidgetFactory).run(config: WidgetConfig, options: WidgetOptions); ``` where ``` WidgetOptions { logs?: { render?: boolean; // Render logs (default: true) eventBus?: boolean; // Event bus logs (default: true) }; } ``` ----------------------- ## Local development Use ```npm link``` for making reference to package ```widget-render``` from your package. For example: ``` # cd widget-render/lib # npm link # cd YOUR_PACKAGE # npm link abc-charts ``` Don't forget local link will be reset after any npm-operations in your package. ### Добавление нового виджета **NOTE:** Все названия директорий и компонентов в **lowerCamelCase** **NOTE:** CodeStyle регламентируется файлом ```tslint.json```. Требуется настроить свою IDE для работы с правилами линтера. - Создаем (копируем из существующего виджета) директорию видa ``` /src/widgets/<widgetName>/ ⎣ index.ts ⎣ <widgetName>.ts ⎣ <widgetName>.less ⎣ settings.ts ``` Уникальное имя файла нужно для того, чтобы имена классов собирались изолировано. Текущее правило для css-классов [name]-[local]. В противном случае классы виджетов пересекуться и использовать их на одной странице станет затруднительно. - Добавляем в /src/widgets/index.ts экспорт. ``` export * from './<widgetName>'; ``` В ```WidgetFactory``` классы автоматически импортируются в скоуп widgets. - Обрабатываем в WidgetFactory ``` private createWidget(...) { ... const widgetsArr: WidgetsArr = { ... "WIDGET_TYPE": () => widgets.YourWidget, } } ``` - Добавляем новый тип виджета в файл```src/models/types.ts``` ```type WidgetType = ... | 'WIDGET_TYPE' ``` ### Deploy - Меняем версию в ```package-lib.json``` - Корректно вносим изменения в ```CHANGELOG.md``` - Вызываем ```npm run npm:publish``` ### Структура виджета #### ```<widgetName>.ts``` Компонент виджета наследуется от класса ```Chart``` , который, в свою очередь, реализует интерфейс ```IChart```, для управления виджетом из вызывающего проекта. В компоненте необходимо реализовать все abstract методы, в частности метод ```run```, *Реализация метода ```run``` не регламентирована ничем, кроме CodeStyle, и каждый виджет может быть написан с использованием уникальных для него подходов (нр, HTML-верстка, SVG, echarts и т.д.).* ##### run() Запуск виджета. Принимает конфигуратор с DOM-элементом, куда записывает результат своей работы. ##### destroy() Уничтожает виджет, в частности все обработчики, которые на него повешаны. ##### redraw() Перерисовать виджет с текущими данными без пересоздания самого виджета. ---- ##### getVariables(): IWidgetVariables Возвращает переменные для общения по шине. *NOTE: Реализовано через generic, чтобы гарантировать правильное использование. Не менять!* ``` getVariables(): IWidgetVariables { const res: IWidgetVariables = []; const addVar = this.addVar(res); addVar(<dataSourceIndex>, 'varName', 'varDesc', 'varHint'); return res; } ``` **Если по шине передаются данные привязанные к конкретным dimensions, надо ОБЯЗАТЕЛЬНО проверять, есть ли такие dimensions (нр, organizationUnit) и не добавлять внешнее событие через addVar, если dimensions нет.** ---- ##### onEventBus: (ev: EventBusEvent, eventObj: IEventData) => void Обработчик сообщений от шины. *NOTE: Реализован через переменную, для сохранения контекста вызова* ---- ##### getSettings: IWidgetSettings Возвращает настройки конкретного виджета для доступа в базовом классе. Сделано через метод, чтобы нельзя было забыть про возврат этих значений (нр, в противовес вызову super.run()) ##### getTemplate(): string | null Получить шаблон. Если не перегружена (null), то шаблонизатор не используется. ##### onResize: (width: number, height: number) => void Обработчик изменения размера. *NOTE: Реализован через переменную, для сохранения контекста вызова* #### ```<widgetName>.less``` Файл стилей, который будет собран в изолированное пространство css-классов, исходя из уникальности имени файла. #### ```settings.ts``` Файл с настройками, уникальными для конкретного виджета. См. ниже "Добавление нового типа настройки" ### Шаблонизатор В проекте используется шаблонизатор [hogan.js](https://twitter.github.io/hogan.js/) синтаксис которого базируется на [mustache.js](https://github.com/janl/mustache.js#usage) #### Пример использования Если нужно рендерить вручную, то можно не переопределять метод ```getTemplate```. ``` class Widget extends Chart { run() { const output = this.renderTemplate({ var1: value1, var2: value2 }); this.config.element.innerHTML = output; } getTemplate(): string { return `Your template {{var1}} {{var2}}`; } } ``` ### Добавление нового типа настройки Все необходимые файлы лежат в папке ```/widgetInfo``` 1. добавляем новый тип setting к ```WidgetSettingsTypes```. 2. в ```/widgetInfo/settings``` в файле ```<YourType>Setting.ts``` описываем структуру данных и функцию создания ```make<YourType>(...): SettingFunc``` 3. добавляем новый тип данных к типу ```WidgetInfoSettingsItem``` 4. Все! Можно использовать вашу функцию для создания новой настройки в конфиге Пример создания настройки ```MyTypeSetting.ts```: ``` >>> 1. задаем тип дефолтного значения type DefaultType = boolean; export interface MyTypeSetting extends BaseSetting<DefaultType> { } >>> 2. дописываем интерфейс настройки MyTypeSetting к общему WidgetInfoSettingsItem в types.ts export function makeBoolean(name: string, label: string, def: DefaultType): SettingFunc { // Через функцию, чтобы гарантировать правильность структуры setting return (): BooleanSetting => ({ name, label, type: 'boolean', >>> 3. Определяем новый тип настройки default: def }); } ``` Пример конфигурации ```widgets\<MyWidget>\settings.ts```: *Примечание: Конфиг задается через функции makeSettings и make<MySetting>, чтобы гарантировать правильность структуры данных. Например, чтобы избежать попыток записать в конфиг несуществующие переменные foo и boo: ```settings: [ {name, value, foo, boo} ]```* ``` export const settings: IWidgetInfo = makeSettings({ settings: [ makeString('title', ''), ], dataSet: { initDataSets: [{viewType: 'DYNAMIC'}, {viewType: 'DYNAMIC'}], canAdd: true, settings: [ makeColor('color', null), ] } }); ``` ### DataProvider Для доступа к данным из GraphQL используется класс ```DataProvider``` из библиотеки. Класс можно заменить собственной реализацией, расширив интерфейс ```IDataProvider``` и передав его через конфигурацию: ```vue class MyDataProvider implements IDataProvider { ... } const config: WidgetConfig = WidgetConfig(); config.dataProvider = MyDataProvier() ```