Power Platform Web Resource Skill

Shambhu Tiwary

# Model-Driven Power Apps Web Resources & Client Scripting Documentation


Combined documentation for developing web resources and client-side scripts for Model-Driven Power Apps.


---


## Folder Structure


```

.agents/skills/model-driven-powerapps-web-resources/

├── SKILL.md

├── ScriptsDoc.md (this file)

├── web-resources/

│   ├── overview.md

│   └── html-css-js.md

├── client-scripting/

│   ├── object-model.md

│   ├── formcontext.md

│   ├── xrm-api.md

│   ├── events.md

│   └── best-practices.md

└── samples/

    ├── pass-data-web-resource.md

    └── import-files-web-resources.md

```


---


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/SKILL.md -->

<!-- ====================================================================== -->


---

name: model-driven-powerapps-web-resources

description: Provides comprehensive guidance for developing web resources (HTML, JavaScript, CSS) and client-side scripts for Model-Driven Power Apps. Use this skill when creating or modifying JavaScript libraries for form events, HTML web resources, CSS files, or when working with the formContext API and Xrm object model. Covers best practices for naming conventions, execution context, event handlers, and Dataverse integration.

license: MIT

compatibility: Requires Model-Driven Power Apps environment with Dataverse

metadata:

  author: power-platform-dev

  version: "1.0"

  keywords: powerapps, dataverse, javascript, web-resources, form-scripting, client-api

---


## Quick Start Rules


1. Always use supported APIs (formContext, Xrm object model)

2. Define a unique namespace for your JavaScript libraries

3. Use execution context to get formContext

4. Follow Microsoft's naming conventions with solution publisher prefix


## Sub-Skills


### Web Resources


| File | Description |

|------|-------------|

| [web-resources/overview.md](web-resources/overview.md) | Web resource types, referencing methods, naming conventions |

| [web-resources/html-css-js.md](web-resources/html-css-js.md) | HTML, CSS, and JavaScript web resource specifics |


### Client Scripting


| File | Description |

|------|-------------|

| [client-scripting/object-model.md](client-scripting/object-model.md) | Client API object model - executionContext, formContext, gridContext, Xrm |

| [client-scripting/formcontext.md](client-scripting/formcontext.md) | formContext properties and methods reference |

| [client-scripting/xrm-api.md](client-scripting/xrm-api.md) | Xrm.Navigation, Xrm.Utility, Xrm.WebApi methods |

| [client-scripting/events.md](client-scripting/events.md) | Events in forms and grids, event handlers, event pipeline |

| [client-scripting/best-practices.md](client-scripting/best-practices.md) | DOs and DON'Ts, JavaScript templates |


### Samples


| File | Description |

|------|-------------|

| [samples/pass-data-web-resource.md](samples/pass-data-web-resource.md) | Pass multiple values to web resource through data parameter |

| [samples/import-files-web-resources.md](samples/import-files-web-resources.md) | Import files as web resources |


## Documentation References


- [Web Resources Overview](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/web-resources)

- [Client Scripting](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/client-scripting)

- [Client API Reference](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/reference)

- [Walkthrough: Write Your First Client Script](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/walkthrough-write-your-first-client-script)


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/web-resources/overview.md -->

<!-- ====================================================================== -->


# Web Resources Overview


## Web Resource Types


Supported web resource file formats:


| File Type | Extensions | Type Value |

|-----------|------------|------------|

| Webpage (HTML) | .htm, .html | 1 |

| Style Sheet (CSS) | .css | 2 |

| Script (JScript) | .js | 3 |

| Data (XML) | .xml | 4 |

| Image (PNG) | .png | 5 |

| Image (JPG) | .jpg | 6 |

| Image (GIF) | .gif | 7 |

| Stylesheet (XSL) | .xsl, .xslt | 9 |

| Image (ICO) | .ico | 10 |

| Vector format (SVG) | .svg | 11 |


## Capabilities


- Web resources are virtual files stored in Dataverse database

- Retrieve using unique URL address

- Use in form customizations, SiteMap, or application ribbon

- Enable relative path references between files

- Available offline for Dynamics 365 for Outlook users

- Managed as solution components


## Limitations


- No server-side code execution (no ASP.NET)

- Limited to static files processed in browser

- Only available within Dataverse security context

- Maximum file size determined by `Organization.MaxUploadFileSize` (default 5MB)


## Referencing Web Resources


### $webresource Directive (Preferred)


Always use `$webresource:` directive when referencing from ribbon controls or SiteMap:


```xml

$webresource:<name of Web Resource>

```


**Example:**

```xml

$webresource:new_/content/page.htm

```


**Benefits:**

- Creates/updates solution dependencies automatically

- Ensures correct caching and versioning


### Relative URL Path


Use relative URLs when referencing between web resources. Use consistent naming conventions with publisher prefix as virtual root folder.


**Example structure:**

- `new_/content/contentpage.htm`

- `new_/styles/styles.css`

- `new_/scripts/script.js`


**Reference from HTML:**

```html

<script src="../scripts/script.js" type="text/javascript"></script>

<link href="../styles/styles.css" rel="stylesheet" type="text/css" />

<a href="../../isv_/foldername/dialogpage.htm">Dialog Page</a>

```


**For different publishers:**

```html

<script src="../../MyIsv_/scripts/customscripts.js" type="text/javascript"></script>

```


### Full URL Format


```

<Dataverse Environment URL>/WebResources/<name of web resource>

```


**Example:**

```

https://MyOrganization.crm.dynamics.com/WebResources/new_/test/test.htm

```


### Xrm.Navigation.openWebResource


```javascript

Xrm.Navigation.openWebResource(webResourceName, options, data);

```


## Naming Conventions


- Solution publisher customization prefix is added automatically

- Use forward slash `/` to simulate folder structure

- Include file extension in name for clarity


**Example:** With publisher prefix "new":

- `new_/scripts/common.js`

- `new_/styles/main.css`

- `new_/pages/form.html`


## Creating Web Resources


1. Navigate to solution in Power Apps

2. Select **New** > More > **Web resource**

3. Choose file, set display name and name

4. Ensure type matches file extension

5. Save and publish


## Web Resource File Size


Configured via System Settings > Email tab > `MaxUploadFileSize`


Default: 5MB


Applies to:

- Email attachments

- Notes

- Web resources


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/web-resources/html-css-js.md -->

<!-- ====================================================================== -->


# HTML, CSS, and JavaScript Web Resources


## JavaScript Web Resources


### Namespace Pattern (Required)


Always define a unique namespace to avoid function name collisions:


```javascript

var MyNamespace = window.MyNamespace || {};

(function () {

    this.myFunction = function (executionContext) {

        var formContext = executionContext.getFormContext();

        // Your code here

    };

}).call(MyNamespace);

```


### Reference JavaScript from HTML Web Resource


```html

<!-- Same publisher prefix -->

<script type="text/jscript" src="../scripts/myScript.js"></script>


<!-- Different publisher prefix -->

<script type="text/jscript" src="../../MyIsv_/scripts/customscripts.js"></script>

```


### Library Organization


Link shared JavaScript libraries to:

- Form scripts

- Webpage (HTML) web resources

- Ribbon commands


