Home

Wednesday, March 27, 2024

Convert subgrid to Comments

 Index.ts

import { IInputs, IOutputs } from "./generated/ManifestTypes";

import { CommentsGrid, ICommentsGrid } from "./Comments";

import * as React from "react";

import DataSetInterfaces = ComponentFramework.PropertyHelper.DataSetApi;

type DataSet = ComponentFramework.PropertyTypes.DataSet;


export class CommentsControl implements ComponentFramework.ReactControl<IInputs, IOutputs> {

    private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;

    private notifyOutputChanged: () => void;

    private _context: ComponentFramework.Context<IInputs>;

    private _currentUserName: string;

    private _currentUserId: string;

    private _currentEntityId: string;

    private _currentEntityName: string;

    private _webAPI: any;

    private _messageProperty: any;

    private _userProperty: any;

    private _dateProperty: any;

    private _parentLookUpProperty: any


    /**

     * Empty constructor.

     */

    constructor() { }


    /**

     * Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.

     * Data-set values are not initialized here, use updateView.

     * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.

     * @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.

     * @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.

     */

    public init(

        context: ComponentFramework.Context<IInputs>,

        notifyOutputChanged: () => void,

        state: ComponentFramework.Dictionary

    ): void {

        this._context = context;

        this._currentUserName = context.userSettings.userName;

        this._currentUserId = context.userSettings.userId;

        this._currentEntityId = (context.mode as any).contextInfo.entityId;

        this._currentEntityName = (context.mode as any).contextInfo.entityTypeName;

        this._webAPI = context.webAPI;

        this._messageProperty = context.parameters.MessageProperty.raw,

            this._userProperty = context.parameters.UserProperty.raw,

            this._dateProperty = context.parameters.DateProperty.raw,

            this._parentLookUpProperty = context.parameters.ParentLookUpProperty.raw

        this.notifyOutputChanged = notifyOutputChanged;

        this.notifyOutputChanged = notifyOutputChanged;

    }


    /**

     * Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.

     * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions

     * @returns ReactElement root react element for the control

     */

    public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {

        this._context = context;

        let pageRows = this.getAllPageRecords(this._context.parameters.dataset)

        const props: ICommentsGrid = {

            pagerows: pageRows,

            currentUserName: this._currentUserName,

            currentUserId: this._currentUserId,

            currentEntityId: this._currentEntityId,

            currentEntityTypeName: this._currentEntityName,

            webAPI: this._webAPI,

            dataset: this._context.parameters.dataset,

            messageProperty: this._messageProperty,

            dateProperty: this._dateProperty,

            userProperty: this._userProperty,

            parentLookUpProperty: this._parentLookUpProperty

        };

        return React.createElement(

            CommentsGrid, props

        );

    }


    /**

     * It is called by the framework prior to a control receiving new data.

     * @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output"

     */

    public getOutputs(): IOutputs {

        return {};

    }


    /**

     * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.

     * i.e. cancelling any pending remote calls, removing listeners, etc.

     */

    public destroy(): void {

        // Add code to cleanup control if necessary

    }


    private getAllPageRecords(gridParam: DataSet) {

        let functionName = "loadPagingRecords";

        let pagingDataRows: any = [];

        let currentPageRecordsID = gridParam.sortedRecordIds;

        let columnsOnView = gridParam.columns;

        try {

            for (const pointer in currentPageRecordsID) {

                pagingDataRows[pointer] = {}

                pagingDataRows[pointer]["key"] = currentPageRecordsID[pointer];

                columnsOnView.forEach((columnItem: any, index) => {

                    pagingDataRows[pointer][columnItem.name] = gridParam.records[currentPageRecordsID[pointer]].getFormattedValue(columnItem.name);

                });

            }

        } catch (error) {

            console.log(functionName + "" + error);

        }

        return pagingDataRows;

    }

}

Comments.tsx
import * as React from 'react';
import { ActivityItem, IStackProps, IStackStyles, Icon, Link, Separator, Stack, TextField, mergeStyleSets } from '@fluentui/react';
import { Enter } from '@fluentui/keyboard-keys';
const stackStyles: Partial<IStackStyles> = { root: { width: 650 } };
const stackTokens = { childrenGap: 50 };
const columnProps: Partial<IStackProps> = {
  tokens: { childrenGap: 15 },
  styles: { root: { width: 300 } },
};

type DataSet = ComponentFramework.PropertyTypes.DataSet;

