Генерация PDF-файлов — одна из тех вещей, которые Oracle JET не предлагает. Если вы хотите сделать это, вы должны использовать стороннюю библиотеку. В этой истории я покажу вам, как создать и загрузить PDF-документ из заданного пользовательского объекта. Вы можете применить показанные концепции к любому другому варианту использования. На этом пути есть несколько ловушек, которых вы, надеюсь, сможете избежать благодаря этому руководству.

Редактировать: Тем временем мы перенесли генерацию PDF из внешнего интерфейса в бэкенд (подробнее о причинах читайте в нашей Истории успеха Travelcost). Тем не менее, есть веские причины для создания PDF-файлов во внешнем интерфейсе, поэтому, пожалуйста, читайте дальше.

Альтернативой этому подходу может быть использование функции window.print, которая вызывает представление печати для конкретного браузера. Большинство браузеров позволяют пользователю создавать PDF-файл из этого представления. Однако в большинстве случаев вам нужен больший контроль над тем, что печатается и как это выглядит. Кроме того, пользовательский опыт значительно улучшился, потому что (1) прямая загрузка сокращает количество шагов, которые должен выполнить пользователь, и (2) если быть честными, многие пользователи даже не знают, что они могут экспортировать PDF через диалог печати.

Я предполагаю, что вы уже разобрались с основами JET. Вам понадобится небольшое запущенное веб-приложение, чтобы интегрировать в него генерацию PDF. Если у вас нет подходящего шаблона, вы также можете использовать базовый начальный шаблон, который создает генератор Yeoman (сгенерируйте его с помощью yo oraclejet:app --template basic pdfmake-example).

Результирующий документ будет выглядеть так для моих собственных пользовательских данных:

Настраивать

Мы будем использовать замечательную библиотеку pdfmake для создания и печати файла PDF. Загрузите его через npm install --save pdfmake. Эту новую зависимость необходимо настроить для включения в команду grunt build. Для этого добавьте следующую строку в массив copyCustomLibsToStaging.fileList в файле scripts/grunt/config/oraclejet-build.js:

{
  cwd: 'node_modules/pdfmake/build',
  src: ['*.js'], // matches pdfmake.js and vfs_fonts.js
  dest: 'web/js/libs/pdfmake'
}

Это добавит все файлы JavaScript из node_modules/pdfmake/build/ в вашу папку lib. Это файлы pdfmake.js/pdfmake.min.js (фактическая логика генерации PDF) и vfs_fonts.js (шрифты, используемые библиотекой pdfmake). Не забудьте потом запустить grunt build.

Вам понадобится и логика, и шрифты для создания PDF-файлов, поэтому их нужно загрузить через RequireJS. Именно здесь я изначально боролся, потому что сначала нужно загрузить шрифты, а затем библиотеку pdfmake. К счастью, в RequireJS есть что-то под названием shims именно для этих проблем (подробнее о shims). На самом деле вы уже используете прокладку jQuery в своем приложении JET. Измените объект shim в вызове requirejs.config следующим образом (прокладка jQuery уже должна быть там):

shim: {
  'jquery': {
    exports: ['jQuery', '$']
  },
  'vfsfonts': {
    deps: ['pdfmake']
  }
}

Эти зависимости, конечно, также должны быть добавлены к paths, которые предоставляются для вызова requirejs.config:

'pdfmake': 'libs/pdfmake/pdfmake',
'vfsfonts': 'libs/pdfmake/vfs_fonts'

Модуль pdfCreator

Я рекомендую поместить вашу логику PDF в отдельный модуль. Создайте файл pdfCreator.js и добавьте стандартную логику определения модуля RequireJS:

define([], function () {
  function createPDF (user) {
    /* ... */
  }
  return {
    fromUser: createPDF
  };
});

Это экспортирует функцию fromUser, которую вы вызовете через pdfCreator.fromUser(user) позже. Но сначала нам нужно потребовать необходимые зависимости. Вы можете сделать это, передав массив ['pdfmake', 'vfsfonts'] в качестве первого параметра в вызов define. Прокладка, которую мы настроили на этапе настройки, гарантирует, что все загружается в правильном порядке. Поскольку мы хотим вызывать функции в библиотеке pdfmake, нам нужно передать pdfmake в функцию, реализующую наш модуль. Полное определение модуля должно выглядеть так:

define(['pdfmake', 'vfsfonts'], function (pdfmake) {
  function createPDF (travel) {
    /* ... */
  }
  return {
    fromUser: createPDF
  };
});

С учетом этого давайте добавим логику генерации PDF. В pdfmake документы описываются как простые объекты JavaScript. Объект документа должен как минимум содержать свойство content. Здесь объявляется фактическое содержимое PDF. Мы хотим отображать пользовательские данные в табличном формате, поэтому сначала записываем пользовательские данные в двумерный массив:

var table = [
  [
    {text: 'User', style: 'tableHeader'},
    {text: '', style: 'tableHeader'}
  ],
  [
    'Name',
    user.name
  ],
  [
    'Twitter Handle',
    user.twitter
  ],
  [
    'Favorite Band',
    user.band
  ]
];

Это определяет таблицу 4*2, где первая строка предназначена для заголовка. Обратите внимание, что мы намеренно установили второй заголовок в пустую строку, потому что нам нужен только заголовок для первого столбца. Текст может быть определен либо непосредственно как строка, либо через объект, имеющий свойство text. Поскольку table — это простой массив JavaScript, мы можем динамически добавлять больше контента, если захотим:

if (user.color) {
  table.push(['Favorite Color', user.color]);
}

Нам также нужен красивый заголовок для нашего документа. Мы создаем объект, который хранит текст и имеет стиль header:

var title = {
  text: 'User Info for ' + user.name,
  style: 'header'
};

Но pdfmake еще не знает, что означает этот стиль header, поэтому мы должны определить эти стили сейчас. pdfmake также позволяет стилизовать документ с помощью пар ключ-значение, подобных CSS. Они могут быть объявлены встроенными, но в большинстве случаев вы хотите сохранить их в объекте, который передается в свойство styles объекта документа. Таким образом, вы можете повторно использовать их для разных частей документа. Мы вводим стиль для заголовка документа и заголовка таблицы следующим образом:

var styles = {
  header: {
    fontSize: 18,
    bold: true,
    margin: [0, 0, 0, 10]
  },
  tableHeader: {
    bold: true,
    fontSize: 13,
    color: 'black'
  }
};

Последнее, что вы можете сделать, это отобразить какой-нибудь логотип в заголовке. Многие официальные документы имеют логотип компании-эмитента, напечатанный в верхнем правом или верхнем левом углу. Это немного сложно, потому что вы не можете, например, просто установить display: inline, как в CSS. Вместо этого мы делаем это:

var image = {
  image: 'data:image/png;base64,iVBORw0KGgoAAAANS...',
  width: 73,
  height: 28,
  alignment: 'right',
  margin: [0, 25, 25, 0]
};

Обратите внимание, что мы установили выравнивание на right, потому что мы поместили изображение в верхний правый угол документа. Ему также назначается поле в 25 пикселей, сверху и справа. Поскольку мы запускаем приложение в браузере, мы не можем получить доступ к файловой системе. Поэтому изображение кодируется base64. Для этого можно использовать онлайн-сервис, я использовал base64-image.de.

Наши различные части документа теперь могут быть объединены в определение документа следующим образом:

var document = {
  background: image,
  content: [
    title,
    {
      table: {
        widths: [200, '*'],
        headerRows: 1,
        body: table
      },
      layout: 'headerLineOnly',
      margin: [0, 10, 0, 0]
    }
  ],
  styles: styles
};

Обратите внимание на свойство content, которое определено как массив в pdfmake. Здесь мы устанавливаем title и описываем таблицу. Определение таблицы предполагает наличие двумерного массива в качестве body (которое мы определили ранее). Мы также устанавливаем widths столбцов таблицы, сообщаем таблице, что первой строкой должно быть headerRow, и устанавливаем конкретное предопределенное layout, чтобы оно выглядело красиво. Мы также установили логотип в качестве фонового изображения. Это важно, потому что в противном случае документ будет испорчен. Не стесняйтесь добавлять изображение в content и посмотрите, как это повлияет на макет.

Опять же, вот скриншот окончательного PDF-документа:

Это завершает определение документа. Теперь нам просто нужно создать PDF-файл через pdfmake.createPdf(document), что позволит нам инициировать загрузку PDF-файла через pdf.download(). Вы также можете создавать функции для печати PDF или открытия его в браузере через print() и open() соответственно.

Теперь вы можете запросить этот модуль в любом месте вашего приложения Oracle JET и использовать его функцию fromUser для загрузки PDF-файла с данными пользователей. Вы можете сделать гораздо больше с pdfmake — отправляйтесь на игровую площадку, если хотите глубже изучить. Полный пример pdfCreator доступен в виде краткого описания на github.

Заключение / TL;DR

  • используйте pdfmake для создания файлов PDF
  • настроить прокладку так, чтобы RequireJS знал, как загружать pdfmake и его шрифты
  • определять данные документа и таблицы, используя простые объекты и массивы JavaScript
  • создайте PDF через pdfmake.createPdf(document), а затем загрузите через pdf.download()