SharePoint Forms and Workflow – A Different Perspective

advanced-formWhenever I get into conversations about forms in SharePoint (or anywhere else for that matter), the conversation almost always turns immediately for workflow. It seems to greatly surprise a lot of people when I say that sometimes workflow is irrelevant for forms. I’d say that 80%+ of SharePoint forms have no workflow at all. (I think it’s a higher percentage, but I know many of you live and die by workflow.)

I think forms and workflow are too often intertwined as concepts, making the forms discussion overly complex.

IMO, forms are for collecting or editing data. Workflows are for managing that data. By keeping those two concepts discrete, we can have excellent forms that just do what forms should do.

Conflating the two will probably delay the possibility of a robust new form tool for SharePoint. We know that something is coming to replace InfoPath, but we don’t know what it is yet.

We learned early in 2014 that InfoPath is dead. In actuality, it’s not dead; it’s only entered its twilight years. We have until 2023 before it isn’t “supported” anymore, and it will probably be useful for many people even after that. (I won’t make any snarky comments about “supported” software.)

I made up the 80% number above based on my own experience. It really depends on type of SharePoint installation you’re working in. My work is more toward the KM and Intranet side of things, and it’s very rare that I end up implementing a workflow. Knowledge workers can be trusted to do their work in the right way to create value, and it’s rarely a sequential or predictable thing. The few cases where workflows matter – time sheet submission, time off requests, article posting, etc. – the workflows tend to be very simple.

For similar reasons, I haven’t seen much need for InfoPath. With a little JavaScript and CSS, I can usually layer a veneer over the default list forms to give them any boost they need to meet business needs. Even so, the default list forms are fine probably 90%+ (another number I’m making up) of the time.

So much of this depends on the culture of the organization, too. If it’s an open and trusting culture, workflows come up infrequently. If it’s more of a command and control culture, they want workflows for everything. That is until you ask them to describe the repeatable process and they realize that there really isn’t one. Either they have to define a real process (lots of hard work) or they keep doing things the way they have – in a slightly disorganized way that still works.

My point is that assuming that there’s always a coupling of forms and workflow means that everything gets more complicated fast. I like the fact that forms and workflow are separate but connectable in SharePoint now. It means I can plug in a workflow if and when I need it; the forms engine isn’t too cluttered by the workflow artifacts.

What is your experience on this? Are forms and workflow always intertwined or are they really two separate ideas? I’ve created a little poll below to capture your feelings on this. Add your voice into the mix and I will try not to use the statistics inappropriately, as do may others.

SharePoint List Settings Issue: “Internet Explorer is required to use this feature.”

This one belongs firmly in the “things that make you go hmm…” department.

Let’s say you are a good do-be like me and you use all of the Microsoft products that you can get your hands on. You’ve got email and SharePoint running on Office 365, you use all the Office applications and tools, you’re up to date on your version of Internet Explorer… You get the picture.

Well, sometimes all that good behavior doesn’t pay. Today I wanted to edit an InfoPath form attached to a list in SharePoint 2013 on premises. (You won’t run into this problem on Office365, at least it doesn’t happen in my tenant. Office365 seems to know IE11.) I went to the List Settings and clicked on the Form Settings link. I’m using Internet Explorer 11, so I’m as up to date as I can be without getting into beta territory.

Lo and behold:

Internet Explorer is required to use this feature.

Internet Explorer is required to use this feature.

Yup. I’m using Internet Explorer and the message says “Internet Explorer is required to use this feature.” The issue is that the on premises install that I’m working with doesn’t understand Internet Explorer 11. It *should* see it as Internet Explorer, though, and give me a better message. I’m not exactly sure what patch level we’re on, but I think it’s pretty close to current.

Fortunately, there is an easy fix for this. Simply hit F12 to bring up the Developer Tools and scroll all the way down to the bottom of the left side icon list.The one you want to click on looks like this:

Emulation

That will give you access to the Emulation settings. Change the User agent string setting to Internet Explorer 10.

2014-06-18_10-21-10

Fixing the issue by changing the User agent string

The page will reload and now SharePoint will understand that you are the good do-be that you are, speaking IE10ish instead of IE11ish. After you do your work, it’s a good idea to switch back to default so that you are speaking IE11ish again.

The Emulation settings give you a bunch of other nice tools (want to act like an XBox360?), so if you have interest in testing things in different browsers types, etc., be sure to check out the rest of the settings as well.

Easily Hide Columns on a SharePoint Form with jQuery

This is a really simple little thing. I end up writing little functions like this all the time, and never think much about them. But they are darn useful.

