Wednesday, 5 August 2015

SharePoint Client Object Model with JavaScript

SharePoint Client Object Model

In Part 1 of this article I took an in-depth look at the SharePoint Client Object Model; how it works and how to form queries and work with objects. Now I'll take a practical look at using the Client OM with examples using .NET Managed code, using WPF, JavaScript used in a Application page, and finally with Silverlight in a SharePoint webpart.

Setup

To facilitate the examples the first thing was to create a SharePoint project that creates a simple Contact list and populates it with a few Contacts to begin with. This project is included in the downloads for this article. For the Silverlight example I created a Silverlight project and added the output to a Document Library on the site.
The demo apps may not be the best designed, but they are functional and effectively demonstrate using the Client Object Model in a somewhat pratical manner.

Using Client OM with .NET Managed Code

Image1.png This application first gets a ListItemCollection containing all the Contacts in the demo list.
public static ListItemCollection GetList()
{
    using(ClientContext ctx = new ClientContext(SITE))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);
        CamlQuery query = new CamlQuery();
        query.ViewXml = "<View>" +
                            "<Query>" +
                                "<OrderBy>" +
                                    "<FieldRef Name='Title'/>" +
                                    "<FieldRef Name='FirstName'/>" +
                                "</OrderBy>" +
                            "</Query>" +
                            "<ViewFields>" +
                                "<FieldRef Name='ID'/>" +
                                "<FieldRef Name='Title'/>" +
                                "<FieldRef Name='FirstName'/>" +
                                "<FieldRef Name='WorkAddress'/>" +
                                "<FieldRef Name='WorkCity'/>" +
                                "<FieldRef Name='WorkState'/>" +
                                "<FieldRef Name='WorkZip'/>" +
                            "</ViewFields>" +
                        "</View>";

        ListItemCollection listItems = list.GetItems(query);
        ctx.Load(listItems);
        ctx.ExecuteQuery();

        return listItems;
    }
}
Since this article is more about usage of the Client Object Model I won't delve into the XAML too much. Just to show, however, the ListBox is bound to the ListItemCollection returned from the above code and the TextBlocks in the DataTemplate are bound to the indexer property of the ListItem for the specified FieldValue
<ListBox x:Name="Contacts" ItemsSource="{Binding}" Width="225" Height="300" SelectionChanged="Contacts_SelectionChanged">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=[FirstName]}" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Path=[Title]}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Part 1 of this article covers the details for retrieving ListItems, ClientContext, CamlQuery and other objects and methods being used so I won't go into them here.

Updating ListItems

Image2.png Clicking on the Edit button in the application causes the edit fields to be displayed, once again I'm not focusing on the WPF, you can explore the download files for more details on that aspect. After clicking the Save button it's simply a matter of finding the ListItem to be updated, update the FieldValues, then commit the changes.
public static void Update(int id, string firstName, string lastName, string address,
    string city, string state, string postalCode)
{
    using(ClientContext ctx = new ClientContext(SITE))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);

        // Get the item being updated
        ListItem item = list.GetItemById(id);

        // Update the FieldValues
        item["Title"] = lastName;
        item["FirstName"] = firstName;
        item["WorkAddress"] = address;
        item["WorkCity"] = city;
        item["WorkState"] = state;
        item["WorkZip"] = postalCode;

        // Must make sure to call this
        item.Update();

        // Commit the change
        ctx.ExecuteQuery();
    }
}
One thing to note here the lack of any calls to Load or LoadQuery as in the examples in Part 1. Since nothing is being returned in this case there is no need to use these methods. Think of it as the equivalent of the ADO.NET ExecuteNonQuery method. Another thing to note is although it may seem tempting to cache the ListItem and update it directly, without the GetItemById call, this won't work. The item must be retrieved within the context of the ClientContext object, similar to how the Entity Framework functions for those familiar with it. It is not recommended to cache the ClientContext since it does hold resources and may cause degradation of your application. Best to follow the database connection model; open late, use and close.

Adding ListItems

