List Items manipulation via REST API in SharePoint 2010

Introduction

Apart from CSOM API,  REST API introduces another approach to access SharePoint list data from platforms on which the CSOM may be unavailable. The SharePoint REST interface is based on the REST-based Open Data protocol (OData) which  is a platform-independent open standard.

Examples

This section contains sample code for all of the CRUD operations.

Create

In order to  perform a Create operation via REST, you must perform the following actions:

  • Create an HTTP request using the POST verb.
  • Use the service URL of the list to which you want to add an entity as the target for the POST.
  • Set the content type to application/json.
  • Serialize the JSON objects that represent your new list items as a string, and add this value to the request body.

The following code snippet demonstrates how to perform a Create operation against a SharePoint list.

function createListItem(webUrl,listName, itemProperties, success, failure) {
$.ajax({
url: webUrl + "/_vti_bin/listdata.svc/" + listName,
type: "POST",
processData: false,
contentType: "application/json;odata=verbose",
data: JSON.stringify(itemProperties),
headers: {
"Accept": "application/json;odata=verbose"
},
success: function (data) {
success(data.d);
},
error: function (data) {
failure(data.responseJSON.error);
}
});
}
//Usage: create task
var taskProperties = {
'TaskName': 'Order Approval',
'AssignedToId': 12
};
createListItem('https://contoso.sharepoint.com/project/','Tasks',taskProperties,function(task){
console.log('Task' + task.TaskName + ' has been created');
},
function(error){
console.log(JSON.stringify(error));
}
);

view raw
createListItem.js
hosted with ❤ by GitHub

Read

In order to  perform a Read operation via REST, you must perform the following actions:

  • Create an HTTP request using the GET verb.
  • Use the service URL of the list item to which you want to add an entity as the target for the GET.
  • Set the content type to application/json.

The code sample demonstrates of how to retrieve an item based on its ID:

function getListItemById(webUrl,listName, itemId, success, failure) {
var url = webUrl + "/_vti_bin/listdata.svc/" + listName + "(" + itemId + ")";
$.ajax({
url: url,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
success(data.d);
},
error: function (data) {
failure(data.responseJSON.error);
}
});
}
//Usage
getListItemById('https://contoso.sharepoint.com/project/','Tasks',2,function(taskItem){
console.log(taskItem.TaskName);
},
function(error){
console.log(JSON.stringify(error));
}
);

view raw
getListItemById.js
hosted with ❤ by GitHub

Update

To  update an existing entity, you must perform the following actions:

  • Create an HTTP request using the POST verb.
  • Add an X-HTTP-Method header with a value of MERGE.
  • Use the service URL of the list item you want to update as the target for the POST
  • Add an If-Match header with a value of the entity’s original ETag.

In contrast to reading list item  to update an item you will need to pass the eTag value, which could be obtained during item read.

About eTags

When updating or deleting items within SharePoint lists via REST you must specify the Entity Tag (eTag) value that was returned with the item during the initial query. This enables SharePoint to determine if the item has changed since it was requested. Alternatively you can tell SharePoint to perform the operation regardless by specifying * as the eTag value. For example:

  1. “If-Match”: item.__metadata.etag can be used to specify the actual eTag value (‘item’ is the object returned from SharePoint containing the list item in JSON format).
  2. “If-Match”: “*” can be used to match any eTag value resulting in the operation being performed regardless of the actual value.

They form part of the Ajax call for updating an item. eTags are part of the HTTP Protocol V1.1, more information on the topic can be found here.

function updateListItem(webUrl,listName,itemId,itemProperties,success, failure)
{
getListItemById(webUrl,listName,itemId,function(item){
$.ajax({
type: 'POST',
url: item.__metadata.uri,
contentType: 'application/json',
processData: false,
headers: {
"Accept": "application/json;odata=verbose",
"X-HTTP-Method": "MERGE",
"If-Match": item.__metadata.etag
},
data: Sys.Serialization.JavaScriptSerializer.serialize(itemProperties),
success: function (data) {
success(data);
},
error: function (data) {
failure(data);
}
});
},
function(error){
failure(error);
});
}
//Usage: set Task Name & AssignedTo
var taskProperties = {
'TaskName': 'Approval',
'AssignedToId': 12
};
updateListItem('https://contoso.sharepoint.com/project/','Tasks',2,taskProperties,function(item){
console.log('Task has been updated');
},
function(error){
console.log(JSON.stringify(error));
}
);

view raw
updateListItem.js
hosted with ❤ by GitHub

Delete

To  delete an  entity, you must perform the following actions:

  • Create an HTTP request using the POST verb.
  • Add an X-HTTP-Method header with a value of DELETE.
  • Use the service URL of the list item you want to update as the target for the POST
  • Add an If-Match header with a value of the entity’s original ETag.

The Delete operation is similar to Update operation,  the code below demonstrates how to perform a Delete operation:

function deleteListItem(webUrl, listName, itemId, success, failure) {
getListItemById(webUrl,listName,itemId,function(item){
$.ajax({
url: item.__metadata.uri,
type: "POST",
headers: {
"Accept": "application/json;odata=verbose",
"X-Http-Method": "DELETE",
"If-Match": item.__metadata.etag
},
success: function (data) {
success();
},
error: function (data) {
failure(data.responseJSON.error);
}
});
},
function (error) {
failure(error);
});
}
//Usage: delete Task with Id=3
deleteListItem('https://contoso.sharepoint.com/project/','Tasks',3,function(){
console.log('Task has been deleted');
},
function(error){
console.log(JSON.stringify(error));
}
);

view raw
deleteListItem.js
hosted with ❤ by GitHub

References

Introduction to Client Forms Validation in SharePoint 2013

Client side event handlers in SharePoint

In addition to server side Event Model,  SharePoint also provides client side  event handling capabilities when working with List Forms.

Let’s see briefly how it works. In List Forms pages Save button click handler triggers PreSaveItem function , PreSaveItem function is declared in forms.js:

function PreSaveItem()
{
if ("function"==typeof(PreSaveAction))
{
return PreSaveAction();
}
return true;
}

view raw
PreSaveItem.js
hosted with ❤ by GitHub

PreSaveAction function from another hand, is a user defined function that allows to override standard behavior for a Save button handler, so in terms of server side event model it is a binding mechanism.

I find this mechanism  pretty convenient for implementing custom client side validation in List Forms,  the following example demonstrates how to implement client side validation for Email field in Contacts list:

function validateEmail(email) {
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
function PreSaveAction(){
var emailBox = getFieldControl('Email','Text');
if (!validateEmail(emailBox.val())) {
var errorHtml = '<span class="ms-formvalidation"><span role="alert">Invalid email address<br></span></span>';
emailBox.after(errorHtml);
return false;
}
return true;
}
function getFieldControl(fieldName,fieldType)
{
var control = $('[id^=' + fieldName + '_][id$=' + fieldType + 'Field]');
return control;
}

view raw
PreSave.js
hosted with ❤ by GitHub

EmailValidate

Client forms validation in SharePoint 2013

In SharePoint 2013 as part of  Client-Side Rendering (CSR), there is SPClientForms.ClientValidation  namespace (clientforms.js) that contains  types and objects for performing  client side validation in List Forms. For example,

SPClientForms.ClientValidation.RequiredValidator object is intended for evaluating the value of an field control to ensure that the user enters a value (it  is invoked for a fields when SPField.Required property is set to True):

SPClientForms.ClientValidation.RequiredValidator = function() {
};
SPClientForms.ClientValidation.RequiredValidator.prototype.Validate = function(value) {
value = SPClientTemplates.Utility.Trim(value);
var hasError = value === '';
var errorMsg = hasError ? Strings.STS.L_SPClientRequiredValidatorError : '';
return new SPClientForms.ClientValidation.ValidationResult(hasError, errorMsg);
};

Below is provided a complete example that demonstrates how to implement  client-side validation for Email field in Contacts list:

CustomClientValidation = {};
CustomClientValidation.EmailValidator = function() {
};
CustomClientValidation.EmailValidator.prototype.Validate = function(value) {
value = SPClientTemplates.Utility.Trim(value);
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var hasError = !re.test(value);
var errorMsg = hasError ? 'Invalid email address' : '';
return new SPClientForms.ClientValidation.ValidationResult(hasError, errorMsg);
};
function EmailField_Edit(rCtx) {
if (rCtx == null)
return '';
var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(rCtx);
if (formCtx == null || formCtx.fieldSchema == null)
return '';
var _value = formCtx.fieldValue != null ? formCtx.fieldValue : '';
if (_value.length > 0) {
var validators = new SPClientForms.ClientValidation.ValidatorSet();
validators.RegisterValidator(new CustomClientValidation.EmailValidator());
formCtx.registerClientValidator(formCtx.fieldName, validators);
}
return SPFieldText_Edit(rCtx); //default renderer for Text field
}
(function () {
var emailFieldCtx = {};
emailFieldCtx.Templates = {};
emailFieldCtx.Templates.Fields = {
'Email': {
'NewForm': EmailField_Edit,
'EditForm': EmailField_Edit
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(emailFieldCtx);
})();

Key points:

  • CustomClientValidation.EmailValidator object implements a custom validation  for Email field
  • since Email field is a standard Text Field,  the rendering of field control is reused, only custom validator is registered for a field (see function EmailField_Edit for a details)

Working with the SharePoint Online REST service via PowerShell

Since SharePoint 2013 introduces a Representational State Transfer (REST) service that is comparable to the existing SharePoint client object models, it opens up a huge capabilities, in particular for administering and automating SharePoint Online when used with PowerShell.

My first idea was to utilize  Invoke-RestMethod cmdlet, which was introduced in Windows PowerShell 3.0.  Invoke-RestMethod cmdlet contains Credential parameter which could  accept basic, digest, NTLM, and Kerberos authentication.

SharePoint Client Component SDK comes with  a SharePointOnlineCredentials class  which represents an object that provides credentials to access SharePoint Online resources. But unfortunately SharePoint Online credentials could not be passed in Invoke-RestMethod cmdlet, since claims based authentication is not supported by this cmdlet.

Below is demonstrated a simplified PowerShell script  that to some extent mimics Invoke-RestMethod cmdlet. This script is intended for sending HTTPS request to a SharePoint Online REST service:

Param(
[Parameter(Mandatory=$True)]
[String]$Url,
[Parameter(Mandatory=$False)]
[Microsoft.PowerShell.Commands.WebRequestMethod]$Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get,
[Parameter(Mandatory=$True)]
[String]$UserName,
[Parameter(Mandatory=$False)]
[String]$Password
)
Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
if([string]::IsNullOrEmpty($Password)) {
$SecurePassword = Read-Host Prompt "Enter the password" AsSecureString
}
else {
$SecurePassword = $Password | ConvertTo-SecureString AsPlainText Force
}
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $SecurePassword)
$request = [System.Net.WebRequest]::Create($Url)
$request.Credentials = $credentials
$request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f")
$request.Accept = "application/json;odata=verbose"
$request.Method=$Method
$response = $request.GetResponse()
$requestStream = $response.GetResponseStream()
$readStream = New-Object System.IO.StreamReader $requestStream
$data=$readStream.ReadToEnd()
$results = $data | ConvertFrom-Json
$results.d.results

view raw
Invoke-RestSPO.ps1
hosted with ❤ by GitHub

Examples

#Example 1. Gets a value that specifies usage information about the site, including bandwidth, storage, and the number of visits to the site collection
$result = .\Invoke-RestSPO.ps1 Url "https://contoso.sharepoint.com/_api/site/usage" UserName "username@tenant.onmicrosoft.com" Password "password"
write-host 'Total amount of disk space, currently being used by the site collection (Mb):' ([math]::round($result.Usage.Storage /1Mb))
#Example 2. Get List Items
.\Invoke-RestSPO.ps1 Url "https://contoso.sharepoint.com/_api/web/lists/getbytitle('Contacts&#39;)/title" UserName "username@tenatnt.onmicrosoft.com" Password "password"

References

Working with Discussions List via SharePoint Client Object Model

Overview

In this post  will be demonstrated how to perform a common CRUD operations when working with Discussions List via  Client Object Model (JavaScript CSOM) in SharePoint 2010/2013.

Create a Discussion

So, let’s get started with creating a Discussion item. SP.Utilities.Utility.createNewDiscussion method is used for creating a discussion item:

function createDiscussion(listTitle,properties,OnItemAdded,OnItemError)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(listTitle);
context.load(list);
var discussionItem = SP.Utilities.Utility.createNewDiscussion(context, list, properties.Subject);
for(var propName in properties) {
if(propName == 'Subject') continue;
discussionItem.set_item(propName, properties[propName])
}
discussionItem.update();
context.load(discussionItem);
context.executeQueryAsync(
function() {
OnItemAdded(discussionItem);
},
OnItemError
);
}

view raw
createDiscussion.js
hosted with ❤ by GitHub

Load all Discussions

Since Discussion Content Type derives from Folder Content Type  we could utilize SP.CamlQuery.createAllFoldersQuery method to construct a query to retrieve all the discussions:

function getDiscussions(listTitle,OnItemsLoaded,OnError)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(listTitle);
context.load(list);
var qry = SP.CamlQuery.createAllFoldersQuery();
var discussionItems = list.getItems(qry);
context.load(discussionItems);
context.executeQueryAsync(
function() {
OnItemsLoaded(discussionItems);
},
OnError
);
}

view raw
getDiscussions.js
hosted with ❤ by GitHub

Create a Message

The SP.Utilities.Utility.createNewDiscussionReply method is used to create a Message item (add reply to a discussion item):

