Aug 29 2011

Setting Up a DVWP to Use jQueryUI Accordion and Tabs

SPXSLTCarlos Nunes-Ueno posted some XSL you can use in a DVWP to the SPXSLT Codeplex Project a while back which allows you to easily utilize the jQueryUI accordion and tabs functions. I asked him if he’d mind if I did a blog post on it, and he agreed. (If you happen to read the thread, I admit that I had just gotten back from vacation at the time and thought that the post came from the SPServices Discussions. That’s why I sound confused – I was.)

Here’s what Carlos told me about himself:

I’ve been working with SharePoint pretty much exclusively for the past year.  I work for a major online retailer on internally facing sites and I’ve been focused primarily on getting processes off paper.  My previous background is mainly in MS Access.  I’m still working on the ins and outs of SharePoint and trying to wring as much functionality as I can out of it.  I have found jQuery and jQueryUI to be extremely useful tools for that.

Accordions and tabs are two of the popular widgets offered by jQueryUI. In case you haven’t seen them in action, here are some simple examples. (The links will take you to the jQueryUI site, where there are live examples.)

Accordion

accordionTabs

tabsBy structuring your markup in a specific way, you can make simple calls to jQueryUI to re-shape what you’ve rendered on the page into these cool user interface elements. Using a Data View Web Part (DVWP), you can pull the labels for the sections and content in the accordion or the tabs names and content from lists. All it takes is emitting some well-structured markup in your XSL.

Here are Carlos’ examples:

Accordion

<xsl:template name="dvt_1">
  <xsl:variable name="dvt_StyleName">Table</xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:variable name="dvt_RowCount" select="count($Rows)" />
  <xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0" />
  <xsl:choose>
    <xsl:when test="$dvt_IsEmpty">
      <xsl:call-template name="dvt_1.empty" />
    </xsl:when>
    <xsl:otherwise>
        <div id="accordion">
          <xsl:call-template name="accordionMarkup">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
        </div>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="accordionMarkup">
  <xsl:param name="Rows" />
  <xsl:for-each select="$Rows">
    <h3>
      <a href="#">
        <xsl:value-of select="@Title" />
      </a>
    </h3>
    <div>
      <xsl:value-of select="@panelText" disable-output-escaping="yes" />
    </div>
  </xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.empty">
  <xsl:variable name="dvt_ViewEmptyText">No items.</xsl:variable>
  <table border="0" width="100%">
    <tr>
      <td class="ms-vb">
        <xsl:value-of select="$dvt_ViewEmptyText" />
      </td>
    </tr>
  </table>
</xsl:template>

Tabs

<xsl:template name="dvt_1">
  <xsl:variable name="dvt_StyleName">Table</xsl:variable>
  <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
  <xsl:variable name="dvt_RowCount" select="count($Rows)" />
  <xsl:variable name="dvt_IsEmpty" select="$dvt_RowCount = 0" />
  <xsl:choose>
    <xsl:when test="$dvt_IsEmpty">
      <xsl:call-template name="dvt_1.empty" />
    </xsl:when>
    <xsl:otherwise>
        <div id="tabs">
          <xsl:call-template name="tabsList">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
          <xsl:call-template name="tabsPanels">
            <xsl:with-param name="Rows" select="$Rows" />
          </xsl:call-template>
        </div>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="tabsList">
  <xsl:param name="Rows" />
  <ul>
    <xsl:for-each select="$Rows">
      <li><a href="{concat('#tabs-',@ID)}"><xsl:value-of select="@Title" /></a></li>
    </xsl:for-each>
  </ul>
</xsl:template>

<xsl:template name="tabsPanels">
  <xsl:param name="Rows" />
  <xsl:for-each select="$Rows">
    <div id="{concat('tabs-',@ID)}">
      <xsl:value-of select="@panelText" disable-output-escaping="yes" />
    </div>
  </xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.empty">
  <xsl:variable name="dvt_ViewEmptyText">No items.</xsl:variable>
  <table border="0" width="100%">
    <tr>
      <td class="ms-vb">
        <xsl:value-of select="$dvt_ViewEmptyText" />
      </td>
    </tr>
  </table>
</xsl:template>

One of the nice things about jQueryUI is that using the basic functionality once you have the markup in place requires very little script.

For an accordion:

$(document).ready(function(){
  $("#accordion").accordion();
});

For tabs:

$(document).ready(function(){
  $("#tabs").tabs();
});

There are various options for both functions which you can read about on the jQueryUI site, but the simple script above gets you the default functionality. Obviously, you’d need to reference the jQuery and jQueryUI files and link to the jQueryUI css, but if you’ve used jQueryUI, or even just jQuery, you are probably familiar with how to do that.

Thanks, Carlos!