Image3.png Adding a new ListItem to the List is almost as easy as updating. One of the main differences though is the ListItemCreationInformation object.
public static void AddContact(string firstName, string lastName, string address,
    string city, string state, string postalCode)
{
    using(ClientContext ctx = new ClientContext(SITE))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);

        // Create the Listitem
        // ListItemCreationInformation can bee null for root folder
        ListItemCreationInformation createInfo = null;

        // Or for adding item to a folder
        //ListItemCreationInformation createInfo = new ListItemCreationInformation();
        //createInfo.FolderUrl = "site/lists/listname/folder";

        ListItem item = list.AddItem(createInfo);

        // Set the FieldValues
        item["Title"] = lastName;
        item["FirstName"] = firstName;
        item["WorkAddress"] = address;
        item["WorkCity"] = city;
        item["WorkState"] = state;
        item["WorkZip"] = postalCode;

        // Save changes
        item.Update();

        // Commit
        ctx.ExecuteQuery();
    }
}
Just as with the methods demonstrated in Part 1 when the ExecuteQuery method is invoked the Client OM API will create an XML string and POST it to the client.svc Web Service. As you can see below this is very similar to the "get" methods but you'll notice the Method element specifying which method is to be called and the Parameter elements passing the type and value.
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1
X-RequestDigest: 0xE8312251E4B[truncated for space]0000
Content-Type: text/xml
X-RequestForceAuthentication: true
Host: mySite
Content-Length: 2461
Expect: 100-continue

<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" 
        ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
  <Actions>
    <ObjectPath Id="25" ObjectPathId="24" />
    <ObjectPath Id="27" ObjectPathId="26" />
    <ObjectPath Id="29" ObjectPathId="28" />
    <ObjectPath Id="31" ObjectPathId="30" />
    <ObjectIdentityQuery Id="32" ObjectPathId="30" />
    <ObjectPath Id="34" ObjectPathId="33" />
    <Method Name="SetFieldValue" Id="35" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">Title</Parameter>
        <Parameter Type="String">Betic</Parameter>
      </Parameters>
    </Method>
    <Method Name="SetFieldValue" Id="36" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">FirstName</Parameter>
        <Parameter Type="String">Alpha</Parameter>
      </Parameters>
    </Method>
    <Method Name="SetFieldValue" Id="37" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">WorkAddress</Parameter>
        <Parameter Type="String">Main street </Parameter>
      </Parameters>
    </Method>
    <Method Name="SetFieldValue" Id="38" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">WorkCity</Parameter>
        <Parameter Type="String">Anytown</Parameter>
      </Parameters>
    </Method>
    <Method Name="SetFieldValue" Id="39" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">WorkState</Parameter>
        <Parameter Type="String">PA</Parameter>
      </Parameters>
    </Method>
    <Method Name="SetFieldValue" Id="40" ObjectPathId="33">
      <Parameters>
        <Parameter Type="String">WorkZip</Parameter>
        <Parameter Type="String">12345</Parameter>
      </Parameters>
    </Method>
    <Method Name="Update" Id="41" ObjectPathId="33" />
    <Query Id="42" ObjectPathId="33">
      <Query SelectAllProperties="false">
        <Properties>
          <Property Name="Title" ScalarProperty="true" />
          <Property Name="FirstName" ScalarProperty="true" />
          <Property Name="WorkAddress" ScalarProperty="true" />
          <Property Name="WorkCity" ScalarProperty="true" />
          <Property Name="WorkState" ScalarProperty="true" />
          <Property Name="WorkZip" ScalarProperty="true" />
        </Properties>
      </Query>
    </Query>
  </Actions>
  <ObjectPaths>
    <StaticProperty Id="24" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
    <Property Id="26" ParentId="24" Name="Web" />
    <Property Id="28" ParentId="26" Name="Lists" />
    <Method Id="30" ParentId="28" Name="GetByTitle">
      <Parameters>
        <Parameter Type="String">SPUG Demo Contacts</Parameter>
      </Parameters>
    </Method>
    <Method Id="33" ParentId="30" Name="GetItemById">
      <Parameters>
        <Parameter Type="Int32">4</Parameter>
      </Parameters>
    </Method>
  </ObjectPaths>
