As part of developing Dynamics CRM/Dynamics 365 Customer Engagement (CRM/D365CE) plug-ins day in, day out, you can often forget about the Execution Mode setting. This can be evidenced by the fact that I make no mention of it in my recent tutorial video on plug-in development. In a nutshell, this setting enables you to customise whether your plug-in executes in Synchronous or Asynchronous mode. Now, you may be asking – just what the hell does that mean?!? The best way of understanding is by rephrasing the terminology; it basically tells the system when you want your code to be executed. Synchronous plug-ins execute all of your business logic whilst the record is being saved by the user, with this action not being considered complete and committed to the backend database until the plug-in completes. By comparison, Asynchronous plug-ins are queued for execution after the record has been saved. A System Job record is created and queued alongside other jobs in the system via the Asynchronous Service. Another way of remembering the difference between each one is to think back to the options available to you as part of a Workflow. They can either be executed in real time (synchronously) or in the background (asynchronously). Plug-ins are no different and give you the flexibility to ensure your business logic is applied immediately or, if especially complex, queued so that the system has sufficient time to process in the background.

I came across a strange issue with an arguably even stranger Synchronous plug-in the other day, which started failing after taking an inordinately long time saving the record:

Unexpected exception from plug-in (Execute): MyPlugin.MyPluginClass: System.AggregateException: One or more errors occurred.

The “strange” plug-in was designed so that, on the Create action of an Entity record, it goes out and creates various related records within the application, based on a set of conditions. We originally had issues with the plug-in a few months back erroring, due to the execution time exceeding the 2 minute limit for sandbox plug-ins. A rather ingenious and much more accomplished developer colleague got around the issue by implementing a degree of asynchronous processing within the plug-in, achieved like so:

await Task.Factory.StartNew(() =>
{
    lock (service)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        Guid record = service.Create(newRecord);
        tracing.Trace("Record with ID " + record.ToString() + " created successfully after: {0}ms.", stopwatch.ElapsedMilliseconds);
    }
});

I still don’t fully understand just exactly what this is doing, but I put this down to my novice level C# knowledge 🙂 The important thing was that the code worked…until some additional processing was added to the plug-in, leading to the error message above.

At this juncture, our only choice was to look at forcing the plug-in to execute in Asynchronous mode by modifying the appropriate setting on the plug-in step within the Plugin Registration Tool:

After making this change and attempting to create the record again in the application, everything worked as expected. However, this did create a new problem for us to overcome – end users of the application were previously used to seeing the related records created by the plug-in within sub-grids on the Primary Entity form, which would then be accessed and worked through accordingly. As the very act of creating these records now took place within the background and took some time to complete, we needed to display an informative message to the user to advise them to refresh the form after a few minutes. You do have the ability within plug-ins to display a custom message back to the user, but this is only in situations where you are throwing an error message and it didn’t seem to be a particularly nice solution for this scenario.

In the end, the best way of achieving this requirement was to implement a JScript function on the form. This would trigger whenever the form is saved and displays a message box that the user has to click OK on before the save action is carried out:

function displaySaveMessage(context) {

    var eventArgs = context.getEventArgs();
    var saveMode = eventArgs.getSaveMode();

    if (saveMode == 70 || saveMode == 2 || saveMode == 1 || saveMode == 59) {
        var message = "Records will be populated in the background and you will need to refresh the form after a few minutes to see them on the Sub-Grid. Press OK to save the record."
        Xrm.Utility.alertDialog(message, function () {
            Xrm.Page.data.save().then(function () {
                Xrm.Page.data.refresh();
            })
        });
    }
}

By feeding through the execution context parameter, you are able to determine the type of save action that the alert will trigger on; in this case, SaveSave & CloseSave & New and Autosave. Just make sure you configure your script with the correct properties on the form, which are:

  • Using the OnSave event handler
  • With the Pass execution context as first parameter setting enabled

From the end-users perspective, they will see something similar to the below when the record is saved:

It’s a pity that we don’t have similar kind of functionality exposed via Business Rules that enable us to display OnSave alerts that are more in keeping with the applications look and feel. Nevertheless, the versatility of utilising JScript functions should be evident here and can often achieve these types of bespoke actions with a few lines of code.

