power platform

How to Create a Reusable In-App Notification Feature in Power Platform and D365

james

james

Share this post:
How to Create a Reusable In-App Notification Feature in Power Platform and D365

How to Create a Reusable In-App Notification Feature in Power Platform and D365

In-app notifications are a powerful way to enhance user experience in Power Platform/D365, allowing you to provide real-time feedback and alerts to your users. This guide will show you how to create a reusable notification system that any developer in your organization can easily implement.

Overview

Why Create a Reusable Notification System?

When implementing notifications in D365, you could write the notification code directly in each form or ribbon where it’s needed. However, this leads to:

  • Duplicated code across multiple forms
  • Inconsistent notification behavior
  • Difficult maintenance when changes are needed
  • More time spent testing each implementation

Instead, we’ll create a reusable notification system using two files:

  • Code/Utils/Notifications/notificationUtils.js: A shared utility that handles all notification logic
  • Code/Ribbon/userRibbon.js: An example implementation showing how to use the utility

This approach allows any developer to add notifications to their forms by simply:

  1. Including the notificationUtils.js library
  2. Calling our standardized notification methods

Step-by-Step Implementation

Step 1: Establish Naming Conventions

Before we start coding, let’s establish a clear naming convention for our JavaScript modules. We’ll use a three-part namespace structure:

window.OrgName.ModuleName.Function;

This structure provides several benefits:

  • Organization: The root namespace (James) prevents conflicts with other solutions in the global scope
  • Modularity: The second part (Utils, Ribbon) groups related functionality together, making it easier to maintain and extend
  • Specificity: The final part (InAppNotifications) clearly identifies the purpose of each module, improving code readability
  • Encapsulation: Each level of the namespace provides a natural boundary for related code, reducing the risk of naming collisions
  • Discoverability: The hierarchical structure makes it easier for developers to find and use the functionality they need

For example:

  • James.Utils.InAppNotifications: Our shared notification utilities
  • James.Ribbon.User: Ribbon button functionality for user notifications

Step 2: Create the Notification Utility

First, let’s create our reusable notification code in notificationUtils.js. We’ll use a block scope to avoid polluting the global namespace and implement proper initialization checks:

// Initialize namespace
window.James = window.James || {};
window.James.Utils = window.James.Utils || {};
window.James.Utils.InAppNotifications =
  window.James.Utils.InAppNotifications || {};

// Implementation in block scope to avoid polluting global scope
{
  // Only initialize if core functionality is not present
  if (!window.James.Utils.InAppNotifications.SendAppNotificationRequest) {
    console.log("notificationUtils.js - Initializing InAppNotifications");

    // Define enums as immutable constants
    const TOAST_TYPE = Object.freeze({
      TIMED: 200000000, // Shows for 4 seconds then auto-dismisses
      HIDDEN: 200000001 // Only visible in notification center
    });

    const ICON_TYPE = Object.freeze({
      INFO: 100000000, // Blue information icon (i)
      SUCCESS: 100000001, // Green checkmark
      FAILURE: 100000002, // Red X
      WARNING: 100000003, // Yellow warning triangle
      MENTION: 100000004, // @ symbol for mentions
      CUSTOM: 100000005 // Custom icon (requires IconUrl)
    });

    const PRIORITY = Object.freeze({
      LOW: 200000000, // Low priority
      NORMAL: 200000001, // Normal priority
      HIGH: 200000002 // High priority
    });

    // Safely extend the namespace with our enums
    Object.assign(window.James.Utils.InAppNotifications, {
      TOAST_TYPE,
      ICON_TYPE,
      PRIORITY
    });

    // Helper function to create side pane actions
    window.James.Utils.InAppNotifications.createSidePaneAction = function (
      title,
      paneTitle,
      entityName,
      recordId,
      width = 400
    ) {
      return {
        title: title,
        data: {
          type: "sidepane",
          title: paneTitle,
          width: width,
          entityName: entityName,
          recordId: recordId
        }
      };
    };

    // Helper function to create record links
    window.James.Utils.InAppNotifications.createRecordLink = function (
      title,
      entityName,
      recordId,
      options = false
    ) {
      const useNewWindow =
        typeof options === "boolean" ? options : (options?.newWindow ?? false);

      if (options?.useSidePane) {
        return window.James.Utils.InAppNotifications.createSidePaneAction(
          title,
          options.paneTitle || title,
          entityName,
          recordId,
          options.width
        );
      }

      return {
        title: title,
        data: {
          type: "record",
          target: useNewWindow ? "newwindow" : "form",
          entityName: entityName,
          recordId: recordId
        }
      };
    };
  }
}

Step 3: Create the User Ribbon Implementation