---


## HTML Web Resources


### Document Structure


```html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>Page Title</title>

    <script src="Script/script.js" type="text/javascript"></script>

    <link href="CSS/styles.css" rel="stylesheet" type="text/css" />

</head>

<body>

    <!-- Content -->

</body>

</html>

```


### Query String Parameters


HTML web resources accept these parameters:


| Parameter | Name | Description |

|-----------|------|-------------|

| `typename` | Table Name | Name of the table |

| `type` | Table Type Code | Integer uniquely identifying table |

| `id` | Object GUID | GUID representing a record |

| `orgname` | Organization Name | Unique name of organization |

| `userlcid` | User Language Code | Language code for current user |

| `orglcid` | Organization Language Code | Language code for organization |

| `data` | Optional Data Parameter | Custom parameter value |

| `formid` | Form ID | GUID representing form ID |

| `entrypoint` | Entry Point | String for custom help content |


### Accessing Parameters with JavaScript


```javascript

document.onreadystatechange = function () {

    if (document.readyState == "complete") {

        getDataParam();

    }

};


function getQueryParam(name) {

    var vals = new Array();

    if (location.search != "") {

        vals = location.search.substr(1).split("&");

        for (var i in vals) {

            vals[i] = vals[i].replace(/\+/g, " ").split("=");

        }

        for (var i in vals) {

            if (vals[i][0].toLowerCase() == name.toLowerCase()) {

                return decodeURIComponent(vals[i][1]);

            }

        }

    }

    return null;

}

```


### Using Global Context in HTML Web Resource


```html

<script type="text/javascript">

    function getContext() {

        var context;

        if (typeof GetGlobalContext != "undefined") {

            context = GetGlobalContext();

        } else {

            if (typeof Xrm != "undefined") {

                context = Xrm.Utility.getGlobalContext();

            } else {

                throw new Error("Context is not available.");

            }

        }

        return context;

    }

</script>

```


### Restrictions


- Cannot use ASP.NET pages (no server-side code)

- Limited query string parameters

- Form controls embedding HTML may be reloaded during navigation


---


## CSS Web Resources


### Reference CSS from HTML


```html

<!-- Same publisher -->

<link rel="stylesheet" type="text/css" href="../styles/styles.css" />


<!-- Different publisher -->

<link rel="stylesheet" type="text/css" href="../../MyIsv_/styles/styles.css" />

```


### Best Practices


- Use relative URLs between web resources

- Maintain consistent folder structure

- Include publisher prefix in path planning


---


## Preventing Edits in Managed Solutions


For complex HTML web resources, set managed properties:


1. Open web resource in solutions

2. Open **Managed Properties** dialog

3. Set **Customizable** to `false`


This prevents modification of complex web resources in managed solutions.


---


## Complete HTML Template with All Resources


```html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>Web Resource Example</title>

    

    <!-- Reference JavaScript -->

    <script src="../scripts/myScript.js" type="text/javascript"></script>

    

    <!-- Reference CSS -->

    <link href="../styles/styles.css" rel="stylesheet" type="text/css" />

</head>

<body onload="SDK.ImportWebResources.showData()">

    <div id="results"></div>

    

    <script type="text/javascript">

        // Access form context from parent

        var formContext = parent.Xrm.Page;

        // Or use global context

        var globalContext = parent.Xrm.Utility.getGlobalContext();

    </script>

</body>

</html>

```


---


## Important Notes


1. **HTML web resources added to forms cannot use global objects defined by JavaScript libraries loaded in the form**


2. **Access Xrm via parent:**

   ```javascript

   parent.Xrm.Page // or

   parent.Xrm.Utility

   ```


3. **Load required libraries within HTML web resource**


4. **References between web resources are NOT tracked as solution dependencies**


5. **All characters in query string go through validation - invalid parameters cause request failure**


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/client-scripting/object-model.md -->

<!-- ====================================================================== -->


# Client API Object Model


Root objects in the Client API object model.


## executionContext


Represents the execution context for events in forms and grids.


**Usage:**

```javascript

function myEventHandler(executionContext) {

    var formContext = executionContext.getFormContext();

    // ... your code

}

```


**Key Methods:**

| Method | Description |

|--------|-------------|

| `getFormContext()` | Returns reference to form or grid |

| `getEventArgs()` | Returns event arguments |

| `getDepth()` | Returns handler execution order |

| `getSharedVariable(key)` | Gets shared variable |

| `setSharedVariable(key, value)` | Sets shared variable for handlers |


---


## formContext


Provides reference to form or form item being executed.


**Obtain from executionContext:**

```javascript

function formOnLoad(executionContext) {

    var formContext = executionContext.getFormContext();

}

```


### formContext.data


```javascript

// Access entity

formContext.data.entity;


// Access attributes

formContext.data.attributes;


// Access process

formContext.data.process;


// Methods

formContext.data.refresh(save).then(successCallback, errorCallback);

formContext.data.save().then(successCallback, errorCallback);

formContext.data.getIsDirty();     // Boolean

formContext.data.isValid();        // Boolean

formContext.data.addOnLoad(handler);

formContext.data.removeOnLoad(handler);

```


### formContext.data.entity


```javascript

// Methods

formContext.data.entity.getId();                    // Record GUID

formContext.data.entity.getEntityName();            // Table logical name

formContext.data.entity.getIsDirty();               // Boolean

formContext.data.entity.getPrimaryAttributeValue(); // Primary field value

formContext.data.entity.save();

formContext.data.entity.addOnSave(handler);

formContext.data.entity.removeOnSave(handler);


// Attributes collection

formContext.data.entity.attributes;

```


### formContext.ui


```javascript

// Properties

formContext.ui.controls;       // All controls

formContext.ui.tabs;           // Tab collection

formContext.ui.navigation;     // Navigation items

formContext.ui.quickForms;     // Quick view forms

formContext.ui.formSelector;   // Form selector

formContext.ui.process;        // Business process


// Methods

formContext.ui.getFormType();                        // Returns: 0-6

formContext.ui.close();

formContext.ui.setFormNotification(message, level, uniqueId);

formContext.ui.clearFormNotification(uniqueId);

formContext.ui.getViewPortHeight();

formContext.ui.getViewPortWidth();

formContext.ui.refreshRibbon();

formContext.ui.addOnLoad(handler);

formContext.ui.removeOnLoad(handler);

formContext.ui.addLoaded(handler);

formContext.ui.removeLoaded(handler);

formContext.ui.setFormEntityName(name);

```


---


## gridContext


Reference to grid or subgrid on a form.


```javascript

function gridOnLoad(executionContext) {

    var gridContext = executionContext.getFormContext();

    var grid = gridContext.getControl("gridName");

    

    // Grid methods

    grid.getGridType();

    grid.getTotalRecordCount();

    grid.getRows();

    grid.getSelectedRows();

    grid.refresh();

}

```


---


## Xrm Object


Global object for operations not directly impacting form data/UI.


### Xrm.Navigation


| Method | Description |

|--------|-------------|

| `navigateTo` | Navigate to table list, record, HTML web resource, or custom page |

| `openAlertDialog` | Display alert dialog |

| `openConfirmDialog` | Display confirmation dialog |

