Tuesday, November 13, 2018

How To Fix Email Always Being Dirty

Recently, some customers complained that the email form was always showing as dirty.  Normally this happens when something is triggered post save that makes the form dirty again, and tracking it down can be difficult.  The simplest thing (assuming you have some JS executing on the on save) is to put a break point in the on save, and see what attributes are dirty in the console window:
Xrm.Page.getAttribute().map(function (a) { return a.getIsDirty() + " - " + a.getName(); });
The other option is to look at the requests either in the F12 Developer Tools, or in Fiddler, and see what data is being sent to the server.  In this case though, the only field that was being marked as dirty was the description, but I couldn’t visually see any changes that were occurring.  The description in the email in this case contained some html that was inserted via a signature template, so I decided to see if there was anything in the html that was different, so I edited the JS to include storing the description in a class level variable that then could be used to compare with the current value in an on change event.  Sure enough, there were some differences in the html.  The spaces after semicolons and colons in tags were being removed, as well as quotes around numbers (ie. size=”3” –> size=3).  Finally I also noticed that blank characters were being encoded differently as well (“ ” vs “&nbps;”).  After a good deal of trial and error, I finally came up with this supported solution (Note, this type of solution can be applied to any situation where you want to ignore formatting differences.  Also, this is for an 8.2 CRM instance, so if this issue occurs in the new UI for CRM, you’ll need to access the context in the correct manner): 
var serverEmailDescription = "";
var ignoreDescriptionUpdates = true;
/**
 * When dealing with html in the body, the form will format it differently than the server, resulting in some changes happening post save.
 * This then shows up as the field being dirty, but saving it again, will update the format again, and cause it to still look dirty
 * Fix is to mark it as submitmode = never if it isn't really dirty.  This will prevent the form from looking like it needs to be saved.
 */
function handleDescriptionAlwaysBeingDirty() {
    var description = Xrm.Page.getAttribute("description");
    if (!description) {
        return;
    }
    serverEmailDescription = removeServerDifferentFormatting(description.getValue());
    description.setSubmitMode("never");
    description.addOnChange(submitIfActuallyDirty);
    Xrm.Page.getAttribute("modifiedon").addOnChange(function() {
        serverEmailDescription = removeServerDifferentFormatting(Xrm.Page.getAttribute("description").getValue());
        description.setSubmitMode("never");
        ignoreDescriptionUpdates = true;
        setTimeout(function() { ignoreDescriptionUpdates = false; }, 1);
    });
    setTimeout(function () { ignoreDescriptionUpdates = false; }, 1);
}
 
function removeServerDifferentFormatting(v) {
    // Some Html Tags get surrounded with \"
    // Some spaces are added for ";" and ":"
    // Blank spaces are encoded differently
    return v.replace(newRegExp(": ", "g"), ":")
        .replace(new RegExp("\\\"", "g"), "")
        .replace(new RegExp("; ", "g"), ";")
        .replace(new RegExp(" ", "g"), " ");
}
 
function submitIfActuallyDirty() {
    var att = Xrm.Page.getAttribute("description");
    var description = removeServerDifferentFormatting(att.getValue());
    if (ignoreDescriptionUpdates) {
        serverEmailDescription = description;
        return;
    }
    if ((description === serverEmailDescription) === (att.getSubmitMode() === "dirty")) {
        att.setSubmitMode(description === serverEmailDescription ? "never" : "dirty");
    }
}
The main function is the handleDescriptionAlwaysBeingDirty function, which is called onLoad of the form.  It ensures that the description field is on the form, and then adds an onChange function to the modifiedOn and description attributes.  It also caches the initial value of the description, as well as setting the submit mode to “never”.
The modifiedOn attribute will get updated by the server post save, so the function can be used to store what the value of the description field was just after saving.  Due to the issue of the formatting being different, I first attempt to replace anything that would be different between how the server stores the format, and how the client framework updates it post save, before caching the description. 
Whenever the description is updated, either via a user, or post save, the formatting is normalized to compare it with the normalized cached version to see if it truly has changed.  If it has, the submit mode is changed from “never” to “dirty”.  The submit mode is updated for two reasons:
  1. There is no supported method to update the IsDirty flag.
  2. When an attribute isn’t set to be submitted, the form doesn’t check to see if it’s dirty when showing the “Unsaved Changes” text in the lower right hand corner of the screen.  This solves our infinite loop issue with the description always being updated!
Please note, this is not a perfect solution.  If someone edits the description of the e-mail and adds a space after a colon or a semicolon, the change won’t be registered,  Also the function removeServerDifferentFormatting may not include all of the possible formatting changes.  But it works on my machine and resolves the current issue at hand, and hopefully it is helpful for you as well!

Wednesday, September 12, 2018

Tell Your Code To Debug When You’re Good And Ready!

When doing dev work for the Early Bound Generator I frequently have to kick off a process in the XrmToolBox, and then attempt to attach my debugger as quickly as possible.  This is a really annoying race condition, and if I’m using local metadata, a race that I don’t usually win.  I can’t believe I hadn’t thought of this before, and for all I know the pattern exists else where in the wild, but it made my “attach to process to debug” workflow a guarantee, rather than a race:

if (ConfigHelper.GetAppSettingOrDefault("WaitForAttachedDebugger", false))
{
    while (!Debugger.IsAttached)
    {
        Console.WriteLine("[**** Waiting For Debugger ****]");
        Thread.Sleep(3000);
    }
}

The ConfigHelper is a helper class in the DLaB.Common that will look for the given AppSettings in the Config file, and if it doesn’t exist, default to the given value (“false” in this case).  So if I add an AppSetting to “WaitForAttachedDebugger” in my config, then my app will wait until a debugger is attached, checking indefinitely every 3 seconds to see if I’ve finally attached it. 

If you found this helpful, make sure to check out, my Raise The Bar video series

Happy coding!