DEV Community

Stefano
Stefano

Posted on • Updated on

Render dynamically a .docx file with JavaScript

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:

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 the official website.

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:

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:

<button @click="createDOC()">Export DOCX</button>

Then the methods:

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:

        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 this package. 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! πŸ‘‹πŸ»

Top comments (0)