Taxonomy Picker in SharePoint Provider-Hosted App


One of the exciting announcements at the 2014 SharePoint Conference, was the general availability the Office App Model Samples (OfficeAMS) on Codeplex.  OfficeAMS is grass-roots effort by volunteer Microsoft developers to provide code samples using the Office/SharePoint app model that address common customization scenarios.  The samples in OfficeAMS include many scenarios that the SharePoint community felt was impossible or challenging to deliver without full-trust code.  It includes almost 30 complex scenarios such as site provisioning, branding, templating, profile sync, UX controls, and many more. Vesa Juvonen has a more comprehensive write-up of OfficeAMS on his blog (Vesa was also a significant contributor to the effort).

One of the scenarios that I contributed in OfficeAMS is a Taxonomy Picker control for provider-hosted apps. I used this control in several SPC sessions and was surprised by the number of people interested in using it in their apps. In this post, I will detail this Taxonomy Picker control and how to use it in your apps.  I’ve also included a video that outlines the use of the control.

[View:https://www.youtube.com/watch?v=sgTEAVBcrK0]

Setup

The taxonomy picker connects to TermSets in the Managed Metadata Service in SharePoint. To do this from an app, the app needs a minimum of Read permissions to the Taxonomy scope. The app needs Write permissions to the Taxonomy scope if you plan to use “Open” TermSets that users can create Terms in (aka – “Folksonomies”).

Taxonomy scoped Permissions in AppManifest.xml

The app project also needs to include several script and style files from the OfficeAMS download.  These are available in the Contoso.Components.TaxonomyPicker project.  The important files are as follows:

  • Scripts\taxonomypickercontrol.js
  • Scripts\taxonomypickercontrol_resources.en.js
  • Styles\taxonomypickercontrol.css
  • Styles\Images (entire folder of images)
OPTIONAL STEPS: If you plan to use the taxonomy picker in conjunction with ListItems in SharePoint (ex: read/write items from lists with managed metadata columns), you should also add a reference to the Microsoft.SharePoint.Client.Taxonomy.dll assembly, which makes it easy to work with TaxonomyFieldValue and TaxonomyFieldValueCollection values. The OfficeAMS project also has a TaxonomyPickerExtensions.cs file with extension methods for working with these values and the taxonomy picker. Again, the assembly reference and extension class are optional steps for working with ListItems that have managed metadata columns and are explained below.

 

The taxonomy picker is a client-side control, which uses the client-side object model (CSOM) to query the Managed Metadata Service.  To do this, the app needs to reference a number of JavaScript files from SharePoint, such as sp.runtime.js, sp.js, sp.requestexecutor.js, init.js, and sp.taxonomy.js.  The sp.taxonomy.js file allows the app to make calls into the Managed Metadata Service. I like to use Microsoft’s AJAX library to load these scripts dynamically using the getScript extension.  This library is automatically added to ASP.NET pages with a ScriptManager control. Below is the complete block of script I use to load the appropriate files for both the taxonomy picker and the SharePoint client chrome control

Dynamically Loading Scripts

//Wait for the page to load
$(document).ready(function () {

    //Get the URI decoded SharePoint site url from the SPHostUrl parameter.
    var spHostUrl = decodeURIComponent(getQueryStringParameter(‘SPHostUrl’));
    var appWebUrl = decodeURIComponent(getQueryStringParameter(‘SPAppWebUrl’));
    var spLanguage = decodeURIComponent(getQueryStringParameter(‘SPLanguage’));

    //Build absolute path to the layouts root with the spHostUrl
    var layoutsRoot = spHostUrl + ‘/_layouts/15/’;

    //load all appropriate scripts for the page to function
    $.getScript(layoutsRoot + ‘SP.Runtime.js’,
        function () {
            $.getScript(layoutsRoot + ‘SP.js’,
                function () {
                    //Load the SP.UI.Controls.js file to render the App Chrome
                    $.getScript(layoutsRoot + ‘SP.UI.Controls.js’, renderSPChrome);

                    //load scripts for cross site calls (needed to use the people picker control in an IFrame)
                    $.getScript(layoutsRoot + ‘SP.RequestExecutor.js’, function () {
                        context = new SP.ClientContext(appWebUrl);
                        var factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
                        context.set_webRequestExecutorFactory(factory);
                    });

                    //load scripts for calling taxonomy APIs
                    $.getScript(layoutsRoot + ‘init.js’,
                        function () {
                            $.getScript(layoutsRoot + ‘sp.taxonomy.js’,
                                function () {
                                    //TAXONOMY PICKERS READY TO BIND
                                });
                        });
                });
        });
});

 

Binding the Taxonomy Picker

After the setup steps, the taxonomy picker is extremely easy to implement. All you need is a hidden input control in html and one line of JavaScript to convert the input into a taxonomy picker:

Binding the Taxonomy Picker

<input type=”hidden” id=”taxPickerKeywords” />


//bind the taxonomy picker to the default keywords termset
$(‘#taxPickerKeywords’).taxpicker({ isMulti: true, allowFillIn: true, useKeywords: true }, context);

 

The taxpicker extension takes two required parameters…the options for the taxonomy picker and the SharePoint client context object (SP.ClientContext).  The taxonomypicker.js version I’ve attached also has a third optional parameter for a value changed callback (not in OfficeAMS yet).  Below is a table that details the options you can pass into the taxpicker extension and some examples of bindings:

Parameters

options The first parameter of the TaxonomyPicker sets the options for the control.  The properties that can be set include:

  • isMulti – Boolean indicating if taxonomy picker support multiple value
  • allowFillIn – Boolean indicating if the control allows fill-ins (Open TermSets only)
  • termSetId – the GUID of the TermSet to bind against (available from Term Mgmt)
  • useHashtags – Boolean indicating if the default hashtags TermSet should be used
  • useKeyword – Boolean indicating if the default keywords TermSet should be used
  • maxSuggestions – integer for the max number of suggestions to list (defaults is 10)
  • lcid – the locale ID for creating terms (default is 1033)
  • language – the language code for the control (defaults to en-us)
context The second parameter is an initialized SP.ClientContext object
changed callback The third parameter is an optional value changed callback delegate to provide events when the value of the taxonomy picker changes

 

Examples of Taxonomy Picker Bindings

//bind the taxonomy picker to the default keywords termset
$(‘#taxPickerKeywords’).taxpicker({ isMulti: true, allowFillIn: true, useKeywords: true }, context);

//Single-select open termset field (TODO: change the GUID to a termset id from your deployment)
$(‘#taxPickerOpenSingle’).taxpicker({ isMulti: false, allowFillIn: true, termSetId: ‘ac8b3d2f-37e9-4f75-8f67-6fb8f8bfb39b’ }, context);

//Multi-select closed termset field (TODO: change the GUID to a termset id from your deployment)
$(‘#taxPickerClosedMulti’).taxpicker({ isMulti: true, allowFillIn: false, termSetId: ‘1c4da890-60c8-4b91-ad3a-cf79ebe1281a’ }, context);

//Use default Hashtags termset and limit the suggestions to 5 and value changed callback
$(‘#taxPickerHashtags’).taxpicker({ isMulti: true, allowFillIn: true, useHashtags: true, maxSuggestions: 5 }, context, function() { alert(‘My value changed’); });

 

You can find the ID of a TermSet by selecting the TermSet in the Term Store Manager.  The Term Store Manager is exposed to site collection administrators within s site collection or from Tenant/Central Administration.

Getting TermSet ID from Term Store Manager

Reading and Writing Values

If you want to work with the taxonomy picker in .NET, you need to follow the optional steps in the setup section above to include the Microsoft.SharePoint.Client.Taxonomy reference and extension class.  You should also change the hidden fields to include the attribute runat=”server”.  The taxonomy picker control can be set by initializing the value of the hidden field with JSON in the following format [{“Id”:TermID, “Name”: TermLabel}] (ex: [{“Id”:”a8ff0f61-2c10-4add-8307-cb1712703887″, “Name”: “Exchange”}]).  The TaxonomyPickerExtensions.cs extension class includes extension methods for converting TaxonomyFieldValue and TaxonomyFieldValueCollection objects into this JSON format:

Setting Taxonomy Picker Value from .NET

protected void Page_Load(object sender, EventArgs e)
{
    //The following code shows how to set a taxonomy field server-side
    var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
    using (var clientContext = spContext.CreateUserClientContextForSPHost())
    {
        var list = clientContext.Web.Lists.GetByTitle(“TaxTest”);
        var listItem = list.GetItemById(1);

        clientContext.Load(listItem);
        clientContext.ExecuteQuery();

        taxPickerOpenSingle.Value = ((TaxonomyFieldValue)listItem[“OpenSingle”]).Serialize();
        taxPickerClosedMulti.Value = ((TaxonomyFieldValueCollection)listItem[“ClosedMulti”]).Serialize();
    }
}

 

The taxonomy picker will store the selected terms in the hidden field using JSON string format.  These values can be accessed by other client-side scripts or server-side following a post.  The JSON will include the Term Name, Id, and PathOfTerm (ex: World;North America;United States).  JSON.parse can be used client-side to convert the hidden input’s value to a typed object and any number of server-side libraries can be used (ex: JSON.net).

Conclusion

Building in the SharePoint app model does NOT mean you have to compromise on user experience. I hope this taxonomy picker helps illustrate that.  You can get the taxonomy picker and many other code samples in the Office App Model Samples on Codeplex.

Code sample from blog: TaxonomyPickerSample.zip

Comments (17)

  1. Mike S. says:

    OMG…this and the codeplex project are awesome!  It has so many of the things my company was hoping for with apps (we have prohibited customizations in SharePoint thus far).  I noticed you used Office 365 in the video…does this work with SharePoint in our data centers?

    -Mike

  2. Hey Mike. Glad you like this and the OfficeAMS project. Almost all of the samples work for on-premises and online SharePoint. The documentation that accompanies each sample will usually say if it is specific to one. The taxonomy picker outlined above works with both.

    If you plan to do apps on-premises, I highly recommend familiarizing yourself with the link below. Apps on-premises will typically be configured to do OAuth with certificated instead of Azure ACS that SharePoint Online uses. The link below describes how to set this up:

    msdn.microsoft.com/…/fp179901(v=office.15).aspx

    Richard

  3. chaitanya says:

    The above control i used in a sharepoint hosted app for office-365 site. There is a problem i.e. when the taxonomy dialog is opened, user will select some terms, but if the user wants to remove any of the selected taxonomies, the textbox is not editable. In the inbuilt control that comes with editable and auto fill options without allowing duplicates. But this control is allowing duplicate entries also. Please suggest.

  4. Brandon Johnson says:

    Excellent work I appreciate this resource as it has helped me greatly.

  5. Nanddeep Nachan says:

    Hi Richard, Nice article.

    Is there any way for Setting Taxonomy Picker Value from Client side?

  6. Matt Williams says:

    Nandeep

    You have to set the value taxPickerKeywords to be the term you want as default.

    The value needs to be formatted like this:

    [{"Id":"term giud", "Name":"term name" }]

  7. Aleksi N says:

    I'm trying to use the taxonomy picker on a high trust app but keep getting "Loading TermSet failed" alert. Debugging revealed that the underlying error is "Your domain doesn't match the expected domain for this app deployment.". The app is deployed at https://[machinename]:9999 on a local IIS. This is obviously not the same domain as the appweb. How can I make this work?

  8. sabira says:

    how to set the selected terms using managed CSOM ?

  9. dinsh says:

    this control is loading from termset, but i need to load it from some inner term(stat from inchorid)

  10. arun says:

    It's nice taxonomy control.

    I am wondering is there any way to select the term from suggestions using keyboard.

    I am not able to pick up suggested term without mouse.

    Thanks!

  11. arun says:

    Its nice taxonomy control post.

    I am wondering is there any way to select the term from suggestions using keyboard.

    I am not able to pick up suggested term without mouse.

    Thanks!

  12. Swapna Thomas says:

    Getting "Loading TermSet failed" alert, access denied for AppWebProxy.aspx The app is deployed at https://[machinename]:443 on a local IIS for provider hosted app.Can anyone please suggest how to fix this.

  13. Jane says:

    Hi Richard, This is a great control.  However, when I use it on my form that has some postback, the control disappears.  Is there a way around this?  Thanks.

  14. Kiril says:

    Hi Richard,

    This is a great control indeed. Recently I embedded it into a sharepoint app I'm working on. All was fine until I added a second metadata column and tried to update a list item. Looks like only the last field gets updated, the others do not change. I've tried various approaches to update the list item using CSOM, like SetFieldValueByValue (using TaxonomyFieldValue), ParseAndSetFieldValue method, ValidateUpdateListItem method, even setting the value as formatted string directly to the field and its corresponding hidden field. But all the time I get only the last field populated with a value, the rest do not change .. and of course, no errors from sharepoint.

    So I suppose it is not a problem in the picker control itself, but something in the way the list items are updated. Do you have an idea what could be causing this? And if that helps, I'm working with SharePoint Online envrironment.

    Thanks in advance!

    Kiril

  15. JaneR says:

    Hi, I am able to use the taxonomy control in my provider hosted app.  However, the display of the terms in the termsets are not sorted.  The term store have them sorted alphabetically.  I was wondering if I am missing a setting.  Please advice.  Thanks.

    Jane

  16. sagar.dasalkar@live.com says:

    Hi Richard,

    This is really a great control.

    Just an addition to this post. In the Term Store Management, for each term, there is a checkbox in general setting for “Available for Tagging”. Following is the OOB behavior for this setting

    • If the checkbox is checked, then the term can be viewed and selection is allowed whereas

    • If the checkbox is unchecked, then the term can be viewed but term selection is not allowed. Here, a different image is displayed against the term (i.e. EMMTermDisabled.png)

    To accommodate OOB behavior, following changes can be done in the utility. This code change will perform following for the terms which are not allowed for tagging

    1. Does not allow user to select a term by mouse click

    2. Exclude such terms from the suggestions when user types in the textbox

    Change in Project: Add EMMTermDisabled.png to the project

    Description: Download image from  /_layouts/15/Images/EMMTermDisabled.png and add it to the project

    Change in taxonomypickercontrol.js

    Step1: Add new property in the Term constructor (after line 15)

    Description: Add new property to hold setting value

    Code:

    this.IsAvailableForTagging = rawTerm.get_isAvailableForTagging();

    Step2: Changes in toHtmlLabel method (at line: 26)

    Description: Check whether the term is available for tagging and return markup with appropriate image  

    Code:

    return $('<li class="cam-taxpicker-treenode-li"><div class="cam-taxpicker-treenode"><div class="cam-taxpicker-expander ' + addlClass + '"></div><img src="../content/images/' + (this.IsAvailableForTagging == true ? 'EMMTerm.png' : 'EMMTermDisabled.png') + '" alt=""/><span class="cam-taxpicker-treenode-title" data-item="' + this.Name + '|' + this.Id + '">' + this.Name + '</span></div></li>');

    Step3: Change in getSuggestions method (at line: 134)

    Description: Check whether the term is available for tagging and add it to the suggestions list accordingly

    Code:

    if (e.Name.toLowerCase().indexOf(text.toLowerCase()) == 0 && e.IsAvailableForTagging == true)

    Step4: Change in pushSelectedTerm method (after line: 879)

    Description: Add if condition to check if the term is available to tagging. Move the code inside if method.

    Code:

    if (term.IsAvailableForTagging == true) {

        var clonedTerm = term.clone();

        //clear the RawTerm so it can be serialized

        clonedTerm.RawTerm = null;

        //pop the existing term if this isn't a multi-select

        if (!this._isMulti)

             this.popSelectedTerm();

        //add the term to the selected terms array

        this._selectedTerms.push(clonedTerm);

        this._hiddenValidated.val(JSON.stringify(this._selectedTerms));

    }

    Hope this helps.

    Thanks

  17. Icortraja Erattaimuthu says:

    Hi Richard,

    This is cool control. I have used this in SharePoint hosted app, all working nice. But when i entered new term then i selected available tag, the new term getting vanished. Please guide me to resolve this.

Skip to main content