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.

Advertisements
Posted in CRM, CRM Portal | Leave a comment

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.

Posted in CRM, CRM Portal | Leave a 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 | 2 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):

<fetch>
  <entity name="bnh_degree_bnh_subject" >
    <filter type="and" >
      <condition attribute="bnh_degreeid" operator="eq" value="BE830F0A-50E1-E711-847C-B86B23AC9F7C" />
    </filter>
    <link-entity name="bnh_subject" from="bnh_subjectid" to="bnh_subjectid" >
      <attribute name="bnh_name" />
      <attribute name="bnh_code" />
    </link-entity>
  </entity>
</fetch>

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 inclusion…

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 | Leave a 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