vte.cxとReactでPDF出力機能の実装

#概要
**[以前作成したアプリ](https://qiita.com/kako-2748/items/55a67a24ac29ef657045)**の一覧画面をPDFで出力する機能を実装しました。
**[vte.cx](https://blog.vte.cx/)**について詳しく知りたい方は**[vte.cxのドキュメント](https://vte.cx/documentation.html)**をご覧ください。

今回実装したPDF出力機能の仕様は以下になります。

- 一覧を表示した状態で「PDF出力」ボタンを押すとPDFをダウンロードする
- 一覧画面に表示されているデータだけをダウンロードする

実際にダウンロードしてみるとこんな感じになります。
![スクリーンショット 2019-10-30 12.39.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478072/b12b0d16-da2e-2a0b-1667-56c54580eee3.png)



PDF出力機能を実装するには、サーバーサイドで実行されるサーバーサイドJavaScriptについて知る必要があります。
詳しくはこちらの記事を参考にしてください。
https://qiita.com/stakezaki/items/d596afd085988d76983c

#実装
今回編集したファイルは一覧画面(ListProf)と新しく作ったPDF出力ファイル(ssr.pdf)になります。

では、PDF出力ファイルから見ていきます。
まず、``/src/server/``の配下にssr.pdf.tsxファイルを作ってください。
実際のファイルが以下になります。
##PDF出力ファイル(ssr.pdf)

```tsx:ssr.pdf
import * as vtecxapi from 'vtecxapi'
import * as React from 'react'
import * as ReactDOMServer from 'react-dom/server'
import * as pdfstyles from '../pdf/pdfstyles'

const PrintToPdf = () => {

  const feed = vtecxapi.getFeed('/foo')
  const currentPage = Number(vtecxapi.getQueryString('currentPage'))

  let sliceFirstNumber = 0
  let sliceLastNumber = 5

  if(currentPage !== 1) {
    sliceFirstNumber = currentPage * 5 - 5
    sliceLastNumber = currentPage * 5

  }

  const showFeed = feed.slice(sliceFirstNumber, sliceLastNumber)

  return (
     <html>
       <body>
         <div className="_page" style={pdfstyles._page}>
         {feed.length > 0 &&
           <table style={pdfstyles._table}>
             <tr>
               <th style={pdfstyles._th}>名前</th>
               <th style={pdfstyles._th}>メール</th>
               <th style={pdfstyles._th}>職業</th>
               <th style={pdfstyles._th}>住所</th>
               <th style={pdfstyles._th}>身長</th>
               <th style={pdfstyles._th}>誕生日</th>
               <th style={pdfstyles._th}>性別</th>
               <th style={pdfstyles._th}>チェック</th>
               <th style={pdfstyles._th}>セレクト</th>
               <th style={pdfstyles._th}>メモ</th>
             </tr> 

             {showFeed.map((entry) => (
               <tr>
                 <td style={pdfstyles._td}>{entry.user!.name}</td>
                 <td style={pdfstyles._td}>{entry.user!.email}</td>
                 <td style={pdfstyles._td}>{entry.user!.job}</td>
                 <td style={pdfstyles._td}>{entry.user!.address}</td>
                 <td style={pdfstyles._td}>{entry.user!.height}</td>
                 <td style={pdfstyles._td}>{entry.user!.birthday}</td>
                 <td style={pdfstyles._td}>{entry.user!.gender}</td>
                 <td style={pdfstyles._td}>{entry.user!.check}</td>
                 <td style={pdfstyles._td}>{entry.user!.select}</td>
                 <td style={pdfstyles._td}>{entry.user!.memo}</td>
               </tr>
             ))}

           </table>
         }
         </div>
       </body>
     </html>
    )
  }

const html = ReactDOMServer.renderToStaticMarkup(PrintToPdf())

// PDF出力
vtecxapi.toPdf(1, html, 'ListProf.pdf')
```
まずは、PDFファイルに表示させるデータを取得します。

```tsx
const feed = vtecxapi.getFeed('/foo')
```
上記のように、``vtecxapi.getFeed()``を使うことで、これまで``/d/foo``に対して行ってきたGETリクエストをサーバサイドJSから行うことができます。この場合、``/foo``に``/d``はつけなくて大丈夫です。
これを以下のコマンドでデプロイしてください。

```tsx
npm run watch:server -- --env.entry=/server/ssr.pdf.tsx
```

データが取得できたので、実際に表示させてみましょう。

```tsx
 return (
     <html>
       <body>
         <div className="_page" style={pdfstyles._page}>
         {feed.length > 0 &&
           <table style={pdfstyles._table}>
             <tr>
               <th style={pdfstyles._th}>名前</th>
               <th style={pdfstyles._th}>メール</th>
               <th style={pdfstyles._th}>職業</th>
               <th style={pdfstyles._th}>住所</th>
               <th style={pdfstyles._th}>身長</th>
               <th style={pdfstyles._th}>誕生日</th>
               <th style={pdfstyles._th}>性別</th>
               <th style={pdfstyles._th}>チェック</th>
               <th style={pdfstyles._th}>セレクト</th>
               <th style={pdfstyles._th}>メモ</th>
             </tr> 

             {showFeed.map((entry) => (
               <tr>
                 <td style={pdfstyles._td}>{entry.user!.name}</td>
                 <td style={pdfstyles._td}>{entry.user!.email}</td>
                 <td style={pdfstyles._td}>{entry.user!.job}</td>
                 <td style={pdfstyles._td}>{entry.user!.address}</td>
                 <td style={pdfstyles._td}>{entry.user!.height}</td>
                 <td style={pdfstyles._td}>{entry.user!.birthday}</td>
                 <td style={pdfstyles._td}>{entry.user!.gender}</td>
                 <td style={pdfstyles._td}>{entry.user!.check}</td>
                 <td style={pdfstyles._td}>{entry.user!.select}</td>
                 <td style={pdfstyles._td}>{entry.user!.memo}</td>
               </tr>
             ))}

           </table>
         }
         </div>
       </body>
     </html>
    )
```

PDF出力ファイルはJSXで書けますが、ページ構造が決まっています。詳しくは[vte.cxのドキュメント](https://vte.cx/documentation.html)のPDFスタイルシートにページ構造が記載されているので、そちらをご覧ください。
データを表示させるテーブルタグの中身は一覧画面とほとんど一緒です。

```tsx
const html = ReactDOMServer.renderToStaticMarkup(PrintToPdf())

// PDF出力
vtecxapi.toPdf(1, html, 'ListProf.pdf')
```

``ReactDOMServer.renderToStaticMarkup({表示させたいコンポーネント名})``とし、
``vtecxapi.toPdf(ページ数,生成元のhtml,PDFのファイル名)``の生成元のhtmlに指定することで、指定したパラメータを元にPDFを生成します。

これで、ブラウザから``http://{サービス名}/s/ssr.pdf``を開くとPDFファイルがダウンロードされます。
![スクリーンショット 2019-10-30 13.38.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478072/10aa154e-da63-be35-8999-9ad8ca6cfef0.png)
しかし、これだと一覧画面に表示されているデータだけではなく、画像のように全てのデータが表示されてしまうので、表示するデータを指定する必要があります。

```tsx
const currentPage = Number(vtecxapi.getQueryString('currentPage'))
```
変数``currentPage``に一覧画面のページ数を格納します。
``getQueryString(取得したいパラメータ)``とすることで、URLパラメータを取得することができます。
後ほど説明しますが、一覧画面でPDF出力ボタンを押下した際に、ページ遷移と同時に変数 ``currentPage``の値を同時に送ることで、ページ数を取得します。

```tsx
  let sliceFirstNumber = 0
  let sliceLastNumber = 5

  if(currentPage !== 1) {
    sliceFirstNumber = currentPage * 5 - 5
    sliceLastNumber = currentPage * 5

  }
 const showFeed = feed.slice(sliceFirstNumber, sliceLastNumber)
```

表示するデータを指定するためには、``slice``を使います。そのために、``currentPage``を使い、最初と最後の値を指定して抜き出します。

最後に、一覧画面にPDF出力ボタンを配置します。
今回はボタンタグを追加しただけなので、JSXのボタンを配置したところだけ載せておきます。
##一覧画面(ListProf)

```tsx:ListProf

       return ( 
          <button type="button" onClick={() => location.href="http://vtecx_sample_app.vte.cx/s/ssr.pdf?currentPage="+currentPage}>PDF出力</button>
```

``onClick``イベントに``location.fref``でURLを指定することで、ボタン押下時にPDF出力ファイルに遷移し、PDFをダウンロードできます。
また、最後にURLの最後に``'?currentPage='+currentPage``を追加することで、現在の一覧画面のページ数を一緒に受け渡すことができます。

**注意！**
一覧画面にボタンを配置した際に、``npm run watch:server -- --env.entry=/server/ssr.pdf.tsx``のままになっていると一覧画面を保存しても変更が反映されないので、必ず`` npm run watch:index``で反映させてください。

これで、実際にボタンを押下してみてください。
![スクリーンショット 2019-10-30 12.39.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478072/74a05add-3920-a11c-ebe4-684f0308ae1a.png)
こんな感じで、一覧画面と同じデータが表示されたら成功です。

お疲れ様です。