Loading entity form dynamically from JavaScript in CRM Portal

This has been tested on xRM Portal CE only.

There may be times where you need to load and display an entity form by JavaScript in CRM Portal. For example, you may have a content page with a link that when clicked, should display an entity form in a modal dialog. You want the modal dialog to display the entity form without any of the site’s branding, navigation, footer, etc.

You can achieve this by leveraging an OOTB service that renders the entity form. This is the same service that is used by the OOTB entity lists and sub-grids.

The service…

The URL for this service is:

[rootWebSite]/_portal/modal-form-template-path/[portalId]?id=[recordId]&entityformid=[entityFormId]

  • portalId: The service seems to be happy with any GUID. We used an empty GUID (00000000-0000-0000-0000-000000000000) and it was fine.
  • recordId: Guid of the record you want to display
  • entityFormId: Guid of the entity form record you want to use

This service returns an HTML document containing the specified entity form loaded with the specified record. The site’s branding, navigation, footer, etc. are excluded, which means the HTML document is ready to be used in a modal dialog via an iframe.

The modal dialog…

CRM Portal uses Bootstrap, so we will use Bootstrap’s modal to load an iframe that invokes the service above and displays the entity form. Again, this is the same approach used by the OOTB entity lists and sub-grids.

Below is the HTML that should be added to the page. The first part (the div) is the normal page content. The second part (the section) is the modal dialog that stays hidden until the user clicks the link in our content. The code for this second part was adapted from the modal element used by the OOTB entity list. Please review the inline comments below.

<!--This is our main content-->

<div>
	Hello world. <a href="javascript:loadEntityFormAsModal();">Click me!</a>
</div>


<!--Code adapted from modal element of entity list. The ID of the section element is used in the JavaScript below.-->

<section id="myModalDialog" aria-hidden="true" aria-label="<span class='fa fa-info-circle' aria-hidden='true'></span> My Modal Title" class="modal fade modal-form modal-form-details" data-backdrop="static" role="dialog" tabindex="-1" style="display: none;">
	<div class="modal-lg modal-dialog">
		<div class="modal-content">
			<div class="modal-header">
				<button aria-label="Close" class="close" data-dismiss="modal" tabindex="0" title="Close" type="button">
					<span aria-hidden="true">×</span><span class="sr-only">Close</span>
				</button>
				<h1 class="modal-title h4" title="My Modal Title">My Modal Title</h1>
			</div>
			<div class="modal-body">
				<div class="form-loading" style="display: none;">
					<span class="fa fa-spinner fa-spin fa-4x" aria-hidden="true"></span>
				</div>

				<iframe src=""></iframe>
			</div>
		</div>
	</div>
</section>

Next we need to add the below JavaScript to the page. If embedded on the same page, then should be wrapped in <script> tags. This script was adapted from the OOTB entity-grid.js script. Please review the inline comments below.

function loadEntityFormAsModal() {
	//Code adapted from entityGrid.prototype.addDetailsActionLinkClickEventHandlers in \js\entity-grid.js.

	//Setup the record and entity form you want to display
	var portalId = "00000000-0000-0000-0000-000000000000";
	var recordId = "B3FEC1EB-7F68-E811-859D-B86B23AC9F7C";
	var entityFormId = "396293DD-9D46-E811-8551-B86B23AC9F7C";

	//Form the URL to the service
	var url = "/_portal/modal-form-template-path/" + portalId + "?id=" + recordId + "&entityformid=" + entityFormId;

	//This ID needs to match the ID of the modal section element.
	var $modal = $("#myModalDialog");

	//Retrieve the iframe of the modal and set it load the service
	var $iFrame = $modal.find("iframe");

	$iFrame.attr("src", url);

	//Hook in handler to hide loader image when done loading
	$iFrame.on("load", function () {
		$modal.find(".form-loading").hide();
		$modal.find("iframe").contents().find("#EntityFormControl").show();
	});

	//Show the loader image at start
	$modal.find(".form-loading").show();
	$modal.find("iframe").contents().find("#EntityFormControl").hide();

	$modal.on('show.bs.modal', function () {
		$modal.attr("aria-hidden", "false");
	});

	$modal.off("hidden.bs.modal.entitygrid").on("hidden.bs.modal.entitygrid", function () {
		$modal.attr("aria-hidden", "true");
	});

	$modal.modal();
}

That’s it!

And there you have it, you can now dynamically launch an entity form in JavaScript. Leveraging the OOTB service means that any entity permissions you may have setup will be honoured, and any JavaScript attached to the entity form itself will also be executed.

By the way…

Do a lot of HTLM, JavaScript, CSS and Liquid coding in CRM Portal? You should check out my Visual Studio extension CRMQuickDeploy. This allows you to work with HTML, JavaScript, CSS and Liquid in Visual Studio and quickly deploy them to CRM Portal. Not only this improves your productivity, but also allows you to source control these artefacts and facilitates scenarios where multiple developers work on the same code base.

Advertisement
Posted in CRM, CRM Portal | 8 Comments

Get current entity logical name and record ID in CRM Portal forms using JavaScript

Use the following JavaScript to retrieve the current entity logical name and record ID in CRM Portal forms:

For Entity Form:

Entity logical name: $(“#EntityFormControl_EntityFormView_EntityName”).val()

Record ID: $(“#EntityFormControl_EntityFormView_EntityID”).val()

For Web Form Step:

Entity logical name: $(“#EntityFormView_EntityName”).val()

Record ID: $(“#EntityFormView_EntityID”).val()

Tested on xRM Portal CE.

 

UPDATE 10/01/2019:

The HTML structure is now different for Portal version 9.0.12.12 (cloud). There is now a GUID in the HTML field ID, e.g. EntityFormControl_978076ec8cf7e811a965000d3ae13a46_EntityFormView_EntityID.

For Entity Form, use the following:

  • Entity logical name: $(“input[id^=’EntityFormControl_’][id$=’_EntityFormView_EntityName’]”).val()
  • Record ID: $(“input[id^=’EntityFormControl_’][id$=’_EntityFormView_EntityID’]”).val()
  • Record state: $(“input[id^=’EntityFormControl_’][id$=’_EntityFormView_EntityState’]”).val()
  • Record status: $(“input[id^=’EntityFormControl_’][id$=’_EntityFormView_EntityStatus’]”).val()
Posted in CRM, CRM Portal | 1 Comment

Leveraging built-in services in CRM Portal

On a recent project we decided to build a custom entity list using jQuery DataTables as the OOTB entity list did not meet our requirements. As part of this we needed to build a delete list item functionality that should work similarly to the OOTB counterpart. To achieve this we needed a service that we can call from JavaScript to delete a given record (i.e. a list item).

After some investigation, we discovered that there are several built-in services in CRM Portal that we can invoke using JavaScript. These are the same services used by the OOTB components. Using these services means that we did not have to develop/deploy a custom service to support our custom UI component. It also means that the entity permissions we have configured in CRM are automatically honoured, which is a huge bonus.

In this post I will write about these built-in services, in particular the ‘delete list item’ service, and how you can call them from your JavaScript.

Important: this investigation was done against xRM Portals Community Edition (i.e. the on-prem version of CRM Portal). The findings may or may not apply to CRM Portal on cloud that is hosted by Microsoft. References to source code refer to the source code of XRM Portals Community Edition, which can be downloaded from https://github.com/Adoxio/xRM-Portals-Community-Edition).

The built-in ‘delete list item’ service

This service lives at [portalUrl]/_services/entity-grid-delete/[recordId to delete]. You can invoke this service by performing a POST to this URL. The service accepts an EntityReference parameter to identify the record to delete, and it expects to find this in the POST’s body as JSON, e.g.:

{
"Id":"[guid]",
"LogicalName":"new_myentity"
}

This particular service route maps to the Delete method of the class Site.Areas.Portal.Controllers.EntityGridController, which is defined in the file \Areas\Portal\Controllers\EntityGridController.cs of the Portal project.

A side note: other built-in services

The route mapping of the ‘delete list item’ service is defined in the RegisterArea method of the Site.Areas.Portal.PortalAreaRegistration class, which is defined in the file \Areas\Portal\PortalAreaRegistration.cs of the Portal project.

Reviewing this file quickly shows that there are actually many more built-in services in CRM Portal that you should be able to invoke from JavaScript. Below is a screenshot to illustrate:

Invoking the ‘delete list item’ service from JavaScript

There is a slight complication in invoking this service from JS, and that is it has anti-forgery enabled. This means that our requests to the service must contain a __RequestVerificationToken header with a valid token value.

Fortunately CRM Portal has a built-in JavaScript object that helps us with this. Every page in the Portal automatically includes a reference to the JS file \js\antiforgerytoken.js. This JS defines a global object called shell, which has a method called ajaxSafePost.

ajaxSafePost is essentially a wrapper around jQuery’s ajax method, and is responsible for ensuring the request contains a valid __RequestVerificationToken header before sending it off to the server. This method returns a promise object as it may relies on an async request to retrieve the token (more details later).

Below is an example of how you could use the ajaxSafePost method to invoke the ‘delete list item’ service.

var recordId = "4ca2e5d1-d0fe-4700-b383-59cee9890392";

var entityReference = {};
entityReference.LogicalName = "new_myentity";
entityReference.Id = "4ca2e5d1-d0fe-4700-b383-59cee9890392";

var ajaxPromise = shell.ajaxSafePost({
	type: "POST",
	contentType: "application/json",
	url: "/_services/entity-grid-delete/" + recordId,
	data: JSON.stringify(entityReference)
});

ajaxPromise.done(function () {
	alert("Record deleted!");
});

ajaxPromise.fail(function () {
	alert("Something has gone wrong...");
});

How does ajaxSafePost retrieve the token?

This method first attempts to retrieve the token from an input field on the page. If one is not found, it makes an async request to the URL [portalUrl]/_layout/tokenhtml to request a token.

So… is this supported?

Strictly speaking, since it’s not documented (as far as I’m aware), I’d say this is unsupported. Since the service is fairly loosely coupled to our code however, switching over to a custom service when the need arises should not incur much overhead. You however need to make good judgement based on your own scenario.

So there you have it…

The built-in services are hidden gems that could really streamline your customisation efforts. There are built-in support facilities that make invoking them relatively easy. You however need to consider your scenario carefully as these are not documented, and therefore may change without notice.

I have not had a chance to test whether they also work on CRM Portal online. That is a to-do for the near future.

 

Posted in Adxstudio, CRM, CRM Portal | 4 Comments

Changing Quick Find to perform ‘contains’ search in CRM

OOTB CRM Quick Find automatically appends a wildcard character (*) to the end of your query, and therefore effectively performs a ‘begins with’ search. If you searched for ‘Canberra’ for example, you’d find ‘Canberra Hospital’, ‘Canberra College’, etc., but you won’t find ‘The Awesome Pub of Canberra’.

To perform a ‘contains’ search, you would need to explicitly include the wildcard character at the start of your query, e.g. ‘*Canberra’. I suspect it was done this way for performance reasons. This however may not be the best user experience in some scenarios.

I have added a new feature to the Enhanced Quick Find solution that allows you to automatically add the wildcard character to the start of users’ Quick Find queries. This turns the query to ‘*Canberra*’ for example, and effectively becomes a ‘contains’ search.

What is this Enhanced Quick Find solution?

This is a CRM solution that I have previously developed that allows you to perform Advanced Find-like queries using text in Quick Find. Essentially it allows you to configure an XML that defines how the query should be interpreted and transformed prior to fetching the results. You can read more about this solution here.

So how do I enable this ‘contains’ search thing for Quick Find?

A new attribute, namely prependWildcardToQueryText, has been added to the QueryTransformation element in the configuration schema. Set this attribute to true to have the wildcard character appended to all Quick Find queries for the target entity. Below is a simple example:

<QuickFindConfiguration>
	<QueryTransformation prependWildcardToQueryText='true'/>
</QuickFindConfiguration>

Where will this work?

This will work in Quick Find for all the entities that you have created a Quick Find Configuration entity for. This will also work when searching within the lookup field/dialog for those entities. This however does not work with the multi-entity search.

Is there any performance impact?

The solution intercepts and transforms search queries. This in its own should have minimal or no performance impact. Depending on the size of your data, and the number of Quick Find columns you have configured for a given entity however, ‘contains’ searches may have some performance impacts comparing to ‘begins with’ searches. You therefore should consider your scenario and deploy this feature selectively.

Download

You can download the solution ZIP here:

Posted in CRM | Leave a comment

Querying many-to-many relationship in CRM Portal using JavaScript

How do you programmatically query an N:N relationship in CRM Portal using JavaScript without developing additional services?

You can do this with FetchXml and Liquid, and a small hack. Here is a good walkthrough of using FetchXml in Liquid to provide a data service in CRM Portal. I will explain how to adapt this to work for N:N relationships and the hack that would be required.

One thing you should understand is that when you create an N:N relationship in CRM, a “Relationship Entity” is also created behind the scene. This relationship entity is the “joining” entity that you would typically find in a normalised database. You can specify the name of this relationship entity when creating the N:N relationship in CRM as shown below.

To query an N:N relationship in CRM Portal, we will use FetchXml/Liquid to query this relationship entity instead of the actual entities on either end of the relationship.

For example, say I have a Degree and a Subject entities with an N:N relationship, and I want to query all the Subjects for a given Degree (retrieving Subject Name and Subject Code). First thing first, the FetchXml would look as follow (bnh_degree_bnh_subject is the name of my relationship entity):


  
    
      
    
    
      
      
    
  

If you use this FetchXml in Liquid as described by the link earlier on in the post however, you will not get any result. This is because FetchXml in Liquid (as of v8) uses Entity Permission. You need to grant Entity Permission for the relationship entity. (I would guess that Entity Permission for Degree and Subject would also be required – although I have not tested it without).

Here is where the small hack comes in. OOTB you cannot create Entity Permission for a relationship entity. To workaround this:

  1. Create an Entity Permission for a random entity. Set the Scope to Global and grant it the Read privilege.
  2. Add appropriate Web Roles to it.
  3. Create a workflow to update the field adx_entitylogicalname of this Entity Permission record to the name of the relationship entity (bnh_degree_bnh_subject in this example).
  4. UPDATE 02/01/2018: Unfortunately you cannot use a workflow to update the field above. This is because the field is read-only on the form, and therefore the workflow editor does not allow you to set a value for it. Some options to workaround this:
    1. Write a simple C# script
    2. Use a Chrome extension to effectively hack the form and edit the field. Below are some options I would recommend:
      1. Dynamics CRM Power Pane
      2. Level Up

That’s it! Your FetchXml/Liquid should now return results!

Is this hack supported?

Strictly speaking, probably not.

Is it likely to stop working in future updates to CRM and CRM Portal?

I’d say no, but you need to make your own judgement.

So in conclusion…

You can query an N:N relationship in CRM Portal using JavaScript without developing additional services. You just need to query the relationship entity, and apply a small hack to grant the required permission.

 

Posted in Adxstudio, CRM, CRM Portal | 1 Comment

Reduce code duplication between Insert/Edit forms in CRM Portal with CRMQuickDeploy

CRM Portal is an interesting product, in a good way. One thing it doesn’t do very well however, is that a single Entity Form (or Web Form) cannot be used to handle both inserting and editing records for an entity.

This means you’d need a separate form to handle insert, and a separate form to handle edit – even if the two functionalities should work exactly the same way. This creates an issue as you’d need to duplicate any configuration and code associated with this form.

For a while now CRMQuickDeploy has been allowing you to work with code (JS, CSS, HTML, Liquid) for Portal artefacts in Visual Studio. A new feature has now been added to version 3.3 of this tool to help address the code duplication issue in this particular scenario.

New “link item” in CRMQuickDeploy 3.3

As a quick overview, you can now create a “link item” in your Visual Studio project that points to another item. When this “link item” is deployed, the content of the referenced item is used to update the Portal artefact in CRM.

Sound like “Add as Link” that comes OOTB with Visual Studio? Well, yes.. it is pretty much the same idea. “Add as Link” however does not work within the same project OOTB.

Creating “link item” and referencing source item

You can create a “link item” by creating a normal item for a Portal artefact like you normally would, and then append the “.link” extension to it. You specify the source item within the content of the link item using the INI file format. The INI key is sourceItemPath, and the value is the project-relative path to the source item.

So for example, a link item that holds the JavaScript for an Entity Form could be:

Form1 – Create.js.link

To specify its source content (in this case Form1 – Update.js), specify the following in the content of the link item:

sourceItemPath=PortalEntityForms\Form1 – Update.js

A more detailed example

Let say you have two Entity Forms to create and update Application with the following names in CRM:

  • Application Form – Create
  • Application Form – Update

Because OOTB a Web Page can only host one Entity Form, you also have two Web Pages with the following names in CRM:

  • Application Page – Create
  • Application Page – Update

Imagine you have some JavaScript attached to the Entity Form, and also some HTML/Liquid attached to the Web Page. Because the two functionalities (Create and Update Application) should function the same way, you’d need to duplicate the JavaScript and HTML/Liquid across these Entity Forms and Web Pages.

Using link items, your Visual Studio project would look like this:

Application Form – Create.js would contain the actual JavaScript to be deployed as normal.

Application Form – Update.js.link would contain:

sourceItemPath=PortalEntityForms\Application Form – Create.js

When a deployment is triggered on Application Form – Update.js.link, the target artefact will be deduced from the file name of the link item, in this case Application Form – Update. The content however will be drawn from the referenced source item, i.e. Application Form – Create.js.

Likewise, Application Page – Create.html would contain the actual HTML/Liquid to be deployed for the Web Pages.

Application Page – Update.js.link would contain:

sourceItemPath=PortalWebPages\Application Page – Create.html

Which artefact types are supported with link item?

The following artefact types are supported:

  • Web Page (HTML/Liquid, CSS, JS)
  • Entity Form (JS)
  • Web Form Step (JS)

Download CRMQuickDeploy

You can download CRMQuickDeploy from the Visual Studio Marketplace.

 

Posted in Adxstudio, CRM, CRM Portal, CRMQuickDeploy | Leave a comment

Fixing issues with migrating Adxstudio/CRM Portal v8 using Configuration Migration tool

The Configuration Migration tool does a pretty good job at migrating Adxstudio/CRM Portal configuration in v7. There are some changes in Portal v8 however that are causing issues with this tool. This is true at least in the case for OnPrem (Community Edition). For Online, I have not tested but suspect that the behaviour would be the same.

Issue #1 – Value of Partial URL field of the Home Page of your website can only be /. Any other value will lead to issues in your Portal.

The import log produces the above error when processing the adx_webpage entity. In Portal v8 there is a new validation that checks the Partial URL of web pages that the code determines to be a homepage. The code determines a web page as a homepage when it does not have a value in the Parent Page field.

As the Parent Page field is a reference field to the same entity (i.e. adx_webpage), I suspect the Configuration Migration tool first saves the record without a value for this field, and then fixes it up in a 2nd pass. Creating the web page without a Parent Page however causes the validation to see it as a homepage, and hence the Partial URL fails the validation.

This validation occurs in a plugin. To workaround this issue we need to disable the following plugin steps prior to running the import:

  1. Plugin assembly: Adxstudio.Xrm.Plugins
    • Plugin: Adxstudio.Xrm.Plugins.MultiLanguage.WebPageValidationPlugin
      1. Step: Adxstudio.Xrm.Plugins.MultiLanguage.WebPageValidationPlugin: Update of adx_webpage
      2. Step: Adxstudio.Xrm.Plugins.MultiLanguage.WebPageValidationPlugin: Create of adx_webpage

Issue #2 – Unexpected exception from plug-in (Execute): Adxstudio.Xrm.Plugins.MultiLanguage.WebPageUpdatePlugin: System.NullReferenceException: Object reference not set to an instance of an object

The “Object reference not set to an instance of an object” is probably the bane of all developers as it is often quite hard to pinpoint the root cause without debugging. Unfortunately this is the case in this scenario, and I don’t quite know the root cause of this issue. You can however workaround it by disabling the following plugin step prior to running the import:

  1. Plugin assembly: Adxstudio.Xrm.Plugins
    • Plugin: Adxstudio.Xrm.Plugins.MultiLanguage.WebPageUpdatePlugin
      1. Step: Adxstudio.Xrm.Plugins.MultiLanguage.WebPageUpdatePlugin: Update of adx_webpage

Issue #3 – Duplicate web pages

One of the changes in Portal v8 is the introduction of localised web pages. When you create a web page (Portal refers to this as a Root page), a separate localised web page is automatically created for the corresponding root page and is linked to it. So if you create a web page called Dashboard for example, a second web page would be automatically created for you. This page, also named Dashboard by default, would hold the localised content of the Dashboard page you explicitly created.

The localised page is created by a plugin, which means that you’d end up with duplicate web pages when importing data via the Configuration Migration tool. This is because the migration tool would import both the root page and the localised page, and the plugin would trigger on the creation of the root page, which in turns would create a duplicate localised page.

To workaround this issue we need to disable the following plugin step prior to running the import:

  1. Plugin assembly: Adxstudio.Xrm.Plugins
    • Plugin: Adxstudio.Xrm.Plugins.MultiLanguage.WebPagePlugin
      1. Step: Adxstudio.Xrm.Plugins.MultiLanguage.WebPagePlugin: Create of adx_webpage

(Potential) Issue #4

OK, this is not one that I have actually encountered, but one that I just thought about while describing issue #3 above.

When the localised web page is automatically created, it has the same name as the root page by default. The Configuration Migration tool however uses the primary name field of an entity to determine uniqueness while importing if no duplicate detection (uniqueness) condition is specified for that entity. I wonder if this could lead to the migration tool applying updates to incorrect records during the import?

In our implementation we always append the language name to the name of localised web pages, e.g. Dashboard is the root page and Dashboard (English) is the localised page. We therefore have not encountered this issue, but it is something that we should check.

 

So in summary…

When migrating configuration for Portal v8 you should disable four plugin steps prior to the import (and remember to enable them again afterward).

I would suggest always append the language name to the name of localised web pages to avoid confusion for yourself, and possibly also for the Configuration Migration tool.

Posted in CRM, CRM Portal | 4 Comments

Auto deploy on save from Visual Studio for Adxstudio Portal artefacts in new version of CRMQuickDeploy

I have released a new version of CRMQuickDeploy with the following improvements:

  • Option to auto deploy on save for Portal artefacts
  • Ability to specify MIME Type and Display Order for Portal web files

Auto deploy on save for Portal artefacts

You can now enable this option to have Visual Studio automatically deploys a Portal artefact file when you save it from the code editor.

This option is disabled by default, but you can enable it by going to Tools \ Options \ CRMQuickDeploy.

Note that this option is only applicable to Portal artefacts.

Ability to specify MIME Type and Display Order for Portal web files

Configuration settings for web files are defined in the DeploymentSettings.xml file. This file now allows you to specify MIME Type and Display Order for web files. Below is an annotated example XML.

<?xml version="1.0" encoding="utf-8" ?>
<DeploymentSettings>

	<!-- Define a collection of items that will be deployed as 
Web Files and associated with a particular website. -->
	<WebFiles>

		<!-- Separately define each local files to be 
deployed as Web Files.

localPath: Required. Relative to the root PortalWebFiles 
folder of the project. Value should not start with '\'. 

targetName: Optional. The name of the web file once deployed.
Default to the same value as partialUrl. 

parentPage: Required. The name of the parent web page in CRM.

partialUrl: Optional. The partial URL of the web file once 
deployed. Default to the file name of the Visual Studio item 
with all spaces removed. 

mimeType: Optional. The MIME type of the file. If not 
specified, the MIME type will be determined based on the 
file extension. 

displayOrder: Optional. The display order of the file. 
Must be an integer. 

The file extension to MIME type mapping is as follow: 

txt: text/plain 
css: txt/css 
js: txt/javascript 
htm, html: text/html 
gif: image/gif 
jpg, jpeg: image/jpeg 
png: image/png 
svg: image/svg+xml 
ico: image/x-icon 
xml: application/xml 
json: application/json 
all other extensions: application/octet-stream -->
		<File localPath="common.js" parentPage="Home" />
		<File localPath="scripts\script1.js" parentPage="Home" />
		<File localPath="styles\styles.css" targetName="Portal Styles" parentPage="Home" partialUrl="styles.css" mimeType="text/css" displayOrder="1"/>

		<!-- Define local files to be deployed as Web files 
using the folder approach. When this approach is used, default 
value will be used for targetName and partialUrl for each item 
under the folder. mimeType for each item will be determined based 
on the file extension, unless the item is specified in the 
folder's MimeTypeOverride child element. A Folder element only 
covers the files that are found directly underneath that folder. 
Separate Folder elements must be defined for each sub-folders. 

localPath: Required. Relative to the root PortalWebFiles folder 
of the project. Value should not start with '\'. To refer to 
files directly underneath the root PortalWebFiles, specify an 
empty string for this attribute, i.e. localPath="". 

parentPage: Required. The name of the parent web page in CRM 
that will be applied to all items under this folder. 

targetNamePrefix: Optional. The specified value will be added 
to the targetName for all items under this folder. -->
		<Folder localPath="scripts" parentPage="Home" />
		<Folder localPath="styles" parentPage="Home" targetNamePrefix="My Portal - ">
			
			<!-- Define MIME type for selected items underneath 
this folder. This element is optional. -->
			<MimeTypeOverride>
			
				<!-- Define MIME type for items underneath 
this folder. The MIME type for an item will be automatically 
determined based on the file extension if the item is not 
defined here. 

name: Required. The filename of the item. Value should not start 
with '\'. 

mimeType: Required. The MIME type for the item. -->
				<File name="styles.css" mimeType="text/css" />
			</MimeTypeOverride>
			
			<!-- Define display order for selected items 
underneath the folder. This element is optional. -->
			<DisplayOrderOverride>
			
				<!-- Define display order for items underneath 
this folder. The display order for an item will be blank if the 
item is not defined here. 

name: Required. The filename of the item. Value should not 
start with '\'. 

displayOrder: Required. Must be an integer. -->
				<File name="styles.css" displayOrder="1" />
			</DisplayOrderOverride>
		</Folder>
	</WebFiles>
</DeploymentSettings>

Download

You can download this extension from the Visual Studio Marketplace.

I hope you find this addition useful!

Posted in Adxstudio, CRM, CRM Portal, CRMQuickDeploy | Leave a comment

Enhancing Quick Find in CRM with configurable custom query language

Unlike Advanced Find, Quick Find in CRM is pretty rudimentary. While you can specify the search columns in the Quick Find View, the Quick Find text query does not allow you to specify field filtering on the target record, or on one or more levels of related records.

Advanced Find is great for performing these types of more complex searches, but it requires multiple clicks and is time consuming to setup. This is not ideal in scenarios where users need to be able to find records quickly and there are a high number of records and enquiries, e.g. in call centres.

Here are some examples of searches that are not possible with Quick Find:

  • Find Accounts where “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields)
  • Find Accounts where Account Name contains “tech”, and “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields), and that Contact has a Case that contains “laptop problem” in Case Title or Description
  • Find Accounts where any associated Contact has a Case regarding the Product “Laptop”
  • Find Accounts where any associated Contact has a Case regarding the Product “Laptop”, and where the Account has an associated Opportunity that contains “laptop” in the Description

I have developed a solution that allows users to perform searches similar to the above using Quick Find. The solution essentially augment Quick Find with a custom query language that allows users to specify filtering in the query text. With exception for syntax, the query language is configurable and can be extended to OOTB and custom entities. In fact, the query syntax is similar to that used by Outlook, Gmail and SharePoint.

For example, the enhanced query allows users to perform the above searches using the following:

Search Enhanced Query Examples (Configurable)
Find Accounts where “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields)  contact:mccloud
Find Accounts where Account Name contains “tech”, and “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields), and that Contact has a Case that contains “laptop problem” in Case Title or Description  *tech contact:mccloud\case:”laptop problem”
Find Accounts where any associated Contact has a Case regarding the Product “Laptop”  contact\case\product:laptop

