<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[JavaScript - Alexander Development]]></title><description><![CDATA[JavaScript - Alexander Development]]></description><link>https://alexanderdevelopment.net/</link><image><url>https://alexanderdevelopment.net/favicon.png</url><title>JavaScript - Alexander Development</title><link>https://alexanderdevelopment.net/</link></image><generator>Ghost 1.20</generator><lastBuildDate>Fri, 24 Apr 2026 14:17:12 GMT</lastBuildDate><atom:link href="https://alexanderdevelopment.net/tag/javascript/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Setting values in a Dynamics 365 CE quick create form from the main form]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Earlier this week I was asked to populate a field in a Dynamics 365 Customer Engagement quick create form with a value from a field on the main form. Unfortunately, the main form would not be saved at the time the quick create form was opened, so the value couldn't</p></div>]]></description><link>https://alexanderdevelopment.net/post/2018/03/17/setting-values-in-a-dynamics-365-ce-quick-create-form-from-the-main-form/</link><guid isPermaLink="false">5aad852d44999a000186ddb9</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Dynamics 365]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Sat, 17 Mar 2018 21:39:19 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Earlier this week I was asked to populate a field in a Dynamics 365 Customer Engagement quick create form with a value from a field on the main form. Unfortunately, the main form would not be saved at the time the quick create form was opened, so the value couldn't be read from the database.</p>
<p>While there is no good way to access the opening form from the quick create form using the Xrm.Page object model, there is a way to pass the value from the main form using the regular JavaScript browser object model. Because the quick create form and the main form are both children of the same topmost browser window, the main form can create a property in the top window that the quick create form can access when it loads.</p>
<p>Here's sample code that runs in the main form to set the topmost window's property value:</p>
<pre><code>var setValsForQuickCreate = function(){
  window.top.attributename = Xrm.Page.getAttribute(&quot;new_attributename&quot;).getValue();
}
</code></pre>
<p>And here's the corresponding sample code to run when the quick create form loads:</p>
<pre><code>var setValFromMainForm = function(){
  Xrm.Page.getAttribute(&quot;new_attributename&quot;).setValue(window.top.attributename);
}
</code></pre>
<p>This is a relatively simple example that assumes the value to set in the quick create form is the exact same value from the opening form, but there's no reason you can't do transformations if necessary. Additionally, there's no error checking here, so you'll probably want to at least add null checking/handling in the quick create form's script.</p>
</div>]]></content:encoded></item><item><title><![CDATA[A Dynamics 365 local message listener for web client notifications - part 2]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In <a href="https://alexanderdevelopment.net/post/2017/07/19/a-dynamics-365-local-message-listener-for-web-client-notifications-part-1">part one</a> of this series, I discussed an approach for passing notifications from local applications to the Dynamics 365 web client through a message listener process that runs on an end user's PC. Today I will show the code I used to build the message listener and the code</p></div>]]></description><link>https://alexanderdevelopment.net/post/2017/07/21/a-dynamics-365-local-message-listener-for-web-client-notifications-part-2/</link><guid isPermaLink="false">5a5837246636a30001b978cd</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Dynamics 365]]></category><category><![CDATA[integration]]></category><category><![CDATA[utilities]]></category><category><![CDATA[C#]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 21 Jul 2017 12:30:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>In <a href="https://alexanderdevelopment.net/post/2017/07/19/a-dynamics-365-local-message-listener-for-web-client-notifications-part-1">part one</a> of this series, I discussed an approach for passing notifications from local applications to the Dynamics 365 web client through a message listener process that runs on an end user's PC. Today I will show the code I used to build the message listener and the code to consume notifications in Dynamics 365.</p>
<h4 id="themessagelistener">The message listener</h4>
<p>My message listener is a lightweight web server built in C# using the &quot;WebServer&quot; class from this <a href="https://codehosting.net/blog/BlogEngine/post/Simple-C-Web-Server">blog post</a>.</p>
<p>I've made a couple of small modifications to the original web server code to enable cross-origin resource sharing (CORS). Otherwise requests from the Dynamics 365 web resource would fail because the sites have different origins. Here's my updated WebServer class:</p>
<pre><code>/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2013 David's Blog (www.codehosting.net) 
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 * associated documentation files (the &quot;Software&quot;), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
*/

//The code below is mostly taken from https://codehosting.net/blog/BlogEngine/post/Simple-C-Web-Server
//I've added the headers to enable CORS requests.

using System;
using System.Net;
using System.Threading;
using System.Linq;
using System.Text;

namespace SimpleWebServer
{
    public class WebServer
    {
        private readonly HttpListener _listener = new HttpListener();
        private readonly Func&lt;HttpListenerRequest, string&gt; _responderMethod;

        public WebServer(string[] prefixes, Func&lt;HttpListenerRequest, string&gt; method)
        {
            if (!HttpListener.IsSupported)
                throw new NotSupportedException(
                    &quot;Needs Windows XP SP2, Server 2003 or later.&quot;);

            // URI prefixes are required, for example 
            // &quot;http://localhost:8080/index/&quot;.
            if (prefixes == null || prefixes.Length == 0)
                throw new ArgumentException(&quot;prefixes&quot;);

            // A responder method is required
            if (method == null)
                throw new ArgumentException(&quot;method&quot;);

            foreach (string s in prefixes)
                _listener.Prefixes.Add(s);

            _responderMethod = method;
            _listener.Start();
        }

        public WebServer(Func&lt;HttpListenerRequest, string&gt; method, params string[] prefixes)
            : this(prefixes, method) { }

        public void Run()
        {
            ThreadPool.QueueUserWorkItem((o) =&gt;
            {
                //Console.WriteLine(&quot;Webserver running...&quot;);
                try
                {
                    while (_listener.IsListening)
                    {
                        ThreadPool.QueueUserWorkItem((c) =&gt;
                        {
                            var ctx = c as HttpListenerContext;
                            try
                            {
                                string rstr = _responderMethod(ctx.Request);
                                byte[] buf = Encoding.UTF8.GetBytes(rstr);
                                if (ctx.Request.HttpMethod == &quot;OPTIONS&quot;)
                                {
                                    ctx.Response.AddHeader(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type, Accept, X-Requested-With&quot;);
                                    ctx.Response.AddHeader(&quot;Access-Control-Allow-Methods&quot;, &quot;GET, POST&quot;);
                                    ctx.Response.AddHeader(&quot;Access-Control-Max-Age&quot;, &quot;1728000&quot;);
                                }
                                ctx.Response.AddHeader(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);
                                ctx.Response.ContentLength64 = buf.Length;
                                ctx.Response.OutputStream.Write(buf, 0, buf.Length);
                            }
                            catch { } // suppress any exceptions
                            finally
                            {
                                // always close the stream
                                ctx.Response.OutputStream.Close();
                            }
                        }, _listener.GetContext());
                    }
                }
                catch { } // suppress any exceptions
            });
        }

        public void Stop()
        {
            _listener.Stop();
            _listener.Close();
        }
    }
}
</code></pre>
<p>Here is the code for a proof-of-concept application that runs the listener web server. It allows you to enter messages directly on the command line for easy testing, but ideally you would configure your message listener to run as a service or in some other mostly headless fashion. You will need the <a href="http://www.newtonsoft.com/json">JSON.Net</a> library to compile this code.</p>
<pre><code>using System;
using System.Collections.Generic;
using System.Net;
using SimpleWebServer;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CrmIntegrationServer
{
    class Program
    {
        static List&lt;string&gt; _messages;

        static void Main(string[] args)
        {
            _messages = new List&lt;string&gt;();
            WebServer ws = new WebServer(ProcessRequest, &quot;http://localhost:8080/&quot;);
            ws.Run();
            Console.WriteLine(&quot;Demo CRM integration server. Type 'CTRL+C' to exit.&quot;);
            while (true)
            {
                Console.WriteLine(&quot;Enter your message:&quot;);
                string receivedline = Console.ReadLine();
                _messages.Add(&quot;{\&quot;data\&quot;:\&quot;&quot;+receivedline+&quot;\&quot;}&quot;);
            }
        }

        public static string ProcessRequest(HttpListenerRequest request)
        {
            string receviedmessage = string.Empty;
            string response = string.Empty;
            if (request.HasEntityBody)
            {
                using (System.IO.Stream body = request.InputStream) // here we have data
                {
                    using (System.IO.StreamReader reader = new System.IO.StreamReader(body, request.ContentEncoding))
                    {
                        receviedmessage = reader.ReadToEnd();
                    }
                    JObject reqobject = JObject.Parse(receviedmessage);
                    switch (reqobject.Value&lt;string&gt;(&quot;action&quot;).ToUpper())
                    {
                        case &quot;QUEUE&quot;:
                            string messagebody = reqobject[&quot;messagebody&quot;].ToString(Formatting.None);
                            _messages.Add(messagebody);
                            response =  &quot;{\&quot;result\&quot;:\&quot;success\&quot;}&quot;;
                            break;
                        case &quot;READ&quot;:
                            response = JsonConvert.SerializeObject(_messages);
                            _messages.Clear();
                            break;
                    }
                }
            }
            return response;
        }
    }
}
</code></pre>
<h4 id="thedynamics365clientjavascript">The Dynamics 365 client JavaScript</h4>
<p>Finally, here's the code for the Dynamics 365 web resource that reads the messages. I have it set to poll for new messages every 100 milliseconds, which seems plenty fast, but you can experiment to find the value that works best for you. Because it's just making local requests and not putting additional load on your Dynamics 365 org, you don't need to worry about negatively impacting performance.</p>
<pre><code>&lt;html&gt;  
&lt;head&gt;  
    &lt;title&gt;listener demo &lt;/title&gt;
    &lt;script src=&quot;ClientGlobalContext.js.aspx&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://code.jquery.com/jquery-2.2.4.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
	var intervalId = null;
	var openrequest = false;
	var requestData = function(){
		if(!openrequest){
			openrequest = true;
			var request = $.ajax({
				url: 'http://localhost:8080',
				type: 'POST',
				contentType: 'application/json',
				dataType: &quot;json&quot;,
				data: JSON.stringify({action:'read'})
			});
			request.done(function( msg ) {
				openrequest = false;
				for(var i=0;i&lt;msg.length;i++){
					$('#messageDiv').append(JSON.parse(msg[i]).data + '&lt;br /&gt;')
				}
			});
			request.fail(function( jqXHR, textStatus ) {
				openrequest = false;
				clearInterval(intervalId);
				$('#messageDiv').append('Request failed: ' + textStatus );
			});
		}
	}
	var intervaltime = 100;
	$(function(){
		intervalId = setInterval(requestData, intervaltime);
    });
	
    &lt;/script&gt;
&lt;/head&gt;  
&lt;body&gt;  
	&lt;div id='messageDiv' /&gt;
&lt;/body&gt;  
&lt;/html&gt;
</code></pre>
<h4 id="finalthoughts">Final thoughts</h4>
<ol>
<li>You could modify the my proof-of-concept listener application to support outbound integrations with local workstation resources. For example, if you want to start a local program based on a Dynamics 365 form event, you post a specific kind of JSON request to the listener from Dynamics 365, and then the listener would start the program running.</li>
<li>Keeping the queued messages as a list of strings is probably not be the best long-term approach, especially if you have different types of messages passing through the listener that you want to handle differently. In that case, you'd want to store the messages in a data structure that allows you to retrieve just a particular kind of message.</li>
<li>The list of queued messages in my proof-of-concept application does not persist if the listener process stops running. This probably isn't a big deal because the whole idea of the message listener is to facilitate real-time communication, but I wanted to make the point explicitly clear.</li>
</ol>
<p>What do you think about this approach? Can you see yourself using it on your projects? Let us know in the comments!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Automatically executing HTTP POST requests in Dynamics 365 iframes - part 2]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Several months ago, I wrote a <a href="https://alexanderdevelopment.net/post/2016/09/09/automatically-executing-http-post-requests-in-dynamics-crm-iframes/">post</a> that showed how to automatically display the results of an HTTP POST request inside a Dynamics 365 iframe. I was working on a project last week where I was tried to use that approach, but I ran into some problems, so today I</p></div>]]></description><link>https://alexanderdevelopment.net/post/2017/07/10/automatically-executing-http-post-requests-in-dynamics-365-iframes-part-2/</link><guid isPermaLink="false">5a5837246636a30001b978bc</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Dynamics 365]]></category><category><![CDATA[integration]]></category><category><![CDATA[jQuery]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Mon, 10 Jul 2017 21:48:55 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2017/07/posting-flow-1.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2017/07/posting-flow-1.png" alt="Automatically executing HTTP POST requests in Dynamics 365 iframes - part 2"><p>Several months ago, I wrote a <a href="https://alexanderdevelopment.net/post/2016/09/09/automatically-executing-http-post-requests-in-dynamics-crm-iframes/">post</a> that showed how to automatically display the results of an HTTP POST request inside a Dynamics 365 iframe. I was working on a project last week where I was tried to use that approach, but I ran into some problems, so today I will present an updated approach.</p>
<p>Specifically, my iframe needed to post a key value that was retrieved asynchronously by a function that was called when the main CRM form loaded. Because the iframe was contained in a collapsed tab, the iframe might not be loaded by the time the main form's script had retrieved the key value, so trying to dynamically write out HTML to the iframe wouldn't work like I showed previously. I tried a few different ways to trigger the &quot;write a dynamic form and submit it&quot; code after retrieving the key value, but I never could get it working consistently with an iframe in a collapsed tab.</p>
<p>The solution I found was to create a new helper HTML web resource and then load that into the iframe when the main form loads. The helper page then used an onload event to check to see if the main form's script had retrieved the key value for posting yet. Once the key value was available, then the helper page used it to execute the form submission. The passing of the key value from the main form to the helper page was handled with the JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">postMessage</a> function. Here's what that flow looked like:<br>
<img src="https://alexanderdevelopment.net/content/images/2017/07/posting-flow.png#img-thumbnail" alt="Automatically executing HTTP POST requests in Dynamics 365 iframes - part 2"></p>
<p>Here's the code I am running in the main CRM form:</p>
<pre><code>var _keyvalue = '';