Permanent link to this article: http://sympmarc.com/2011/08/29/setting-up-a-dvwp-to-use-jqueryui-accordion-and-tabs/

Aug 24 2011

Using SPServices with jQueryUI’s Autocomplete Function on InfoPath Forms in SharePoint

Yes, that title says using jQueryUI on InfoPath forms in SharePoint. InfoPath forms are fantastic, except when they just aren’t quite able to do what you want. While InfoPath can help you take your SharePoint list forms to a whole new level, there may be times when you want to add some behavior to those forms which InfoPath simply doesn’t offer.

Guess what? jQuery and jQueryUI can come to the rescue in some of those cases, just as it can with the standard list forms.

I recently worked with a client to do a relatively simple thing, but it was a huge hit. They had a SharePoint list had a Single line of text column, to which the user could add whatever they wanted. However, there was a desire to make suggestions as the user typed, based on the contents of a different list, which had thousands of items.

A dropdown doesn’t make sense in that situation, because there are simply too many items. They also wanted to show matches to what the user had typed, regardless where those characters occurred in the list item.

This is a perfect situation in which to use the idea of autocomplete. We all are familiar with this idea, if not the actual term. You probably see it in action every single day as you use a search engine. As you type your search term(s), the search engine shows suggestions, probably based on some fairly sophisticated algorithms.

Here’s an example from Bing:

image

and from Google:

image

(I can’t help but point out that Google was more on the mark in guessing that I was searching for SPServices, but hey, who’s counting?)

When InfoPath forms are rendered in the browser, they are built of ordinary markup just like any other page, albeit fairly complicated markup, driven by a lot of script.  That doesn’t mean that you can’t add some script of your own as well to add additional capabilities. There are a few peculiarities to this situation, though, which you need to handle.

Here’s a slightly dumbed down version of the script we ended up with.

window.onload = function() {
  window.setTimeout(readyCall, 1000);
}

function readyCall(){

  var externalParties = [];

  $().SPServices({
    operation: "GetListItems",
    listName: "External Parties",
    CAMLViewFields: "",
    async: false,
    completefunc: function (xData, Status) {
      $(xData.responseXML).SPFilterNode("z:row").each(function() {
        externalParties.push($(this).attr("ows_Title"));
      });
    }
  });

  //<input tabIndex="0" title="" class="q_zwfUqJo2fRthHnM4_0 as_zwfUqJo2fRthHnM4_0 b9_zwfUqJo2fRthHnM4_0" id="ctl00_m_g_a226da68_1383_40e3_8410_1ada27d49dcf_FormControl0_V1_I1_T2" aria-invalid="true" style="position: relative;" onfocus="return (TextBox.OnFocus(this, event));" onblur="return (TextBox.OnBlur(this, event));" onpropertychange="return (TextBox.OnPropertyChange(this, event));" type="text" OriginalId="V1_I1_T2" FormId="ctl00_m_g_a226da68_1383_40e3_8410_1ada27d49dcf_FormControl0" ViewDataNode="3" direction="ltr" wrapped="true" ScriptClass="TextBox" VCARD_NAME="91161f891e59461042587839b2504693728ce05a" ?=""/>
  $("input[id$='FormControl0_V1_I1_T2'], input[id$='FormControl0_V1_I1_T3']").autocomplete({
    source: externalParties,
    minLength: 3
  });
}

When InfoPath forms load in the browser, they don’t load with the rest of the page, but instead they are loaded slightly afterward in what amounts to an asynchronous load. Because of that, using $(document).ready(), our trusted jQuery friend, doesn’t work. Instead, as you can see in lines 1-3, we simply wait for 1000 milliseconds (1 second) before we run our script. We found that this was an adequate amount of wait time for our particular form; you might need to adjust this.

In lines 9-19, we use my SPServices library to call the Lists Web Service, using the GetListItems operation. This operation simply reads items from the list based upon the criteria you specify. Once we have the data, we push each of the Title column values into an array called externalParties.

Finally, we call the jQueryUI function autocomplete, using two selectors. In line 21 above, which is commented out, you can see an example of the markup for one of the input elements rendered in the InfoPath form. One of the hardest parts of all of this was to figure out what selector to use. We settled on looking for an input element where the id contained ‘FormControl0_V1_I1_T2′.  (We actually added the autocomplete behavior to two columns in the form, thus the second selector for ‘FormControl0_V1_I1_T3′.)

We added this script into the newifs.aspx and editifs.aspx pages using a trusty Content Editor Web Part (CEWP). Since this was SharePoint 2010, and because it makes for far better code management, we stored the script in a separate file and referenced it using the Content Link.

Bingo-bango, we had a nice, little additional piece of functionality which made the users very happy. This is an example where thinking about all of the tools at your disposal and how you might glue them together into the right solution to get the job done can be the right approach rather than a lot of custom coding.

