SPEasyForms: Why is my Form so Jumpy?

As in why does it look just like an OOTB form for several seconds, then all of the sudden it is redrawn with tabs, and hidden fields, and all of the stuff I configured SPEasyForms to do? This annoys me! my users! my boss! how do I fix it?

Out of the box, when a form first loads it is displayed as a standard SharePoint form without any SPEasyForms containers or conditional visibility applied. Then there is a delay and eventually the form is redrawn with all of the SPEasyForms configuration applied. How long a delay depends on a lot of things like how fast is your SharePoint farm, how good is your network bandwidth and latency, and how fast is your workstation/browser.

This is a common problem with any solution that waits for SharePoint to draw it’s OOB forms, and then manipulates the DOM to change the way they look and behave. You pretty much have to wait until the document is ready so the DOM elements you want to manipulate are present. This is a very simplistic explanation, especially with the timing issues in later versions of SharePoint and especially Office 365 caused by things like scripts on demand, and minimal download strategy, and how those interact with scripts loaded through custom actions; but most of you don’t really care about any of that, you just want me to provide a smoother experience for your users, right?

The short answer is, I can’t. More accurately, I can’t without modifying your master page, and that is something that I am completely unwilling to do in any kind of an automated fashion. I’ve been there, done that; it always ends badly. But I can tell you how you can modify your master page so that the user experience is a bit smoother, so that is what I’m going to do in this post. I’m only going to tell you what to put in your master page, not how to get it there. If you need help with that, there are many blog posts out there that can help with how to modify a master page in whatever version of SharePoint you have.

So for starters, add the following to your master page at the end of the HEAD section:

<!-- Hide forms until after SPEasyForms is loaded -->
<style type='text/css'>
table.ms-formtable { display: none; }
</style>

This is basically going to hide the form. If SPEasyForms is installed and you put this in your master page, your user’s will have a less jumpy user experience. The form will not be displayed until SPEasyForms is finished with it. If you’re wondering how the form is displayed at all, SPEasyForms has to look at each page load to see if this is a form, and if so do I have any configuration for this form, etc. When it is done processing, it always sets the display property of table.ms-formtable back to block so the form is displayed. It does this weather it had any other work to do or not, just so you can put the above text in you master page to give your users a less jumpy experience. Isn’t that nice of it?

But there is still a problem? Go to your solution gallery and disable SPEasyForms. Now try to load a form. That’s not cool; all of your forms are gone. So every time you disable SPEasyForms you need to modify your master page? And then enable it and modify it again? That’s not really so much a viable solution. And some day you’re going to get hit by a bologna truck as it were, and the poor sap who takes over your job is going to disable SPEasyForms for whatever reason, and nobody is going to know why all of your forms are gone. The good news is, it doesn’t have to be that way. Add the following code to your master page just before the closing body tag:

<SharePoint:ScriptBlock runat="server" id="spEasyFormsMasterPageScriptBlock" >
    // Get an array of all nodes with a given class. Optionally, return only nodes
    // with the given tagName, and start the search from the given node.
    function getElementsByClassName(className, tagName, node) {
        node || (node = document);
        tagName || (tagName = "*");
        var descendents = node.getElementsByTagName(tagName);
        var i = 0;
        var matched = [];
        for (; i < descendents.length; i++) {
            var currentClassName = descendents[i].className;
            if ((" " + currentClassName  + " ").indexOf(" " + className + " ") > -1) {
                matched.push(descendents[i]);
            }
        }
        return matched;
    };

    // Show the form table iff SPEasyForms is not loaded
    function showFormTable() {
        if (typeof (spefjQuery) === 'undefined') { // iff SPEasyForms is not loaded
            // get all tables with the class ms-formtable (should be only one)
            var tmp = getElementsByClassName("ms-formtable", "table");
            for (var ind = 0; ind < tmp.length; ind++) {
                // show the table
                tmp[ind].style.display = "block";
            }
        }
    }
        
    _spBodyOnLoadFunctionNames.push("showFormTable"); 
</SharePoint:ScriptBlock>

This will set the display property back to block if SPEasyForms is not loaded. The variable spefjQuery is a global variable that holds my instance of jQuery. Because of timing issues, we add showFormTable to _spBodyOnLoadFunctionNames, which causes SharePoint to execute the function after everything is loaded into the browser and just before the page is displayed. If spefjQuery is not defined by then, SPEasyForms is either not installed or not enabled so we need to show the form table; if it is defined we don’t do anything.

The first time I published this tutorial, I was in a hurry and I used document.getElementsByClassName to significantly reduce the amount of code in this script block, but with the shortcoming that it wouldn’t work in older broswers. That wasn’t a very satisfying solution, so I came back to fix it and discovered I had a pretty big problem. The solution described above does not work at all on SharePoint 2013 or Office 365 with the feature Minimal Download Strategy (MDS) enabled. I had to spend a couple of days working out similar issues with SPEasyForms itslef with MDS enabled, but the solution there was not applicable to this case. It took a couple more days of fiddling, but I did come up with a solution, which looks like:

