SPServices Stories #11 – Using SPServices and jQuery to Perform a Redirect from a SharePoint List NewForm to EditForm

This entry is part 11 of 21 in the series SPServices Stories

Introduction

I ran across this SPServices Story in a post on the blog at CTS. The author of the post, Matt Ingle, is a Senior Consultant at CTS in Birmingham, AL. According to Matt, he is a team lead for a group called the SharePoint Factory. The SharePoint Factory team roles correspond to the various areas of the SharePoint family of products. This role-based approach allows for technical depth and specialization within the SharePoint functional areas.

SPServices has a function called SPRedirectWithID, but it’s problematic in SharePoint 2010 due to the dialogs that SharePoint uses to show the list forms. You can ensure that the function works just fine by turning off the dialogs, but that’s not always desirable. I’d turn them all off if I had my druthers because most list forms require more screen real estate than the dialogs provide without a lot of scrolling. They also don’t obviate the need for a ca-chunk postback when you save the item.

Given this state of things, Matt came up with a way to accomplish a similar thing in a different way. Here’s Matt’s take on the task. It’s a nice way to go if your information architecture is very clear and you know that your forms won’t change much, if at all, since Matt is bypassing the simple NewForm we get for “free” from SharePoint. On the other hand, this type of approach may become more common as we move forward with SharePoint and HTML5 compatibility.

Using SPServices and jQuery to Perform a Redirect from a SharePoint List NewForm to EditForm

By Matt IngleMatt Ingle

Problem: In SP 2010, you have a parent list and multiple child lists connected through Lookup columns. You have created the custom Display and Edit forms for the parent list containing XsltListView web parts for the child lists filtered through query string view parameters. Refer to this article for more information on this solution.

Now you need a way to redirect the user from the NewForm to the EditForm after saving the parent list item while passing the newly created item ID as a query string parameter. The problem is there is no way to do this out-of-the-box (OOB).

Solution: You can use jQuery and the SPServices library from CodePlex to utilize SharePoint’s Web Services. The two operations I will use in the following example are GetUserInfo and UpdateListItems.

Note: Be sure to read the IMPORTANT NOTES on the SPServices Home page about supported versions. This example will use the (“[nodeName=’z:row’]“) syntax which no longer works with jQuery 1.7. I will be using jQuery 1.6.2 and SPServices 0.6.2 in my example. However, if you want to use 1.7, an alternative solution to using this syntax can be found here by Steve Workman. [ed: Use the SPFilterNode function, which will work with any version of jQuery: .SPFilterNode("z:row") and ensures cross-browser compatibility.]

By using the SPServices operation to create the new list item you will be bypassing the OOB Save process for the SharePoint list form. This means that you will lose the validation on the form and will need to create your own validation logic using jQuery. In the following example I will be implementing the required field validation for my list fields.

Example: At this point you should already have a custom NewForm for your parent list.

Below each field to be displayed on the form, add some text wrapped in a <span> tag with unique ids that will be used to display our custom validation message. I worded mine the same as the text displayed by SharePoint. Notice my id for the <span> tag below is ‘valBU’. We will use jQuery later to select this element by the id.

clip_image0024

Additionally, we will need to add a custom button to our form in place of the OOB Save button. This will allow us to attach our custom code to the Save button’s click event.

clip_image004

Next, add a Content Editor WebPart (CEWP) directly below the DataFormWebPart. This will hold our jQuery/SPServices script.

In the CEWP, add the script references to the jQuery and SPServices libraries. I uploaded mine in a new ‘Scripts’ folder stored in the ‘Style Library’ folder so that it is accessible to other pages in the site as needed.

To begin the script, add code to hide the text containing our custom validation messages. Place this code in the $(document).ready function. Next, add a line to attach the method CreateNewItem to the Save button’s click event.

clip_image006

Next, we can implement our custom validation logic. The following method will be called in our CreateNewItem method to validate our form before creating the new item. It starts by using jQuery selectors to obtain the values of the list fields on the form. We can use the ‘title’ attribute for the selectors which contains the name of the list field. In my example, I have 2 drop-down list boxes so I am looking for select elements. For a normal text field you would look for input elements.

For a PeoplePicker, the selector used is a little different. There are different ways to obtain this value, but for our purposes (looking for a single PeoplePicker on the page) you can just look for a textarea element with the title ‘People Picker’. If you have multiple PeoplePickers on the form then you would need to research a way to be more selective. [ed: You can use the SPFindPeoplePicker function in SPServices for this.]

