Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 40

Reusable JavaScript Libraries for Form Scripts

Recommended resources

Hello! Welcome back to our advanced Power Platform developer course.

In our last lesson, we explored how to build and integrate custom pages, and we saw how JavaScript is essential for launching these pages contextually from a Model-driven App's command bar. That work highlighted the need for well-structured, maintainable client-side code.

Today, we will address that need directly. Your goal for this lesson is to learn how to implement reusable JavaScript web resource libraries to share functions across multiple form scripts. We'll move away from the traditional approach of writing isolated scripts for each form and instead adopt a modern, professional workflow for building scalable and maintainable client-side logic. Given your background in C# and structured development, you'll find these patterns both familiar and powerful.

1. From Script Files to Code Libraries

In classic Dynamics 365 development, it was common to manage client-side logic by creating multiple .js files and loading all of them onto a form. This approach often leads to several challenges:

  • Global Scope Pollution: All functions from all loaded scripts exist in the same global window object, increasing the risk of naming collisions.
  • Dependency Management: If two different scripts require different versions of a third-party library (like jQuery or lodash), it's difficult or impossible to manage on the same form.
  • Maintainability: As the number of scripts grows, it becomes difficult to track dependencies and understand the overall architecture, especially in a team environment.

The modern approach, which we'll cover today, solves these problems by using professional web development tools to bundle our code into organized, efficient libraries.


2. Structuring Your Code with TypeScript and Namespaces

The first step toward building a reusable library is to structure your code logically. Instead of having a flat list of functions, we group them into namespaces. For a developer with your C# experience, the concept of namespaces for avoiding name conflicts and organizing code is second nature.

TypeScript is the ideal language for this. It's a superset of JavaScript that adds static typing, classes, interfaces, and modules, allowing you to write more robust and self-documenting code. The Power Platform client-side object model (formContext, Xrm) has excellent TypeScript type definitions available, which provides IntelliSense and compile-time checking in your editor.

A common pattern is to create a common or helpers file for functions that can be used across any entity, and then create entity-specific files for form logic.

How to create a TypeScript project for Model Driven Apps

This article, 'How to create a TypeScript project for Model Driven Apps' by Wilmer Alcivar, provides a practical example of this structure. We won't follow the project setup steps exactly, as we'll use a more advanced bundler shortly, but focus on how the code itself is organized.

Read the section titled 'Add a helper file'. Pay close attention to how the author creates a helpers.ts file and wraps the functions inside a namespace: namespace P365I.Common.Helpers { ... }. This is the foundational concept for creating a shared library.

By using namespaces (e.g., YourPrefix.Common.Helpers and YourPrefix.Forms.Contact), you can create a clear hierarchy and prevent function names like onLoad from clashing between different forms.


3. Bundling with Webpack for Professional Deployment

Having organized TypeScript files is great for development, but Dataverse web resources are still JavaScript. Furthermore, we want to combine all our separate .ts files and any third-party libraries into a single, optimized .js file for deployment. This is where a module bundler like webpack comes in.

Webpack is a standard tool in modern web development that processes your project's files and dependencies and outputs one or more bundled files (called "bundles") that you can deploy.

TypeScript and Dynamics 365 / Model-Driven App client-side ...

The article 'TypeScript and Dynamics 365 ... Part 2' by Benjamin Bertin provides an excellent guide on setting up a professional TypeScript project using webpack. This approach is highly recommended for real-world projects.

Please read through the article from the 'Introduction' to the 'Testing' section. This is the core of today's lesson. Focus on these key concepts: The Problem: The introduction reaffirms the drawbacks of managing script files manually. Webpack Configuration (webpack.config.js): Understand the purpose of the entry (your source files) and output (the bundled result) properties. Exposing the Library: This is the most critical part. Pay close attention to the output.library configuration. The line library: { name: 'www', type: 'var' } tells webpack to create a global variable (named www in this example) that will contain all your exported functions. This is how the Model-driven App form can access your bundled code. Organizing with Namespaces: See how the author refactors the project to use a main entry file (crm.ts) that imports and exports modules to create a nested namespace structure like www.CRM.Contact.onFormLoad. Pros and Cons: The section 'About the single generated file' provides important architectural considerations that are relevant to your role as a senior developer.

