Category Archives: SharePoint 2013

Get radio button selected text from SharePoint choice field on page layout – JQuery, bind to change event

So I couldn't find anything obvious for this so chucked this together, hopefully useful for someone else 🙂

Page layout:

<div class="featured-breaking">
        <SharePoint:RadioButtonChoiceField FieldName="FeaturedBreaking" runat="server"></SharePoint:RadioButtonChoiceField>
</div>

JavaScript:

var selectedVal = "";
var selected = $(".featured-breaking input[type='radio']:checked");
if (selected.length > 0) {
    selectedVal = selected.parent().attr('title');
}
console.log(selectedVal);

On load, bind to change event:

<script type="text/javascript">
	_spBodyOnLoadFunctionNames.push("LoadNewsEntryPage");
	function LoadNewsEntryPage() {
		$(".jsFeaturedBreaking input[type='radio']").bind("change", FeaturedBreakingChanged);
		FeaturedBreakingChanged();
	}

	function FeaturedBreakingChanged() {
		var selectedVal = "";
		var selected = $(".jsFeaturedBreaking input[type='radio']:checked");
		if (selected.length > 0) {
			selectedVal = selected.parent().attr('title');
		}
		if (selectedVal == "N/A") {
			$('.jsExpiresOn').hide();
		}
		else {
			$('.jsExpiresOn').show();
		}
	}
</script>

 

PowerShell to add and sort Managed Metadata navigation – on premises

Here's a useful snippet of PowerShell code for creating Managed Metadata navigation and sorting it – on premises

Add-PSSnapin Microsoft.SharePoint.Powershell

# Change these variables based on environment ****
$centralAdminURL = "http://centraladmin"
$siteCollectionURL = "http://sitecollection"

$termStoreName = “Managed Metadata Service”
$termGroupName = “Navigation Group”
$termGroupDescription = "Group Description"
$termsetName = "Navigation Termset"
$termSetDescription = "Termset Description"
# *************************************************


function CreateTerm($parent, $name, $url)  
 {  
    Write-Host "Adding term $name to parent $parent.Name"  
    $term = $parent.CreateTerm("$name", 1033)  
    $term.IsAvailableForTagging = $false    
    $newUrl = $url
    if(!$url.ToLower().Contains("http"))
    {  
        # Logic to append site url to relative path to create complete url which is needed for Simple Link Navigation
        $newUrl = $url
    }

    $term.SetLocalCustomProperty("_Sys_Nav_SimpleLinkUrl", $newUrl)   
    return $term  
 } 

$site = Get-SPSite -Identity $centralAdminURL
$session = Get-SPTaxonomySession -Site $centralAdminURL
$termStore = $session.TermStores[$termStoreName]

# Remove if exists already
$group = $termStore.Groups[$termGroupName]
if ($group -ne $null)
{
    Write-Host "Removing terms"
    $group.TermSets|foreach{
        $_.Delete()
    }
    $group.Delete()
    $termstore.CommitAll()
}

# Create term group and term set
$group = $termstore.CreateGroup($termGroupName)
$group.Description = $termGroupDescription
$termStore.CommitAll()

Write-Host "TermGroup - $termGroupName created successfully"  

$termSet = $group.CreateTermSet($termsetName,1033)
$termSet.Description = $termSetDescription
$termSet.IsAvailableForTagging = $false
$termSet.IsOpenForTermCreation = $false
$navigationSet = $group.TermSets[$termsetName]
$navigationSet.SetCustomProperty("_Sys_Nav_IsNavigationTermSet", "True")
$termStore.CommitAll()

Write-Host "TermSet - $termsetName created successfully"  

# Create terms

# Home
$homeTerm = CreateTerm $termSet "Home" $($siteCollection + "/")

# Other term set

$otherTerm = CreateTerm $termSet "Other Term" $($siteCollection + "/subsite/Pages/home.aspx")

$termStore.CommitAll()