function createMessage(discussionItem,properties,OnItemAdded,OnItemError)
{
var context = new SP.ClientContext.get_current();
var messageItem = SP.Utilities.Utility.createNewDiscussionReply(context, discussionItem);
for(var propName in properties) {
messageItem.set_item(propName, properties[propName])
}
messageItem.update();
context.executeQueryAsync(
function() {
OnItemAdded(messageItem);
},
OnItemError
);
}
function createMessages(discussionItem,messagesProperties,OnItemsAdded,OnItemsError)
{
var context = new SP.ClientContext.get_current();
var messageItems = [];
$.each(messagesProperties, function (i, properties) {
messageItems.push(SP.Utilities.Utility.createNewDiscussionReply(context, discussionItem));
for(var propName in properties) {
messageItems[i].set_item(propName, properties[propName])
}
messageItems[i].update();
});
context.executeQueryAsync(
function() {
OnItemsAdded(messageItems);
},
OnItemsError
);
}

view raw
createMessage.js
hosted with ❤ by GitHub

Load all Messages

Since message items  are contained within discussion container (Folder) , to identify messages by discussion we will use SPBuiltInFieldId.ParentFolderId field in CAML query:

function getMessages(listTitle,disscussionId,OnItemsLoaded,OnError)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(listTitle);
context.load(list);
var qry = createAllMessagesByDisscussionIDQuery(disscussionId);
var messageItems = list.getItems(qry);
context.load(messageItems);
context.executeQueryAsync(
function() {
OnItemsLoaded(messageItems);
},
OnError
);
}
function createAllMessagesByDisscussionIDQuery(disscussionId) {
var qry = new SP.CamlQuery;
var viewXml = "<View Scope='Recursive'> \
<Query> \
<Where> \
<Eq> \
<FieldRef Name='ParentFolderId' /> \
<Value Type='Integer'>" + disscussionId + "</Value> \
</Eq> \
</Where> \
</Query> \
</View>";
qry.set_viewXml(viewXml);
return qry;
};

view raw
getMessages.js
hosted with ❤ by GitHub

Import Discussion List

And finally in order to see how to work with Discussions List in action,  let’s discuss one more example.  Suppose we need to import Discussions List content from an external source, let’s say from Stack Exchange. To access Stack Exchange content (Questions and Answers in our case) we will utilize Stack Exchange API:

function DiscussionBoardImporter()
{
this.sourceQuery = '/2.1/questions?order=desc&sort=votes&site=sharepoint&filter=!-.AG)sL*gPlV';
this.targetDiscussionsListName = 'Discussions List';
}
DiscussionBoardImporter.prototype = (function() {
var importData = function(data,discussionsListName)
{
$.each(data, function (i, question) {
var discussionProperties = {'Body': question.body,'Subject':question.title,'VoteNumber': question.score};
createDiscussion(discussionsListName,discussionProperties,
function(discussionItem){
console.log('Discussion ' + discussionItem.get_item('Title') + ' has been created successfully');
var messagesProperties = [];
$.each(question.answers, function (j, answer) {
messagesProperties.push({'Title': answer.title,'Body': answer.body});
});
createMessages(discussionItem,messagesProperties,
function(){
console.log('Messages have been created successfully');
},
function(sender,args){
console.log('Error occured while creating messages:' + args.get_message());
}
);
},
function(sender,args){
console.log('Error occured while creating disscussion:' + args.get_message());
});
});
};
var loadData = function(query,targetListName, loaded)
{
$.ajax({
url: 'http://api.stackexchange.com&#39; + query,
dataType: 'json',
success: function(data) {
loaded(data.items,targetListName);
}
});
};
return {
constructor:DiscussionBoardImporter,
import: function()
{
loadData(this.sourceQuery,this.targetDiscussionsListName,importData);
}
};
})();
var importer = new DiscussionBoardImporter();
importer.import();

How to populate a SharePoint List via CSOM

Overview

Populating a SharePoint List with data is  required for different purposes quite often. In this article I am going to demonstrate how to populate Contacts list with a fake data using CSOM.

Provision column to a List via SharePoint CSOM

We will utilize Contacts List for demonstration purposes. But before we  proceed, let’s add a Geolocation field to a list.

Since the Geolocation column is not available by default in SharePoint lists, it’s common practice to provision this field to a list programmatically. The code below demonstrates  how to add the Geolocation field  by using the SharePoint client object model:

function ProvisionField(fieldSchema, listTitle,OnSuccess,OnError)
{
var context = SP.ClientContext.get_current();
var list = context.get_web().get_lists().getByTitle(listTitle);
var fields = list.get_fields();
var fieldToAdd = fields.addFieldAsXml(fieldSchema, true, SP.AddFieldOptions.AddToDefaultContentType);
fieldToAdd.update();
context.load(fieldToAdd);
context.executeQueryAsync(OnSuccess(fieldToAdd),OnError);
}
function AddGeolocationFieldToContactsList()
{
ProvisionField("<Field Type='Geolocation' DisplayName='Location'/>","Contacts",
function(field){
console.log('Geolocation field has been added');
},
function(sender,args){
console.log('Error occured while adding Geolocation field:' + args.get_message());
})
}

Generate list data

For populating Contacts list we will utilize Faker.js JavaScript library that is intended for generating massive amounts of fake data, below is provided the complete code:

function AddListItem(listTitle,itemProps,OnItemAdded,OnItemError)
{
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(listTitle);
var itemCreateInfo = new SP.ListItemCreationInformation();
var listItem = list.addItem(itemCreateInfo);
for(var propName in itemProps) {
listItem.set_item(propName, itemProps[propName])
}
listItem.update();
context.load(listItem);
context.executeQueryAsync(
function() {
OnItemAdded(listItem);
},
OnItemError
);
}
function GenerateContacts()
{
var contactsCount = 108;
for(var i = 0; i < contactsCount; i++){
var contactEntry = CreateContactEntry();
AddListItem('Contacts',contactEntry,
function(contactItem){
console.log('Contact ' + contactItem.get_item('Title') + ' has been created successfully');
},
function(sender,args){
console.log('Error occured while creating contact:' + args.get_message());
});
}
}
function CreateContactEntry()
{
var contactCard = Faker.Helpers.createCard();
return {'Title': contactCard.username,
'FullName': contactCard.name,
'Email': contactCard.email,
'Company': contactCard.company.name,
'WorkPhone': contactCard.phone,
'WorkAddress': contactCard.address.streetA,
'WorkCity': contactCard.address.city,
'WorkZip': contactCard.address.zipcode,
'WorkCountry': contactCard.address.ukCountry,
'WebPage': 'http://&#39; + contactCard.website,
'Location': 'Point(' + contactCard.address.geo.lng + ' ' + contactCard.address.geo.lat + ')'};
}
var scriptbase = _spPageContextInfo.siteAbsoluteUrl + '/_layouts/15/';
$.getScript(scriptbase + 'SP.js',
function () {
$.getScript(_spPageContextInfo.siteAbsoluteUrl + '/SiteAssets/Faker.js', GenerateContacts);
}
);

view raw
GenerateListData.js
hosted with ❤ by GitHub

Results

After running the script the Contacts list will be populated with a fake data as demonstrated below.

Contacts default view

Contacts

Contacts map view

ContactsMap

References

Some tips and tricks of using SharePoint Client Object Model in PowerShell. Part 1