</Request>
Although nothing is returned, such as with Load or LoadQuery, there is a response from the Client.svc Web Service call. As you see below the response simply contains the object that was updated, however not the _ObjectVersion_ property which incremented with the version of the object returned.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json
Server: Microsoft-IIS/7.5
SPRequestGuid: 472b1d44-cc1e-4295-a7b0-c38cf3b68a62
Set-Cookie: WSS_KeepSessionAuthenticated={4e4cffb3-203e-4857-8f5a-90a4b18789a4}; path=/
X-SharePointHealthScore: 0
X-Content-Type-Options: nosniff
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 14.0.0.6029
Content-Length: 672

[
{
"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.6106.5001","ErrorInfo":null
},25,{
"IsNull":false
},27,{
"IsNull":false
},29,{
"IsNull":false
},31,{
"IsNull":false
},32,{
"_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:6bb[truncated for space]3964:list:9bf3[truncated for space]d045"
},34,{
"IsNull":false
},42,{
"_ObjectType_":"SP.ListItem",
"_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:6bb[truncated for space]964:list:9bf[truncated for space]d045:item:4,1",
"_ObjectVersion_":"2",
"Title":"Betic",
"FirstName":"Alpha",
"WorkAddress":"Main street ",
"WorkCity":"Anytown",
"WorkState":"PA",
"WorkZip":"12345"
}
]

Deleting ListItems

Deleting a ListItem is the simplest of the operations. After obtaining the ListItem, call it's DeleteObject method and commit the changes with an ExecuteQuery.
public static void Delete(int id)
{
    using(ClientContext ctx = new ClientContext(SITE))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);

        // Get the item being deleted
        ListItem item = list.GetItemById(id);
        item.DeleteObject();

        // Commit
        ctx.ExecuteQuery();
    }
}
POST http://mySite/_vti_bin/client.svc/ProcessQuery HTTP/1.1
X-RequestDigest: 0x2BD0E4[truncated for space]0000
Content-Type: text/xml
X-RequestForceAuthentication: true
Host: mySite
Content-Length: 994
Expect: 100-continue

<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" 
  ApplicationName=".NET Library" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009">
  <Actions>
    <ObjectPath Id="54" ObjectPathId="53" />
    <ObjectPath Id="56" ObjectPathId="55" />
    <ObjectPath Id="58" ObjectPathId="57" />
    <ObjectPath Id="60" ObjectPathId="59" />
    <ObjectIdentityQuery Id="61" ObjectPathId="59" />
    <ObjectPath Id="63" ObjectPathId="62" />
    <Method Name="DeleteObject" Id="64" ObjectPathId="62" />
  </Actions>
  <ObjectPaths>
    <StaticProperty Id="53" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
    <Property Id="55" ParentId="53" Name="Web" />
    <Property Id="57" ParentId="55" Name="Lists" />
    <Method Id="59" ParentId="57" Name="GetByTitle">
      <Parameters>
        <Parameter Type="String">SPUG Demo Contacts</Parameter>
      </Parameters>
    </Method>
    <Method Id="62" ParentId="59" Name="GetItemById">
      <Parameters>
        <Parameter Type="Int32">6</Parameter>
      </Parameters>
    </Method>
  </ObjectPaths>
</Request>
Once again, although nothing is returned a response is sent from the Web Service. This time though there is no object since it has been deleted.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json
Server: Microsoft-IIS/7.5
SPRequestGuid: 28594025-6d96-4798-ba72-06a6b81f0c3e
Set-Cookie: WSS_KeepSessionAuthenticated={4e4cffb3-203e-4857-8f5a-90a4b18789a4}; path=/
X-SharePointHealthScore: 0
X-Content-Type-Options: nosniff
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 14.0.0.6029
Content-Length: 343

[
{
"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.6106.5001","ErrorInfo":null
},54,{
"IsNull":false
},56,{
"IsNull":false
},58,{
"IsNull":false
},60,{
"IsNull":false
},61,{
"_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:6bb[truncated for space]964:list:9bf[truncated for space]d045"
},63,{
"IsNull":false
}
]

Client OM with JavaScript