export interface ICommentsGrid {
  pagerows: any,
  currentUserName: string,
  currentUserId: string,
  currentEntityId: string,
  currentEntityTypeName: string,
  webAPI: any,
  dataset: any,
  messageProperty: string,
  dateProperty: string,
  userProperty: string,
  parentLookUpProperty: string
}

const classNames = mergeStyleSets({
  exampleRoot: {
    marginTop: '20px',
    marginLeft: '5px'         
  },
  nameText: {
    fontWeight: 'bold',
  }
});

export const CommentsGrid = React.memo(({ pagerows, currentUserName, currentUserId, currentEntityId, currentEntityTypeName, webAPI, dataset, 
                                                      messageProperty, dateProperty, userProperty, parentLookUpProperty }: ICommentsGrid): JSX.Element => {
  const [state, setState] = React.useState<any[]>([]);
  const containerRef = React.useRef<HTMLDivElement>(null); 
  const [enteredText, setEnteredText] = React.useState(''); 
  let targetType = dataset.getTargetEntityType()
  React.useEffect(() => {
    const fetchData = () => {
      let autoGroupColumnDef = [];
      try {
        for (let i = 0; i < pagerows.length; i++) {
          let date = pagerows[i][dateProperty];
          autoGroupColumnDef.push({
            key: 0,
            activityDescription: [<Link
              key={1}
              className={classNames.nameText}
              onClick={() => {

              }}>{pagerows[i][userProperty]}</Link>,
            <span key={2}> commented</span>,
            ],
            activityIcon: pagerows[i][userProperty] == currentUserName ? <Icon iconName={'Message'} /> : <Icon iconName={'ReplyAll'} />,
            comments: [<span key={1}>{pagerows[i][messageProperty]}</span>],
            timeStamp: date
          });
        }
        setState(autoGroupColumnDef);
      } catch (error) {
        setState([]);
        console.log('error')
      }
    }
    fetchData();
  }, [pagerows]);
 
  const handleChange= (event: any) => {
    setEnteredText(event.target.value);
  }
  
  const onSendClick = (event: any) =>  {
    if (event.key === Enter && event.altKey == false && event.ctrlKey == false && event.shiftKey == false) {    
      let comment = event.target.value;                   
      let entity: any = {};
      entity[messageProperty]= comment;
      entity[parentLookUpProperty + '@odata.bind'] = "/" + currentEntityTypeName + "s(" + currentEntityId.replace("{", "").replace("}", "") + ")";     
      webAPI.createRecord(targetType, entity).then(
        function success() {
          console.log("success");  
          setEnteredText('');       
          dataset.refresh();
        },
        function (error: any) {
          console.log(error.message); 
          setEnteredText('');                                 
          dataset.refresh();
        }
      );
    }    
  }

  return (
    <div>
      <Stack horizontal tokens={stackTokens} styles={stackStyles}>
        <Stack {...columnProps}>
          <div ref={containerRef} style={{ maxHeight: '300px', overflowY: 'scroll' }}>
            <TextField id='message' value={enteredText} onKeyDown={onSendClick} onChange={handleChange} multiline autoAdjustHeight />
          </div>
        </Stack>
      </Stack>
      {state.map((item: { key: string | number }) => (
        <ActivityItem {...item} key={item.key} className={classNames.exampleRoot} />
      ))},      
    </div>
  );
});
export default CommentsGrid;

