Power Platform Web Resource Skill
# 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¶m2=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)