Tag Archives: javascript

CRM 2011, OData and datajs

UPDATE: FALSE ALARM about batch updates.  Turns out the CRM 2011 OData service doesn’t support batch updatesSad smile.  I just assumed it did because I’ve written .NET/Silverlight clients against the CRM 2011 OData service using a derived DataServiceContext.  DataServiceContext based contexts using the “unit of work” pattern through SaveChanges()/BeginSaveChangesc() and they’ve always “just worked.”  I just confirmed that Batch isn’t the default SaveChangesOption so all this was predicated on a bad assumption on my partSad smile.  Nothing to see here.

What people often call the CRM 2011 REST Service or Organization Data Service is an OData service.  If you go to the libraries section of the OData site and select JavaScript, you will be linked to the datajs CodePlex site.  So what about the CRM SDK OData samples (SDK.REST.js and SDK.JQuery.js)?  It’s probably one of those “six in one hand, half dozen in the other” situations in terms of the shared capabilities of the libraries.  Which API you like better probably boils down to a style preference.  There are some differences in capabilities as well.  The CRM SDK libraries have explicit functions to associate / disassociate records.  One of the things the CRM SDK sample libraries don’t offer is a facility to do batch updates, but datajs does.  This comes in really handy when you want to do a bunch of work client side to some data and send it all at once.  Have a look at the OData Code Snippets.  They also have a full OData API reference.

@devkeydet

Geocoding and displaying a map for an address in CRM 2011

There are a few blog posts out there that cover this topic, but the ones I’ve found don’t take you through it step by step.  I will, however, make assumptions like you know how to create an entity, add web resources to a form, etc.  Here goes…

The overall solution is going to consist of:

If you are a “yea yea, blog blah blah, just give me the code” type then jump to the bottom of the postSmile.

Let’s get started.  First, you will need the Developer Toolkit for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online and NuGet installed.  Let’s create an entity called GeocodeMapSample. As a habit, I tend to uncheck all the entity defaults after the Options for Entity section of the form.  You can always go back and turn additional features on as you need them after you create the entity.

image

Next create the following fields (all of type single line of text):

  • AddressLine1
  • AddressLine2
  • City
  • State
  • Zip
  • Latitude
  • Longitude

Yours should look like mine except you will have a different prefix than dkdt_.

image

Finally, put everything on the form:

image

Once you’ve done all this, go ahead and Publish your changes.  Next, create a new Dynamics CRM 2011 Package project called GeocodeMapSamplePackage:

image

Fill out the Connect to Dynamics CRM Server dialog:

image

If you are using CRM Online, then your discovery service is dev.crm.dynamics.com if you log in with a Live ID or disco.crm.dynamics.com if your org was provisioned through Office 365.  Make sure you select HTTPS as the protocol if necessary.  Add a Dynamics CRM 2011 Plug-in Library called GeocodMapSamplePlugins to the Visual Studio solution:

image

Find the GeocodeMapSample entity in the Entities node of CRM Explorer, right-click it and select Create Plug-in:

image

Make your dialog look like mine:

image

I am picking the Pre-Operation for the Pipeline Stage because I want to be able to throw an exception back to the caller and roll the transaction back if geocoding fails because my business requirement is that addresses must be valid.  Create another plug-in for the same entity.  This time, we want to do a few things differently.  First, set the value of Message to Update, but DO NOT click OK yet:

image

We only want this plug-in to fire when address related fields change.  Click the Filtering Attributes ellipsis (…) to bring up the following dialog:

image

Make sure you have deselected all of the fields, then ONLY selected items in the image.  In an update scenario, we want to make sure the plugin has the previous values from when the form was loaded.  Click the Parameters ellipsis (…) next to Pre Image Alias to bring up a similar dialog to before and select the same set of attributes.  We’re going to ask the Bing Maps REST service for JavaScript Object Notation (JSON) since it’s the fastest across the wire.  Add the DynamicJson package from NuGet to the plug-in project:

image

image