<!--NOTE: replace the opening ScriptBlock tag with a plain old script tag on SP 2010 (i.e. <script type="text/javascript">)-->
<SharePoint:ScriptBlock runat="server" id="spEasyFormsMasterPageScriptBlock" >
    // Get an array of all nodes with a given class. Optionally, return only nodes
    // with the given tagName, and start the search from the given node.
    function getElementsByClassName(className, tagName, node) {
        node || (node = document);
        tagName || (tagName = "*");
        var descendents = node.getElementsByTagName(tagName);
        var i = 0;
        var matched = [];
        for (; i < descendents.length; i++) {
            var currentClassName = descendents[i].className;
            if ((" " + currentClassName  + " ").indexOf(" " + className + " ") > -1) {
                matched.push(descendents[i]);
            }
        }
        return matched;
    };

    // Show the form table iff SPEasyForms is not loaded
    function showFormTable() {
        if (typeof (spefjQuery) === 'undefined') { // iff SPEasyForms is not loaded
            // get all tables with the class ms-formtable (should be only one)
            var tmp = getElementsByClassName("ms-formtable", "table");
            for (var ind = 0; ind < tmp.length; ind++) {
                // show the table
                tmp[ind].style.display = "block";
            }
        }
    }
        
    // If Minimal Download Strategy is enabled, call showFormTable once 
    // each time start.js is loaded, otherwise add it to 
    // _spBodyOnLoadFunctionNames just like before.
    function initShowFormTable() {
        // if asyncDeltaManager is defined, Minimal Download Strategy is enabled
        if (typeof(asyncDeltaManager) !== "undefined") {
            // call show form table once each time start.js is loaded (i.e. each 
            // partial page load)
            ExecuteOrDelayUntilScriptLoaded(function () {
                asyncDeltaManager.add_endRequest(showFormTable);
            }, "start.js");
        }
        else {
            // otherwise, just add showFormTable to the list of function names 
            // to call on load
            _spBodyOnLoadFunctionNames.push("showFormTable"); 
        }
    }
     
    initShowFormTable();
</SharePoint:ScriptBlock>
<!--NOTE: replace the closing ScriptBlock tag with a plain old closing script tag on SP 2010 (i.e. </script>)-->

Basically, I replaced the last line of the script block with the function initShowFormTable and I call that function. At a high level, initShowFormTable does the following:

  1. Determine if MDS is enabled; it does this by checking if asyncDeltaManager is defined. This object is the client-side engine for partial page rendering so if MDS is enabled it will be defined.
  2. If MDS is not enabled, it adds showFormTable to _spBodyOnLoadFunctionNames just like before.
  3. If MDS is enabled, it does the following:
    1. Wait until start.js is loaded. This effectively does the same for partial page loads that _spBodyOnLoadFunctionNames does for full page loads, it waits for the SharePoint JavaScript context to be initialized before running a function. It does this by calling ExecuteOrDelayUntilScriptLoaded with an anonymous function and ‘start.js’ as arguments. When start.js has loaded, the anonymous function will be executed. In fact, under the hood init.js calls ExecuteOrDelayUntilScriptLoaded and waits for core.js before calling back the _spBodyOnLoadFunctionNames, so its really doing the exact same thing except that core.js isn’t loaded for all partial page loads so I wait for start.js instead on partial page loads.
    2. The anonymous function just calls asyncDeltaManager.add_endRequest with showFormTable as a callback, which tells the delta manager to call showFormTable when the partial page load is finished.

Either way, showFormTable just shows the form if SPEasyForms is not loaded, just like before. At this point our master page will hide the form table until SPEasyForms has done its work and show it again if SPEasyForms is not loaded, and it should work on SharePoint 2010 and/or 2013/Office 365 with or without Minimal Download Strategy enabled.

I’ve tested this primarily on Office 365, with or without MDS enabled, and with or without SPEasyForms enabled. I haven’t done anything fancy or used any new fangled JavaScript stuff, so logically I think it should work fine on SharePoint 2013 and 2010 and with older browsers so long as they work reasonably well with SharePoint in the first place (I wouldn’t want to vouch for Netscape 4.5). If you try this and run into a problem on 2013/2010 or on an older browser, let me know and I’ll take a closer look. Note that per the comments, you need to replace the ScriptBlock tags with script tags on SharePoint 2010. The ScriptBlock control was introduced later (thanks to Casey D for testing it and pointing that out to me). Just as important, don’t use plain script tags on 2013 or Office 365 or Minimal Download Strategy could make you very unhappy.

I’ve attached a complete example, not so much for you to use but just in case my descriptions of where to insert this stuff into your master page aren’t clear. This is the OOB seattle.master from a Teams subsite on Office 365, with the modifications described in this post, so don’t try to use it on SharePoint 2013 or 2010 for sure.

seattle.zip

2 thoughts on “SPEasyForms: Why is my Form so Jumpy?

  1. I have tried installing the SPEasyForms on SharePoint Foundation 2013. I have gone through the Why Is My Form So Jumpy steps. I can only see items that are in the Default Container. Additionally, while editing, I never see any but the Wizard container in the form view pane on the right. Are you able to advise on a solution for this? This product looks great, but I’m unable to get it working currently.

    Please advise. Thank you in advance!

    Like

    1. Hi Jeremy,

      Before trying the ‘Why is My Form So Jumpy?’ steps, did SPEasyForms appear to be working correctly? I assume so from your description, so if that’s not true the rest of my response may not make much sense ;). It doesn’t surprise me that there may be significant differences in the master page for SharePoint Foundation.

      As a general rule, I’d say probably some bit of JavaScript is crashing so the code isn’t running to completion. Either that, or some HTML element I’m expecting doesn’t exist so I’m appending my DOM elements to nothing. That’s not all that helpful, but I need more info to offer much more. If you have any experience using IE developer tools, you could launch that in debug mode and see if you can get more information.

      I haven’t used SharePoint Foundation much or in quite a while, in fact I haven’t used SharePoint Foundation 2013 at all, only 2010. And I checked on CloudShare and don’t even see any SharePoint Foundation 2013 VMs I can spin up so it could be hard for me to look at (they do have 2010). But if you could send me that modified master page, I might be able to spot something (click my user name on CodePlex and click the ‘contact’ link to send an email directly, and I’ll respond so you can send me the master page as an attachment and I’ll see what I can see).

      Joe

      P.S. I’m responding here and on CodePlex, but it will probably be a bit easier if we continue this conversation only on CodePlex.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s