var form_OnLoad = function (){
	//add a listener for the message from the iframe
	window.addEventListener('message', receiveMessage, false);
	Xrm.Page.ui.controls.get('NAME_OF_YOUR_IFRAME').setSrc('PATH_TO_YOUR_HELPER_RESOURCE');

	//run some other code that sets the value of _keyvalue asynchronously 
	//...
	//...
}    

var receiveMessage = function (event){
	//make sure that we are only responding to messages from the same origin as the CRM form
	var originarr = Xrm.Page.context.getClientUrl().split('/');
	originarr.pop();
	var origin = originarr.join('/');
	
	//if not, return
	if (event.origin !== origin)
		return;
	
	//if the received message is &quot;getkeyvalue,&quot; send a response
	if(event.data == 'getkeyvalue'){
		event.source.postMessage(_keyvalue, event.origin);
	}
}
</code></pre>
<p>Here is the code for the helper page:</p>
<pre><code>&lt;html&gt;
&lt;head&gt;
	&lt;title&gt;helper page&lt;/title&gt;
	&lt;script src=&quot;ClientGlobalContext.js.aspx&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
	&lt;script src=&quot;https://code.jquery.com/jquery-2.2.4.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
	&lt;script&gt;
	//default the keyvalue to an empty string
	var _keyvalue = '';

	var receiveMessage = function (event){
		//make sure that we are only responding to messages from the same origin as the CRM form
		var originarr = Xrm.Page.context.getClientUrl().split('/');
		originarr.pop();
		var origin = originarr.join('/');
		if (event.origin !== origin)
			return;
		
		if(event.data){
			_keyvalue = event.data;
		}
	}

	//start checking for the keyvalue from the parent form when the document is ready
	$(function(){
		//register the message listener
		window.addEventListener(&quot;message&quot;, receiveMessage, false);
		var checkinterval = null;
		var waittime = 100; //check every X ms
		checkinterval = setInterval(
			function(){
				//ask the parent for the keyvalue
				var originarr = Xrm.Page.context.getClientUrl().split('/');
				originarr.pop();
				var origin = originarr.join('/');
				parent.postMessage('getkeyvalue', origin);
				
				//if parent form has responded with a non-empty keyvalue
				if(_keyvalue!=''){
					//stop the cycle
					clearInterval(checkinterval);
					
					//set the value of the form input
					$('#keyvalue').attr('value',_keyvalue);

					//submit the form
					$('form').submit();
				}
			}, waittime
		);
	});
	&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
	Loading . . .
	&lt;form id=&quot;webpartform&quot; method=&quot;POST&quot; action=&quot;SOME_DESTINATION&quot;&gt;
		&lt;input type=&quot;hidden&quot; name=&quot;keyvalue&quot; id=&quot;keyvalue&quot; value=&quot;&quot; /&gt;
	&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Two additional points:</p>
