abc-charts
Version:
Widget render for using in 'ABC consulting' projects
392 lines (306 loc) • 14.8 kB
Markdown
# 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()
```