<UPDATE dateTime=”2011-08-25T23:51″>

My partner in crime for this exercise was Marcel Meth (@marcelmeth), and he’s done a post on it, too, which you can read here. I was able to steal his image of the results, which I’ve added above. Note that the image is not showing the real data but our test data, which was simply a list of the 3000 or so major cities and towns in the US.

</UPDATE>

<UPDATE dateTime=”2011-08-26T09:25″>

I got a question in the comments below about how we added the script to the InfoPath page, and I wanted to add those details here.

  • First, we opened each form in the browser, which launched it in a dialog box.
  • We got the actual URL of the page in the dialog by right-clicking and looking at its properties. The NewForm was newifs.aspx and the EditForm was editifs.aspx (as I mention above).
  • We opened the form page directly in a new browser window and used the toolpaneview=2 Query String parameter trick to put the page into edit mode. This allows you to edit a list form page and add Web Parts to it.
  • We added the CEWP, and put the reference to our script file in the Content Link.
  • Apply and done.

</UPDATE>

Permanent link to this article: http://sympmarc.com/2011/08/24/using-spservices-with-jqueryuis-autocomplete-function-on-infopath-forms-in-sharepoint/

Aug 22 2011

Middle Tier Magic: The SPSTCDC Web Site Speakers and Sessions Pages – Recording Available

Sharepoint Saturday ConferenceIn case you missed my Middle Tier Magic: The SPSTCDC Web Site Speakers and Sessions Pages webinar which I did a little over a week ago with Dux Sy (@meetdux), I just wanted to do a quick post about it.

The webinar was my “session” for SharePoint Saturday: The Conference (SPSTCDC) since I wasn’t able to attend. In it, I show the work I did to make the Sessions and Speakers pages on the SPSTC.org Web site work using Data View Web Parts (DVWPs) and some custom CSS.

The recording of the webinar is available at Screencast.com here.  As I mentioned during the webinar, all of the important code bits are shown in my post announcing the webinar. If you have any questions, please feel free to post a comment on this post.

Permanent link to this article: http://sympmarc.com/2011/08/22/middle-tier-magic-the-spstcdc-web-site-speakers-and-sessions-pages-recording-available/

Aug 18 2011

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.

Permanent link to this article: http://sympmarc.com/2011/08/18/taming-long-sharepoint-list-forms-using-jquery-to-break-them-into-sections/

Aug 15 2011

Compound Filtering in Data View Web Parts (DVWPs) with SharePoint Designer

When you build a Data View Web Part (DVWP) in SharePoint Designer, there are times when you might need compound filtering. By this, I mean something like:

Show me the items where (City=”Abington” or Approval Status=”Approved”) and Approval Status!=”Pending”

It’s sort of a silly example, but you should get the gist. Where you put the parentheses matters. The silly example below says the same thing, but I’ve moved the parentheses:

Show me the items where City=”Abington” or (Approval Status=”Approved” and Approval Status!=”Pending”)

This would give you a different result, so you need some way to tell SharePoint Designer where the “parentheses” should go.

When you go to create this type of filter in SharePoint Designer, it’s virtually impossible to figure out how to do it. The UI for this is horrid.

What you need to do is select the rows you’d like to group together by using Shift-Click on each row. The rows must also be adjacent. Then you can either right click on the highlighted rows and choose Group or click the Group button below.clip_image002

Selecting the rows is the horrid UI part. One would have thought that this would have gotten better in SharePoint Designer 2010, but…no such luck.

Once you’ve done that, you’ll see a little blue bracket on the left, as below. The grouping is sort of like parentheses.

clip_image004

If you want to ungroup, you Right-Click again and choose Ungroup or highlight one of the rows and click the Ungroup button.

Changing the And to an Or or vice versa is a little bit easier to figure out, but it’s still horrid UI. You can click on the And, which will display a dropdown which allows you to change it to Or.

clip_image006

Depending on what selections you make, the filtering you set up this way will end up in the CAML for the DVWP, which is the most efficient way to filter. In some cases, your filtering will end up happening in the definition of the Rows variable, something like this:

<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[@City = 'Abington']"/>

This is *usually* less efficient, but there are exceptions to everything; you still need to understand your information architecture to know what’s best. You can add this sort of filtering manually (I typed it myself in the example above) or check the “Add XSLT Filtering” box and then the “Edit” button.

In all of these cases, the UI leaves a lot to be desired, but you can get the job done if you persevere.

Permanent link to this article: http://sympmarc.com/2011/08/15/compound-filtering-in-data-view-web-parts-dvwps-with-sharepoint-designer/

Page 30 of 161« First...1020...2829303132...405060...Last »
%d bloggers like this: