Иморт сторонних модулей

Добрый день. Хочу реализовать составление отчётов через библиотеку docx-templates. Как на платформе импортировать сторонний модуль?

Через инклюд скрипты.
Script Include.

В вашем примере нужно использовать браузерную версию
https://unpkg.com/docx-templates/lib/browser.js

и проверить, чтобы библиотека не обращалась к DOM-элементам или инструментам браузера (например, console.log).
Такие элементы нужно переопределить.

1 лайк

Всем привет!

Хочу поделиться опытом, как использовать сторонний модуль на примере модуля 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() ещё не успеет завершиться).

Видать еще не сталкивались с “Кровавым Ынтерпрайзом” :slight_smile:
И работа on-prem в закрытом контуре вполне себе рабочий сценарий.

Народ в соседней ветке вообще жалуется, что у них доступ к сипмлу через WAF сделалм и у них не работает вполне легитимный с точки зрения симпла код.

PS. Пример кода выше выдран из вполне рабочего виджета. Поэтому может быть и лишнее.

Если бы Вы были внимательны, то заметили бы мои самые первые слова о том, что данное решение предлагается для облачных экземпляров. Для on-prem вообще не нужен весь этот сыр-бор с клиентскими скриптами - достаточно просто установить модуль на сервере, и его функционал сразу будет доступен. Не понимаю, зачем в этом случае возиться с инклудниками?..

PS. Возможно, код и рабочий - если в коде виджета не используется сразу функционал самого подгружаемого модуля, т.е. есть время его подгрузить. В моём случае ряд элементов в шаблоне сразу используют функционал модуля, поэтому Ваш вариант - не вариант - он просто не выдаст нужного результата.