Posted on 29. March 2013

Asynchronous loading of JavaScript Web Resources after U12/POLARIS

We all know that UR12/POLARIS was a monumental release for Dynamics CRM what with the new Process Forms and Cross Browser support, but also included were some performance optimisations. One such improvement was a change to the way JavaScript Web Resources are loaded on forms so that they load and execute asynchronously rather than in the order that they were added to the form. The drawback with this optimisation is that it can cause the '..is undefined' script error if you have scripts that depend on other scripts being loaded first.

This post describes the loading behaviour and some possible solutions.

How did it work before UR12/POLARIS?

In my example I have 3 scripts, each dependant on the last. Mscorlib.js is the Script# system library that is needed before any other libraries can be loaded. I'm not talking about code that runs in the 'onload' event of a form but global code that is run after the script has downloaded used to define the prototypes of the objects that are used by the onload code.

In the Client.js, I might have some Script# generated code that requires a core Script# (ss.IEnumerable) type that is defined in a different script file.

Xrm.Sdk.DataCollectionOfEntity.registerClass('Xrm.Sdk.DataCollectionOfEntity', null, ss.IEnumerable);

For this code to run, the mscorlib.js must be loaded and executed first. The same applies if you are using jQuery and jQuery-UI.

The form definition used to have each script added in order so that they would be added to the 'head' section of the page as follows:

<head>
..
<script src=”/%7B635001685810000000%7D/WebResources/fdocs_/js/mscorlib.js” type=”text/javascript”></script>
<script src= /%7B635001685810000000%7D/WebResources/fdocs_/js/Xrm.js” type=”text/javascript”></script>
<script src=” /%7B635001685810000000%7D/WebResources/fdocs_/js/Client.js” type=”text/javascript”></script>
..

