11 June 2013

Populating SharePoint Taxonomy fields via JavaScript - idiot-proof version

My current piece of work involves creating a SharePoint 2010 survey that a relatively small number of users (around 200) will fill in on behalf of their customers. One piece of information to capture is effectively the user's home office, which in this case is a Managed Metadata Service termset. Due to the number of surveys each user needs to create, it is desirable that this is pre-populated for the user - a few hours work by the developers (in this case yours truely!) will offer a significant saving in time for the end user.

We have a separate list that stores the user against their "home office", so thanks to the rather excellent SPServices jQuery Library for SharePoint it should be a simple matter to get the current user, look up the appropriate value for their office from the list and set the field in the survey. Except that it turns out that setting Managed Metadata fields via JavaScript is slightly complex, and the small number of descriptions of this on the internet are not sufficiently idiot proof, at least for me!

So I thought I'd document my approach, with particular notes on the pieces that tripped me up, which will hopefully be of use to someone out there. But first I should credit those that helped me get to a solution -

The Taxonomy field

This piece of HTML is the taxonomy field as it typically appears in the form (where your metadata column is myTermSetColumn) -
    <span dir="none">
        <span id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl00">
            <input name="ctl00$m$g_f6a3da81_eb42_4143_9415_6b177e5f4d28$ff11$ctl00$ctl01" id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl01" class="ms-taxonomy ms-taxonomy-height ms-long" type="hidden">
            <div id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl02" class="ms-taxonomy ms-taxonomy-height ms-long">
                <div class="ms-taxonomy-control-holder ms-long" id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl02controlHolder">
                    <img class="ms-taxonomy-browser-button" tabindex="0" alt="Browse for a valid choice" title="Browse for a valid choice" src="/_layouts/images/EMMCopyTerm.png">
                    <div style="width: 362px;" title="myTermSetColumn" class="ms-taxonomy-fieldeditor">
                        <div restrictpastetotext="True" allowmultilines="false" disableribboncommands="True" class="ms-rtestate-write ms-taxonomy-writeableregion" aria-multiline="true" aria-haspopup="true" aria-autocomplete="both" role="textbox" id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl02editableRegion" contenteditable="true">?</div>
                    </div>
                </div>
                <div unselectable="on" class="ms-taxonomy-suggestion-container ms-rtefocus-invalid ms-taxonomy-hidden" id="ctl00_m_g_f6a3da81_eb42_4143_9415_6b177e5f4d28_ff11_ctl00_ctl02suggestionsContainer">
                    <div unselectable="on" class="ms-taxonomy-suggestion-holder"></div>
                    <img class="ms-taxonomy-panel-resizer" unselectable="on" src="/_layouts/images/CornerGrip.gif">
                </div>
            </div>
        </span>
    </span>

My first tripping point is that the various solutions out there talk about the Container Id, but which one do you need? The answer is the second one! Note that the innermost <div> has the same ID as the outermost <div> (the container), except that the former is appended with 'editableRegion'.

This jQuery will get the editableRegion version -
var myMetadataField = $('div[Title="myTermSetColumn"]').find('div[role="textbox"]');

Populating that with just the text value of your required term is part one of the solution. Note that it's not necessary to pass the ID of the item to the hidden <input> field, assuming each term is unique in the termset.

Validating the field

Part two of the solution is to validate the content you've just populated via a Microsoft function. The piece that tripped me here is that you need to pass a HtmlDivElement not a jQuery object to the function.

If like me you're using jQuery, get the Id of myMetadataField, trim off 'editableRegion' and hence get the ContainerId -

var myContainer = $('#' +ContainerId)
var divElement = myContainer.get(0);

If you've using the DOM then it's just document.getElementById(ContainerId).

The function call is then as follows -

var controlObject = new Microsoft.SharePoint.Taxonomy.ControlObject(divElement);
controlObject.validateAll();

You should now have a validated entry in the taxonomy field (i.e. it's black text with a solid underline, not red text with a dashed underline) and the form will submit successfully.