Showing posts with label Managed Metadata. Show all posts
Showing posts with label Managed Metadata. Show all posts

Tuesday, 28 April 2015

Using the Client-side Taxonomy Picker Control in an AngularJS directive on Office365

In my last post I wrote about using the Microsoft client-side people picker within an AngularJS directive.

In this post I want to take a look at using the client-side taxonomy picker wrapped up as an AngularJS directive, in an app running on an Officer 365 site. In a subsequent post I'll expand on the how the code works, but this post is just intended to give a quick overview of it.

You can download the code from Github here: https://github.com/matthewyarlett/AngularJS-Client-side-Taxonomy-Picker-Directive

The Taxonomy Picker control comes from the Office 365 Developer Patterns and Practises site.

The files for this app are uploaded to a folder on the root of the site. The apps parent file, app.html, is added to a page via Content Editor Webpart.

The app looks like this:


To use the directive, follow these basic steps.

1. Add the CSS and script references to your apps html page.

The Taxonomy Picker control comes with it's own CSS, so you need to include this.

<link href="../angularjs-taxonomy/Styles/taxonomypickercontrol.css" rel="stylesheet" />

You need to add a reference to the JS file the directive is declared in, as well as all of the Microsoft Scripts that the SharePoint Taxonomy Picker requires, as well as the scripts the app requires.

<!-- Load the constant variables -->
<script type="text/ecmascript" src="../angularjs-taxonomy/config/config.constants.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/models/models.js"></script>
<!-- Load third party scripts -->
<!-- AngularJS, Sanitize, resource -->
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/angular.min.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/angular-sanitize.min.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/angular-resource.min.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/angular-route.min.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/ui-bootstrap.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/jquery-1.9.1.min.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/scripts/taxonomypickercontrol.js"></script>
<!-- All of this scripts are used to create the app. -->
<script type="text/ecmascript" src="../angularjs-taxonomy/app.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/config/config.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/config/config.taxonomy.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/common/common.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/common/logging.js"></script>
<script type="text/ecmascript" src="../angularjs-taxonomy/controllers/controllers.js"></script>

2. Add the directive as a dependency to your app.

(function () {
    'use strict';        
    var app = angular.module('app', [
      //inject other Angular Modules 
      'ngSanitize',
      'ngResource', 
      'ui.bootstrap',
      //add the tax picker directive
      'ui.TaxonomyPicker',
      //inject App modules
      'common'
    ]);
})();

3. Add the directive to the page (you can add multiple instances of it).

When add the directive, it has several attributes that can be set to control the behaviour of the taxonomy picker.

Most aren't mandatory, but you must add the data model (via ng-Model), and you must add the pp-ready-to-load attribute (which tells the directive the data model has updated, and is ready to be used).

Possible attributes and they're values
AttributePossible valuesDefault valueRequired?
data-ng-modelAn array, containing a list of terms, in the following format:

var termsArray = [{ 'Name': object.TermLabel, 'Id': object.TermGuid}]
nullYes (you must supply a model to the data-n-model attribute, but it contain a null value)
data-pp-ready-to-loadtrue | falsefalseYes (the taxonomy picker will not render until this value is set to true)
data-pp-is-multi-valuedtrue | falsefalseNo
data-pp-widthAny numerical value, followed by 'px'220pxNo
data-pp-termsetidThe GUID of the termset.
E.g.
a5bd4ff2-2823-4ea2-8dd6-2d02c845493b
nullNo
data-pp-is-hashtagstrue | falsefalseNo
data-pp-is-keywordstrue | falsefalseNo

An example of adding a the taxonomy picker that references a termset;


<div ui-Taxonomy ng-model="vm.terms" pp-ready-to-load="{{vm.loadTaxonomyPickers}}" pp-termsetid="a5bd4ff2-2823-4ea2-8dd6-2d02c845493b" pp-is-multi-valued="{{true}}" id="taxPickerGeography" ></div>

An example of adding a the taxonomy picker that references the hashtags termset;


<div ui-Taxonomy ng-model="vm.hashtags" pp-ready-to-load="{{vm.loadTaxonomyPickers}}" pp-is-hashtags="{{true}}" pp-is-multi-valued="{{false}}" id="taxPickerHashtags" ></div>

An example of adding a the taxonomy picker that references the keywords termset;