When it comes to plug-in development, understanding the impact and processing time that your code has within the application is important for two reasons – first, in ensuring that end users are not frustrated by long loading times and, secondly, in informing the choice of Execution Mode when it comes to deploying out a plug-in. Whilst Asynchronous plug-ins can help to mitigate any user woes and present a natural choice when working with bulk operations within the application, make sure you fully understand the impact that these have on the Asynchronous Service and avoid a scenario where the System Job entity is queued with more jobs then it can handle.

This is an accompanying blog post to my YouTube video Dynamics 365 Customer Engagement Deep Dive: Creating a Basic Jscript Form Function, the first in a series that aims to provide tutorials on how to accomplish developer focused tasks within Dynamics 365 Customer Engagement. You can watch the video in full below:

Below you will find links to access some of the resources discussed as part of the video and to further reading topics.

PowerPoint Presentation (click here to download)

Full Code Sample

function changeAddressLabels() {

    //Get the control for the composite address field and then set the label to the correct, Anglicised form. Each line requires the current control name for 'getControl' and then the updated label name for 'setLabel'

    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line1").setLabel("Address 1");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line2").setLabel("Address 2");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_line3").setLabel("Address 3");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_city").setLabel("Town");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_stateorprovince").setLabel("County");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_postalcode").setLabel("Postal Code");
    Xrm.Page.getControl("address1_composite_compositionLinkControl_address1_country").setLabel("Country");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line1"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line1").setLabel("Address 1");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line2"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line2").setLabel("Address 2");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line3"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_line3").setLabel("Address 3");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_city"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_city").setLabel("Town");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_stateorprovince"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_stateorprovince").setLabel("County");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_postalcode"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_postalcode").setLabel("Postal Code");

    if (Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_country"))
        Xrm.Page.getControl("address2_composite_compositionLinkControl_address2_country").setLabel("Country");
}

Download/Resource Links

Visual Studio 2017 Community Edition

Setup a free 30 day trial of Dynamics 365 Customer Engagement

W3 Schools JavaScript Tutorials

Source Code Management Solutions

Further Reading

MSDN – Use JavaScript with Microsoft Dynamics 365

MSDN – Use the Xrm.Page. object model

MSDN – Xrm.Page.ui control object

MSDN – Overview of Web Resources

Debugging custom JavaScript code in CRM using browser developer tools (steps are for Dynamics CRM 2016, but still apply for Dynamics 365 Customer Engagement)

Have any thoughts or comments on the video? I would love to hear from you! I’m also keen to hear any ideas for future video content as well. Let me know by leaving a comment below or in the video above.

When working with form-level JScript functionality on Dynamics CRM/Dynamics 365 for Enterprise (D365E), you often uncover some interesting pieces of exposed functionality that can be utilised neatly for a specific business scenario. I did a blog post last year on arguably one of the best of these functions when working with Lookup field controls – the Xrm.Page.getControl().addPreSearch method. Similar to other methods exposed via the SDK, its prudent and effective implementation can greatly reduce the amount of steps/clicks that are involved when populating Entity forms.

I’ve already covered as part of last years post just what this method does, its sister method, addCustomFilter, and also some of the interesting problems that are encountered when working with the Customer lookup field type; a special, recently introduced field type that allows you to create a multi-entity lookup/relationship onto the Account and Contact entities on one field. I was doing some work again recently using these method(s) in the exact same conditions, and again came across some interesting quirks that are useful to know when determining whether the utilisation of these SDK methods is a journey worth starting in the first place. Without much further ado, here are two additional scenarios that involve utilising these methods and the “lessons learned” from each:

Pre-Filtering the Customer Lookup to return Account or Contact Records Only

Now, your first assumption with this may be that, if you wanted your lookup control to only return one of the above entity types, then surely it would be more straightforward to just setup a dedicated 1:N relationship between your corresponding entity types to achieve this? The benefits of this seem to be pretty clear – this is a no-code solution that, with a bit of ingenious use of Business Rules/Workflows, could be implemented in a way that the user never even suspects what is taking place (e.g. Business Rule to hide the corresponding Account/Contact lookup field if the other one contains a value). However, assume one (or all) of the following:

  • You are working with an existing System entity (e.g. Quote, Opportunity) that already has the Customer lookup field defined. This would, therefore, mean you would have to implement duplicate schema changes to your Entity to accommodate your scenario, a potential no-no from a best practice point of view.
  • Your entity in question already has a significant amount of custom fields, totalling more than 200-300 in total. Additional performance overheads may occur if you were to then choose to create two separate lookup fields as opposed to one.
  • The entity you are customising already has a Customer lookup field built in, which is populated with data across hundreds, maybe thousands, of records within the application. Attempting to implement two separate lookups and then going through the exercise of updating every record to populate the correct lookup field could take many hours to complete and also have unexpected knock-on effects across the application.