Now it’s time to generate some early-bound types.  The CRM SDK covers early-bound vs. late-bound here.  Most developers prefer the early bound approach unless they truly need a late-bound for writing very generic, reusable code.  This is because you become more productive and less error prone due to intellisense and compile time checking.  While the developer toolkit does have the ability to generate strongly typed classes:

clip_image002

…it does not give you any control over what classes get generated.  It just brute force creates one for each entity you have read permissions to.  Therefore, you end up generating a bunch of unnecessary code that bloats your codebase and makes your plugin WAY BIGGER than it needs to be.  Instead, I prefer to use this approach to generate the classes:

http://dkdt.me/LO71gI

Which one you choose, is up to you.  Generate Wrapper is OK for now since you are just learning, but take my advice and learn the approach above for production codeSmile.  Add a new class to the plug-in project called GeocodeMapSampleCommon.cs.  Replace the entire contents of the class declaration with the following code:

using System;

using System.Net;

using System.Text;

using Codeplex.Data;

using GeocodeMapSamplePackage.GeocodeMapSamplePlugins.Entities;

using Microsoft.Xrm.Sdk;

 

namespace GeocodeMapSamplePackage.GeocodeMapSamplePlugins

{

    class GeocodeMapSampleCommon

    {

        internal static void GeocodeAddress(Plugin.LocalPluginContext localContext, Entity preImageEntity)

        {

            if (localContext == null)

            {

                throw new ArgumentNullException("localContext");

            }

 

            var pluginExecutionContext = localContext.PluginExecutionContext;

            var targetEntity = pluginExecutionContext.InputParameters["Target"] as Entity;

 

            if (targetEntity == null)

            {

                throw new NullReferenceException("targetEntity");

            }

 

            try

            {

                var targetGeocodeMapSampleEntity = targetEntity.ToEntity<dkdt_GeocodeMapSample>();

                var sb = new StringBuilder();

                

                // see http://dkdt.me/KH4roL for Bing Maps REST API reference

                const string restQueryStart = "https://dev.virtualearth.net/REST/v1/Locations/";

                const string noAddress = restQueryStart + ",,,,";

                var address1 = string.Empty;

                var address2 = string.Empty;

                var city = string.Empty;

                var state = string.Empty;

                var zip = string.Empty;

 

                sb.Append(restQueryStart);

 

                if (preImageEntity != null)

                {

                    var previousGeocodMapSampleEntity = preImageEntity.ToEntity<dkdt_GeocodeMapSample>();

 

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 == null && previousGeocodMapSampleEntity.dkdt_AddressLine1 != null)

                        address1 = previousGeocodMapSampleEntity.dkdt_AddressLine1;

                    if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 == null && previousGeocodMapSampleEntity.dkdt_AddressLine2 != null)

                        address2 = previousGeocodMapSampleEntity.dkdt_AddressLine2;

                    if (targetGeocodeMapSampleEntity.dkdt_City == null && previousGeocodMapSampleEntity.dkdt_City != null)

                        city = previousGeocodMapSampleEntity.dkdt_City;

                    if (targetGeocodeMapSampleEntity.dkdt_State == null && previousGeocodMapSampleEntity.dkdt_State != null)

                        state = previousGeocodMapSampleEntity.dkdt_State;

                    if (targetGeocodeMapSampleEntity.dkdt_Zip == null && previousGeocodMapSampleEntity.dkdt_Zip != null)

                        zip = previousGeocodMapSampleEntity.dkdt_Zip;

                }

 

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine1 != null)

                    address1 = targetGeocodeMapSampleEntity.dkdt_AddressLine1.Trim();

