Добрый день. Хочу реализовать составление отчётов через библиотеку docx-templates. Как на платформе импортировать сторонний модуль?
Через инклюд скрипты.
Script Include.
В вашем примере нужно использовать браузерную версию
https://unpkg.com/docx-templates/lib/browser.js
и проверить, чтобы библиотека не обращалась к DOM-элементам или инструментам браузера (например, console.log).
Такие элементы нужно переопределить.
Всем привет!
Хочу поделиться опытом, как использовать сторонний модуль на примере модуля XLSX. И сразу оговорюсь, что расскажу, как использовать на стороне клиентской части, например, в своём виджете. Это будет актуально для тех, кто использует облачный вариант для своего экземпляра, и не имеет доступа к “физическому” серверу. Чтобы использовать на серверной стороне (в бизнес-правилах, например), потребуется совсем другой подход: либо довольно существенная переделка исходного кода (фактически, заново написать, подгоняя под возможности серверного кода - пробовал повозиться с одним из модулей - так ничего и не вышло), либо надо иметь доступ к серверу, где установлен экземпляр SimpleOne.
Итак, у нас есть исходный код модуля на сайте unpkg.com. Обычно в описании модуля можно найти такую ссылку, и для модуля XLSX это - https://unpkg.com/xlsx/dist/xlsx.full.min.js
Создаём виджет, у которого в шаблоне опишем одну кнопку, по которой можно будет загрузить xlsx-файл:
<button
buttonType="primary"
disabled={data.fileButtonDisabled}
event-click="window.s_widget_custom.selectFile()">
Select file
</button>
В клиентском скрипте изначально деактивируем кнопку и добавляем подгрузку модуля:
s_widget.setFieldValue ('fileButtonDisabled', true);
const script_xlsx = document.createElement ('script');
script_xlsx.type = 'text/javascript';
script_xlsx.src = 'https://unpkg.com/xlsx/dist/xlsx.full.min.js';
document.head.appendChild (script_xlsx);
Поскольку модуль можно будет использовать только после того, как он окончательно загрузится, то определим функцию, которая будет проверять, доступен ли функционал модуля на примере попытки обратиться к параметру XLSX.version:
window.XLSXTryingNumber = 0;
function checkXLSX() {
return new Promise (async (resolve) => {
var intvl = await setInterval (function() {
window.XLSXTryingNumber++;
try {
const version = XLSX.version;
clearInterval (intvl);
resolve ('success');
} catch (error) {
//console.log (`ERROR #${window.XLSXTryingNumber}: `+error);
}
if ( window.XLSXTryingNumber > 300 ) { // timeout after 30 seconds
clearInterval (intvl);
resolve ('timeout');
}
}, 100);
});
}
И ждём загрузки модуля, проверяя каждые 100мс:
const checkResult = await checkXLSX();
if ( checkResult == 'success' ) {
// здесь какой-то код с использованием возможностей модуля
// например, можно просто активировать кнопку
// (либо сделать весь виджет видимым)
s_widget.setFieldValue ('fileButtonDisabled', false);
} else {
console.log ('таймаут...');
}
И пример функции, запускаемой по нажатию кнопки:
window.s_widget_custom.selectFile = async function() {
const options = {
types: [
{
description: 'XLSX Files',
accept: {
'application/xlsx': ['.xlsx'],
},
},
],
};
const [fileHandle] = await window.showOpenFilePicker (options);
const file = await fileHandle.getFile();
const xlsbook = XLSX.read (await file.arrayBuffer());
xlsbook.SheetNames.forEach (name => {
const worksheet = xlsbook.Sheets[name];
const json = XLSX.utils.sheet_to_json (worksheet, { raw: false, cellDates: true });
// console.log (`JSON for ${name}: ` + JSON.stringify(json));
// как-то обрабатываем полученное содержимое...
});
}
Ненадежно (т.к. привязывается к загрузке из внешнего сайта. Можно и не дождаться) и не работает в закрытом контуре.
Рекомендую приложить скрипт в виде вложения к тому же виджету как файл и через ** SimpleAttachment()** получать ссылку на файл и получать его уже из локального хранилища. Попутно это страхует от подмены скрипта на стороне сервера.
Есть еще альтернативный способ загрузки скриптов в пользовательскую область.
В данном случае во внешние скрипты сохраняем скачанный код необходимой библиотеки.
Далее в серверном скрипте
if (action === 'INIT') {
const script = new SimpleRecord('sys_script_include');
const id_tabulator = '173703565708453401' //tabulator
script.get(id_tabulator);
data.script = script.getValue('script');
const id_xlsx = '173703565706666601' //xlsx
script.get(id_xlsx);
data.script += script.getValue('script');
}
В клиентском скрипте
;(async () => {
window.s_widget_custom = window.s_widget_custom || {};
if (!s_widget.getFieldValue('initialized')) {
await init();
}
//Далее идет код виджета
})();
async function init() {
s_widget.setFieldValue('action', 'INIT');
await s_widget.serverUpdate();
s_widget.setFieldValue('action', '');
initLibsJS();
s_widget.setFieldValue('initialized', true);
}
function initLibsJS() {
const scriptAction = new Function(s_widget.getFieldValue('script'));
scriptAction();
s_widget.setFieldValue('script', '');
}
Я предпочитаю использовать самую свежую версию модуля, поэтому берётся из интернета.
А Ваш клиентский скрипт, кстати, требует доработки, и в таком виде, имхо, не заработает. Поскольку стоит “await init()”, а init() написан без промиса (await-ить нечего). И функция initLibsJS() запускается асинхронно. В итоге, когда пойдёт “код виджета”, функции модуля ещё будут недоступны (код scriptAction() ещё не успеет завершиться).
Видать еще не сталкивались с “Кровавым Ынтерпрайзом” ![]()
И работа on-prem в закрытом контуре вполне себе рабочий сценарий.
Народ в соседней ветке вообще жалуется, что у них доступ к сипмлу через WAF сделалм и у них не работает вполне легитимный с точки зрения симпла код.
PS. Пример кода выше выдран из вполне рабочего виджета. Поэтому может быть и лишнее.
Если бы Вы были внимательны, то заметили бы мои самые первые слова о том, что данное решение предлагается для облачных экземпляров. Для on-prem вообще не нужен весь этот сыр-бор с клиентскими скриптами - достаточно просто установить модуль на сервере, и его функционал сразу будет доступен. Не понимаю, зачем в этом случае возиться с инклудниками?..
PS. Возможно, код и рабочий - если в коде виджета не используется сразу функционал самого подгружаемого модуля, т.е. есть время его подгрузить. В моём случае ряд элементов в шаблоне сразу используют функционал модуля, поэтому Ваш вариант - не вариант - он просто не выдаст нужного результата.