ControlManifest
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="mmm" constructor="CommentsControl" version="0.0.1" display-name-key="CommentsControl" description-key="CommentsControl description" control-type="virtual" >
    <!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
    If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
    Example1:
      <external-service-usage enabled="true">
        <domain>www.Microsoft.com</domain>
      </external-service-usage>
    Example2:
      <external-service-usage enabled="false">
      </external-service-usage>
    -->
    <external-service-usage enabled="false">
      <!--UNCOMMENT TO ADD EXTERNAL DOMAINS
      <domain></domain>
      <domain></domain>
      -->
    </external-service-usage>
    <!-- dataset node represents a set of entity records on CDS; allow more than one datasets -->
    <data-set name="dataset" display-name-key="Dataset_Display_Key">
      <!-- 'property-set' node represents a unique, configurable property that each record in the dataset must provide. -->
      <!-- UNCOMMENT TO ADD PROPERTY-SET NODE
      <property-set name="samplePropertySet" display-name-key="Property_Display_Key" description-key="Property_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" />
      -->
    </data-set>
    <property name="MessageProperty" display-name-key="MessageProperty_Display_Key"
    description-key="MessageProperty_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
    <property name="DateProperty" display-name-key="DateProperty_Display_Key"
    description-key="DateProperty_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
    <property name="UserProperty" display-name-key="UserProperty_Display_Key"
    description-key="UserProperty_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
    <property name="ParentLookUpProperty" display-name-key="ParentLookUpProperty_Display_Key"
    description-key="ParentLookUpProperty_Desc_Key" of-type="SingleLine.Text" usage="input" required="true" />
    <resources>
      <code path="index.ts" order="1"/>
      <platform-library name="React" version="16.8.6" />
      <platform-library name="Fluent" version="8.29.0" />
      <!-- UNCOMMENT TO ADD MORE RESOURCES
      <css path="css/CommentsControl.css" order="1" />
      <resx path="strings/CommentsControl.1033.resx" version="1.0.0" />
      -->
    </resources>    
    <feature-usage>      
      <uses-feature name="WebAPI" required="true" />
    </feature-usage>   
  </control>
</manifest>

Wednesday, February 10, 2021

Feedback Entity in Dynamics 365


 Introduction

Feedback entity lets customers write feedback for any entity record or rate entity records within a defined rating range.

This feature was introduced in CRM Online 2016 Update 1 and CRM 2016 Service Pack 1 (on-premises).

When Would This be Useful?

The best way to show how Feedback is useful is to use an example.

In the service scenario, you can enable feedback or ratings on the Case entity to receive feedback on the support experience the customer received. When several customers are rating a record, the ratings can be consolidated for each record through a custom rollup field.

In a sales scenario, you can enable the Product entity for feedback to get users' feedback on the products you sell.

Enable Feedback

Note: Once enabled, the feedback cannot be disabled on an entity.

By default, feedback is enabled for the Knowledge Article entity and the rollup field that stores the rating is added to knowledge article form.


1.      Make sure that you have the System Administrator or System Customizer security role or equivalent permissions.

2.      Open solution explorer.

3.      Under Components, expand Entities, and then select the entity you want to enable feedback for.

4.      Under Communication & Collaboration, select the Feedback check box.

5.      Publish your customizations:

o    To publish customizations for only the component you are currently editing, on the nav bar or in the navigation pane, select the entity you have been working on, and then select Publish.

After you enable an entity for feedback, a regarding relationship is created between the entity and the Feedback entity.

Add a subgrid for feedback on the entity form

By default, users must go to the list of associated records of the record you want to add feedback to. To make it easier for users to add feedback, you may want to add a feedback subgrid to the form of the entity you are enabling feedback for.


1.      Make sure that you have the System Administrator or System Customizer security role or equivalent permissions.

2.      Open solution explorer.

3.      Under Components, expand Entities, and then expand the entity you've enabled for feedback.

4.      Select Forms.

5.      Open the form of type Main or Main - Interactive experience.

6.      Select the section you want to insert the subgrid in, and on the Insert tab, in the Control group, select Sub-Grid.

7.      In the Set Properties dialog box, fill in the name and label for the subgrid.

8.      In the Data Source section, select the information:

o    Records. Select Only Related Records.

o    Entity. Select Feedback (Regarding).

o    Default View. Select a default view for the list.

9.      Publish your customizations:

o    To publish customizations for only the component you are currently editing, on the nav bar or in the navigation pane, select the entity you have been working on, and then select Publish.

How to Use the Feedback Entity

The Feedback entity stores the following information:

·         Feedback title

·         Feedback comments

·         Feedback rating. You can also define a range for ratings by specifying a minimum and maximum (numerical) value for ratings. For example, a rating of 4 on the scale of 1-5.

·         Normalized rating for feedback that is automatically calculated to show the specified user rating scaled to a value between 0 and 1 based on the minimum and maximum rating values.

·         Feedback status such as Open or Closed

·         Feedback source to display the source from where the feedback was submitted. If the feedback was created from within Customer Engagement (on-premises), the value is set to Internal. Developers can add a value of their choice depending on the application used to provide feedback.

·         User who created or last modified the feedback record

·         Entity record that the feedback is associated with


The feedback entity will be useful for organizations who want to provide feedback on specific entities and entity records, it allows them to collect feedback from contacts and display the data in a neat fashion.

Friday, March 13, 2020