The resulting load pattern would be something like:

  • The script load/execution would be approximately:

    1. Mscrolib.js downloads and then executes
    2. Xrm.jsd downloads and then executes after mscrolib.js has completed executing since it follows it in the <HEAD> section of the page
    3. Client.js downloads and then executes after Xrm.js has completed executing since again it is added to the <HEAD> section in this order.
    4. The On Load event code then runs when all the scripts have loaded and finished executing

     

     

    What changes with the UR12/POLARIS update?

    Scripts are no longer in the <HEAD> section but are loaded asynchronously via the 'loadScriptAdv' function:

    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fmscorlib.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fmscorlib.js', false);
    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fXrm.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fXrm.js', false);
    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fClient.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fClient.js', false);
    

    The new asynchronous load behaviour results in the OnLoad being run after a smaller wait time since each script can execute without waiting for the preceding ones. This is a good performance gain but causes scripts to fail if the dependant scripts are not loaded at the point of execution.

  • The interesting thing is that this wasn't immediately apparent or was only an intermittent problem because once the scripts are loaded into the browser cache, the execution would usually be in the correct order. I can consistenly reproduce the issue by clearing down the browser cache and then disabling all caching.

    Ribbon JavaScript

    It's been a common technique to add dependant libraries to Ribbon Commands by adding them as Command Actions with a function of 'isNaN'

    <Actions>
    <JavaScriptFunction Library=”$webresource:mscorlib_crm.js” FunctionName=”isNaN”/>
    <JavaScriptFunction Library=”$webresource:xrm.js” FunctionName=”isNaN”/>
    <JavaScriptFunction Library=”$webresource:RibbonCommands.js” FunctionName=”someCommand”/>
    </Actions>
    
    

    It seems that the same issue exists with these libraries since they are loaded in an asynchronous fashion.

    Solutions

    Unfortunately, any solution that uses depending scripts in this way has to be updated – and like with all things, the right solution depends on your specific case.

    1) All script in a single library

    By far the simplest solution is to put all of your scripts into a single file in the correct order.

    I didn't' settle for single script option for the following reasons:

    1. A single script is harder to maintain
    2. Common libraries can't be shared between solutions
    3. All code must be downloaded on first use, rather than only downloading what is required at the time.

    2) RequireJs or HeadJs

    There are some good JavaScript loading libraries out there such as RequiresJs or HeadJs. These libraries allow you to dynamically load dependant script before you execute code that needs them.
    Gayan has an example of this technique on his blog. Maarten also has a good tutorial on his blog.

    I didn't settle for the RequireJs/HeadJs option for the following reasons:

    1. It requires you to manually set the Cache key to ensure that scripts are not downloaded every time they are needed
    2. It completely bi-passes the Dynamics CRM script loading mechanism in an 'unsupported' way
    3. There is some possibility that a backward compatibility option may be added into a future release (I'm ever the optimist!). Adopting this approach would make it harder to revert back to the standard script registration approach.

    3) Manually wait for required libraries to load

    The approach I took was to build a simple wait function to wrap code in that prevented execution until the required scripts had loaded. Each time a script completes, it adds its name to a semaphore array that is waited on by other libraries. The result is much the same as before UR12:

  • The difference here is that the scripts start executing as soon as they are loaded, but then wait using setTimeout until the required scripts have completed. The code will work on pre-UR12 systems as well, but because the wait is unnecessary, the code will simply execute the script without checking dependancies. setTimeout is used to release control to other scripts because Javascript is single-threaded.

    The Code

    Each script that has dependencies must be wrapped in the following:

    waitForScripts("client",["mscorlib", "xrm",],
    function () {
    //Original Code goes here
    });
    

    The first parameter provides the name of the current script, and the array is the list of scripts that must be loaded first.

    So in the Xrm.js library, you would wrap it in:

    waitForScripts("xrm",["mscorlib"],
    function () {
    //Original Code goes here
    });
    

    Each script must also include the waitForScript function at the bottom:

    function waitForScripts(name, scriptNames, callback) {
        var hasLoaded = false;
        window._loadedScripts = window._loadedScripts || [];
        function checkScripts() {
            var allLoaded = true;
            for (var i = 0; i < scriptNames.length; i++) {
                var hasLoaded = true;
                var script = scriptNames[i];
                switch (script) {
                    case "mscorlib":
                        hasLoaded = typeof (window.ss) != "undefined";
                        break;
                    case "jquery":
                        hasLoaded = typeof (window.jQuery) != "undefined";
                        break;
                    default:
                        hasLoaded = window._loadedScripts[script];
                        break;
                }
                allLoaded = allLoaded && hasLoaded;
                if (!allLoaded) {
                    setTimeout(checkScripts, 10);
                    break;
                }
            }
            if (allLoaded) {
                callback();
                window._loadedScripts[name] = true;
            }
        }
        // Only check for async loading of scripts if later than UR12/POLARIS
        if (typeof(APPLICATION_FULL_VERSION)!='undefined' && parseFloat(APPLICATION_FULL_VERSION.replace('5.0.',''))>9690.2835) {
    	setTimeout(checkScripts, 0);
        }
        else {
    	callback();
            window._loadedScripts[name] = true;
        }
    }

    Script# 'Script.template'

    I almost exclusively use Script# in my Dynamics CRM projects. What made this solution really work well for me was that I just include the code in the Script.template file to wrap the '#include[as-is] "%code%"'. The beauty is that I can now forget it's there and it just gets minified along with the rest of the code when deployed.

    This issue has affected a number of people I know, and I'm sure it'll start to become more of an issue as people start to upgrade to the latest Rollup. If you have any suggestions/comments, please let me know.

    The code was originally posted by me in the MSDN forums:

     http://social.msdn.microsoft.com/Forums/en-US/crmdevelopment/thread/fdca4779-e866-4e51-bab9-97a159f9cd37

    @ScottDurow

    Posted on 12. January 2013

    getServerUrl is Deprecated. Use getClientUrl instead.

    Although at the time of writing UR12 is not yet released - I was checking though the changes in the latest SDK documentation.

    In addition to the Ribbon Workbench being listed (yay!) I noticed the following statement about the getServerUrl function

    "Deprecated. Use getClientUrl instead. This method is deprecated as of Microsoft Dynamics CRM 2011 Update Rollup 12 and the Microsoft Dynamics CRM December 2012 Service Update."

     

    Out with the old

     

    Using getServerUrl on it's own always had the potential to cause 'Access Denied' messages if the browser domain Url was different to the server Url stored in the CRM database. This was to do with cross domain access restrictions in IE. A work around was to use a method similar to Daniel Cai gives in his post on the subject - http://danielcai.blogspot.co.uk/2012/02/get-right-server-url-in-your-crm-client.html

    In with the new

    The SDK described the new getClientUrl as:

    "Returns the base URL that was used to access the application."

    The function nolonger blindly return the server Url in the database - but looks at the url that was used to access CRM so that cross domain access issues will be a thing of the past!

    Read more in the SDK:
    http://msdn.microsoft.com/en-us/library/d7d0b052-abca-4f81-9b86-0b9dc5e62a66#BKMK_getClientUrl

    @ScottDurow

     

    Posted on 27. September 2012

    Script# (ScriptSharp) delete statement

    I've been using Script# very succesfully for sometime now to generate CRM2011 Javascripts (you can get a version of the Xrm library for Script# at http://sharpxrmpage.codeplex.com/).

    I problem I encounted recently was where I needed to issue a delete statement. E.g.

    delete xmlDoc;

    There is only Type.DeleteField support in Script# which allows deleting of a field on an object, but there is no way of outputing a simple local scope delete statement.

    I orignally used:

    Script.Literal("delete xmlDoc");

    This worked fine for debug scripts, however when using the release scripts that are minified, of course the variable xmlDoc is output as something like $1_0 and so the literal statement fails.

    After some research I found that the following statement works:

    Script.Literal("delete {0}",xmlDoc);

    Hope this helps.

    Posted on 18. January 2012

    iPad killed the Silverlight star?

    After the initial Statement of Direction in May and then the interview with Brad Wilson the then general manager of Dynamics CRM, there has been quite some excitement about cross-browser support for Dynamics CRM 2011. The timescale given was "the first half of 2012" being release with UR8. The thought of access to Dynamics CRM from an iPad is quite an attractive prospect I have to admit - and I'm really excited to see how Microsoft are going to structure the CRM user interface when they deliver it through cross browser HTML5.

    • Are we going to get a single cross browser interface, or will there still be a IE specific one with richer functionality?
    • Will the cross browser support be more like the mobile client - with it's own form design - Will IE still be the preferred browser?
    • You will still need IE to use the Outlook Client so will the cross browser support be more of a 'selling point' than something that actually makes a significant difference to business users?

     

    Cross browser HTML5 will mean replacing the IE specific .htc 'behaviours' which is quite a considerable piece of work - but a subject closer to my heart is the future of Silverlight in CRM2011. Silverlight is supported on most modern browsers (http://www.microsoft.com/getsilverlight/get-started/install/default.aspx#) but not on iPads. Could this mean an end to Silverlight WebResources just so that we can get iPad support?

    Only time will tell, but it might make it harder to choose the developer productivity and user experience gains that come with Silverlight. The iPad is not a desktop replacement. There are limitations with SalesForce.com on the iPad (http://www.crmverse.com/using-salesforce-com-on-ipad/). Unless a client has decided to use iPads as a primary user interface device, I am still recomending developing Silverlight WebResources but structuring them in a way that will make it easier to port them to an HTML5 user interface when the developer tooling support is improved.

    Maybe with Windows 8 Tablets the problem will go away...then again...maybe not!

    References:

    Posted on 6. January 2012

    Adding an Advanced Find Query to a Form Sub grid

    I've yet to see a clear post on how to add an Advanced Find Query as a subgrid on an entity form for CRM 2011. This is something that was quite common in CRM 4 due to the lack of sub grid support - but with CRM 2011 occationally we come up against the limitation of only showing related records and fields of only a single relationship away from the root entity.

    Here are the steps:

    1. Find the SavedQueryId of the default advanced find view for the entity you are reporting on. Use the following SQL against the MSCRM database to find it:

    Select Name,SavedQueryId,ReturnedTypeCode,FetchXml,LayoutXml from SavedQuery where QueryType=1 and IsDefault=1
    Order by ReturnedTypeCode

    2. Create a webresource named 'new_SubGridFetchXml.htm' of type 'Web Page (HTML)'

    Provide the following html:

    
    <HTML><HEAD><TITLE></TITLE>
    <SCRIPT type=text/javascript src="ClientGlobalContext.js.aspx"></SCRIPT>
    <SCRIPT type=text/javascript>
        function submitForm() {
            var form = document.forms[0];
            var context = GetGlobalContext();
            form.action = context.getServerUrl() + '/AdvancedFind/fetchData.aspx';
            form.LayoutXml.value ='<LAYOUTXML>';
            form.FetchXml.value = '<FETCHXML>'
            form.submit();
             }
    </SCRIPT>
    <META charset=utf-8></HEAD>
    <BODY onload=submitForm()>
    <FORM method=post action="">
    <INPUT name=FetchXml type=hidden> 
    <INPUT name=LayoutXml type=hidden> 
    <INPUT name=EntityName value=contact type=hidden> 
    <INPUT name=DefaultAdvFindViewId value=<SavedQueryID> type=hidden> 
    <INPUT name=ViewId value=<SavedQueryID> type=hidden> 
    <INPUT name=ViewType value=4230 type=hidden> 
    <INPUT name=SortCol value=<SortColumnLogicalName>:1; type=hidden> 
    <INPUT name=UIProvider type=hidden> <INPUT name=DataProvider type=hidden> </FORM></BODY></HTML>
    
    

    Adjust the <SavedQueryID> reference to the default advanced find query id found in step 1.

    Adjust the <SortColumnLogicalName> to be the logical name of the field you want to sort by.

    Replace the LayoutXml and FetchXml strings with the values returned from the query in 1 - you can then adjust them to suit your particular needs.

    IMPORTANT:

    If you add the web resource to a sub 'virtual path', you must alter the reference to the ClientGlobalContext.js.aspx to include the corresponding number of '../'

    Don't worry about the script error when saving the page in the WebResource editor.

    Your finished page would look something like

    
    <HTML><HEAD><TITLE></TITLE>
    <SCRIPT type=text/javascript src="ClientGlobalContext.js.aspx"></SCRIPT>
    <SCRIPT type=text/javascript>
        function submitForm() {
            var form = document.forms[0];
            var context = GetGlobalContext();
            form.action = context.getServerUrl() + '/AdvancedFind/fetchData.aspx';
    form.LayoutXml.value ='<grid name="resultset" object="2" jump="lastname" select="1" icon="1" preview="1"><row name="result" id="contactid"><cell name="fullname" width="300" /><cell name="telephone1" width="125" /></row></grid>';
    form.FetchXml.value = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="contact"><attribute name="fullname"/><attribute name="telephone1"/><attribute name="contactid"/><order attribute="fullname" descending="false"/></entity></fetch>';
                form.submit();
             }
    </SCRIPT>
    <META charset=utf-8></HEAD>
    <BODY onload=submitForm()>
    <FORM method=post action="">
    <INPUT name=FetchXml type=hidden> 
    <INPUT name=LayoutXml type=hidden> 
    <INPUT name=EntityName value=contact type=hidden> 
    <INPUT name=DefaultAdvFindViewId value={00000000-0000-0000-00AA-000000666400} type=hidden> 
    <INPUT name=ViewId value={00000000-0000-0000-00AA-000000666400} type=hidden> 
    <INPUT name=ViewType value=4230 type=hidden> 
    <INPUT name=SortCol value=fullname:1; type=hidden> 
    <INPUT name=UIProvider type=hidden> 
    <INPUT name=DataProvider type=hidden> </FORM></BODY></HTML>
    
    
    

    3. Add the webresource to the form that you want to show it on.

    4. Publish and Go!

    Usual disclaimers apply - and this is not a fully supported solution since it uses un-documented functionality

    Posted on 8. September 2011

    Calling a function in an HTML Web Resource from another JavaScript Web Resource

     

    If you have a field value on an entity form that an Html Web Resource is dependant on, you might have the following in your onload event of the Html Web Resource:

    var crmForm = window.parent.Xrm.Page;
    var lookup = crmForm.getAttribute("customerid").getValue();
    if (lookup != null) customerid = lookup[0].id;
    

     

    If so, will need to ensure that when this value changes, you update the values displayed on the html web resource. Rather than simply re-loading the web resource, it's going to be better for the user if you create a function to update the values without it blanking and re-loading.

    You can do this by implementing a function in your html web resource and then calling it from the onchange event of the dependent field:

    Xrm.Page.getControl("WebResource_<Name>").getObject().contentWindow.window.fooBar();
    

    Hope this helps.

     

    Posted on 3. June 2011

    Embedding CRM reports in IFrames using the CRM2011 Report Viewer

    It is quite common for users to request that reports be included in Iframes on forms, or in Dashboards. Although this can be done by pointing the Iframe directly at SQL Reporting services, this essentially bypasses the CRM Report connector and relies on the user authenticating directly with SQL Reporting Services and SQL Server. If you have SQL Server and Reporting Services on the same server or Kerberos is set up correctly, this isn't a problem. If however your users can only access the report through the CRM Front end, then you need to use the CRM Report review rather than the Reporting Services native one.

    1) Create a html web resource named 'ReportViewer' or similar

    <html>
    <head>
    <title></title>
    <script src="ClientGlobalContext.js.aspx" ></script>
    <script type="text/javascript">
        function submitForm() {
            var form = document.forms[0];
            var context = GetGlobalContext();
            form.action = context.getServerUrl() + '/CRMReports/rsviewer/reportviewer.aspx';
            form.uniquename.value = context.getOrgUniqueName();
    
            var query = window.location.search.substring(1);
            var vars = query.split("&");
            var idSet = false;
    
            for (var i = 0; i < vars.length; i++) {
                var pair = vars[i].split("=");
                switch (pair[0]) {
                    case 'data':
                        var params = unescape(pair[1]).split("&");
                        for (var j = 0; j < params.length; j++) {
                            var param = params[j].split("=");
                            switch (param[0]) {
                                case 'id':
                                    form.id.value = param[1];
                                    idSet = true;
                                    break;
                                case 'hideparams':
                                    form.PromptAreaCollapsed.value = param[1];
                                    break;
                                case 'iscustomreport':
                                    form.iscustomreport.value = param[1];
                                    break;
                                default:
                                    // Add any other values as report parameters
                                    var paramInput = document.createElement('input');
                                    paramInput.setAttribute('type', 'hidden');
                                    paramInput.setAttribute('name', 'p:' + param[0]);
                                    paramInput.setAttribute('value', param[1]);
                                    form.appendChild(paramInput);
                                    break;
                            }
                        }
                }
            }
            
    
            if (idSet) {
                form.submit();
            }
            else {
                // Show message
                var dvMessage = document.createElement("div");
                dvMessage.innerHTML ="Report Id is not set";
                form.appendChild(dvMessage);
    
            }
        }
        
    </script>
    </head>
    <body onload="submitForm()">
    <form action ="" method="post">
    <input type="hidden" name="id" value="{xxx}" />
    <input type="hidden" name="uniquename" value="" />
    <input type="hidden" name="iscustomreport" value="true" />
    <input type="hidden" name="reportName" value="Report Name" />
    <input type="hidden" name="isScheduledReport" value="false" />
    <input type="hidden" name="PromptAreaCollapsed" value="false" />
    </form>
    </body>
    </html>
    
    
    

    2) Include in a Dashboard or Webresource on a form using the following Web Resource Properties (The Customer Parameter Data field):

    Id={xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} (make sure you add the curly braces)

    You can also pass any report parameters you have by adding on :

    param1=value1&param2=value2

    If you want to have the report parameters hidden by default use:

    &hideparams=true

     

    So the whole data field would be something like:

    Id={xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}&hideparams=true&param1=value1&param2=value2

    I don't want to spoil all your fun – so I'll leave you to add the query to lookup the id by passing in the report name rather than the ID!

    Have fun!

    Posted on 18. May 2011

    CRM Developer ‘Must Know’ #2: Web Resource Caching

    With the introduction of Web Resources in CRM 2011 the task of adding custom user interface functionality (beyond simple JavaScript) has become a whole lot easier to build and deploy. The fact that web resource are part of the solution means that there is no need to have custom deployment routines to create sites in the ISV folder.

    You always want your users to have the faster experience possible, so it is important in each of these situations to ensure that your web resources are being cached by the Client browser to avoid downloading a copy with each request.

    How Web Resources are cached

    CRM 2011 uses an very simple but effective means of ensuring that not only Web Resources are cached by the browser, but when you update any of them, the cache is invalidated and the new version is downloaded.

    When a Web Resource is referenced, although the Url shown on the Web Resource form is something like:

    http://crm/Contoso/WebResources/new_/custom_page.htm

    If you request this url in a browser, and use Internet Explorer's F12 Developer Tab Network monitor, you'll see that the response has the following Headers:

    Cache-Contro:    private
    Expires:     <Today's Date Time>
    

    This instructs the browser/proxy server that is should never cache this file, and every time the browser asks for it, get the latest version from the server. This may not seem so bad for the odd file, but if you add all the files the browser needs for every page request and then multiply by the number of users you have, it introduces a considerable network load and download time.

    So how are they cached?

    When the Web Resource is referenced by a CRM 2011 page, the following format is used:

    http://crm/Contoso/%7B634411504110000000%7D /WebResources/new_/custom_page.htm

    If you look at the response headers you'll now see:

    Cache-Control:    public
    Expires:     <Today's Date Time Plus One Year>
    
    

    So the browser/proxy server will keep a copy of this web resource for a year and use that before it goes to get the latest version.

    So how is the client cache cleared when I publish a new version of the web resource?

    The additional %7B634411504110000000%7D is the 'cache directory' and every time the customisations are re-published is updated to a new number. Since the browser/proxy cache is linked to the url of the file, if the url changes, the cache is no longer valid, and it is considered a new file altogether to be downloaded.

    So how do we ensure that this cache strategy is always used?

    Luckily, in most cases CRM 2011 handles this for us providing we play by its rules:

    Among the options for showing a Web Resource

    1) Embedding in an Entity form
    2) Showing from a site map link
    3) Ribbon button image
    4) A Popup dialogue from a Ribbon button via a javascript function.

    Here are the ways to ensure caching in each of these scenarios:

    1) Embedding in an Entity form

    If you embed a web resource in an Entity form, the cache directory is automatically used for you. However, if that web resource is an html page, ensure that you use relative paths in any javascript/css/image links so that you always stay in the cache directory.

    E.g. If you have the following webresources:

    • new_/Account/AssignAccount.htm
    • new_/scripts/Common.js

    In your AssignAccount.htm page, reference relatively using:

    <script src="../scripts/Common.js" type="text/javascript"></script>
    

    Do not use absolute:

    <script src="/Webresources/scripts/Common.js" type="text/javascript"></script>
    

    2) Showing from a site map link

    When showing an html page from a site map link, you can ensure that CRM uses the cache directory by avoiding absolute paths and use the $webresource token in the SubArea definition:

    <SubArea 
         Id="nav_assignaccount"
         PassParams="0" 
         Client="All" 
         Url="$webresource:new_/Account/AssignAccount.htm" 
         Icon="$webresource:new_/icons/assign16.gif">
    

    3) Ribbon button image

    When referencing images from site maps/ribbons, use the $webresource token as above rather than using the absolute path of the web resource.

    4) - A Popup dialogue from a Ribbon button via a JavaScript function

    UR8 or later 

    UR8 introduced a new utility function called 'openWebResource' - this will ensure that if you need to show a popup window with a webresource, caching is used rather than having to provide an absolute path:

    Xrm.Utility.openWebResource("new_page.htm");

    This results in a url being used along the lines of:

    http://server/Org/%7B634940089750000000%7D/WebResources/new_page.htm

    Before UR8

    Prior to UR8, this final one poses a bit more of a challenge. If you want to show a popup window that references a web resource page, before UR8 there was no way in the customisations to achieve this – it has to be JavaScript which means you need to construct the cache directory web resource location yourself. This is biggest area where I see developers writing code resulting in the browser always downloading the file with every request.

    To avoid this, you can find the current cache directory from another web resource that is currently loaded using something similar to:

    function GetCacheDirectory(webresourceUrl) {
        var scripts = document.getElementsByTagName("script");
        for (var i = 0; i < scripts.length; i++) {
            var url = scripts[i].src;
            var p1 = url.search("/%7B")
            if (p1 > 0) {
                var p2 = url.search("%7D/", p1) + 4;
                var resourceCache = url.substr(p1, (p2 - p1));
                break;
            }
        }
         var url = "/" + Xrm.Page.context.getOrgUniqueName() + resourceCache + webresourceUrl;
        return url;
    }
    

    And calling it using:

    // Get cache directory resource url
    var url = GetCacheDirectory('/WebResources_/new/Account/AssignAccount.htm');
    
    // Open Window
    window.open(url);
    
    

    Of course - I would recommend you upgade to the latest rollup if you don't have UR8 installed yet!

    If you follow these steps, you will ensure that your user's browser only download files when they need to resulting in less network load and faster load time.

    Happy caching!

    Posted on 16. March 2011

    How to change the default Lookup type on ‘Customer’ fields

    In CRM 4, this used to be a very popular request:
    "Please change the default lookup type on the customer field to contact"
    Also…
    "Please limit the lookup type on the customer field to only allow selection of contacts"
    In CRM 2011, there still doesn't seem to be an easy way of doing this, so we have to revert to 'unsupported' means:
    document.getElementById("customerid").setAttribute("lookuptypes", "2");
    document.getElementById("customerid").setAttribute("defaulttype", "2");
    
    If I find a more supported way, I'll update this post.
    Posted on 22. February 2011

    How to clear options without leaving an empty entry

    Using the clearOptions method on PickLists, can leave an empty entry because Drop Down lists in HTML must have at least one entry.

    To work around use the following code that removes all options other than the current value to avoid the empty entry.

    var attribute = Xrm.Page.getAttribute(“attributename”);
    // NOTE: We can't use clearOptions since this adds an empty option 
    // attribute.controls.forEach(function (control) { control.clearOptions(); })
    
    for (var j = 0; j < attribute.getOptions().length; j++) {
    	if (attribute.getValue() != attribute.getOptions()[j].value) {
    		attribute.controls.forEach(function (control) {
    			control.removeOption(attribute.getOptions()[j].value);
    		});
    	}
    
    };
    
    

    If this doesn't work for you, the empty entry can be manually removed using:

    
    attribute.controls.forEach(function (control) { 
    control.removeOption('') 
    });