(note: this was reposted to fix issues in the code from here: https://blogs.msdn.microsoft.com/peterhauge/2015/10/01/how-to-enable-rich-text-in-dynamics-crm-using-ckeditor/ , so all the comments related to this post are located at the URL above).
I recently found myself in the position where we needed to submit notes from customer conversations into Dynamics CRM. I’m a fan of ‘fancy’ text (headings, bullet points, highlighting) to help make the notes much more readable, but Dynamics CRM doesn’t support this out of the box on their website. Instead of making my notes less fancy, I opted to just enable this capability in Dynamics CRM! We will be using the CKEditor in this example, you can find it here: http://www.ckeditor.com/ .
There are already a few options online for doing this:
In my case, I was looking for something a little simpler where I didn’t need to do synchronization between the nested iFrame and the control that I was trying to replace. I’m planning on using CKEditor which does a great job replacing controls inline, let’s make that work!
The first step is to create a web resource to hold some JavaScript. We are going to do everything dynamically in JavaScript so we won’t need any other resources, files, scripts, etc. You can do this by navigating to the form that you want to change (the one that needs some fancy text!) and open the form editor. Click the “Form Properties” button on the toolbar, this is where we can add a web resource. On the dialog that comes up, click “Add”
On the next dialog we hit “New” to add a new web resource, this is the place where we’ll put our JavaScript to convert controls on the page!
On this next dialog (to create a new web resource), we fill out the fields (example below) and can either upload the javascript file or we can paste in the javascript by clicking on the “”text editor”
Now comes the interesting part – what JavaScript should we be using anyway? Well, the first step is importing the CKEditor library. We can’t just do a straight import because it’s considered cross site scripting (since the JS file resides in another domain) so we need to be a bit more clever to make this work. There is a way to load another JavaScript file and that’s to insert another “script” element in the “<head>” section in the html. Once we do that, the browser will load the CKEditor JavaScript file for us to use.
var doc = parent.parent.document.getElementById("contentIFrame0").contentWindow.document; var headblock = doc.getElementsByTagName('head')[0]; var newscriptblock = doc.createElement('script'); newscriptblock.type = 'text/javascript'; newscriptblock.src = '//cdn.ckeditor.com/4.5.3/standard/ckeditor.js'; headblock.appendChild(newscriptblock);
Line by line, we first grab the ‘head’ element, but you’ll see the ‘parent.parent’ bit in there. We need this because CRM puts the web resource inside an IFrame, and that IFrame is inside another IFrame as part of the content in the form. So we use “parent.parent” to go up two levels, then get the main content IFrame so we can start adding elements there. Next few lines we construct the script block we need, and last line we append that script block to the original head element.
At this point the browser will start loading the script file! BUT, we have a catch, we can’t just write more JavaScript to start the replacement because we need to first wait for the CKEditor.js file to be loaded (and it’s dependencies). So, we register an “onload” event before adding the script block that will do the actual replacements. Like this:
newscriptblock.onload = function() { // We put our code here to get rich text };
In my case, I have several text fields I want to replace, so instead of writing the code for each one separately, I created a string array with all the field names and will loop through. We also need to do some tweaking on the page when the editor is loaded (hide the ‘value’ version of the field and show the ‘edit’ version of the field), we need to update the underlying field whenever the user types into the Rich Text Editor control, and wrap the whole thing in a function call (so we can call it from the form load method. All in all the script came together nicely – you can see the full details below!
UPDATE [2/8/2016]: I updated the code below to work with the latest Dynamics CRM Online, thanks to Anton Kurnitzky for his help!
UPDATE [4/17/2017]: Code below updated again to work with forms & more dynamic UI, thanks to Brian Poff for his help! NOTE: In this version you have a few more places to update, search for “description”, your first field in the list should be updated in all the locations. Thanks!
function convertToRichText() { var CKIFrame = findCKEditorFieldIFrame("description"); // import the ckeditor script, but do it without web resources.. so we create a script tag in the DOM var headblock = parent.parent.document.getElementById(CKIFrame).contentWindow.document.getElementsByTagName('head')[0]; var newscriptblock = parent.parent.document.getElementById(CKIFrame).contentWindow.document.createElement('script'); newscriptblock.type = 'text/javascript'; // we have to wait until the script is loaded before we can use it, so registering a callback event newscriptblock.onload = function () { var CKIFrame = findCKEditorFieldIFrame("description"); // some configuration for the CKEDITOR rich text control var CKE = parent.parent.document.getElementById(CKIFrame).contentWindow.CKEDITOR; CKE.config.allowedContent = true; CKE.config.toolbarCanCollapse = true; CKE.config.toolbarStartupExpanded = false; CKE.config.width = '95%'; var fieldsToReplace = ["description", "detailedstatus"]; for (var i = 0; i < fieldsToReplace.length; i++) { var fieldname = fieldsToReplace[i]; // We find the 'edit' control for the engagement overview and replace it with a rich text control var richtexteditor = CKE.replace(fieldname + '_i'); richtexteditor.on('instanceReady', function () { parent.parent.document.getElementById(CKIFrame).contentWindow.document.querySelector('div#' + fieldname + ' > div.ms-crm-Inline-Value').style.display = "none"; parent.parent.document.getElementById(CKIFrame).contentWindow.document.querySelector('div#' + fieldname + ' > div.ms-crm-Inline-Edit').style.display = "inline-block"; parent.parent.document.getElementById(CKIFrame).contentWindow.document.querySelector('div#' + fieldname + ' > div.ms-crm-Inline-Edit').style.width = "95%"; (function (field) { richtexteditor.on('change', function (evt) { // when the value in the rich text control changes, we update the underlying entity field + the 'view' version of the control Xrm.Page.data.entity.attributes.get(field).setValue(richtexteditor.getData()); }); })(fieldname); }); } }; newscriptblock.src = '//cdn.ckeditor.com/4.5.3/standard-all/ckeditor.js'; headblock.appendChild(newscriptblock); } function findCKEditorFieldIFrame(fieldName) { var frameNum = 0; var doc = parent.parent.document; while (doc.getElementById("contentIFrame" + frameNum.toString()) != undefined) { if (doc.getElementById("contentIFrame" + frameNum.toString()).contentWindow.document.getElementById(fieldName) != undefined) return "contentIFrame" + frameNum.toString(); frameNum++; } }
Now that we have the script, we copy/paste this into the “Text Editor” for the web resource we were creating above and click “Save” and close the Web Resource form, then click “Add” on the Look Up Record dialog box (assuming the web resource you just created is selected). Next, on the “Form Properties” dialog under Event Handlers, we click “Add” to add the function call to the JavaScript we included above.
This part is the easy part, we just need a pointer to our function above! We named our function convertToRichText, so I filled out the dialog like this:
Click OK, save the updated form and publish live – you should be good to go! At this point, when the page loads the text field will be replaced by a rich text field, changes will get automatically applied to the underlying field and you’re all set. I particularly like this approach (sitting inside the DOM with the rest of the elements instead of inside an IFrame) because you can resize the editor (grab the lower right and drag) to make the editor bigger and all the elements will fall in correctly around it, auto-magically!