                sb.Append(address1);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_AddressLine2 != null)

                    address2 = targetGeocodeMapSampleEntity.dkdt_AddressLine2.Trim();

                sb.Append(address2);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_City != null)

                    city = targetGeocodeMapSampleEntity.dkdt_City.Trim();

                sb.Append(city);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_State != null)

                    state = targetGeocodeMapSampleEntity.dkdt_State.Trim();

                sb.Append(state);

                sb.Append(",");

                if (targetGeocodeMapSampleEntity.dkdt_Zip != null)

                    zip = targetGeocodeMapSampleEntity.dkdt_Zip.Trim();

                sb.Append(zip);

 

                var restQuery = sb.ToString();

 

                if (restQuery == noAddress)

                {

                    return;

                }

 

                // TODO: Move key to plugin configuration

                const string key = "?key=[INSERT_YOUR_BING_MAPS_KEY]";

 

                var webClient = new WebClient();

 

                var jsonString = webClient.DownloadString(restQuery + key);

                var response = DynamicJson.Parse(jsonString);

 

                if (response.statusCode != 200)

                {

                    throw new InvalidPluginExecutionException("Bad address.  Please fix it and try again.");

                }

 

                var coordinates = response.resourceSets[0].resources[0].point.coordinates;

                targetGeocodeMapSampleEntity.dkdt_Latitude = coordinates[0].ToString();

                targetGeocodeMapSampleEntity.dkdt_Longitude = coordinates[1].ToString();

            }

            catch (Exception ex)

            {

                throw new InvalidPluginExecutionException("Something went wrong in the plug-in.  This must be buggy sample code eh?", ex);

            }

        }

    }

}

This code does all the geocoding work.  Your generated classes will have different prefix values than dkdt_, so fix the errors by replacing dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Because of the way I am sharing this class across multiple plugins we, need to make a slight tweak to the Plugin.cs class.  Change protected class Plugin : IPlugin to protected internal class Plugin : IPlugin.  Now is a good time to build.  If you get build errors, well fix em Smile.  It’s a blog post after all.  Assuming you got your build errors fixed, there are a couple more things to do.  Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

GeocodeMapSampleCommon.GeocodeAddress(localContext, null);

Open PreGeocodeMapSampleCreate.cs and replace the contents of  the ExecutePreGeocodeMapSampleCreate method with:

if (localContext == null)

{

    throw new ArgumentNullException("localContext");

}

 

var context = localContext.PluginExecutionContext;

 

var preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(preImageAlias)) ? context.PreEntityImages[preImageAlias] : null;

 

GeocodeMapSampleCommon.GeocodeAddress(localContext, preImageEntity);

Probably a good time to build and troubleshoot again.  If your build is successful, then sign the plug-in assembly:

image

image

Ok, now you should be able to right-click the package project and deploy:

image

This will register your plug-in and messages.  Now go create a new GeocodeMapSample entity through the CRM UI.  Save, but don’t save and close.  You will see Latitude and Longitude have values after the save.  So now we have to present this stuff on a map.  Let’s do the easy one first: static map image.  Go back to Visual Studio and add a new web resource to the package project:

image

image

Replace the contents of StaticMap.htm with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <script type="text/javascript">
   1:  

   2:             function renderStaticMap() {

   3:                 // ReSharper disable InconsistentNaming

   4:                 var Xrm = window.parent.Xrm;

   5:                 // ReSharper restore InconsistentNaming

   6:  

   7:                 var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

   8:                 var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

   9:  

  10:                 if (latitude == null) {

  11:                     return;

  12:                 }

  13:                 if (longitude == null) {

  14:                     return;

  15:                 }

  16:  

  17:                 // see http://dkdt.me/KH4roL for Bing Maps REST API reference

  18:                 var imageUrl = "http://dkdt.me/KH4rF4" +

  19:                 latitude + "," + longitude +

  20:                     "/18?key=[INSERT_YOUR_BING_MAPS_KEY]";

  21:                 document.getElementById("mapImage").setAttribute("src", imageUrl);

  22:             }

  23:         

