Creating a Parent/Child List Relationship with a Document Library
But I like the cookie

Creating a Parent/Child List Relationship with a Document Library

Posted by on Tuesday, April 16th, 2013  

 

To this day, my most popular blog posts continue to be my posts on how to set up a parent/child list relationship in SharePoint. These blog posts show you how to automatically set the ID of a Lookup field for a “child” list for different versions of SharePoint using various methods:

As helpful as many have found these solutions, they only worked for lists and not for document libraries.

 

The reason for this is that we are using query string variables to pass the ID of the parent list to the child new form. However, when you add a document to a document library there are two forms. The first form is a dialog for uploading your file, and the second form is the page used for entering all the various properties for the file including the lookup field for the parent.  When using the query string method from the previous posts, the ID for the parent list item is passed to the upload dialog, NOT the properties page.

So, the problem is that we need to persist the parent item’s ID until the properties form is displayed for our newly uploaded file and then set the form field accordingly. I don’t really want to get into customizing the upload dialog to pass along the query string variable and adding script to the Master Page for this one purpose would be woeful.  So, what to do?

C is for Cookie

Sorry, I couldn’t think of any other cookie analogy but by taking advantage of cookies we can set a cookie with our parent list item ID and our properties page will be able to read it.

Why I like the cookie option

Using a cookie ends up being a fairly simple solution and even adds some additional benefits:

  • By setting a cookie with your Parent List ID you can easily have multiple child lists that can all easily access the cookie.
  • A cookie will work for both lists and document libraries.
  • You can store other field values in additional cookies and pass those to your forms as well.

Why I DON’T like the cookie

So, as cool as this cookie idea sounds and as many benefits as I get, I’m still not THRILLED about using a cookie for a couple of reasons:

  • When you create a cookie, it remains accessible to your pages until it expires. This has benefits, but it also CAN create issues. You could conceivably have a page use a cookie that you didn’t mean for it to use if you aren’t careful.
  • Once a developer (especially inexperienced ones) latch onto a device such as cookies they can go a bit crazy with it and start using it all over the place. I don’t like the thought of someone creating a ton of cookies that they don’t expire and then asking me to debug their form because for SOME reason it ALWAYS sets a form field to the value “42”.
  • On that same note, if a developer creates a generic name for a cookie and some other developer on that site decides to use cookies too and uses the same generic name, you’re going to be possibly overwriting each others values. You’re code will work fine on your machine… but have fun debugging that one. Make sure to use a unique cookie name.
  • How do I handle expiring a cooking? Expire it right after I’ve used it? Let it expire in 5 minutes? a day? well… “it depends”. Which means you SHOULD give them some thought when use them and not just blindly make a decision. So, it CAN add some complexity. For this blog I chose to keep it simple and let my cookies expire after 5 minutes.

The Solution

As I said, the solution ends up being actually fairly simple, and you can view the video below to see step-by-step how I did it. In the video I’ll be using the cookie for both a child list and a child document library. With just a couple of scripts we can be up and running quite quickly.  If you don’t want to watch the video, but want to know what I did, here are the steps to take:

Create the infrastructure

Create your lists and libraries. Create lookup fields to your parent list in your child lists and libraries. For this blog, I’ll stick with my conventions from my previous posts on the subject.  I will have a Parent list “Issue”, a child list “Time”, and a new child library “IssueDocuments”. Each child list has a lookup field to the “Issue” list with the name “Issue” and I’m looking up on the “Title” field.

Edit the default display form for your parent list.

image

Add list view web parts for your children forms onto the page.

image

Create web part connections from your parent list to your children list filtering the children list by the parent list ID and the child list/library lookup field.

image

image

After you have you child list views added and the web part connection set up, go ahead and manually create a couple of entries in your children lists/libraries selecting a valid parent from the lookup field. You will see that only the children for the parent list item will show in the list views on the Issue display form. Now to set it up so you don’t have to manually set the lookup field value for the parent.

Add a script to the default display form of the parent list

Edit the default display form of the parent list again, add a content editor web part and link it to the following script which I’ll be placing in my Site Assets library. In this script I’m creating a cookie for the parent list ID that exists in the query string of my parent display form. I’m giving the cookie the name “IssueID” and having it expire in 5 minutes.

<script type="text/javascript">
    //get the ID for the Issue from the Query String
    var issueID = getParameterByName("ID");

    //create a cookie with the name "IssueID"
    createCookie("IssueID",issueID);

    // The following function should really be put into a utility library
    // with all of your commonly called functions
    //
    // no, I didn't write this function from scratch, I found it at
    // http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
    function getParameterByName(name)
    {
      name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
      var regexS = "[\\?&]" + name + "=([^&#]*)";
      var regex = new RegExp(regexS);
      var results = regex.exec(window.location.href);
      if(results == null)
        return "";
      else
        return decodeURIComponent(results[1].replace(/\+/g, " "));
    }

    function createCookie(name, value) {
       var date = new Date();
       //make the cookie expire in 5 minutes
       date.setTime(date.getTime()+(300*1000));
       var expires = "; expires="+date.toGMTString();
       document.cookie = name+"="+value+expires+"; path=/";
    }
</script>

If you’ve followed my previous blogs on the subject, you’ll remember that we had to previously modify the link to the “Add New Item” on the child list view to make it pass the IssueID to the child list form in the query string. Well, since we are using cookies, we don’t need to modify the default “Add new item” or “Add new document” links at all. This also means you can add several child list views without having to modify the above script at all. That’s a nice side benefit of using cookies.

So, now when you load the the display form of your parent list item, a cookie is being set called “IssueID” that contains the ID of that parent list item.

Add a script to our children forms

At this point, all we have to do to make this all work together is add a script to the New Item Form of our child Lists and the Edit Forms of our child Document Libraries.

<script  type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js"></script>

<script type="text/javascript">
     jQuery(document).ready(function($) {
        var issueID = getCookie("IssueID");
        if (issueID != undefined)
        {
            SetLookup("Issue",issueID );
        }

     });

    function getCookie(name) {
        var cookieArray = document.cookie.split(";");
        for (index in cookieArray)
        {
            var keyValuePair = cookieArray[index].split("=");
            var key = keyValuePair[0];
            key  = key.replace(/^\s+|\s+$/g, "");
            if (key == name)
            {
                var value = keyValuePair[1];
                return value;
            }
        }
    }

    //Sets the value of a lookup field whether it is an input or select field. 
    function SetLookup( fieldTitle, lookupVal)
    {
        //Set default value for lookups with less that 20 items
        if ( $("select[title='" +fieldTitle+ "']").html() != null)
        {
            var element = $("select[title='"+ fieldTitle +"']");
           $(element).val(lookupVal);    
            //disable field to stop pesky users from changing it
            $(element).find('option:not(:selected)').attr('disabled', true);
        }
        else
        {
            choices = $("input[title='" +fieldTitle +"']").attr("choices");
            hiddenInput = $("input[title='" +fieldTitle +"']").attr("optHid");
            $("input[id='" +hiddenInput +"']").attr("value",lookupVal)

            choiceArray = choices.split("|");
            for (index = 1; index < choiceArray.length; index = index + 2)
            {
                if (choiceArray[index] == lookupVal){
                    var element = $("input[title='" +fieldTitle +"']");
                    $(element).val(choiceArray[index - 1]);    
                    //disable field to stop pesky users from changing it
                    $(element).attr('readonly',true);
                    //hide the button that is used for the dropdown list
                    $(element).next('img').hide();
                }
            }
        }
    }

</script>

In this script, the script simply looked for a cookie called “IssueID” and if it found the cookie, it used the value of the cookie and set the form field “Issue” to that value. It then disabled the “Issue” field on the form to stop those pesky users from changing it to something else. If you are passing multiple fields in multiple cookies you would just use the same logic to set the various form fields for each cookie. If you need to know how to set various form fields in a SharePoint form, you can reference my previous blog post on the subject:

A Dummies Guide to SharePoint and jQuery–Getting & Setting SharePoint Form Fields

The last thing I’ll point out about this script is that it takes into account the 20 item lookup field “feature” in SharePoint 2007 and 2010:

Setting SharePoint Lookup Lists w/ jQuery (+/- 20 items)

This does not seem to be an issue in SharePoint 2013 and a lookup field is always a select (unless something has changed since I last tried), so if you are using SharePoint 2013 you don’t need the portion of the script that looks for the “Issue” field as an input.

That’s it! You’re done! Now when you open up the display form for your parent list and click on the “add new…” link for any of your child lists (that you applied the above script to on their respective forms), the parent lookup field value will automatically be set and the lookup field will be disabled.

The Video

So, you don’t like reading and you just want to watch me do it all? Okay, let’s see if I can do it all in one take without screwing up:

image

Conclusion

So, this solution will work on SharePoint 2007, 2010, 2013 and in Office 365. That’s pretty cool. Will I start using cookies more often? I’ll continue to use them judiciously and when it makes sense. I’m not enamored by the thought of having this data floating around that I no longer need and getting bit by it because some developer didn’t create a unique cookie name and it messes up some other page.

I see this being an extremely useful tool to use though, but please learn all your tool options and use the right one for the right project.

Thanks again for taking the time to read my ramblings. Yes, there’s 100 ways to skin a cat, so if you have a better/easier solution please feel free to share with the class!

 

Disclaimer
The sample scripts are not supported under any Summit 7 Systems standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Summit 7 Systems further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Summit 7 Systems, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Summit 7 Systems has been advised of the possibility of such damages.

Subscribe to RSS Feed

Sign Up for Newsletter