Geeks With Blogs

News





The SharePoint Hillbilly Fewer Big Words... More Pretty Pictures...

So, I mentioned a few blog posts back in jQuery in SharePoint for Hackers that I used SPServices and jQuery to clone a parent list item and all the children associated with the parent.  A few people expressed interest in seeing how this was done. This is that blog post.

Prerequisites 

Before diving in head first like I usually do, it would probably be a good idea for you to make sure you are comfortable with a few of my previous blog posts:

Creating a SharePoint List Parent/Child Relationship – VIDEO REMIX – The result of this blog is the starting point for this blog. I’ll be cloning an issue and all the time entries associate with an issue.  Why would you ever want to do that in real life? No clue, a more likely scenario would be to clone a project and all it’s tasks to create a similar project. However, you know by now that I’m far to lazy to go and create an entire new site when I have another one that will service my purposes. Regardless, this blog will help get your mind wrapped around where this one is starting and it gets you used to some concepts like getting the parent ID out of the Query String (which we will be utilizing in this blog).

I get by with a little help from jQuery and SPServices… – This blog will introduce you to using SPServices in SharePoint and it’s also based upon the previous blog post up there on a parent/child list relationship. It will walk you through getting everything installed and ready to go so that I can skip all that minutia here and jump right in.

Okay… are we good? Ready to get started?  If at any point in this blog you get lost, it most likely means you didn’t read both the blogs above..  or maybe you aren’t a developer as there is quite a bit of code to go through.

Let’s break this down…

Okay, so what exactly are we going to do here? Well… here we have a screen shot or our parent/child lists:

image

We are going to take a list item (in this case an “Issue”) and create a copy of it. We will then copy all the children of that item (in this case “Time Entries”) and associate those copies with our newly created parent item. Make sense? I think this is an ideal use for SPServices because it’s very quick and efficient (assuming you aren’t cloning hundreds of items at once) and it provides functionality that you could not get previously without managed code. 

One important note, if you DO plan to use this functionality to create more than a couple of hundred entries, you will need to write your code to handle the updates in batches. I have not tested SPServices to see what this magic number is, when using Web Services with .NET this number was around 400 – 500 for ideal performance.  I would expect it to be less in SPServices, but again, I have not tested that.

Are we all on the same page?  Here’s what we’re going to do:

  1. Use the ID from query string to retrieve the Issue to clone
  2. Create a copy of the Issue and get the ID for the newly created Issue
  3. Find all the children for the original issue
  4. Create copies of all the children inserting in the ID for the newly created Issue

Sound easy enough? It’s not too bad really once you get the syntax down.

Use the ID from query string to retrieve the Issue to clone

Okay.. let’s start writing our code.  First off, let’s add a Content Editor Web Part (CEWP) to the page above for our JavaScript. Let’s create a skeleton for our little chunk of code where we include our necessary files, create an empty method, and create a button to execute our method:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

I added an alert for us to make sure we are executing our code and to make sure we are getting values we expect.  Again, it is VERY important to write your jQuery/JavaScript in chunks like this. It will help you figure out where you are having problems.  Go ahead and click the button… you know you want to.

Okay… now we need to get the ID for the Issue we are viewing. Have any ideas where we can get that from? This is the interactive portion of the blog… anyone? Yes… you.. the exhausted looking lady in the back? Exactly.. the ID for the issue is in the Query String, more specifically, the Query String variable “ID”. So we need to write some JavaScript to get that.  Here’s what that looks like:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

In the above JavaScript we are searching our location string (the query string) and looking for the “ID” Query String variable. We are then stuffing that ID into a variable called “parentID”. I then throw up an alert to make sure we got the ID correctly. Go ahead, try it out… yep.. it actually works… We now have the ID for the Issue we want to clone.

Now it’s time to retrieve the issue so we can get the fields we want to clone. I should point out here that you COULD just parse the DOM to get all the fields you need for the Issue IF these fields are already displayed. If that’s the way you want to get the values, more power to you.  I think I’ll use SPServices. Let’s make a call to the “GetListItems” method in SPServices for our “Issues” list:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