Deactivate views in dynamics 365 using power-shell scripts

Install-Module -Name Microsoft.Xrm.Data.Powershell -RequiredVersion 2.8.7 -Scope CurrentUser
#$crmOrg = Get-CrmConnection –InteractiveMode
$password = ConvertTo-SecureString "***" -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential("username@domain.com",$password)
$crmOrg = Get-CrmConnection -Credential $credentials -DeploymentRegion NorthAmerica -OnlineType Office365 -OrganizationName "dev-dev"  -MaxCrmConnectionTimeOutMinutes 5

Write-Host '------------------------------------------------------------'
Write-Host 'Information: Connection established successfully....'
Write-Information -MessageData "Connection established successfully...." -Tags success
$viewNames = @( "Accounts being Followed",
"Accounts I Follow",
"Won Project Service Opportunities"
);

$viewSb = [System.Text.StringBuilder]::new()

for($i = 0; $i -lt $viewNames.length; $i++)
{
 $viewSb = $viewSb.Append("<condition attribute='name' operator='eq' value='" + $viewNames[$i] +"'/>" )
}

$viewFetchXml = "<fetch>
     <entity name='savedquery'>
                       <attribute name='statecode' />
                        <attribute name='name' />
                        <attribute name='returnedtypecode' />
                        <attribute name='statuscode' />
                        <attribute name='isdefault' />
                        <filter type='or'>"+ $viewSb.ToString() +
      "</filter>       
       <order attribute='name' />
                    </entity>
                </fetch>";
      
$viewsToDeactivate = Get-CrmRecordsByFetch -Fetch $viewFetchXml -conn $crmOrg

$success = 0
$failure = 0

foreach($viewRecord in $viewsToDeactivate['CrmRecords'])
{
   Set-CrmRecordState -CrmRecord $viewRecord -StateCode 1 -StatusCode 2 -conn $crmOrg -ErrorAction SilentlyContinue -ErrorVariable viewError
   if($viewError){
   Write-Warning -Message "$($viewRecord.name) - Deactivation Failed"
   Write-Information -MessageData "$($viewRecord.name) ' - Deactivation Failed" -Tags success
   #Write-Error -Message $viewError[0].Exception.Message
   $failure++
   }
   else {
   Write-Information -MessageData "$($viewRecord.name) ' - Deactivated Successfully" -Tags failure
   Write-Host 'Information: '$viewRecord.name ' - Deactivated Successfully'
   $success++
   }
}

Write-Host '------------------------------------------------------------'
Write-Host 'Information: Total count of views to be Deactivated - ' $viewsToDeactivate.Count
Write-Information -MessageData "Total count of views to be Deactivated - &($viewsToDeactivate.Count)" -Tags success
Write-Host 'Information: Total count of views Deactivated Successfully- ' $success
Write-Information -MessageData "Total count of views Deactivated Successfully - $($success)" -Tags success
Write-Host 'Information: Total count of views failed to Deactivate - ' $failure
Write-Information -MessageData "Total count of views failed to Deactivate - $($failure)" -Tags failure
Write-Host '------------------------------------------------------------'

Autoumber attribute in Dynamics 365 using power-shell scripts

# This PS snippet is to create or update autonumber attribute.
# The User  Name, Password and the Organization Name are to be updated before executing this.

Install-Module -Name Microsoft.Xrm.Data.Powershell -RequiredVersion 2.8.7 -Scope CurrentUser
$password = ConvertTo-SecureString "***" -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential("username@domain.com",$password)
$crmOrg = Get-CrmConnection -Credential $credentials -DeploymentRegion NorthAmerica -OnlineType Office365 -OrganizationName "dev-dev"  -MaxCrmConnectionTimeOutMinutes 5