| `openErrorDialog` | Display error dialog |

| `openFile` | Open a file |

| `openForm` | Open entity form or quick create form |

| `openUrl` | Open a URL |

| `openWebResource` | Open HTML web resource |


### Xrm.Utility


| Method | Description |

|--------|-------------|

| `closeProgressIndicator` | Close progress dialog |

| `getAllowedStatusTransitions` | Get valid state transitions |

| `getEntityMetadata` | Get entity metadata |

| `getGlobalContext` | Get global context |

| `getPageContext` | Get page context |

| `getResourceString` | Get localized string |

| `invokeProcessAction` | Invoke action |

| `lookupObjects` | Open lookup control |

| `refreshParentGrid` | Refresh parent grid |

| `showProgressIndicator` | Display progress dialog |


### Xrm.WebApi


| Method | Description |

|--------|-------------|

| `createRecord` | Create a record |

| `retrieveRecord` | Retrieve a record |

| `retrieveMultipleRecords` | Retrieve multiple records |

| `updateRecord` | Update a record |

| `deleteRecord` | Delete a record |


---


## formContext.ui.getFormType() Values


| Value | Form Type |

|-------|-----------|

| 0 | Undefined |

| 1 | Create |

| 2 | Update |

| 3 | Read Only |

| 4 | Disabled |

| 6 | Bulk Edit |


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/client-scripting/formcontext.md -->

<!-- ====================================================================== -->


# formContext Reference


## formContext.data


### Properties


| Property | Description |

|----------|-------------|

| `attributes` | Collection of non-table data on form |

| `entity` | Methods for record info, save, and columns collection |

| `process` | Objects and methods for business process flow |


### Methods


| Method | Description |

|--------|-------------|

| `addOnLoad(handler)` | Add function called when form data loads |

| `getIsDirty()` | Returns boolean if form data modified |

| `isValid()` | Returns boolean if all form data valid |

| `refresh(save)` | Refreshes and optionally saves data |

| `removeOnLoad(handler)` | Removes function from data load |

| `save()` | Asynchronously saves record |


---


## formContext.data.entity


### Methods


| Method | Description |

|--------|-------------|

| `addOnSave(handler)` | Add function to OnSave event |

| `removeOnSave(handler)` | Remove function from OnSave event |

| `getId()` | Returns GUID of record |

| `getEntityName()` | Returns logical name of table |

| `getIsDirty()` | Returns boolean if entity modified |

| `getPrimaryAttributeValue()` | Returns primary field value |

| `save()` | Saves record |

| `attributes` | Collection of attributes |


---


## formContext.ui


### Properties


| Property | Description |

|----------|-------------|

| `controls` | Collection of all controls on page |

| `formSelector` | Form selector object |

| `navigation` | Navigation items collection |

| `process` | Business process flow control |

| `quickForms` | Quick view controls collection |

| `tabs` | Tab collection |


### Methods


| Method | Description |

|--------|-------------|

| `addOnLoad(handler)` | Add function to form OnLoad |

| `removeOnLoad(handler)` | Remove function from OnLoad |

| `addLoaded(handler)` | Add function when form loaded |

| `removeLoaded(handler)` | Remove loaded handler |

| `clearFormNotification(uniqueId)` | Clear form notification |

| `close()` | Close form |

| `getFormType()` | Get form type (0-6) |

| `getViewPortHeight()` | Get viewport height |

| `getViewPortWidth()` | Get viewport width |

| `refreshRibbon()` | Refresh ribbon |

| `setFormEntityName(name)` | Set table display name |

| `setFormNotification(message, level, uniqueId)` | Show notification |


### Notification Levels


- `"INFO"` - Information

- `"WARNING"` - Warning

- `"ERROR"` - Error


---


## Attribute Methods


### Get Attribute


```javascript

var attr = formContext.getAttribute("fieldname");

// Or get all attributes

var allAttrs = formContext.data.entity.attributes;

```


### All Attribute Types


| Method | Returns |

|--------|---------|

| `getName()` | String - attribute name |

| `getValue()` | Attribute value |

| `setValue(value)` | Void |

| `getAttributeType()` | String: "boolean", "datetime", "decimal", etc. |

| `getFormat()` | String: "date", "email", "phone", etc. |

| `getIsDirty()` | Boolean |

| `isValid()` | Boolean |

| `getParent()` | Entity object |

| `getUserPrivilege()` | Privilege object |

| `fireOnChange()` | Void - fires onchange |

| `addOnChange(handler)` | Add onchange handler |

| `removeOnChange(handler)` | Remove onchange handler |

| `getRequiredLevel()` | String: "none", "required", "recommended" |

| `setRequiredLevel(level)` | Void |

| `getSubmitMode()` | String: "always", "never", "dirty" |

| `setSubmitMode(mode)` | Void |

| `controls` | Collection of controls |


### Boolean Attributes


```javascript

attr.getInitialValue();

```


### Choice/Choices Attributes


```javascript

attr.getInitialValue();       // Number

attr.getOption(value);       // Option object

attr.getOptions();           // Array of options

attr.getSelectedOption();    // Selected option

attr.getText();               // Selected text

```


### Lookup Attributes


```javascript

attr.getIsPartyList();        // Boolean


// getValue returns array

var lookup = attr.getValue();

// Each item: { id, name, entityType }

```


### Number Attributes


```javascript

attr.getMax();        // Maximum value

attr.getMin();        // Minimum value  

attr.getPrecision();  // Decimal places

attr.setPrecision(value);

```


### String Attributes


```javascript

attr.getMaxLength();  // Maximum length

```


---


## Control Methods


### Get Control


```javascript

var ctrl = formContext.getControl("controlname");

// Or from attribute

var ctrl = attr.controls.get(0);

```


### Control Methods


| Method | Returns |

|--------|---------|

| `getName()` | String - control name |

| `getLabel()` | String - label text |

| `setLabel(text)` | Void |

| `getVisible()` | Boolean |

| `setVisible(bool)` | Void |

| `show()` | Void |

| `hide()` | Void |

| `getDisabled()` | Boolean |

| `setDisabled(bool)` | Void |

| `setNotification(msg, id)` | Boolean |

| `clearNotification(id)` | Boolean |

| `addCustomFilter(filter, entity)` | Void |

| `addPreSearch(handler)` | Void |

| `removePreSearch(handler)` | Void |

| `getAttribute()` | Attribute object |


---


## Tab Methods


```javascript

var tab = formContext.ui.tabs.get("tabname");


tab.getName();

tab.getVisible();

tab.setVisible(bool);

tab.setFocus();

tab.getDisplayState();        // "expanded" or "collapsed"  

tab.setDisplayState(state);  // "expanded" or "collapsed"

tab.sections;                // Sections collection

```


---


## Section Methods


```javascript

var section = tab.sections.get("sectionname");


section.getName();

section.getVisible();

section.setVisible(bool);

section.getLabel();

section.setLabel(text);

section.controls;           // Controls collection

```


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/client-scripting/xrm-api.md -->

<!-- ====================================================================== -->


# Xrm API Reference


## Xrm.Navigation


### navigateTo


Navigate to table list, record, HTML web resource, or custom page.


