Showing posts with label Taxonomy. Show all posts
Showing posts with label Taxonomy. Show all posts

Saturday, November 4, 2017

Problem with reusing taxonomy terms with navigation settings in different term sets in Sharepoint

Suppose that we have the following sub sites in the same Sharepoint Online site collection:

  • /en – publishing site
  • /content/en –authoring site

And we need to use the same managed metadata navigation on these publishing and authoring sub sites. In order to achieve that we need to create 2 navigation term sets (e.g. Navigation.en-US and NavigationContent.en-US) and configure navigation settings for the terms:

We can’t use same term set for 2 sub sites because Sharepoint allows to use navigation term set for single site only. As we need to create 2 term sets anyway we would like at least to reuse terms from 1st term set Navigation.en-US in 2nd term set NavigationContent.en-US like it is shown on the picture above (in order to have less maintenance work). And here we face with the problem: it seems like that during reusing navigation settings of the terms are not reused. I.e. if we create terms in 1st term set Navigation.en-US, then reuse them in 2nd term set NavigationContent.en-US and then configure navigation settings in Navigation.en-US – reused terms in NavigationContent.en-US won’t inherit changed navigation settings automatically as we would expect.

Workaround for this problem is quite simple: at first configure navigation settings in source term set (Navigation.en-US) and only after that reuse terms in 2nd term set (NavigationContent.en-US). In this case navigation settings will be inherited properly. But if you will change navigation settings after that in original terms from the source term set – they won’t be changed automatically in 2nd term set. I.e. you will need to change them in 2nd term set explicitly.

Tuesday, October 17, 2017

One way to avoid “Term update failed because of save conflict” error when create managed metadata terms in Sharepoint

In one of the project we used the following PowerShell CSOM code for creating managed metadata navigation terms in Sharepoint Online:

   1: $newTerm = $navTermSet.CreateTerm($web.Title,
   2:     [Microsoft.SharePoint.Client.Publishing.Navigation.NavigationLinkType]::SimpleLink,
   3:     [System.Guid]::NewGuid())
   4: $newTerm.SimpleLinkUrl = $web.ServerRelativeUrl
   5: $termStore.CommitAll()
   6: $ctx.ExecuteQuery()

It worked successfully for many tenants but for one tenant it gave the following error:

Exception calling "ExecuteQuery" with "0" argument(s): "Term update failed because of save conflict."

Error appeared randomly, i.e. for the same sub site some time term has been created successfully and some time it failed with above error. There were no other changes so the reason was not in pending changes.

In order to avoid this error we applied the following workaround:

   1: do
   2: {
   3:     Try
   4:     {
   5:         $newTerm = $navTermSet.CreateTerm($web.Title,
   6:             [Microsoft.SharePoint.Client.Publishing.Navigation.NavigationLinkType]::SimpleLink,
   7:             [System.Guid]::NewGuid())
   8:         $newTerm.SimpleLinkUrl = $web.ServerRelativeUrl
   9:         $termStore.CommitAll()
  10:         $ctx.ExecuteQuery()
  11:         break
  12:     }
  13:     Catch
  14:     {
  15:         Write-Host "Error occured, try one more time" -foregroundcolor yellow
  16:     }
  17: } while ($true)

I.e. instead of single call to CreateTerm and ExecuteQuery we call it in the loop until call will be successful. Log showed that with this approach all navigation terms have been created properly at the end although for some sub sites it failed 1 time, for other 2 and for some even 3 times until it created term successfully, while for most of sub sites there were no errors at all. Hope that this engineering approach will help some one:).

Monday, June 5, 2017

Perform CAML queries with managed metadata fields to Sharepoint lists via javascript object model

In this post I will show how to perform CAML queries which contain conditions with managed metadata (taxonomy) fields via javascript object model (JSOM). Suppose that we have custom list with 2 fields:

  1. Location – managed metadata
  2. Path – single line text which contains url for specific location

We need to query this list using location value and get path for this specific location. Here is the javascript code which can be used for that:

   1: SP.SOD.executeFunc("sp.js", "SP.ClientContext", function () {
   2:     try {
   3:         if (typeof (location) == "undefined" || location == null ||
   4:             location == "") {
   5:             return;
   6:         }
   7:  
   8:         var ctx = new SP.ClientContext("http://example.com");
   9:         var list = ctx.get_web().get_lists().getByTitle("Locations");
  10:         var query = new SP.CamlQuery();
  11:         query.set_viewXml(
  12:             '<View>' +
  13:                 '<Query>' +
  14:                     '<Where>' +
  15:                         '<Contains>' +
  16:                           '<FieldRef Name=\'Location\'/>' +
  17:                           '<Value Type=\'Text\'>' + location + '</Value>' +
  18:                         '</Contains>' +
  19:                     '</Where>' +
  20:                 '</Query>' +
  21:                 '<RowLimit>1</RowLimit>' +
  22:             '</View>');
  23:  
  24:         var items = list.getItems(query);
  25:         ctx.load(items, 'Include(Path)');
  26:         ctx.executeQueryAsync(
  27:             Function.createDelegate(this, function (sender, args) {
  28:                 var path = "";
  29:                 var enumerator = items.getEnumerator();
  30:                 while (enumerator.moveNext()) {
  31:                     var item = enumerator.get_current();
  32:                     path = item.get_item("Path").get_url();
  33:                     break;
  34:                 }
  35:  
  36:                 console.log("Path: " + path);
  37:             }),
  38:             Function.createDelegate(this, function (sender, args) {
  39:                 console.log(args.get_message());
  40:             }));
  41:  
  42:     } catch (ex) {
  43:         console.log(ex.message);
  44:     }
  45: });

Code is self-descriptive so I won’t detailed explain what it does. The only moment to notice is that in order to get list item by taxonomy value in Location field we use Contains operator and pass term label to the query (lines 12-22). After that we just iterate through returned items (in this example we set RowLimit to 1, but in your scenarios you can of course get many items) and read Path field value. In order to be able to access Path field we included it to result set (line 25).

Tuesday, April 19, 2016

Avoid “Value does not fall within the expected range” error when read list item with taxonomy fields via javascript in Sharepoint Online

When list contain many taxonomy fields and you try to read list item with all these fields using CAML query in javascript:

   1: var ctx = SP.ClientContext.get_current();
   2: var list = ctx.get_web().get_lists().getByTitle("MyList");
   3: var query = new SP.CamlQuery();
   4: query.set_viewXml('<View>' +
   5:                       '<Query>' +
   6:                           '<Where>' +
   7:                           '</Where>' +
   8:                       '</Query>' +
   9:                       '<RowLimit>1</RowLimit>' +
  10:                   '</View>');
  11: var items = list.getItems(query);
  12: ctx.load(items, 'Include(Field1,Field2,Field3,...)');
  13: ctx.executeQueryAsync(Function.createDelegate(this, function (sender, args) { /* */ }),
  14:     Function.createDelegate(this, function (sender, args) { console.log("Error: " +
  15:         args.get_message() + "\n" + args.get_stackTrace()); }));

you may get the following error:

Value does not fall within the expected range error

It happens because of List Lookup Threshold Limit which is set to 12 in Sharepoint Online (see Increased site collection and list lookup limits). In order to avoid this error use the following workaround: at first read necessary item to get it’s id. Then load this item using List.getItemById method which will return list item containing all fields without need to explicitly enumerate them in “Include(…)” statement:

   1: var ctx = SP.ClientContext.get_current();
   2: var list = ctx.get_web().get_lists().getByTitle("MyList");
   3: var query = new SP.CamlQuery();
   4: query.set_viewXml('<View>' +
   5:                       '<Query>' +
   6:                           '<Where>' +
   7:                           '</Where>' +
   8:                       '</Query>' +
   9:                       '<RowLimit>1</RowLimit>' +
  10:                   '</View>');
  11: var items = list.getItems(query);
  12: ctx.load(items);
  13: ctx.executeQueryAsync(
  14:     Function.createDelegate(this, function (sender, args) {
  15:         getPropsSuccess(ctx, list, items);
  16:     }),
  17:     Function.createDelegate(this, function (sender, args) {
  18:         console.log("Error: " + args.get_message() + "\n" + args.get_stackTrace());
  19:     })
  20: );
  21:  
  22: function getPropsSuccess(ctx, list, items) {
  23:     var itemId = null;
  24:     var enumerator = items.getEnumerator();
  25:     while (enumerator.moveNext()) {
  26:         var item = enumerator.get_current();
  27:         itemId = item.get_id();
  28:         break;
  29:     }
  30:  
  31:     if (itemId == null) {
  32:         return;
  33:     }
  34:  
  35:     var item = list.getItemById(itemId);
  36:     ctx.load(item);
  37:     ctx.executeQueryAsync(
  38:         Function.createDelegate(this, function (sender, args) {
  39:             // here you may get values of all fields in the list
  40:             // item.get_item("Field1"), item.get_item("Field2"), item.get_item("Field3"), ...
  41:         }),
  42:         Function.createDelegate(this, function (sender, args) { console.log("Error: " +
  43:             args.get_message() + "\n" + args.get_stackTrace()); }));
  44: },

In this example we read all items from the list first and get id of the 1st item only, but you may retrieve item using your own criteria of course by specifying appropriate CAML query. With this approach it is possible to get values of all fields from the list in list item.

Friday, November 13, 2015

Provision managed metadata fields to Sharepoint Online via client object model

In one of my previous posts I showed how to provision managed metadata fields via sandbox solution and how to fix issues related with it: see Fix managed metadata fields after updating sandbox solution using client object model in Sharepoint. In this post I will show how to provision them via client object model. This method also can be used in Sharepoint Online and doesn’t require installations of any wsps.

First of all we need to declare taxonomy field itself and related note field:

   1: <Field ID="{CA0AA6FD-EB29-4E99-86FD-49CD88F1E5BA}"
   2:     Type="Note"
   3:     Name="MyFieldTaxHTField"
   4:     StaticName="MyFieldTaxHTField" 
   5:     Required="FALSE"
   6:     DisplayName="MyFieldTaxHTField"
   7:         ShowInViewForms="FALSE"
   8:         Hidden="TRUE"
   9:     CanToggleHidden="TRUE" />
  10:  
  11: <Field ID="{E5874300-A621-4EFD-BC6F-B6E521B40311}"
  12:     Type="TaxonomyFieldType"
  13:     Name="MyField"
  14:     StaticName="MyField"
  15:     ShowField="Term1035"
  16:     Required="FALSE"
  17:     Group="Custom"
  18:     DisplayName="My Field">
  19:         <Default></Default>
  20:         <Customization>
  21:             <ArrayOfProperty>
  22:             <Property>
  23:                 <Name>SspId</Name>
  24:                 <Value xmlns:q1="http://www.w3.org/2001/XMLSchema" p4:type="q1:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{f3ae3865-e5a0-4173-add8-2190e865ec47}</Value>
  25:             </Property>
  26:             <Property>
  27:                 <Name>GroupId</Name>
  28:             </Property>
  29:             <Property>
  30:                 <Name>TermSetId</Name>
  31:                 <Value xmlns:q2="http://www.w3.org/2001/XMLSchema" p4:type="q2:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{6F186952-8CAC-4099-B0E4-CCE3BAA3177F}</Value>
  32:             </Property>
  33:             <Property>
  34:                 <Name>AnchorId</Name>
  35:                 <Value xmlns:q3="http://www.w3.org/2001/XMLSchema" p4:type="q3:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">00000000-0000-0000-0000-000000000000</Value>
  36:             </Property>
  37:             <Property>
  38:                 <Name>UserCreated</Name>
  39:                 <Value xmlns:q4="http://www.w3.org/2001/XMLSchema" p4:type="q4:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
  40:             </Property>
  41:             <Property>
  42:                 <Name>Open</Name>
  43:                 <Value xmlns:q5="http://www.w3.org/2001/XMLSchema" p4:type="q5:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
  44:             </Property>
  45:             <Property>
  46:                 <Name>TextField</Name>
  47:                 <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{CA0AA6FD-EB29-4E99-86FD-49CD88F1E5BA}</Value>
  48:             </Property>
  49:             <Property>
  50:                 <Name>IsPathRendered</Name>
  51:                 <Value xmlns:q7="http://www.w3.org/2001/XMLSchema" p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
  52:             </Property>
  53:             <Property>
  54:                 <Name>IsKeyword</Name>
  55:                 <Value xmlns:q8="http://www.w3.org/2001/XMLSchema" p4:type="q8:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
  56:             </Property>
  57:             <Property>
  58:                 <Name>TargetTemplate</Name>
  59:             </Property>
  60:             <Property>
  61:                 <Name>CreateValuesInEditForm</Name>
  62:                 <Value xmlns:q9="http://www.w3.org/2001/XMLSchema" p4:type="q9:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">false</Value>
  63:             </Property>
  64:             <Property>
  65:                 <Name>FilterAssemblyStrongName</Name>
  66:                 <Value xmlns:q10="http://www.w3.org/2001/XMLSchema" p4:type="q10:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Microsoft.SharePoint.Taxonomy, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Value>
  67:             </Property>
  68:             <Property>
  69:                 <Name>FilterClassName</Name>
  70:                 <Value xmlns:q11="http://www.w3.org/2001/XMLSchema" p4:type="q11:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">Microsoft.SharePoint.Taxonomy.TaxonomyField</Value>
  71:             </Property>
  72:             <Property>
  73:                 <Name>FilterMethodName</Name>
  74:                 <Value xmlns:q12="http://www.w3.org/2001/XMLSchema" p4:type="q12:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">GetFilteringHtml</Value>
  75:             </Property>
  76:             <Property>
  77:                 <Name>FilterJavascriptProperty</Name>
  78:                 <Value xmlns:q13="http://www.w3.org/2001/XMLSchema" p4:type="q13:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">FilteringJavascript</Value>
  79:             </Property>
  80:             </ArrayOfProperty>
  81:         </Customization>
  82:     </Field>

The following properties are most important:

- SspId – term store id. You may get it in Site settings > Term store management and select top level node in the left tree view;
- TermSetID – id of term set which current field will be bound to. You may also get it from Site settings > Term store management;
- TextField – should contain id of related note field (declared above).

Having this xml definition we may create fields using the following client object model code:

   1: string fieldAsXml = "";
   2:  
   3: var clientContext = new ClientContext("http://example.com");
   4: var secure = new SecureString();
   5: foreach (char c in "password")
   6: {
   7:     secure.AppendChar(c);
   8: }
   9: var credentials = new SharePointOnlineCredentials("userame", secure);
  10: clientContext.Credentials = credentials;
  11:  
  12: var web = clientContext.Web;
  13: var fields = web.Fields;
  14: clientContext.Load(web, w => w.Fields);
  15: clientContext.Load(fields);
  16: clientContext.ExecuteQuery();
  17: fields.AddFieldAsXml(fieldAsXml, false, AddFieldOptions.AddFieldInternalNameHint);
  18: web.Update();
  19: clientContext.ExecuteQuery();

After that field will be available in site columns.