Working with the Client OM in JavaScript is quite similar to Managed Code. The objects are essentially the same, except the method names conform to JavaScript coding standards.
Image4.png One major difference, however, is the Client OM implementation in JavaScript uses asynchronous methods where the Managed Code implementation uses synchronous methods. In Managed Code though this doesn't prevent you from using Asynchronous methods when implementing your code as the Silverlight example will show.
/// Retrieve all contacts in the list
function loadContactsList()
{
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    var list = web.get_lists().getByTitle(LIST_NAME);

    var camlQuery = new SP.CamlQuery();
    var queryXml = "<View>" +
                        "<Query>" +
                            "<OrderBy>" +
                                "<FieldRef Name='Title'/>" +
                                "<FieldRef Name='FirstName'/>" +
                            "</OrderBy>" +
                        "</Query>" +
// Can specify fields here
//                        "<ViewFields>" +
//                            "<FieldRef Name='ID'/>" +
//                            "<FieldRef Name='Title'/>" +
//                            "<FieldRef Name='FirstName'/>" +
//                        "</ViewFields>" +
                    "</View>";

    camlQuery.set_viewXml(queryXml);
    this.listItems = list.getItems(camlQuery);
    // Can use this or LoadQuery
    //ctx.load(this.listItems);
    // Fields can be included here rather than in CAML
    ctx.load(this.listItems, 'Include(ID, Title, FirstName)');

    ctx.executeQueryAsync(
            Function.createDelegate(this, onLoadContactsSuccess),
            Function.createDelegate(this, onFail));
}
As you can see in the above code querying for the ListItems is the same as in the WPF example; get a ClientContext, get the objects necessary, form a CamlQuery and call executeQueryAsync. As you'll notice, getting the ClientContext with JavaScript is slightly different. Just as with the Managed Code implementation you can construct a ClientContext object by passing a URL. However, in JavaScript this is a relative URL, rather than a full URL, since the code is executing with a SharePoint site already.
var ctx = SP.ClientContext("serverRelativeUrl");
The get_current method is particular to the JavaScript Client OM implementation and, as you can see below, constructs the ClientContext object from the get_webServerRelativeUrl method which has been filled in from other SharePoint JavaScript code.
SP.ClientContext.get_current = function() {ULS5Vl:;
    if (!SP.ClientContext.$1S_1) {
        SP.ClientContext.$1S_1 = new SP.ClientContext(SP.PageContextInfo.get_webServerRelativeUrl());
    }
    return SP.ClientContext.$1S_1;
}
After executeQueryAsync has been called successfully the delegate specified in the first parameter, onLoadContactsSuccess in this case, will be called.
/// Function to be called after executeQueryAsync
/// in loadContactsList has been successful 
function onLoadContactsSuccess(sender, args)
{
    $("#contactList").empty();

    if(this.listItems.get_count() == 0)
    {
        $("#contactList").append("<span>No contacts found</span>");
    }
    else
    {
        // Get Enumerator and iterate through it
        var listEnumerator = this.listItems.getEnumerator();
        while (listEnumerator.moveNext())
        {
            var item = listEnumerator.get_current();

            // Properties on FieldValues will match
            // Fields returned by query
            var title = item.get_fieldValues().Title;
            var firstName = item.get_fieldValues().FirstName;
            var id = item.get_fieldValues().ID;

            // Create the html element
            var contact = "<span  önclick='displayContactDetails(" + id + ")'>" + firstName + " " + title + "</span>";

            // Append to "list"
            $("#contactList").append(contact + "<br/>");
        }
    }
}
Getting an individual ListItem is again almost the same as previous examples. The load method, however, is a little different. Since JavaScript doesn't support Lambda expression you can pass an array of strings that specify the columns to be returned in the object.
function displayContactDetails(id)
{
    selectedItemId = id;
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    var list = web.get_lists().getByTitle(LIST_NAME);

    listItem = list.getItemById(id);

    // Returns all fields
    //ctx.load(this.listItem);

    // Returns only fields specified
    ctx.load(this.listItem, "Title", "FirstName", "WorkAddress", "WorkCity", "WorkState", "WorkZip");

    ctx.executeQueryAsync(
            Function.createDelegate(this, onContactDetailsSuccess),
            Function.createDelegate(this, onFail));
}