Overview

When it comes to using SharePoint 2010 Client Object Model (CSOM) we need to be ready for certain kind of  limitations  in PowerShell. First of all, it concerns the usage of Generics Methods, for the example ClientRuntimeContext.Load<T> method:

public void Load<T>(
T clientObject,
params Expression<Func<T, Object>>[] retrievals
)
where T : ClientObject

An attempt to call the method ClientRuntimeContext.Load<T> directly will result in the following error PSGenericMethods

This is a limitation of PowerShell  (V1, V2) AFIK. There are several options how to bypass this limitation but in this post I would like to concentrate only on one technique that was originally described in the post Invoking Generic Methods on Non-Generic Classes in PowerShell. The basic idea is to replace the call for ClientRuntimeContext.Load<T> method with the following one:

Function Invoke-LoadMethod() {
param(
$ClientObject = $(throw "Please provide an Client Object instance on which to invoke the generic method")
)
$ctx = $ClientObject.Context
$load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")
$type = $ClientObject.GetType()
$clientObjectLoad = $load.MakeGenericMethod($type)
$clientObjectLoad.Invoke($ctx,@($ClientObject,$null))
}

For invoking a generic methods we utilize MethodInfo.MakeGenericMethod method. Below are demonstrated some examples of usage SharePoint 2010 Client Object Model (CSOM) in PowerShell.

Example: load Web client object

Let’s start with a simple example for loading Web Client Object:

Add-Type Path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.Client.dll'
Add-Type Path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.Client.Runtime.dll'
function PrintWebProperties()
{
param(
[Parameter(Mandatory=$true)][string]$url,
[Parameter(Mandatory=$false)][System.Net.NetworkCredential]$credentials
)
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$ctx.Credentials = $credentials
$web = $ctx.Web
Invoke-LoadMethod ClientObject $web
$ctx.ExecuteQuery()
Write-Host "Web Properties:"
Write-Host "Title: $($web.Title)"
Write-Host "Url: $($web.ServerRelativeUrl)"
}
$credentials = New-Object System.Net.NetworkCredential('username', 'password','domain')
$url = 'http://contoso.intranet.com/'
PrintWebProperties $url $credentials

Example: create Wiki page via CSOM

The example below demonstrates how to create wiki page via CSOM.

C# version:

/// <summary>
/// Create Wiki page via CSOM
/// </summary>
/// <param name="webUrl"></param>
/// <param name="pageName"></param>
/// <param name="pageContent"></param>
public static void CreateWikiPage(string webUrl, string pageName,string pageContent)
{
const string templateRedirectionPageMarkup = "<%@ Page Inherits=\"Microsoft.SharePoint.Publishing.TemplateRedirectionPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c\" %> <%@ Reference VirtualPath=\"~TemplatePageUrl\" %> <%@ Reference VirtualPath=\"~masterurl/custom.master\" %>";
using (var ctx = new ClientContext(webUrl))
{
var wikiPages = ctx.Web.Lists.GetByTitle("Pages");
ctx.Load(wikiPages);
ctx.ExecuteQuery();
var file = new FileCreationInformation
{
Url = pageName,
Content = Encoding.UTF8.GetBytes(templateRedirectionPageMarkup),
Overwrite = true
};
var wikiFile = wikiPages.RootFolder.Files.Add(file);
ctx.Load(wikiFile);
ctx.ExecuteQuery();
var wikiPage = wikiFile.ListItemAllFields;
wikiPage["PublishingPageContent"] = pageContent;
wikiPage["PublishingPageLayout"] = "/_catalogs/masterpage/EnterpriseWiki.aspx, Basic Page";
wikiPage.Update();
ctx.ExecuteQuery();
}
}

view raw
CreateWikiCSOM.cs
hosted with ❤ by GitHub

PowerShell version:

Add-Type Path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.Client.dll'
Add-Type Path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.Client.Runtime.dll'
function CreateWikiPage()
{
param(
[Parameter(Mandatory=$true)][string]$webUrl,
[Parameter(Mandatory=$false)][System.Net.NetworkCredential]$credentials,
[Parameter(Mandatory=$true)][string]$pageName,
[Parameter(Mandatory=$true)][string]$pageContent
)
$templateRedirectionPageMarkup = '<%@ Page Inherits="Microsoft.SharePoint.Publishing.TemplateRedirectionPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %> <%@ Reference VirtualPath="~TemplatePageUrl" %> <%@ Reference VirtualPath="~masterurl/custom.master" %>';
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl)
$ctx.Credentials = $credentials
$wikiPages = $ctx.Web.Lists.GetByTitle("Pages")
Invoke-LoadMethod ClientObject $wikiPages
$ctx.ExecuteQuery()
$file = New-Object Microsoft.SharePoint.Client.FileCreationInformation
$file.Url = $pageName
$file.Content = [System.Text.Encoding]::UTF8.GetBytes($templateRedirectionPageMarkup)
$file.Overwrite = $true
$wikiFile = $wikiPages.RootFolder.Files.Add($file)
Invoke-LoadMethod ClientObject $wikiFile
$wikiPage = $wikiFile.ListItemAllFields
$wikiPage["PublishingPageContent"] = $pageContent
$wikiPage["PublishingPageLayout"] = "/_catalogs/masterpage/EnterpriseWiki.aspx, Basic Page"
$wikiPage.Update()
$ctx.ExecuteQuery();
}
$credentials = New-Object System.Net.NetworkCredential('username', 'password','domain')
$webUrl = 'http://contoso.intranet.com/knowledgebase/'
$pageName = 'MyFirstWikiPage.aspx'
$pageContent = '<h1>Welcome to the Knowledge Base!</h1>'
CreateWikiPage $webUrl $credentials $pageName $pageContent

view raw
CreateWikiPage.ps1
hosted with ❤ by GitHub

MyFirstWikiPage

Summary

In contrary to article Using PowerShell to Get Data from a SharePoint 2010 List that explains how to execute generic methods via inline C#  in PowerShell, this post demonstrates how to utilize Generics Methods in PowerShell natively.

References

Access and Manipulate Navigation Settings via SharePoint Client Object Model

Overview

SharePoint Server OM provides  PortalNavigation class that represents navigation for portal pages and other portal navigation objects. In turn the PublishingWeb class  is used to access the navigation settings (PortalNavigation type).

What about Client Object Model? Unfortunately it is not supported to access and manipulate Navigation settings via CSOM since there is no PublishingWeb Client Object  in SharePoint 2010. But still it possible to accomplish via Client Object Model.

Retrieving Web Client Object with Properties

PortalNavigation class uses internally SPWeb.AllProperties property to store Navigation settings and the same type also exist as a Client Object – Web.AllProperties

ClientObject provides a base class for objects on a remote client, the full list of  client objects available in SharePoint 2010 could be found on MSDN page.

When accessing client objects there are some differences that concern how object properties are initialized. According to the MSDN, in the server object model, when you return an SPWeb object, all of its properties become available for use in code. But to improve performance in the client object model, when you return a Web object (JavaScript: Web) certain properties are not included, and you must explicitly retrieve them.