<ol>
<li>My implementation is actually a bit more sophisticated than what I've shared here. Instead of just passing a string key value, I pass a JSON object that contains the key value and the correct URL for the form action so that I can use the same helper page for multiple targets. Once you get the postMessage calls working between a parent CRM form and its iframe(s), you can handle many different complex scenarios.</li>
<li>I suspect this approach is on right on the edge of the supported/unsupported line. The <a href="https://msdn.microsoft.com/en-us/library/hh771584.aspx">SDK documentation for using JavaScript</a> does say not to access the Document Object Model (DOM). Technically this approach uses the Browser Object Model (BOM), so it's not explicitly unsupported, but I could see changes to Dynamics 365 impacting it in the future, so that's just something to keep in mind.</li>
</ol>
</div>]]></content:encoded></item><item><title><![CDATA[Dynamics CRM AngularJS demo solution deep dive - the editor form]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Earlier this week I shared a <a href="https://alexanderdevelopment.net/post/2016/11/07/angularjs-demo-solution-for-dynamics-crm/">sample solution</a> that shows how to build a custom Dynamics CRM data editor with <a href="https://angularjs.org/">AngularJS</a>. As I described in that post, the actual editor relies on three custom web resource components (not including the AngularJS and ES6 promise libraries):</p>
<ol>
<li>index.htm - This is</li></ol></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/10/dynamics-crm-angularjs-demo-solution-deep-dive-the-editor-form/</link><guid isPermaLink="false">5a5837246636a30001b9785b</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[AngularJS]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 11 Nov 2016 00:10:15 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-10_18-04-25.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-10_18-04-25.png" alt="Dynamics CRM AngularJS demo solution deep dive - the editor form"><p>Earlier this week I shared a <a href="https://alexanderdevelopment.net/post/2016/11/07/angularjs-demo-solution-for-dynamics-crm/">sample solution</a> that shows how to build a custom Dynamics CRM data editor with <a href="https://angularjs.org/">AngularJS</a>. As I described in that post, the actual editor relies on three custom web resource components (not including the AngularJS and ES6 promise libraries):</p>
<ol>
<li>index.htm - This is the editor form.</li>
<li>app.js - This contains the JavaScript code that make the editor work.</li>
<li>webapisdk.js - This library enables easy use of the Web API. Its code is taken from the WebAPIBasicOperations.js sample at <a href="https://msdn.microsoft.com/en-us/library/mt770365.aspx">https://msdn.microsoft.com/en-us/library/mt770365.aspx</a>.</li>
</ol>
<p>The index.htm web resource is fundamentally just a regular HTML page, but there are a few additional things that need to be done to enable the Angular functionality. Today I will go through those Angular-specific bits line by line. If you want to follow along, the full code is available on <a href="https://raw.githubusercontent.com/lucasalexander/AlexanderDevelopment.CrmAngularDemo/master/angulardemo/index.htm">GitHub</a>.</p>
<p>Line 2 - <code>&lt;html ng-app=&quot;crmEditorApp&quot;&gt;</code> - This tells AngularJS we have an application named &quot;crmEditorApp.&quot;</p>
<p>Line 25 - <code>&lt;div ng-controller=&quot;CrmEditorController as crmEditor&quot;&gt;</code> - This invokes the controller defined in our app.js file for this div.</p>
<p>Lines 29 - <code>ng-value=&quot;crmEditor.mainrecord.name&quot;</code> and <code>ng-model=&quot;crmEditor.mainrecord.name&quot;</code> - This binds form input values to the Angular variables, and allows us to easily update data in CRM. The ng-value and ng-model also show up on line 31.</p>
<p>Line 32 - <code>&lt;button ng-click=&quot;crmEditor.UpdateRecord()&quot;&gt;Update record&lt;/button&gt;</code> - This creates a button that will execute the controller's UpdateRecord function when it is clicked.</p>
<p>Line 41 - <code>&lt;tr ng-repeat=&quot;task in crmEditor.tasks&quot;&gt;</code> - This creates a table row for every item in the controller's tasks array.</p>
<p>Line 42 - <code>{{task.subject}}</code> - This displays the value of the &quot;subject&quot; property for the specific task object. The same syntax is used on lines 43 and 44.</p>
<p>Line 50 - <code>&lt;input ng-model=&quot;tasksubject&quot; name=&quot;tasksubject&quot; type=&quot;text&quot; id=&quot;tasksubject&quot; /&gt;</code> - The &quot;ng-model&quot; here makes the value of the input available to the controller. The same syntax is used on lines 51 and 52.</p>
<p>Line 53 - <code>&lt;button ng-click=&quot;crmEditor.AddTask()&quot;&gt;Add new task&lt;/button&gt;</code> - This creates a button that will execute the controller's AddTask function when it is clicked.</p>
<p>We'll take a look at the app.js resource in a future post. Until then, happy scripting!</p>
</div>]]></content:encoded></item><item><title><![CDATA[AngularJS demo solution for Dynamics CRM]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Lately I've started using <a href="https://angularjs.org/">AngularJS</a> to build web resources for custom data editors and viewers in Dynamics CRM. Once you get the hang of Angular, it's not that hard, but it did take me some time to figure out exactly how to work with it as part of a CRM</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/07/angularjs-demo-solution-for-dynamics-crm/</link><guid isPermaLink="false">5a5837246636a30001b97857</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[AngularJS]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Mon, 07 Nov 2016 13:30:00 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/11/iexplore_2016-11-06_07-13-31.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/11/iexplore_2016-11-06_07-13-31.png" alt="AngularJS demo solution for Dynamics CRM"><p>Lately I've started using <a href="https://angularjs.org/">AngularJS</a> to build web resources for custom data editors and viewers in Dynamics CRM. Once you get the hang of Angular, it's not that hard, but it did take me some time to figure out exactly how to work with it as part of a CRM solution. To help other people get started using Angular in Dynamics CRM, I've built a <a href="https://github.com/lucasalexander/AlexanderDevelopment.CrmAngularDemo/">demo solution</a> that includes a custom editor page for a custom entity called &quot;demo parent&quot; and its related tasks.</p>
<p>The custom editor displays an existing demo parent record's name and description fields, both of which are editable. It also shows all tasks related to the demo parent and allows the user to add new tasks.</p>
<p>Here's a screenshot of the entity form:<br>
<img src="https://alexanderdevelopment.net/content/images/2016/11/iexplore_2016-11-06_07-52-02.png#img-thumbnail" alt="AngularJS demo solution for Dynamics CRM"></p>
<p>And here's a screenshot of the custom editor (not very stylish, I know):<br>
<img src="https://alexanderdevelopment.net/content/images/2016/11/iexplore_2016-11-06_07-12-45.png#img-thumbnail" alt="AngularJS demo solution for Dynamics CRM"></p>
<p>The editor page is launched from the &quot;open editor&quot; button on the demo parent main form in CRM.</p>
<p>My solution includes the following components:</p>
<ol>
<li>Demo parent (la_demoparent) entity - The custom Angular editor displays and allows updating this type of record.</li>
<li>index.htm web resource - This is the web page for the editor.</li>
<li>app.js - This holds the JavaScript code for the Angular application.</li>
<li>webapisdk.js - This JavaScript library enables easy use of the Web API. Its code is taken from the WebAPIBasicOperations.js sample at <a href="https://msdn.microsoft.com/en-us/library/mt770365.aspx">https://msdn.microsoft.com/en-us/library/mt770365.aspx</a>.</li>
<li>es6promise.min.js - This is a polyfill for ES6-style Promises that's used to work with async code. Promises basically let you avoid callback hell like we had with all the CRM organization data service examples.</li>
<li>openeditor.js - This contains code to open the editor from the demo parent entity ribbon. It is not required by the actual Angular application.</li>
<li>Editor16.png and Editor32.png - These icons aren't required for the actual Angular application to function.</li>
</ol>
<p>One thing that is notably missing from my application is the AngularJS library. My index.htm page is loading it from the Google CDN, but there's no reason you couldn't upload a copy to your CRM organization and call it from there instead.</p>
<p>You can download the <a href="https://github.com/lucasalexander/AlexanderDevelopment.CrmAngularDemo/raw/master/Angulardemo_0_0_0_1.zip">entire solution</a> to install in your own CRM organization from my GitHub repository, or you can just browse the code <a href="https://github.com/lucasalexander/AlexanderDevelopment.CrmAngularDemo/tree/master/angulardemo">here</a>.</p>
<p>I plan to discuss the different parts of the solution in more detail in future posts, but I think just browsing the code and seeing how the solution works will still be helpful to anyone who is just getting started with Angular in CRM.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Working with Dynamics CRM users assigned roles using JavaScript]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Today turned out to be one of those days where I got multiple requests for <a href="https://alexanderdevelopment.net/post/2016/11/03/checking-dynamics-crm-user-team-membership-with-organization-data-service/">quick Dynamics CRM 2015 JavaScript help</a> on a few projects. A colleague asked me for help showing a field on a form if a user is assigned a role and keeping it hidden otherwise. Instead</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/03/working-with-dynamics-crm-users-assigned-roles-using-javascript/</link><guid isPermaLink="false">5a5837246636a30001b97854</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 03 Nov 2016 19:08:28 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Today turned out to be one of those days where I got multiple requests for <a href="https://alexanderdevelopment.net/post/2016/11/03/checking-dynamics-crm-user-team-membership-with-organization-data-service/">quick Dynamics CRM 2015 JavaScript help</a> on a few projects. A colleague asked me for help showing a field on a form if a user is assigned a role and keeping it hidden otherwise. Instead of just hardcoding everything, I decided to take a more general approach and write a universal role assignment checking function that has parameters for the functions to execute if the user is or is not in the role when the role checking is called. Here it is:</p>
<pre><code>//call this like CheckUserRoleAssignment(&quot;ROLE NAME&quot;, [ARRAY OF NAMES OF FUNCTIONS TO call IF TRUE], [ARRAY OF NAMES OF FUNCTION TO CALL IF FALSE]);