In these instances, it may make more practical sense to implement a small JScript function to conditionally alter how the Customer Lookup field allows the user to populate records when working on the form. The benefit of this being is that you can take advantage of the multi-entity capablities that this field type was designed for, and also enforce the integrity of your business logic/requirements on the applications form layer.

To that end, what you can look at doing is applying a custom FetchXML snippet that prevents either Account or Contact records from returning when a user clicks on the control. Paradoxically, this is not done by, as I first assumed, using the following snippet:

var filter = "<filter type='and'><condition attribute='accountid' operator='not-null' /></filter>";
Xrm.Page.getControl("mycustomerlookupfield").addCustomFilter(filter, "account");

This will lead to no records returning on your lookup control. Rather, you will need to filter the opposite way – only return Contact records where the contactid equals Null i.e. the record does not exist:

var filter = "<filter type='and'><condition attribute='contactid' operator='null' /></filter>";
Xrm.Page.getControl("mycustomerlookupfield").addCustomFilter(filter, "contact");

Don’t Try and Pass Parameters to your addCustomerFilter Function (CRM 2016 Update 1)

If your organisation is currently on Dynamics CRM 2016 Update 1, then you may encounter a strange – and from what I can gather, unresolvable – issue if you are working with multiple, parameterised functions in this scenario. To explain further, lets assume you have a Customer Lookup and a Contact Lookup field on your form. You want to filter the Contact Lookup field to only return Contacts that are associated with the Account populated on the Customer Lookup. Assume that there is already a mechanism in place to ensure that the Customer lookup will always have an Account record populated within it, and your functions to use in this specific scenario may look something like this:

function main() {

    //Filter Contact lookup field if Customer lookup contains a value

    var customerID = Xrm.Page.getAttribute('mycustomerlookupfield').getValue();

    if (customerID != null) {
   
        Xrm.Page.getControl("mycontactfield").addPreSearch(filterContactNameLookup(customerID[0].id));
    }
    
}

function filterContactNameLookup(customerID) {

    var filter = "<condition attribute='parentcustomerid' operator='eq' value='" + customerID + "' />";
    Xrm.Page.getControl("mycontactfield").addCustomFilter(filter, "account");

}

The above example is a perfectly sensible means of implementing this. Because, surely, it make more practical sense to only obtain the ID of our Customer Lookup field in one place and then pass this along to any subsequent functions? The problem is that CRM 2016 Update 1 throws some rather cryptic errors in the developer console when attempting to execute the code, and does nothing on the form itself:

Yet, when we re-write our functions as follows, explicitly obtaining our Customer ID on two occasions, this runs as we’d expect with no error:

function main() {

    //Filter Contact lookup field if Customer lookup contains a value

    var customerID = Xrm.Page.getAttribute('mycustomerlookupfield').getValue();

    if (customerID != null) {
   
        Xrm.Page.getControl("mycontactfield").addPreSearch(filterContactNameLookup);
    }
    
}

function filterContactNameLookup() {

    var customerID = Xrm.Page.getAttribute('mycustomerlookupfield').getValue()[0].id;
    var filter = "<condition attribute='parentcustomerid' operator='eq' value='" + customerID + "' />";
    Xrm.Page.getControl("mycontactfield").addCustomFilter(filter, "account");

}

I’ve been scratching my head at why this doesn’t work, and the only thing I can think of is that the first function – main – would be executed as part of the forms OnLoad event, whereas the filterContactNameLookup is only triggered at the point in which the lookup control is selected. It’s therefore highly possible that the first instance of the customerID is unobtainable by the platform at this stage, meaning that you have to get the value again each time the lookup control is interacted with. If anyone else can figure out what’s going on here or confirm whether this is a bug or not with Dynamics CRM 2016 Update 1, then do please let me know in the comments below.