The following example is aimed to demonstrate how to retrieve Web client object with it properties:

namespace Publishing.Navigation.Client.Samples
{
class PortalNavigation
{
/// <summary>
/// Read Web All Properties
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static PropertyValues LoadWebWithProperties(string url)
{
using (var clientContext = new ClientContext(url))
{
var web = clientContext.Web;
clientContext.Load(web,w => w.AllProperties);
clientContext.ExecuteQuery();
return web.AllProperties;
}
}
}
}

Access and manipulate Navigation settings via CSOM

The following class represents Client Object Model (CSOM) implementation of PortalNavigation class:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.SharePoint.Client;
namespace SharePoint.Client.Publishing.Navigation
{
/// <summary>
/// Represents navigation for portal pages and other portal navigation objects
/// </summary>
class ClientPortalNavigation
{
public ClientPortalNavigation(Web web)
{
_web = web;
}
#region CRUD operations
private void EnsureLoaded()
{
if (!_web.IsObjectPropertyInstantiated("AllProperties"))
{
Context.Load(_web, w => w.AllProperties);
Context.ExecuteQuery();
}
}
public void SaveChanges()
{
_web.Update();
Context.ExecuteQuery();
}
#endregion
public void ExcludeFromNavigation(bool useGlobal, Guid item)
{
ModifyNavigationExclude(true, useGlobal, item);
}
public void IncludeInNavigation(bool useGlobal, Guid item)
{
ModifyNavigationExclude(false, useGlobal, item);
}
/// <summary>
/// Controls whether publishing pages in this site will be automatically included in global navigation.
/// </summary>
public bool GlobalIncludePages
{
get
{
return (GlobalIncludeTypes & NodeTypes.Page) == NodeTypes.Page;
}
set
{
GlobalIncludeTypes = !value ? GlobalIncludeTypes & ~NodeTypes.Page : GlobalIncludeTypes | NodeTypes.Page;
}
}
/// <summary>
/// Controls whether publishing pages in this site will be automatically included in current navigation.
/// </summary>
public bool CurrentIncludePages
{
get
{
return (CurrentIncludeTypes & NodeTypes.Page) == NodeTypes.Page;
}
set
{
CurrentIncludeTypes = !value ? CurrentIncludeTypes & ~NodeTypes.Page : CurrentIncludeTypes | NodeTypes.Page;
}
}
/// <summary>
/// Controls the ordering of navigation items owned by this site.
/// </summary>
public OrderingMethod OrderingMethod
{
get
{
return (OrderingMethod)GetProperty("__NavigationOrderingMethod", 2);
}
set
{
SetProperty("__NavigationOrderingMethod", (int)value);
}
}
/// <summary>
/// Controls the property to use when automatically sorting navigation items owned by this site.
/// </summary>
public AutomaticSortingMethod AutomaticSortingMethod
{
get
{
return (AutomaticSortingMethod)GetProperty("__NavigationAutomaticSortingMethod", 0);
}
set
{
SetProperty("__NavigationAutomaticSortingMethod", (int)value);
}
}
/// <summary>
/// Sorts items in a portal navigation in ascending alphanumeric order.
/// </summary>
public bool SortAscending
{
get
{
return GetProperty("__NavigationSortAscending", true);
}
set
{
SetProperty("__NavigationSortAscending", value);
}
}
/// <summary>
/// Controls whether sub-site's of this site will be automatically included in its global navigation.
/// </summary>
public bool GlobalIncludeSubSites
{
get
{
return (GlobalIncludeTypes & NodeTypes.Area) == NodeTypes.Area;
}
set
{
GlobalIncludeTypes = !value ? GlobalIncludeTypes & ~NodeTypes.Area : GlobalIncludeTypes | NodeTypes.Area;
}
}
public bool CurrentIncludeSubSites
{
get
{
return (CurrentIncludeTypes & NodeTypes.Area) == NodeTypes.Area;
}
set
{
CurrentIncludeTypes = !value ? CurrentIncludeTypes & ~NodeTypes.Area : CurrentIncludeTypes | NodeTypes.Area;
}
}
#region Private Properties
private NodeTypes CurrentIncludeTypes
{
get
{
return (NodeTypes)GetProperty("__CurrentNavigationIncludeTypes", 2);
}
set
{
SetProperty("__CurrentNavigationIncludeTypes", (int)value);
}
}
private NodeTypes GlobalIncludeTypes
{
get
{
return (NodeTypes)GetProperty("__GlobalNavigationIncludeTypes", 2);
}
set
{
SetProperty("__GlobalNavigationIncludeTypes", (int)value);
}
}
#endregion
#region Private methods
private void ModifyNavigationExclude(bool exclude, bool useGlobal, Guid item)
{
Dictionary<Guid, bool> excludes;
if (useGlobal)
{
if (_globalNavigationExcludes == null)
InitializeNavigationExcludes(true);
excludes = _globalNavigationExcludes;
}
else
{
if (_currentNavigationExcludes == null)
InitializeNavigationExcludes(false);
excludes = _currentNavigationExcludes;
}
excludes[item] = exclude;
SetProperty(useGlobal ? "__GlobalNavigationExcludes" : "__CurrentNavigationExcludes", ConcatenateGuidStrings(excludes));
}
internal static string ConcatenateGuidStrings(Dictionary<Guid, bool> excludes)
{
var guidStrings = new StringBuilder();
foreach (var exclude in excludes)
{
if (exclude.Value)
guidStrings.Append(exclude.Key + ";");
}
return guidStrings.ToString();
}
private void InitializeNavigationExcludes(bool useGlobal)
{
var excludesValue = GetProperty(useGlobal ? "__GlobalNavigationExcludes" : "__CurrentNavigationExcludes", "");
var excludeGuids = new string[0];
if (!string.IsNullOrEmpty(excludesValue))
excludeGuids = excludesValue.Split(new[] { ';' },StringSplitOptions.RemoveEmptyEntries);
Dictionary<Guid, bool> excludes;
if (useGlobal)
{
_globalNavigationExcludes = new Dictionary<Guid, bool>(excludeGuids.Length);
excludes = _globalNavigationExcludes;
}
else
{
_currentNavigationExcludes = new Dictionary<Guid, bool>(excludeGuids.Length);
excludes = _currentNavigationExcludes;
}
foreach (var g in excludeGuids)
{
if (!string.IsNullOrEmpty(g) && !g.Equals(true.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase))
excludes.Add(new Guid(g), true);
}
}
private T GetProperty<T>(string propName, T defaultPropValue)
{
EnsureLoaded();
if (!_web.AllProperties.FieldValues.ContainsKey(propName))
return defaultPropValue;
return (T)Convert.ChangeType(_web.AllProperties[propName], typeof(T));
}
private void SetProperty<T>(string propName, T propValue)
{
_web.AllProperties[propName] = propValue;
}
#endregion
private ClientRuntimeContext Context
{
get { return _web.Context; }
}
private readonly Web _web;
private Dictionary<Guid, bool> _globalNavigationExcludes;
private Dictionary<Guid, bool> _currentNavigationExcludes;
internal const string PropNameIncludePagesInNavigation = "__IncludePagesInNavigation";
}
#region NodeTypes
/// <summary>
/// Represents the various node types in Microsoft SharePoint Foundation.
/// </summary>
[Flags]
public enum NodeTypes
{
None = 0,
Area = 1,
Page = 2,
List = 4,
ListItem = 8,
PageLayout = 16,
Heading = 32,
AuthoredLinkToPage = 64,
AuthoredLinkToWeb = 128,
AuthoredLinkPlain = 256,
Custom = 512,
Error = 1024,
AuthoredLink = AuthoredLinkPlain | AuthoredLinkToWeb | AuthoredLinkToPage,
Default = AuthoredLink | Heading | Page | Area,
All = Default | Custom | PageLayout | ListItem | List,
}
#endregion
#region AutomaticSortingMethod
/// <summary>
/// Provides options that specify which property to use when automatically sorting navigation items.
/// </summary>
public enum AutomaticSortingMethod
{
Title,
CreatedDate,
LastModifiedDate,
}
#endregion
#region OrderingMethod
/// <summary>
/// Options that specify how navigation items are ordered.
/// </summary>
public enum OrderingMethod
{
Automatic,
ManualWithAutomaticPageSorting,
Manual,
}
#endregion
}

The following example  demonstrates how to print and update navigation settings using ClientPortalNavigation class :

namespace Publishing.Navigation.Client.Samples
{
class PortalNavigation
{
/// <summary>
/// Print Navigation settings
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static void PrintNavigationSettings(string url)
{
using (var clientContext = new ClientContext(url))
{
var navigation = new ClientPortalNavigation(clientContext.Web);
Console.WriteLine("CurrentIncludePages: {0}", navigation.CurrentIncludePages);
Console.WriteLine("GlobalIncludePages: {0}", navigation.GlobalIncludePages);
Console.WriteLine("OrderingMethod: {0}", navigation.OrderingMethod);
Console.WriteLine("AutomaticSortingMethod: {0}", navigation.AutomaticSortingMethod);
Console.WriteLine("SortAscending: {0}", navigation.SortAscending);
Console.WriteLine("GlobalIncludeSubSites: {0}", navigation.GlobalIncludeSubSites);
Console.WriteLine("CurrentIncludeSubSites: {0}", navigation.CurrentIncludeSubSites);
}
}
/// <summary>
/// Update Navigation settings
/// </summary>
/// <param name="url"></param>
public static void UpdateNavigationSettings(string url)
{
using (var clientContext = new ClientContext(url))
{
var navigation = new ClientPortalNavigation(clientContext.Web);
navigation.CurrentIncludePages = true;
navigation.GlobalIncludePages = false;
navigation.SaveChanges();
}
}
}
}

Summary

Since  Publishing API is not supported in Client Object Model of SharePoint 2010, there is no way to manage  the navigation settings for a publishing site the same way as in Server Object Model. But as was demonstrated in this article it could be accomplished via Web Client Object and Web.AllProperties property.

In turn, in SharePoint 2013 Microsoft.SharePoint.Client.Publishing.Navigation namespace was introduced, in particular WebNavigationSettings class for managing the navigation settings for a publishing site.

References

Embedding Video to a SharePoint Library

Overview

We have already discussed how to embed video from YouTube and another Video Providers into SharePoint, in particular it was demonstrated  how to:

In both cases some kind of customization should be applied for List/Library in order to render YouTube player in List Forms and Views. In first case custom fields are used for storing embed code properties and a computed field for rendering player. In the second case, custom fields are used for storing embed code itself and YouTube player is rendered via customized List View and Field.

This time we will discuss another approach that is intended for sharing video in out-of-the box List and Libraries, like Blogs and Discussion Boards.

How to embed Video in SharePoint Blog and Discussion Board

Before we will dive into technical details about implementation let me show some usage scenarios.

In order to embed a video into SharePoint:

  • On YouTube site click the Share button located under the video.
  • Click the Embed button.
  • Copy VideoId value from  src attribute value provided in the expanded box .
    EmbedYT
  • Add reply  for a Discussion and select Insert Tab – Share VideoYouTube Video Button to insert YouTube video on page
    ShareVideo_Ribbon
  • Specify YouTube Video parameters and click OK button to insert YouTube player on page
    ShareVideo_RteDialog
  • Saved Message item (for Discussion List)
    ShareVideo_Message
  • Blogs View
    ShareVideo_Blogs

Solution

The solution consist of:

  • SharePoint Ribbon and Page Component for Sharing Video
  • RTE with the capability to insert video player
  • Render video player on List Forms and Views

SharePoint Ribbon and Page Component for Sharing Video

1. The existing group Ribbon.EditingTools.CPInsert.Media is extended with the buttons for video sharing.

2. Video Sharing Page component is developed for interaction with the Server Ribbon:

Type.registerNamespace('MediaExtensions.Ribbon.RTE');
//Page Component for Sharing Video
MediaExtensions.Ribbon.RTE.VideoPageComponent = function () {
MediaExtensions.Ribbon.RTE.VideoPageComponent.initializeBase(this);
}
MediaExtensions.Ribbon.RTE.VideoPageComponent.prototype = {
mediaCommands: ['VideoGroup', 'ShareVideo', 'ShareVideoMenuOpen', 'ShareVideoMenuClose', 'EmbedVideo', 'InsertVideoWeb'],
registerWithPageManager: function () {
CUI.Page.PageManager.get_instance().addPageComponent(this);
}, getFocusedCommands: function () {
return this.mediaCommands;
}, getGlobalCommands: function () {
return this.mediaCommands;
}, canHandleCommand: function (commandId) {
return true;
}, handleCommand: function (commandId, properties, sequence) {
if (commandId === 'ShareVideo') {
this.invokeVideoCommand(commandId);
return true;
}
if (commandId === 'InsertVideoWeb') {
this.invokeVideoCommand(commandId);
return true;
}
return true;
}, isFocusable: function () {
return true
}, receiveFocus: function () {
return true;
}, yieldFocus: function () {
return true;
}, invokeVideoCommand: function (commandId) {
RTE.SnapshotManager.takeSnapshot();
var range = RTE.Cursor.get_range();
if (!range.get_isEditable()) {
return;
}
var dlg = new MediaExtensions.Ribbon.RTE.ShareVideoDialog();
dlg.show(commandId);
RTE.SnapshotManager.takeSnapshot();
}
}
MediaExtensions.Ribbon.RTE.VideoPageComponent.registerClass('MediaExtensions.Ribbon.RTE.VideoPageComponent', CUI.Page.PageComponent);
//Register Page Component for Sharing Video
NotifyScriptLoadedAndExecuteWaitingJobs("sharevideoribbon.js");
function ShareVideoInit() {
var currentPageManager = SP.Ribbon.PageManager.get_instance();
currentPageManager.addPageComponent(new MediaExtensions.Ribbon.RTE.VideoPageComponent());
}

view raw
sharevideoribbon.js
hosted with ❤ by GitHub

RTE with the capability to insert video player

By default  YouTube video player is rendered as IFrame element. Since in SharePoint 2010 it is not allowed to store iframe content into Publishing HTML field the alternative solution is proposed here:

1. User provides all  the properties for video in RTE dialog
ShareVideo_RteDialogOnly

2. Video player tag is inserted into SharePoint Rich Text Editor from RTE dialog:

<!– Video player tag is inserted into SharePoint Rich Text Editor from RTE Dialog –>
<!– When the List Form or View page is rendered, this tag is replaced with <iframe> (and video player) via YouTube IFrame API –>
<div id="ytplayer">
<span style="display: none;">Z6-iUNfJsJw;320;215</span> <!– player info in the following format VideoId;Width:Height –>
</div>

view raw
RTE_Video.html
hosted with ❤ by GitHub

3. When the List Form or View page is rendered, YouTube player is created and video is loaded.

Render video player on List Forms and Views

The IFrame player API is intended for embedding a YouTube video player on a website and control the player using JavaScript. The IFrame API posts content to an <iframe> tag on a page.

The code demonstrates how to create an embedded player that will load a video

var ytplayersinfolist = [];
//This function creates an <iframe> (and YouTube player)
//after the API code downloads
function onYouTubeIframeAPIReady() {
if (typeof window.playerInfoList === 'undefined')
return;
for (var i = 0; i < window.playerInfoList.length; i++) {
createYTPlayer(window.playerInfoList[i]);
}
}
// This function loads the IFrame Player API code asynchronously
function registerYTIFrameApi() {
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api&quot;;
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
function initYTPlayer(playerInfo) {
return new YT.Player(playerInfo.id, {
height: playerInfo.height,
width: playerInfo.width,
videoId: playerInfo.videoId
});
}

view raw
ytplayer.js
hosted with ❤ by GitHub

References

Understanding the List Dialogs setting in SharePoint 2010

Overview

With the introduction of SharePoint 2010 two modes became available  when working with Libraries/List forms   from user experience perspective. Dialogs option in List Settings indicates whether to navigate the full page (Yes) or that the list form page is launched in a modal dialog (No)

XLV_DialogsOption

Figure 1. List settings 

Pay attention to the last part of the description that says:

Note: Dialogs may not be available on all forms.

Interesting, isn’t it, but what exactly does it mean? In order to find out we need to investigate how List Dialogs settings is handled in SharePoint 2010.  So, Let’s get started.

How it works

Depending on which option is selected link events for List/Library forms (New|Edit|View) are handled differently. When the “Launch forms in a dialog” is selected form pages are opened in modal dialog boxes.

How it is implemented

Through SharePoint Object Model the specified List settings for enabling/disabling modal dialog boxes is accessible via  SPList.NavigateForFormsPages property.   As it turns out, the value of  this property is passed to the XSL transform via  XsltListViewWebPart.ModifyXsltArgumentList method:

/// <summary>
/// Partial implementation of XsltListViewWebPart.ModifyXsltArgumentList method
/// The remaining code is omitted for clarity here
/// </summary>
protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
{
argList.AddParameter("NavigateForFormsPages", string.Empty, this.SPList.NavigateForFormsPages ? (object) BaseXsltListWebPart.XslOneString : (object) BaseXsltListWebPart.XslZeroString);
}

The XSLT global parameter NavigateForFormsPages  declared in main.xsl file is utilized in CTXGeneration template:

<!–
Partial implementation of CTXGeneration template
(The remaining code is omitted for clarity here)
–>
<xsl:template name="CTXGeneration" ddwrt:ghost="always">
<script type="text/javascript">
ctx = new ContextInfo();
<!–Save List Dialogs parameter in NavigateForFormsPages property of ContextInfo structure–>
ctx.NavigateForFormsPages = <xsl:choose>
<xsl:when test="$NavigateForFormsPages='1'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>;
ctx<xsl:value-of select="$ViewCounter"/> = ctx;
g_ctxDict['ctx<xsl:value-of select="$ViewCounter"/>'] = ctx;
</script>
</xsl:template>

view raw
CTXGeneration.xsl
hosted with ❤ by GitHub

This template is intended for rendering  extra information about List settings on the client side  (ContextInfo structure). It is invoked every time when the List View is rendered on page. Pay attentions that List Dialogs setting that corresponds to NavigateForFormsPages property of ContextInfo structure.

And finally the event handler of the List form links is as follows:

//Handler for List form links in SharePoint 2010
// (from CORE.js)
function _EditLink2(elm, ctxNum)
{ULSrLq:;
var fn=function()
{ULSrLq:;
var url=GetGotoLinkUrl(elm);
if (url==null)
return;
var ctxT=window["ctx"+ctxNum];
if (ctxT !=null && ctxT.clvp !=null)
{
var clvp=ctxT.clvp;
if (FV4UI() && !ctxT.NavigateForFormsPages) //check if Dialogs option (NavigateForFormsPages property of ContextInfo) is set to 'No'
{
PreventDefaultNavigation();
clvp.ShowPopup(url); //open form in modal dialog boxes
return false;
}
}
GoToLink(elm); //navigate using href attribute of link
}
EnsureScript("inplview", typeof(inplview), fn);
}

view raw
_EditLink2.js
hosted with ❤ by GitHub

How the List Form link should be handled is determined by the NavigateForFormsPages parameter in the specified function, i.e. :

  • by navigating to a full page using href attribute of link
  • display form in modal dialog box

List Dialogs setting in ListViewWebPart

Now it is time to return to the question about why this settings is not available on all the forms. For answering on that question let me step back and explain a little bit about rendering a List View. Up until now we were talking only about rendering a List View via XsltListViewWebPart  that  handles view rendering for default lists, such as document libraries and announcements. But what about ListViewWebPart that is used for rendering specific List Views like  Calendar, Gantt or Chart Views.

The point is that this parameter (List Dialogs) does not take into account when a List View is rendered via ListViewWebPart. And the message “Dialogs that may not be available on all forms” concerns exactly this situation.

Then the question arise, how do we deal with the case where we need to open List Forms as a full pages for ListViewWebPart?  Let’s consider a real-world example when we need a Calendar forms to be opened as a full pages.

There are several ways how to achieve it, but we will consider only one, one of the simplest. The idea is to specify explicitly NavigateForFormsPages parameter for ContextInfo structure.

Steps:

1. Add CEWP into Calendar View page (Calendar.aspx)

2.  Place the following JavaScript code into CEWP

//Additional properties for ContextInfo () to be utilized in Calendar List View
//Specify actions navigate to the full page
var ContextInfo = (function() {
var ContextInfo_Orig = ContextInfo;
return function() {
ContextInfo_Orig();
this.NavigateForFormsPages = true; // Set navigate to the full page for list forms links
}
})();

That’s all, after that the Calendar forms will be opened as a full pages.

Provisioning Content Types in SharePoint 2010

Overview

Provisioning in SharePoint has always been not the simplest part, especially if it concerns the Content Type provision.

It is known that in SharePoint 2010, you can declare content types in a feature or you can create them programmatically. Programmatically created content types are more flexible and provide more granular control over how and when updates to the content type are made during upgrades. In contrast, you cannot change the declarative definition of a content type that was created by using Collaborative Application Markup Language (CAML) after the content type is activated—instead, you must update the content type programmatically.

Given the Pros and Cons, it is up to you to decide which way is better suits your needs.

How to create a field based on the specified schema

There is one method that allows to create a field based on the specified schema, see AddFieldAsXml method for details . It means that you could declare a field once (CAML), but to provision it either via feature activation or programmatically. As for me, I find this technique very useful for curtain cases. But what about content types?

For content type there is no such capability that allows to create a content type based on the specified schema. And the goal of this post is to demonstrate how it could be achieved.

How to create a content type based on the specified schema

Listing 1.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema

namespace Deployment.ContentType
{
/// <summary>
/// Content Type
/// </summary>
public static class SPContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// An instance of the new content type.
/// </returns>
/// <param name="contentTypes">Content Type collection</param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static SPContentType AddContentTypeAsXml(this SPContentTypeCollection contentTypes, string schemaXml)
{
SPContentType contentType;
using (var xrdr = new XmlTextReader(new StringReader(schemaXml)))
{
xrdr.ProhibitDtd = true;
contentType = contentTypes.CreateContentType();
LoadXmlInternal(contentType, xrdr);
contentTypes.Add(contentType);
}
return contentType;
}
/// <summary>
/// Create content type
/// </summary>
/// <param name="contentTypes"></param>
/// <returns></returns>
private static SPContentType CreateContentType(this SPContentTypeCollection contentTypes)
{
var constructor = (typeof(SPContentType)).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var contentType = (SPContentType)constructor.Invoke(new object[0]);
contentType.SetWeb(contentTypes.GetWeb());
return contentType;
}
/// <summary>
/// Load schema for content type
/// </summary>
/// <param name="contentType"></param>
/// <param name="xmlReader"></param>
private static void LoadXmlInternal(SPContentType contentType, XmlReader xmlReader)
{
var loadMethod = contentType.GetType().GetMethod("Load",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new[] { typeof(XmlReader) },
null);
loadMethod.Invoke(contentType, new object[] { xmlReader });
}
private static SPWeb GetWeb(this SPContentTypeCollection contentTypes)
{
var webProp = typeof(SPContentTypeCollection).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
return (SPWeb)webProp.GetValue(contentTypes, null);
}
private static void SetWeb(this SPContentType contentType,SPWeb web)
{
var webProp = typeof(SPContentType).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
webProp.SetValue(contentType, web, null);
}
}
}