```javascript

var pageInput = {

    pageType: "entitylist",

    entityName: "account"

};

var navigationOptions = {

    target: 2

};

Xrm.Navigation.navigateTo(pageInput, navigationOptions);

```


**Page Types:**

- `"entitylist"` - Table list

- `"entityrecord"` - Table record

- `"webresource"` - HTML web resource

- `"custom"` - Custom page


### openAlertDialog


Display alert dialog with message and button.


```javascript

Xrm.Navigation.openAlertDialog({

    text: "Record saved successfully.",

    title: "Success"

}).then(

    function success(result) {

        // Dialog closed

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### openConfirmDialog


Display confirmation dialog with message and two buttons.


```javascript

var confirmStrings = {

    text: "Are you sure you want to delete this record?",

    title: "Confirm Deletion",

    confirmButtonLabel: "Delete",

    cancelButtonLabel: "Cancel"

};

var confirmOptions = { height: 200, width: 450 };


Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(

    function success(result) {

        if (result.confirmed) {

            // User confirmed

        } else {

            // User cancelled

        }

    }

);

```


### openErrorDialog


Display error dialog.


```javascript

Xrm.Navigation.openErrorDialog({

    message: "An error occurred",

    errorCode: "500",

    details: "Detailed error message"

});

```


### openForm


Open entity form or quick create form.


```javascript

var entityFormOptions = {

    entityName: "account",

    openInNewWindow: true

};

var formParameters = {

    name: "Contoso Ltd"

};


Xrm.Navigation.openForm(entityFormOptions, formParameters).then(

    function success(result) {

        var recordId = result.recordId;

    }

);

```


### openWebResource


Open HTML web resource in new window.


```javascript

var webResourceName = "new_/content/page.htm";

var windowOptions = {

    height: 500,

    width: 600

};

var data = "param1=value1&param2=value2";


Xrm.Navigation.openWebResource(webResourceName, windowOptions, data);

```


---


## Xrm.Utility


### getGlobalContext


Get global context for application info.


```javascript

var globalContext = Xrm.Utility.getGlobalContext();


// User settings

var userName = globalContext.userSettings.userName;

var userId = globalContext.userSettings.userId;

var userRoles = globalContext.userSettings.roles;

var userLanguageId = globalContext.userSettings.languageId;

var userSecurityRole = globalContext.userSettings.securityRolePrivileges;


// Client info

var clientName = globalContext.client.getClient();

var clientState = globalContext.client.getState();


// Organization info

var orgName = globalContext.organizationSettings.uniqueName;

var orgLanguage = globalContext.organizationSettings.languageId;


// App URL

var appUrl = globalContext.getCurrentAppUrl();

var clientUrl = globalContext.getClientUrl();

```


### showProgressIndicator / closeProgressIndicator


```javascript

Xrm.Utility.showProgressIndicator("Processing...");


// Perform operation


Xrm.Utility.closeProgressIndicator();

```


### lookupObjects


Open lookup dialog for record selection.


```javascript

var lookupOptions = {

    entityTypes: ["account", "contact"],

    defaultEntityType: "account",

    allowMultiSelect: true,

    disableMru: true,

    filters: [{filter: "statecode eq 0", entityName: "account"}]

};