Conclusions or Wot I Think

It could be argued quite strongly that the examples shown here in this article have little or no use practical use if you are approaching your CRM/D365E implementation from a purely functional point of view. Going back to my earlier example, it is surely a lot less hassle and error-prone to implement a solution using a mix of out of the box functionality within the application. The problem that you eventually may find with this is that the solution becomes so cumbersome and, frankly, undecipherable when someone is coming into your system cold. With anything, there always a balance should be striven for on all occasions and, with a bit of practical knowledge of how to write JScript functionality (something that any would-be CRM expert should have stored in their arsenal), you can put together a solution that is relatively clean from a coding point of view, but also benefits from utilising some great functionality built-in to the application.

The ability to modify the values within Option Set fields on a Dynamics CRM/Dynamics 365 for Enterprise (D365E) form is, depending on your business scenario, one of the best reasons to learn how to write basic JScript form functions. Microsoft makes available 3 methods via the Xrm.Page.ui control (which can be handily shortcutted to Xrm.Page.getControl):

  • addOption – Lets you add a new Option Set value, with the ability to optionally specify its position within the index.
  • clearOptions – Removes all options from an Option Set field.
  • removeOption – Allows you to remove a specific Option Set field value.

With these options at your disposal, you can begin to leverage functionality to accommodate the following business logic:

  • Only allow an Option Set to be populated with values (either all or just specific ones) once information has been entered into other fields.
  • Dynamically change the options available for selection, based on information entered within the primary entity form, or even a related one.

Now the key takeaway with all of this is that, ultimately, your code cannot do anything that would make the “base” Option Set collection invalid. For example – let’s assume you have a field called Fruit with the Option Set values of Apple, Pear and Apricot. If you then tried to introduce the value Banana on the form level, your code will more than likely error when saving the record.

From a CRM/D365E point of view, there are two additional field types which are also technically classed as Option Sets, thereby allowing you to utilise the above methods with them – the Status and Status Reason fields. If you look closely at the Status Reason field within the Customizations area of the application, you can see why: just like option sets, you specify a label and underlying value for it, that is dictated by your solution publisher:

The only difference worth noting is that, unlike normal Option Set fields, you have no choice when it comes to which underlying value is used. If your organisation is prone to changing Status Reason values often for an entity, you may begin to notice large gaps in Option Set values over time; annoying, but I can understand why it’s in place.

All of the above code examples should be sufficient for common scenarios, such as when you want to remove single option set values or are interacting with the Status Reason field on the main part of the form. What I wanted to do as part of today’s blog post is highlight two non-standard scenarios for the above and illustrate a solution for each – which, when brought together, demonstrate an example I was recently involved in developing a solution for.

The Problem

The business in question was using an entity across multiple parts of their business. On the entity, there was then a flag field to indicate which side of the business the entity record belonged to. This would then assist when running reports and, when coupled with Business Unit segregation for the records, ensured that colleagues in the business only saw records that they needed to as part of their day-to-day usage of the application.

Because the entity was shared, fields within the entities – such as Status Reason – were also shared and, by implication, contained values that were only relevant to a specific part of the business. We were therefore tasked with finding a solution to ensure that the Status Reason value list was modified for each person to reflect the record type they were working with. Colleagues primarily worked within the Web Application and the Status Reason field was on each form, within the Header area.

Solution #1: Efficiently Removing Multiple Option Set Values

When we take a look at the code examples, we are given pretty much everything we need to start implementing a solution. So if we assume that our Status Reason field contains 15 values, 10 of which we want to remove, we may be tricked into writing the following code and doing the good ol’ copy & paste:

Xrm.Page.getControl('statuscode').removeOption(100000000);
Xrm.Page.getControl('statuscode').removeOption(100000001);
Xrm.Page.getControl('statuscode').removeOption(100000002);
Xrm.Page.getControl('statuscode').removeOption(100000003);
Xrm.Page.getControl('statuscode').removeOption(100000004);
Xrm.Page.getControl('statuscode').removeOption(100000005);
Xrm.Page.getControl('statuscode').removeOption(100000006);
Xrm.Page.getControl('statuscode').removeOption(100000007);
Xrm.Page.getControl('statuscode').removeOption(100000008);
Xrm.Page.getControl('statuscode').removeOption(100000009);