function CreateUpdateAutonumberAttribute {
    param ( 
        [Parameter(Position=0, Mandatory=$true)][string]$AttributeName,
        [Parameter(Position=1, Mandatory=$true)][string]$AttributeDisplayname,
        [Parameter(Position=2, Mandatory=$true)][string]$EntitySchemaname,
        [Parameter(Position=3, Mandatory=$true)][string]$AutoNumberFormat,
[Parameter(Position=4, Mandatory=$true)][string]$Message
    )

 try{
  Write-Output "Processing autonumber attribute $AttributeName ..."
  $autonumattribute = New-Object Microsoft.Xrm.Sdk.Metadata.StringAttributeMetadata
  $autonumattribute.SchemaName = $AttributeName
  $autonumattribute.LogicalName = $AttributeName
  $autonumattribute.DisplayName = New-Object Microsoft.Xrm.Sdk.Label($attributedisplayname,1033)
  $autonumattribute.Format = [Microsoft.Xrm.Sdk.Metadata.StringFormat]::Text
  $autonumattribute.MaxLength = 100
  $autonumattribute.AutoNumberFormat = $AutoNumberFormat 

  if($message -eq 'create'){
  $request = New-Object Microsoft.Xrm.Sdk.Messages.CreateAttributeRequest
  }
  elseif($message  -eq 'update'){
  $request = New-Object Microsoft.Xrm.Sdk.Messages.UpdateAttributeRequest
  }
  
  $request.Attribute = $autonumattribute
  $request.EntityName = $EntitySchemaname
  $response = $crmOrg.Execute($request)   
  Write-Output "... autonumber attribute $AttributeName Processed."  

  return $response
 }
 catch { 
   Write-Warning -Message $_.Exception.Message
 }
}

CreateUpdateAutonumberAttribute -AttributeName 'accountnumber' -AttributeDisplayname 'Account Number' -EntitySchemaname 'account' -AutoNumberFormat '{DATETIMEUTC:yyyyMMddhhmmss}' -Message 'update'

Deactivate forms in Dynamics 365 using power-shell

Install-Module -Name Microsoft.Xrm.Data.Powershell -RequiredVersion 2.8.7 -Scope CurrentUser
#$crmOrg = Get-CrmConnection –InteractiveMode
$password = ConvertTo-SecureString "***" -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential("username@domain.com",$password)
$crmOrg = Get-CrmConnection -Credential $credentials -DeploymentRegion NorthAmerica -OnlineType Office365 -OrganizationName "dev-dev" -MaxCrmConnectionTimeOutMinutes 5

Write-Host '------------------------------------------------------------'
Write-Host 'Information: Connection established successfully....'
Write-Information -MessageData "Connection established successfully...." -Tags success
$formNames = @( "Account for interactive experience","Contacts Form"
);

$formsSb = [System.Text.StringBuilder]::new()
for($i = 0; $i -lt $formNames.length; $i++)
{
$formsSb = $formsSb.Append("<condition attribute='name' operator='eq' value='" + $formNames[$i] +"'/>" )
}

$formFetchXml = "<fetch>
<entity name='systemform'>
<attribute name='name' />
<attribute name='isdefault' />
<attribute name='formactivationstate' /> 
<filter type='or'>"+ $formsSb.ToString() +
"</filter>
<order attribute='name' />
</entity>
</fetch>";

$formsToDeactivate = Get-CrmRecordsByFetch -Fetch $formFetchXml -conn $crmOrg
$success = 0
$failure = 0

foreach($formRecord in $formsToDeactivate['CrmRecords'])
{
Set-CrmRecord -EntityLogicalName systemform -Fields @{"formactivationstate"=New-CrmOptionSetValue(0) } -Id $formRecord.formid -conn $crmOrg -PrimaryKeyField formid -ErrorAction SilentlyContinue -ErrorVariable formError
if($formError){
Write-Warning -Message "$($formRecord.name) - Deactivation Failed"
Write-Information -MessageData "$($formRecord.name) ' - Deactivation Failed" -Tags failure
#Write-Error -Message $formError[0].Exception.Message
$failure++
}
else {
Write-Information -MessageData "$($formRecord.name) ' - Deactivated Successfully" -Tags success
Write-Host 'Information: '$formRecord.name ' - Deactivated Successfully'
$success++
}
}

Write-Host '------------------------------------------------------------'
Write-Host 'Information: Total count of forms to be Deactivated - ' $formsToDeactivate.Count
Write-Information -MessageData "Total count of forms to be Deactivated - &($viewsToDeactivate.Count)" -Tags success
Write-Host 'Information: Total count of forms Deactivated Successfully- ' $success
Write-Information -MessageData "Total count of forms Deactivated Successfully - $($success)" -Tags success
Write-Host 'Information: Total count of forms failed to Deactivate - ' $failure
Write-Information -MessageData "Total count of forms failed to Deactivate - $($failure)" -Tags failure
Write-Host '------------------------------------------------------------'

Saturday, September 16, 2017

Show/Hide business process in Dynamics 365


Use getVisible method to retrieve whether the business process control is visible
Xrm.Page.ui.process.getVisible();

