Lately, as part of solution I have been building for a client, we had to deal with several document generation flows. I’m talking about really complicated document templates. For example there is one that is composed of several tables and sections that compare different credits / mortgages that a client can take and their financial impact, benefits and downsides and more. This is nothing like the simple mortgage documents that your local bank shares with you. In fact, I am happy for their clients that will receive such well-thought documents, but as for the poor developers, this was not an easy task.
There are two out-of-the-box options when you need to populate Word documents.
Word templates feature that existed for ages in Dynamics CRM and now inherited by Power Platform. This is normally the easiest option, but it has many limitation and only works in harmony with rows in a table and its direct related tables.
Word Online Connector which is a premium connectors and more capable without dependency on any table. The main issue with this one is when you have flows in your solution that will be deployed in different environments or any time the location of your document needs to change. In this case instead of the name of these placeholders you would need to use random numbers because this connector cannot figure out the schema of the document or lock it after your build one.
For the second options there are some workaround, that I will be writing about soon in future, but what if the logic is so complex that using Flows or a Power App does not make sense or not enough.
In search for an open source library
At this point I started looking for an open source .NET library (preferably) to let us get the job done. It should exist in this day and age, right? The answer is no, it doesn’t. There are only some paid libraries and services that are quite expensive for what they do and working with them is not as easy as one might think. After digging deeper into GitHub, I was able to find some libraries that either rely on Open Office or a headless browser or the brain of the library is in a closed-source black box. I have to say honestly the fact that there is no open-source library and not in .NET was annoying enough, so I decided to start one and I decided to start simple and make it as easy as possible for the developer to use with no knowledge of the underlying format (😎 ehem which I know pretty well, but not to Eric White level well ☺).
Novo Docx
NovoDocx’s first readme file push in Git
Today I share with you my first iteration of Novo Docx, my take on a simple-to-use and simple-to-host library and service that you can use in you own projects. The current features are enough to handle a complex documents and quite fast. I have tried to capture many edge cases too. I will be focusing on enriching the functionality and providing more hosting options. The source code is hosted in GitHub. It includes a simple library and an Azure Functions App that you can use however you like.
Low-code is all the rush these days. It makes for more maintainable solutions that are built faster, specially when it comes to extending existing platforms like Power Platforms / Dynamics. It makes our work more decoupled from that of Microsoft’s and we can each focus on building better solutions without treading on each other’s toes. 😊
One of the main extension points in Power Platform is adding custom buttons to the Command bar. If you are not fully familiar with command bar, be sure to read the following article in the official docs to know what it is and where it is before reading further.
The previous experience was called Ribbon which is still around. If you would like to know more about this evolution, please read the following page from the official docs.
Reusable JavaScript library to open custom pages as dialog boxes
Custom button in command bar
Custom page in model-driven app to act as a dialog box
A cloud flow
In case you are wondering if this is a good practice to display custom pages in dialog boxes, this whole thing is recommended by Microsoft, but the only caveat right now is that Microsoft has not (yet?) provided a low-code solution to open custom pages. Instead, they have given us a list of JavaScript samples that can be used as starting points. I specially recommend you read the first article to have a good idea of where we are headed.
First things first, right? If we are building a dialog box to run cloud flows, we would first need a cloud flow. Anything would do, just remember it should be a flow that can be triggered by Power Apps and I strongly recommend to respond back with the result in your flow.
You’ll notice in the following screenshot that I have a flow that uses PowerApps (V2) as trigger and at the end it is returning two parameters as a result to any app that calls the flow.
Adding a Respond to a Power App or Flow at the end will let the caller (app or flow) to know if your flow has succeeded or not and the caller will have a chance to react accordingly.
A custom page in your model-driven app
Next step is to build a custom page that can be used as a dialog box. The goal of this custom page (which is a kind od Canvas app with some more power from model-driven apps by the way) is to help end-user run a flow (or several flows) and wait for them to complete while displaying a spinner and once the flow complete let the user know if something went wrong or simply disappear and refresh the data in the form.
To add a custom page to your model-driven app, you need to open the app in the new modern editor and click on the New Page button.
I suggest you build your custom page the responsive way, so you can display it in any kind of dialog no matter what size. To make a responsive page, I take the following approach.
As you can see on the image, I have one screen that contains a vertical container, an image called BusySpinner, a rectangle that fills the entire page to hide the content when the flow is ongoing.
In the VerticalContainer, I have two horizontal containers, the first one contains the only input parameter (language) I need to ask to user and the last one contains the OkButton. In your case you might need several horizontal containers. One per each parameter.
The visual layout of the page is similar to the following diagram. If it’s not clear enough, let me know in the comments and I will write all the properties you might need in a table here.
You will two variables to store the state of the page, IsBusy and IsValid.
IsBusy – this variable will have the value true when the cloud flow is executing and false when not.
IsValid – this variable will be false by default and will be true when all the required parameters are given by the end user and they are valid.
In your app’s OnStart you need the following commands to initialize the variables. I use this event to also initialize a simple collection I will use to fill a combo-box for my language parameter.
Set(IsValid, Not(IsBlank(Param("recordId"))));
Set(IsBusy, false);
If(Not(IsValid), Notify("Opportunity is not selected!"));
ClearCollect(LanguageCodes, "EN", "FR", "DE", "PT");
Next, you’ll need to add the cloud flow to your page, once you do that, you can trigger it in the OnSelect event of the OkButton.
As you have noticed in the above code, I’m setting the IsBusy variable to true then I start the flow, while putting its result in a variable called FlowResult, this allows me to check the result of the flow in the line that follows. If the result is successful, I simply close the page (dialog), otherwise I’ll inform the user. At the end I set IsBusy to false.
To display the spinner and fade the content behind, when the flow is ongoing, you simply need to put the IsBusy in the Visible property of both BusySpinner and BusyOverlay.
You can also use the following expression in the DisplayMode property of the OkButton. This will disable the button when the flow is ongoing or when the input parameters are not valid.
If(IsValid And Not(IsBusy), DisplayMode.Edit, DisplayMode.Disabled)
Reusable JavaScript library to show custom pages in dialogs
Save the following script in a .js file (e.g. dialogs.js) and then open your solution in https://make.powerapps.com, and click on New, from the drop-down menu, open More and click on Web resource.
/* This function uses Navigation API to display a centered dialog. */
/* Simple parameters are used instead of object to make the function compatible with command bar parameters. */
function displayDialog(rowId, tableName, pageName, title, contentWidth, contentHeight, primaryControl) {
const parseSize = (str, defaultStr) => (str || defaultStr || "500px").match(/([\d\.]*)(.*)/).splice(1,2);
let [w, wUnit] = parseSize(contentWidth);
let [h, hUnit] = parseSize(contentHeight);
var pageInput = {
pageType: "custom",
name: pageName,
entityName: tableName,
recordId: rowId.replace(/[{}]/g, "")
};
var navigationOptions = {
target: 2,
position: 1,
width: {value: parseFloat(w), unit: wUnit},
height: {value: parseFloat(h), unit: hUnit},
title: title
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions)
.then(
function () {
if (primaryControl.getFormContext) { primaryControl.getFormContext().data.refresh(); }
else {primaryControl.data.refresh();}
}
).catch(
function (error) {
/* TODO: Add exception handling here. */
}
);
}
The above code is based on the Centered Dialog sample of Microsoft. I have only made a bit more production ready.
The whole script is a function that can be easily called from the new Command bar designer (still in preview).
Width and height have default values to be used in case no value is given by the custom button.
Curly braces are removed from rowId to make it Power App friendly.
When the dialog box closes, the content is refreshed.
Pay attention to lines 23 and 24 and see how I’m getting to the formContext object. It is done that way so you can use the same script from any flavor of command bar.
I recommend you improve the script to cover for some edge cases and more importantly add exception handling to notify the end-user properly if “something went wrong“. â˜
Adding a custom button to command bar
The new command bar editor can be found in the modern app editor (which is in preview at the time of this writing). You need to select an app in your solution and click on the three dots to open it context menu and select Edit in preview item. Just like the following screenshot.
Once in the new App editor, scroll down to the table where you its command bar to receive this fancy new button. In the following screenshot, you’ll notice I’m editing the command bar of Opportunity table.
Power Platform will ask you which command bar you are interested in. I am going to choose Main form, but in case you are wondering what are these options, you need to read the first link I have shared in the beginning of my post 😉.
Once in the command bar editor, add a new button with the following properties.
Property
Value
Label
Run my flow (can be anything you want).
Action
Run JavaScript (in future I hope we will be able to use PowerFx instead).
Library
The name you gave to your web resource. I used fancy_dialogs for example. NOTE! If you can’t find your library in the list you will need to click on the pen icon just besides the drop-down menu.
Function name
displayDialog
Parameter 1
FirstPrimaryItemId
Parameter 2
String | opporunity (this should be the logical name of your table)
Parameter 3
String | fancy_flowlauncher (this should be the name of your custom page)
Parameter 4
String | Run flow (this will be the title of your dialog)
Parameter 5
String | 500px
Parameter 6
String | 500px
Parameter 7
PrimaryControl
If you have followed all the steps correctly, the end result should be similar to the following.
When deciding between business rules and client-side scripts in Model-driven Power Apps, the best practice is to use business rules are much as possible and only resort to writing code when necessary. The following table lists some of the most important reasons you might choose one over the other.