Dynamically adding items to ribbon menu

If you try to dig a little bit into Microsoft Office development, is inevitable to face the problem I am going to discuss about. And this is related to adding control to a ribbon menu. It seems the control is read only after ribbon is loaded, so suggested solution was to implement the changes in the constructor logic. This solution might work for most of the situation. However, there are situations when you want to add or remove menu items dynamically after ribbon is loaded. And this can be done very simple by changing the “Dynamic” property to true in Visual Studio.
RibbonMenuDynamicProperty
Once you have modified this property, you are allowed to modify items for the menu. Below is an example how you can do this, more exactly I have added buttons as items for each list inside a SharePoint.

            // Clear items
            this.mnSharePointLists.Items.Clear();

            using(ClientContext ctx = new ClientContext("Url"))
            {
                // Connection details, implement a string to secure string transformation to have a working example
                string username = "username";        
                SecureString password = new SecureString();
        
                // Set connection details to SharePoint Online
                ctx.Credentials = new SharePointOnlineCredentials(username, password);

                Web oWeb = ctx.Site.RootWeb;
                ListCollection oLists = oWeb.Lists;
                ctx.Load(oLists);
                ctx.ExecuteQuery();
                for(int i = 0; i < oLists.Count(); i++)
                {                   
                   RibbonButton btn = Factory.CreateRibbonButton();
                   btn.ShowImage = true;
                   btn.Image = HP.Properties.Resources.Img;   // Image is stored as resource
                   btn.Label = oLists[i].Title;
                   btn.Description = Convert.ToString(oLists[i].Description);
                   this.mnSharePointLists.Items.Add(btn);                  
                }  
            }

I removed lines and adapt the code because this is only suppose to give you an idea (a full example will come later). However, I hope you have a clear image about how to work with ribbon menu to obtain a result similar with one from the screenshot below.
RibbonMenuExample

Advertisements

Create a custom web part page layout

As SharePoint customization is pretty much about creating custom page layouts, I think is a good point to discuss about how to create a custom web part page. For this a SharePoint Designer is the only tool you need, except the situation you need to package the solution to deliver to customers.

Create custom master page

Open the site in SharePoint Designer, navigate to _catalogs/masterpage folder and go to File – Blank Master Page menu item option. You will be prompted to specify the name of your master page, in my case being named “sitecustom.master”. Once you save the file, open “minimal.master” and copy all the code into new created master. This will assure a minimum functionality for page layout which derived from it. As an easier alternative, you can read this MSDN article about how to create from interface a minimal master page.

Create page layout

In the same folder, go to File – ASPX menu item option. This way, you will create a simple ASPX web form page, not linked yet to the master. You can delete entire code and add the following declarations (page directives).

<%@ Page Language="C#" MasterPageFile="~site/_catalogs/masterpage/sitecustom.master" inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>
<%@ Register tagprefix="SharePoint" namespace="Microsoft.SharePoint.WebControls" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="WebPartPages" namespace="Microsoft.SharePoint.WebPartPages" assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Version number can be different as is dependent by SharePoint version you are using. Mine is SharePoint Online and represents the latest version available.
You have successfully linked content page to master. However, an easier way is to select from File ribbon menu “New from master page” option. This should do the same thing, but if you choose this way, please be sure you have all required page directives.
Now your page is almost ready, but is still not editable because no web part zone is placed inside. Let’s do this and place web part zone inside “PlaceHolderMain” content placeholder.

<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<WebPartPages:WebPartZone runat="server" Title="Zone 2" ID="CentralZone"><ZoneTemplate></ZoneTemplate></WebPartPages:WebPartZone>
</asp:Content>

You can create other content placeholders as well and populate them with web part zone. Is up to you here and you have freedom to customize everything. Just a small note from my side. If you are working in SharePoint Online or 2013, do not remove the tag below. It seems somehow is affecting edit panel for web part, making them not to be visible when you want edit something. Not sure why and what is happening there, but you better trust me.

<SharePoint:AjaxDelta id="DeltaPlaceHolderMain" IsMainContent="true" runat="server">
</SharePoint:AjaxDelta>

Associate page with content type

The last phase is to associate the page to a content type to allow creation of the pages using built-in functionality. To do this, you need to edit the properties of the page. You can be automatically directed to edit page from SharePoint Designer by clicking on the page in explorer view and choosing “Manage all file properties in the browser” option.
This being done, you need to make this page a page layout and give a value for associated content type. Below is my example.
WPartPage1
WPartPage2
Once you have done this, you need to test if functionality is correct. Just navigate to Page library, go to New Document menu option and select Web Page. On the next screen you should see the page you have just created available in the options list. Choose it and check if everything is working.
WPartPage3

Here is a very basic example about how to create a custom layout for web part pages, but I will cover complex scenarios in the next posts. But until next posts, I wish you “Happy Coding!”. 🙂

Stock market price data in SharePoint

Reading financial data, like stock market price for a company, is a task which can be very easy achieved in SharePoint. There are several ways you can do this. Very popular is JSONP way to read the data across domain, but I won’t write this time about it. I will write about how you can do this using a control special designed for this. And this is SharePoint:XmlUrlDataSource.