Use setVisible method to show or hide the business process flow control.
Xrm.Page.ui.process.setVisible();


// isVisible parameter should be true or false based on your requirement.
function showHideBP(isVisible){   
        Xrm.Page.ui.process.setVisible(isVisible);   
}


Dynamics 365 Javascript Best Practices

Avoid using unsupported methods
As a general rule, any function which is available through Xrm.Page should be considered as supported and rest are unsupported, if you are navigating the HTML DOM structure and using window.getElementById or even manipulating the innerHTML or outerHTML,These methods may work but because they are not supported you can’t expect that they will continue to work in future versions or update rollups of CRM.

Do not use jQuery for form script or commands
Microsoft do not recommend or support using jQuery for any pages within the application. This includes form scripts and ribbon commands

Recognize limitations for content delivery network (CDN) libraries
Content delivery network (CDN) JavaScript libraries provide many advantages for public websites. Because these libraries are hosted on the Internet, you do not need to create web resources that contain the content of the libraries. For Microsoft Dynamics 365 you should consider the following issues before you use a CDN JavaScript library.
  • Users of the Microsoft Dynamics 365 for Microsoft Office Outlook with Offline Access client have the capability to work with no Internet connection while working offline. If you are depending on an Internet connection for your JavaScript libraries, your code will fail.
  • Some organizations will restrict Internet access for employees. Unless they configure the network to allow access to the CDN library sites, your code may fail for those organizations.


Do not access the DOM
The Microsoft Dynamics CRM development team reserves the right to change how pages are composed, including the ID values for elements, so using the Xrm.Page object model protects your code from changes in how pages are implemented.

Use asynchronous data access methods
When you access data by using the Microsoft Dynamics 365 web services, always use an XmlHttpRequest that is configured to execute asynchronously. The reason is that the browser operates on a single thread. If that thread is being used to execute a long-running process synchronously the browser will stop responding.

Keep your libraries as small as possible
JavaScript libraries are stored in web resources and requested by the form each time a form is loaded.  This means that each time the form for a record is opened every JavaScript library tied to that form is downloaded from CRM’s web resource table.  Keep your libraries as simple and as clean as possible. This will increase performance and improve maintainability.

Keep Common utility file to address the common functions
Keep utility file that can be used to write down all the utility functions that would be used across all entities and custom HTML web resources. A classic example would be something like – getUserRoles(). So instead of this you can do something like this.

Create a webresource named myOrg.Utilities.js then you can define the methods in the file like below.

if (typeof (myOrg) == "undefined") {
   myOrg = { __namespace: true };
}
myOrg.Utilities = {
    _getUserInfo: function(){},
   //Other common functions
}

You can then include the webresource wherever you need. To call the method of the file, you would need to use the fully qualified name as
Var isAdmin = myOrg.Utilities._getUserInfo();

Keep separate scripts for forms and ribbon functions for each entity
For example take account entity as an example keep two sets of scripts for easy identifying one for form events and ribbon events.
Account.Form.js – This can be the script for all your form on-load, on-save and on-change events that you register for the form. So if there is any error you are getting during on save, on load and onchange of the form, you can be rest assured that the origin you can trace from this file.
Account.Ribbon.js – This can be very useful. If you place all the custom ribbon button event handler in this file, you no longer need to check the ribbon diff xml to find out which file the ribbon event handler is located in.
for opportunity.form.js

if (typeof (myOrg) == "undefined") {
   myOrg = { __namespace: true };
}
if (typeof (myOrg.Opportunity) == "undefined") {
   myOrg.Opportunity = { __namespace: true };
}
myOrg.Opportunity.Form= {
   _Load: function(){},
   _Save: function(){}
}

Namespace your library names
Associate each of your functions with a JavaScript object to create a kind of namespace to use when you call your functions, as shown in the following example.
//If the MyUniqueName namespace object isn’t defined, create it.
if (typeof (MyUniqueName) == "undefined")
 { MyUniqueName = {}; }

  // Create Namespace container for functions in this library;
  MyUniqueName.MyFunctions = {
   performMyAction: function(){
   // Code to perform your action.
   //Call another function in your library
   this.anotherAction();
  },
  anotherAction: function(){
   // Code in another function
  }
};

Then when you use your function you can specify the full name. The following example shows this.
MyUniqueName.MyFunctions.performMyAction();



Set form notifications in dynamics 365

