07 External
External
был нашей первой реализацией спецкласса для отрисовки кастомных визуализаций, еще до того, как веб-клиент стала полностью React-приложением, и основное назначение класса сводится к тому, чтобы вставить iframe
, занимающий все доступное пространство дешлета (за вычетом высоты заголовка с названием дешлета по умолчанию, если тот не был выключен в настройках) и в качестве src
атрибута указывается адрес, который вы прописали конфиге дешлета в блоке Файл
(url
), который по итогу парсится веб-клиентом в полноценный урл до файла.
В режиме JSON config
вы даже можете сделать ссылку на внешний ресурс в интернете, если необходимо, однако такой внешний дешлет будет декоративным и не будет реагировать на события веб-клиента (т.е. на любые ваши действия с данными или в интерфейсе).
В режиме Editor
вам такая возможность по умолчанию недоступна (потому что слишком специфична для обывателя) и вы, при попытке указать файл для указанных выше типов визуализаций, обозреваете ресурсы всех атласов, которые доступны вашему пользователю. Это выглядит как навигация по папкам с названиями атласов, где список файлов - это все ,что хранится в соответствующем разделе resources
выбранного атласа. В результате выбора файла вы получаете в конфиге дешлета ключ url
например в таком виде:
"url": "res:MyExternalComponent.html"
Такая запись означает, что веб-клиент должен пойти в ресурсы текущего атласа (пусть это будет тот же ds_51
), на котором мы находимся и асинхронно (via Promise
) загрузить файл (или его контент, если это Internal
) с именем, указанным после res:
.
По итогу файл будет запрашиваться по адресу /srv/resources/ds_51/MyExternalComponent.html
и эта ссылка вставиться в src
у iframe
. То, что отрендерится в результате такого подключения зависит от скриптов внутри MyExternalComponent.html
Вы можете писать там любой код, как при разметке обычной веб-страницы. Можете даже подключать любые библиотеки и фреймворки, если сочтете нужным. Однако, у этого типа дешлета есть ряд ограничений:
- Вам будет необходимо подключить нашу библиотеку
bixel.js
, предоставляющую вам событийную систему и синхронизирующую работу фрейма и родительского окна.
Страница этой библиотеки тут https://github.com/luxms/bixel.
На всякий случай fallback (https://drive.google.com/file/d/1KQfrQQewEkfrk0zhm6TsUL2M9_0lTEoK/view?usp=sharing)
Так вот, сделав поведение вашего компонента зависящим от событий, которые генерирует эта библиотека вы получите кастомный компонент, который умеет взаимодействовать с веб-клиентом и делать широкий круг вещей. Однако ему будут недоступны многие модули и сервисы веб-клиента, либо доступны опосредованно через их сохраненные в объекте window
версии.
Например window.parent.__koobFiltersService
- сохраненный инстанс сервиса, который хранит в себе информацию о выбранных вами фильтрах для кубов.
Пример:
const service = window.parent.__koobFiltersService;
service.subscribeUpdatesAndNotify(myCallback);
const myCallback = (model) => {
/* тут логика работы с объектом текущей модели сервиса, например с его ключом filters,
где указаны выбранные фильтры для кубов */
}
const onClick = (e) => {
service.setFilters("", {
"age", [">=", 50],
"name": ["=", "Rick Astley"]
}) /* При клике обратится к фильтру и выставит фильтры
на дименшн age и name (фиксированные или из e.target например) */
}
Плюс остаются стандартные ограничения, существующие между фреймом и родительским окном, налагаемые браузерами и javascript.
Причины для использования этого:
- Для него требуется сравнительно невысокий входной порог по навыкам
- Не нужно билдить, при особом желании можно править код прямо в разделе ресурсы на сервере, без проекта
BMR
. - Поскольку это фрейм, то вы можете подгружать любые библиотеки. То есть добавить компонент, рендерящий
Angular
илиVue
компонент внутриReact
-приложения. Сомнительная инициатива, но запретить ее мы не можем. - Вставить визуал с другого инстанса BI пользуясь логикой токенов для безпарольного входа (для этого есть отдельная дока по запросу).
Пример такого файла на external
:
<!DOCTYPE html>
<html>
<head>
<!--Для старых браузеров, не умеющих в Promise, подключаем полифилл es6-promise.auto.js из интернета-->
<script>window.Promise || document.write('<scr'+'ipt src="es6-promise.auto.js"></sc'+'ript>');</script>
<script src="bixel.js"></script>
</head>
<style>
html, body {
margin: 0;
font-size: 13px;
min-height: 100%;
}
#wrapper {
overflow-y: auto;
overflow-x: hidden;
width: 100%;
min-height: 100%;
padding: 1rem;
box-sizing: border-box;
}
.elements {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
padding: 0.25rem;
}
.element {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.5rem;
}
.element:last-child{
margin-bottom: 0;
}
.element .title {
flex: 0 0 auto;
max-width: 70%;
width: 100%;
}
.element .value {
flex: 0 0 auto;
max-width: 30%;
padding: 0.5rem 0.25rem;
width: 100%;
border: 1px solid #7ba7db;
text-align:center;
}
</style>
<body>
<main id="wrapper">
<div class="elements" id="target"></div>
</main>
<script>
let dashlet, config, ms, ls, ps, M, L, P, data, axes, xs, ys, zs;
// Первоначальный инит библиотеки. Обычно нужен для разовой предварительной настройки компонента и чтении конфига дешлета
bixel.init({
zsCount: 1,
}).then(function(d) {dashlet = d; config = dashlet.config || {}});
// Данных нет, или не указаны важные ключи в конфиге дешлета. Нужен для заглушки типа Нет данных
bixel.on('no-data', function(axes) {
// no data
});
// Событие триггерится каждый раз, когда вы выставляете фильтр вручную или через управляющий деш. И когда данные есть
bixel.on('load', function (d, a) {
data = d;
axes = a;
xs = axes.getXs();
ys = axes.getYs();
zs = axes.getZs();
// Просто рандомная функция, которая отвечает за основной рендер. Учитывайте, что событие load может происходить часто, потому не забывайте очищать контейнер перед вставкой в DOM элементов
_render();
});
bixel.on('url', function (url) {
// данный блок будет выполняться каждый раз, когда вы поменяете url
_render();
});
function _render() {
console.log(xs, ys, zs, data);
console.log(data.getValue(xs[0], ys[0], zs[0])); //покажет первый элемент таблицы
}
document.addEventListener('DOMContentLoaded', function(){ // Аналог $(document).ready(function(){
document.getElementById('wrapper').addEventListener('click', function(event) {
console.log(config);
if (config.hasOwnProperty('onClickDataPoint')) {
// Прокидываем обработчик встроенной логики клика по элементу графика onClickDataPoint
bixel.invoke('CLICK_DATA_POINT', {
x_id: xs[0].id,
y_id: ys[0].id,
z_id: zs[0].id,
event: { pageX: event.pageX, pageY: event.pageY },
});
}
});
});
</script>
</body>
</html>
import React from "react";
import './DatePickers.scss';
// Подключили сервис
import {KoobFiltersService, useService, useServiceItself} from "bi-internal/services";
const MyComponent = (props) => {
const {cfg, subspace, dp} = props;
const koob
public render() {
const {data} = this.state;
return (
<div className="DatePickers">
{/* что-то делаем с data */}
<div className="DatePickers__SelectButtons">
<div className="DatePickers__SelectButton active" onClick={this.onSubmitClick}>Применить</div>
</div>
</div>
</div>
);
}
}