</script>

    </head>

    <body onload="renderStaticMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <img id="mapImage" alt="An map image of the current record" src=""/>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Go through the same process to build a web resource named InteractiveMap.htm.  Replace the contents of the file with the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>

    <head>

        <title></title>

        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

        <script type="text/javascript" src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1"></script>
   1:  

   2:         <script type="text/javascript">

   3:         var _map;

   4:  

   5:         function loadMap() {

   6:             _map = new Microsoft.Maps.Map(document.getElementById("map"), { credentials: "[INSERT_YOUR_BING_MAPS_KEY]" });

   7:  

   8:             // ReSharper disable InconsistentNaming

   9:             var Xrm = window.parent.Xrm;

  10:             // ReSharper restore InconsistentNaming

  11:  

  12:             var latitude = Xrm.Page.getAttribute("dkdt_latitude").getValue();

  13:             var longitude = Xrm.Page.getAttribute("dkdt_longitude").getValue();

  14:  

  15:             if (latitude == null) {

  16:                 return;

  17:             }

  18:             if (longitude == null) {

  19:                 return;

  20:             }

  21:  

  22:             var location = new Microsoft.Maps.Location(latitude, longitude);

  23:             var pushpin = new Microsoft.Maps.Pushpin(location);

  24:             _map.setView({ center: location, zoom: 10 });

  25:             _map.entities.push(pushpin);

  26:         }

  27:         

</script>

    </head>

    <body onload="loadMap();" style="BORDER-RIGHT-WIDTH: 0px; BACKGROUND-COLOR: rgb(246,248,250); MARGIN: 0px; PADDING-LEFT: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; PADDING-TOP: 0px">

        <div id="map" style="position: relative; width: 100%; height: 100%"></div>

    </body>

</html>

Again, replace dkdt_ with your prefix.  You will also have to replace the [INSERT_YOUR_BING_MAPS_KEY] string with yours from http://dkdt.me/KH4pNx.  Drop the web resource in the GeocodeMapSample entity form.  Drop the web resource in the GeocodeMapSample entity form.  Publish it all and you should find a static map image on the form next time you load/refresh it.  Finally, Latitude & Longitude really are just there for code interaction.  They don’t need to be visible on the form.  Go ahead and hide them.

That’s it!  If you made it through the whole post, you now know the fundamentals of using plug-ins, web resources, and the Bing Maps SDKs to add basic “geospatial” functionality to CRM 2011.  Of course, you probably won’t want to scatter your Bing Maps key through out your code, but I wanted to keep the walkthrough as simple to follow as possible.  You can grab the unmanaged solution and Visual Studio source code here:

http://sdrv.ms/KH4j8D

@devkeydet

Minify Selection in Visual Studio 2010

You learn something new every day!  One of the things I’ve been talking to CRM folks about a bit lately is Minifying your CRM 2011 JavaScript and CSS.  Well, I just discovered that when you install the Microsoft Ajax Minifier, you get a handy little context menu option to minify.

image

It shows up in both .js and .htm files when you select the JavaScript.  Pretty cool!

@devkeydet

Using the CRM 2011 OData service from a Metro style app

UPDATE: Fixed a few bugs in the downloadable sample including one where it wouldn’t work in Office 365 based CRM Online subscriptions.  Edited some text in the post for clarity.

UPDATE2: Added some text about how to get this working for Windows Authentication.

UPDATE3: I’ve just blogged about an update to the sample here.  The updates in the code might make following this post a little confusing.  The reason is that due to the final terminology for Windows Store Apps, I needed to refactor the solution/project files and class names.  While I plan on updating this post soon, I wanted to get the changes published.  Hopefully, the naming changes are obvious enough for folks until I update this post. 

Scenario:

“I want to build a Windows 8 Metro style application.  Since my app is going to just perform CRUD operations, I want to use the OData service using json so that I have the least amount of ‘data across the wire’ possible.”

DISCLAIMER: This a pretty hacky approach, but it works and is the only way I know of to do it.  If you like it, use it.  If not, wait until there’s an official sample in the SDK.  You’ve been warned.  I’m just a guy getting creative to solve a problemSmile.

First I will walk you through how to do it.  Then I will explain how it works.  To get started, download and import the following solution into you CRM 2011 organization:

http://sdrv.ms/S3MND1

Next, download the source code of my sample library:

http://sdrv.ms/RYzVzj

I say sample library very loosely.  This is FAR from being a complete, well tested library.  I have good intentions to make it one, but wanted to get a sample of the basic plumbing working for those of you who have asked me how to do it.  This sample should be enough to get you started, but you’ll have to finish out the rest of the functionality.  If anyone wants to take this sample and turn it into an open source project, YOU HAVE MY FULL PERMISSIONSmile.

USING THE SAMPLE

Create a new Metro style app.  I am going to use the Blank App template to keep things simple:

image

Add my sample library to the solution:

image

Don’t forget to right-click References nod of the Crm2011MetroStyleODataApp in Solution Explorer and a reference to the project you just added:

image

image

Find the Devkeydet.CrmMetroODataHelper.csproj file and add it.  Replace the default Grid in MainPage.xaml with the following:

<Grid x:Name="LayoutRoot" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel x:Name="MainUIStackPanel" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed">

        <TextBlock x:Name="myTextBlock" Text="TextBlock" TextAlignment="Center" />

        <Button x:Name="queryButton" Content="Button" />

    </StackPanel>

</Grid>

In MainPage.xaml.cs, paste the following code into the OnNavigatedTo method:

var oDataHelperWebResource = "https://devkeydet.crm.dynamics.com/WebResources/dkdt_/MetroODataHelper.htm";

 

if (!CrmMetroODataHelper.IsLoggedIn)

{

    CrmMetroODataHelper.SignIn(oDataHelperWebResource, LayoutRoot);

    CrmMetroODataHelper.SignInComplete = () => MainUIStackPanel.Visibility = Visibility.Visible;

}

Make sure to replace “https://devkeydet.crm.dynamics.com” with the right url for you.  You will have to add a using Devkeydet statement to the file.  Ok, now it’s time to “Add Service Reference” to the OData service.  Unless you are using Windows Authentication, you can’t point the “Add Service Reference” dialog to the data service url.  Instead, you need to download it and point to it locally, from Visual Studio:

image

image

Save it somewhere and point, then “Add Service Reference” by pointing Visual Studio to the file:

image

Add the following method to MainPage.xaml.cs:

private  async void ExecuteODataQueryAsync()

{

    var ctx =

        new devkeydetContext(new Uri("https://devkeydet.crm.dynamics.com/XRMServices/2011/OrganizationData.svc/"));

 

    // Use the generated DataServiceContext to compose the query using LINQ, 

    // but get the string of the query via ToString().

    // We still get to use the query composition capabilities of LINQ!

    var query = from a in ctx.AccountSet

                where a.Name.StartsWith("A")

                select new Account

                {

                    Name = a.Name

                };

 

    var accounts = await CrmMetroODataHelper.RetrieveMultipleRecordsAsync<Account>(query.ToString());

 

    myTextBlock.Text = accounts.First().Name;

}

Again, remember to replace “https://devkeydet.crm.dynamics.com” with the right url for you.  You will have to add a using statement based on the Namespace you gave the generated code and devkeydetContext will need to be replaced with your generated context name.  Now, wire up an event handler for the button (ex: double click the button on the design surface) and call the method:

ExecuteODataQueryAsync();

If your app is using Windows Authentication, then you need to double-click on the Package.appxmanifest and make sure you have the following Capabilities checked:

image

Go ahead and run the app.  You will be prompted for your credentials if necessary:

image

Sign in with your credentials, click the button, and the TextBlock should have the value of the Name property of the first result:

image

There you go!  You just issued an OData query from a C# Metro style app!

HOW THE SAMPLE WORKS