You can display any number of notifications using “setFormNotification” and they will be displayed until they are removed using clearFormNotification. The height of the notification area is limited so each new message will be added to the top. Users can scroll down to view older messages that have not yet been removed.

Syntax
To clear the notifications on the form.
Xrm.Page.ui.clearFormNotification(uniqueId);
To set  the notifications on the form.
Xrm.Page.ui.setFormNotification(message, level, uniqueId);
To set the notifications on the form for a particular field.
Xrm.Page.getControl(fieldname).setNotification(message, uniqueId);
--------------------------------------------------------------------------------------

To set the notifications on the form.
function setFormNotifications() {
    Xrm.Page.ui.setFormNotification("This is an INFORMATION notification.", "INFO","1");
    Xrm.Page.ui.setFormNotification("This is a WARNING notification. ", "WARNING" , "2");
    Xrm.Page.ui.setFormNotification("This is an ERROR notification. ", "ERROR", "3");
    Xrm.Page.getControl("name").setNotification("Message to display", "101")
}

To clear the notifications from field and form.
function clearFormNotifications() {
    // Cleards notifications from the field "name" with unique identifier 101
    Xrm.Page.getControl("name").clearNotification("101");
    // Clears notifications from the form for unique id 1
    Xrm.Page.ui.clearFormNotification("1");
}

Dynamics 365 JavaScript functions

Get the value from a CRM field
 var varMyValue = Xrm.Page.getAttribute(“ fieldName”).getValue() ;

Set the value of a CRM field
Xrm.Page.getAttribute(“ fieldName”).setValue(‘My New Value’);

Hide/Show a tab/section
Xrm.Page.ui.tabs.get(5).setVisible(false);
Xrm.Page.ui.tabs.get(5).setVisible(true);

Hide/Show field
Xrm.Page.ui.controls.get(fieldName).setVisible(false); Xrm.Page.ui.controls.get(fieldName).setVisible(true);

Call the onchange event of a field
 Xrm.Page.getAttribute(“ fieldName”).fireOnChange();

Get the selected value of picklist
Xrm.Page.getAttribute(“ fieldName”).getSelectedOption().text;

Set the requirement level
Xrm.Page.getAttribute(“ fieldName”).setRequiredLevel(“none”);
Xrm.Page.getAttribute(“ fieldName”).setRequiredLevel(“required”);
Xrm.Page.getAttribute(“ fieldName”).setRequiredLevel(“recommended”);

Update a readonly field
Xrm.Page.getAttribute("fieldName").setSubmitMode("always");

Set the focus to a field
Xrm.Page.getControl(“ fieldName”).setFocus(true);

Disable/Enable field
 Xrm.Page.getControl(fieldName).setDisabled(true); Xrm.Page.getControl(fieldName).setDisabled(false);

Stop an on save event
event.returnValue = false;

Return array of strings of users security role GUIDs
Var arrUserRoles = Xrm.Page.context.getUserRoles();

Get record GUID
var recordId = Xrm.Page.data.entity.getId();
var recordIdWithoutCurlyBraces = Xrm.Page.data.entity.getId().substring(1,37);

Get Organization unique name
var OrganizationUniqueName = Xrm.Page.context.getOrgUniqueName();

Gets current user Guid
var userGUID = Xrm.Page.context.getUserId();

Returns the base URL that was used to access the application.
var clientURL = Xrm.Page.context.getClientUrl();

Returns a string current Microsoft Office Outlook theme chosen by the user.
var currentTheme = Xrm.Page.context.getCurrentTheme();

Returns whether Autosave is enabled for the organization.
var isAutoSaveEnnabled = Xrm.Page.context.getIsAutoSaveEnabled();


Returns the Dynamics 365 Language Pack that is user's preferred language.
var userLCID = Xrm.Page.context.getUserLcid();

Returns the name of the current user
var userLCID = Xrm.Page.context.getUserName();

Returns the version number of the Dynamics 365 server
var currentVersion = Xrm.Page.context.getVersion();

Method to close the form.
Xrm.Page.ui.close();

Method to get the form type of the record.
Xrm.Page.ui.getFormType();

Method to refresh the ribbon.

Xrm.Page.ui.refreshRibbon();

Returns the difference between the local time and Coordinated Universal Time (UTC).
var timeDifference = Xrm.Page.context.getTimeZoneOffsetMinutes();
 
Set Form Notifications in Dynamics 365

Convert subgrid to Comments