Friday, January 14, 2011

Javascript Auto-Populate Complex Fields

It's been awhile since my last post, but I've been hard at work rolling out new functionality to our portal. Recently, I've been creating several dialog actions in SharePoint 2010 to enable users to perform various functions. The new SharePoint dialog framework is a fantastic addition to the product, but you may hit a few road blocks when trying to perform tasks that should be fairly straight forward, like pre-populating a dialog with parameters from a parent window. Hopefully this post will help you stay sane.


One of the primary features of our portal is that it acts as a Knowledge Base for our Customer Care Agents. We are constantly striving to keep our content up-to-date, but with over 4000 knowledge base articles, it takes more than just 1 or 2 sets of eyes to keep that content updated. In an attempt to streamline the update process, we decided to add a feedback mechanism to allow users to submit suggestions or requests. The best method for storing and reporting on this information, in my opinion, was to create a custom list to store requests. Using a SharePoint list allows us to easily track requests, maintain an SLA for updating content, and generating notifications and tasks based on these items.

In v1 of the content feedback system left much to be desired. We only captured a simple url, and submitting feedback used an ajax and custom modal form to push the data to the list. Unfortunately, we were only capturing the url, which didn't offer us the ability to categorize and assign the feedback.

In v2 we wanted to use a more out-of-the-box approach, and auto-populate various fields to allow us to assign the request to a specific content owner or group based on Taxonomy fields and User Fields on the parent knowledge base article. Easy, right?

Unfortunately, many of the SharePoint JS library are undocumented. The Client OM offers some nice functionality, but in regards to modifying any of the advanced field types (Taxonomy Field, Rich Text Editor, User Field), there just isn't much out there. Yours truly decided that "no mountain is too high" and tackled pre-populating these field types anyway. After hours of sifting through thousands of lines of js, here are my findings. Hopefully they save you more time than it took me to discover them.

Taxonomy Field Values - how to set them via javascript


The Taxonomy Field proved to be the most difficult to auto-populate via javascript. The Taxonomy Field allows a user to paste values into the editable region of the field, but a web service call is required to validate the terms and populate the Taxonomy Field hidden storage with the correct Term Label and Guid.

I first needed to locate the script that loads the Taxonomy Field Control. The script can be found in the /14/TEMPLATE/LAYOUTS/ folder, and is called ScriptForWebTaggingUI.js. Step 1 complete, and the rest is butter, right? Unfortunately, ScriptForWebTaggingUI.js is minified, and there isn't a nice clean debug.js file to accompany it. Time to start digging. After a few painstaking hours, I stumbled across the appropriate method for updating the Taxonomy Field after populating the values.

Here are the steps I took

1. Created a hidden field on the parent display form using the following code:


<xsl:element name="input">
  <xsl:attribute name="type">hiddenxsl:attribute>
   <xsl:attribute name="id">hdArticleServicesxsl:attribute>
   <xsl:attribute name="value">
     <xsl:value-of select="@Services"/>
  xsl:attribute>
xsl:element>

2. Next I the function to open the list form for my feedback list, and put a button on the form to call the function:

<script type="text/javascript">
        function OpenArticleFeedbackDialog() {
            var options = SP.UI.$create_DialogOptions();

            options.url = "/AboutC3/Lists/Article Feedback/NewForm_User.aspx";
            options.title = "Submit Article Feedback";
            options.width = 630;
            options.height = 500;
            options.args = {
                title: $('#hdArticleTitle').val(),
                category: $('#hdArticleSupportCategory').val(),
                services: $('#hdArticleServices').val(),
                attached: $('#hdArticleAttachedServices').val(),
                owner: $('#hdArticleOwner').val(),
                backup: $('#hdArticleOwnerBackup').val(),
                id: $('#hdArticleId').val(),
                url: location.href
            };

        options.dialogReturnValueCallback = Function.createDelegate(null, CloseArticleFeedbackDialogCallback);
        SP.UI.ModalDialog.showModalDialog(options);
    }

    function CloseArticleFeedbackDialogCallback(result, target) {
        if (result) {
            alert("Thank you for your feedback.");
        }
    }
script>

As you can see, it's a standard showModalDialog call, but notice the options.args object. I'm using jQuery to grab the hidden field values and send them to the child window. 

3. Next, I created a function on the newform.aspx file to grab the variables and populate the fields on the form. I added this function to the "PlaceHolderAdditionalPageHead" place holder on the newform.aspx page.

<script type="text/javascript">
        function _setArticleValues() {
            var args = SP.UI.ModalDialog.get_childDialog().get_args();

            $.each(args, function (i, n) {
                switch (i) {

                    case "title":
                        $("input[title='Feedback Title']").val(n);
                        $("input[title='Title']").val(n);
                        break;

                    case "category":
                        var c = _parseTaxValue(n);
                        $('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff131_ctl00_ctl02editableRegion').html(_parseTaxValue(n));
                        _setTaxFieldValues('ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff131_ctl00_ctl02editableRegion');
                        break;

                    case "services":
                        $('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff141_ctl00_ctl02editableRegion').html(_parseTaxValue(n));
                        _setTaxFieldValues('ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff141_ctl00_ctl02editableRegion');
                        break;

                    case "attached":
                        $('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff151_ctl00_ctl02editableRegion').html(_parseTaxValue(n));
                        _setTaxFieldValues('ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff151_ctl00_ctl02editableRegion'); break;

                    case "owner":
                        $('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff161_ctl00_ctl00_UserField_upLevelDiv').html(_parseUserValue(n));
                        break;

                    case "backup":
                        $('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff171_ctl00_ctl00_UserField_upLevelDiv').val(_parseUserValue(n));
                        break;

                    case "id":
                        $("input[title='Content Id']").val(n);
                        break;

                    case "url":
                        $("input[title='Feedback URL']").val(_parseUrlValue(n));
                        $("input[title='Description']").val(_parseUrlValue(n));
                        break;
                }
            });
        }

        function _setTaxFieldValues(id) {
            var obj = new Microsoft.SharePoint.Taxonomy.ControlObject(document.getElementById(id).parentNode.parentNode.parentNode);
            obj.validateOnTextChangedDontTouchDom();
        }

        function _parseUserValue(user) {
            var $h = $(user),
img = $h.find("img.ms-imnImg"),
output = "";

            if (img) {
                output = img.attr('sip');
            }

            return output;
        }

        function _parseTaxValue(input) {
            var s = input.split(";"),
output = "";

            if (s.length > 0) {
                for (var i = 0; i < s.length; i++) {
                    var t = s[i].split(":");
                    output += t[t.length - 1].replace('&', '&') + ";";
                }
            }

            return output;
        }

        function _parseUrlValue(input) {
            var output = "",
s = input.split("&");
            if (s.length > 0) {
                output = s[0];
            }

            return output;
        }

        ExecuteOrDelayUntilScriptLoaded(_setArticleValues, "scriptforwebtaggingui.js")
script>

As you can see, I'm performing quite a few functions to normalize the data which I will get into shortly. 

The primary script is called _setArticleValues, and I use the SharePoint ExecuteOrDelayUntilScriptLoaded function to wait for the taxonomy field js to load. ExecuteOrDelayUntilScriptLoaded is an extremely valuable function when attempting to update complex field values from javascript because in many cases the field controls don't exist until the initial script is complete.

4. I first added the fields to the form, and did a simple html source view to grab the dynamic id's of the controls. The next step was to grab the html of the taxonomy field by writing the js generated html to a div tag on the form. This is where I discovered that the editable region of the Taxonomy Field is just the ID of the parent div + "editableRegion". Setting the HTML of the editable region is easy enough using jQuery, but unlike the Rich Text Editor, the Taxonomy Field doesn't post it's editable region contents to a hidden storage field before submitting the form (I will go into the RTE functionality in depth next). This is where I got stuck and decided to torture myself by sifting through SharePoint JS, and how I discovered this function:

validateOnTextChangedDontTouchDom();

Unfortunately, this is part of the taxonomy control object, so you have to create an object before calling this function like so:

var obj = new Microsoft.SharePoint.Taxonomy.ControlObject(document.getElementById(id).parentNode.parentNode.parentNode);
            obj.validateOnTextChangedDontTouchDom();

This was the magic function to fix all of my pain. Once I ran this function, the taxonomy field values were automatically saved to the list item, and peace was restored to the galaxy. I can go into much more detail, but would rather get to the next field type.

Rich Text Editor - Setting Field Values

The above code doesn't really demonstrate how to pre-populate a rich text field, but another project of mine required a modal dialog html editor. Painful yes, but quite rewarding once it was complete. I will go into the details of creating an application page with the Microsoft.SharePoint.Publishing.WebControls.HtmlEditor in my next blog post, but for now I will show you how to auto-populate it with html.

1. Much like the Taxonomy Field, you need to find the ID of the control. In an application page, this can be done dynamically using the reference, but in a list form you will have to grab the id from the rendered source. 

2. Once you have the ID, it's really a piece of cake to set the field value using jQuery or Javascript. This is the code I used for my application page:

$('#_hiddenDisplay').val(n);




As you can see, the _hiddenDisplay field is the only field you need to set. Once this field is set, the contents are rendered accordingly. One item to note, when passing the argument to your child dialog, do not use the SP.Utilities.HttpUtility.htmlEncode function, as there is no decode function in the js.

User Field Values


The SharePoint user field is almost identical to the Rich Text Editor field, but requires a little bit of finessing to get the correct value. When an data form web part renders the xsl version of the User Field, quite a bit of html is injected to display the hyperlink and user presence image. That's where this function comes in:


function _parseUserValue(user) {
    var $h = $(user),
img = $h.find("img.ms-imnImg"),
output = "";
    if (img) {
        output = img.attr('sip');
    }

    return output;}



As you can see, I simply convert the user field value to a jQuery object, and then run some simple html functions against it to grab the appropriate attribute that holds the email address of the user. I then set the User Field editable region with the email address value like so:

$('#ctl00_ctl21_g_09d208ae_a4f5_4702_a96a_c661d67d2c11_ff161_ctl00_ctl00_UserField_upLevelDiv').html(_parseUserValue(n));

Fairly self-explanatory, but you still have to determine the ID of the rendered User Field, and the appropriate div where the editable region is located.

Conclusion

Auto-populating complex field types via javascript can be a real bitch. Determining what field types you are populating is the first step; you then need to determine what order the SharePoint scripts are loaded so you can execute your functions after the final script control has been loaded on the page. I hope this post helps save you some time, and keeps you sane. As always, feel free to drop me a note if this post helped, or if you have any additional questions. And please excuse the poor grammar and sentence structure; I spent most of last night working on this, and wanted to get it up here before I forgot my steps.















5 comments:

  1. Terrific work! This is the type of information that should be shared around the web. Shame on the search engines for not positioning this post higher!

    nolvadex

    ReplyDelete
  2. Awesome post! I was getting very frustrated trying to set the value of a metadata field via javascript - this post was extremely helpful!!! Thanks

    ReplyDelete
  3. This is a good,common sense article.Very helpful to one who is just finding the resouces about this part.It will certainly help educate me.
    ----------------------------------------------------------
    Flower Girl Dresses|Empire Wedding Dresses|New Style Wedding Dresses

    ReplyDelete
  4. I have similar code, but more specific to one particular field. The script was working when tested with IE Developer Tools, but only after using
    ExecuteOrDelayUntilScriptLoaded(_setArticleValues, "scriptforwebtaggingui.js")
    it ran at the right time. Thanks for the tip!

    ReplyDelete
  5. I liked your article, I will share your article to everyone!!



    ________________________________________________________________________
    WoW gold|Diablo 3 Gold|RS Gold|GW2 Gold

    ReplyDelete