Updating ListItem

No real surprises here. The difference with JavaScript is using the set_item method rather than using the indexer in Managed Code.
function saveContact()
{
    $(".field").each(function ()
    {
        var id = $(this).attr("ID");
        var value = $(this).next("input").val();

        listItem.set_item(id,value);
    });
    
    listItem.update();

    var ctx = SP.ClientContext.get_current();
    
    ctx.executeQueryAsync(
            Function.createDelegate(this, onSaveContactSuccess),
            Function.createDelegate(this, onFail));
}

Adding ListItems

By now you should be getting the hang of this.
function onAdd()
{
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    var list = web.get_lists().getByTitle(LIST_NAME);

    // can be null when adding item to root folder
    var createInfo = null;
    //var createInfo = new SP.ListItemCreationInformation();
    var item = list.addItem(createInfo);

    $(".field").each(function ()
    {
        var id = $(this).attr("ID");
        var value = $(this).val();

        item.set_item(id, value);
    });

    item.update();

    ctx.executeQueryAsync(
            Function.createDelegate(this, onAddContactSuccess),
            Function.createDelegate(this, onFail));
}

Deleting ListItem

Just to complete the example
function onDelete()
{
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    var list = web.get_lists().getByTitle(LIST_NAME);

    var listItem = list.getItemById(selectedItemId);
    listItem.deleteObject();

    ctx.executeQueryAsync(
            Function.createDelegate(this, onDeleteSuccess),
            Function.createDelegate(this, onFail));
}

Client OM with Silverlight

By now you are probably bored working with ListItems using the Client OM. Since there isn't much difference with the previous methods I'll use Silverlight to focus on other things that can be done with the Client OM; in Managed code, JavaScript or Silverlight. Specifically, with Silverlight I'll demonstrate working with WebParts on a page.
Image5.png Upon loading, this Silverlight WebPart will read the WebParts on the designated page, default.aspx in this example, and display the titles in the ListBox.
private void LoadWebParts()
{
    SelectedItem = null;
    wpList.Items.Clear();
    using(ClientContext ctx = new ClientContext(SITE))
    {
        try
        {
            // Get the default page for the site
            File file = ctx.Web.GetFileByServerRelativeUrl(PAGE);

            // Get the WebPart manager to locate all the WebParts
            LimitedWebPartManager wpm = file.GetLimitedWebPartManager(PersonalizationScope.Shared);

            // Load all WebPart definitions found on the page
            WPDefinitions = ctx.LoadQuery(wpm.WebParts.Include(w => w.Id, w => w.WebPart));

            ctx.ExecuteQueryAsync(OnLoadSucceeded, OnFail);
        }
        catch(System.Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}
As you can see, like all of the other examples, you first get a CLientContext to work with, using a server relative URL since the page is operating within a SharePoint site already. Next is to get the File to work with by using the well named method GetFileByServerRelativeUrl. After this is getting the LimitedWebPartManager to obtain a collection of WebPartDefinitions from via the LoadQuery method. As with other examples I'm using a Lambda expression to limit the results to only what is necessary, the ID and WebPart properties in this case. At this point using the Client OM with Silverlight will diverge from the other environments. Since this code is being run from the main thread of the application you'll need to use the asynchronous method, ExecuteQueryAsync to make the query to prevent blocking the UI thread. This method takes delegates to ClientRequestSucceededEventHandler and ClientRequestFailedEventHandler that will be called when the method succeeds or fails.
private void OnLoadSucceeded(object sender, ClientRequestSucceededEventArgs e)
{
    // Create delegate to update ListBox in UI thread
    Action updateList = () =>
        {
            if(WPDefinitions.Count() != 0)
            {
                foreach(WebPartDefinition def in WPDefinitions)
                {
                    wpList.Items.Add(new ListItemHelper() { ID = def.Id, Title = def.WebPart.Title });
                }
            }
        };

    this.Dispatcher.BeginInvoke(updateList);
}
In this method I create an anonymous delegate to pass to the BeginInvoke method that will iterate through the WebPartDefinition collection populated by the LoadQuery call.

Adding WebPart

Using the Client OM to add WebParts to a page is relatively straight forward.
private void OnUpdate(object sender, System.Windows.RoutedEventArgs e)
{
    // Get the title and zone that may have been updated
    SelectedItem.Title = wpTitle.Text;
    SelectedItem.Zone = ((ComboBoxItem)wpZone.SelectedItem).Content.ToString();

    // Start a worker thread to do the processing
    ThreadPool.QueueUserWorkItem(new WaitCallback(CSOMHelper.UpdateTitle), SelectedItem);

    // Update the controls
    wpList.Items[SelectedItem.ItemIndex] = wpTitle.Text;
    wpTitle.Text = "";
}
In this example I'll use a slightly different method to call the Client OM methods. Once again since this code is being executed in the main UI thread you can't use any blocking methods. In this case I use a worker thread to do the processing since there isn't anything that needs to be updated on the UI from the Client OM code.
public static void UpdateTitle(Object state)
{
    ListItemHelper item = (ListItemHelper)state;

    using(ClientContext ctx = new ClientContext(SITE))
    {
        // Get the default page for the site
        File file = ctx.Web.GetFileByServerRelativeUrl(PAGE);

        // Get the WebPart manager to locate all the WebParts
        LimitedWebPartManager wpm = file.GetLimitedWebPartManager(PersonalizationScope.Shared);

        // Load all WebPart definitions found on the page
        IEnumerable<WebPartDefinition> definitions = ctx.LoadQuery(wpm.WebParts.Include(w => w.Id, w => w.WebPart));

        // Use the synchronous method here since the 
        // method is being called in separate thread
        ctx.ExecuteQuery();

        // Find the definition for the webpart to be updated
        WebPartDefinition def = definitions.FirstOrDefault(d => d.Id == item.ID);
        if(def != null)
        {
            def.WebPart.Title = item.Title;
            def.MoveWebPartTo(item.Zone.ToLower(), 0);
            // Save the changes
            def.SaveWebPartChanges();
            // Commit
            ctx.ExecuteQuery();
        }
    }
}
This method is just like the load method, get a ClientContext and collection of WebPartDefinitions. After finding the WebPart being updated you set the Title property and call the handy and well named method MoveWebPartTo to move the WebPart to a new WebPartZone. Again, the well named method SaveWebPartChanges saves the updates that have been made and the changes are committed by the call to ExecuteQuery. Since this is being executed within the worker thread you can use the synchronous method. This shows that both synchronous and asynchronous methods are available in the Silverlight implementation of the Client OM, unlike Managed Code where only synchronous methods are available and JavaScript where only asynchronous methods are available.

Deleting WebParts

Deleting WebParts is just as easy. Follow the same steps as above and call the DeleteWebPart method.
// Find the definition for the webpart to be deleted
WebPartDefinition def = definitions.FirstOrDefault(d => d.Id == item.ID);
if(def != null)
{
    def.DeleteWebPart();
    // Save the changes
    def.SaveWebPartChanges();
    // Commit
    ctx.ExecuteQuery();
}

UserAction Menus

For the final example I'll demonstrate working with the UserActions menu to add items to the EditControlBlock menu of a list and the Site Actions menu.
public static void AddUserActions()
{
    using(ClientContext ctx = new ClientContext(URL))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);
        UserCustomActionCollection actions = list.UserCustomActions;

        UserCustomAction action = actions.Add();
        action.Location = "EditControlBlock";
        action.Sequence = 100;
        action.Title = "SPUG Custom Action";
        action.Url = URL + @"/default.aspx";
        action.Update();

        ctx.Load(list, l => l.UserCustomActions);

        ctx.ExecuteQuery();
    }
}
Adding a menu item to the Site Actions menu is same as the above example, only the Location and Group properties need to be changed.
UserCustomAction action = actions.Add();
action.Location = "Microsoft.SharePoint.StandardMenu";
action.Group = "SiteActions";
action.Sequence = 101;
action.Title = "SPUG Custom Action";
action.Url = URL + @"/default.aspx";
action.Update();
Removing the menu item just a matter of iterating through the UserCustomActions, finding the correct one and calling the DeleteObject. This code will remove menu items from both the EditControlBlock and Site Actions menus since it is only comparing the title. Obviously, you'll want to check the Location property to be more specific.
public static void RemoveUserActions()
{
    using(ClientContext ctx = new ClientContext(URL))
    {
        Web web = ctx.Web;
        List list = web.Lists.GetByTitle(LIST_NAME);
        UserCustomActionCollection actions = list.UserCustomActions;

        ctx.Load(list, l => l.UserCustomActions.Include(a => a.Title));

        ctx.ExecuteQuery();

        for(int x = 0; x < actions.Count; x++)
        {
            if(actions[x].Title == "SPUG Custom Action")
            {
                actions[x].DeleteObject();
                ctx.ExecuteQuery();
                break;
            }
        }
    }
}