This little function will hide a list column’s row in the form. You might want to do this on a NewForm but not the EditForm, for instance, so setting the column to be hidden may not be a good answer. With this little function, you can hide the column’s row conditionally or on page load – whatever suits your fancy.

Here’s the function. All you need to pass it is the DisplayName of the column.

// Function to hide a column's row in the form
function hideColumn(c) {
  $(".ms-formlabel h3 nobr").filter(function() {
    var thisText = $.trim($(this).clone().children().remove().end().text());
 //   alert("::" + thisText + "::");
    return thisText.indexOf(c) === 0 && thisText.length === c.length;
  }).closest("tr").hide();
}

Then you might call the function like this:

$(document).ready(function() {
  hideColumn("Priority");
});

This will simply hide the column, but any value in it will still be submitted.

For instance, I just added a column to a list called “User Agent String”, set like so:

$("textarea[title='User Agent String']").val(navigator.userAgent);

and then hid it:

hideColumn("User Agent String");

Put it together and what have you got? (Bonus points to anyone who knows the next line in the song.)

$(document).ready(function() {

  // Set the User Agent String
  $("textarea[title='User Agent String']").val(navigator.userAgent);

  // Hide the columns which aren't relevant to the user
  hideColumn("User Agent String");

});

// Function to hide a column's row in the form
function hideColumn(c) {
  $(".ms-formlabel h3 nobr").filter(function() {
    var thisText = $.trim($(this).clone().children().remove().end().text());
 //   alert("::" + thisText + "::");
    return thisText.indexOf(c) === 0 && thisText.length === c.length;
  }).closest("tr").hide();
}

Improved Efficiency Is Possible with No Job Loss

The other day I tweeted:

http://twitter.com/#!/sympmarc/status/124109058532835328

Dan Dragnea called me on it, fairly enough:

The thing that caused me to tweet what I did was an annoying one with a simple fix, and there was a reason I tacked on the “with no cost in jobs” bit. I was driving out of the parking ramp (garage to those of us who aren’t from the upper Midwest) at my hotel. There were two exit lanes, one marked with a sign saying “Monthly Cardholders Only” and the other saying “Hotel Guests, Credit Cards, and Cash”. All of the cars were dutifully lined up in the latter lane, since (I assume) none of us were monthly cardholders. When I got to the booth, I noticed that the other lane also had a card reader for hotel guest keys as well as credit cards. I pointed this out to the main in the booth and he basically said “Huh. I never noticed that.” So the twenty or so of us who waited in one lane could have been using the other lane instead, at least those of us not using cash.

This is the sort of no job loss inefficiency that drives me crazy. Not only is the booth guy necessary, but someone could get a little work out of fixing the signs.So in this case, it’s a tiny job *gain*, not a job loss to improve the efficiency.

Back in the 1990s, the concept of efficiency was probably irrevocably intertwined with job loss. The Business Process Reengineering (BPR) movement and characters like Chainsaw Al’s tenure at Sunbeam caused many people to begin to fear for their livelihoods. (Think about how good we all had it back then compared to the world of impermanence we now inhabit.)  This was a huge detriment to the knowledge management consulting work we were doing at Renaissance Solutions at the time because many people thought that allowing their knowledge to be captured and disseminated was going to make them obsolete. There was literally no goal for that in the work that we were doing. By elevating everyone’s performance, we hoped to allow everyone to work on more value-added tasks than the mundane or rote parts of their jobs where they added less value. This was around the dawning of the knowledge worker moniker. We don’t want to get rid of well-performing knowledge workers (the badly performing ones are a totally different story); we want them to work on things that will add more to the bottom line.

Now we call all of this collaboration and we run into many of the same fears and doubts. Add to the mix the erosion of employee *and* employer loyalty, and it’s reasonable for the fears to be even stronger than they were back then.

There *are* no-job-loss efficiencies we can implement all around us, though, just like that parking ramp example. The guy in the booth would be happier because people wouldn’t be as testy when they got to him, and each person would save some few minutes on their way out.

Think about the person in HR who has to collect and collate all of the paper forms we use to elect our benefits. (Yes, plenty of places still use paper-based forms.) They probably don’t love that work and they probably feel that they should be doing something more important. Well, by electronifying those forms and routing them to the right places automagically with a workflow, that person can focus on something we all care about, like improving HR’s customer service around the process or holding more informative sessions explaining what the forms cover. By improving the efficiency, we can improve the overall experience, and there’s no reason a job would be lost.

Or take the case of a team working to accomplish a project’s goals. If we can make that team more efficient by giving them a robust team workspace in which to asynchronously collaborate, then they will have more time to find other experts in the organization to bring into their effort, or pursue tangential possibilities that may address the project goals better, or do more research using external sources, or attend more educational conferences. No one loses a job, the team performs at a higher level of efficiency *and* effectiveness, and we’ve opened the door to more serendipitous results.