Now, this code will work fine and can be pretty clearly deciphered. The problem lies in the number of lines it is expressed in (thereby increasing load times/processing time when CRM is processing your function). We can get round this by thinking back to Programming 101 and making the correct assumption that JScript has the ability to loop through similar commands – in this case, via the for Loop. By introducing an array into the mix as well, we can then express our code much more simply:

var statusCodes = [100000000, 100000001, 100000002, 100000003, 100000004,
                   100000005, 100000006, 100000007, 100000008, 100000009];
                   
    for (index = 0; index < statusCodes.length; ++index) {

        Xrm.Page.getControl('statuscode').removeOption(statusCodes[index]);

    }

Solution #2: Working with Header Controls A.K.A. Why I Hate ‘Object reference not set to an instance of an object’ Errors

I perhaps should have prefaced the above when I said ‘Now this code will work fine’. 🙂 Going back to the task at hand, when I attempted to deploy a version of the above code into the target environment, I immediately got the dreaded error message referenced above. This is generally the hallmark error to steer you towards checking your code for any typos, as the error message is basically telling you it can’t find the thing you are asking it to – in this case, the field statuscode.

After triple-checking the exact spelling of statuscode and making sure the field was on the form, I did some further diving into Developer Tools with the form loaded to figure out just what the hell was going wrong. When performing a search for the statuscode field on the DOM Explorer, I noticed that I got a few hits for statuscode – but that it was prefaced with the value header_. I followed this up with some further research online, which confirmed that, if fields in the Header need to be referenced, then they should be prefaced with header_. So our for Loop example would need to look as follows instead:

    for (index = 0; index < statusCodes.length; ++index) {

        Xrm.Page.getControl('header_statuscode').removeOption(statusCodes[index]);

    }

Bringing it All Together

After encapsulating all of the above into a single function, removeStatusCodes(), we can then call the following snippet as part of an OnLoad function to hide our Status Reason values if the flag field is set to the correct value:

if (Xrm.Page.getAttribute('new_myflagfield').getValue() == "100000000") {
                                                           
        removeStatusCodes();
    }

This will then work as intended and ensure that end-users only see the Status Reason values that directly concern them; no doubt assisting greatly in making the system as streamlined as possible to use and to avoid any potential for data-entry problems further down the line.

An oft-requested requirement as part of any Dynamics CRM/Dynamics 365 for Enterprise (D365E) deployment is a level of integration with another application system. In some of these cases, this will involve pulling through external web pages and passing them form-level attribute values, to load an external systems report, record page etc. From a CRM/D365E point of view, this can be very straightforwardly achieved thanks to some of the functionality provided as part of the Xrm.Page object model. For example. let’s assume that you have an IFrame control on your form and you wanted to load an ASP.NET web page, passing the ID of the record as a query parameter in the URL. Setup your IFrame on your form, with a random URL and set to hidden. Then, a JScript function like this on the OnLoad event would get the job done for you:

function loadIFrame() {

    //Get the current record ID

    var entityID = Xrm.Page.data.entity.getId();

    //Replace { & } with their appropriate URL counterparts in entityID

    entityID = entityID.replace("{", "%7b");
    entityID = entityID.replace("}", "%7d");

    //Create the URL

    var url = "http://myexternalwebpage.com/MyAspPage.aspx?id=" + entityID;

    //Then, update the IFrame with the new URL and make it visible on the form

    Xrm.Page.getControl("IFRAME_myiframe").setSrc(url);
    Xrm.Page.getControl("IFRAME_myiframe").setVisible(true);
}

What helps with the above is that there are well-documented code samples that assists when putting together this example, so you can be confident that the solution will work and is fully supported.

Things get a little more complicated once we are operating outside the standard CRM/D365E environment. Assume that instead of displaying this IFrame control on a form, it needs to be displayed as part of an Entity Form in Adxstudio/CRM Portals. Here is where the head scratching can commence in earnest, and you need to look at getting your hand’s dirty writing custom code to achieve your requirements. There a few hurdles to overcome in the first instance:

  • How do you access attribute values from an Entity Form, such as a record ID?
  • Once you are able to access the attribute value, how to you set this up on your Entity Form?
  • How do you embed an IFrame within an Entity Form?