<div ui-Taxonomy ng-model="vm.keywords" pp-ready-to-load="{{vm.loadTaxonomyPickers}}" pp-is-keywords="{{true}}" pp-is-multi-valued="{{true}}" id="taxPickerKeywords" ></div>

4. Load the data for the model.

Before initialising the directive (via changing the value of the property used in the data-pp-ready-to-load attribute to true), ensure you up the data model used for the taxonomy picker with the current data (if any).

For example, you might perform a REST call to get the current values in a list item. You would then create the model for the taxonomy picker when data from the REST call is returned.

After updating the data in the taxonomy pickers model, you would then change the value of the property used in the data-pp-ready-to-load attribute to true.

The code below shows an example of doing this.


//App Controller
(function () {
   'use strict';
   var controllerId = 'appCtrlr';
   angular.module('app').controller(controllerId, ['$scope', appCtrlr]);

   function appCtrlr($scope) {
      var vm = this;   
      vm.data = [];    
      vm.context = null;
      vm.terms = [];
      //The data model for the taxonomy picker requires an array of values. Each value 
      //represents a term, and has the Id and Name properties. The Id property takes the 
      //terms TermGuid and the Name property correlates to the terms Label property.       
      vm.terms = [{Id:"dc146209-c1b2-697e-c0e2-9259ba19e750",Name:"Failed Inspection"}];
      vm.keywords = [];
      vm.hashtags = [];
      vm.loadTaxonomyPickers = true;
      init();
      function init() {   
         //init code.
         //e.g. get data via REST and set the data model.                   
      }
   }
})();


As a side note, when you perform a REST call to Office365 or SharePoint, for a taxonomy
field, the data comes back as an object that has the TermGuid and Label properties, as seen in the following screen capture from Fiddler:



Internally, the Microsoft Client-side Taxonomy control requires the field properties as Id and Name,
which is why we format the input object with those properties above.

However, for the sake of usability, you can also pass in the raw value from a REST call. In this case, the init function might look like this (no formatting of the data takes place):

function init() {   
   getData().then(function(data){
      vm.data = data;
      vm.loadTaxonomyPickers = true;
      if (!$scope.$root.$$phase) {
         $scope.$apply();
      }
   }) 
}

And the HTML for the control, might look like this (note the vm.data.tags.results object that is passed in as the model).

<div ui-Taxonomy ng-model="vm.data.Tags.results" pp-ready-to-load="{{vm.loadTaxonomyPickers}}" pp-termsetid="a5bd4ff2-2823-4ea2-8dd6-2d02c845493b" pp-is-multi-valued="{{true}}" id="taxPickerTags" ></div>

That's it - as far as initialising and using the control goes.

To use the value(s) of a the taxonomy picker directive while the page is open, simple reference the taxonomy picker model, like you would any other property within a controllers data model.

<span data-ng-repeat="r in vm.terms track by $index">
    {{r.Name}} <span style="color:BURLYWOOD;">(Path: {{r.PathOfTerm}})</span><span data-ng-if="!$last">,&nbsp;</span>
</span>

Persisting the value(s) of the a taxonomy picker control is just as easy. You use the data in the taxonomy pickers model to write back to the server.

In the example below, I have a model used in a REST call for updating a list item that has a taxonomy field. Data from the model (used by taxonomy picker directive) is formatted for the REST call. Each term in the array of terms is transformed into a term string, that can be passed to the server via the REST call.

sr.models.shipInfoModel = function (id) {
   this.Id = id ? id : -1;
   //"pa28754972f84ff7a6bf3e4762f23e23" is the hidden text field (automatically) created for the taxonomy field
   //This is the field that you need to use to update the taxonomy field via REST.
   //You need to format selected terms as GUID|Label pairs
   this.pa28754972f84ff7a6bf3e4762f23e23;
   this.__metadata = {
      type: 'SP.Data.SrShipRegisterListItem'
   };
}

function populateShipInfoModel(srcModel, tagsTerms) {
   var dstModel = new sr.models.shipInfoModel(srcModel.Id);   
   //Update the taxonomy field values 
   if(srcModel.Tags && srcModel.Tags.results){
      if(srcModel.Tags.results.length == 0){
         dstModel.pa28754972f84ff7a6bf3e4762f23e23 = '';
      }else{
         for(var i = 0; i < srcModel.Tags.results.length; i++)
         {
            var t = srcModel.Tags.results[i];
            dstModel.pa28754972f84ff7a6bf3e4762f23e23 = dstModel.pa28754972f84ff7a6bf3e4762f23e23 + ';' + t.Name + '|' + t.Id;   
         } 
      } 
   }
   dstModel.__metadata.etag = srcModel.__metadata.etag;
   dstModel.__metadata.id = srcModel.__metadata.id;
   dstModel.__metadata.uri = srcModel.__metadata.uri;
   return dstModel;
}

And that's it!

Monday, 30 March 2015

Saving a Managed Metadata (Taxonomy) Field Value to Office 365 using REST

In this post I want to demonstrate how to save a Managed Metadata (Taxonomy) field value (single or multiple terms) to a Managed Metadata (Taxonomy) Field in Office 365 via the REST API.

The Hidden Managed Metadata Field

The trick to being able to save a taxonomy field value via REST, is knowing the name of the hidden text field that gets created when you create a Managed Metadata field.

When you make a REST call to update the Managed Metadata field, you need to pass a string representation of the managed metadata terms that you're saving into this hidden field.

The string representation of the terms is in the format of "termName|termId;".

In the screen shot below, you can see the field value of a hidden Managed Metadata field (the hidden field name is pa28754972f84ff7a6bf3e4762f23e23).


Finding the Name of the Hidden Field

Finding the name of the hidden text field of a Managed Metadata field is easy. The fastest way to do it, is by using the REST API via a browser.

1. Open Internet Explorer
2. In the address bar, enter the URL for your Office 365, followed by the REST API endpoint for to list the fields in your list.

https://matthewyarlett.sharepoint.com/sites/appcenter/_api/web/lists/getbytitle('srShipRegister')/fields

3. Use the browsers find function (Ctrl+F) to locate your field name in the xml


4. Look at the TextField property, and copy the copy
5. Use the browsers find function (Ctrl+F) to locate your field name in the xml, by searching for the guid
6. Make note of the hidden fields static name


Formatting a taxonomy field value

Now that you know the name of the hdden Taxonomy text field, you need to format the term values you want to save.

The terms should be formatted in the format of "termName|termId;".

An example model for updating the list in the above screens could look like this:
Loading ....
... and this is what the JSON looks like when you post it back to Office 365



Thursday, 6 March 2014

Get a List of Fields in a Site Collection that are using a Managed Metadata TermSet

Ever wondered how many fields are referencing a Managed Metadata Termset? It's going to be a long and boring job using the Web UI to click through every web... and every list in every web... and every field in every list, looking for all the fields referencing a particular termset. Just writing that in a sentence was long enough!

This is the sort of job where PowerShell really shines!

The example below demonstrates creating a script (with a number of functions) to recurse through a site collection, creating a report of all the fields using a termset.

Just take me to the Microsoft TechNet Gallery, so I can download the script:  Find all SPFields that are using a Managed Metadata TermSet

The basic PowerShell used to check a field is:

$termSetId = "e07cab2f-ef85-473e-a4a7-1104b5daf192"            
$field = (Get-SPWeb "http://mdysp13").Lists["Documents"].Fields["Country"]            
if($field.GetType().Name -eq "TaxonomyField"){            
 if($field.TermSetId.ToString() -eq $termSetId){            
  Write-Host "Houston, we have a match!" -foregroundcolor darkyellow;            
 }            
}

Or, for a collection of fields:

$fieldCollection = (Get-SPWeb "http://mdysp13").Lists["Documents"].Fields            
$termSetId = "e07cab2f-ef85-473e-a4a7-1104b5daf192"            
foreach($field in $fieldCollection)            
{            
 if($field.GetType().Name -ne "TaxonomyField"){            
  continue;            
 }            
 if($field.TermSetId.ToString() -ne $termSetId){            
  continue;            
 }            
 #if we get to here, we have a match!            
}

I hear you say, "That's awesome Matt, but where the hell do I get the Taxonomy TermSet ID from?!"

Well, that's quite easy.

$w = Get-SPWeb "http://mdysp13";                        
$tsession = Get-SPTaxonomySession -Site $w.Site;                        
$tsession.GetTermSets("Countries",1033) | FT Name,ID
#Or, if you want to get a term set based on the SPWeb's default language ID            
$tsession.GetTermSets("Countries",$w.Language) | FT Name,ID

Pretty cool huh?

If you want to get a list of all the termsets, then you can write a simple function to return all the termsets as a list.

function List-AllTermSets{            
 [CmdletBinding()]            
  Param(             
    [parameter(Mandatory=$true, ValueFromPipeline=$true)][Microsoft.SharePoint.SPWeb]$web            
   )            
 $termSetInfo = New-Object psobject            
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "Store" -value ""
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "StoreId" -value ""
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "Group" -value ""
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "GroupId" -value ""
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "TermSet" -value ""
 $termSetInfo | Add-Member -MemberType NoteProperty -Name "TermSetId" -value ""
             
 $tsession = Get-SPTaxonomySession -Site $web.Site;            
 $tstores =  $tsession.TermStores;             
 $list = @();            
 foreach($tstore in $tstores)            
 {            
  $tgroups = $tstore.Groups;            
  foreach($tgroup in $tgroups)            
  {            
   $tsets = $tgroup.TermSets;            
   foreach($tset in $tsets)            
   {            
    $tinfo = $null;            
    $tinfo = $termSetInfo | Select-Object *;            
    $tinfo.Store = $tstore.Name;            
    $tinfo.StoreId = $tstore.ID;            
    $tinfo.Group = $tgroup.Name;            
    $tinfo.GroupId = $tgroup.ID;            
    $tinfo.TermSet = $tSet.Name;            
    $tinfo.TermSetId = $tSet.ID;            
    $list += $tinfo;            
   }            
  }             
 }            
 return $list;            
}

So, what if I want all of this scripted? A function I can call that generates a report. Well, prepare to roll up your sleeves and poise your fingers over the Ctrl+C key combo!

We need a couple of functions for this,performing the following tasks;

1. A function to get a list of all the taxonomy (managed metadata) fields in a field collection referencing a termset
2. A function to call that will report on all the taxonomy (managed metadata) fields in the web, the webs lists, and the webs sub webs, that are referencing a given termset.

I've outlined each function below. If you'd rather just download the script, download it from the Microsoft TechNet Gallery here: Find all SPFields that are using a Managed Metadata TermSet

1. Get a list of all the fields (in a field collection) using a termset

function Get-FieldsUsingTermSet            
{            
 [CmdletBinding()]            
  Param(             
    [parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)][Microsoft.SharePoint.SPFieldCollection]$fieldCollection,            
    [parameter(Mandatory=$true, Position=2)][Microsoft.SharePoint.Taxonomy.TermSet]$TermSet            
   )            
 $MetadataField = New-Object psobject            
 $MetadataField | Add-Member -MemberType NoteProperty -Name "ParentListUrl" -value ""
 $MetadataField | Add-Member -MemberType NoteProperty -Name "ParentListTitle" -value ""
 $MetadataField | Add-Member -MemberType NoteProperty -Name "FieldTitle" -value ""
 $MetadataField | Add-Member -MemberType NoteProperty -Name "FieldId" -value ""            
             
 $matches = @();            
 foreach($field in $fieldCollection)            
 {            
  if($field.GetType().Name -ne "TaxonomyField"){            
   continue;            
  }            
  if($field.TermSetId.ToString() -ne $TermSet.Id.ToString()){continue;}            
  $tf = $MetadataField | Select-Object *;            
  $tf.ParentListUrl = $field.ParentList.ParentWeb.Url;            
  $tf.ParentListTitle = $field.ParentList.Title;            
  $tf.FieldTitle = $field.Title;            
  $tf.FieldId = $field.ID;            
  $matches += $tf;            
 }            
 return $matches;            
}

2. A parent function to bring it together, that will give you some options (like recursively checking the web,  searching just web level fields)

function Get-ManagedMetadataFieldUses            
{            
 [CmdletBinding()]            
  Param(             
    [parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)][Microsoft.SharePoint.SPWeb]$web,            
    [parameter(Mandatory=$true, Position=2)][Microsoft.SharePoint.Taxonomy.TermSet]$TermSet,
    [parameter(Mandatory=$false, Position=4)][switch]$Recurse,            
    [parameter(Mandatory=$false, Position=5)][switch]$WebLevelFieldsOnly            
   )             
             
 $matches = @();             
 $matches += Get-FieldsUsingTermSet $web.Fields $TermSet;            
             
 if($WebLevelFieldsOnly -eq $false)            
 {            
  foreach($list in $web.Lists)            
  {            
   $matches += Get-FieldsUsingTermSet $list.Fields $TermSet            
  }            
 }            
             
 if($Recurse)            
 {            
  foreach($subweb in $web.Webs)            
  {            
   $matches += Get-ManagedMetadataFieldUses $subweb $TermSet $Recurse $WebLevelFieldsOnly;            
  }            
 }            
             
 return $matches            
}

Examples of using the script to create some reports.

1. Download the script from here:
2. Save the script somewhere. "C:\Temp" is a good place!
3. If you haven't already, set the PowerShell execution policy to Bypass (this will allow you to import all PowerShell scripts)

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser

4. Import the script into PowerShell.

Import-Module C:\Temp\Get-ManagedMetadataFieldUses.ps1

5. Run a few commands to get a termset to report on. In this example, I get a termset called "Countries"

#Get the SPWeb object            
$w = Get-SPWeb http://mdysp13;            
#Get the taxonomy session used by the SPWeb's site            
$tsession = Get-SPTaxonomySession -Site $w.Site;            
#Get all the TermSets with the name "Countries", and the web's default Language ID
$termSets = $tsession.GetTermSets("Countries",$w.Language)            
#Display the TermSets found            
$termSets | FT @{Label="Group";Expression={($_.Group).Name}},Name,ID            
#Select the first TermSet            
$termSet = $termSets[0]



6. Call the Get-ManagedMetadataFieldUses function, and store the results in the $matchingFields variable.

$matchingFields = Get-ManagedMetadataFieldUses -web $w -TermSet $termSet -Recurse

Do some reporting!!

Display all of the results in the raw format.

$matchingFields | FT



Display all of the results, grouping them by the Site. This view of the data will show you how many fields in each site (or web) are referencing the termset)

$matchingFields | Group-Object ParentListUrl



This improves on the previous command, displaying all of the results, grouping them by the Site. In this view, all the fields are listed, grouped under the site they belong to.

$matchingFields | Group-Object ParentListUrl | Select -ExpandProperty Group  | Format-Table -GroupBy ParentListUrl



Finally, group the objects into a Hash Table. This will allow you to directly reference a web URL, to a get a list of fields in that web that reference the termset.

$hashTable = $matchingFields | Group-Object ParentListUrl -AsHashTable -AsString
$hashTable."http://mdysp13" | FT ParentListTitle,FieldTitle,FieldId -AutoSize



And "even more finally", you can export your results to a CSV file for further analysis!

$matchingFields | Export-CSV  -Path C:\temp\fieldreport.csv -NoTypeInformation -Delimiter "`t"

Download the full script from the Microsoft TechNet Gallery here: Find all SPFields that are using a Managed Metadata TermSet


Wednesday, 20 March 2013

Clear (Delete) the value of a taxonomy field using PowerShell

[Update, 3/July/2013]: I've converted this PowerShell into a script which can be downloaded from the Microsoft TechNet Gallery, here: http://gallery.technet.microsoft.com/sharepoint/Update-or-Clear-Metadata-94959cb3

The script makes it easy to clear or update the metadata values of a metadata column.

E.g.

Import-Module C:\Scripts\Update-MetadataField.ps1
$listitems = (Get-SPWeb http://devmy101).Lists["mylistwithdatainit"].Items 
Clear-MetadataField -items $listitems -MetadataFieldInternalName metadataField -ClearAllItems

[Original Article]

Yesterday I got stumped working out how to clear the value of taxonomy field when helping someone on the Microsoft forums. I could clear the value of the field using the TaxonomyField.ParseAndSetValue(item, value) method of the field, setting the value to $null, but it only worked if the list item belonged to a list. If the item was in a document library, the value wouldn't be cleared. Eventually (after a little persistence), I worked it out, and thought I'd share it here.
The proper way to clear a taxonomy field (which seems obvious upon reflection!), is to get an empty taxonomy value, using the Taxonomy.GetFieldValue method, and then pass it to the TaxonomyField.SetFieldValue method.

The only other difference between working with lists and document libraries, is the way in which we retrieve the list item.

Working with Lists
$web = get-spweb http://my
$list = $web.Lists["my list"]
$item = $list.items[1] #Get an item that currently has a value set in the Taxonomy (Managed Metadata) field
$taxField = $item.Fields["metadataField"] -as [Microsoft.SharePoint.Taxonomy.TaxonomyField] 
$taxFieldValue = $taxField.GetFieldValue("");
$taxField.SetFieldValue($item,$taxFieldValue)
$item.Update()

Working with Document Libraries
$web = get-spweb http://my
$list = $web.Lists["document library"] -as [Microsoft.SharePoint.SPDocumentLibrary]
$item = $list.items[1] #Get an item that currently has a value set in the Taxonomy (Managed Metadata) field
$url = [String]::Format("{0}{1}",$web.Url, $item.File.Url)
$file = $web.GetFile($url)
if($file.CheckOutStatus -eq "None")
{
$file.CheckOut()
$fileItem = $file.Item
$taxField = $fileItem.Fields["metadataField"] -as [Microsoft.SharePoint.Taxonomy.TaxonomyField]
$taxFieldValue = $taxField.GetFieldValue("");
$taxField.SetFieldValue($fileItem,$taxFieldValue)
$fileItem.Update()
$file.CheckIn("Updated Taxonomy Field Value")
}
else
{
$msg = [String]::Format("This file, {0}, is checked out and cannot be edited at the moment",$file.Name)
Write-Host $msg -ForegroundColor DarkYellow
}

Other tips...

Getting an item by it's ID
$item = $list.GetItemById(12)

Looping through the list
$web = get-spweb http://my
$list = $web.Lists["my list"]
$items = $list.items
$taxField = $list.Fields["metadataField"] -as [Microsoft.SharePoint.Taxonomy.TaxonomyField] 
$taxFieldValue = $taxField.GetFieldValue("");
foreach($item in $items)
{
$taxField.SetFieldValue($item,$taxFieldValue)
$item.Update()
}

Tuesday, 14 August 2012

Bulk Updating a Taxonomy Field Value on List Items with PowerShell

It's pretty easy to update the value of a taxonomy field on a collection of list items using PowerShell. Basically, you get a collection of list items, get a reference to the taxonomy field, and then loop through the items, using the SetFieldValue method of the taxonomy field to update the taxonomy field value of each item. Here's how:

1. Get the taxonomy session for the site collection

$ts = Get-SPTaxonomySession -Site http://myweb

2. Get the term store

$tstore = $ts.TermStores[0]

3. Get the term group that contains the termset for the taxonomy field you're updating.
# To list the term group names: $tstore.Groups | FT -AutoSize Name

$tgroup = $tstore.Groups["MyTermGroupName"]

4. Get the termset for the taxonomy field you're updating.
# To list the termsets: $tgroup.TermSets | FT -AutoSize Name
$tset = $tgroup.TermSets["MyTermSetName"]

5. Get the term you want to use to update the list items using the terms ID (or name).
# To list all the terms in the termset: $tset.Terms | FT -AutoSize Name,ID
# Get the term using the term name: $term = $tset.Terms["MyTermName"]

$term = $tset.Terms[[System.Guid]("f5a95261-b9a2-428b-90ce-8f85111dfd55")]

6. Get the SPWeb and List containing the list you want to update

$w = Get-SPWeb http://myweb/subsite
$l = $w.Lists["My List"]

7. Get the first list item, and use it to get a reference to the taxonomy field
$firstItem = $l.Items[0]
$tf = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$firstItem.Fields["MyTaxonomyColumnName"]

8. Loop through the collection of list items and update the value

foreach($i in $l.Items){if($i.Title -like "*Presentation*"){$tf.SetFieldValue($i, $term);$i.Update();Write-host "Updated"$i.Title;}}

You could (and should) write this a little more efficiently, especially if the list is large, by using a query to get your collection of items to loop through (more efficient than looping through the entire list);

$ic = $l | ?{$_.Title -like "*Presentation*"}
foreach($i in $ic){$tf.SetFieldValue($i, $term);$i.Update();Write-host "Updated"$i.Title;}

Thursday, 26 July 2012

Creating new terms in a taxonomy store using PowerShell

Ever wondered how to create new terms in a taxonomy set using PowerShell? It's super easy.

Get the Taxonomy Session for your site
$ts = Get-SPTaxonomySession -Site http://myweb/
$tstore = $ts.TermStores[0]

List the term groups (if you don't know the name)
$tstore.Groups | ft name

Get the term group
$tgroup = $tstore.Groups["AdministrativeTermsets"]

List the term sets if you don' t know the name
$tgroup.TermSets | FT Name

Get the termset
$eTypeTS = $tgroup.TermSets["EventType"]

Create a new term (the CreateTerm method can be call with just two parameters, the name and locale id, if you're happy for SharePoint to automatically generate a Guid for the terms ID).
$eTypeTS.CreateTerm("Seminar", 1033, [System.Guid]("9A4B69D1-D359-4152-B9F6-34357C769711"))

When you're finished creating terms, update the termgroup to commit the changes.
$tgroup.TermStore.CommitAll()

Tuesday, 17 July 2012

Adding Taxonomy Fields to Content Types in Visual Studio

Recently I created some content types in Visual Studio to be packaged and deployed as part of a wider solution. I thought I'd document how I went about it, in case you're interested.

My solution (actually two solutions) consists of a base solution that contains all of the field definitions, and a second solution (dependant on the first) that contains the content types, list definitions, views, etc.

To create the first solution, containing the fields that will be used in the new content types:

1. Create a new Empty SharePoint project in Visual Studio

2. Add a new Empty Element to the project

3. Next, add all the field definitions that you intent to use. For my project, I added several taxonomy (managed metadata) fields, as well as a few other various fields. I keep to a couple of rules when adding fields;

Rule 1. Name all my custom fields with a prefix that identifies our organisation (in my case, this "ict")
Rule 2. Always specify the Group attribute, to logically group my fields

4.To add the taxonomy field, you need to create two field definitions, a taxonomy field, and a text field. The example below highlights the attributes I'm setting (note that I've set the FillInChoice attribute to true, to allow terms to be added to the termset via the new / edit list forms in the browser).

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
 <Field ID="{58BFD5B7-808E-4C39-ABD1-5EF402F419A9}"  
DisplayName="_Technology" Name="ictTechnology0" 
StaticName="ictTechnology0" Group="Ince Managed Fields" 
Type="Note" ShowInViewForms="FALSE" Required="FALSE" 
Hidden="TRUE" CanToggleHidden="TRUE" RowOrdinal="0" />
 <Field ID="{C3BC216A-10E6-4A52-A875-B368BF663297}" 
DisplayName="Technology" Name="ictTechnology" 
StaticName="ictTechnology" Group="Ince Managed Fields" 
Type="TaxonomyFieldType" ShowField="Term1033" FillInChoice="TRUE"/>
</Elements> 


5. Once the fields have been added, I use an feature receiver to do the rest (associate the taxonomy field to a Term Set, and associate the text field to the taxonomy field. I've seen blogs where people do this declaratively, but I prefer the feature receiver - then I can check if the termset exists, create it if it doesn't, and optionally create default terms.

The code in my feature receiver (I've scoped my feature to the Site level) looks a little like this, performing the following functions; Check the termgroup exists (create it if it doesn't), check the termset exists (create it if it doesn't), configure the fields.

Note. Make sure you add a reference to Microsoft.SharePoint.Taxonomy


public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
try
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Activating Ince Base Fields."), null);
using (SPSite site = properties.Feature.Parent as SPSite)
{
if (site == null)
{SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Unexpected, EventSeverity.ErrorCritical), TraceSeverity.Unexpected, String.Format("[FeatureActivation] Could not get a reference to the site. Aborting."), String.Empty);
return;}

TaxonomySession session = new TaxonomySession(site);
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Taxonomy Session: {0}", session), null);
if (session.TermStores.Count != 0)
{
var termStore = session.TermStores[0];
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Term Store: {0}", termStore.Name), null);

//Get the Term Group, creating it if it doesn't already exist
var group = GetTermGroup(termStore, "InceFunctional");
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Group: {0}", group.Name), null);

//Get the Term Set, creating it if it doesn't already exist
TermSet technologyTermSet = GetTermSet(group, "Technology", new Guid("{B71B29C9-4FBA-4FEE-9D77-77EF84C43ED2}"));
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Term Set: {0}", technologyTermSet.Name), null);

Guid fieldId = new Guid("{C3BC216A-10E6-4A52-A875-B368BF663297}"); //Technology field.
if (site.RootWeb.Fields.Contains(fieldId))
{
TaxonomyField taxonomyField = site.RootWeb.Fields[fieldId] as TaxonomyField;
if (taxonomyField == null)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Taxonomy Field is null for field id: {0}.", fieldId), null);
return;
}
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Setting Taxonomy Field settings for field: {0}.", fieldId), null);

//Associate the taxonomy field to the termset.
taxonomyField.SspId = technologyTermSet.TermStore.Id;
taxonomyField.AnchorId = Guid.Empty;
taxonomyField.TermSetId = technologyTermSet.Id;
taxonomyField.AllowMultipleValues = true;

//Configure the taxonomy field to allow terms to be added via the form
taxonomyField.CreateValuesInEditForm = true;
//Associate the text field to the taxonomy field.
taxonomyField.TextField = new Guid("{58BFD5B7-808E-4C39-ABD1-5EF402F419A9}");
taxonomyField.Update();
}}}}
catch (Exception e)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Unexpected, EventSeverity.ErrorCritical), TraceSeverity.Unexpected, String.Format("[FeatureActivation] Unexpected error activating Ince Base Fields solution. Error: {0}", e.Message), e.StackTrace);
}}

private static TermSet GetTermSet(Group group, String termsetName, Guid termsetId)
{
try
{
foreach (TermSet set in group.TermSets)
{
if (set.Name.ToLower().Equals(termsetName.ToLower()))
{return set;}
}
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Termset does not exist. Creating Termset. "), null);
var termSet = group.CreateTermSet(termsetName, termsetId, 1033);
group.TermStore.CommitAll();
return termSet;
}
catch (Exception e)
{
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Unexpected, EventSeverity.ErrorCritical), TraceSeverity.Unexpected, String.Format("[FeatureActivation] Unexpected error activating ITContentTypes solution. Error: {0}", e.Message), e.StackTrace);
throw new Exception("[GetTermSet] Exception getting term set.");
}}

private static Group GetTermGroup(TermStore store, string groupName)
{
try
{
foreach (Group g in store.Groups)
{
if (g.Name.ToLower().Equals(groupName.ToLower()))
{
return g;
}}
SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Ince.BaseFields", TraceSeverity.Medium, EventSeverity.Information), TraceSeverity.Medium, String.Format("[FeatureActivation] Group does not exist. Creating Group. "), null);
store.CreateGroup(groupName);
store.CommitAll();
return store.Groups[groupName];
}
catch (Exception)
{
throw new Exception("[GetTermGroup] Exception getting term store group.");
}}


