UNPKG

xrm-ex

Version:

Xrm-Ex is a JavaScript framework that simplifies the usage of Dynamics 365 Client API. It provides intuitive wrappers for formContext and Xrm Object, helping developers reduce the amount of code, increase maintainability, and decrease errors. Ideal for de

1,117 lines (1,116 loc) 255 kB
/// <reference path="../node_modules/@types/xrm/index.d.ts" /> var XrmEx; (function (XrmEx) { /** * Throws an error with the given error message. * @param {string} errorMessage - The error message to throw. * @throws {Error} - Always throws an error with the given error message. */ function throwError(errorMessage) { throw new Error(errorMessage); } XrmEx.throwError = throwError; /** * Returns the name of the calling function. * @returns {string} - The name of the calling function. */ function getFunctionName() { try { const error = new Error(); const stackTrace = error.stack?.split("\n").map((line) => line.trim()); const callingFunctionLine = stackTrace && stackTrace.length >= 3 ? stackTrace[2] : undefined; const functionNameMatch = callingFunctionLine?.match(/at\s+([^\s]+)\s+\(/) || callingFunctionLine?.match(/at\s+([^\s]+)/); const functionName = functionNameMatch ? functionNameMatch[1] : ""; return functionName; } catch (error) { throw new Error(`XrmEx.getFunctionName:\n${error.message}`); } } XrmEx.getFunctionName = getFunctionName; /** * Retrieves the error message from an error object. * @param error - The error object to retrieve the message from. * @returns The error message. */ function getErrorMessage(error) { let msg = error, seen = new Set(), depth = 0; const validJSON = (t) => { try { JSON.parse(t); return true; } catch { return false; } }; while (msg && depth++ < 20 && !seen.has(msg)) { seen.add(msg); if (msg.raw) { msg = msg.raw; continue; } if (msg.message) { msg = msg.message; continue; } if (typeof msg === "string" && validJSON(msg)) { msg = JSON.parse(msg); continue; } break; } return msg; } XrmEx.getErrorMessage = getErrorMessage; /** * Displays a notification for an app with the given message and level, and lets you specify whether to show a close button. * @param {string} message - The message to display in the notification. * @param {'SUCCESS' | 'ERROR' | 'WARNING' | 'INFO'} level - The level of the notification. Can be 'SUCCESS', 'ERROR', 'WARNING', or 'INFO'. * @param {boolean} [showCloseButton=false] - Whether to show a close button on the notification. Defaults to false. * @returns {Promise<string>} - A promise that resolves with the ID of the created notification. */ async function addGlobalNotification(message, level, showCloseButton = false) { const levelMap = { SUCCESS: 1, ERROR: 2, WARNING: 3, INFO: 4, }; const messageLevel = levelMap[level] || levelMap.INFO; const notification = { type: 2, level: messageLevel, message, showCloseButton, }; try { return await Xrm.App.addGlobalNotification(notification); } catch (error) { throw new Error(`XrmEx.${getFunctionName()}:\n${error.message}`); } } XrmEx.addGlobalNotification = addGlobalNotification; /** * Clears a notification in the app with the given unique ID. * @param {string} uniqueId - The unique ID of the notification to clear. * @returns {Promise<string>} - A promise that resolves when the notification has been cleared. */ async function removeGlobalNotification(uniqueId) { try { return await Xrm.App.clearGlobalNotification(uniqueId); } catch (error) { throw new Error(`XrmEx.${getFunctionName()}:\n${error.message}`); } } XrmEx.removeGlobalNotification = removeGlobalNotification; /** * Retrieves the value of an environment variable by using its schema name as key. * If the environment variable has both a default value and a current value, this function will retrieve the current value. * @param {string} environmentVariableSchemaName - The schema name of the environment variable to retrieve. * @returns {Promise<string>} - A promise that resolves with the value of the environment variable. * @async */ async function getEnvironmentVariableValue(environmentVariableSchemaName) { let response = await executeFunction("RetrieveEnvironmentVariableValue", [ { Name: "DefinitionSchemaName", Type: "String", Value: environmentVariableSchemaName, }, ]); return Object.hasOwn(response, "Value") ? response.Value : response; } XrmEx.getEnvironmentVariableValue = getEnvironmentVariableValue; const isGuid = (value) => /^\{?[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\}?$/.test(value); /** * A map of CRM data types to their corresponding type names, structural properties, and JavaScript types. * @type {Object.<string, { typeName: string, structuralProperty: number, jsType: string }>} */ let typeMap = { String: { typeName: "Edm.String", structuralProperty: 1, jsType: "string" }, Integer: { typeName: "Edm.Int32", structuralProperty: 1, jsType: "number" }, Boolean: { typeName: "Edm.Boolean", structuralProperty: 1, jsType: "boolean", }, DateTime: { typeName: "Edm.DateTimeOffset", structuralProperty: 1, jsType: "object", }, EntityReference: { typeName: "mscrm.crmbaseentity", structuralProperty: 5, jsType: "object", }, Decimal: { typeName: "Edm.Decimal", structuralProperty: 1, jsType: "number", }, Entity: { typeName: "mscrm.crmbaseentity", structuralProperty: 5, jsType: "object", }, EntityCollection: { typeName: "Collection(mscrm.crmbaseentity)", structuralProperty: 4, jsType: "object", }, Float: { typeName: "Edm.Double", structuralProperty: 1, jsType: "number" }, Money: { typeName: "Edm.Decimal", structuralProperty: 1, jsType: "number" }, Picklist: { typeName: "Edm.Int32", structuralProperty: 1, jsType: "number", }, }; function getStructuralProperty(value) { const type = typeof value; if (type == "string" || type == "number" || type == "boolean") return 1; if (value instanceof Date) return 1; if (Array.isArray(value)) return 4; return 5; } XrmEx.getStructuralProperty = getStructuralProperty; function getTypeName(value) { const type = typeof value; if (type === "string") { if (isGuid(value)) { return "mscrm.crmbaseentity"; } return "Edm.String"; } if (type === "number") return "Edm.Int32"; // Default to Int32 for numbers if (type === "boolean") return "Edm.Boolean"; if (value instanceof Date) return "Edm.DateTimeOffset"; if (Array.isArray(value)) return "Collection(mscrm.crmbaseentity)"; return "mscrm.crmbaseentity"; // Default for objects and unknown types } XrmEx.getTypeName = getTypeName; /** * Builds a request object for use with Xrm.WebApi.online.execute or executeMultiple. * * This function extracts the logic to prepare parameters and create * the request object without executing it. You can: * - Directly execute the returned request object using `Xrm.WebApi.online.execute()`. * - Use the request object later with `Xrm.WebApi.online.executeMultiple()`. * * @param {string} actionName - The unique name of the request (action/function/CRUD operation). * @param {RequestParameter[] | {[key: string]: any}} requestParameters - An array of request parameters or an object representing key-value pairs of request parameters. * - If an array of `RequestParameter[]` is provided, each element should have `Name`, `Type`, and `Value` fields describing the request parameter. * - If an object is provided, its keys represent parameter names, and values represent the parameter values. * @param {number} operationType - The type of the request. Use: * - `0` for Action * - `1` for Function * - `2` for CRUD * @param {EntityReference} [boundEntity] - An optional `EntityReference` indicating the entity the request is bound to. * * @returns {object} - The request object that can be passed into `Xrm.WebApi.online.execute` or `Xrm.WebApi.online.executeMultiple`. * * @example * // Build a request object for a custom action "new_DoSomething" (operationType = 0 for actions) * const request = buildRequestObject("new_DoSomething", { param1: "value1", param2: 123 }, 0); * * // Execute the request immediately * const result = await Xrm.WebApi.online.execute(request); * * // Or store the request and execute it later using executeMultiple * const requests = [request, anotherRequest]; * const batchResult = await Xrm.WebApi.online.executeMultiple(requests); */ function buildRequestObject(actionName, requestParameters, operationType, boundEntity) { const prepareParameterDefinition = (params) => { const parameterDefinition = {}; const p = Array.isArray(params) ? [...params] : { ...params }; if (Array.isArray(p)) { p.forEach((param) => { parameterDefinition[param.Name] = { typeName: typeMap[param.Type].typeName, structuralProperty: typeMap[param.Type].structuralProperty, }; }); } else { Object.keys(p).forEach((key) => { parameterDefinition[key] = { structuralProperty: getStructuralProperty(p[key]), typeName: getTypeName(p[key]), }; }); } return parameterDefinition; }; const createRequest = (params, definition) => { const metadata = { boundParameter: boundEntity ? "entity" : null, operationType: operationType, operationName: actionName, parameterTypes: definition, }; const mergedParams = Array.isArray(params) ? Object.assign({}, ...params.map((p) => ({ [p.Name]: p.Value }))) : params; return Object.assign({ getMetadata: () => metadata }, mergedParams); }; if (boundEntity) { if (Array.isArray(requestParameters)) { requestParameters.push({ Name: "entity", Value: boundEntity, Type: "EntityReference", }); } else { requestParameters["entity"] = boundEntity; } } const parameterDefinition = prepareParameterDefinition(requestParameters); const request = createRequest(requestParameters, parameterDefinition); return request; } XrmEx.buildRequestObject = buildRequestObject; /** * Executes a request. * @param {string} actionName - The unique name of the request. * @param {RequestParameter[] | object} requestParameters - An array of objects with the parameter name, type, and value. * @param {EntityReference} [boundEntity] - An optional EntityReference of the bound entity. * @param {number} [operationType=1] - The type of the request. 0 for actions, 1 for functions, 2 for CRUD operations. * @returns {Promise<any>} - A Promise with the request response. * @throws {Error} - Throws an error if the request parameter is not of a supported type or has an invalid value. */ async function execute(actionName, requestParameters, boundEntity, operationType = 1) { const request = buildRequestObject(actionName, requestParameters, operationType, boundEntity); const result = await Xrm.WebApi.online.execute(request); if (result.ok) return result.json().catch(() => result); } XrmEx.execute = execute; /** * Executes an Action. * @param {string} actionName - The unique name of the action. * @param {RequestParameter[] | object} requestParameters - An array of objects with the parameter name, type, and value. * @param {EntityReference} [boundEntity] - An optional EntityReference of the bound entity. * @returns {Promise<any>} - A Promise with the request response. * @throws {Error} - Throws an error if the request parameter is not of a supported type or has an invalid value. */ async function executeAction(functionName, requestParameters, boundEntity) { return await execute(functionName, requestParameters, boundEntity, 0); } XrmEx.executeAction = executeAction; /** * Executes a Function. * @param {string} functionName - The unique name of the function. * @param {RequestParameter[] | object} requestParameters - An array of objects with the parameter name, type and value. * @param {EntityReference} [boundEntity] - An optional EntityReference of the bound entity. * @returns {Promise<any>} - A Promise with the request response. * @throws {Error} - Throws an error if the request parameter is not of a supported type or has an invalid value. */ async function executeFunction(functionName, requestParameters, boundEntity) { return await execute(functionName, requestParameters, boundEntity, 1); } XrmEx.executeFunction = executeFunction; /** * Executes a CRUD request. * @param {string} messageName - The unique name of the request. * @param {RequestParameter[] | object} requestParameters - An array of objects with the parameter name, type, and value. * @param {EntityReference} [boundEntity] - An optional EntityReference of the bound entity. * @returns {Promise<any>} - A Promise with the request response. * @throws {Error} - Throws an error if the request parameter is not of a supported type or has an invalid value. */ async function executeCRUD(functionName, requestParameters, boundEntity) { return await execute(functionName, requestParameters, boundEntity, 2); } XrmEx.executeCRUD = executeCRUD; /** * Makes a GUID lowercase and removes brackets. * @param {string} guid - The GUID to normalize. * @returns {string} - The normalized GUID. */ function normalizeGuid(guid) { if (typeof guid !== "string") throw new Error(`XrmEx.normalizeGuid:\n'${guid}' is not a string`); return guid.toLowerCase().replace(/[{}]/g, ""); } XrmEx.normalizeGuid = normalizeGuid; /** * Wraps a function that takes a callback as its last parameter and returns a Promise. * @param {Function} fn the function to wrap * @param context the parent property of the function f.e. formContext.data.process for formContext.data.process.getEnabledProcesses * @param args the arguments to pass to the function * @returns {Promise<any>} a Promise that resolves with the callback response */ function asPromise(fn, context, ...args) { return new Promise((resolve, reject) => { const callback = (response) => { resolve(response); }; try { // Call the function with the arguments and the callback at the end fn.call(context, ...args, callback); } catch (error) { reject(error); } }); } XrmEx.asPromise = asPromise; /** * Opens a dialog with dynamic height and width based on text content. * @param {string} title - The title of the dialog. * @param {string} text - The text content of the dialog. * @returns {Promise<any>} - A Promise with the dialog response. */ async function openAlertDialog(title, text) { try { const rows = text.split(/\r\n|\r|\n/); let additionalRows = 0; rows.forEach((row) => { let width = getTextWidth(row, "1rem Segoe UI Regular, SegoeUI, Segoe UI"); if (width > 940) { additionalRows += width / 940; } }); const longestRow = rows.reduce((acc, row) => (row.length > acc.length ? row : acc), ""); const width = Math.min(getTextWidth(longestRow, "1rem Segoe UI Regular, SegoeUI, Segoe UI"), 1000); const height = 109 + (rows.length + additionalRows) * 20; return await Xrm.Navigation.openAlertDialog({ confirmButtonLabel: "Ok", text, title, }, { height, width, }); } catch (error) { console.error(error.message); throw new Error(`XrmEx.${getFunctionName()}:\n${error.message}`); } /** * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. * * @param {String} text The text to be rendered. * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). * * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 */ function getTextWidth(text, font) { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); context.font = font; const metrics = context.measureText(text); return metrics.width; } } XrmEx.openAlertDialog = openAlertDialog; /** * Opens an error dialog with the specified error information. * @param error The error object containing details about the error. */ async function openErrorDialog(error) { try { return await Xrm.Navigation.openErrorDialog({ message: getErrorMessage(error), details: JSON.stringify(error, null, 4), errorCode: error?.errorCode, }); } catch (error) { console.error(error.message); throw new Error(`XrmEx.${getFunctionName()}:\n${error.message}`); } } XrmEx.openErrorDialog = openErrorDialog; class Process { static get data() { return Form.formContext.data.process; } static get ui() { return Form.formContext.ui.process; } /** * Use this to add a function as an event handler for the OnPreProcessStatusChange event so that it will be called before the * business process flow status changes. * @param handler The function will be added to the bottom of the event * handler pipeline. The execution context is automatically * set to be the first parameter passed to the event handler. * Use a reference to a named function rather than an * anonymous function if you may later want to remove the * event handler. */ static addOnPreProcessStatusChange(handler) { Form.formContext.data.process.removeOnPreProcessStatusChange(handler); return Form.formContext.data.process.addOnPreProcessStatusChange(handler); } /** * Use this to add a function as an event handler for the OnPreStageChange event so that it will be called before the * business process flow stage changes. * @param handler The function will be added to the bottom of the event * handler pipeline. The execution context is automatically * set to be the first parameter passed to the event handler. * Use a reference to a named function rather than an * anonymous function if you may later want to remove the * event handler. */ static addOnPreStageChange(handler) { Form.formContext.data.process.removeOnPreStageChange(handler); return Form.formContext.data.process.addOnPreStageChange(handler); } /** * Use this to add a function as an event handler for the OnPreProcessStatusChange event so that it will be called when the * business process flow status changes. * @param handler The function will be added to the bottom of the event * handler pipeline. The execution context is automatically * set to be the first parameter passed to the event handler. * Use a reference to a named function rather than an * anonymous function if you may later want to remove the * event handler. */ static addOnProcessStatusChange(handler) { Form.formContext.data.process.removeOnProcessStatusChange(handler); return Form.formContext.data.process.addOnProcessStatusChange(handler); } /** * Use this to add a function as an event handler for the OnStageChange event so that it will be called when the * business process flow stage changes. * @param handler The function will be added to the bottom of the event * handler pipeline. The execution context is automatically * set to be the first parameter passed to the event handler. * Use a reference to a named function rather than an * anonymous function if you may later want to remove the * event handler. */ static addOnStageChange(handler) { Form.formContext.data.process.removeOnStageChange(handler); return Form.formContext.data.process.addOnStageChange(handler); } /** * Use this to add a function as an event handler for the OnStageSelected event so that it will be called * when a business process flow stage is selected. * @param handler The function will be added to the bottom of the event * handler pipeline. The execution context is automatically * set to be the first parameter passed to the event handler. * Use a reference to a named function rather than an * anonymous function if you may later want to remove the * event handler. */ static addOnStageSelected(handler) { Form.formContext.data.process.removeOnStageSelected(handler); return Form.formContext.data.process.addOnStageSelected(handler); } /** * Use this to remove a function as an event handler for the OnPreProcessStatusChange event. * @param handler If an anonymous function is set using the addOnPreProcessStatusChange method it * cannot be removed using this method. */ static removeOnPreProcessStatusChange(handler) { return Form.formContext.data.process.removeOnPreProcessStatusChange(handler); } /** * Use this to remove a function as an event handler for the OnPreStageChange event. * @param handler If an anonymous function is set using the addOnPreStageChange method it * cannot be removed using this method. */ static removeOnPreStageChange(handler) { return Form.formContext.data.process.removeOnPreStageChange(handler); } /** * Use this to remove a function as an event handler for the OnProcessStatusChange event. * @param handler If an anonymous function is set using the addOnProcessStatusChange method it * cannot be removed using this method. */ static removeOnProcessStatusChange(handler) { return Form.formContext.data.process.removeOnProcessStatusChange(handler); } /** * Use this to remove a function as an event handler for the OnStageChange event. * @param handler If an anonymous function is set using the addOnStageChange method it * cannot be removed using this method. */ static removeOnStageChange(handler) { return Form.formContext.data.process.removeOnStageChange(handler); } /** * Use this to remove a function as an event handler for the OnStageChange event. * @param handler If an anonymous function is set using the addOnStageChange method it * cannot be removed using this method. */ static removeOnStageSelected(handler) { return Form.formContext.data.process.removeOnStageSelected(handler); } /** * Use this method to asynchronously retrieve the enabled business process flows that the user can switch to for an entity. * @returns returns callback response as Promise */ static getEnabledProcesses() { return asPromise(Form.formContext.data.process.getEnabledProcesses, Form.formContext.data.process); } /** * Returns all process instances for the entity record that the calling user has access to. * @returns returns callback response as Promise */ static getProcessInstances() { return asPromise(Form.formContext.data.process.getProcessInstances, Form.formContext.data.process); } /** * Progresses to the next stage. * @returns returns callback response as Promise */ static moveNext() { return asPromise(Form.formContext.data.process.moveNext, Form.formContext.data.process); } /** * Moves to the previous stage. * @returns returns callback response as Promise */ static movePrevious() { return asPromise(Form.formContext.data.process.movePrevious, Form.formContext.data.process); } /** * Set a Process as the active process. * @param processId The Id of the process to make the active process. * @returns returns callback response as Promise */ static setActiveProcess(processId) { return asPromise(Form.formContext.data.process.setActiveProcess, Form.formContext.data.process, processId); } /** * Sets a process instance as the active instance * @param processInstanceId The Id of the process instance to make the active instance. * @returns returns callback response as Promise */ static setActiveProcessInstance(processInstanceId) { return asPromise(Form.formContext.data.process.setActiveProcessInstance, Form.formContext.data.process, processInstanceId); } /** * Set a stage as the active stage. * @param stageId the Id of the stage to make the active stage. * @returns returns callback response as Promise */ static setActiveStage(stageId) { return asPromise(Form.formContext.data.process.setActiveStage, Form.formContext.data.process, stageId); } /** * Use this method to set the current status of the process instance * @param status The new status for the process * @returns returns callback response as Promise */ static setStatus(status) { return asPromise(Form.formContext.data.process.setStatus, Form.formContext.data.process, status); } } XrmEx.Process = Process; class Fields { /** * Adds a handler or an array of handlers to be called when the attribute's value is changed. * @param fields An array of fields to on which this method should be applied. * @param handlers The function reference or an array of function references. */ static addOnChange(fields, handler) { fields.forEach((field) => { field.addOnChange(handler); }); } /** * Fire all "on change" event handlers. * @param fields An array of fields to on which this method should be applied. */ static fireOnChange(fields) { fields.forEach((field) => { field.fireOnChange(); }); } /** * Removes the handler from the "on change" event. * @param fields An array of fields to on which this method should be applied. * @param handler The handler. */ static removeOnChange(fields, handler) { fields.forEach((field) => { field.removeOnChange(handler); }); } /** * Sets the required level. * @param fields An array of fields to on which this method should be applied. * @param requirementLevel The requirement level, as either "none", "required", or "recommended" */ static setRequiredLevel(fields, requirementLevel) { fields.forEach((field) => { field.setRequiredLevel(requirementLevel); }); } /** * Sets the submit mode. * @param fields An array of fields to on which this method should be applied. * @param submitMode The submit mode, as either "always", "never", or "dirty". * @default submitMode "dirty" * @see {@link XrmEnum.AttributeRequirementLevel} */ static setSubmitMode(fields, submitMode) { fields.forEach((field) => { field.setSubmitMode(submitMode); }); } /** * Sets the value. * @param fields An array of fields to on which this method should be applied. * @param value The value. * @remarks Attributes on Quick Create Forms will not save values set with this method. */ static setValue(fields, value) { fields.forEach((field) => { field.setValue(value); }); } /** * Sets a value for a column to determine whether it is valid or invalid with a message * @param fields An array of fields to on which this method should be applied. * @param isValid Specify false to set the column value to invalid and true to set the value to valid. * @param message The message to display. * @see {@link https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/reference/attributes/setisvalid External Link: setIsValid (Client API reference)} */ static setIsValid(fields, isValid, message) { fields.forEach((field) => { field.setIsValid(isValid, message); }); } /** * Sets the required level. * @param fields An array of fields to on which this method should be applied. * @param required The requirement level, as either false for "none" or true for "required" */ static setRequired(fields, required) { fields.forEach((field) => { field.setRequired(required); }); } /** * Sets the state of the control to either enabled, or disabled. * @param fields An array of fields to on which this method should be applied. * @param disabled true to disable, false to enable. */ static setDisabled(fields, disabled) { fields.forEach((field) => { field.setDisabled(disabled); }); } /** * Sets the visibility state. * @param fields An array of fields to on which this method should be applied. * @param visible true to show, false to hide. */ static setVisible(fields, visible) { fields.forEach((field) => { field.setVisible(visible); }); } /** * Sets a control-local notification message. * @param fields An array of fields to on which this method should be applied. * @param message The message. * @param uniqueId Unique identifier. * @returns true if it succeeds, false if it fails. * @remarks When this method is used on Microsoft Dynamics CRM for tablets a red "X" icon * appears next to the control. Tapping on the icon will display the message. */ static setNotification(fields, message, uniqueId) { fields.forEach((field) => { field.setNotification(message, uniqueId); }); } /** * Displays an error or recommendation notification for a control, and lets you specify actions to execute based on the notification. * @param fields An array of fields to on which this method should be applied. */ static addNotification(fields, message, notificationLevel, uniqueId, actions) { fields.forEach((field) => { field.addNotification(message, notificationLevel, uniqueId, actions); }); } /** * Clears the notification identified by uniqueId. * @param fields An array of fields to on which this method should be applied. * @param uniqueId (Optional) Unique identifier. * @returns true if it succeeds, false if it fails. * @remarks If the uniqueId parameter is not used, the current notification shown will be removed. */ static removeNotification(fields, uniqueId) { fields.forEach((field) => { field.removeNotification(uniqueId); }); } } XrmEx.Fields = Fields; /** * Represents a form in Dynamics 365. */ class Form { static _formContext; static _executionContext; constructor() { } /**Gets a reference to the current form context*/ static get formContext() { return this._formContext; } /**Gets a reference to the current executio context*/ static get executionContext() { return this._executionContext; } /**Gets a lookup value that references the record.*/ static get entityReference() { return Form.formContext.data.entity.getEntityReference(); } /**Sets a reference to the current form context*/ static set formContext(context) { if (!context) throw new Error(`XrmEx.Form.setFormContext: The executionContext or formContext was not passed to the function.`); if ("getFormContext" in context) { this._executionContext = context; this._formContext = context.getFormContext(); } else if ("data" in context) this._formContext = context; else throw new Error(`XrmEx.Form.setFormContext: The passed context is not an executionContext or formContext.`); } /**Sets a reference to the current execution context*/ static set executionContext(context) { if (!context) throw new Error(`XrmEx.Form.setExecutionContext: The executionContext or formContext was not passed to the function.`); if ("getFormContext" in context) { this._executionContext = context; this._formContext = context.getFormContext(); } else if ("data" in context) this._formContext = context; else throw new Error(`XrmEx.Form.setExecutionContext: The passed context is not an executionContext or formContext.`); } /**Returns true if form is from type create*/ static get IsCreate() { return Form.formContext.ui.getFormType() == 1; } /**Returns true if form is from type update*/ static get IsUpdate() { return Form.formContext.ui.getFormType() == 2; } /**Returns true if form is not from type create*/ static get IsNotCreate() { return Form.formContext.ui.getFormType() != 1; } /**Returns true if form is not from type update*/ static get IsNotUpdate() { return Form.formContext.ui.getFormType() != 2; } /** * Displays a form level notification. Any number of notifications can be displayed and will remain until removed using clearFormNotification. * The height of the notification area is limited so each new message will be added to the top. * @param message The text of the notification message. * @param level The level of the notification which defines how the message will be displayed, such as the icon. * ERROR: Notification will use the system error icon. * WARNING: Notification will use the system warning icon. * INFO: Notification will use the system info icon. * @param uniqueId Unique identifier for the notification which is used with clearFormNotification to remove the notification. * @returns true if it succeeds, othenprwise false. */ static addFormNotification(message, level, uniqueId) { try { return Form.formContext.ui.setFormNotification(message, level, uniqueId); } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Clears the form notification described by uniqueId. * @param uniqueId Unique identifier. * @returns True if it succeeds, otherwise false. */ static removeFormNotification(uniqueId) { try { return Form.formContext.ui.clearFormNotification(uniqueId); } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Adds a handler to be called when the record is saved. */ static addOnSave(handlers) { try { if (!Array.isArray(handlers)) { handlers = [handlers]; } handlers.forEach((handler) => { if (typeof handler !== "function") { throw new Error(`'${handler}' is not a function`); } Form.formContext.data.entity.removeOnSave(handler); Form.formContext.data.entity.addOnSave(handler); }); } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Adds a function to be called after the OnSave is complete. * @param handler The handler. * @remarks Added in 9.2 * @see {@link https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/events/postsave External Link: PostSave Event Documentation} */ static addOnPostSave(handlers) { try { if (!Array.isArray(handlers)) { handlers = [handlers]; } handlers.forEach((handler) => { if (typeof handler !== "function") { throw new Error(`'${handler}' is not a function`); } Form.formContext.data.entity.removeOnPostSave(handler); Form.formContext.data.entity.addOnPostSave(handler); }); } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Adds a function to be called when form data is loaded. * @param handler The function to be executed when the form data loads. The function will be added to the bottom of the event handler pipeline. */ static addOnLoad(handlers) { try { if (!Array.isArray(handlers)) { handlers = [handlers]; } handlers.forEach((handler) => { if (typeof handler !== "function") { throw new Error(`'${handler}' is not a function`); } Form.formContext.data.removeOnLoad(handler); Form.formContext.data.addOnLoad(handler); }); } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Adds a handler to be called when the attribute's value is changed. * @param handler The function reference. */ static addOnChange(fields, handlers, execute) { try { if (!Array.isArray(handlers)) { handlers = [handlers]; } handlers.forEach((handler) => { if (typeof handler !== "function") { throw new Error(`'${handler}' is not a function`); } fields.forEach((field) => { field.removeOnChange(handler); field.addOnChange(handler); }); }); if (execute) { fields.forEach((field) => { field.Attribute.fireOnChange(); }); } } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } static async cloneRecord() { var entityFormOptions = {}; var formParameters = {}; var entityName = this.formContext.data.entity.getEntityName(); entityFormOptions["entityName"] = entityName; this.formContext.data.entity.attributes.forEach((c) => { var attributeType = c.getAttributeType(); var attributeName = c.getName(); var attributeValue = c.getValue(); if (!attributeValue || attributeName === "createdon" || attributeName === "modifiedon" || attributeName === "createdby" || attributeName === "modifiedby" || attributeName === "processid" || attributeName === "stageid" || attributeName === "ownerid" || attributeName.startsWith("transactioncurrency")) return; if (attributeType === "lookup" && !c.getIsPartyList() && attributeValue.length > 0) { formParameters[attributeName] = attributeValue[0].id; formParameters[attributeName + "name"] = attributeValue[0].name; if (attributeName === "customerid" || attributeName === "parentcustomerid" || (c.getAttributeType && c.getAttributeType() === "lookup" && typeof c.getLookupTypes === "function" && Array.isArray(c.getLookupTypes()) && c.getLookupTypes().length > 1)) { formParameters[attributeName + "type"] = attributeValue[0].entityType; } } else if (attributeType === "datetime") { formParameters[attributeName] = new Date(attributeValue).toISOString(); } else { formParameters[attributeName] = attributeValue; } }); try { var result = await Xrm.Navigation.openForm(entityFormOptions, formParameters); console.log(result); } catch (error) { console.error(error.message); throw new Error(`XrmEx.${getFunctionName()}:\n${error.message}`); } } } XrmEx.Form = Form; let Class; (function (Class) { /** * Used to execute methods related to a single Attribute */ class Field { Name; _attribute; constructor(attributeName) { this.Name = attributeName; } setValue(value) { return this.Attribute.setValue(value); } getAttributeType() { return this.Attribute.getAttributeType(); } getFormat() { return this.Attribute.getFormat(); } getIsDirty() { return this.Attribute.getIsDirty(); } getName() { return this.Attribute.getName(); } getParent() { return this.Attribute.getParent(); } getRequiredLevel() { return this.Attribute.getRequiredLevel(); } getSubmitMode() { return this.Attribute.getSubmitMode(); } getUserPrivilege() { return this.Attribute.getUserPrivilege(); } removeOnChange(handler) { return this.Attribute.removeOnChange(handler); } setSubmitMode(submitMode) { return this.Attribute.setSubmitMode(submitMode); } getValue() { return this.Attribute.getValue(); } setIsValid(isValid, message) { return this.Attribute.setIsValid(isValid, message); } get Attribute() { return (this._attribute ??= Form.formContext.getAttribute(this.Name) ?? XrmEx.throwError(`The attribute '${this.Name}' was not found on the form.`)); } get controls() { return this.Attribute.controls; } /** * Gets the value. * @returns The value. */ get Value() { return this.Attribute.getValue(); } set Value(value) { this.Attribute.setValue(value); } /** * Sets a control-local notification message. * @param message The message. * @param uniqueId Unique identifier. * @returns true if it succeeds, false if it fails. * @remarks When this method is used on Microsoft Dynamics CRM for tablets a red "X" icon * appears next to the control. Tapping on the icon will display the message. */ setNotification(message, uniqueId) { try { if (!message) throw new Error(`no message was provided.`); if (!uniqueId) throw new Error(`no uniqueId was provided.`); this.controls.forEach((control) => control.setNotification(message, uniqueId)); return this; } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Sets the visibility state. * @param visible true to show, false to hide. */ setVisible(visible) { try { this.controls.forEach((control) => control.setVisible(visible)); return this; } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Sets the state of the control to either enabled, or disabled. * @param disabled true to disable, false to enable. */ setDisabled(disabled) { try { this.controls.forEach((control) => control.setDisabled(disabled)); return this; } catch (error) { throw new Error(`XrmEx.${XrmEx.getFunctionName()}:\n${error.message}`); } } /** * Sets the required level. * @param requirementLevel The requirement level, as either "none", "required", or "recommended" */ setRequiredLevel(requirementLevel) {