Xrm.Utility.lookupObjects(lookupOptions).then(

    function success(result) {

        result.forEach(function(item) {

            console.log(item.id + " - " + item.name);

        });

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### refreshParentGrid


Refresh parent grid after record creation.


```javascript

Xrm.Utility.refreshParentGrid({

    entityName: "account",

    parentId: accountId

});

```


### getEntityMetadata


Get entity metadata.


```javascript

Xrm.Utility.getEntityMetadata("account", ["name", "accountnumber"]).then(

    function success(metadata) {

        console.log(metadata.Attributes);

    }

);

```


---


## Xrm.WebApi


### createRecord


Create a new record.


```javascript

var newAccount = {

    name: "New Account",

    revenue: 1000000,

    primarycontactid: {

        id: contactId,

        entityType: "contact"

    }

};


Xrm.WebApi.createRecord("account", newAccount).then(

    function success(result) {

        console.log("Created: " + result.id);

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### retrieveRecord


Retrieve a single record.


```javascript

Xrm.WebApi.retrieveRecord("account", accountId, "?$select=name,revenue,telephone1").then(

    function success(result) {

        console.log("Name: " + result.name);

        console.log("Revenue: " + result.revenue);  

        console.log("Phone: " + result.telephone1);

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### retrieveMultipleRecords


Retrieve multiple records.


```javascript

Xrm.WebApi.retrieveMultipleRecords("account",

    "?$select=name,revenue&$filter=statecode eq 0&$top=10"

).then(

    function success(result) {

        result.entities.forEach(function(entity) {

            console.log(entity.name + " - " + entity.revenue);

        });

        if (result.nextLink) {

            // More records available

        }

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### updateRecord


Update a record.


```javascript

var accountUpdate = {

    name: "Updated Account Name",

    revenue: 2000000

};


Xrm.WebApi.updateRecord("account", accountId, accountUpdate).then(

    function success(result) {

        console.log("Record updated");

    },

    function error(error) {

        console.log(error.message);

    }

);

```


### deleteRecord


Delete a record.


```javascript

Xrm.WebApi.deleteRecord("account", accountId).then(

    function success(result) {

        console.log("Record deleted");

    },

    function error(error) {

        console.log(error.message);

    }

);

```


---


## Xrm.Encoding


### Methods


```javascript

var encoded = Xrm.Encoding.xmlEncode("<script>alert('test')</script>");

var htmlEncoded = Xrm.Encoding.htmlEncode("<div>content</div>");

var attributeName = Xrm.Encoding.attributeNameEncode("attribute_name");

```


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/client-scripting/events.md -->

<!-- ====================================================================== -->


# Events in Forms and Grids


## Overview


Events initiate all client-side code. Associate JavaScript functions to events to execute when they occur.


## Event Handlers


Each event handler specifies:

1. JavaScript library containing the function

2. Function name to execute

3. Parameters to pass


**Important:** Execution context is automatically passed as first parameter when using code to add handlers.


---


## Available Events


### Form Events


| Event | Description | Handler Method |

|-------|-------------|----------------|

| OnLoad | Form loads | `formContext.ui.addOnLoad(handler)` |

| Loaded | Form finished loading | `formContext.ui.addLoaded(handler)` |

| OnSave | Form saves | `formContext.data.entity.addOnSave(handler)` |

| Data OnLoad | Form data loads | `formContext.data.addOnLoad(handler)` |


### Field Events


| Event | Description | Handler Method |

|-------|-------------|----------------|

| OnChange | Field value changes | `attribute.addOnChange(handler)` |


### Lookup Events


| Event | Description | Handler Method |

|-------|-------------|----------------|

| PreSearch | Before lookup search | `control.addPreSearch(handler)` |


### kbsearch Events


| Event | Description | Handler Method |

|-------|-------------|----------------|

| OnResultOpened | Result opened | `control.addOnResultOpened(handler)` |

| OnSelection | Selection made | `control.addOnSelection(handler)` |

| PostSearch | After search | `control.addOnPostSearch(handler)` |


---


## Adding Event Handlers via UI


### Form OnLoad / OnSave


1. Open form in editor

2. Select Form Properties

3. Go to **Events** tab

4. Select **On Load** or **On Save**

5. Click **+ Event Handler**

6. Select library

7. Enter function name: `MyNamespace.functionName`

8. Check **Pass execution context as first parameter**


### Field OnChange


1. Open form in editor

2. Select field

3. Go to **Events** tab

4. Select **On Change**

5. Click **+ Event Handler**

6. Select library

7. Enter function name

8. Check **Pass execution context as first parameter**


---


## Adding Event Handlers via Code


### Form OnLoad


```javascript

function addFormOnLoadHandler() {

    formContext.ui.addOnLoad(myOnLoadFunction);

}


function removeFormOnLoadHandler() {

    formContext.ui.removeOnLoad(myOnLoadFunction);

}

```


### Form OnSave


```javascript

function addFormOnSaveHandler() {

    formContext.data.entity.addOnSave(myOnSaveFunction);

}


function removeFormOnSaveHandler() {

    formContext.data.entity.removeOnSave(myOnSaveFunction);

}

```


### Attribute OnChange


```javascript

function addAttributeOnChangeHandler(formContext) {

    var attr = formContext.getAttribute("fieldname");

    attr.addOnChange(myChangeFunction);

}

```


### Lookup PreSearch


```javascript

function addPreSearchHandler(formContext) {

    var lookup = formContext.getControl("parentcustomerid");

    lookup.addPreSearch(myPreSearchFunction);

}


function myPreSearchFunction() {

    var lookup = formContext.getControl("parentcustomerid");

    lookup.addCustomFilter("statecode eq 0", "account");

}

```


---


## Event Pipeline


### Execution Order


- Up to 50 event handlers per event

- Handlers execute in order displayed in Form Properties

- Use `getDepth()` to determine handler sequence


### Shared Variables


Pass data between event handlers:


```javascript

function firstHandler(executionContext) {

    // Set shared variable

    executionContext.setSharedVariable("myData", { key: "value" });

}


function secondHandler(executionContext) {

    // Get shared variable

    var myData = executionContext.getSharedVariable("myData");

}

```


### Handler Depth


```javascript

function myHandler(executionContext) {

    var depth = executionContext.getDepth();

    // Depth indicates execution order (0 = first)

}

```


---


## OnSave Modes


| Value | Save Mode |

|-------|-----------|

| 1 | Save |

| 2 | Save and Close |

| 5 | Save and New |

| 6 | Save as Completed (activities) |

| 7 | Save and Deactivate |

| 15 | Save as Draft |

| 47 | Assign |

| 58 | Save as Completed |

| 59 | Auto Save |


### Preventing Save


```javascript

function formOnSave(executionContext) {

    var saveEventArgs = executionContext.getEventArgs();

    var saveMode = saveEventArgs.getSaveMode();

    

    if (saveMode === 59) {

        // Auto save - validate first

        if (!validateForm()) {

            saveEventArgs.preventDefault();

        }

    }

}

```


---


## Bulk Edit Mode


By default, event handlers aren't called in bulk edit mode.


To enable handlers in bulk edit forms, modify Form XML:


```xml

<event name="onload" application="false" BehaviorInBulkEditForm="Enabled">

    <Handlers>

        <Handler functionName="MyNamespace.formOnLoad" />

    </Handlers>

</event>

```


Use `getFormType()` to detect bulk edit mode (returns 6).


---


## Example: Complete Event Handler


```javascript

var MyNamespace = window.MyNamespace || {};

(function () {

    this.formOnLoad = function (executionContext) {

        var formContext = executionContext.getFormContext();

        var formType = formContext.ui.getFormType();

        

        if (formType === 1) {

            // Create form - set defaults

            setDefaultValues(formContext);

        }

        

        // Add handler to field

        var attr = formContext.getAttribute("accountname");

        if (attr) {

            attr.addOnChange(MyNamespace.accountNameOnChange);

        }

    };

    

    this.formOnSave = function (executionContext) {

        var formContext = executionContext.getFormContext();

        var saveEventArgs = executionContext.getEventArgs();

        var saveMode = saveEventArgs.getSaveMode();

        

        // Validate before save

        if (!validateForm(formContext)) {

            saveEventArgs.preventDefault();

            return;

        }

        

        // Log save mode

        console.log("Save mode: " + saveMode);

    };

    

    this.accountNameOnChange = function (executionContext) {

        var formContext = executionContext.getFormContext();

        var attr = formContext.getAttribute("accountname");

        if (attr && attr.getValue()) {

            // Handle change

        }

    };

}).call(MyNamespace);

```


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/client-scripting/best-practices.md -->

<!-- ====================================================================== -->


# Best Practices


## DO


### 1. Use Namespaced JavaScript Libraries


Always define a unique namespace to avoid naming conflicts:


```javascript

var MyCompany = window.MyCompany || {};

(function () {

    this.myFunction = function (executionContext) {

        // Code here

    };

}).call(MyCompany);

```


### 2. Pass Execution Context


Always pass execution context as first parameter to event handlers:


```javascript

function formOnLoad(executionContext) {

    var formContext = executionContext.getFormContext();

    // Your code here

}

```


When registering in Form Properties, check **"Pass execution context as first parameter"**.


### 3. Use formContext Instead of Xrm.Page


`Xrm.Page` is deprecated. Use `formContext` from execution context:


```javascript

// DON'T

var name = Xrm.Page.getAttribute("name").getValue();


// DO

function myHandler(executionContext) {

    var formContext = executionContext.getFormContext();

    var name = formContext.getAttribute("name").getValue();

}

```


### 4. Check for Null/Undefined


Always validate objects before using:


```javascript

function formOnLoad(executionContext) {

    var formContext = executionContext.getFormContext();

    

    var attr = formContext.getAttribute("fieldname");

    if (attr) {

        var value = attr.getValue();

        if (value != null && value !== "") {

            // Process value

        }

    }

}

```


### 5. Use Relative URLs for Web Resources


```html

<!-- Good -->

<script src="../scripts/common.js"></script>

<link href="../styles/main.css" rel="stylesheet" />


<!-- Bad -->

<script src="/WebResources/new_scripts/common.js"></script>

```


### 6. Use $webresource Directive


For ribbon commands and SiteMap:


```xml

$webresource:new_/scripts/library.js

```


### 7. Handle Asynchronous Operations


```javascript

// Use promises

Xrm.WebApi.retrieveRecord("account", id, "?$select=name").then(

    function success(result) {

        // Handle success

    },

    function error(error) {

        // Handle error

        console.log(error.message);

    }

);


// Use async/await

async function loadAccount(id) {

    try {

        var result = await Xrm.WebApi.retrieveRecord("account", id, "?$select=name");

        return result;

    } catch (error) {

        console.log(error.message);

    }

}

```


### 8. Remove Console.log Before Production


```javascript

// During development

console.log("Debug: value = " + value);


// Remove or comment before deployment

```


---


## DON'T


### 1. Don't Use Xrm.Page


Deprecated - use `formContext` instead.


### 2. Don't Use Xrm.Internal Namespace


Unsupported and may break without notice.


### 3. Don't Manipulate DOM Directly


```javascript

// DON'T

document.getElementById("fieldname").style.display = "none";


// DO

formContext.getControl("fieldname").setVisible(false);

```


### 4. Don't Use window.top


Causes script errors and incorrect application behavior. Use supported APIs instead.


### 5. Don't Use Deprecated APIs


Check documentation for deprecated methods. Common deprecated items:

- `Xrm.Page` - Use `formContext`

- `Xrm.Utility.alertDialog` - Use `Xrm.Navigation.openAlertDialog`

- `Xrm.Utility.confirmDialog` - Use `Xrm.Navigation.openConfirmDialog`

- `Xrm.Utility.openEntityForm` - Use `Xrm.Navigation.openForm`

- `Xrm.Utility.openQuickCreate` - Use `Xrm.Navigation.openForm`

- `Xrm.Utility.openWebResource` - Use `Xrm.Navigation.openWebResource`


### 6. Don't Create ASP.NET Pages as Web Resources


Server-side code is not supported. Web resources are static files.


### 7. Don't Exceed File Size Limits


Default limit is 5MB. Configure in System Settings > Email > Maximum file size.


### 8. Don't Use /WebResources/ Root Path


Can fail if user belongs to multiple organizations. Use relative paths instead.


---


## Complete JavaScript Template


```javascript

var MyNamespace = window.MyNamespace || {};

(function () {

    var _formContext;

    

    this.formOnLoad = function (executionContext) {

        _formContext = executionContext.getFormContext();

        var formType = _formContext.ui.getFormType();

        var formName = _formContext.ui.formSelector.getCurrentItem().getLabel();

        

        console.log("Form: " + formName + ", Type: " + formType);

        

        switch (formType) {

            case 1:

                onCreateForm();

                break;

            case 2:

                onUpdateForm();

                break;

            case 3:

                onReadOnlyForm();

                break;

        }

        

        attachEventHandlers();

    };

    

    this.formOnSave = function (executionContext) {

        var formContext = executionContext.getFormContext();

        var saveEventArgs = executionContext.getEventArgs();

        var saveMode = saveEventArgs.getSaveMode();

        

        if (!validateForm(formContext)) {

            saveEventArgs.preventDefault();

            return;

        }

    };

    

    this.fieldOnChange = function (executionContext) {

        var formContext = executionContext.getFormContext();

        var attr = formContext.getAttribute("fieldname");

        

        if (attr && attr.getValue()) {

            var value = attr.getValue();

            processFieldValue(value);

        }

    };

    

    function onCreateForm() {

        setDefaultValues();

    }

    

    function onUpdateForm() {

        loadRelatedData();

    }

    

    function onReadOnlyForm() {

        // Handle read-only form

    }

    

    function attachEventHandlers() {

        var attr = _formContext.getAttribute("fieldname");

        if (attr) {

            attr.addOnChange(MyNamespace.fieldOnChange);

        }

    }

    

    function setDefaultValues() {

        var nameAttr = _formContext.getAttribute("name");

        if (nameAttr && !nameAttr.getValue()) {

            nameAttr.setValue("Default Name");

        }

    }

    

    function validateForm(formContext) {

        var isValid = true;

        var requiredAttr = formContext.getAttribute("requiredfield");

        

        if (requiredAttr && !requiredAttr.getValue()) {

            formContext.getControl("requiredfield").setNotification("This field is required");

            isValid = false;

        }

        

        return isValid;

    }

    

    function processFieldValue(value) {

        // Process the value

    }

    

    function loadRelatedData() {

        var accountId = _formContext.data.entity.getId();

        

        Xrm.WebApi.retrieveRecord("account", accountId, "?$select=name,revenue").then(

            function success(result) {

                console.log("Loaded: " + result.name);

            },

            function error(error) {

                console.log(error.message);

            }

        );

    }

}).call(MyNamespace);

```


---


## Error Handling Template


```javascript

function safeExecute(fn, context) {

    try {

        return fn(context);

    } catch (e) {

        console.log("Error: " + e.message);

        Xrm.Navigation.openErrorDialog({

            message: "An error occurred: " + e.message

        });

        return null;

    }

}


// Usage

function formOnLoad(executionContext) {

    safeExecute(function(ctx) {

        var formContext = ctx.getFormContext();

        // Your code here

    }, executionContext);

}

```


---


## Performance Tips


1. **Minimize Web API calls** - Cache data when possible

2. **Use getFormType()** - Only run code for relevant form types

3. **Detach handlers** - Remove handlers when not needed

4. **Avoid synchronous calls** - Use async operations

5. **Debounce rapid events** - For fields that change frequently


```javascript

var debounceTimer;

function fieldOnChange(executionContext) {

    clearTimeout(debounceTimer);

    debounceTimer = setTimeout(function() {

        // Process after 300ms delay

    }, 300);

}

```


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/samples/pass-data-web-resource.md -->

<!-- ====================================================================== -->


# Sample: Pass Multiple Values to a Web Resource Through the Data Parameter


An HTML web resource page can only accept a single custom parameter called `data`. To pass more than one value in the data parameter, you need to encode the parameters and decode the parameters in your page.


**Note:** Only alphanumeric characters are supported as parameters to web resources. All characters included in the query string go through validation to ensure the validity of the parameters passed. If there are any parameters found to be not valid, the request will fail.


---


## Sample HTML Web Resource


The HTML code below represents a webpage (HTML) web resource that includes a script that defines three functions:


- **getDataParam**: Called from the `body.onload` event, retrieves any query string parameters passed to the page and locates one named `data`.

- **parseDataValue**: Receives the data parameter from `getDataParam` and builds a DHTML table to display any values passed within the `data` parameter.

- **noParams**: Displays a message when no parameters are passed to the page.


```html

<!DOCTYPE html>

<html lang="en-us">

<head>

    <title>Show Data Parameters Page</title>

    <style type="text/css">

        body {

            font-family: Segoe UI, Tahoma, Arial;

            background-color: #d6e8ff;

        }

        tbody {

            background-color: white;

        }

        th {

            background-color: black;

            color: White;

        }

    </style>

    <script type="text/javascript">

        document.onreadystatechange = function() {

            if (document.readyState == "complete") {

                getDataParam();

            }

        }


        function getDataParam() {

            //Get the any query string parameters and load them

            //into the vals array


            var vals = new Array();

            if (location.search != "") {

                vals = location.search.substr(1).split("&");

                for (var i in vals) {

                    vals[i] = vals[i].replace(/\+/g, " ").split("=");

                }

                //look for the parameter named 'data'

                var found = false;

                for (var i in vals) {

                    if (vals[i][0].toLowerCase() == "data") {

                        parseDataValue(vals[i][1]);

                        found = true;

                        break;

                    }

                }

                if (!found) {

                    noParams();

                }

            } else {

                noParams();

            }

        }


        function parseDataValue(datavalue) {

            if (datavalue != "") {

                var vals = new Array();


                var message = document.createElement("p");

                setText(message, "These are the data parameters values that were passed to this page:");

                document.body.appendChild(message);


                vals = decodeURIComponent(datavalue).split("&");

                for (var i in vals) {

                    vals[i] = vals[i].replace(/\+/g, " ").split("=");

                }


                //Create a table and header using the DOM

                var oTable = document.createElement("table");

                var oTHead = document.createElement("thead");

                var oTHeadTR = document.createElement("tr");

                var oTHeadTRTH1 = document.createElement("th");

                setText(oTHeadTRTH1, "Parameter");

                var oTHeadTRTH2 = document.createElement("th");

                setText(oTHeadTRTH2, "Value");

                oTHeadTR.appendChild(oTHeadTRTH1);

                oTHeadTR.appendChild(oTHeadTRTH2);

                oTHead.appendChild(oTHeadTR);

                oTable.appendChild(oTHead);

                var oTBody = document.createElement("tbody");

                //Loop through vals and create rows for the table

                for (var i in vals) {

                    var oTRow = document.createElement("tr");

                    var oTRowTD1 = document.createElement("td");

                    setText(oTRowTD1, vals[i][0]);

                    var oTRowTD2 = document.createElement("td");

                    setText(oTRowTD2, vals[i][1]);


                    oTRow.appendChild(oTRowTD1);

                    oTRow.appendChild(oTRowTD2);

                    oTBody.appendChild(oTRow);

                }


                oTable.appendChild(oTBody);

                document.body.appendChild(oTable);

            } else {

                noParams();

            }

        }


        function noParams() {

            var message = document.createElement("p");

            setText(message, "No data parameter was passed to this page");


            document.body.appendChild(message);

        }


        //Added for cross browser support.

        function setText(element, text) {

            if (typeof element.innerText != "undefined") {

                element.innerText = text;

            } else {

                element.textContent = text;

            }

        }

    </script>

</head>

<body>

</body>

</html>

```


---


## Using This Page


### Step 1: Create the Web Resource


Create a webpage web resource called "new_/ShowDataParams.htm" using the sample code above.


### Step 2: Pass Parameters


The parameters you want to pass are:

```

first=First Value&second=Second Value&third=Third Value

```


**Static Parameters (Form Editor):**


If adding static parameters using the Web Resource Properties dialog box from the form editor, you can simply paste the parameters without encoding them into the **Custom Parameter(data)** column. These values will be encoded for you automatically.


**Dynamic Parameters (Code):**


For dynamic values generated in code, use the `encodeURIComponent` method:


```javascript

var params = "first=First Value&second=Second Value&third=Third Value";

var encodedParams = encodeURIComponent(params);

// Result: first%3DFirst%20Value%26second%3DSecond%20Value%26third%3DThird%20Value

```


### Step 3: Open the Page


Open the page passing the encoded parameters as the value of the data parameter:


```

https://<server name>/WebResources/new_/ShowDataParams.htm?data=first%3DFirst%20Value%26second%3DSecond%20Value%26third%3DThird%20Value

```


---


## Result


The `new_/ShowDataParams.htm` page will display a dynamically generated table:


| Parameter | Value |

|-----------|-------|

| first | First Value |

| second | Second Value |

| third | Third Value |


---


## How It Works


1. **getDataParam()**: When the page loads, this function extracts query string parameters and searches for one named `data`.


2. **parseDataValue()**: Uses `decodeURIComponent` to decode the values, splits them into name-value pairs, and dynamically generates a table.


3. **noParams()**: Called if no data parameter is found, displays a "No data parameter was passed" message.


---


## Helper Functions


### Encoding Parameters


```javascript

function encodeParams(params) {

    var encoded = [];

    for (var key in params) {

        encoded.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key]));

    }

    return encoded.join("&");

}


// Usage

var params = {

    first: "First Value",

    second: "Second Value",

    third: "Third Value"

};

var encodedData = encodeParams(params);

// Result: first=First%20Value&second=Second%20Value&third=Third%20Value

```


### Opening Web Resource with Data


```javascript

function openWebResourceWithData() {

    var params = {

        accountname: "Contoso Ltd",

        accountid: accountId,

        status: "active"

    };

    

    var encodedData = "";

    for (var key in params) {

        if (encodedData !== "") encodedData += "&";

        encodedData += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);

    }

    

    Xrm.Navigation.openWebResource("new_/ShowDataParams.htm", {

        height: 500,

        width: 600

    }, encodedData);

}