or shortcut:

case-prd:laptop

Find Accounts where any associated Contact has a Case regarding the Product “Laptop”, and where the Account has an associated Opportunity that contains “laptop” in the Description case-prd:laptop opp:laptop

So how does it work?

After installing the solution (download at the end of this post), the Quick Find Configurations entity is available to system administrators under Settings.

Create a Quick Find Configuration record for each entity where you want to enable the enhanced Quick Find.

The following fields are required:

Field Description
Entity Name The CRM name of the target entity. Type-ahead is enabled to help you locate the correct entity.
Configuration Name The name of the Quick Find Configuration record. For descriptive purpose only. Defaults to the display name of the selected target entity.
Configuration XML that describes the search schema for the target entity. A skeleton XML is automatically generated for you by default but you will need to update it. The schema of this XML is described in details later in this post.

Enhanced Quick Find is automatically enabled for the target entity once you save the Quick Find Configuration record.

Once enabled, users can start using the enhanced query in the target entity’s Quick Find:

You can update the enhanced Quick Find configuration for any entity at anytime by editing the relevant Quick Find Configuration record. To disable enhanced Quick Find for an entity, delete or deactivate the relevant configuration record. Reactivating a Quick Find Configuration record re-enables enhanced Quick Find for the relevant entity.

Hmm… what is the query syntax?