Next, let’s create userRibbon.js to demonstrate how to use our notification utility. We’ll add proper dependency validation:

// Initialize namespace
window.James = window.James || {};
window.James.Ribbon = window.James.Ribbon || {};
window.James.Ribbon.User = window.James.Ribbon.User || {};

// Implementation in block scope
{
  // Validate required dependencies
  if (!window.James?.Utils?.InAppNotifications?.SendAppNotificationRequest) {
    console.error(
      "UserRibbon.js - Required dependency 'SendAppNotificationRequest' not found"
    );
    throw new Error(
      "Required dependency 'SendAppNotificationRequest' not found"
    );
  }

  // Validate required enums
  const requiredEnums = ["PRIORITY", "ICON_TYPE", "TOAST_TYPE"];
  for (const enumName of requiredEnums) {
    if (!window.James?.Utils?.InAppNotifications?.[enumName]) {
      console.error(`UserRibbon.js - Required enum '${enumName}' not found`);
      throw new Error(`Required enum '${enumName}' not found`);
    }
  }

  window.James.Ribbon.User = {
    SendNotification: async function (
      primaryControl,
      recipientAttribute,
      title,
      body,
      useSidePane = false
    ) {
      try {
        // Get recipient
        const recipientId = primaryControl
          ?.getAttribute(recipientAttribute)
          ?.getValue?.()?.[0]?.id;
        if (!recipientId) {
          throw new Error("No recipient selected");
        }

        // Format recipient URL
        const recipientUrl = `/systemusers(${recipientId.replace(/[{}]/g, "")})`;
        const recordId = primaryControl.data.entity
          .getId()
          .replace(/[{}]/g, "");
        const entityName = primaryControl.data.entity.getEntityName();

        // Create record link action
        const actions = window.James.Utils.InAppNotifications.createRecordLink(
          "View Record",
          entityName,
          recordId,
          useSidePane
            ? {
                useSidePane: true,
                paneTitle: "Record Details",
                width: 400
              }
            : false
        );

        // Create notification with markdown support
        const overrideContent = {
          "@odata.type": "#Microsoft.Dynamics.CRM.expando",
          title: "**" + title + "**",
          body: `A new ${entityName} has been assigned to you. ${body}`
        };

        // Send notification
        const notificationRequest =
          new window.James.Utils.InAppNotifications.SendAppNotificationRequest(
            title,
            recipientUrl,
            body,
            window.James.Utils.InAppNotifications.PRIORITY.HIGH,
            window.James.Utils.InAppNotifications.ICON_TYPE.SUCCESS,
            window.James.Utils.InAppNotifications.TOAST_TYPE.TIMED,
            30,
            overrideContent,
            actions
          );

        const result = await Xrm.WebApi.online.execute(notificationRequest);
        if (result.ok) {
          console.log("Notification sent successfully");
        }
      } catch (error) {
        console.error("Error in SendNotification:", error);
        await Xrm.Navigation.openErrorDialog({ message: error.message });
      }
    }
  };
}

Key Improvements in This Implementation

  1. Immutable Enums: We use Object.freeze() to make our enum objects immutable, preventing accidental modifications.

  2. Safe Namespace Extension: We use Object.assign() to safely extend the namespace with our enums, which is cleaner and less error-prone.

  3. Proper Initialization Check: Instead of using a flag, we check for the existence of core functionality (SendAppNotificationRequest).

  4. Dependency Validation: The ribbon code validates both the core functionality and required enums before proceeding.

  5. Block Scoping: Both files use block scoping to avoid polluting the global namespace while still exposing the necessary functionality.

How to Use

  1. Create two JavaScript web resources:

    • Code/Utils/Notifications/notificationUtils.js: Contains the core notification functionality
    • Code/Ribbon/userRibbon.js: Contains the implementation code
  2. Add both web resources to your form, ensuring notificationUtils.js is loaded first.

  3. Call the notification function from your ribbon button:

// Example ribbon button command
James.Ribbon.User.SendNotification(
  primaryControl,
  "James_recipientid",
  "New Assignment",
  "You have been assigned a new task.",
  true // Use side pane
);

Best Practices

  1. Load Order: Always load notificationUtils.js before any code that depends on it.
  2. Error Handling: Use try-catch blocks and provide meaningful error messages.
  3. Dependency Checks: Validate required dependencies before executing code.
  4. Immutability: Use Object.freeze() for constant values like enums.
  5. Namespace Protection: Use block scoping to avoid global namespace pollution.
  6. Type Safety: Use JSDoc comments to document parameters and return types.

By following these patterns, you’ll create maintainable, reusable notification code that any developer in your organization can easily implement.

About the Author

james

james

Technical Consultant