When you have an empty PeoplePicker value you will get ’&#160;’ which is the HTML code for a non-breaking space. Simply check for this value versus an empty string as done for the other fields.

This method counts the number of empty fields and toggles display of the custom validation messages. If there are no empty required fields then it returns true, false otherwise.

clip_image008

Now we can implement the CreateNewItem method. First, use jQuery to select the form fields and obtain the values.

Again, we need to do something different for the PeoplePicker. The value of the PeoplePicker contains a lot of stuff we don’t really need so we have to parse through it to find the user login name.

With this value stored in the ‘buFinContollerPerson’ variable, we will supply it to the ‘userLoginName’ parameter for the GetUserInfo operation. This will be a value like ‘EXT\mingle’.

We are calling the GetUserInfo operation in order to get the correct format needed to save the PeoplePicker value to the list. This format is ’37;#Matt Ingle’ (<UserID>;#<UserName>).

clip_image010

To complete the CreateNewItem method we will call the UpdateListItems method. To specify the creation of a new list item you need to specify “New” for the batchCmd parameter. In addition, supply the list display name (one shown in UI) for the listName parameter. The values to be submitted for the new list item are specified as value pairs in the valuepairs parameter.

Note:We are supplying the fullUserName obtained from the GetUserInfo operation as the value for the PeoplePicker field BUFinContollerPerson.

Once the operation has completed we will parse the XML response to find the newly created item ID. This is where we use the (“[nodeName=’z:row’]“) syntax. This value is supplied for the ID query string parameter to redirect the user to the EditForm.

Also notice the <div> tag below the <script> tag. This is where you can specify the location of the debug output for the SPServices operations. Simply uncomment the calls to SPDebugXMLHttpResult to see the operation results displayed. Be sure to comment out the redirect though when debugging, otherwise you will not stay on the page to see the results. :-)

clip_image012

Redirect to Another Page from NewForm.aspx with the New Item’s ID: Redux

UPDATE 2009-10-19: I solved this for real in the jQuery Library for SharePoint Web Services function called $().SPServices.SPRedirectWithID.  Feel free to read this post, but this is the solution you’re looking for.

The most visited post on my blog, hands down, is the one on Redirect to Another Page from NewForm.aspx with the New Item’s ID.  (Oddly enough, the one about Harvey Balls for Office Documents is right up there, too.)

I never liked the solution that much, as it seems sort of messy, but it works.  Over the last few days, I’ve been trying to figure out a way to take advantage of the SharePoint Web Services which we’ve “wrapped” in the jQuery Library for SharePoint Web Services to come up with a cleaner approach.  Unfortunately, I’m still stumped.

The basic issue is this: until you commit the item which you are creating, there is no ID for it.  There’s nothing in NewForm.aspx to hook into to grab that new ID because it doesn’t exist yet.  This makes chaining pages together in a wizard-like way sort of kludgy, at least from NewForm.aspx to the next page in the chain.

Right now, I have a jQuery function for the library called $().SPServices.SPGetLastItemId in the lab which returns the latest ID for a given user on a given list.  This is a bit cleaner than the DVWP approach, as you can just drop a call into your interstitial page like this:

var thisID = $().SPServices.SPGetLastItemId({
    webURL: "/Demos",
    listName: "Sales Opportunities"
});

The function uses two Web Services calls to get the latest item’s ID.  First, it calls the GetUserInfo operation of the Users and Groups Web Service to find out the user’s ID based on the user’s account (either specified in the options or as returned by the $().SPServices.SPGetCurrentUser() function).  Next it calls the GetListItems operation of the Lists Web Service to find the latest item created for that user.

But you still need to have that interstitial page, which is what I don’t like in the first place!  Before I make this function a part of the library, I want to think some more about how we can improve upon this method.

If you have any ideas on how to get this to work better, drop me a line.  I’d love to offer up a better solution!

Redirect to Another Page from NewForm.aspx with the New Item’s ID

UPDATE 2009-10-19: I solved this for real in the jQuery Library for SharePoint Web Services function called $().SPServices.SPRedirectWithID.  Feel free to read this post, but this is the solution you’re looking for.

UPDATE 2009-10-01: I’ve posted an update on this idea.  It’s possible to simplify the interstitial page using the Web Services from the jQuery Library for SharePoint Web Services, but I’m still not satisfied: Redirect to Another Page from NewForm.aspx with the New Item’s ID: Redux.

