Friday, November 27, 2015

Create custom ribbon button in list view for Sharepoint Online using client object model

In this post I will show how to create custom ribbon button in specific list for Sharepoint Online. Suppose that we have the following xml file with declaration of the ribbon button:

   1: <UserCustomAction Location="CommandUI.Ribbon"
   2:     Sequence="5" Title="Get tasks">
   3: <![CDATA[<CommandUIExtension>
   4:   <CommandUIDefinitions>
   5:     <CommandUIDefinition Location="Ribbon.Documents.Manage.Controls._children">
   6:       <Button Id="Ribbon.ListItem.Manage.GetTasks"
   7:         Alt="Get tasks"
   8:         Sequence="10001"
   9:         Command="Get tasks_Button"
  10:         Image32by32="/_layouts/images/rtrsendtoicon.png"
  11:         Image16by16="/_layouts/images/copy16.gif"
  12:         LabelText="Get tasks"
  13:         TemplateAlias="o1" />
  14:     </CommandUIDefinition>
  15:   </CommandUIDefinitions>
  16:   <CommandUIHandlers>
  17:    <CommandUIHandler Command="Get tasks_Button" CommandAction="javascript: GetTasks();" />
  18:   </CommandUIHandlers>
  19: </CommandUIExtension>]]>
  20: </UserCustomAction>

For more convenient work we create own class for that in order to be able to deserialize it from xml:

   1: public class UserCustomActionEntity
   2: {
   3:     [XmlAttribute("Location")]
   4:     public string Location;
   5:     [XmlAttribute("Sequence")]
   6:     public int Sequence;
   7:     [XmlAttribute("Title")]
   8:     public string Title;
   9:     [XmlText]
  10:     public string CommandUIExtension;
  11: }

With these preparations ribbon button can be created using the following code:

   1: var web = ...;
   2: var list = ...;
   3: var existingActions = list.UserCustomActions;
   4: web.Context.Load(existingActions);
   5: web.Context.ExecuteQueryRetry();
   6:  
   7: bool exists = false;
   8: foreach (var a in existingActions)
   9: {
  10:     if (string.Compare(a.Title, ac.Title, false) == 0)
  11:     {
  12:         exists = true;
  13:         break;
  14:     }
  15: }
  16:  
  17: if (exists)
  18: {
  19:     return;
  20: }
  21:  
  22: var action = existingActions.Add();
  23: action.Location = ac.Location;
  24: action.Sequence = ac.Sequence;
  25: action.Title = ac.Title;
  26: action.CommandUIExtension = ac.CommandUIExtension;
  27: action.Update();
  28: web.Context.ExecuteQuery();

Here we first check that same ribbon button is not added already by comparing titles (lines 7-20) and if not, add new custom ribbon button using data from xml declaration (lines 22-28).

Monday, November 23, 2015

Problem in Sharepoint Online with TaxonomyFieldValue represented as Object

Some time ago we faced with interesting problem in Sharepoint Online: there was javascript code which worked properly when it was called from ribbon button shown in list view. This code copied documents with metadata from one document library to another using javascript object model:

   1: var targetFile = null;
   2: var files = targetFolder.get_files();
   3: var enumerator = files.getEnumerator();
   4: while (enumerator.moveNext()) {
   5:     var file = enumerator.get_current();
   6:     if (file.get_name() == fileName) {
   7:         targetFile = file;
   8:         break;
   9:     }
  10: }
  11:  
  12: if (targetFile == null) {
  13:     return;
  14: }
  15:  
  16: var targetItem = targetFile.get_listItemAllFields();
  17: if (sourceItem.get_item("DocumentStatus") != null) {
  18:     // update target item's DocumentStatus field
  19:     // ...
  20: }

However same code didn’t work when it was called from wiki page. When I checked the result of sourceItem.get_item(“DocumentStatus”) call, which supposed to return TaxonomyFieldValue instance, I found that it actually returned Object instance:

image

After that I checked output html for list view and wiki page and found that some system js files were missing in the last one. After some experimenting I found that following 3 files are needed on wiki page in order to make TaxonomyFieldValue available:

   1: <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
   2: <script type="text/javascript" src="/_layouts/15/sp.js"></script>
   3: <script type="text/javascript" src="/_layouts/15/sp.taxonomy.js"></script>

Added them to ScriptEditorWebPart on wiki page and after that result became returned as TaxonomyFieldValue instance:

image

And code started to work also on wiki page.

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.