function CheckUserRoleAssignment(rolename, truefunctions, falsefunctions) {
	SDK.REST.retrieveMultipleRecords(&quot;Role&quot;, 
		&quot;$select=Name, RoleId&amp;$filter=Name eq '&quot;+rolename+&quot;'&quot;, 
		function(data){
			var userinrole = false;
			if(data.length&gt;0) {
				var allroles = []
				for(var i=0;i&lt;data.length;i++){
					allroles[i] = data[i].RoleId.toString();
				}
				var userroles = Xrm.Page.context.getUserRoles();
				for(var i=0;i&lt;userroles.length;i++){
					if(allroles.indexOf(userroles[i])&gt;-1){
						userinrole = true;
					}
				}
			}
			if(userinrole){
				//loop through array of &quot;true&quot; functions
				for(var i=0;i&lt;truefunctions.length;i++){
					//call the function
					truefunctions[i]();
				}
			}
			if(!userinrole){
				//loop through array of &quot;false&quot; functions
				for(var i=0;i&lt;falsefunctions.length;i++){
					//call the function
					falsefunctions[i]();
				}
			}
		},
		function(error){
			//do nothing
		},
		function(){
			//do nothing on completion
		}
	);
}
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Checking Dynamics CRM user team membership with organization data service]]></title><description><![CDATA[<div class="kg-card-markdown"><p>With the all the recent news about Dynamics 365, this post probably seems a tad outdated, but today I had a colleague ask me for a way to check whether a user is a member of a team in a Dynamics CRM 2015 organization using JavaScript. Without further commentary, here</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/03/checking-dynamics-crm-user-team-membership-with-organization-data-service/</link><guid isPermaLink="false">5a5837246636a30001b97851</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 03 Nov 2016 15:42:47 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>With the all the recent news about Dynamics 365, this post probably seems a tad outdated, but today I had a colleague ask me for a way to check whether a user is a member of a team in a Dynamics CRM 2015 organization using JavaScript. Without further commentary, here goes:</p>
<pre><code>//get the userid
var userid = Xrm.Page.context.getUserId();

//check if the user is on the team using a synchronous odata call
function isUserOnTeam(userid, teamid){
	//assume user is not on the team
	var returnvalue = false;
	var oDataSetName = &quot;TeamMembershipSet&quot;;
	var filter = &quot;SystemUserId eq guid' &quot; + userid + &quot; ' and TeamId eq guid' &quot; + teamid + &quot;'&quot;;
	var columns = &quot;*&quot;;
	var requestResults = retrieveMultipleSync(oDataSetName, columns, filter);
	
	if (requestResults != null) {
		//more than zero results -&gt; user is on the team
		if(requestResults.results.length&gt;0){
			returnvalue = true;
		}
	}
	return returnvalue;
}

//helper function for making sync odata calls - taken from https://rajeevpentyala.com/2012/05/27/retrieve-multiple-records-synchronously-using-odata-jquery-in-crm-2011/
function retrieveMultipleSync(odataSetName, select, filter) {
	// Get Server URL
	var serverUrl = &quot;&quot;;
	if (Xrm.Page.context.getClientUrl) {
		//Post UR 12
		serverUrl = Xrm.Page.context.getClientUrl();
	}
	else {
		//Pre UR 12
		serverUrl = Xrm.Page.context.getServerUrl();
	}
	var ODATA_ENDPOINT = &quot;/XRMServices/2011/OrganizationData.svc&quot;;
	var odataUri = serverUrl + ODATA_ENDPOINT + &quot;/&quot; + odataSetName + &quot;?&quot;;
	if (select) {
		odataUri += &quot;$select=&quot; + select;
	}
	if (filter) {
		odataUri += &quot;&amp;&quot; + &quot;$filter=&quot; + filter;
	}
	var service = getRequestObject();
	if (service != null) {
		service.open(&quot;GET&quot;, odataUri, false);
		service.setRequestHeader(&quot;X-Requested-Width&quot;, &quot;XMLHttpRequest&quot;);
		service.setRequestHeader(&quot;Accept&quot;, &quot;application/json,text/javascript, */*&quot;);
		service.send(null);
		var requestResults = eval('(' + service.responseText + ')').d;
		return requestResults;
	}
}

//helper function for making xmlhttp requests
function getRequestObject() {
	if (window.XMLHttpRequest) {
		return new window.XMLHttpRequest;
	}
	else {
		try {
			return new ActiveXObject(&quot;MSXML2.XMLHTTP.3.0&quot;);
		}
		catch (ex) {
			return null;
		}
	}
}
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Custom call handling logic with the Dynamics CRM USD generic listener]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I was recently working on a Dynamics CRM Unified Service Desk project where I needed to populate a custom search form with call details instead of doing a direct search for a matching record in CRM. I didn't want to write my own CTI adapter, so I investigated using the</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/10/22/custom-call-handling-logic-with-the-dynamics-crm-usd-generic-listener/</link><guid isPermaLink="false">5a5837246636a30001b9783a</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Unified Service Desk]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Sat, 22 Oct 2016 16:05:10 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/10/1-cti-route-1-1.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/10/1-cti-route-1-1.png" alt="Custom call handling logic with the Dynamics CRM USD generic listener"><p>I was recently working on a Dynamics CRM Unified Service Desk project where I needed to populate a custom search form with call details instead of doing a direct search for a matching record in CRM. I didn't want to write my own CTI adapter, so I investigated using the <a href="https://msdn.microsoft.com/en-us/library/dn864961.aspx">USD generic listener adapter</a>. Although the USD generic listener is designed to only search CRM records, I developed a workaround to route the inbound call values to a custom HTML web resource that could process them using JavaScript.</p>
<ol>
<li>
<p>Create an action to open a custom HTML web resource that reads values from a query string and processes them appropriately with JavaScript. Note that the query string here includes the output of a scriptlet, which is addressed in the following step. <img src="https://alexanderdevelopment.net/content/images/2016/10/4-cti-open-page-action.png#img-thumbnail" alt="Custom call handling logic with the Dynamics CRM USD generic listener"></p>
</li>
<li>
<p>It's possible that the phone system might send special characters that could &quot;break&quot; a query string, so you should use a scriptlet like this to create a urlencoded string instead of passing them directly to your custom page. This scriptlet generates a query string containing the caller's Social Security number and phone number. <img src="https://alexanderdevelopment.net/content/images/2016/10/4-cti-urlencode-scriptlet.png#img-thumbnail" alt="Custom call handling logic with the Dynamics CRM USD generic listener"></p>
</li>
<li>
<p>Create a CTI navigation rule to execute a search just like usual. <img src="https://alexanderdevelopment.net/content/images/2016/10/1-cti-route-1.png#img-thumbnail" alt="Custom call handling logic with the Dynamics CRM USD generic listener"></p>
</li>
<li>
<p>Make sure your search will never return a match. The search in this screenshot searches accounts for a phonenumber that starts with &quot;nevermatch.&quot; <img src="https://alexanderdevelopment.net/content/images/2016/10/2-cti-search.png#img-thumbnail" alt="Custom call handling logic with the Dynamics CRM USD generic listener"></p>
</li>
<li>
<p>In the &quot;no matches&quot; condition for your CTI navigation rule, set the decision to &quot;Create Session then Do Action&quot; or just &quot;Do Action&quot; and execute the action you created in step #1. <img src="https://alexanderdevelopment.net/content/images/2016/10/3-cti-route-3.png#img-thumbnail" alt="Custom call handling logic with the Dynamics CRM USD generic listener"></p>
</li>
</ol>
<p>Finally, is here sample code from my HTML web resource to parse the query string and extract inbound call values. In this case I am just displaying the values to the user with jQuery, but this can be modified to address your specific use case.</p>
<pre><code>&lt;html&gt;
&lt;head&gt;
	&lt;script src=&quot;ClientGlobalContext.js.aspx&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
	&lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js&quot;&gt;&lt;/script&gt;
	&lt;script&gt;
	function getParameterByName(name, url) {
		if (!url) url = window.location.href;
		name = name.replace(/[\[\]]/g, &quot;\\$&amp;&quot;);
		var regex = new RegExp(&quot;[?&amp;]&quot; + name + &quot;(=([^&amp;#]*)|&amp;|#|$)&quot;),
			results = regex.exec(url);
		if (!results) return null;
		if (!results[2]) return '';
		return decodeURIComponent(results[2].replace(/\+/g, &quot; &quot;));
	}

	function doPageLoad() {
		var ani = &quot;&quot;;
		var ssn = &quot;&quot;;
		var datavalue = getParameterByName('data');
		if(datavalue != null){
			var params = decodeURIComponent(datavalue).split(&quot;&amp;&quot;);
			for (var i in params) {
				params[i] = params[i].replace(/\+/g, &quot; &quot;).split(&quot;=&quot;);
				if (params[i][0] == &quot;ani&quot;) {
					ani = params[i][1];
				}
				if (params[i][0] == &quot;ssn&quot;) {
					ssn = params[i][1];
				}
			}
		}
		$('#ani').text(ani);
		$('#ssn').text(ssn);
	}
	
	$().ready(function() {
		doPageLoad();
	});
	&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
ANI: &lt;span id='ani'&gt;&lt;/span&gt;&lt;br /&gt;
SSN: &lt;span id='ssn'&gt;&lt;/span&gt;&lt;br /&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Easy dependent picklists for Dynamics CRM]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I've created a new solution for creating dependent picklists on Dynamics CRM forms that requires no coding to implement. You can download it from my Crm-Sample-Code GitHub <a href="https://github.com/lucasalexander/Crm-Sample-Code">repository</a> here: <a href="https://raw.githubusercontent.com/lucasalexander/Crm-Sample-Code/master/misc-code-samples/easydependentpicklists.js">https://raw.githubusercontent.com/lucasalexander/Crm-Sample-Code/master/misc-code-samples/easydependentpicklists.js</a></p>
<p>Assume you have three picklists with related options like in the</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/10/06/easy-dependent-picklists-for-dynamics-crm/</link><guid isPermaLink="false">5a5837246636a30001b9784a</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[utilities]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 06 Oct 2016 22:25:11 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/10/chrome_2016-10-06_17-21-16.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/10/chrome_2016-10-06_17-21-16.png" alt="Easy dependent picklists for Dynamics CRM"><p>I've created a new solution for creating dependent picklists on Dynamics CRM forms that requires no coding to implement. You can download it from my Crm-Sample-Code GitHub <a href="https://github.com/lucasalexander/Crm-Sample-Code">repository</a> here: <a href="https://raw.githubusercontent.com/lucasalexander/Crm-Sample-Code/master/misc-code-samples/easydependentpicklists.js">https://raw.githubusercontent.com/lucasalexander/Crm-Sample-Code/master/misc-code-samples/easydependentpicklists.js</a></p>
<p>Assume you have three picklists with related options like in the list below:</p>
<ul>
<li>lpa_level1picklist</li>
<li>1</li>
<li>2</li>
<li>lpa_level2picklist</li>
<li>1a</li>
<li>1b</li>
<li>2a</li>
<li>2b</li>
<li>lpa_level3picklist</li>
<li>1a1</li>
<li>1a2</li>
<li>1b1</li>
<li>2a1</li>
</ul>
<p>You can represent a tree of possible options like this:<br>
1 -&gt; 1a -&gt; 1a1<br>
1 -&gt; 1a -&gt; 1a2<br>
1 -&gt; 1b -&gt; 1b1<br>
2 -&gt; 2a -&gt; 2a1<br>
2 -&gt; 2a -&gt; 1a1<br>
2 -&gt; 2b</p>
<p>Should notice two important points:</p>
<ol>
<li>You don't have to have an equal number of options in each path. For example, under 2 -&gt; 2b, there are no children. In this case the new_level3 picklist should show no options when 2 -&gt; 2b is selected in the two parent picklists.</li>
<li>The same child can appear in multiple option paths. That is, level 3's &quot;1a1&quot; is a child of both &quot;1a&quot; and &quot;2a.&quot;</li>
</ol>
<p>To implement this filtering functionality on a CRM form, you just need to add my JavaScript file to your solution as a web resource and then call its initializePicklistFilter function when you want to apply the filtering. This could be in the form onload event or at any other time it makes sense.</p>
<p>The initializePicklistFilter function takes three input parameters:</p>
<ol>
<li>
<p>picklists - A pipe-delimited string that contains the picklists in the hierarchy in order of first parent to last child.<br>
Example: &quot;lpa_level1picklist|lpa_level2picklist|lpa_level3picklist&quot;</p>
</li>
<li>
<p>separator - String of characters used to separate levels in the parent-child option paths. This can be any string you like.<br>
Example: &quot; -&gt; &quot;</p>
</li>
<li>
<p>optionTree - An array of strings containing all possible parent-child option paths you want to enable on the form.<br>
Example: [&quot;1 -&gt; 1a -&gt; 1a1&quot;,&quot;1 -&gt; 1a -&gt; 1a2&quot;,&quot;1 -&gt; 1b -&gt; 1b1&quot;,&quot;2 -&gt; 2a -&gt; 2a1&quot;,&quot;2 -&gt; 2a -&gt; 1a1&quot;,&quot;2 -&gt; 2b&quot;]</p>
</li>
</ol>
<p>Putting it all together, your call to initializePicklistFilter for this example would look like this:</p>
<pre><code>initializePicklistFilter(
  &quot;lpa_level1picklist|lpa_level2picklist|lpa_level3picklist&quot;,
  &quot;-&gt;&quot;,
  [&quot;1 -&gt; 1a -&gt; 1a1&quot;,
    &quot;1 -&gt; 1a -&gt; 1a2&quot;,
    &quot;1 -&gt; 1b -&gt; 1b1&quot;,
    &quot;2 -&gt; 2a -&gt; 2a1&quot;,
    &quot;2 -&gt; 2a -&gt; 1a1&quot;,
    &quot;2 -&gt; 2b&quot;]
);
</code></pre>
<p>Here are a few points to consider:</p>
<ol>
<li>My code is based on the optionset labels, so it does not automatically support multiple languages. If you need to handle multiple languages, you would need to create a separate option tree array for each language and then call the initializePicklistFilter function with the corresponding array depending for the user's language.</li>
<li>Because the initializePicklistFilter call can be executed any time you want (and even at multiple times for the same set of pickilists on a form), you can dynamically modify the options based on external criteria. For example, you could use this to create different possible selection rules based on user roles.</li>
<li>My example uses a three-level picklist hierarchy, but this solution works for any number of picklists.</li>
<li>To apply picklist filters for multiple groups of picklists on the same form, just call the initializePicklistFilter multiple times with different parameters.</li>
<li>The chaining all possible paths through the option tree can make the initializePicklistFilter call unwieldy for picklists with lots of options. If your child options only depend on their immediate parents, you can chain together multiple initializePicklistFilter calls. That is, you can call it for the first parent and first child, then call it again for the first child and its first child, then that child and its child, etc.</li>
</ol>
<p>That's all for now. If you use this solution or have any thoughts, I'd love to hear about it in the comments.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Downloading Dynamics CRM attachments with JavaScript]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I ran into a requirement this week where I needed to give Dynamics CRM users an easy way to download PDF attachments from a set of links in an iframe. I thought it would be easy enough to just grab the corresponding annotation id and pass it to the CRM</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/09/29/downloading-dynamics-crm-attachments-with-javascript/</link><guid isPermaLink="false">5a5837246636a30001b9784e</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 29 Sep 2016 23:30:34 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>I ran into a requirement this week where I needed to give Dynamics CRM users an easy way to download PDF attachments from a set of links in an iframe. I thought it would be easy enough to just grab the corresponding annotation id and pass it to the CRM download.aspx page, but that didn't work. A slightly more complex solution that did work was the following:</p>
<ol>
<li>Retrieve the file name and document body data for the annotation that has the attachment you want to download.</li>
<li>Convert the document body (a Base64-encoded representation of the attachment) to a Uint8Array.</li>
<li>Initiate a file download using the <a href="http://danml.com/download.html">download.js</a> library.</li>
</ol>
<p>Here's the code that does all this:</p>
<pre><code>//method to retrieve PDF attachment data and execute callbacks as necessary
function retrieveAttachment(objectid){
	var select = &quot;$select=DocumentBody,FileName&quot;;
	var filter = &quot;$filter=ObjectId/Id eq (guid'&quot;+objectid+&quot;')&amp;IsDocument eq true and MimeType eq 'application/pdf'&quot;;
	var orderby = &quot;$orderby=CreatedOn desc&amp;$top=1&quot;;
	
	var query = select+&quot;&amp;&quot;+filter+&quot;&amp;&quot;+orderby
	
	//this was originally written for CRM 2015, but the approach should work for CRM 2016 or it can be converted to use the Web API
	SDK.REST.retrieveMultipleRecords(&quot;Annotation&quot;, query, retrieveAttachmentSuccess, retrieveAttachmentError, retrieveAttachmentComplete);
}

//empty function because SDK.REST.retrieveMultipleRecords expects something
function retrieveAttachmentComplete(){}

//function to convert the Base64-encoded attachment to a Uint8Array - from http://stackoverflow.com/a/21797381
function base64ToArrayBuffer(base64) {
	var binaryString =  window.atob(base64);
	var len = binaryString.length;
	var bytes = new Uint8Array( len );
	for (var i = 0; i &lt; len; i++)        {
		bytes[i] = binaryString.charCodeAt(i);
	}
	return bytes.buffer;
}

//look at the returned data and initiate the download
function retrieveAttachmentSuccess(returnData){
	if (returnData != null &amp;&amp; returnData.length==1) {
		if(returnData[0].DocumentBody){
			//obviously you need to have included the download.js library for this next line to work
			download(base64ToArrayBuffer(returnData[0].DocumentBody), returnData[0].FileName, &quot;application/pdf&quot;);
		}
		else{
			alert(&quot;Cannot render document from missing body.&quot;);
		}
	}
	else{
		alert(&quot;Cannot find single matching attachment.&quot;);
	}
}

//handle errors
function retrieveAttachmentError(err){
	alert(&quot;There was an error retrieving the attachment: &quot; + err);
}
</code></pre>
<p>Note this code requires the <code>window.atob</code> function, so it will not work in Internet Explorer prior to version 10. You can check for compatibility across all major browsers <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/atob#Browser_compatibility">here</a>. Happy attachment downloading!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Automatically executing HTTP POST requests in Dynamics CRM iframes]]></title><description><![CDATA[<div class="kg-card-markdown"><p>The Dynamics CRM SDK allows you to set the source URL for an iframe control on a form, and that is fine if all you need to do is load web pages that are accessible via HTTP GET requests. If you need to automatically display the results of an HTTP</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/09/09/automatically-executing-http-post-requests-in-dynamics-crm-iframes/</link><guid isPermaLink="false">5a5837246636a30001b9783e</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[jQuery]]></category><category><![CDATA[integration]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 09 Sep 2016 17:57:12 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/09/chrome_2016-09-09_12-44-43.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/09/chrome_2016-09-09_12-44-43.png" alt="Automatically executing HTTP POST requests in Dynamics CRM iframes"><p>The Dynamics CRM SDK allows you to set the source URL for an iframe control on a form, and that is fine if all you need to do is load web pages that are accessible via HTTP GET requests. If you need to automatically display the results of an HTTP POST request inside an iframe, it's a bit more challenging. Today I will show an approach that I have used to implement this functionality.</p>
<p>This approach uses a Dynamics CRM form JavaScript library to do the following:</p>
<ol>
<li>Dynamically create an HTML form in the iframe using regular JavaScript.</li>
<li>Dynamically append hidden form inputs with the correct parameter names and values using jQuery.</li>
<li>Submit the form using jQuery.</li>
</ol>
<pre><code>function iframePost(data, url, iframename, formid) {
  //get reference to iframe using supported crm sdk approach
  var iFrame = Xrm.Page.ui.controls.get(iframename);

  //get reference to iframe guts using plain old javascript
  var iFrameDoc = iFrame.getObject().contentWindow.document;

  //overwrite anything in the iframe with an empty form
  iFrameDoc.write('&lt;html&gt;&lt;body&gt;&lt;form id=&quot;'+formid+'&quot; method=&quot;POST&quot; action=&quot;'+url+'&quot;&gt;&lt;/form&gt;&lt;/body&gt;&lt;/html&gt;');

  //loop through json object attributes to add form inputs
  $.each(data, function(n,v){
    $(iFrameDoc).find('#'+formid).append('&lt;input type=&quot;hidden&quot; name=&quot;' + n + '&quot; value=&quot;' + v + '&quot; /&gt;');
  });

  //submit the form
  $(iFrameDoc).find('#'+formid).submit();
}
</code></pre>
<p>This function expects the parameters to be supplied to the POST endpoint as a JSON object. Here's an example of how to use it. Assume the following:</p>
<ol>
<li>The target URL for the form POST is <a href="http://example.org/testservice">http://example.org/testservice</a>.</li>
<li>&quot;IFRAME_TESTFRAME&quot; is the name of the iframe where you want to display the results.</li>
<li>Input parameters are:
<ol>
<li>color = 'blue'</li>
<li>team = 'Auburn'</li>
</ol>
</li>
</ol>
<p>Your JavaScript code would look like this:</p>
<pre><code>var targeturl = 'http://example.org/testservice';

var iframename = 'IFRAME_TESTFRAME';

var formid = 'myform'; //This is the id of the dynamically generated form. You can call it whatever you want.

var formparams = {};
formparams.color = 'blue';
formparams.team = 'Auburn';

iframePost(formparams, targeturl, iframename, formid);
</code></pre>
<p><em>(It should go without saying that you need to make a jQuery library available to your CRM form, too.)</em></p>
<p>And that's all there is to it. Have you needed to do anything similar in your CRM projects? What approach did you take? Please share in the comments!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Get next case functionality for CRM Unified Service Desk]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Last week I shared an approach for implementing <a href="https://alexanderdevelopment.net/post/2015/10/01/get-next-case-functionality-for-dynamics-crm/">next case functionality for Dynamics CRM</a> so that users can get the &quot;next&quot; case to work from a queue just by clicking a button. In today's post I will show an easy way to add the same functionality to Unified</p></div>]]></description><link>https://alexanderdevelopment.net/post/2015/10/07/get-next-case-functionality-for-crm-unified-service-desk/</link><guid isPermaLink="false">5a5837226636a30001b97770</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CRM 2015]]></category><category><![CDATA[Unified Service Desk]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 08 Oct 2015 02:41:36 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2015/10/usd-found-case-1.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2015/10/usd-found-case-1.png" alt="Get next case functionality for CRM Unified Service Desk"><p>Last week I shared an approach for implementing <a href="https://alexanderdevelopment.net/post/2015/10/01/get-next-case-functionality-for-dynamics-crm/">next case functionality for Dynamics CRM</a> so that users can get the &quot;next&quot; case to work from a queue just by clicking a button. In today's post I will show an easy way to add the same functionality to Unified Service Desk (USD), but as an added bonus the case will also open in a new session tab.</p>
<p>Before talking through the code and configuration elements in detail, let's take a look at exactly how the get next case button works in my USD demo instance.</p>
<p>I'm displaying the get next button in the left panel, but it can be displayed in any visible panel.<br>
<img src="https://alexanderdevelopment.net/content/images/2015/10/usd-main.png#img-thumbnail" alt="Get next case functionality for CRM Unified Service Desk"></p>
<p>Once I click the button, a new session is opened if a &quot;next&quot; case is found in the queue.<br>
<img src="https://alexanderdevelopment.net/content/images/2015/10/usd-found-case.png#img-thumbnail" alt="Get next case functionality for CRM Unified Service Desk"></p>
<p>If no &quot;next&quot; case can be found, an alert message is displayed.<br>
<img src="https://alexanderdevelopment.net/content/images/2015/10/usd-no-case.png#img-thumbnail" alt="Get next case functionality for CRM Unified Service Desk"></p>
<p>There are three elements to make this work:</p>
<ol>
<li>The custom workflow activity and custom action from my previous blog post.
</li><li>A USD-specific web resource to expose the button and open the "next" case record.
</li><li>USD configuration elements (hosted controls, action calls, etc.) to load the get next case web resource and handle its output.
</li></ol>
<p>Because there are no changes to the existing custom workflow activity or custom action, I'm not going to discuss those further in this post, but let's take a closer look at the other elements.</p>
<h4 id="thewebresource">The web resource</h4>
<p>In my previous post, clicking the &quot;get next case&quot; button searched a queue for a case and then generated a link for a user to manually click to open the case. To streamline the functionality in USD, I decided to modify the web resource to automatically open the case if one is found. If no case can be found in the queue, an alert message is displayed.</p>
<p>Because USD allows you to modify JavaScript logic as described in this <a href="http://blogs.msdn.com/b/usd/archive/2015/09/25/inserting-usd-events-into-html-javascript-logic-and-acting-upon-it.aspx">MSDN post</a>, I considered reusing my existing get next web resource from before, and adjusting the USD configuration to pop open the case record. Ultimately I decided it would be easier to just add a new &quot;USD&quot; version of the web resource, which is included in the CRM solution in the sample code at the bottom of this post. That web resource is almost identical to my previous version except for this section of code:</p>
<pre><code>    //if a case is found
    if(caseId != '00000000-0000-0000-0000-000000000000') {
		var caseUrl = _server + "/main.aspx?etc=112&extraqs=&pagetype=entityrecord&id=%7b" + caseId + "%7d";
		////display the case number as a hyperlink
		//$("#outputdiv").append("Case: <a href="https://alexanderdevelopment.net/post/2015/10/07/get-next-case-functionality-for-crm-unified-service-desk/" + caseUrl + "" target="_blank">" + caseNumber + "</a>");
        //automatically open case record
		window.open(caseUrl);
	}
	else {
		//otherwise display a message that no "next" case could be found
		//$("#outputdiv").append("No next case found");
		alert("No next case found");
	}</code></pre>
<h4 id="theusdconfiguration">The USD configuration</h4>
<p>Once the web resource is ready, the following USD configuration records need to be created:</p>
<ol>
<li>Create a CRM page hosted control to display the get next button web resource. As mentioned above, I am displaying the web resource in the left panel, but it will work in any visible panel.
![Get next case button hosted control](/content/images/2015/10/01-get_next-control.png#img-thumbnail)
</li><li>Create a CRM page hosted control to display the case session.
![Case session hosted control](/content/images/2015/10/02-case_session-control.png#img-thumbnail)
</li><li>Create a session name session information record to display the case number on the session tab. This isn't strictly necessary, but it looks better than "new session."
![Case session name record](/content/images/2015/10/03-session_name.png#img-thumbnail)
</li><li>Create an action call to load the button. As in my previous post, the queue id and assign/remove from queue flags are supplied as encoded query string parameters. I've hardcoded the queue id, but you could use some sort of logic to dynamically set it. Also, you'll note the image here shows the assign and remove from queue flags set to false.
![Load get next action call](/content/images/2015/10/04-get_next-action.png#img-thumbnail)
</li><li>Add the action call to the global manager DesktopReady event.
![Global manager DesktopReady event](/content/images/2015/10/05-desktop_ready.png#img-thumbnail)
</li><li>Create a window navigation rule to display case (incident) records opened from the get next case button hosted control in the case session tab.
![Open case from get next window navigation rule](/content/images/2015/10/06-window_routing.png#img-thumbnail)
</li><li>(Optionally) Update any agent scripting configuration to handle cases being loaded in session tabs. In my USD configuration I already had agent scripting configured for accounts per this <a href="https://msdn.microsoft.com/en-us/library/dn864881.aspx">USD walkthrough</a>, so I created agent scripting for case sessions, too.
![Case agent script](/content/images/2015/10/07-case-script.png#img-thumbnail)
![Load case script action call](/content/images/2015/10/09-case_scripting-action.png#img-thumbnail)
![Case session tab BrowserDocumentComplete event](/content/images/2015/10/08-browser_document_complete.png#img-thumbnail)
</li><li>Finally you need to add all of these elements to the USD configuration record.
</li></ol>
<h4 id="wrappingup">Wrapping up</h4>
<p>You can download a CRM solution with the custom assembly, custom CRM action and web resources from my Crm-Sample-Code repository on <a href="https://github.com/lucasalexander/Crm-Sample-Code/tree/master/CrmQueueGetNext/crm">GitHub</a>, but you'll have to do the USD configuration on your own.</p>
<p>What do think about this approach? Could you see yourself using it in your CRM projects? Let me know in the comments!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Get next case functionality for Dynamics CRM]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Dynamics CRM offers sophisticated tools for working with cases and service queues, but sometimes users just want a quick and simple way to get the next case to work. In today's post, I'll share an easy way to implement this functionality in your Dynamics CRM organization.</p>
<p>There are three components</p></div>]]></description><link>https://alexanderdevelopment.net/post/2015/10/01/get-next-case-functionality-for-dynamics-crm/</link><guid isPermaLink="false">5a5837226636a30001b9776b</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[C#]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CRM 2015]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 02 Oct 2015 02:13:00 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2015/10/process-1.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2015/10/process-1.png" alt="Get next case functionality for Dynamics CRM"><p>Dynamics CRM offers sophisticated tools for working with cases and service queues, but sometimes users just want a quick and simple way to get the next case to work. In today's post, I'll share an easy way to implement this functionality in your Dynamics CRM organization.</p>
<p>There are three components to my approach:</p>
<ol>
<li>First, there is a custom workflow activity that searches a queue for unassigned cases and assigns the one that entered the queue earliest to a user. This activity returns the assigned case id and case number as output parameters.
</li><li>Second, there is a CRM action that that acts as a wrapper for the custom workflow activity.
</li><li>Finally, there is a web resource that displays a button to call the CRM action and generate a link to the assigned case (or a message if there are no cases in the queue to assign).
</li></ol>
<p>Let's look at each of these pieces in more detail. If you'd rather get right to the code, scroll to the bottom of this post for a link.</p>
<h4 id="thecustomworkflowactivity">The custom workflow activity</h4>
<p>The custom workflow activity has the following parameters:</p>
<ol>
<li>Queue - input - Reference to the queue that contains the case
</li><li>User - input - Reference to the user who will be assigned the case
</li><li>Assign - input - Flag for whether to assign the case or just return the case details
</li><li>Remove from queue - input - Flag for whether to completely remove the case from the queue when it is assigned
</li><li>Case id - output - Case GUID as a string (string because it makes working with non-existing cases easier in the custom action)
</li><li>Case number - output - Case ticket number value
</li><li>Case found - output - Flag for whether a case was found
</li><li>Case title - output - Case title value
</li></ol>
<p>The activity then executes a FetchXML query to retrieve unassigned cases in ascending order of the date they entered the queue. If the &quot;assign&quot; flag is set to true, the first case is assigned to the user, and then the details are returned as output parameters. If the flag is set to false, the case details are returned as output parameters, but the case is not updated.</p>
<h4 id="thecrmaction">The CRM action</h4>
<p>The action is the simplest part of the solution. It just takes input parameters to pass to the custom activity and then returns the output parameters.</p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/process.png#img-thumbnail" alt="Get next case functionality for Dynamics CRM"></p>
<h4 id="thewebresource">The web resource</h4>
<p>Finally the web resource uses JavaScript to call the action and display a link to the assigned case. If no  &quot;next&quot; case can be found, then a message is displayed to the user. In my solution, the queue id and assign/remove from queue flags are supplied as encoded query string parameters.</p>
<p>The image below shows the output after the get next button is clicked:<br>
<img src="https://alexanderdevelopment.net/content/images/2015/10/button.png#img-thumbnail" alt="Get next case functionality for Dynamics CRM"></p>
<p>The web resource approach allows you to embed the button in a dashboard or CRM form through an iframe, but you could also trigger the action from a ribbon button using similar JavaScript.</p>
<h4 id="thecode">The code</h4>
<p>You can download all the custom code and a CRM solution extract from my Crm-Sample-Code repository on <a href="https://github.com/lucasalexander/Crm-Sample-Code/tree/master/CrmQueueGetNext">GitHub</a>. Happy case management!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Authenticating from a Node.js client to Dynamics CRM via AD FS and OAuth2]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Last week I decided to finally take a look at using OAuth2 as an authentication protocol with Dynamics CRM. I wanted to understand how it could enable non-Windows clients to consume CRM data. As it turns out, I was unable to find any documentation or comprehensive code samples for non-Windows</p></div>]]></description><link>https://alexanderdevelopment.net/post/2015/01/23/authenticating-from-a-node-js-client-to-dynamics-crm-via-ad-fs-and-oauth2/</link><guid isPermaLink="false">5a5837226636a30001b97747</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[JSON]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CRM 2015]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Sat, 24 Jan 2015 00:00:00 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2015/10/adfs-diesel.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2015/10/adfs-diesel.jpg" alt="Authenticating from a Node.js client to Dynamics CRM via AD FS and OAuth2"><p>Last week I decided to finally take a look at using OAuth2 as an authentication protocol with Dynamics CRM. I wanted to understand how it could enable non-Windows clients to consume CRM data. As it turns out, I was unable to find any documentation or comprehensive code samples for non-Windows clients, so I put together my own Node.js client, and I've added the code to my Crm-Sample-Code repository on GitHub here: <a href="https://github.com/lucasalexander/Crm-Sample-Code/tree/master/NodeClientDemo">https://github.com/lucasalexander/Crm-Sample-Code/tree/master/NodeClientDemo</a>. Having endured a lot of frustration in getting this to work, I'd like to share some additional notes that might be helpful if you decide to start using OAuth2 with CRM.</p>
<p>If you're not already familiar with OAuth2, I suggest you take a look at this post on the Microsoft Dynamics CRM blog that explains how CRM uses OAuth at a high level: <a href="http://blogs.msdn.com/b/crm/archive/2013/12/12/use-oauth-to-authenticate-with-the-crm-service.aspx">http://blogs.msdn.com/b/crm/archive/2013/12/12/use-oauth-to-authenticate-with-the-crm-service.aspx</a>. Although there's no code in that post, it will help you understand how OAuth authentication works.</p>
<h4 id="infrastructureandenvironmentprep">Infrastructure and environment prep</h4>
<p>I am running Dynamics CRM 2015 and Active Directory Federation Services (AD FS) on a single Windows Server 2012 R2 Azure VM. CRM and AD FS are configured for IFD. The CRM website is running on port 443. AD FS is running on port 444.</p>
<p>Before writing any code, I completed all the prep work outlined in this CRM SDK walkthrough - <a href="https://msdn.microsoft.com/en-us/library/dn531010.aspx">https://msdn.microsoft.com/en-us/library/dn531010.aspx</a>. Specifically I enabled OAuth2 for CRM, and I registered an OAuth2 client application in AD FS. Although this was all done as part of an on-premise Dynamics CRM deployment, I don't see any reason that it won't work with CRM Online.</p>
<h4 id="thecode">The code</h4>
<p>Now, let's take a look at the Node.js application. I chose to write a Node.js client instead of something in C# for two main reasons. First, Node.js made it easy for me to inspect and modify all the HTTP headers that are required for getting OAuth2 to work. Second, Node.js is basically server-side JavaScript, so most of the work I've done can be easily ported to a client-side JavaScript implementation.</p>
<p>My Node.js application is written using the <a href="http://expressjs.com/">Express web framework</a>, and it serves four web pages via routes. They are:</p>
<ol start="2">
<li>/ - This is the web application index page. It displays links to the login page and contacts display page.</li>
<li>/auth/login - This page handles redirection of the browser to the AD FS login page.</li>
<li>/auth/callback - This is the page to which AD FS redirects the user's browser after a successful login.</li>
<li>/authenticated/contacts - This page queries the CRM OrganizationData service for contacts using an OAuth token for authentication.</li>
</ol>
<p>The basic flow of the application is:</p>
<ol start="2">
<li>A user starts on the index page. The page checks for an OAuth token in a session variable. If no token is present, a link to the login page is shown. If a token is present, a link to the contact display page is shown.</li>
<li>When a user navigates to the login page, it makes a request to the CRM OrganizationData service to request the correct URL to use for authentication and redirects the browser to that page. The client id, resource name and redirect uri are all included in the query string of the request to AD FS. (See <a href="https://github.com/nordvall/TokenClient/wiki/OAuth-2-Authorization-Code-grant-in-ADFS">https://github.com/nordvall/TokenClient/wiki/OAuth-2-Authorization-Code-grant-in-ADFS</a> for more information on how this works.)</li>
<li>The user authenticates with AD FS, and then AD FS redirects the user to the callback page with an authorization code in the query string.</li>
<li>The callback page parses the authorization code from the query string and sends it to AD FS to request a token. The token is stored in a session cookie, and then the user is redirected to the index page, which should now show a link to the contact display page.</li>
<li>The contact display page reads the token from the session cookie and makes an Odata request to CRM with the token supplied as the authorization header. The results are then parsed and displayed.</li>
</ol>
<p>A few caveats:</p>
<ol start="2">
<li>OAuth2 tokens eventually expire. The default AD FS OAuth2 token expiration value is 3600 seconds (one hour). It is possible to request a new token using a refresh token that is provided at the same time as the authorization token. Using the refresh token allows for reauthorization without needing to supply credentials again. My code sample does not demonstrate use of a refresh token.</li>
<li>My sample application doesn't have much in the way of error handling, and it has not been extensively tested. If you plan to use OAuth with CRM, I highly recommend you don't just deploy my code in production without any further testing.</li>
</ol>
</div>]]></content:encoded></item><item><title><![CDATA[A Better Dynamics CRM E-mail Editor With TinyMCE (updated for IE8 and 9)]]></title><description><![CDATA[<div class="kg-card-markdown"><p>When I developed the JavaScript for my <a href="http://www.alexanderdevelopment.net/post/2013/06/26/A-Better-Dynamics-CRM-E-mail-Editor-With-TinyMCE">A Better Dynamics CRM E-mail Editor With TinyMCE</a> post back in June, I tested it in both Chrome and IE10. Since then I have gotten a few reports of it not working in IE8 or IE9. Initially, I thought there might be a</p></div>]]></description><link>https://alexanderdevelopment.net/post/2013/08/14/a-better-dynamics-crm-e-mail-editor-with-tinymce-updated-for-ie8-and-9/</link><guid isPermaLink="false">5a5837226636a30001b976e2</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[CRM 2011]]></category><category><![CDATA[programming]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Thu, 15 Aug 2013 00:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>When I developed the JavaScript for my <a href="http://www.alexanderdevelopment.net/post/2013/06/26/A-Better-Dynamics-CRM-E-mail-Editor-With-TinyMCE">A Better Dynamics CRM E-mail Editor With TinyMCE</a> post back in June, I tested it in both Chrome and IE10. Since then I have gotten a few reports of it not working in IE8 or IE9. Initially, I thought there might be a problem with the way the earlier versions of IE were handling the protocol-relative URLs to the TinyMCE CDN script, but it turned out that the script just wasn't loading properly when the editor popup was initially launched. I'm not entirely sure I understand why this is, but I suspect it has something to do with the way IE8 and 9 handle the page load event for dynamically constructed popup windows. Even after I reworked the popup window script to use jQuery, document.ready didn't behave as expected.</p>
<p>Ultimately my fix involved writing a function that uses jQuery to load the TinyMCE script and then make the editor UI visible after that. Because document.ready misbehaves in my popup window, I use the good old body onLoad attribute to call the custom function. You can download the updated web resource here: <a href="https://alexanderdevelopment.net/content/images/2013/08/crmemaileditor.htm.txt">crmemaileditor.htm.txt</a>.</p>
<p>Happy e-mailing (again)!</p>
</div>]]></content:encoded></item></channel></rss>