The syntax for the enhanced query is as follow:

The query can contain text criteria and filter parts.

  • Text criteria:   – this is searched against the Quick Find columns of the target entity as per OOTB behaviour
  • Filter parts:     – these apply additional filters to the result set that would be returned by the text criteria

Text criteria and filter parts are optional and can be specified in any order. A query can contain multiple filter parts. The AND operator is used between the text criteria and specified filter parts.

Filter part

A filter part consists of an alias for a related entity, and a criteria for that entity. The alias, and the fields to search against for that alias, are configurable in the configuration schema (more on this later). A colon (:) is used to separate the alias and its criteria. Wrap the criteria in double quotes (“) if it is more than one word. Criteria are searched against defined search fields using the LIKE operator.

A filter part can target a related entity at any level of the relationship hierarchy (grandchild, great grandchild etc.). This is done by using the backslash character (\) to specify the relationship path.

Here are some examples.

Example Effect
contact:abc Filter the result set to those with a related ‘contact’ where ‘contact’ matches ‘abc’
contact\case:xyz Filter the result set to those with a related ‘contact’ where ‘contact’ has a related ‘case’, AND ‘case’ matches ‘xyz’
contact:”abc 12″\case:xyz Filter the result set to those with a related ‘contact’ where ‘contact’ matches ‘abc 12’, AND contact has a related ‘case’ that matches ‘xyz’