```


---


## Reference


- [WebPage (HTML) Web Resources](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/webpage-html-web-resources)

- [Web Resources Overview](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/web-resources)


<!-- ====================================================================== -->

<!-- FILE: .agents/skills/model-driven-powerapps-web-resources/samples/import-files-web-resources.md -->

<!-- ====================================================================== -->


# Sample: Import Files as Web Resources


When developing a large number of files to use as web resources, you can save time by importing them rather than manually adding each file through the application. Many web resources can be developed and tested outside of Model-Driven apps and then imported as a batch.


---


## Overview


This sample demonstrates:


1. Creating web resources in the context of a solution

2. Uploading files from disk

3. Combining web resource record data with file data


---


## File Structure


The sample includes these files:


```

FilesToImport/

├── ShowData.htm          # Main HTML web resource

├── CSS/

│   └── Styles.css        # CSS styles

├── Data/

│   └── Data.xml          # XML data file

├── Script/

│   └── Script.js         # JavaScript library

└── XSL/

    └── Transform.xslt    # XSL transformation

```


---


## ImportJob.xml


This file provides data about the web resource records to be created:


```xml

<webResources>

    <webResource>

        <path>FilesToImport/ShowData.htm</path>

        <displayName>Show Data Page</displayName>

        <description>HTML page that displays data table</description>

        <name>_/ShowData.htm</name>

        <type>1</type>

    </webResource>

    <webResource>

        <path>FilesToImport/CSS/Styles.css</path>

        <displayName>Styles CSS</displayName>

        <description>CSS styles for ShowData</description>

        <name>_/CSS/Styles.css</name>

        <type>2</type>

    </webResource>

    <webResource>

        <path>FilesToImport/Data/Data.xml</path>

        <displayName>Data XML</displayName>

        <description>XML data file</description>

        <name>_/Data/Data.xml</name>

        <type>4</type>

    </webResource>

    <webResource>

        <path>FilesToImport/Script/Script.js</path>

        <displayName>Script JS</displayName>

        <description>JavaScript library</description>

        <name>_/Script/Script.js</name>

        <type>3</type>

    </webResource>

    <webResource>

        <path>FilesToImport/XSL/Transform.xslt</path>

        <displayName>Transform XSLT</displayName>

        <description>XSL transformation file</description>

        <name>_/XSL/Transform.xslt</name>

        <type>9</type>

    </webResource>