Here’s a good question from prostration in the MSDN SharePoint – Design and Customization forum:

I need to go from the newform page to the editform page of the item I just created. So when I click save I want it to create the item and take me to it’s editform page. How can I do this?

My first answer was bollocks, but I do have a workable approach.

The problem is that the ID for the new item is unknown before the item has been created.  I came up with a scheme about a year ago to deal with this, and some Binging tells me that there aren’t any spiffier ideas out there, at least that I could find.

The trick is to commit the new item and then determine its ID to pass to whatever page you want on the Query String.  I did this by creating an intermediate page which just finds the ID with a DVWP and then redirects to the other page.

So first convert the NewForm.aspx to XSLT.  (I usually just create a new DVWP from scratch so that I can keep the page code clean.)  Then, change the Save button behavior like so:

<input type="button" value="Save" name="btnSave" onclick="javascript: {ddwrt:GenFireServerEvent('__commit;__redirect={GetLastID.aspx?RedirectURL=EditForm.aspx}')}" />

The code for my intermediate page is below.  Note that I’ve constructed it so that you can pass in the redirect page (RedirectURL) so that you can use it to do other things.  All this page does in its DVWP is find the most recent item in the list which was created by the current user and redirect to RedirectURL with the ID on the Query String.

<%@ Page Language="C#" inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<html>

<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

<body>
<form id="form1" runat="server">
<WebPartPages:DataFormWebPart runat="server" IsIncluded="True" FrameType="None" NoDefaultStyle="TRUE" ViewFlag="0" Title="Events" __markuptype="vsattributemarkup" __WebPartId="{020AF483-2135-4D37-B6C7-CD6A6FD6AF5D}" id="g_a8a4d070_e7c0_4105_a113_acff9dea3328" pagesize="1" __AllowXSLTEditing="true" WebPart="true" Height="" Width="">
    <DataSources>
        <SharePoint:SPDataSource runat="server" DataSourceMode="List" UseInternalName="true" selectcommand="<View><Query><Where><Eq><FieldRef Name=&quot;Author&quot;/><Value Type=&quot;Integer&quot;><UserID/></Value></Eq></Where><OrderBy><FieldRef Name=&quot;Created_x0020_Date&quot; Ascending=&quot;FALSE&quot;/></OrderBy></Query></View>" id="dataformwebpart2">
            <SelectParameters>
                <WebPartPages:DataFormParameter Name="ListName" ParameterKey="ListName" PropertyName="ParameterValues" DefaultValue="Assignments"/>
            </SelectParameters>
        </SharePoint:SPDataSource>
    </DataSources>
   
<parameterbindings>
        <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
        <ParameterBinding Name="RedirectURL" Location="QueryString(RedirectURL)" DefaultValue=""/>
    </parameterbindings>
    <datafields>@Title,Title;@Start_x0020_Time,Start Time;@End_x0020_Time,End Time;@ZIP_x0020_Code,ZIP Code;@Amount,Amount;@Nominee,Nominee;@ID,ID;@ContentType,Content Type;@Modified,Modified;@Created,Created;@Author,Created By;@Editor,Modified By;@_UIVersionString,Version;@Attachments,Attachments;@File_x0020_Type,File Type;@FileLeafRef,Name (for use in forms);@FileDirRef,Path;@FSObjType,Item Type;@_HasCopyDestinations,Has Copy Destinations;@_CopySource,Copy Source;@ContentTypeId,Content Type ID;@_ModerationStatus,Approval Status;@_UIVersion,UI Version;@Created_x0020_Date,Created;@FileRef,URL Path;</datafields>
    <XSL>
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
    <xsl:output method="html" indent="no"/>
    <xsl:param name="UserID">CurrentUserName</xsl:param>
    <xsl:param name="RedirectURL" />
	<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
        <xsl:call-template name="dvt_1"/>
    </xsl:template>

    <xsl:template name="dvt_1">
        <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
        <xsl:for-each select="$Rows">
            <xsl:call-template name="dvt_1.rowview" />
        </xsl:for-each>
    </xsl:template>

    <xsl:template name="dvt_1.rowview">
        <script type="text/javascript">
            document.location.href = &apos;<xsl:value-of select="$RedirectURL" />&apos; +
                &quot;?ID=&quot; + &apos;<xsl:value-of select="@ID" />&apos;;
        </script>   
    </xsl:template>
    </xsl:stylesheet>
</XSL>
</WebPartPages:DataFormWebPart>
</form>
</body>
</html>