Ok… tell me about this configuration schema!

The XML that you specify in the Configuration field of the configuration record defines the search schema and query language available to the users for the target entity.

Below is the annotated XML configuration that would enable the example searches earlier on in this post.

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

<!-- enableLogging: Optional. When set to true, details of how 
the query was interpreted are logged to CRM's tracing service.
Default is false. -->
<QuickFindConfiguration enableLogging="false">
	<QueryTransformation>
		<AliasReplacements>

			<!-- Defines a set of alias replacements for the 
query. Aliases matching the list below will be replaced with the 
specified values before the query is processed. Multiple alias 
replacements are supported. 

	replace: Required. The alias to look for. 
	with: Required. The value to replace the match with. -->
			<AliasReplacement replace="case-prd" with="contact\case\product"/>
		</AliasReplacements>
	</QueryTransformation>
	
	<!-- Defines an entity for quick find augmentation. The 
target entity of the quick find MUST be defined, as well as 
other relationships that are allowed in the augmented query. 

	entityName: Required. The CRM name of the entity. -->
	<Entity entityName="account">
		<Relationships>

			<!-- Defines a relationship from/to the entity 
that can be searched in the query. Multiple relationships are 
supported. 

	alias: Required. The alias used in the query to refer to the 
related entity, e.g. contact:ABC. 

	entityName: Required. The CRM name of the related entity. 
An Entity element with a matching entityName MUST also be 
defined. 

	fromField: Required. The name of the field that establishes 
the relationship between the parent entity and the related 
entity. 

	toField: Required. The name of the field that establishes 
the relationship between the parent entity and the related 
entity. -->
			<Relationship alias="contact" entityName="contact" fromField="parentcustomerid" toField="accountid"/>
			<Relationship alias="opp" entityName="opportunity" fromField="parentaccountid" toField="accountid"/>
		</Relationships>
		<FieldGroups>

			<!-- Defines a virtual grouping of fields 
on the target entity of the quick find. The grouping can be 
referred to in the query using an alias, e.g. profile:"XYZ". 
Groupings will only be applied against the target entity of the 
quick find. Multiple groupings are supported. 

	alias: Required. The alias used in the query to refer to 
this group of fields on the target entity. -->
			<FieldGroup alias="profile">
				<SearchFields>

					<!-- Defines a field on 
the target entity that belongs to this grouping. Multiple 
fields are supported and are applied to the search using the OR 
operator. 

	name: Required. The CRM name of the field. -->
					<Field name="industrycodename"/>
					<Field name="sic"/>
				</SearchFields>
			</FieldGroup>
		</FieldGroups>
	</Entity>
	<Entity entityName="contact">
		<SearchFields>

			<!-- Defines a field on this particular 
entity that will be used in the query for filtering. Multiple 
fields are supported and are applied to the search using the 
OR operator. 

	name: Required. The CRM name of the field. -->
			<Field name="firstname"/>
			<Field name="lastname"/>
			<Field name="description"/>
		</SearchFields>
		<Relationships>
			<Relationship alias="case" entityName="incident" fromField="customerid" toField="contactid"/>
		</Relationships>
	</Entity>
	<Entity entityName="incident">
		<SearchFields>
			<Field name="title"/>
			<Field name="description"/>
		</SearchFields>
		<Relationships>
			<Relationship alias="product" entityName="product" fromField="productid" toField="productid"/>
		</Relationships>
	</Entity>
	<Entity entityName="product">
		<SearchFields>
			<Field name="name"/>
		</SearchFields>
	</Entity>
	<Entity entityName="opportunity">
		<SearchFields>
			<Field name="description"/>
		</SearchFields>
	</Entity>
</QuickFindConfiguration>

Entity element

Entity elements define the entities that may be involved in the enhanced Quick Find for the target entity. An Entity element must exist for the target entity. An Entity element must also exist for any relationship that the user should be able to filter on in the enhanced query.

Relationship element

Relationship is a child element of an Entity element, and defines the related entities that could be used in the query for that parent entity.

The Relationship element defines the alias that the user can use to refer to the related entity in the query. A corresponding Entity element must exist for the related entity identified by the Relationship element.

SearchFields/Field element

The SearchFields/Field element is a child element of an Entity element, and is used to define a field that should be searched against when filtering on that entity. Multiple SearchFields/Field elements can be specified, in which case the OR operator will be used when applying the filter criteria against that particular entity.

FieldGroups/FieldGroup element

The FieldGroups/FieldGroup element is a child element of the Entity element, and allows you to define an alias that users can use to apply filtering criteria directly to the target entity (rather than related entities).

Normally the filter ‘alias:criteria‘ is applied to a related record type (identified by alias). The FieldGroup element allows to you define an alias that refers to a group of fields on the target entity.

For example, Account has an Industry and SIC Code fields. Using the FieldGroup element, you can enable users to filter against these particular fields when searching for Accounts.

Given the XML above, users can search for Accounts with ‘software’ in Account Name and ‘tech’ in the Industry or SIC Code fields using:

*software profile:tech

A FieldGroup element contains one or more SearchFields/Field elements that define the fields belonging to this group. When multiple fields are specified, the OR operator will be used when applying the filter criteria.

Note that FieldGroup elements are only applicable to the target entity.

QueryTransformation/AliasReplacements/AliasReplacement element

This element allows you to apply transformation to the user’s query before it is processed. This allows you to define shortcuts that users can use in their query. This is particularly useful when filtering against many-to-many related records, as without shortcuts, users would need to filter from the target entity to the many-to-many joining table, and then to the intended related record.

Filtering against OptionSet fields

Criteria are applied using the LIKE operator. When including an OptionSet field in the configuration schema you therefore should use the corresponding name field of the OptionSet field.

In case you are not already aware, all OptionSet fields in CRM has a corresponding ‘name’ field, which stores the text value of the selected OptionSet value. For example, Account has an Industry pick-list field. The name of this field is ‘industrycode‘ and the name of the corresponding OptionSet name field is ‘industrycodename‘.

Filtering against many-t0-many relationships

Each many-to-many relationship in CRM consists of two relationships under the hood: a 1-to-many relationship from entity A to the joining table, and a many-to-1 relationship from the joining table to entity B.

In order to filter against a many-to-many relationship in the enhanced query, you would need to specify both of these underlying relationships in the configuration schema. It is also recommended that you add a query transformation to give users a shortcut to filter the related entity more naturally.

For example, the configuration XML below can be used to enable filtering of Lead by Competitor.

<?xml version="1.0" encoding="utf-8" ?>
<QuickFindConfiguration enableLogging="false">
	<QueryTransformation>
		<AliasReplacements>
			<AliasReplacement replace="comp" with="leadcomps\comp"/>
		</AliasReplacements>
	</QueryTransformation>
	
	<Entity entityName="lead">
		<Relationships>
			<Relationship alias="leadcomps" entityName="leadcompetitors" fromField="leadid" toField="leadid"/>
		</Relationships>
	</Entity>
	<Entity entityName="leadcompetitors">
		<Relationships>
			<Relationship alias="comp" entityName="competitor" fromField="competitorid" toField="competitorid"/>
		</Relationships>
	</Entity>
	<Entity entityName="competitor">
		<SearchFields>
			<Field name="name"/>
		</SearchFields>
	</Entity>
</QuickFindConfiguration>

The query would be:

leadcomps\comp:”Competitor A”

or more naturally (thanks to QueryTransformation):

comp:”Competitor A”

How does it work underneath the hood?

When a Quick Find Configuration record is created, a plugin is triggered. This plugin automatically registers a new plugin on RetrieveMultiple of the target entity. The XML configuration is passed to the RetrieveMultiple plugin via its unsecured configuration.

The RetrieveMultiple plugin intercepts users’ queries and transforms it according to the XML configuration.

How about performance impact?

Performance impact of this solution is virtually none to minimal. This is because the majority of the work is in-memory string parsing and manipulation of the query object. There are no additional service calls made to CRM.

Where can users use this?

The enhanced query will be recognised in the following places:

  • Quick Find on the grid of enabled entities
  • Searching in look up forms

Unfortunately it won’t be recognised in the multi-entity search.

Support for OnPrem and Online

This solution works on both OnPrem and Online.

Download

You can download the solution ZIP here:

Note that only the managed solution adds the Quick Find Configuration entity to the sitemap. For the unmanaged solution, you will need to do this manually, or use Advanced Find to retrieve and create new records of this entity.

So in conclusion…

This solution enhances the OOTB Quick Find with a configurable query language and search schema that allow users to use text query to perform advanced searches. Users with exposure to search query in SharePoint, Outlook or Gmail should find this enhanced query syntax familiar.

Posted in CRM | Leave a comment

Source-control Adxstudio/CRM Portal JS, CSS and Liquid with CRMQuickDeploy

When working with Adxstudio Portal (OnPrem) or CRM Portal (Online) you will find that JavaScript, CSS and Liquid web templates are stored in CRM records (i.e. CRM database), which is not good for source-control or scenarios where there are more than one developer actively working on the system.

CRMQuickDeploy now has the ability to deploy these artefacts from Visual Studio to Adxstudio/CRM Portal, including:

  • Web Files
  • Web Templates
  • Entity Lists (JavaScript only)
  • Entity Forms (JavaScript only)
  • Web Form Steps (JavaScript only)
  • Web Pages (JavaScript, CSS and Copy (HTML) only)

Note that currently the tool does not deploy the configuration of these artefacts, but only the associated JavaScript, CSS and Liquid content as indicated above.

This allows you to edit JavaScript, CSS and HTML using the full editing power of Visual Studio with tight source-control integration. Having source-control, and using this as the source of truth for your source code, also allows multiple developers to actively work on the system concurrently.

Download

You can download CRMQuickDeploy from the Visual Studio Marketplace.

Support for OnPrem and Online

This tool supports both Adxstudio OnPrem (at the time of writing currently at version 7.0.0.25) and CRM Portal (Online). There are some minor differences in behaviour however depending on the type of artefact being deployed. These are highlighted in the relevant sections below.

Overview of how it works

The tool requires portal artefacts in the Visual Studio project to be placed into specific folders based on the artefact type. For example, web files should be placed in the PortalWebFiles folder, and web templates in the PortalWebTemplates folder, etc.

In most cases, the linkage between a file in Visual Studio and the corresponding record in CRM is by the name of the file in Visual Studio. For example, the file PortalWebPages\Contact Us.css will be deployed as the CSS for the Contact Us web page in CRM.

You can deploy an item by:

  1. Right-click in the active code editor and choose Deploy to CRM
  2. Right-click on one or more files (or a folder) in the Solution Explorer and choose Deploy to CRM
  3. Right click on the project in Solution Explorer and choose Deploy Portal Artefacts. This will deploy all portal artefacts found within the project.

The deployment behaviour for each artefact type are outlined later in this post, but here’s a summary table:

Artefact Type Expected Folder Name Create or Update? Linkage to CRM Record
Web page PortalWebPages Update only Via the filename in Visual Studio
Web template PortalWebTemplates Create and Update Via the filename in Visual Studio
Entity list PortalEntityLists Update only Via the filename in Visual Studio
Entity form PortalEntityForms Update only Via the filename in Visual Studio
Web form step PortalWebForms Update only Via the filename in Visual Studio
Web file PortalWebFiles Create and Update Via a special XML config file in Visual Studio

Specifying CRM connection string and target portal website

Similar to previous versions of the tool, the CRM connection string is specified at the solution node in the Solution Explorer:

Some artefact types need to be scoped to a particular website record in CRM. The name of this website record can be specified at the project node in the Solution Explorer:

The rest of this post discusses the deployment behaviour for each artefact type.

Web pages

Web page items should be placed in the PortalWebPages folder at the root level of the Visual Studio project. The tool allows you to update the following fields of the corresponding web page record in CRM with content from files in Visual Studio:

  • Copy (HTML)
  • Custom JavaScript
  • Custom CSS

The filename (without extension) in Visual Studio determines the target web page record. The file extension determines the field to update using the following rules:

  • .html or .htm:   content of file will be used for the Copy (HTML) field
  • .js:   content of file will be used for the Custom JavaScript field
  • .css:   content of file will be used for the Custom CSS field