Okay, so in the code above I retrieve the “Title” field from my Issues list. That’s all I’m copying because this example is so simple. If you want to copy other fields you need to make sure you specify every field you want to return in your “CAMLViewFields” using the INTERNAL FIELD NAME not the display name.  So, if I  also had a field called “Comments” I wanted to retrieve, my CAMLViewFields would look like:

CAMLViewFields: "",

You may also notice the weird piece of code where I’m storing the value of the title “var title = $(this).attr("ows_Title"); “.  This is where we get into some of the SharePoint Web Services funkiness. The returned value for a field is always the INTERNAL FIELD NAME preceded with “ows_”.  Live it, love it, get used to it.  If I wanted to store the value for my “Comments” field that code would look like:

var comments = $(this).attr("ows_Comments");

Again, please remember, if you want to retrieve a value from a field, you must specify that field in your . Yes, there ARE a few fields that are ALWAYS returned from a Web Services call like ID and a few others, so please no snarky comments from the peanut gallery. Just so you don’t drive yourself crazy, if you want to retrieve the value for a field, specify it in your ViewFields. 

Just to summarize, we have now retrieved the fields we want to clone from our parent issue by using the ID in the Query String and SPServices to retrieve the information we need.  The script above will pop up a nice little alert message with the title field of our issue if you did it correctly.

Create a copy of the Issue and get the ID for the newly created Issue

We now need to create our copy of the Issue and get the ID of the newly created Issue.

So… let’s pass our “title” field that we found above to a new function so that it’s a little easier to read:

function CloneIssue(title)
{
    $().SPServices({
        operation: "UpdateListItems",
        async: false,
        listName: "Issues",
        updates: "" +
                    "" +
                        "" + title + " - clone " +  "" +
                    "" +
                "",
        completefunc: function (xData, Status) {
            //get the ID for the newly created Issue
            var newID = $(xData.responseXML).find("[nodeName=z:row]").attr("ows_ID");
            alert(newID);
        }
    });
}

This time we are using SPServices and executing the “UpdateListItems” method. This method is used to do both add and update of list items. In this instance we are using it to add a new Issue.  The heart of this method is the text for the “updates” option:

"" +
    "" +
        "" + title + " - clone " +  "" +
    "" +
""

You can see that we are are creating a new entry and setting the field named “Title” to the value we pass into the function (also called “title”). If you had multiple fields to set you would just keep adding additional “” rows.  Again, this uses the internal field names (but you don’t need the preceding “ows_”). After we complete the call to “UpdateListItems” we are retrieving the ID for the newly created Issue and storing that value in a variable called “newID”. We will use this newID value to create the copies of the children. You also notice that I set the title of my cloned issue to the title of the parent plus “ – clone”. This helps us to see our newly created Issue.  So, when all is said and done our code now looks like:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

And when we press our button it will create a cloned Issue and then pop up an alert with the ID of our new Issue:

image

Find all the children for the original issue

Okay… now that we have cloned the parent item, we now need to find all the children of the original parent. We will again use the SPServices method “GetListItems” and the ID from the Query String”

function CloneChildren(oldParentID,newParentID){
    $().SPServices({
        operation: "GetListItems",
        listName: "Time",
        CAMLViewFields: "",
        CAMLQuery: "" + oldParentID + "",
        completefunc: function (xData, Status) {
            $(xData.responseXML).find("[nodeName=z:row]").each(function() {
                var title = $(this).attr("ows_Title");
                
                alert(title);
                
             });
        }
    });
}

In this function we passing in the ID of our old parent ID and finding all the entries in the “Time” list that have an “IssueID” equal to the old parent ID. We are also retrieving the title, date, and hours field from the Time list this time. So, when we put all our code together now it looks like:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

So, when we click the button now, it should create a copy of the parent Issue, and then alert you with all the titles of all the children for that old parent Issue. 

Create copies of all the children inserting in the ID for the newly created Issue

