Render dynamically a .docx file with JavaScript
Tags: javascript, docx, vue
Published: 2019-12-20 11:41:07

Hi everyone, Stefano here, and this is my first article! Feel free to give me advices on writing in general or on stuff related to this post 🙌🏻

# The Need:
I am building a webapp with Vue.JS + Nuxt.JS for one of my clients: this app has the purpose to create proposals based on the products they manufacture, calculating prices and a bunch of other things. 
Eventually, the finished proposal needs to be converted in a printable format.

#The Problem:
Each proposal has different base informations (title, creation date etc..) and different products in it. Also each product is different, it may have some key/value pairs that some other product doesn't have.
The fact is that **i want to export a file that always look the same (a template) but rendered with the data from my proposal**.

#The Tools:
Searching in the web i discovered an awesome tool called **docxtemplater** that will do exactly this job. *Let's see it in action:*

Imagine a .docx (Word, Google Doc etc..) like this:

```
Hello {name}!
You have all these games: {#gameList}{.} {/gameList}
{#hasXbox}And you have also an XBOX!{/}

```
With **docxtemplater** you can pass in it an object, for example, like this: 
```javascript
let obj = {
   name: `Sam`,
   gameList: [`Metal Gear Solid`, `Crash Bandicoot`, `Final Fantasy 7`],
   hasXbox: false
}
```
after the render, you'll be able to download the document, and in this case will look like this:
```
Hello Sam!
You have all these games: Metal Gear Solid Crash Bandicoot Final Fantasy 7
```

Did you notice?
**Conditionals and loops over arrays are possible** giving you freedom to build a template that will meet your needs.
The entire xbox sentence is omitted thanks to the boolean *hasXbox*.
You can also loop over an array of objects, which gives you even more power.
For the entire documentation i suggest to take a look at <a href="https://docxtemplater.readthedocs.io/en/latest/tag_types.html">the official website</a>.

#Setup
As i said before, i'm using Vue, but the following is easily adaptable on other environments.

You'll need to *npm install* some dependancies:
```
npm install --save docxtemplater jszip@2 jszip-utils file-saver
```
*docxtemplater* accepts zip, so *jszip* and *jszip-utils* are useful for this purpose, *file-saver* is useful in order to save the rendered .docx on the device.
*Note: jszip@2 to prevent installing version 3+ that do not seem to work on my environment: feel free to try both.*

That said I imported them in the component like this:
```javascript
import docxtemplater from 'docxtemplater'
import JSzip from 'jszip'
import JSzipUtils from 'jszip-utils'
import { saveAs } from 'file-saver'
```

In the html template I have this button:
```html
<button @click="createDOC()">Export DOCX</button>
```
Then the methods:
```javascript
methods:{
    loadFile(url,callback){
        JSzipUtils.getBinaryContent(url,callback);
    },

    createDOC(){
        let prev = this.getLoadedPrev
        this.loadFile('/template.docx',function(error,content){
            if (error) { throw error };
            let zip = new JSzip(content);
            let doc = new docxtemplater().loadZip(zip)
            doc.setData(prev)

            try {
                doc.render()
            }

            catch (error) {
                let e = {
                    message: error.message,
                    name: error.name,
                    stack: error.stack,
                    properties: error.properties,
                }
                console.log(JSON.stringify({error: e}));
                // The error thrown here contains additional information when logged with JSON.stringify (it contains a property object).
                throw error;
            }

            let out = doc.getZip().generate({
                type:"blob",
                mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                })
            saveAs(out,`${prev.titolo}.docx`)
        })
    }
}
```

In my case the method *loadFile* will retrieve the .docx template inside the static folder of the app (all of this happens client side, it is possible to setup all of this server side with node).
The *jszip* utility will zip the .docx that will be passed in order to instantiate a new *docxtemplate* document.

In the *doc.setData(prev)* I'm passing an object with **all the informations about the proposal (title, creationDate, productList, author, etc..)** and then it will try to render the doc.

The code block after the error handling:
```javascript
        let out = doc.getZip().generate({
            type:"blob",
            mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            })
        saveAs(out,`${prev.title}.docx`)
    })
```
is responsible for the output of rendered document.

#Conclusion
Be aware that if you need to generate a PDF instead, **it's possible** through <a href="https://www.npmjs.com/package/@nativedocuments/docx-wasm">this package</a>. For whom is confident with Lambda Functions it will be a breeze.
For now i don't have the needs to do it so I can't help with real examples.

That's all,
cheers! 👋🏻