So for example you may have the following files to update the Contact Us page:

PortalWebPages\Contact Us.html

PortalWebPages\Contact Us.js

PortalWebPages\Contact Us.css

Not all three files must exist. You can include only those that are required.

The tool does not create new web page records. The web page record must already exist in the target CRM, and must be scoped to the website as identified by the Portal Website Name property of the Visual Studio project.

Web templates

Web template items should be placed in the PortalWebTemplates folder at the root level of the Visual Studio project.

The filename (without extension) in Visual Studio determines the target web template record. While any file extension can be used, the native HTML editor in Visual Studio seems to be the best choice for editing Liquid/HTML, and as such the .html or .htm extension is recommended.

So for example you may have the following file to deploy the Dashboard template:

PortalWebTemplates\Dashboard.html

The tool creates the web template if it does not exist.

Note regarding OnPrem vs Online:

In CRM Portal (Online) web templates must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing web templates. This setting is ignored when the deployment target is OnPrem.

Entity lists and entity forms

Entity list items should be placed in the PortalEntityLists folder at the root level of the Visual Studio project, and entity forms in the PortalEntityForms folder.

The tool allows you to update the following fields of the corresponding entity list/entity form record in CRM with content from files in Visual Studio:

  • Custom JavaScript

The filename (without extension) in Visual Studio determines the target entity list/entity form record. The file extension determines the field to update using the following rules:

  • .js:   content of file will be used for the Custom JavaScript field

So for example you may have the following file to update the Account List entity list:

PortalEntityLists\Account List.js

And the following file to update the Account Edit entity form:

PortalEntityForms\Account Edit.js

The tool does not create new entity list/entity form records. The entity list/entity form record must already exist in the target CRM.

Note regarding OnPrem vs Online:

In CRM Portal (Online) entity lists and entity forms must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing entity lists/entity forms. This setting is ignored when the deployment target is OnPrem.

Web form steps

Web form step items should be grouped by the parent web forms, and placed in sub-folders of the PortalWebForms folder, which should sit at the root level of the Visual Studio project. The names of the sub-folders are used to identify the parent web form records in CRM.

For example, the following files hold content for the steps Step 1 and Step 2 of the Registration and Feedback web forms.

PortalWebForms\Registration\Step 1.js

PortalWebForms\Registration\Step 2.js

PortalWebForms\Feedback\Step 1.js

PortalWebForms\Feedback\Step 2.js

The tool allows you to update the following fields of the corresponding web form step record in CRM with content from files in Visual Studio:

  • Custom JavaScript

The filename (without extension) in Visual Studio determines the target web form step record. The file extension determines the field to update using the following rules:

  • .js:   content of file will be used for the Custom JavaScript field

The tool only updates existing web form step records, and does not create new records. It also does not update/create web form records.

Note regarding OnPrem vs Online:

In CRM Portal (Online) web forms must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing web forms/web form steps. This setting is ignored when the deployment target is OnPrem.

Web files

Web file items should be placed in the PortalWebFiles folder at the root level of the Visual Studio project.

The tool creates new or updates existing web files on the target CRM. You define the web files that should be deployed and their key properties (including Name, Parent Page, Partial Url) in a special XML config file, namely DeploymentSettings.xml.

The DeploymentSettings.xml file should be placed in the PortalWebFiles folder, and the tool can automatically generate a skeleton file for you. When you attempt to deploy a web file, the tool offers to create a skeleton DeploymentSettings.xml file for you if one is not found in the expected location.

Only local files identified in the DeploymentSettings.xml file are eligible for deployment as web files. Below is the generated skeleton file with explanation comments and examples:

<DeploymentSettings>
	<!-- Define a collection of items that will be deployed as Web Files and associated with a particular website. -->
	<WebFiles>
		<!-- Separately define each local files to be deployed as Web Files. -->
<!-- -->
<!-- localPath: Required. Relative to the root PortalWebFiles folder of the project. Value should not start with '\'. -->
<!-- targetName: Optional. The name of the web file once deployed. Default to the same value as partialUrl. -->
<!-- parentPage: Required. The name of the parent web page in CRM. -->
<!-- partialUrl: Optional. The partial URL of the web file once deployed. Default to the file name of the Visual Studio item with all spaces removed. -->
<!-- -->
		<File localPath="common.js" parentPage="Home" />
		<File localPath="scripts\script1.js" parentPage="Home" />
		<File localPath="styles\styles.css" targetName="Portal Styles" parentPage="Home" partialUrl="styles.css"/>

		<!-- Define local files to be deployed as Web files using the folder approach. When this approach is used, default value will be used for targetName and partialUrl for each item under the folder. A Folder element only covers the files that are found directly underneath that folder. Separate Folder elements must be defined for each sub-folders. -->
<!-- -->
<!-- localPath: Required. Relative to the root PortalWebFiles folder of the project. Value should not start with '\'. To refer to files directly underneath the root PortalWebFiles, specify an empty string for this attribute, i.e. localPath="". -->
<!-- parentPage: Required. The name of the parent web page in CRM that will be applied to all items under this folder. -->
<!-- targetNamePrefix: Optional. The specified value will be added to the targetName for all items under this folder. -->
<!-- -->
		<Folder localPath="scripts" parentPage="Home" />
		<Folder localPath="styles" parentPage="Home" targetNamePrefix="My Portal - " />
	</WebFiles>
</DeploymentSettings>

As web files must be scoped to a website, the tool uses the website name as identified by the Portal Website Name project property when searching for existing or creating new web files.

Note regarding file type restriction in CRM:

In Adxstudio/CRM Portal web files are essentially created as attachments to notes. Unless a partialUrl value is defined in DeploymentSettings.xml, the attachment will have the same name as the file in Visual Studio (with spaces removed). CRM however by default blocks certain attachment types, including .js. To enable deployment of .js web files, you will need to unblock this type in CRM.

Conclusion

So there you have it, a new set of commands aimed at Adxstudio/CRM Portal developers to help you work more effectively as a team, and sleep a bit easier at night knowing that you source code are safely tucked away somewhere :).

As always, I would love to hear from you with suggestions and feedback.

 

Posted in Adxstudio, CRM, CRM Portal, CRMQuickDeploy | 1 Comment