# Create sub terms
$otherTermSub1 = CreateTerm $otherTerm "Other Term Sub 1" $($siteCollection + "/subsite/Pages/othertermsub1.aspx")
$otherTermSub2 = CreateTerm $otherTerm "Other Term Sub 2" $($siteCollection + "/subsite/Pages/othertermsub2.aspx")
$otherTermSub3 = CreateTerm $otherTerm "Other Term Sub 3" $($siteCollection + "/subsite/Pages/othertermsub3.aspx")
$otherTermSub4 = CreateTerm $otherTerm "Other Term Sub 4" $($siteCollection + "/subsite/Pages/othertermsub4.aspx")

# Set sort order
$customSortOrder = $otherTermSub1.id.ToString() + ":" + $otherTermSub2.id.ToString() + ":" + $otherTermSub3.id.ToString() + ":" + $otherTermSub4.id.ToString()
$otherTerm.CustomSortOrder = $customSortOrder

$termStore.CommitAll()

Write-Host "Terms for Term Set - $termsetName created successfully"   

 

How to connect to SharePoint Online

Running PowerShell with SharePoint on premises is pretty easy, you jump on the server and run up PowerShell or PowerShell ISE and start connecting and commanding. So how do you connect to SharePoint online from either a server or your local machine?

You need to install Windows Management Framework 3.0 and SharePoint Online Management Shell. The cool thing about SharePoint online and cmdlets is that you can run them from any machine with the tools installed.

Create a new PowerShell script by either opening a new notepad document and saving it as a '.ps1' file, or by opening PowerShell ISE. Paste in the following code and change the user, password and URLs.

Clear-Host
$spOnlineUser = ''
$spOnlinePassword = ''
$spOnlineSiteAdminUrl = 'https://pkswlgsandbox-admin.sharepoint.com'
$spOnlineSiteUrl = 'https://pkswlgsandbox.sharepoint.com/sites/devicechannels'

$cred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $spOnlineUser, $(convertto-securestring $spOnlinePassword -asplaintext -force)
Connect-SPOService -Url $spOnlineSiteAdminUrl -Credential $cred
Write-Host "Connecting to " $spOnlineSiteUrl

$site = Get-SPOSite -Identity $spOnlineSiteUrl
Write-Host "Success!!" -ForegroundColor "Green"
$site

Now that you have a .ps1 file ready, run SharePoint Online Management Shell as Administrator and run your script.

Quick side note, if you get the following error "….ps1 cannot be loaded because running scripts is disabled on this system….", you need to run the following command "Set-ExecutionPolicy RemoteSigned" and click "Yes".

In SharePoint Online Management Shell:

In PowerShell ISE:

Once you have scripts running, and if you have set your credentials and URL's correctly, you should see the following:

I love PowerShell ISE, so if your planning on writing many scripts you should use it. It allows editing and running of scripts on one screen as well as intellisense and debugging.


So, what can you actually do against SharePoint online?? The cmdlets available out of the box are very limited, which means you need to write CSOM in your PowerShell. Here is a post by Chris O'Brien with more information on CSOM in PowerShell. You can also start to use custom CMDlets:

Māori spell checking in SharePoint 2013 and SharePoint Online

There isn't (as far as I can tell from anywhere) a Māori dictionary in SharePoint, even though you can set it as a prefered language in your profile. I found a post from SharePoint 2010 which specifies a work around for setting another language as the spell check language by adding some JavaScript into your master page, you can also add the code into a page layout. This worked fine for French when I tried it, but didn't work for Māori which uses the local ID 1153. Instead I got the following message "The spelling check cannot be completed because there is no dictionary installed for the current language". Dictionaries must only be installed when a language pack is installed, of which there isn't a huge variety.

It's not ideal, but you can still include Māori words in the dictionary. Keep reading!

In SharePoint 2010, you could add a custom dictionary which would be used by spell check throughout the site. This hasn't changed in SharePoint 2013 and SharePoint Online.