Let’s assume we are requested to display into a page stock market price for Apple, Microsoft and Google. A short search on the internet will reveal we can find this information at the following address. Yahoo provides an API for this at http://query.yahooapis.com/v1/public/yql and is only a question of passing correct parameters to it. The good news is you do not need to write code for it as the control I am talking about can do everything for you.

<SharePoint:XmlUrlDataSource runat="server" XPath="query/results/quote" id="DSYahooSource" AuthType="None" HttpMethod="GET" selectcommand="https://query.yahooapis.com/v1/public/yql">
	<SelectParameters>
		<asp:Parameter Name="q" DefaultValue="select * from yahoo.finance.quotes where symbol in  (&quot;YHOO&quot;,&quot;AAPL&quot;,&quot;GOOG&quot;,&quot;MSFT&quot;)"/>
		<asp:Parameter Name="env" DefaultValue="http://datatables.org/alltables.env"/>
	</SelectParameters>
</SharePoint:XmlUrlDataSource>

Kindly note select parameters I have added and XPath. Select parameters will be automatically added to the API call and XPath will filter the XML obtained in order to display the data in a repeater.

From this point, everything is straight forward. Just place an asp:Repeater to your page, set data source to be the above one and set what information you want to display (I have used ask price as an example).

<asp:Repeater runat="server" DataSourceID="DSYahooSource">
    <ItemTemplate><%# DataBinder.Eval(Container.DataItem,"Ask")%></ItemTemplate>
</asp:Repeater>

If you want to do this on SharePoint:Online, very easy you can use a dataformwebpart with some XSLT, and you only need SharePoint Designer. If you want more complex solution, like something to deliver to a customer, which should look professional, go for a visual web part. Both choices are valid. The main idea is you do not need to look for solution outside the SharePoint, as it is already there.

Read SharePoint Online list from ASP.NET

Today, IT world is all about connecting systems, so to connect an ASP.NET Web Forms based website to SharePoint Online can be a usual task for developers. This approach is classic and you can say is old fashion. We can say Web Forms are deprecate, but this is not true and this will be another topic on my blog.

But, let’s start with the beginning and present the steps you need for this.

Add required libraries to site

To be able to connect to SharePoint Online, or on Premise as well, you need to import some libraries into your website, or project if you decide to go for an ASP.NET project. These can be found on Microsoft site under the name of “SharePoint Server 2013 Client Components SDK” and can be downloaded from this address. Just copy and page these libraries to Bin folder you will enable SharePoint client objects into your code.

Create class to perform data operations

As you have imported required libraries, you can perform operations against a SharePoint list anywhere in your code. However, as you probably want a little bit of code organization, I would suggest you to create a class dedicated for this. I have called it “SharePoint”, which is probably not the best name, as it can create confusion.

using System;
using System.Collections.Generic;
using System.Security;
using System.Linq;
using System.Web;
using Microsoft.SharePoint.Client;

/// <summary>
/// Summary description for SharePoint
/// </summary>
public class SharePoint : IDisposable
{
    string username;
    SecureString password;
    string url;
    ClientContext context;

	public SharePoint(string url, string username, string password)
	{
        this.url = url;
        this.password = password.ToSecureString();
        this.username = username;
        this.context = new ClientContext(this.url);
        this.context.Credentials = new SharePointOnlineCredentials(this.username, this.password);
	}

  

   /// <summary>
   /// Get list items (method overloaded)
   /// </summary>
   /// <param name="listName">List name</param>
   /// <returns></returns>
    public Microsoft.SharePoint.Client.ListItemCollection GetListItems(string listName)
    {
        return this.GetListItems(listName, CamlQuery.CreateAllItemsQuery());
    }

    /// <summary>
    /// Get list items (method overloaded)
    /// <param name="listName">List name</param>
    /// <param name="camlQuery">CAML query</param>
    /// <returns></returns>
    public Microsoft.SharePoint.Client.ListItemCollection GetListItems(string listName, string camlQuery)
    {
        var oQuery = new CamlQuery();
        oQuery.ViewXml = camlQuery;
        return this.GetListItems(listName, oQuery);
    }

    /// <summary>
    /// Get list items (method overloaded)
    /// </summary>
    /// <param name="listName">List name</param>
    /// <param name="camlQuery">CAML query</param>
    /// <returns></returns>
    public Microsoft.SharePoint.Client.ListItemCollection GetListItems(string listName, CamlQuery camlQuery)
    {
        Web oWeb = context.Site.RootWeb;
        List oList = oWeb.Lists.GetByTitle(listName);
        Microsoft.SharePoint.Client.ListItemCollection oItems = oList.GetItems(camlQuery);
        context.Load(oItems);
        context.ExecuteQuery();
        return oItems;
    }
    
    void IDisposable.Dispose()
    {
        this.context.Dispose();
    }