Creating a List

So far all of the examples have used previously created lists. Now I'll show how to create one and add Fields to it.
Before creating a List you should, of course, check if it already exists. One somewhat awkward part of this is the Client OM will throw a ServerException when the List can't be found rather than just returning a null object.
List list = web.Lists.GetByTitle("CSOM List");
try
{
    ctx.ExecuteQuery();
    if(list != null)
    {
        list.DeleteObject();
        ctx.ExecuteQuery();
    }
}
catch
{
    // ServerException is thrown if list does not exist
}
After the check has been made and the List deleted if necessary, you can create the List using the ListCreationInformation. Just as with the ListItemCreationInformation shown earlier, this objects applies information about the List that is to be created.
// Set info about this list
ListCreationInformation createInfo = new ListCreationInformation();
createInfo.Title = "CSOM List";
createInfo.QuickLaunchOption = QuickLaunchOptions.On;
createInfo.TemplateType = (int)ListTemplateType.GenericList;

// Add list to the web
list = web.Lists.Add(createInfo);
list.EnableFolderCreation = true;
// Make sure to call Update to apply settings
list.Update();
After the List has been created you add the Fields. The XML supplied to the AddFieldAsXml method is the same as you would use in an Elements.xml to define Fields being added to your by a SharePoint project.
// Add fields to the list
Field field = list.Fields.AddFieldAsXml("<Field Type='Text' DisplayName='SomeField'></Field>",
    true, AddFieldOptions.DefaultValue);

field.DefaultValue = "Hello, World";
// Don't forget to Update after setting values
field.Update();

field = list.Fields.AddFieldAsXml(@"<Field Type='Choice' DisplayName='IsThisFun' Format='RadioButtons'>
                                        <Default>Yes<</Default>
                                        <CHOICES>
                                            <CHOICE>Yes</CHOICE>
                                            <CHOICE>No</CHOICE>
                                        <</CHOICES>    
                                    </Field>",
    true, AddFieldOptions.DefaultValue);
Now that the List and Fields have been created you add ListItems as in previous examples.
ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
ListItem item = list.AddItem(itemCreateInfo);
item["Title"] = "One";
item.Update();

Adding ListItem with Folder

In the previous examples all ListItems have been added to the root folder of the list. However, there are cases when the ListItem should be placed in folder. In this example I've left out checking of the folder exists since the List is being created here also but you should be able to understand from the previous examples how to make that check.
// Create the folder first
itemCreateInfo = new ListItemCreationInformation();
itemCreateInfo.LeafName = "New Folder";
itemCreateInfo.UnderlyingObjectType = FileSystemObjectType.Folder;

item = list.AddItem(itemCreateInfo);
item.Update();
Here I set the UnderlyingObjectType property to FileSystemObjectType.Folder and the LeafName property to the name of the folder. Afterward, I use the FolderUrl property to the relative address of the folder that was created in this list before adding the ListItem
// Now add item to folder
itemCreateInfo = new ListItemCreationInformation();
itemCreateInfo.FolderUrl = "/Lists/CSOM List/New Folder";

item = list.AddItem(itemCreateInfo);
item["Title"] = "Three";
item.Update();

1 comment:

  1. Nice,.. We are very appreciable about your hard work for writing these SharePoint blogs.
    SharePoint 2013 Online Training

    ReplyDelete