Document Templates have been available in model-driven apps for a while now – they are integrated with the model-driven apps, and it’s easy for the users to access them.
They do have limitations, though. We cannot filter those documents, we can only use 1 level of relationships, on each relationship we can only load 100 records max, etc.
There is “Populate a Microsoft Word Template” action in PowerAutomate. Which might be even more powerful, but the problem here is that it’s not quite clear how to turn this into a good user experience. We’d have to let users download those generated documents from the model-driven apps somehow, and, ideally, the whole thing would work like this:
So, while thinking about it, I recalled an old trick we can use to download a file through javascript: https://www.itaintboring.com/dynamics-crm/dynamics-365-the-craziest-thing-i-learned-lately/
It proved to be quite useful in the scenario above, since, in the end, here is how we can make it all work with a PCF control:
As usual, you will find all source codes on github:
https://github.com/ashlega/ITAintBoring.PCFControls
For this particular component, look in the ITAWordTemplate folder.
If using the component as is, you’ll need to configure a few properties. In a nutshell, here is how it works:
- You will need a flow that is using HTTP request trigger
- You will need to configure that trigger to accept docId parameter:
- After that, you can do whatever you need to generate the document, and, eventually you’ll need to pass that document back through the response action:
Here is a great post that talks about the nuances of working with “word template” action (apparently, in my Flow above it’s a much more simple version):
- Then you will need to put ITAWordTemplate component on the form, configure its properties (including Flow url), and that’s about it
Technically, most of the work will be happening in these two javascript methods:
public downloadFile(blob: any) { if (navigator.msSaveBlob) { // IE 10+ navigator.msSaveBlob(blob, this._fileName); } else { var link = document.createElement("a"); if (link.download !== undefined) { var url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", this._fileName); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } } public getFile() { var docId: string = this.getUrlParameter("id"); var data = { docId: docId }; fetch(this._flowUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(response => { response.blob().then(blob => { this.downloadFile(blob); }) }).then(data => console.log(data)); }
Just one note on the usage of “fetch” (it has nothing to do with FetchXML, btw). At first, I tried using XMLHttpClient, but it kept broking the encoding, so I figured I’d try fetch. And it worked like a charm. Well, it’s the preferred method these days anyway, so there you go – there is no XMLHttpRequest in this code.
One question you may have here is: “what about security?” After all, that’s an http request trigger, so it’s not quite protected. If that’s what you are concerned about, there is another great post you might want to read: https://demiliani.com/2020/06/25/securing-your-http-triggered-flow-in-power-automate/
PS. Also, have a look at the follow-up post which is featuring an improved version of this control.