The sample wraps the WebView control.  The WebView control tries to load an html web resource from the CRM server (that’s what’s in the devkeydetMetroODataHelper_1_0.zip solution).  If you haven’t logged in, then the WebView is set to be visible and it will just present you with the web based login UI.  Once logged in, the WebView visibility is collapsed and the C# wrapper basically proxies OData calls through WebView via the web resource using a modified version of the SDK.REST.js file from the CRM 2011 SDK.  The major modification is that the default SDK.REST.js sample will turn the resulting json string into an JavaScript object.  Instead, I just pass the json string back to the C# wrapper and use JSON.NET to turn it into C# objects.

As I mentioned earlier, this is FAR from being a complete and reusable library.  I have good intentions to make it one, but wanted to get a sample of the basic plumbing working for those of you who have asked me how to do it.  This sample should be enough to get you started, but you’ll have to finish out the rest of the functionality.  I only implemented executing a query that returns multiple results.  Furthermore, I didn’t handle continuation tokens.  If anyone wants to take this sample and turn it into an open source project, YOU HAVE MY FULL PERMISSIONSmile.

@devkeydet

Cross domain calls from JavaScript

It’s exciting times for web developers.  Many of the hoops we used to have to jump through are being eliminated as more browsers support more of the HTML5 spec and more people are using modern browsers.  One of the things that has me excited is Cross-origin resource sharing (CORS)

In the context of Dynamics CRM, it means that more can be done from HTML and JavaScript web resources without having to do things like proxy calls through a server.  It also reduces the scenarios that require you to implement Cross domain calls to the parent CRM 2011 form.  The need to replace window.parent with window.postMessage when you truly do need to communicate cross domain across iFrames.  However, I’ve used the window.postMessage approach to have a hidden iFrame in a CRM form that is a blank ASP.NET page that calls a third party web service from server-side code, then passes the response to a the client, which then passes the results to the CRM form using window.postMessage and the form code prefills controls in the form.  All this hoop jumping is necessary because of the way older browsers work.  The complexity could be reduced with CORS. 

The rub is that there are still browsers out there that don’t support CORS.  The wikipedia link covers which.  The good news is that modern browser adoption is growing fast.  We’re approaching a time where there is a small enough group of the older browsers where you might feel comfortable displaying a message to your users saying “Are those cobwebs I see on your browser?  Please upgrade to use this app.”  Not everyone can do that, but if you can, then read on.

IE10 supports CORS through the XMLHttpRequest object.  IE8 & 9 support it, but the through the XDomainRequest object.  I don’t know about you, but I don’t XMLHttpRequest directly anyway.  I typically use the jQuery $.get(), $.getJSON(), or $.ajax() methods.  There is a helper library for jQuery called jQuery.iecors that allows you to use jQuery unchanged.  The last thing you need to know is that the server you are calling to has to allow CORS.  Thanks to enable-cors.org, you have a handy reference to know how enable it on your server. 

@devkeydet

Using RequireJS with CRM 2011 forms

GOAL: Simplify referencing JavaScript libraries from CRM forms for code-centric developer types like me.

I have always been a fan of “code beside” JavaScript files that contain all my main code for a corresponding HTML page.  For example, if I have a page called hello.htm, then I will have a file called hello.htm.js that is the main code file for that page.  One of the things I have always been a bit frustrated with when using this approach is that all the JavaScript libraries my hello.htm.js page uses are actually referenced in the HTML page.  This leads to a lot of switching back and forth between the files when you need to add script references.  It also becomes a burden when you are reviewing code you haven’t worked with in a while.  You inevitably have to switch to the hello.htm page to familiarize yourself with what other js files the page references.  My brain has challenges with this context switch.  Coming from a .NET development background, I am used to namespaces at the top of my C# files that help me with this.  I’ve recently gotten hip to RequireJS which is a library that makes it really easy to add JavaScript references to a page right from JavaScript.  While you can technically do this without RequireJS, the library wraps the verbose code and all the cross browser nuances plus adds some nice features.  Check it out!

Ok, now to the CRM 2011 context…