</webResources>

```


### XML Elements


| Element | Description |

|---------|-------------|

| `path` | Path to file from FilesToImport folder |

| `displayName` | Display name for the web resource |

| `description` | Description of the web resource |

| `name` | Name with virtual folder structure (backslash for paths) |

| `type` | Web resource type (1=HTML, 2=CSS, 3=JS, 4=XML, 9=XSL) |


---


## FilesToImport/ShowData.htm


```html

<!DOCTYPE html>

<html>

<head>

    <title>Show Data</title>

    <link href="CSS/Styles.css" rel="stylesheet" type="text/css" />

    <script src="Script/Script.js" type="text/javascript"></script>

</head>

<body onload="SDK.ImportWebResources.showData()">

    <div id="results"></div>

</body>

</html>

```


---


## FilesToImport/CSS/Styles.css


```css

body {

    font-family: Segoe UI, Tahoma, Arial;

    background-color: #d6e8ff;

}


tbody {

    background-color: white;

}


th {

    background-color: black;

    color: White;

}


table {

    border-collapse: collapse;

    width: 100%;

}


td, th {

    border: 1px solid #ddd;

    padding: 8px;

    text-align: left;

}


tr:nth-child(even) {

    background-color: #f2f2f2;

}

```


---


## FilesToImport/Data/Data.xml


```xml