I know that I may be a bit of a Pollyanna about all of this. There’s no guarantee that no one will lose a job when greater efficiencies are introduced. But it’s not an inevitable outcome. Take that extra manpower and energy and focus it on process improvement, and you’ll get direct benefits to the bottom line. And we all might get out of the parking ramp a little bit quicker.

Taming Long SharePoint List Forms Using jQuery to Break Them into Sections

Do you have any lists in SharePoint where the forms seem to go on and on, marching down the page, seemingly ad infinitum? I’ve seen lists which have over a hundred, even over 300 columns. This is a horrible thing to do to your users for so many reasons, but one of the primary reasons is that the list forms are simply unmanageable and in many cases unfathomable.

If you do have that many columns in a list and they really do need to be there, consider building custom “sub-forms” which take a more wizard-like approach. What I mean by this is that you might have a relatively short initial, customized version of the NewForm which collects all of the required information. When your user saves that form, you can use SPRedirectWithID from SPServices to go to the next form in the sequence, and so on. You could, of course, develop totally custom forms, whether by using traditional .NET development or by using jQuery, and probably jQueryUI plus other plugins, and the SPServices and the Web Services to write the data into your list(s).

But what if all of that seems overly complex or beyond your skills? There’s a quick thing you can do to tame those ridiculously long forms a tiny bit. Your users still won’t love you, but they may not hate you as much.

The first thing I needed to do for this post was to create a new list with lots of columns. I didn’t have any of these horrid complicated lists lying around in my test environment.) I could have done this any number of ways, including manually, but I decided to write some jQuery with SPServices to do it.