I’ve always found it a bit cumbersome to have to “point and click” my way to adding JavaScript references to a CRM 2011 form.  I totally get that this is useful for building reusable JavaScript libraries that non-devs can use / configure by passing parameters.  However, my mode of operation is usually to have a single JavaScript web resource for the form that programmatically wires up events, etc.  Sound familiar?  I usually think of it as my “code beside” or “View Model” for the form.  Since I often have reusable libraries I want to reference from my primary JavaScript web resource, it would be nice not to have to “point and click” my way to adding them.  Thereby having a single JavaScript file that I can read top to bottom to understand everything that’s going on in the form.  Here’s how I do it…

Deploy RequireJS as a web resource and add it to your form, then add another web resource to the form that contains your “View Model” for the form:

image

In my case the “View Model” resource is called dkdt_/scripts/requirejsttest.js.  Make sure you’ve wired up the form to call the starting function in your web resource.  In my case the function is called dkdt_OnLoad.  Here’s the code for the requirejstest.js web resource:

function dkdt_OnLoad() {

    var serverUrl = Xrm.Page.context.getServerUrl();

    var requiredScript = serverUrl + "/WebResources/dkdt_/scripts/somerequiredscript.js";

 

    require([requiredScript], function (m) {      

        dkdt_ConfirmThisLoaded();

    });

}

The source code for the somerequiredscript.js web resource looks like:

function dkdt_ConfirmThisLoaded() {

    alert("The required script loaded");

}

This may seem straightforward, but figuring out what to pass to the require() function took a little investigation.  When using IE10s “F12 Developer Tools,” I noticed that the JavaScript web resources loaded into the form are children of a page called edit.aspx which lives under [serverUrl]/userdefined:

image

The only consistent way I could figure out to tell RequireJS to load the right web resource was to build the full url programmatically.  If you know of a better way, let me know in the comments. 

The way I use this approach is that all of my forms always have a “point and click” reference to require.js and the main JavaScript web resource for the form (requirejsttest.js in this example).  I tend to make the initial function the same for every form as a convention (dkdt_OnLoad in this example).  Everything else, including event wire up and additional JavaScript file references are done in the main web resource of the form.  This gives me a single file to read and understand what’s going on in the form, including what additional JavaScript web resources the code depends on without having to “point and click” my way to finding out.

@devkeydet

Cascading option set dropdowns with CRM 2011

I get asked this once or twice a month.   

“Does CRM 2011 allow me to configure cascading dropdowns out of the box?”

There is no “out of the box” way to do this using point and click customization through the CRM UI.  I find that when people hear “not out of the box” they immediately think “must write code.”  I see this all the time in xRM implementations.  Lots of cut and paste code that does the same thing.  Developer minded folks tend to have that “I can turn this into a reusable library that power users can ‘configure’ by passing parameters to the web resource.”  That’s the key, build your web resources to be reusable where possible. 

What power users really want is a reusable way to apply the cascading dropdown functionality such that they don’t have to ask a developer to write JavaScript code in the form each time.  CRM 2011 extensibility through the SDK to the rescue! While it’s possible to “roll your own” reusable solution using web resources and the SDK, an SDK sample already exists that does this for you:

http://dkdt.me/IxaX0l

This offers a “no code” solution for the power user because the code is already written.  All the power user needs to know is how to wire up the web resources on the form/field events and what parameters to pass to get it all working. 

UPDATE: Someone pointed out that “in 2011 you can create dependent options sets OOB by using custom entities and filtered lookups.”  That’s an excellent point.  It really depends on whether you want to use lookups or dropdowns.  I do plan on putting together a sample that builds off of my Displaying a lookup as a dropdown in a CRM 2011 form sample to show the filtered lookup approach. 

@devkeydet

New Features for the CRM 2011 JavaScript Model Generator

I blogged about this handy community tool before.  See here.  I just noticed that there’s an update version, as of 3-21-2012, with some new features.  From the Release Notes:

Now supports mult-forms and explicitly setting which attributes, tabs, sections, and left navs to include in the model. Check the documentation for more details.

I requested those features, so I am even more excited about this tool to increase productivity when writing JavaScript code that interacts with CRM 2011 forms.