This works per site collection:

  • Create a new word document (if you don't require macrons then you can just use notepad to create the .txt file and follow the same steps)
  • Add a word to each line, as in the screen shot below

    Adding words to custom dictionary

  • Save the file as a .txt file named "Custom Dictionary.txt" – this has to be the exact name of the file, select "Other encoding" and set Inicode (UTF-8)

    saving the custom dictionary

  • In the root of your site collection, create a document library named exactly "Spelling"
  • Upload your Custom Dictionary.txt file into the document library

The words you added to the dictionary will now be correct spelling.

example editing html field with new dictionary

 

 

 

Requesting images with specified width/height in SharePoint 2013 with the query string (image renditions)

Image renditions are a powerful new SharePoint 2013 feature which allow not only users to specify the size of an image when adding one to a page, but also for developers to request a specified size in code.

I'm not going to go over image renditions in too much detail as it's covered pretty well on other blogs, such as this one by Waldek Mastykarz. Bascically you set up an image size (e.g. Banner Image – Width: 750px, Height: 120px) in the image renditions list, this is displayed as an option to the user when adding an image to a page. The user can drag the rendition size around the image (like when cropping a profile image in FaceBook).

For developers this gives us a few new tools to work with to help with performance and usability/governance

Setting Renditions

When adding an RichImageField to a page layout, you can specify the default rendition ID, this means the user can't change the rendition. This would be useful for pages where an image always needs to be a certain size (such as a news banner).

You can do this through SharePoint Design Manager in the snippets screen by entering either some dimensions or the rendition ID into the "Rendition Properties". This will generate the snippet to put in your .html page layout file.

image rendition properties example in snippets

If you are using .aspx files instead of Design Manager files, simply add the following properties to your image fields markup:
<%@Register Tagprefix="PageFieldRichImageField" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<PageFieldRichImageField:RichImageField runat="server" FieldName="942cf913-d3ad-4493-9754-e91cf53e3cd9" RenditionId="1">

edit image rendition not avilable as set by control
As you can see, the Image Rendition is set to rendition ID 1 and the user can't change this. The one issue with this, is that the user can't crop the image.

OR

<PageFieldRichImageField:RichImageField runat="server" FieldName="942cf913-d3ad-4493-9754-e91cf53e3cd9" DisplayHeight="100" DisplayWidth="100" RenditionId="-1">

Setting the display width and height on the field sets the size settings to disabled on the image screen for the user. It also leaves the rendition options open for the user to select. Regardless of the image rendition the user chooses, it will be added to the page as the specified width and height, in this case 200 x 200. The user can then change the rendition post adding to the page. This could be a good option if you want to force the image to a certain size, but still give the user the option to change it later. It seems a little confusing though, the user would wonder why the rendition field is still enabled initially when it isn't used.

specify height on rendition image field
 

Query String

Performance wise, this gives us the ability to request images by specific rendition ID OR by width/height. This is awesome!! Images will now be generated at the specified size server side, meaning users aren't downloading large images unnecessarily. Great for mobile sites.

Recently I was coding a news widget for a client using REST calls to the search service. I had to display the first image in all announcements posts. The problem I encountered was some images being returned were pretty massive and caused the widget to load slowly. Lukily, using renditions I was able to overcome the issue by appending the size I required to the query string. Here's an example of an image's src:

https://site.sharepoint.com/sites/devicechannels/PublishingImages/bg_body_5.jpg?Width=100
OR
https://site.sharepoint.com/sites/devicechannels/PublishingImages/bg_body_5.jpg?RenditionId=2

 

For SharePoint online, this all just works. For on premises, you will need to enable the blog cache.

 

Deployment Approaches for SharePoint 2013 Online & On Premises – Presentation

I recently presented at the Wellington, New Zealand SharePoint User Group on deployment approaches for SharePoint 2013 Online and on premises (transcript below).

I discussed the approaches Provoke Solutions Wellington have taken in the past for deployments in SharePoint 2007 and 2010 and how we got to a pretty comfortable position before 2013 came out. I then go over three different approaches we have taken for SharePoint 2013 deployments including on premises and SharePoint Online, covering a bit of CSOM, design manager, custom CMDlets, sandboxed/farm solutions and more.

In the conclusion I discuss how custom CMDlets are the way of the future for deployments due to the fact that they can run both on premises and on SharePoint online, but also because Microsoft are supporting this approach. Check out the Office 365 Patterns and Practices and also check out Wayne Ewington's Tech Ed 2014 presentation where he discusses new approaches for avoiding farm solutions. Provoke also have a downloadable CMDlets library you can use, find out more here.
 

Presentation Contents:

Overview
The Past
The Present

The Future
Conclusion


Overview

SharePoint has never been the easiest platform to code against or deploy to and often involves a lot of harsh lessons for any new SharePoint developers. It can be easy to go over time and budget if you haven’t planned and accounted for deployments appropriately.

Building a robust deployment approach takes experience, research and time as I will explain through the presentation. But with the right approach you can make development, testing and releases a less stressful process.

SharePoint solution releases can have quite a few moving parts, for this presentation I have kept it pretty simple, only covering basic assets such as master pages, content types etc and creating site structures.

So, let’s talk a little bit about the past.

The Past

SharePoint 2007 and 2010 had many challenges, but the one great thing about them both was farm solutions! Writing C# code for everything was close enough to ASP.Net development that it wasn’t too hard to pick up once you got your head around some basic SharePoint fundamentals. I may be simplifying this a little, but you get the idea.

Over this 5 years, Provoke built an awesome site provisioning class library called Fluent Creation Extensions. This code library allowed you to quickly generate site columns, content types, site structures and more. We used this library in event receivers and console applications for releases and updates inside farm solutions which deployed site assets directly to the SharePoint hive. As far as I’m aware we didn’t use Sandboxed solutions at all during this time.

Here is an example use of the creation library at work (click to enlarge):

As you can see in this specific example, we are quickly and easily defining a new page content type with taxonomy fields, date fields, choice field etc. We are also specifying default values, internal names and the content type group. This allowed most of the SharePoint interactions and cumbersome coding tasks to be abstracted away.

When I joined Provoke, I had been working with SharePoint for a while but mostly on products, not intranets. So when I started work on some 2010 enhancements for a client, I used the Fluent Extensions along with some code samples from previous projects and was able to get something up and running quickly. The development for this project went extremely smoothly and testing hours were well under, deployments also went in without a hitch. The 5 years of sweat, tears and experience from others had paid off.

Everything was awesome… Until SharePoint 2013 and SharePoint online came out. This brings us to the next part of the journey.

The Present

With SharePoint 2013 came a lot of change, heaps of new features and new approaches for deploying structure and assets. We knew that farm solutions were now undesirable and we had to start looking at alternatives. In the past we had not only provisioned sites with server side code, but also built all webparts with it. We now had to think about how we could use out of the box components and strategies.
 

FIRST APPROACH – Design Manager, Sandboxed Solutions, XML + PowerShell, and a little bit of CSOM

So how did we first approach 2013? Note that this first approach is only for on premises.

We started only using Sandboxed solutions for deploying nearly everything including web parts, content types, fields and css. We also started using the new Design Manager for page layouts, master pages and their associated content types and fields.

This new approach had a number of disadvantages, starting with Design Manager. Both our office and the Auckland office encountered a number of issues with using the packages it created in deployments and we decided it wasn’t reliable enough to use any longer.

The other disadvantage to this approach was using such a huge amount of PowerShell. PowerShell is amazing, but it wasn’t the easiest to debug and just didn’t seem the right tool for writing large deployments. The other obvious disadvantage to this approach was we were using exclusively on premises out of the box cmdlets and therefore couldn’t re-use it with online sites. This was early days though and the learning continued.

The XML file specifies the structure of the site and subsites and includes for example which sandboxed solutions to install, which features to activate, which pages to create, managed metadata set up etc. We used a small amount of CSOM in the form of custom CMDlets for connecting managed metadata fields.

Design Manager and PowerShell
 

SECOND APPROACH – CSOM library, Console Application and Sandboxed Solutions

Our next approach involved a few changes. First off we removed Design Manager packages – we still used it for generating page layouts and master pages, but extracted the code files into our own sandboxed solutions.

We also built a CSOM class library and a C# console application. This was starting to get a bit more like our beloved farm solution approach.

This new approach had a few benefits including the ability to run the code against SharePoint online and developers having a more familiar environment to develop and debug in. We used a similar XML structure for defining the site creation.

csom and console apps
 

THIRD APPROACH – Custom CMDlets, CSOM and Sandboxed Solutions

By this stage we were starting to build some good momentum and experience around SharePoint 2013. But there was still progress to be made.

We made the choice to follow the Auckland office’s lead and use custom PowerShell cmdlets with CSOM. This would give Provoke a more unified approach to SharePoint deployments as the Wellington office has always been more code heavy while the Auckland office favoured more out of the box approaches.

Custom powershell cmdlets

This approach has worked magic for our latest releases and we were able to re-use a lot of the tried and tested code from our CSOM library, meaning it wasn’t all wasted. We are heading more towards a point where we have the best possible deployment approach.

Following is an example of creating custom list level permissions

XML structure

<Security>
   <AddCustomListPermissions>
      <CustomListPermission ListTitle="Pages" Group="News &amp; Events" PermissionLevel="Contribute"  />
   </AddCustomListPermissions>
</Security>


PowerShell

function processCustomListPermissions($web, $customListPermissions)
{
    if ($customListPermissions.HasChildNodes)
    {
        foreach($customListPermission in $customListPermissions.CustomListPermission)
        {
            write-host "Adding new permission level to list " $customListPermission.ListTitle $customListPermission.Group $customListPermission.PermissionLevel
            Add-SPOListPermission $web $customListPermission.ListTitle $customListPermission.Group $customListPermission.PermissionLevel
        }
    }
}


Custom CMDlet

    /// <summary>
    /// Breaks inheritance on a list and adds/removes specified groups with specified security level
    /// </summary>
    /// <remarks>
    /// <strong>Arguments<br/></strong>
    /// <strong>Parent (Web):</strong> The parent Web under which the list exists eg. $ctx.Web<br/>
    /// <strong>ListTitle (String):</strong> List Title<br/>
    /// <strong>Group (String):</strong> The group to add or remove<br/>
    /// <strong>PermissionLevel (String):</strong> The permission level to add the group as<br/>
    /// </remarks>
    /// <example><code>Add-SPOListPermission $ctx.Web "List Title" "Group Name" "Contribute"</code></example>
    [Cmdlet(VerbsCommon.Add, "SPOListPermission")]
    public class AddSPOListPermission : PSCmdlet
    {
        [Parameter(Position = 0), ValidateNotNullOrEmpty]
        public Web Parent { get; set; }

        [Parameter(Position = 1), ValidateNotNullOrEmpty]
        public string ListTitle { get; set; }

        [Parameter(Position = 2), ValidateNotNullOrEmpty]
        public string Group { get; set; }

        [Parameter(Position = 3), ValidateNotNullOrEmpty]
        public string PermissionLevel { get; set; }

        protected override void ProcessRecord()
        {
            List list = null;

            var lists = Parent.Lists;
            Parent.Context.Load(lists, l => l.Include(x => x.Title));
            Parent.Context.ExecuteQuery();

            list = lists.Cast<List>().SingleOrDefault(l => l.Title.Equals(ListTitle));

            if (list != null)
            {
                list.BreakRoleInheritance(true, false);
                Parent.Context.ExecuteQuery();

                var group = Parent.SiteGroups.GetByName(Group);
                var role = Parent.RoleDefinitions.GetByName(PermissionLevel);
                Parent.Context.Load(role);
                Parent.Context.Load(group);
                Parent.Context.ExecuteQuery();

                if (group != null)
                {
                    if (role != null)
                    {
                        var roleBinding = new RoleDefinitionBindingCollection(Parent.Context);
                        roleBinding.Add(role);
                        list.RoleAssignments.Add(group, roleBinding);
                        Parent.Context.ExecuteQuery();
                    }
                    else
                        Console.WriteLine(String.Format("Permission level '{0}' not found", PermissionLevel));
                }
                else
                    Console.WriteLine(String.Format("Group '{0}' not found", Group));
            }
            else
                Console.WriteLine(String.Format("List '{0}' not found", ListTitle));
        }
    }

This brings us to the next phase of our journey

The Future

We will continue to invest time and effort in researching and developing more efficient and robust deployment approaches. We will be sticking with the CMDlets approach which has proven a good decision after watching a presentation Wayne Ewington gave at TechEd this month. Microsoft are pushing for avoiding any solution files, including sandboxed. This means building CMDlets for deploying content types and fields as well as for uploading site assets. Luckily as part of this push there are a number of CMDlets available for everyone to use. These are written by community members but endorsed by Microsoft engineers.

We will be looking at these CMDlets alongside our own to determine whether we should use these CMDlets, our own or possibly a combination.

Conclusion

Using custom CMDlets is currently the best approach for deployments.

There are many challenges ahead, one of the biggest being the rapidly changing SharePoint online environment. There’s always new functionality and enhancements going in, which means keeping an eye on the relevance of your deployment code. It also introduces the complexity of maintaining CMDlets which will work for on premises AND online. 

It’s pretty exciting times in the SharePoint world at the moment.

 

Set Current Navigation/Quick Launch and Global Navigation with CSOM

I was recently working on a site and at first glance, couldn’t find any obvious way to manipulate Structural Navigation settings using CSOM.

As it turns out setting the navigation with CSOM is quite different from doing it from server side code or PowerShell which looks like the following (in PowerShell).

$assetsWeb = Get-SPWeb "url"
$SPPubWebAssets = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($assetsWeb)
$SPPubWebAssets.Navigation.InheritCurrent = $false
$SPPubWebAssets.Navigation.ShowSiblings = $false
$SPPubWebAssets.Navigation.CurrentIncludeSubSites = $false
$SPPubWebAssets.Navigation.CurrentIncludePages = $false
$SPPubWebAssets.Update()
$assetsWeb.dispose()

Thankfully, I found this post on technet which discussed the issue I was having and included the following code:

web.AllProperties[“__CurrentNavigationIncludeTypes”] = "3";

The answer worked a charm but I was a little unsure about what “3” was specifying. After a little more digging I found that the values range from 0-3 and are ordered in the way the checkboxes are presented through the user interface (as shown in the screen shot above).

Here are the values for the __GlobalNavigationIncludeTypes and __CurrentNavigationIncludeTypes options

0 = don't show pages or subsites
1 = show subsites only
2 = show pages only
3 = show subsites and pages

Full CSOM code inside a Cmdlet

/// <summary>
    /// Set quick launch navigation settings for web
    /// </summary>
    /// <remarks>
    /// <strong>Arguments<br/></strong>
    /// <strong>Parent (Web):</strong> The parent Web under which to change the settings<br/>
    /// <strong>NavigationSource (String):</strong> Whether the web should use InheritFromParentWeb, PortalProvider, TaxonomyProvider or Unknown for quick launch.<br/>
    /// <strong>CurrentNavigationIncludeTypes (String):</strong> Specify values for including types. 0 = don't show pages or subsites, 1 = show subsites only, 2 = show pages only, 3 = show subsites and pages<br/>
    /// </remarks>
    /// <example><code>Set-SPOQuickLaunchNavigationSettings $parent "PortalProvider" “3”</code></example>
    [Cmdlet(VerbsCommon.Set, "SPOQuickLaunchNavigationSettings")]
    public class SetSPOQuickLaunchNavigationSettings : Cmdlet
    {
        [Parameter(Position = 0), ValidateNotNullOrEmpty]
        public Web Parent { get; set; }

        [Parameter(Position = 1), ValidateNotNullOrEmpty]
        public String NavigationSource { get; set; }

        [Parameter(Position = 2), ValidateNotNullOrEmpty]
        public String NavigationIncludeTypes { get; set; }

        protected override void ProcessRecord()
        {
            var Context = Parent.Context;

            var taxonomySession = TaxonomySession.GetTaxonomySession(Context);
            var settings = new WebNavigationSettings(Context, Parent);

            var navigationSource = StandardNavigationSource.PortalProvider;
            switch (NavigationSource)
            {
                case "PortalProvider" : navigationSource = StandardNavigationSource.PortalProvider; break;
                case "InheritFromParentWeb": navigationSource = StandardNavigationSource.InheritFromParentWeb; break;
                case "TaxonomyProvider": navigationSource = StandardNavigationSource.TaxonomyProvider; break;
                case "Unknown": navigationSource = StandardNavigationSource.Unknown; break;
                default : navigationSource = StandardNavigationSource.PortalProvider; break;
            }
            settings.CurrentNavigation.Source = navigationSource;
            settings.Update(taxonomySession);

            Parent.AllProperties["__CurrentNavigationIncludeTypes"] = NavigationIncludeTypes;

            Parent.Update();
            Context.ExecuteQuery();
        }
    }