Okay.. let’s finish this up and create our cloned children.  Now that we have the ID for the new parent, and are retrieving all the children for the old parent, all we have to do is create new entries for each of the children and set the “IssueID” of those new children to the new Issue we created. Sound easy? To do this we are going to need to make one last SPServices call to execute… which method? anyone?  Yes.. the “UpdateListItems” method:

function CloneChildren(oldParentID,newParentID){

    $().SPServices({
        operation: "GetListItems",
        listName: "Time",
        CAMLViewFields: "",
        CAMLQuery: "" + oldParentID + "",
        completefunc: function (xData, Status) {
            var updateXML = "";
            var index = 1;
        
            $(xData.responseXML).find("[nodeName=z:row]").each(function() {
                var title = $(this).attr("ows_Title");
                var entryDate = $(this).attr("ows_Entry_x0020_Date");
                var hours = $(this).attr("ows_Hours");

                updateXML = updateXML + "" +
                        "" + title  +  "" +
                        "" + entryDate +  "" +
                        "" + hours +  "" +
                        "" + newParentID +  "" +
                        "";
             });
             
            updateXML = updateXML + "";
             
            $().SPServices({
                operation: "UpdateListItems",
                async: false,
                listName: "Time",
                updates: updateXML,
                completefunc: function (xData, Status) {
                    alert("all done!");
                }
            });
        }
    });
}

The code to create all the children is very similar to the code we used to create the parent. The main difference is that instead of one entry for:

 "" +
        "" + title + " - clone " +  "" +
 "" +

We will have multiple “” entries. One for each child, and we’ll increment the “ID” number specified.  So, the resulting XML will look something similar to:

"" +
    "" +
        "" + title1 +  "" +
    "" +
    "" +
        "" + title2 +  "" +
    "" +
    .
    .
    .
    "" +
        "" + titlex + "" +
    "" +
""

That’s what a large part of what our function is doing up there, building this XML using the values from the children of the original parent.

updateXML = updateXML + "" +
                        "" + title  +  "" +
                        "" + entryDate +  "" +
                        "" + hours +  "" +
                        "" + newParentID +  "" +
                        "";

There’s the chunk of code where we are building this string. You can see we are storing the Title, Entry Date, Hours, and setting the ID of the newly created parent.  We finish everything off with an alert that says “all done!”.  So, if we put all the code together it looks like:






"Clone My Junk!" onClick="javascript:CloneMyJunk();"/>

Other stuff…

So, there you have it. If you are not a developer and you followed all of that, then congratulations. You are now a developer. A few notes from what we did above:

Do some error checking

Make sure you check for errors after all your SPServices calls in the completefunc, the easiest way to do this is to make sure the returned status code is all zeros. This means there is no error, anything else and you got something funky going on. Basically, don’t treat this as production ready code until you add some production level error handling to it.

Field names and conventions

Be aware of the funky issues with field names in SharePoint’s Web Services. Again, use the internal field names. If there are space in your internal fields names you’ll need to put “_0x0020_” in the place of the spaces (see my ‘Entry Date’ field in the code above). All returned fields have “ows_” in front of them, and basically be careful with any special characters in your internal field names. Best practice dictates that your internal field names have no spaces or special characters and you just rename the display names as you need to.

As always, thanks for stopping by and I hope you learned something. If you know of a better way, please let us all know!

Posted on Monday, November 22, 2010 5:22 PM | Back to top


Comments on this post: Using SPServices & jQuery to Clone a Parent Item and All its Children

# Mr
Requesting Gravatar...
Mark:

I can't leave well enough alone, so I did a post with some observations and suggestions on this.

M.
Left by Marc Anderson on Nov 22, 2010 9:24 PM

# re: Using SPServices & jQuery to Clone a Parent Item and All its Children
Requesting Gravatar...
Is there a trick to getting a Rich Text field to copy? When I alert the field it has div tags, etc. When I convert to plain text it works just fine.

Thanks!
Left by will266 on Dec 01, 2010 10:46 AM

Your comment:
 (will show your gravatar)


Copyright © Mark Rackley | Powered by: GeeksWithBlogs.net