Simple Approval Design For Model-Driven Apps

Do you know the In-App Notification feature from Model Driven Apps? This feature can create a notification that targeted a specific User. We also can add action to the notification so the User can also interact with multiple actions (now is limited to just an open URL). In short, this feature is very useful for creating the Approval process. Without further ado, let’s go to my proposed solution! 😎

The Necessary Part

I created the below table for this demonstration purpose:

Request Table

As you can see, the User will fill in the Approver that needs to approve the request. Then in the status field, there are 3 options which are Draft, Approved, or Rejected.

For the next one, you need to create a Model-Driven App. The reason for it is because we need to turn on the feature from the Settings (in top-left from the App > go to Features tab > enable the In-app notifications option):

Enable the In-app notifications feature

After you enable the feature, you will see the bell icon inside the app:

In-app notification icon

Create Custom API

To update the data from the backend, we also need to create Custom API. Here is my code:

using Microsoft.Xrm.Sdk;
using System;

namespace DemoPlugin
{
    public class ApprovalApi : IPlugin
    {
        public const string InputParameter = "input";
        public const string OutputParameter = "output";

        public void Execute(IServiceProvider serviceProvider)
        {
            var pluginExecutionContext = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            var serviceFactory =
                (IOrganizationServiceFactory)serviceProvider.GetService(
                    typeof(IOrganizationServiceFactory));
            var service = serviceFactory
                .CreateOrganizationService(pluginExecutionContext.UserId);

            var input = pluginExecutionContext.InputParameters[InputParameter].ToString();

            var data = input.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            var entityName = data[0];
            var entityId = new Guid(data[1]);
            var type = data[2];

            var update = new Entity(entityName, entityId)
            {
                ["tmy_status"] = new OptionSetValue(type == "approve" ? 932000001 : 932000002)
            };
            service.Update(update);

            pluginExecutionContext.OutputParameters[OutputParameter] = "Success";
        }
    }
}

The code is pretty simple. The API will require string input. This string will contain three pieces of information (separated by ‘;‘ character). The entity name, Id, and also action is chosen by the Approver. The next step is just to update the entity with the information collected. For the output, for now, I just send the “Success” string.

Once you did this, we can register the plugin to our Dataverse instance:

Register the plugin

After that, we need to create the Custom API. As usual, I created the Custom API from Custom API Manager by David Rivard – XrmToolBox:

Approval API definition

Unfortunetely at the time of writing, in-app notification only allows opening a URL (GET method) which limits how we can process this design as simple as possible. But for now, I created another WebResource page that will bridge the action to the API that we created before. The flow will be like this:

Approve/Reject flow

So for the HTML, I created with below code:

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.2.1/dist/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <style>
        body,html{
            height:100%;
        }
    </style>
    <title>Approval - Loading</title>
  </head>
  <body>
    <div class="container h-100">
        <div class="row h-100 justify-content-center align-items-center">
            <div id="loading">
                <div class="spinner-grow" style="width: 3rem; height: 3rem;" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
                Please wait..
            </div>
            <div id="result" class="text-center"></div>
        </div> 
    </div>
      
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha512-+NqPlbbtM1QqiK8ZAo4Yrj2c4lNQoGv8P79DPtKzj++l5jnN39rHA/xsqn8zE9l0uSoxaCdrOgFs6yjyfbBxSg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.6/dist/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.2.1/dist/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
    <script>
        function executeApi() {
            var searchParams = new URLSearchParams(window.location.search);
            var input = searchParams.get('data');
            var baseUrl = window.location.origin;
            var url = `${baseUrl}/api/data/v9.2/tmy_approvalapi`;
            $.ajax({
                url: url,
                type:'POST',
                data: JSON.stringify({input:input}),
                contentType: 'application/json',
                dataType: 'json'
            }).done(function(result) {
                $('#loading').hide();

                $('#result').html(`<h3>${result.output}</h3>`);
            });
        }

        $(document).ready(function() {
            window.setTimeout(function() {
                executeApi();
            }, 3000);
        });
    </script>
  </body>
</html>

Create the WebResource and use the above code. Once you created the WebResource, you will get the URL that will be use for the Notification action:

Copy the URL

Demo

Once everything is set up, then we can run our demonstration. I created the below data and get the record Id:

Created record

Then I construct the below Javascript and execute in the Developer Tools > Console:

var systemuserid = "2AD07D50-3A20-ED11-B83B-00224828E219";
var notificationRecord = {
  title: "Request Approval",
  body: "There is new request need your attention.",
  "ownerid@odata.bind": "/systemusers(" + systemuserid + ")",
  icontype: 100000000, // info
  data: JSON.stringify({
    actions: [
      {
        title: "Approve",
        data: {
          url: "https://yourcrm.crm.dynamics.com/WebResources/tmy_loading?data=tmy_request;aa5babb8-872b-ed11-9db1-000d3a542d2f;approve",
          navigationTarget: "dialog",
        }
      },
      {
        title: "Reject",
        data: {
          url: "https://yourcrm.crm.dynamics.com/WebResources/tmy_loading?data=tmy_request;aa5babb8-872b-ed11-9db1-000d3a542d2f;reject",
          navigationTarget: "dialog",
        }
      }
    ],
  }),
};
Xrm.WebApi.createRecord("appnotification", notificationRecord).then(
  function success(result) {
    console.log("notification created with multiple actions: " + result.id);
  },
  function (error) {
    console.log(error.message);
    // handle error conditions
  }
);

Once you execute the Javascript (you can change this creation to the Plugin as well. But for simplicity, I just use this method 😎):

Notification created

Below is the demo once you select Approve/Reject:

Result

Happy CRM-ing! 😎

Advertisement

4 thoughts on “Simple Approval Design For Model-Driven Apps

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.