Listing 2.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema via Managed Client Object Model

namespace Deployment.Client
{
/// <summary>
/// Creates a content type based on the specified schema (CSOM)
/// </summary>
public static class ContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// A newly created client object of a content type.
/// </returns>
/// <param name="clientContext">Client Context </param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static ContentType AddContentTypeAsXml(ClientContext clientContext, string schemaXml)
{
ContentType contentType;
using (var xmlTextReader = new XmlTextReader(new StringReader(schemaXml)))
{
xmlTextReader.ProhibitDtd = true;
contentType = Load(clientContext, clientContext.Web.ContentTypes, xmlTextReader);
}
return contentType;
}
private static ContentType Load(ClientContext clientContext, ContentTypeCollection cts, XmlReader xrdr)
{
var contentTypeCreation = new ContentTypeCreationInformation { };
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Name"))
contentTypeCreation.Name = xrdr.Value;
if (xrdr.MoveToAttribute("Group"))
contentTypeCreation.Group = xrdr.Value;
if (xrdr.MoveToAttribute("Description"))
contentTypeCreation.Description = xrdr.Value;
contentTypeCreation.ParentContentType = GetParentContentType(clientContext, xrdr.GetAttribute("ID"));
var contentType = cts.Add(contentTypeCreation);
//Additional properties
if (xrdr.MoveToAttribute("Hidden"))
contentType.Hidden = xrdr.Value == "TRUE";
bool flagReadOnly = xrdr.MoveToAttribute("ReadOnly");
contentType.ReadOnly = flagReadOnly && xrdr.Value == "TRUE";
xrdr.Read();
while (xrdr.LocalName != "ContentType")
{
switch (xrdr.LocalName)
{
case "FieldRefs":
LoadFieldLinks(clientContext, contentType, xrdr);
break;
}
if (!xrdr.Read())
break;
}
return contentType;
}
private static void LoadFieldLinks(ClientContext clientContext, ContentType contentType, XmlReader xrdr)
{
xrdr.Read();
while (xrdr.LocalName == "FieldRef" || xrdr.LocalName == "RemoveFieldRef")
{
if (xrdr.LocalName == "RemoveFieldRef")
{
xrdr.Read();
}
else
{
LoadFieldLink(clientContext, contentType.FieldLinks, xrdr);
contentType.Update(false);
clientContext.ExecuteQuery();
}
}
}
internal static FieldLink LoadFieldLink(ClientContext clientContext, FieldLinkCollection fieldLinks, XmlReader xrdr)
{
var linkCreationInfo = new FieldLinkCreationInformation();
var hostField = GetField(clientContext, xrdr);
linkCreationInfo.Field = hostField;
var fieldLink = fieldLinks.Add(linkCreationInfo);
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Required"))
{
fieldLink.Required = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
if (xrdr.MoveToAttribute("Hidden"))
{
fieldLink.Hidden = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
xrdr.Read();
if (xrdr.LocalName == "Default")
{
xrdr.Read();
}
return fieldLink;
}
/// <summary>
///
/// </summary>
/// <param name="clientContext"></param>
/// <param name="xrdr"></param>
/// <returns></returns>
private static Field GetField(ClientContext clientContext, XmlReader xrdr)
{
var field = clientContext.Web.AvailableFields.GetById(new Guid(xrdr["ID"]));
clientContext.Load(field);
clientContext.ExecuteQuery();
return field;
}
private static ContentType GetParentContentType(ClientContext clientContext, string id)
{
var ctId = new SPContentTypeId(id);
var parentCtId = ctId.Parent.ToString();
return GetContentTypeById(clientContext, parentCtId);
}
public static ContentType GetContentTypeById(ClientContext clientContext, string ctId)
{
var ct = clientContext.Web.AvailableContentTypes.GetById(ctId);
clientContext.Load(ct);
clientContext.ExecuteQuery();
return ct;
}
public static ContentType GetContentTypeByName(ClientContext clientContext, string name)
{
var cts = clientContext.Web.ContentTypes;
clientContext.Load(cts);
clientContext.ExecuteQuery();
foreach (var ct in cts)
{
if (ct.Name == name)
return ct;
}
return null;
}
}
}

The following list summarizes the limitations for Client OM approach:

  • Not the all content type attributes could be specified via Client OM
  • In particular it is not supported to specify Content Type Id, only parent Content Type could be specified

References