<?xml version="1.0" encoding="UTF-8"?>

<People>

    <Person>

        <FirstName>Apurva</FirstName>

        <LastName>Dalia</LastName>

    </Person>

    <Person>

        <FirstName>Ofer</FirstName>

        <LastName>Daliot</LastName>

    </Person>

    <Person>

        <FirstName>Jim</FirstName>

        <LastName>Daly</LastName>

    </Person>

    <Person>

        <FirstName>Ryan</FirstName>

        <LastName>Danner</LastName>

    </Person>

    <Person>

        <FirstName>Mike</FirstName>

        <LastName>Danseglio</LastName>

    </Person>

    <Person>

        <FirstName>Alex</FirstName>

        <LastName>Darrow</LastName>

    </Person>

</People>

```


---


## FilesToImport/Script/Script.js


```javascript

var SDK;

if (!SDK) SDK = {};


SDK.ImportWebResources = {

    dataFileLocation: "Data/Data.xml",

    transformFileLocation: "XSL/Transform.xslt",


    showData: function() {

        var request = new XMLHttpRequest();

        request.open("GET", SDK.ImportWebResources.dataFileLocation, true);

        request.send();

        request.onreadystatechange = function() {

            if (request.readyState === 4) {

                if (request.status === 200 || request.status === 0) {

                    var xmlDoc = request.responseXML;

                    SDK.ImportWebResources.transformData(xmlDoc);

                } else {

                    var div = document.getElementById("results");

                    div.innerHTML = "Error loading data file: " + request.statusText;

                }

            }

        };

    },


    transformData: function(xmlDoc) {

        var xsltRequest = new XMLHttpRequest();

        xsltRequest.open("GET", SDK.ImportWebResources.transformFileLocation, true);

        xsltRequest.send();

        xsltRequest.onreadystatechange = function() {

            if (xsltRequest.readyState === 4) {

                if (xsltRequest.status === 200 || xsltRequest.status === 0) {

                    var xsltDoc = xsltRequest.responseXML;

                    var xslt = new XSLTProcessor();

                    xslt.importStylesheet(xsltDoc);

                    var result = xslt.transformToFragment(xmlDoc, document);

                    

                    var div = document.getElementById("results");

                    div.innerHTML = "";

                    div.appendChild(result);

                } else {

                    var div = document.getElementById("results");

                    div.innerHTML = "Error loading transform file: " + xsltRequest.statusText;

                }

            }

        };

    }

};

```


---


## FilesToImport/XSL/Transform.xslt


```xml

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">

        <table>

            <thead>

                <tr>

                    <th>First Name</th>

                    <th>Last Name</th>

                </tr>

            </thead>

            <tbody>

                <xsl:for-each select="People/Person">

                    <tr>

                        <td>

                            <xsl:value-of select="FirstName"/>

                        </td>

                        <td>

                            <xsl:value-of select="LastName"/>

                        </td>

                    </tr>

                </xsl:for-each>

            </tbody>

        </table>

    </xsl:template>

</xsl:stylesheet>

```


---


## Creating Web Resources Programmatically


### C# Code to Import Web Resources


```csharp

// Read the descriptive data from the XML file

XDocument xmlDoc = XDocument.Load("../../ImportJob.xml");


// Create a collection of anonymous type references to each of the Web Resources

var webResources = from webResource in xmlDoc.Descendants("webResource")

                   select new

                   {

                       path = webResource.Element("path").Value,

                       displayName = webResource.Element("displayName").Value,

                       description = webResource.Element("description").Value,

                       name = webResource.Element("name").Value,

                       type = webResource.Element("type").Value

                   };


// Loop through the collection creating Web Resources

int counter = 0;

foreach (var webResource in webResources)

{

    // Set the Web Resource properties

    WebResource wr = new WebResource

    {

        Content = getEncodedFileContents(@"../../" + webResource.path),

        DisplayName = webResource.displayName,

        Description = webResource.description,

        Name = _customizationPrefix + webResource.name,

        LogicalName = WebResource.EntityLogicalName,

        WebResourceType = new OptionSetValue(Int32.Parse(webResource.type))

    };


    // Using CreateRequest because we want to add an optional parameter

    CreateRequest cr = new CreateRequest

    {

        Target = wr

    };

    

    // Set the SolutionUniqueName optional parameter so the Web Resources will be

    // created in the context of a specific solution.

    cr.Parameters.Add("SolutionUniqueName", _ImportWebResourcesSolutionUniqueName);


    CreateResponse cresp = (CreateResponse)_serviceProxy.Execute(cr);

    

    // Capture the id values for the Web Resources so the sample can delete them.

    _webResourceIds[counter] = cresp.id;

    counter++;

    Console.WriteLine("Created Web Resource: {0}", webResource.displayName);

}

```


### Encode File Content Method


```csharp

// Encodes the Web Resource File

static public string getEncodedFileContents(String pathToFile)

{

    FileStream fs = new FileStream(pathToFile, FileMode.Open, FileAccess.Read);

    byte[] binaryData = new byte[fs.Length];

    long bytesRead = fs.Read(binaryData, 0, (int)fs.Length);

    fs.Close();

    return System.Convert.ToBase64String(binaryData, 0, binaryData.Length);

}

```


---


## Key Points


### Solution Association


Use `SolutionUniqueName` optional parameter to associate web resources with a specific solution when created:


```csharp

cr.Parameters.Add("SolutionUniqueName", solutionUniqueName);

```


### Virtual Folder Structure


Names include backslash `/` characters to create virtual folder structure so relative links function correctly:


```

_/ShowData.htm

_/CSS/Styles.css

_/Script/Script.js

_/Data/Data.xml

_/XSL/Transform.xslt

```


The customization prefix is prepended automatically.


### Publishing


- **Not required** when web resources are created

- **Required** when web resources are updated


---


## Using the Web Resource


After importing, the web resource can be referenced by:


**URL:**

```

https://<org>.crm.dynamics.com/WebResources/<prefix>_/ShowData.htm

```


**In a form:**

Add as an IFRAME or web resource control.


**Via JavaScript:**

```javascript

Xrm.Navigation.openWebResource("<prefix>_/ShowData.htm");

```


---


## Reference


- [Web Resource Types](https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/web-resources#BKMK_WebResourceTypes)

- [Web resource table reference](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/reference/entities/webresource)

- [Download Sample](https://github.com/microsoft/PowerApps-Samples/tree/master/dataverse/orgsvc/CSharp/ImportWebResources)

Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.