Tag: SharePoint 2013

Setting default values on SharePoint Taxonomy columns with CSOM Powershell

Programmatically or with PnP Provisioning setting a default value for a column is not that problem but with Taxonomy columns and its corresponding Termstore termsets things get a bit more complex.

Let my shortly explain why. Assume we have a simple termset (german locations here):

Termset_GER

Assume further we have a taxonomy column and apply it a default value of “Hamburg”

TaxonomyColumn

then accessing the column with a simple REST call (/_api/web/fields I often use this for investigation) would show us the default property in the following way:

<d:DefaultValue>2;#Hamburg|220ac9fa-64f2-4eaf-9a6d-787aefb3a499</d:DefaultValue>

So what about those three values? The second value “Hamburg” indeed is the Label and the third the GUID we might know from the termstore (With PnP Provisioning for instance we can provision whole termsets with pre-created GUIDs. This gives us great control for further dealing with those termsets.
But what about the first number (“2”)?

This is an item ID from a hidden list called “TaxonomyHiddenList“. Each time you select a term from a termset, this term will be written to this list. We can check this by accessing this list. Although it is hidden, we can open it once we know its name (and if we forget the name /_api/web/lists helps us to remember)

TaxonomyHiddenList

The problem now is: A termset exists once in our tenant (we don’t talk about site collection termsets here) but the list is new in every new site collection. And as we do not control the order we are not sure that “Hamburg” will be the 2nd item in the next site collection, too. Or will it be picked at all? That’s the most important problem! We cannot rely that the termset we want to make to the default one already exists in our site collection’s TaxonomyHiddenList.

So we need some kind of an ensure process and the following CSOM-driven Powershell script will do this for us:

We need a site and its Url, the name of our column, the label and the GUID of the term we want to have as default. With that we call our function.

In the function we first access our column. After that we create a TaxonomyFieldValue with our Label and our GUID, as a WssID we use -1.

Finally, and that’s the “heart” of this function, we use the GetValidatedString method of our Taxonomy field instance. The return value will be what we need and have seen above. If the term already existed in the TaxonomyHiddenList it will contain the existing ID of the list item. If it didn’t it would have been inserted now.

The returned string we can use in our code to set the default value or insert in our Provisioning Template if we use PnP Provisioning or another tool. In PnP we can simply transfer the just evaluated string with a parameter like I did with evaluated default groups in my last post, too.

Dealing with default SharePoint groups in PnP Provisioning

Together with a SharePoint site several default groups are created. The most important ones are:

  • Owners
  • Members
  • Visitors

Unfortunately from a provisioning point of view the names are not fix and depend on the language. Furthermore I discovered this week how those groups get renamed during the creation process of modern Groups sites.

The simplest step is to add additional members to those groups. This is quite easy as you only need to add to your PnP Provisioning template:

<pnp:Security>
 <pnp:AdditionalOwners>
   <pnp:User Name="user1@yourdomain.com"/>
   <pnp:User Name="group1@yourdomain.com"/>
 </pnp:AdditionalOwners>
</pnp:Security>

But what about those attributes where you need the exact group names?

Here you can use a combination of PnP Powershell and the Parameters. Ran the following Powershell script:


$siteUrl = "https://mytenant.sharepoint.com/sites/mysite"
$templatePath="c:\Scripts\in\Template_Groups.xml"
$credentials = Get-Credential -UserName 'Admin@mytenant.onmicrosoft.com' -Message "Enter SPO credentials"
Connect-PnPOnline -Url $siteUrl -Credentials $credentials
$spoOwnerGroup = Get-PnPGroup -AssociatedOwnerGroup
Apply-PnPProvisioningTemplate -Path $templatePath -Parameters @{"SPOOwnerGroup"=$spoOwnerGroup.Title}

The main aspect here is the Get-PnPGroup -AssociatedOwnerGroup command and the parameter “SPOOwnerGroup” we provide to our template.

In our template we pick this up and use it in our Principal attribute when providing different RoleAssignments to our libraries or lists for instance.

In our example we only want to have Read access for Owners to the Site Pages library.

In the new schema 2017-05 it will be also possible to remove permissions. At the time of writing this does not work yet. So I show the way to not copy existing permissions but add new ones from the scratch. The problem with adding new permissions for existing groups is that they will be added and do not overwrite existing permissions. And if a group already has Edit permissions adding Read additionally has absolutely no effect.

When you don’t provision with Powershell but from CSOM code, the way is also valid as you have the AssociatedOwnerGroup property .

SharePoint column dependencies with Client-Side-Rendering (CSR)

Of course it is simple to inject a SharePoint list form with JavaScript and use some jQuery to add EventListeners and manipulate column behavior.

But to get more robust solutions and avoid direct DOM dependencies as much as possible I like the approach of Client-Side-Rendering (CSR). There are some good examples out there in the internet Link MSDN and Another Link, also not to neglect the great series from Martin Hatch, but today I want to show you how to make two columns work together.

Assume you have a simple Yes/No column and on click you want to show another column (Yes) or hide (No).

First we need our Template overrides (Line 1-12, see bottom):


CUSTOMER.SP.Title.fieldOverrides.Templates = CUSTOMER.SP.Title.fieldOverrides.Templates || {};
{
CUSTOMER.SP.Title.fieldOverrides.Templates.Fields =
{
'TitleOptPM': {
'EditForm': CUSTOMER.SP.Title.fieldOverrides.setOptPMChecked
},
'ProPlanTemplate': {
'EditForm': CUSTOMER.SP.Title.fieldOverrides.getPMTemplateRender
}
};
}

The first column is our Yes/No checkbox and the function is responsible for adding the event receiver.

The second is our dependent column and the referenced method is only to preserve some information about the rendered HTML to later find it.

Let’s start with the first function (Line 14-29, see bottom):


CUSTOMER.SP.Title.fieldOverrides.setOptPMChecked = function (ctx) {
var defaultHTML = SPFieldBoolean_Edit(ctx);
defaultHTML = defaultHTML.replace('<input', '<input onchange="CUSTOMER.SP.Title.fieldOverrides.optPMChanged(this)"');

var currentValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
// Hide select with original event receiver but dummy unchecked checkbox
if (currentValue == '0') {
var dummyChkBox = document.createElement('input');
dummyChkBox.type = 'checkbox';
dummyChkBox.checked = false;
$('document').ready(function () {
CUSTOMER.SP.Title.fieldOverrides.optPMChanged(dummyChkBox);
});
}
return defaultHTML;
};

The first two lines are sufficient at a first look. We create the defaultHTML. This means how SharePoint would render it without our “override”. SPFieldBoolean_Edit is the choice for a Boolean column in edit mode. Refer to Stackexchange for further standard render methods.

In the second line with a simple .replace() we inject our event receiver for onchange. In the end we return our defaultHTML.

But what about the rest in between? This is our “going in position”.

With var currentValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name]; we get the current value of the current field. Note this line of code works independently of the field’s name so you can even use it in a method for multiple fields.

If the currentValue is ‘0’ meaning false respectevly No we want to hide our 2nd column from the beginning. For that we create a simple dummy checkbox which is not checked and throw it to our event receiver later when the page is rendered. Note that we are currently producing HTML for our columns independently and do not have them present in current page DOM!

Now lets create the 2nd function (Line 31-40):


CUSTOMER.SP.Title.fieldOverrides.getPMTemplateRender = function (ctx) {
var defaultHTML = SPFieldLookup_Edit(ctx);

var parser = new DOMParser();
var docPMTemplate = parser.parseFromString(defaultHTML, "text/html");
var select = docPMTemplate.getElementsByTagName('select')[0];
CUSTOMER.SP.Title.fieldOverrides.pmTemplateID = select.id;

return defaultHTML;
};

Here we create the defaultHTML again, this time for a Lookup column. Afterwards we create a Parser to parse our HTML string. We expect one select in the HTML for our Lookup column (which is indeed a DOM relation but a quite small one). From that select we preserve the ID and store it in our Namespace where we can reference it later in our Event function. (Line 42-50)


CUSTOMER.SP.Title.fieldOverrides.optPMChanged = function (e) {
  var select = document.getElementById(CUSTOMER.SP.Title.fieldOverrides.pmTemplateID);
  if (e.checked) {
	$(select).closest('tr').show();
  }
  else {
	$(select).closest('tr').hide();
  }
};
)

That function is quite simple again. We get our ID from the column to hide we just stored.

Then we have another small DOM dependency as we expect the column in a TR we can hide to hide the whole column line including the Label on the left. But that’s it.

Quite often developer reference columns like this

$('input[title^="Firstname"]')

This is because the Title attribute includes the Display Name of the field. But expect some user with design rights to rename your column and then your code breaks.

The code above can be used in SharePoint Online with classic experience. What could potentially happen that it breaks? The classic form structure using TABLE, TR, TD could be replaced by DIVs and I won’t find my TR to hide anymore. Also, the rendering of a Lookup column could change and the SELECT is not there anymore. In a classic list experience quite unlikely situations.

But nevertheless I am awaiting the new announced [Link] field customization options also for modern experience sites and I assume something not too far away from the CSR Technology.
Update: Referring to a blog entry from Andrew Connell my assumption is not that bad 😉

Finally refer to the whole Code (all funtions):

Programmatically set default values for Managed Metadata Columns

It is quite easy to set default values for SharePoint columns programmatically or with PnP Provisioning but not for every column type.

Especially Managed Metadata columns are not that easy. The problem is the technical architecture of those columns.

I guess you know about managing the termstore and assume we have the following termset attached to our column:

But once we check the content of a column in an existing list item we see something like this:

‘2;#Hamburg|{5722CAB5-58D1-48B3-B09D-378C67E2B938}’

What we see are three parts of content. The 2nd and the 3rd part are easy to guess:
Hamburg is the text value of the term as we know from the termstore and
{5722CAB5-58D1-48B3-B09D-378C67E2B938} is the GUID of the term also existing in the termstore.

But what about the 1st part? We know such syntax from lookup columns and that’s exactly the case here: The content is alos pointing to a lookup list.

Checking a site collection for all existing lists by a simple REST call like the following
http://sp2013.sp.com/sites/our_test_site/_api/web/lists
we can find a list called “TaxonomyHiddenList”.

Once we have found content like that one shown above we will find a list item with the ID 2 in that list.

The problem is, we will only find items for terms that were already used on that site but not for terms that also exist but were not used before.
So how to set default values for values that were not used already for instance in a use case of an automatically provisioned site with still no content at all?

There is the  TaxonomyField.GetValidatedString  method that helps us. It will produce a value like that one above. If an item in the “TaxonomyHiddenList” already exists or not. The following function is a CSOM Powershell script. But you can easily transfer it to managed .Net code if that fits better to your situation:

We provide a site Url, the column name and a term label and term guid to our function. The function creates a client context to the site. After that we get our column by the column name provided to the function and cast it to a TaxonomyField.

Then we create a TaxonomyFieldValue. It contains our label and GUID and a dummy item ID of -1. (As we don’t know the correct one or even if it already exists.

Finally we ensure this with our GetValidatedString method and return the whole value with all three correct parts as shown in the example above. That’s it.

XsltListViewWebPart on a Wiki page with PnP Provisioning

Here I want to show a simple and reusable example to provision a Wiki page containing a simple XsltListViewWebPart:

You can see (if aware of the list of properties a XsltListViewWebPart has) that we removed most of them. It is very important to not have properties such as ListId or ListUrl present. The properties to identify the list (and view, if you do not want just the Default view) are only ListDisplayName and ViewName which is used by the PnP engine. [Link]

What else matters?

We provision a page with the Layout TwoColumnsHeader. Our webpart we place in Row=”2” (so not in the Header) and Column=”1” so on the left side.

The Webpart’s Title is also “Tasks” and has a TitleUrl pointing to the list which you can also remove if you do not want.