    public static string GetFieldValue(object field = null)
    {
        if (field == null)
            return "";
        
        var fieldType = field.GetType().ToString();
       
    
        // user field
        if (fieldType  == "Microsoft.SharePoint.Client.FieldUserValue")
        {
            return ((Microsoft.SharePoint.Client.FieldUserValue)field).LookupValue;
        }
        // int32
        else if (fieldType == "System.Int32")
        {
            return Convert.ToString(field);
        }
        // string
        else if (fieldType == "System.String")
        {
            return Convert.ToString(field);
        }
        // Lookup field value
        else if (fieldType == "Microsoft.SharePoint.Client.FieldLookupValue") {
            return Convert.ToString(((Microsoft.SharePoint.Client.FieldLookupValue)field).LookupValue);
        }
        // date time
        else if(fieldType == "System.DateTime")
        {
            return Convert.ToDateTime(field).ToLongDateString();
        }
        else if (fieldType == "Microsoft.SharePoint.Client.ContentTypeId") {
            return ((Microsoft.SharePoint.Client.ContentTypeId)field).StringValue;
        }
        else
        {
            // return Convert.ToString(field);
            return fieldType;
        }   
    }
}

The class contains an overloaded method called “GetListItems” and a static method used for converting field values into string based on their types. There is a lot you can improve here, but it doesn’t fall under this post subject. Also we are not going to use for this example all signatures of “GetListItems” method, but for future use you can keep this method overloading.

Create Ajax enabled WCF service to communicate with SharePoint

What you need now is WCF service to communicate with SharePoint. I choose an Ajax enabled one because I wanted to use it through script manager. But if you prefer, you can use a non-Ajax enabled one to consume it using jQuery.

When you actually execute a query against a SharePoint list, you obtain a ListItemCollection object. As far as I know it cannot be de-serialized by default, so you will need to implement this. So you can create following classes to serve as data contract for the web service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.Runtime.Serialization;


/// <summary>
/// Represent list item in a collection returned by WCF service
/// </summary>
[CollectionDataContract(Name = "SharePointListItem")]
public class SharePointListItem : Dictionary<string, string>
{
  
}


/// <summary>
/// Represents a collection of SharePoint list items returned by WCF service
/// </summary>
[CollectionDataContract(Name = "SharePointListItemCollection")]
public class SharePointListItemCollection : List<SharePointListItem>
{

}

Implementation of WCF service is simple. In our case, not to complicate the explanations too much, I have created only one method called “GetListItems”, which will get all items for specified list.

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Xml;
using System.Web.Script.Serialization;
using Microsoft.SharePoint.Client;

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ListData
{
	// To use HTTP GET, add [WebGet] attribute. (Default ResponseFormat is WebMessageFormat.Json)
	// To create an operation that returns XML,
	//     add [WebGet(ResponseFormat=WebMessageFormat.Xml)],
	//     and include the following line in the operation body:
	//         WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";



    [WebGet(ResponseFormat=WebMessageFormat.Json)]
    public SharePointListItemCollection GetListItems(string listName)
    {
        string url = ConfigurationManager.AppSettings["spUrl"];
        string username = ConfigurationManager.AppSettings["spUsername"];
        string password = ConfigurationManager.AppSettings["spPassword"];
        SharePointListItemCollection results = new SharePointListItemCollection();
        using(SharePoint sp = new SharePoint(url,username, password))
        {
        
            Microsoft.SharePoint.Client.ListItemCollection items = sp.GetListItems(listName);
            foreach (ListItem item in items)
            {
                SharePointListItem r = new SharePointListItem();
                foreach (KeyValuePair<string, object> field in item.FieldValues)
                {
                    // SharePointItemField f = new SharePointItemField();
                    // f.FieldName = field.Key;
                    // f.FieldValue = SharePoint.GetFieldValue(field.Value);
                    r.Add(field.Key, SharePoint.GetFieldValue(field.Value));
                }
                results.Add(r);
            };
          
           
        }
        return results;
    }


}

As you noticed, SharePoint class is initialized with 3 parameters, and we are taking these from application settings. To you need to add them in web.config.

  <appSettings>
    <add key="spUsername" value="username" />
    <add key="spPassword" value="password" />
    <add key="spUrl" value="https://url" />
  </appSettings>

Add reference to the WCF service

As the WCF service is Ajax enabled, you need to add a reference to it script manager tag. This will generate a JavaScript for you and will eliminate time waste for writing Ajax calls to the service.

    <asp:ScriptManager runat="server" EnablePartialRendering="true" LoadScriptsBeforeUI="false" EnablePageMethods="true">
        <Services>
            <asp:ServiceReference Path="~/_Services/ListData.svc" />
        </Services>
     </asp:ScriptManager>

Getting the data

And this is the last step, getting the data from SharePoint list. This will take place when page loads to prevent performance issues rendering the mark-up.

function pageLoad(source, args) {
    var service = new ListData();
    service.GetListItems('List name',
        // Success
        function (response) {
            // Do something with JSON response
        },
        // Fail
        function (response) {
            alert(args.get_message());
        });
}

The response is JSON type, so it is up to you what you are going to do with it. My suggestion is to use JavaScript template engine to display it on the page.