Let’s take a look at one approach to the above, working on the same basis as above – an external URL that we pass the record ID to, from within an Entity Form Web Page. Things may get a bit more difficult if you need to access other entity attribute values, which may require some kind of trickery with Liquid Templates to achieve successfully.

Accessing Entity Form Record ID

When your Entity Form page is loaded on your Portal, there are a number of properties regarding the record that are exposed on the underlying web page – the name of the entity, the record ID, Status and Status Reason values. These can be accessed via a div element on the page, which can be viewed within the DOM Explorer as part of a Web Browsers developer tools (in the below example, Internet Explorer is used):

1

The id of the div class will always be the same, except for the value in the middle, which is the GUID for the Entity Form record within CRM/D365E, but without the dashes. So you don’t need to necessarily go into the DOM to get this value; as a time-saving mechanism, simply export your Entity Form record into Excel and view the first hidden column to obtain this value.

Suffice to say, because we know that this value is accessible when our Portal page loads, we can look at programmatically accessing this via a JScript function. The following snippet will do the trick:

var recordID = document.getElementById('EntityFormControl_31c41a020771e61180e83863bb350f28_EntityFormView_EntityID').value;

Now that we have a means of accessing the attribute value, our options in terms of what we can do with it greatly increase 🙂

Executing Entity Form Custom JScript Functions

There are two ways you can place custom JScript on your portal page – you can either place your functions within the Custom JavaScript field, located on the Entity Form form within CRM:

2

Functions will be added to the bottom of your Web Page when loaded, meaning they can be freely accessed after the page has loaded. The second way, which leads us nicely onto the next section, is to wrap your JScript function as a custom HTML snippet on the Web Pages Copy (HTML) field.

Embedding an IFrame on your Web Page

All Web Pages in Adxstudio/Portals – irrespective of what other content the page is loading – contain a Copy (HTML) field. This enables you to write your own bespoke text or other HTML content that is displayed on the Web Page. In the case of an Entity Form Web Page, then the content will be displayed just below the Entity Form. Thanks to the ability to access and write our own custom HTML code for this, options for bespoke development are greatly increased – simply click the Source button to switch to the underlying HTML editor:

3

Then, using a combination of the snippet we used earlier and utilising the <iframe> HTML tag, we can place the following in our Copy (HTML) to do the lot for us – get our record ID, pass it to an external web page and then load this within an IFrame:

<p>
    <script>
        function getEntityID() {
            var url = "http://myexternalwebpage.com/MyAspPage.aspx?id=";
            var entityID = document.getElementById('EntityFormControl_31c41a020771e61180e83863bb350f28_EntityFormView_EntityID').value;
            var iframeSrc = document.getElementById('myiframe').src;

            if (iframeSrc != url + "%7b" + entityID + "%7d") {

                setTimeout(function () {
                    document.getElementById('myiframe').src = url + "%7b" + entityID + "%7d";
                }, 2000);
            }
        }
    </script>
</p>
<h1>My IFrame</h1>
<p>
    <iframe width="725" height="250" id="myiframe" src="" onload="getEntityID();"></iframe>
</p>

The reason why setTimeout is used is to ensure that the entity form <div> class loads correctly, as this is one of the last things that Adxstudio/Portals loads last on the page. For obvious reasons, if this hasn’t loaded, then our JScript function will error. Putting this aside, however, the above solution gets us to where we want to be and means that we can achieve the same outcome as the CRM/D365E example demonstrated at the start of this post 🙂

Conclusions or Wot I Think

Adxstudio/Portals presents some interesting and different learning opportunities, both given its genesis as a separate product to its gradual integration as part of the CRM/D365E family. This can often mean that you have to abandon your base assumptions and ways of thinking when it comes to CRM/D365E development, and instead look at things from a more general approach. I would hope that, in time, we will begin to see the gradual introduction of common XRM object models within CRM Portals, as it is crucially important that there is a unified approach when developing Portal extensions in the future and that we are not in the situation where unsupported code becomes rampant across different Portal deployments. This latter concern would be my chief worry with the examples provided in this post, as there is currently no clear way of determining whether the approach taken is supported or considered “best practice” from an Adxstudio/Portal perspective. I would be interested in hearing from anyone in the comments below if they have any thoughts or alternative approaches that they would recommend to achieve the above requirement.