6. Build and package the solution, then deploy it.

7. The next step is creating a content type that uses the taxonomy field. To do this I've created a new Empty SharePoint Project, and added a new Content Type to the project. I've based mine on an Item.

8. Open the Elements.xml file created for the content type, and add field references to (among others) your new taxonomy fields.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Parent ContentType: Item (0x01) -->
<ContentType ID="0x0100d8cf45e3ef6a4044a6b9dea877e4617f"
   Name="IT Item"
   Group="Ince"
   Description="A list for tracking IT ideas or issues."
   Inherits="TRUE"
   Version="0">
<FieldRefs>
  <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 
  DisplayName="Title"/>
  <FieldRef ID="{C6F3AEEB-1ADE-43CB-81BA-828D38E23D78}" 
  DisplayName="_Project" Name="ictProjectName0"/>
  <FieldRef ID="{75335227-12FD-4376-935F-30C2A57B1AB5}" 
  DisplayName="Project" Name="ictProjectName" Required="FALSE" />
  <FieldRef ID="{58BFD5B7-808E-4C39-ABD1-5EF402F419A9}" 
  DisplayName="_Technology" Name="ictTechnology0"/>
  <FieldRef ID="{C3BC216A-10E6-4A52-A875-B368BF663297}" 
  DisplayName="Technology" Name="ictTechnology" Required="FALSE" />
  <FieldRef ID="{53101f38-dd2e-458c-b245-0c236cc13d1a}" 
  DisplayName="Assigned To" />
  <FieldRef ID="{9da97a8a-1da5-4a77-98d3-4bc10456e700}" 
  DisplayName="Description" NumLines="10"/>
  <FieldRef ID="{c15b34c3-ce7d-490a-b133-3f4de8801b76}" 
  DisplayName="Status"/>
</FieldRefs>
</ContentType>
</Elements> 


9. Bobs your uncle (that means it's finished - deploy it and away you go!). Some other things you might want to consider; adding an activation depency on the first solution, and adding some list definitions to your solution that use the content type.