<script type="text/javascript" language="javascript" src="jQuery Libraries/jquery-1.6.2.min.js"></script><script type="text/javascript" language="javascript" src="jQuery Libraries/jquery.SPServices-0.6.2.min.js"></script>
<script type="text/javascript" language="javascript">
  $(document).ready(function() {

    // Delete the old list (if present)
    $().SPServices({
      operation: "DeleteList",
      listName: "Complicated List",
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

    // Create the new list
    $().SPServices({
      operation: "AddList",
      listName: "Complicated List",
      description: "List with lots of columns",
      templateID: "100",  // Custom list
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

    var newFields = "<Fields>";

    for(i=1; i <= 100; i++) {
      newFields += "<Method ID='" + i + "'>" +
        "<Field Type='Text' DisplayName='Column_" + i + "' FromBaseType='TRUE' MaxLength='255' Description='Description of Column_" + i + "' />" +
        "</Method>";
    }
    newFields += "</Fields>";

    // Add a lot of columns to the list
    $().SPServices({
      operation: "UpdateList",
      listName: "Complicated List",
      newFields: newFields,
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

  });
</script>

This script simply creates a new list for me called Complicated List and adds 100 columns to it named Column_n. It was down and dirty, but you might see a more complex and useful version of this this as a poor man’s templating tool for building lists in your own environment. And yes, I’m doing this in good old WSS 3.0. It’s not such a bad place to be, and the same approach and code will work in SharePoint 2010. Isn’t SPServices grand?

Once I had my list to play with, I copied NewForm.aspx to NewFormCustom.aspx  – never, ever edit the default list forms – and set it as the form to use when creating a new item for the list. Then I added my CSS and script to NewFormCustom.aspx.

<script type="text/javascript" language="javascript" src="../../jQuery Libraries/jquery-1.6.2.min.js"></script><script type="text/javascript" language="javascript" src="../../jQuery Libraries/jquery.SPServices-0.6.2.min.js"></script>
<script type="text/javascript" language="javascript">
  // This function from SPServices gets all of the Query String parameters
  var queryStringVals = $().SPServices.SPGetQueryString();
  // We can pass in a section name on the Query String to expand by default
  var expandSection = queryStringVals.section;

  $(document).ready(function() {

    // Set up each of the sections -
    // see the setupSection function below for an explanation of the parameters
    setupSection("Section1", "This is Section One", "Title", "Column_5");
    setupSection("Section2", "This is Section Two", "Column_6", "Column_15");
    setupSection("Section3", "This is Section Three", "Column_16", "Column_25");
    setupSection("Section4", "This is Section Four", "Column_26", "Column_40");
    setupSection("Section5", "This is Section Five", "Column_41", "Column_80");
    setupSection("Section6", "This is Section Six", "Column_81", "Column_100");

    // If no section on the Query String, expand the first section
    if(expandSection === undefined) {
      $("tr[section-name='Section1']").css("display", "block");
      $("tr#Section1").addClass("demo-collapse");
    }

  });

  function setupSection(sectionShortName, sectionLongName, startColumn, endColumn) {
    /* Set up a form section
      Parameters:
        sectionShortName: This short name is used for element IDs and such. It cannot contain spaces or special characters.
        sectionLongName: This name is what is shown in the section header and can contain any text.
        startColumn: The first column in the section.
        endColumn: The last column in the section.
      The two column names should be the Display Name, e.g., "Product Name", not the InternalName, e.g., "Product_x0020_Name"
    */

    // Find the first and last row in the section and add the section header
    var sectionStartRow = findFormField(startColumn).closest("tr");
    var sectionEndRow = findFormField(endColumn).closest("tr");
    $(sectionStartRow)
      .before("<tr class='demo-section-header' id='" + sectionShortName + "'>" +
        "<td class='demo-section-header-cell' colspan=2>" + sectionLongName + "</td>" +
        "</tr>");
    var thisSection = $(sectionStartRow).nextUntil(sectionEndRow);

    // Give all of the rows in the section an attribute for the section name for easier manipulation later
    $(sectionStartRow).attr("section-name", sectionShortName);
    $(sectionStartRow).nextUntil(sectionEndRow).attr("section-name", sectionShortName);
    $(sectionEndRow).attr("section-name", sectionShortName);
    $(sectionEndRow).find("td").addClass("demo-section-last-row-cell");

    // Hide all of the rows in the section
    $("tr[section-name='" + sectionShortName + "']").css("display", "none");

    // Add the click behavior for the section header
    $("tr#" + sectionShortName).click(function() {
      var thisSectionRows = $("tr[section-name='" + sectionShortName + "']");
      if($(this).next("tr[section-name='" + sectionShortName + "']").css("display") == "block") {
        $(thisSectionRows).css("display", "none");
        $(this).removeClass("demo-collapse");
      } else {
        $(thisSectionRows).css("display", "block");
        $(this).addClass("demo-collapse");
      }
    });

    // Expand the section if there are any validation errors
    $("tr[section-name='" + sectionShortName + "'] .ms-formbody .ms-formvalidation").each(function() {
      if($(this).html().length > 0) {
        $("tr[section-name='" + sectionShortName + "']").css("display", "block");
        $("tr#" + sectionShortName).addClass("demo-collapse");
        // No need to look at any more rows
        return false;
      }
    });

    // Expand the section if it's been passed on the Query String
    if(sectionShortName == expandSection) {
      $("tr[section-name='" + sectionShortName + "']").css("display", "block");
      $("tr#" + sectionShortName).addClass("demo-collapse");
    }

  }

  // This function (borrowed from SPServices) finds a column's formBody in the page
  function findFormField(columnName) {
    var thisFormBody;
    // There's no easy way to find one of these columns; we'll look for the comment with the columnName
    var searchText = RegExp("FieldName=\"" + columnName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "\"", "gi");
    // Loop through all of the ms-formbody table cells
    $("td.ms-formbody").each(function() {
      // Check for the right comment
      if(searchText.test($(this).html())) {
        thisFormBody = $(this);
        // Found it, so we're done
        return false;
      }
    });
    return thisFormBody;
  } // End of function findFormField
</script>

And the CSS:

.demo-hidden {
	display:none;
}
.demo-main {
	height:250px;
	overflow:scroll;
}
.demo-no-item-selected {
	font-size:12px;
}
.demo-section-link {
	padding-left:15px;
	font-size:10px;
}
.demo-section-header {
	background-image:url('/_layouts/images/plus.gif');
	background-repeat:no-repeat;
	background-position:5px center;
	padding:3px 3px 3px 22px;
	background-color:#6699cc;
	font-weight:bold;
	color:#ffffff;
}
.demo-section-header-cell {
	border-top:1px #c2c2c2 solid;
}
.demo-section-last-row-cell {
	border-bottom:2px black solid;
}
.demo-collapse {
	background-image:url('/_layouts/images/minus.gif');
}

Et voila, expando-collapso.

If you’d like to see this working for real, you can take a look at this page on my demo site.

Don’t you hate it when forms “hide” the field that you need to fix? It happens way to often on forms out the on the world wild Web. So note that if there’s a validation error on the form that the script pops open that section to bring it to your attention. Column_13 is required on the demo site. Try saving the form without filling it in.

And finally, if you choose to pass in a section’s short name on the Query String, that section will be expanded instead of the first. Try ?section=SectionN, where N is [1-6].

Note: The CSS I’m using here in my little demo is intentionally very simple. When I built this for real, we had some nice little icons and backgrounds that made it feel even more helpful.