@devkeydet

SAMPLE: Editable grid for CRM 2011 using jqGrid

A while back, I wrote a post called Building an editable grid for CRM 2011.   The goal of the post was to give you the necessary knowledge to know how to write the code to build an editable grid.  Since then, someone introduced me to jqGrid.  This is quite a powerful JavaScript grid control.  Have a look at the site to see all the things you can do with it.

I’ve built a little sample for a customer using jqGrid.  I wanted to spend some time cleaning the code up and making it more reusable before I blogged about it.  My lofty goal was for it to be a fully reusable editable grid control for CRM.  My thinking was that all you would have to do is drop a web resource on the form, pass the web resource a set of parameters (child entity, columns to display, etc.), and the web resource would render a custom grid based on what you passed it.  Well, as most of my lofty goals for side projects go, I haven’t found the time to get it where I want it to be.  Since then I’ve promised a number of folks to at least share what I have until I can get around to turning this into what I want it to be. 

To get started, download the sample from here.  Unzip the contents somewhere.  If you want to open the Visual Studio solution, you will need the Developer Toolkit for Microsoft Dynamics CRM installed.   Make sure you read the README.txt file in the root of the CrmEditableGrid project.  As I say in the readme:

THIS IS NOT A FULLY REUSABLE EDITABLE GRID CONTROL.  IT IS A SAMPLE DEMONSTRATING HOW
TO USE THE FACILITIES OF THE CRM 2011 SDK COMBINED WITH A POPULAR JAVASCRIPT GRID CONTROL (jqGrid).
WITH THIS AS YOUR STARTING POINT, YOU SHOULD BE ABLE TO USE THE CRM 2011 SDK AND THE jqGrid
DOCUMENTATION TO ADD ADDITIONAL CAPABILITIES TO THE GRID SUCH AS: ADD/DELETE ROW, OPTION SETS, ETC.

To get the sample working, import the dkdteditablegrid.zip file into your CRM organization.  It’s an unmanaged solution.  Make sure you publish all customizations at the end of the import.  I didn’t export the sitemap so you will need to find the Parent entity and add it to the Workplace area to see it:

image

Make sure you publish and refresh the browser so the updates show up.  Ok, I warned you that this sample was incomplete.  To get the editable grid into a useable state, you need to create a Parent record and a couple related Child records.  Navigate to Workplace->Extensions->Parents and create a new parent.  Make sure you Save (not Save & Close).

image

Now navigate to Related->Common->Children and add a couple children.

image

Once you’ve added a couple children.  Close the form and open it again.  Like I said, work in progress.  What you’ll see is a grid on the form that looks something like this:

image

If you click the “e” button, then you’ll put the record in edit mode.  The “c” button cancels and the “s” button saves the record.  The way this works is as follows:

  • The grid is an html web resource that is passed the record id from the parent form
  • When the web resource loads, it makes an OData query for the children
  • Upon query completion, it wires up the data to the jqGrid
  • When “s” button is clicked, the code updates the child record through the OData service

The goal of this sample, for now, is to have enough of the core plumbing in place for you to take and elaborate to your specific scenario.  Once you realize how capable jqGrid is, then you’ll understand that it’s not that big of a leap forward to get to where you want to be.  I still plan to transform this from a code sample to something people can just pickup and use on many forms by passing a few parameters to the web resource.  Of course, time needs to be on my side…yes it does.  Let me know if you beat me to it! 

@devkeydet

CRM 2011 JavaScript Model Generator

http://dkdt.me/ycgdJJ 

This my newest favorite tool for Dynamics CRM development!!!

It’s a tool that creates a ViewModel-esque class for a given form.  As the text on the home page says:

“The JS Model Generator will go and grab the Form XML for specified entities and create objects for each attribute, tab, section, and left navigation item that currently exists on the published Main form. This allows you to use intellisense to see which attributes, tabs, sections, left navigation items exist on the form and also provides easier syntax than using Xrm.Page. “

@devkeydet