Development of custom Html/JS Webresources with Help of Modern Frameworks

As you may know, Dialogs have been deprecated and are going away. I have a few customers who’ve asked me to replace their existing dialogs with something that would allow them to get the same result without loses in functionality.

Microsoft recommends 2 techniques to replace Dialogs – BPF and Canvas Apps (embedded or opened by the Url).
BPF just can’t do what I need.
Canvas Apps looks like a good solution but with the ease of customizing, I experienced issues with support and moving Canvas Apps between environments and sending data back to the calling source.

So I decided to choose the third option – development of HTML/JS Web Resources.

Tools and technologies

Let me explain my approach. I plan to develop rich Html/Js webresources with UCI lookalike controls. That’s why I added “Office UI Fabric” to the list. “Office UI Fabric” is a “React”-based framework – that’s why “React” is in the list as well.

To quickly begin app development, I use the “Create React App” package because it contains the initial version of the application, configured webpack and much more so there is no need to do a lot of the initial configuration.

Last but not least – “Visual Studio Code” – that’s the editor I use to work with the code for this project.

Initialization of the project

Before initialization of the project, it’s required to install npm package “create-react-app”. In order to do that, I run the following command from the terminal:

npm install -g create-react-app

Once it is installed, I navigate to the folder of the project and run the following command:

npx create-react-app abcustomdialog --template typescript

After the project is initialized, I open it using “Open Folder” menu item of VSCode. That’s how the initial structure of the project could look like:

To check that everything works the way it should, I run the following command from the terminal:

npm start

As a result, in the browser I see the following application:

Obviously, I don’t need everything that is added to the project by default, so I delete unneeded items and remove all places where those items are referenced. The following screenshot demonstrates the comparison of the project’s structure before and after the “clean-up”:

After all modifications, files look like this:

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

I deleted all of the content from “App.css”. And because of this reason I don’t provide the content of it here.

App.tsx:

import React from 'react';
import './App.css';

function App() {
  return (
    <>
    </>
  );
}

export default App;

index.tsx:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App />,
  document.getElementById('root')
);

During the run of “npm start” command, a blank browser window is presented. So now, after all preparations have been completed, it’s possible to switch to the fun part that is the development of the application.

In my project, I plan to rely on Office UI Fabric (or Fluent UI how it is called now) so I run the following command to install the required packages:

npm install office-ui-fabric-react

Scenario

With a click of the ribbon button, the user is presented with a modal dialog window that contains a text and date input control, initial values for fields are passed from the calling script. By click on the “OK” button located at the bottom of the dialog – the window is closed and user inputs are returned to the calling source. If “Cancel” button is clicked – nothing happens.

Implementation

Let me start from “App.tsx” – the following listings contain all of the code that is required and contains comments (if I missed anything feel free to ask for clarification in comments):

import React from 'react';
import './App.css';
//those components are required because I use it in dialog form
import { Stack, PrimaryButton, TextField, DatePicker, initializeIcons } from 'office-ui-fabric-react';

//initialization of icons - without calling this function icons for DatePicker would not be shown
initializeIcons();

//this interface contains description of state and properties for Dialog application
export interface IABCustomDialogState {
  //text input
  text: string | undefined;
  //date input
  date: Date | undefined;
}

class ABCustomDialog extends React.Component<IABCustomDialogState, IABCustomDialogState> {
  constructor(props: IABCustomDialogState) {
    super(props);

    //passing of data from properties to state of control during initialization
    this.state = props;
  }

  //this function is used to control formatting of datetime field
  private formatDate = (value?: Date | undefined): string => {
    if (!value) {
      return "";
    }

    let result = ("0" + (value.getMonth() + 1).toString()).slice(-2) + "/";
    result += ("0" + value.getDate().toString()).slice(-2) + "/";
    result += value.getFullYear().toString();

    return result;
  }

  //heart of application that returns React markup
  render() {
    return (
      <>
        <TextField
          label="Label of the Text Input"
          value={this.state.text}
          onChange={(event: any, newvalue: string | undefined) => { this.setState({ text: newvalue }); }} />
        <DatePicker
          label="Label of the Date Input"
          value={this.state.date}
          onSelectDate={(newValue: Date | undefined | null) => { this.setState({ date: newValue ? newValue : undefined }); }}
          formatDate={this.formatDate}
        />
        <div className="footerDiv">
          <Stack horizontal horizontalAlign={"end"} tokens={{ childrenGap: 10, padding: 10 }}>
            <PrimaryButton text="OK" onClick={() => {
              //This code on click of "OK" button returns current state to calling part
              window.returnValue = this.state;
              window.close();
            }} />
            <PrimaryButton text="Cancel" onClick={() => {
              //This code closes the dialog window - ATM this is the only possible way
              window.close();
            }}/>
          </Stack>
        </div>
      </>);
  }
}

export default ABCustomDialog;

I used special css class for “footer” div. Here it is:

.footerDiv {
    position: absolute;
    bottom: 0;
    width: 98%;
}

The next place I have to modify is the code that “starts” the application – “index.tsx”. Here is the code:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

//following code parses url of dialog to extract required data including "data" parameter
const queryString = window.location.search.substring(1);
let params: any = {};
const queryStringParts = queryString.split("&amp;");
for (let i = 0; i < queryStringParts.length; i++) {
  const pieces = queryStringParts[i].split("=");
  params[pieces[0].toLowerCase()] = pieces.length === 1 ? null : decodeURIComponent(pieces[1]);
}

//deserializing of the data parameter
const data = JSON.parse(params.data);

//rendering of application and passing parameters inside
ReactDOM.render(
  <App text={data.text} date={new Date(data.date)} />,
  document.getElementById('root')
);

After this change, the App will stop rendering and browser will display bunch of errors, but never mind – it’s ok – the next tests I will do will be tests from CE.

Build and deployment

To build the project I run the following command:

npm run build

Once the project is built, a new folder is created in the project’s folder – “build”. Here is what it could look like:

That’s not the perfect folder structure to maintain. In order to change it, it would be required to change webpack config. To do that I open \node_modules\reacts-scripts\config\webpack.config.js and look for the following part of  configuration and comment it out to change file to:

Also I apply following changes to have consistent naming of css and js parts of the project:

After changes are applied, I run the build command using the following script:

npm run build

Now “build” folder should look like following:

The last change in the files I will make before the deployment to CE is the following change in “index.html” from “build” folder – it’s basically a change of references for .js and .css files from absolute to relative:

Now those 3 files can be imported to CE. I created a solution and added 3 Web Resources to it:

Invoking the Dialog Window

Using following code, I invoke this dialog from the form or ribbon script. As a parameter for the function I pass in formContext:

var AB = AB || {};
AB.ContactRibbon = (function(){

    function openDialog(formContext){
        var data = {
            text: formContext.data.entity.getPrimaryAttributeValue(),
            date: formContext.getAttribute("createdon").getValue()
        };

        var dialogParameters = {
            pageType: "webresource",//required
            webresourceName: "ab_/ABCustomDialog/index.html",//Html Webresource that will be shown
            data: JSON.stringify(data)
        };
        
        var navigationOptions = {
            target: 2,//use 1 if you want to open page inline or 2 to open it as dialog
            width: 400,
            height: 300,
            position: 1
        };

        Xrm.Navigation.navigateTo(dialogParameters, navigationOptions).then(
            function (returnValue) {
                console.log(returnValue);
                //Add your processing logic here
            },
            function (e) {
                Xrm.Navigation.openErrorDialog(e);
            });        
    }

    return {
        OpenDialog: openDialog
    };
})();

Demonstration

The following GIF demonstrates how it would look like if this dialog is called on click of a ribbon button:

About the Author:

Welcome to my portal. My name is Andrew Butenko and I’m an expert in the area of development for Microsoft Dynamics CRM/365.

I graduated from National Technical University “KPI” Faculty of Computer Science in 2006. Up until 2008, I worked as a .Net/T-SQL developer.

I started working with Microsoft Dynamics CRM in 2008, just after version 4.0 came out. Not a lot of educational materials were available back then, so I had to learn the software on my own.

Now I understand that new developers can improve their skills much faster after taking classes with mentors that have real-world, hands-on experience in the software.

Unfortunately, no classes or mentors were available in Ukraine in 2008, so I had to learn Dynamics CRM by implementing it using the trial and error method.

While with Microsoft Dynamics CRM/365, I worked as a Developer, Team Leader, and Architect. In 2010, I was awarded Microsoft’s Most Valuable Professional (MVP) Award. I have received the award in each of the last 10 years.

Twitter – https://twitter.com/a33ik

Youtube – https://www.youtube.com/andrewbutenko

Reference:

Butenko, A. (2020). Development of custom Html/JS Webresources with help of modern frameworks. Available at: https://butenko.pro/2020/04/22/development-of-custom-html-js-webresources-with-help-of-modern-frameworks/ [Accessed: 18th November 2020].

Share this on...

Rate this Post:

Share: