Leveraging SharePoint's OOB Taxonomy Picker

Thursday, June 30, 2016

11

In this post I'll go over how to instantiate the OOB SharePoint Taxonomy Picker using JavaScript.

Sometimes jslink just can't do the job, and we need to build a completely custom form with JavaScript and the REST APIs.  It's not too bad, but Managed Metadata fields are definitely a pain to handle.

I'm not a huge fan of the OOB Taxonomy Picker UI.  If you have the time and resources, it's worth considering rolling your own taxonomy control.  But for efficiency or consistency's sake, we can reuse the OOB picker with just a little bit of code.

Credit for the core of this code goes to this blog post.  There are a few other posts out on the interwebs with similar.  However, I could not get any to consistently work.  Many didn't load dependencies properly, or relied on resources which might not be loaded depending on what version of SharePoint and what type of page you're on.

So, I've bundled everything into an easy to consume wrapper, and made it as robust as possible by explicitly loading every required resource.  This has been tested on an OOB Publishing Page in both O365 and SP 2013 on prem.  It requires jQuery to be loaded.

Helper Functions

First, drop the code below anywhere on the page.  It looks hairy, but is actually fairly straight forward.

When called, it inserts a DIV on to the page to hold the taxonomy control.  It also adds a hidden textbox to hold the GUIDs of the selected terms.

Next, it uses JSOM to retrieve the ID of the current site's default Term Store.  Finally, it wires up some properties on the textbox, and registers an event which will cause SharePoint to transform the textbox into a taxonomy picker when the page loads.

var taxonomyPickerHelper = taxonomyPickerHelper || {
  initPicker: function (containerId, termSetId) {
    // Create empty picker template and hidden input field
    var pickerContainerId = containerId + '_picker';
    var pickerInputId = containerId + '_input';

    var html = '<input name="' + pickerInputId + '" type="hidden" id="';
    html += pickerInputId + '" />';
    html += '<div id="' + pickerContainerId;
    html += '" class="ms-taxonomy ms-taxonomy-height ms-taxonomy-width"></div>';

    jQuery('#' + containerId).html(html);

    // Get Termstore ID and init control
    taxonomyPickerHelper.getTermStoreId().then(function (sspId) {
      taxonomyPickerHelper.initPickerControl(sspId, termSetId, 
        pickerContainerId, pickerInputId);
    });
  },

  getSelectedValue: function (containerId) {
    return jQuery('#' + containerId + '_input input').val();
  },

  getTermStoreId: function () {
    var deferred = jQuery.Deferred();

    var context = new SP.ClientContext.get_current();
    var session = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
    var termStore = session.getDefaultSiteCollectionTermStore();

    context.load(session);
    context.load(termStore);

    context.executeQueryAsync(
      function () {
        var sspId = termStore.get_id().toString();
        deferred.resolve(sspId);
      },
       function () {
         deferred.reject("Unable to access Managed Metadata Service");
       }
     );

    return deferred.promise();
  },

  initPickerControl: function (sspId, termSetId, 
                               pickerContainerId, pickerInputId) {
    var tagUI = document.getElementById(pickerContainerId);
    if (tagUI) {
      tagUI['InputFieldId'] = pickerInputId;
      tagUI['SspId'] = sspId;
      tagUI['TermSetId'] = termSetId;
      tagUI['AnchorId'] = '00000000-0000-0000-0000-000000000000';
      tagUI['IsMulti'] = true;
      tagUI['AllowFillIn'] = false;
      tagUI['IsSpanTermSets'] = false;
      tagUI['IsSpanTermStores'] = false;
      tagUI['IsIgnoreFormatting'] = false;
      tagUI['IsIncludeDeprecated'] = false;
      tagUI['IsIncludeUnavailable'] = false;
      tagUI['IsIncludeTermSetName'] = false;
      tagUI['IsAddTerms'] = false;
      tagUI['IsIncludePathData'] = false;
      tagUI['IsUseCommaAsDelimiter'] = false;
      tagUI['Disable'] = false;
      tagUI['ExcludeKeyword'] = false;
      tagUI['JavascriptOnValidation'] = "";
      tagUI['DisplayPickerButton'] = true;
      tagUI['Lcid'] = 1033;
      tagUI['FieldName'] = '';
      tagUI['FieldId'] = '00000000-0000-0000-0000-000000000000';
      tagUI['WebServiceUrl'] = _spPageContextInfo.webServerRelativeUrl + '\u002f_vti_bin\u002fTaxonomyInternalService.json';

      SP.SOD.executeFunc('ScriptForWebTaggingUI.js', 
        'Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.taggingLoad',
        function () {
          Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.resetEventsRegistered();
        }
      );

      SP.SOD.executeFunc('ScriptForWebTaggingUI.js', 
        'Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad',
        function () {
          Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad(pickerContainerId);
        });
    }
  }
};

CSS

Next, include this CSS file to style the taxonomy picker:

<link rel="stylesheet" type="text/css" 
      href="_layouts/15/1033/styles/WebTaggingUI.css" />

Container DIV

Add a DIV to hold the taxonomy picker:

<div id="my-taxonomypicker">
</div>

Load Dependencies and Initialize

Finally, add the JavaScript below to initialize the picker.  This bad boy makes sure all the necessary dependencies are loaded, then initializes the picker with taxonomyPickerHelper.initPicker.  That takes 2 arguments, the ID of the container to place the picker, and the GUID of the termset to load.

Note: You must plug in the GUID to your termset on line 21

Note 2: sp.rte.js is a required dependency on O365, but does not exist in 2013 on prem.  For O365, be sure to uncomment lines 13, 14, 17, and 24.

jQuery(document).ready(function () {
  SP.SOD.loadMultiple(['sp.js'], function () {
    SP.SOD.registerSod('sp.taxonomy.js', 
        SP.Utilities.Utility.getLayoutsPageUrl('sp.taxonomy.js'));
    SP.SOD.registerSod('scriptforwebtaggingui.js', 
        SP.Utilities.Utility.getLayoutsPageUrl('scriptforwebtaggingui.js'));
    SP.SOD.registerSod('sp.ui.rte.js', 
        SP.Utilities.Utility.getLayoutsPageUrl('sp.ui.rte.js'));
    SP.SOD.registerSod('scriptresources.resx', 
        SP.Utilities.Utility.getLayoutsPageUrl('ScriptResx.ashx?culture=en-us&name=ScriptResources'));

    // UNCOMMENT THIS FOR O365
    // SP.SOD.registerSod('ms.rte.js', 
    //     SP.Utilities.Utility.getLayoutsPageUrl('ms.rte.js'));

    // UNCOMMENT THIS FOR O365
    // SP.SOD.loadMultiple(['ms.rte.js'], function () {
      SP.SOD.loadMultiple(['sp.taxonomy.js', 'sp.ui.rte.js', 
                           'scriptresources.resx'], function () {

        taxonomyPickerHelper.initPicker('my-taxonomypicker', '<TERM SET GUID>');

      });
    // });
  });
});

Wrap Up

To get the selected terms, call:

taxonomyPickerHelper.getSelectedValue('my-taxonomypicker')

The full sample code is available for download here.


11 comments:

Great post! Very helpful, thanks a lot

This was very helpful. However, when I click on the tag icon to view the dialog box, I receive "can’t find the server at _layouts."

Resolved - If you are using O365 on line 74 you must change _spPageContextInfo.webServerRelativeUrl to _spPageContextInfo.siteAbsoluteUrl or the Dialog box will not work properly because of incorrect URL to web service.

How to save taxonomy on a page list e.g. ?

And any idea how to set a value?

If we open this taxonomy control in edit form, then I need to display the autopopulated values in the search field which are already saved.. How to do that?

The download link is not working.

Since the author no longer seems engaged with this, I'd like to offer my modifications to taxonomyhelper.js. This allows you to pass options in to set many of the tagUI properties that are hard coded in the original, to support the enterprise keywords column, and to initialize with values: https://drive.google.com/file/d/1_-Zfl4YYyJ58-T8Y06i0piVGVW2PpcqA/view?usp=sharing

To save values, use javascript similar to this link. I've set this up as a button click handler: https://drive.google.com/file/d/1z7gY0eDvhP6mHd3OquabQ51NKbtndwKw/view?usp=sharing

Post a Comment