By following this pattern, you create a single source of truth for all your client-side code. You can include external packages from npm (like lodash in the example), and webpack will intelligently bundle them into your final JavaScript file.

Test your understanding!

You are tasked with adding a new function, validateIBAN, to your project. This function will be used on the Account and Invoice forms. You also need a new function, onStatusChange, that is specific to the Case form.

Based on the webpack and namespace pattern from the article, describe the high-level steps you would take to implement this. What might the final function calls look like when you configure the event handlers in the app?

Show answer
  1. Create/Update a Common File: Create a new file, src/common/validation.ts, or add to an existing common library file. In it, define and export the validateIBAN function.
  2. Create an Entity-Specific File: Create a new file, src/case/form.ts, and define and export the onStatusChange function.
  3. Update the Main Entry Point: In your main crm.ts (or equivalent) file, import the new modules.
    • import * as Validation from './common/validation';
    • import * as CaseForm from './case/form';
  4. Expose them via the Namespace: In the same file, add them to your main exported object.
    export const CRM = {
        Contact: ContactForm,
        Account: AccountForm,
        Common: {
            Validation: Validation 
        },
        Case: CaseForm
        // ... and so on
    };
    
  5. Configure Event Handlers:
    • On the Account and Invoice forms, the validateIBAN function would be called as: www.CRM.Common.Validation.validateIBAN
    • On the Case form's Status field, the onStatusChange event would call: www.CRM.Case.onStatusChange

4. Deploying and Using Your Library

Once your project is structured and webpack is configured, the workflow is straightforward:

  1. Build: Run your build command (e.g., npm run build). This will generate the single, bundled .js file (e.g., dist/www_crm.js).
  2. Deploy: Upload this single .js file as a web resource in your Dataverse solution.
  3. Configure: Open any Model-driven App form (e.g., Contact, Account, Case). In the form properties, add your single library file to the form.
  4. Register Handlers: In the event handlers for OnLoad, OnSave, or any field's OnChange event, specify the full namespaced path to your function (e.g., www.CRM.Contact.onFormLoad). Don't forget to check "Pass execution context as first parameter."
Configuring an On Save Event Handler in a Model-driven App
This is where you connect your bundled library to the form. The 'Function' field would contain the full namespaced path to your function, such as `www.CRM.Contact.onFormSave`.

The key benefit is reusability: the same www_crm.js web resource is loaded on every form, but you selectively call the functions relevant to that specific form or control, leading to a much cleaner and more manageable solution.


Conclusion

In this lesson, we've elevated your client-side development approach from writing simple scripts to building professional, reusable code libraries. This method is not only best practice but is also essential for managing complex logic in enterprise-scale Power Platform solutions.

Key Takeaways:

  • Traditional multi-file JavaScript approaches in Model-driven Apps lead to maintenance and dependency issues.
  • TypeScript provides type safety and better code organization, which is a natural fit for developers with a background in strongly-typed languages like C#.
  • Webpack is a powerful module bundler that compiles your TypeScript, bundles dependencies, and creates a single, optimized JavaScript file for deployment.
  • The output.library configuration in webpack is crucial for exposing your functions to the Model-driven App's global scope.
  • Organizing your code into namespaces (e.g., YourPrefix.Entity.Function) prevents naming conflicts and makes your library easy to navigate and use across many forms.

Up Next:

You have now mastered creating reusable logic with TypeScript libraries. In our next lesson, we will begin a new module and take this a step further by building reusable UI controls. We will dive into the Power Apps Component Framework (PCF). You'll see how the development setup, involving TypeScript and command-line tools, is a natural and powerful progression from the library-building techniques we've covered today.