Walkthrough: Creating a Project Server Web Part with a JS Grid

Applies to: Office 2010 | Project 2010 | Project Server 2010 | SharePoint Server 2010

Many of the pages in Project Web App include a JS Grid control. The JS Grid control is also useful to display and manipulate tabular data in Web Parts for Microsoft Project Server 2010. This article shows how to develop a Web Part that calls the ListProjects PSI extension and uses a JS Grid control to display all of the projects that contain a specified custom field value. The same Web Part can be added to editable Project Web App pages, project detail pages (PDPs), and project site pages. (The JS Grid control code that is included in this article and in the ListProjects samples in the Project 2010 SDK download is based on samples by Sivaraman Krishnan and Andrew Cuneo, Microsoft Corporation.)

This article includes the following sections:

  • Developing the ListProjects Web Part

    • Creating the Web Part

    • Modifying the User Control

    • Adding Web Part Properties for Configuration

    • Requiring Configuration on First Use

    • Initializing the JS Grid Control

  • Testing the ListProjects Web Part

    • Adding ListProjects to a Project Web App Page

    • Deploying the ListProjects Web Part

    • Adding ListProjects to a Project Details Page

    • Adding ListProjects to a Project Site Page

The complete source code for the ListProjects Web Part is in the Project 2010 SDK download. The code in this article is included in the Samples\WebParts\ListProjects subdirectory of the download. The code for the version that enables column sorting is in the Samples\WebParts\ListProjects_Sorting subdirectory. For a link to the download, see Project 2010 SDK Documentation. For information about the ListProjects PSI extension, see How to: Create a PSI Extension to Read Custom Fields in the RDB. For a video demonstration of using the JS Grid control and customizing Project Web App, see Developing Custom Solutions in PWA. For more information about Web parts, see User Interface Customization Resource Center | SharePoint 2010.

Prerequisites

Developing a Web Part for Microsoft Project Server 2010 requires Microsoft Visual Studio 2010. Development must be done on a computer running Project Server, to enable automated installation and debugging of a SharePoint Feature by Visual Studio. The Visual Studio project must target the Microsoft .NET Framework 3.5.

Warning

Development and testing of Project Web App customizations should be done only on a test installation of Project Server 2010.

When the solution is complete and thoroughly tested, deploy the .wsp solution file on a production server by using Windows PowerShell commands, as described in Deploying the ListProjects Web Part.

Developing the ListProjects Web Part

The ListProjects Web Part sample includes a Visual Web Part item named ListProjectsPart, which has five properties that must be set on first use of the Web Part. The Web Part control is named ListProjectsPartUserControl.

Creating the Web Part

The following procedure is based on the general information in Developing Project Server Web Parts.

Procedure 1. To create the ListProjects Web Part

  1. Run Visual Studio 2010 as an administrator on a local Project Server computer. Create an empty SharePoint 2010 project named ListProjects. The target framework must be .NET Framework 3.5. In the SharePoint Customization Wizard, select the local Project Web App site for debugging, and then click Deploy as a farm solution.

  2. To use WCF for the ListProjects PSI extension, add the following references:

    • Microsoft.Office.Project.Server.Library

    • System.Runtime.Serialization

    • System.ServiceModel

  3. Add a service reference to the PSI extension (https://localhost/pwa/_vti_bin/PSI/ListProjects.svc), and name the reference, for example, SvcListProjects. If you are using the sample solution in the Project 2010 SDK download, right-click the SvcListProjects service in Solution Explorer, and then click Update Service Reference.

  4. Add a new Visual Web Part item to the ListProjects project, and name it ListProjectsPart. Visual Studio creates the necessary files for the Web Part and the user control, and a strong name key to sign the assembly.

  5. In the Elements.xml file, set the name of the Web Part group. For example, set the Group property to JS Grid Examples.

    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="https://schemas.microsoft.com/sharepoint/" >
      <Module Name="ListProjectsPart" List="113" Url="_catalogs/wp">
        <File Path="ListProjectsPart\ListProjectsPart.webpart" 
               Url="ListProjectsPart.webpart" Type="GhostableInLibrary" >
          <Property Name="Group" Value="JS Grid Examples" />
        </File>
      </Module>
    </Elements>
    
  6. In the ListProjectsPart.webpart file, set the Web Part Title property and the Description property.

    <?xml version="1.0" encoding="utf-8"?>
    <webParts>
      <webPart xmlns="https://schemas.microsoft.com/WebPart/v3">
        <metaData>
          <type name="ListProjects.ListProjectsPart.ListProjectsPart, $SharePoint.Project.AssemblyFullName$" />
          <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage>
        </metaData>
        <data>
          <properties>
            <property name="Title" type="string">List Projects with Custom Field Value</property>
            <property name="Description" type="string">Lists the projects that contain a specified custom field value.</property>
          </properties>
        </data>
      </webPart>
    </webParts>
    
  7. Open the Feature1.feature designer, and then edit the feature title and description. For example, change the Title field to ListProjects Feature, and then change the Description field to The ListProjects Web Part shows a list of projects that contain a specified custom field value.

  8. Open the Package.package designer, and then change the name field, for example, to ListProjects Web Part.

Modifying the User Control

Because the ListProjectsPartUserControl.ascx file is a Visual Web Part item, you can easily use the Design view or the Split view to add controls from the Toolbox by dragging to either pane. You can also add controls by typing the control elements and attributes in the Source view.

The JS Grid control uses ECMAScript (JavaScript, JScript) code to manage the grid features and behavior, attach to events, and manipulate grid content on the client side. Because the JS Grid control is not in the Toolbox, you must add it and the associated GridManager function in the code pane (or set a reference to the JavaScript file that contains the GridManager function). For more information, see Tips for Implementing the JS Grid Control.

Procedure 2. To modify the ListProjectsPartUserControl

  1. Open the ListProjectPartUserControl.ascx file, and then show the Split view (Figure 1) to see both the code pane and the design pane.

  2. Drag a Label control from the Toolbox to the design pane or to the bottom of the code pane. Change the ID attribute and the Text attribute, as follows:

    <asp:Label ID="LabelProjects" runat="server" 
    Text="Optics Program custom field value:  "></asp:Label>
    
  3. Add a Textbox control and a Literal control for a line break, for example:

    <asp:TextBox ID="TextBoxCFValue" runat="server" 
    Tooltip="Type the Optics Program custom field value here"></asp:TextBox>
    <asp:Literal ID="Break1" runat="server"><br /></asp:Literal>
    
  4. Drag a Button control to the design pane, set the button ID to ButtonListProjects, set the Text value to List Projects, and then double-click the button. Visual Studio creates the ButtonListProjects_Click event handler, which you can use to initialize the JS Grid control and call the ListProjects service to get data for the grid. Also add two more line breaks, to separate the button from the JS Grid control.

    <asp:Button ID="ButtonListProjects" runat="server" Text="List Projects" 
        onclick="ButtonListProjects_Click" />
    <asp:Literal ID="Break2" runat="server"><br /><br /></asp:Literal>
    
  5. Add a JS Grid control by typing the following in the code pane:

    <SharePoint:JSGrid ID="_grid" runat="server" /> 
    
  6. Add the JavaScript code that creates a GridManager function. You can add the code in one of the following two ways:

    • Add the code inline. For example, in the ListProjectsPartUserControl.ascx file, add the code within a script element:

      <script type="text/javascript">
          Type.registerNamespace("GridManager");
          GridManager = function () {
              this.Init = function (jsGridControl, initialData, props) {
                  var dataSource = new SP.JsGrid.StaticDataSource(initialData);
                  var jsGridParams = dataSource.InitJsGridParams();
                  jsGridControl.Init(jsGridParams);
              }
          };
      </script>
      
    • The best practice is to add the code in a separate .js file that is within the SharePoint mapped Layouts folder:

      1. Right-click the ListProjects project, click Add, and then click SharePoint "Layouts" Mapped Folder. Visual Studio creates the Layouts folder and the ListProjects subfolder.

      2. Right-click the Layouts\ListProjects folder, click Add, and then click New Item. In the Installed Templates pane of the Add New Item dialog box, click Web. Click JScript File, name it GridManager.js, and then click Add.

      3. Open the GridManager.js file, and then copy in the following code:

        Type.registerNamespace("GridManager");
        GridManager = function () {
            this.Init = function (jsGridControl, initialData, props) {
                var dataSource = new SP.JsGrid.StaticDataSource(initialData);
                var jsGridParams = dataSource.InitJsGridParams();
                jsGridControl.Init(jsGridParams);
            }
        };
        
      4. If you expect to develop additional functions for the JS Grid control, copy the following lines into the top of the GridManager.js file. The reference elements provide Intellisense completion for the common SharePoint and JS Grid control functions that are used.

        /// <reference path="~/_layouts/SP.core.debug.js"/>
        /// <reference path="~/_layouts/SP.debug.js"/>
        /// <reference path="~/_layouts/JSGrid.debug.js" />
        

        You can add references to additional .js files for SharePoint Foundation, SharePoint Server, and Project Server. For example, search for a function in .js files in the [Program Files]\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS directory.

      5. In the ListProjectsPartUserControl.ascx file, add the following line before the line that adds the JSGrid control:

        <script type="text/javascript" src="~/_layouts/ListProjects/GridManager.js"></script>
        

        Ensure that you use the closing </script> tag, rather than />, for closing the script element.

    Figure 1 shows the Split view, with the JS Grid control selected in the design pane. The Properties pane shows additional grid properties. The GridUtilities.cs file and the Deployment folder in Solution Explorer are added in later procedures.

    Figure 1. Split view of the ListProjectsPartUserControl.ascx file

    Split view of the ListProjects user control

  7. Open the code-behind file for the user control (ListProjectsPartUserControl.ascx.cs), and then add the using statements that will be needed. Following is the complete list:

    using System;
    using System.IO;
    using System.Security.Principal;
    using System.ServiceModel;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    
    using System.Data;
    using Microsoft.SharePoint.JSGrid;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Utilities;
    using Microsoft.SharePoint.WebControls;
    using Microsoft.Office.Project.Server.Library;
    
  8. Add the helper methods for setting the WCF endpoint of the ListProjectsClient object.

    Because the ListProjects Web Part must run in Project Web App, in a PDP, or in a project site, the GetPwaSiteUrl method gets the base URL of Project Web App in any of those three conditions. The IsContextForActiveBrowsing method determines whether the HTTP context is from an active browsing session in Internet Explorer.

    // Determine whether the HttpContext is for active browsing.
    private static bool IsContextForActiveBrowsing(HttpContext httpContext)
    {
        return httpContext != null && httpContext.User != null 
            && httpContext.Server != null;
    }
    
    // Get the base URL of Project Web App.
    private static string GetPwaSiteUrl(HttpContext httpContext)
    {
        if (IsContextForActiveBrowsing(httpContext))
        {
            // If the current site is a Project Web App site, return that.  
            SPWeb web = SPControl.GetContextWeb(httpContext);
            if (web.WebTemplateId == WSS.WssPWADefaultTemplateNumericId)
            {
                return httpContext.Request.Url.IsLoopback 
                    ? SPUrlUtility.CombineUrl(
                        httpContext.Request.Url.GetLeftPart(UriPartial.Authority),
                                                        web.Site.ServerRelativeUrl)
                    : web.Site.Url;
            }
    
            // If the current site is a project site, return the sponsoring 
            // Project Web App site.
            if (web.WebTemplateId >= WSS.WssPWSTemplateNumericIdMinLimit &&
                web.WebTemplateId <= WSS.WssPWSTemplateNumericIdMaxLimit)
            {
                return web.AllProperties["PWAURL"] as string;
            }
        }
        return null;
    }
    
  9. In the helpers region, add the SetClientEndpoints method. Override the Dispose method so that it calls the DisposeClients method.

    private void SetClientEndpoints(string pwaUrl, bool isHttps)
    {
        const int MAXSIZE = 500000000;
    
        BasicHttpBinding binding = null;
    
        if (isHttps)
        {
            // Create a binding for HTTPS.
            binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
        }
        else
        {
            // Create a binding for HTTP.
            binding = new BasicHttpBinding(
                BasicHttpSecurityMode.TransportCredentialOnly);
        }
    
        binding.Name = "basicHttpConf";
        binding.SendTimeout = TimeSpan.MaxValue;
        binding.MaxReceivedMessageSize = MAXSIZE;
        binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE;
        binding.MessageEncoding = WSMessageEncoding.Text;
        binding.Security.Transport.ClientCredentialType = 
            HttpClientCredentialType.Ntlm;
    
        EndpointAddress addressListProjects = 
            new EndpointAddress(pwaUrl + SVCLISTPROJECTS_PATH);
    
        listProjectsClient = 
            new SvcListProjects.ListProjectsClient(binding, addressListProjects);
        listProjectsClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel
            = TokenImpersonationLevel.Impersonation;
        listProjectsClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;
    }
    
    public override void Dispose()
    {
        DisposeClients();
        base.Dispose();
    }
    
    // Dispose the service clients.
    public void DisposeClients()
    {
        listProjectsClient.Close();
    }
    
  10. Add class constants for the ListProjects service and for the ListProjectsClient object. Add the ListProjectsPartUserControl constructor. In the constructor, get the Project Web App URL, determine whether the URL uses the HTTPS protocol, and then call SetClientEndpoints to initialize the SvcListProjects.ListProjectsClient object.

    private const string SVCLISTPROJECTS_PATH = "/_vti_bin/PSI/ListProjects.svc";
    
    private static string pwaUrl;
    private static SvcListProjects.ListProjectsClient listProjectsClient;
    
    public ListProjectsPartUserControl()
    {
        // Get the URL of Project Web App.
        HttpContext context = HttpContext.Current;
    
        // Get the base URL of Project Web App, for example, https://ServerName/pwa.
        pwaUrl = GetPwaSiteUrl(context);
    
        bool isHttps = pwaUrl.Contains("https");
        SetClientEndpoints(pwaUrl, isHttps);
    }
    
  11. Set general properties for controls in the Page_Load event handler. For example, set the JS Grid control height to 100 pixels.

    protected void Page_Load(object sender, EventArgs e)
    {
        _grid.Height = 100;
    }
    

Adding Web Part Properties for Configuration

The ListProjects Web Part requires configuration for the Microsoft SQL Server name, Reporting database (RDB) name, stored procedure name, and name of the parameter for the stored procedure, before it can call the ListProjects PSI extension. Because it is a Visual Web Part, you can easily add Web Part properties to the default editor part control.

Procedure 3. To add Web Part properties

  1. In the ListProjectsPartUserControl class, add class variables that can hold values of the Web Part properties. Add a property named HostWebPart to contain the ListProjectsPart object. The cfValue string will be set by the value in the text box control.

    private static string sqlServerName = string.Empty;
    private static string rdbName = string.Empty;
    private static string sprocName = string.Empty;
    private static string sprocParamName = string.Empty;
    private static string cfValue = string.Empty;
    
    public ListProjectsPartHostWebPart { get; set; }
    
  2. In the ListProjectsPart.cs file, add the constants for the default SQL Server name and the name of the property panel in the editor part. Add the public properties, and then initialize the properties in the ListProjectsPart constructor. You can also set the default Web Part size and appearance in the constructor.

    The attributes that decorate each property set the property to be visible in the default editor part, set the personalization scope, and set values for the property panel in the editor part.

    private const string DEFAULT_SERVER = "SERVER_NAME";
    private const string PROPERTY_PANEL_NAME = "List Projects Data Source";
    
    #region Web Part Properties
    [WebBrowsable(true), 
    Personalizable(PersonalizationScope.Shared), 
    WebDisplayName("SQL Server Name"), 
    WebDescription("Name of the SQL Server instance that includes the Reporting database."),
    SPWebPart.SPWebCategoryName(PROPERTY_PANEL_NAME)]
    public string SqlServerName { get; set; }
    
    [WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared), 
    WebDisplayName("Reporting Database Name"), 
    WebDescription("Name of the Reporting database (RDB) for Project Server."),
    SPWebPart.SPWebCategoryName(PROPERTY_PANEL_NAME)]
    public string RdbName { get; set; }
    
    [WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared), 
    WebDisplayName("Stored Procedure Name"), 
    WebDescription("Name of the stored procedure in the RDB that lists the projects."),
    SPWebPart.SPWebCategoryName(PROPERTY_PANEL_NAME)]
    public string SprocName { get; set; }
    
    [WebBrowsable(true), 
    Personalizable(PersonalizationScope.Shared), 
    WebDisplayName("SPROC Parameter Name"), 
    WebDescription("Name of the parameter for the custom field value in the stored procedure."),
    SPWebPart.SPWebCategoryName(PROPERTY_PANEL_NAME)]
    public string SprocParam { get; set; }
    
    # endregion
    
    public ListProjectsPart()
    {
        this.ChromeType = PartChromeType.TitleAndBorder;
        this.Height = 180;
    
        SqlServerName = DEFAULT_SERVER;
        RdbName = "ProjectServer_Reporting";
        SprocName = "ListProjectsInProgram";
        SprocParam = "@cfValue";
    }
    
  3. In the CreateChildControls method, change the default Visual Web Part control type to the actual type of the user control, and initialize the ListProjectPartUsercontrol.HostWebPart property. For example, change Control control = Page.LoadControl(_ascxPath); as follows:

    protected override void CreateChildControls()
    {
        ListProjectsPartUserControl control = Page.LoadControl(_ascxPath) 
            as ListProjectsPartUserControl;
    
        if (control != null) control.HostWebPart = this;
    
        Controls.Add(control);
    }
    
  4. In the ListProjectsPartUserControl.ascx.cs file, override the OnPreRender event handler in the ListProjectsPartUserControl class to set the Web Part properties from the ListProjectsPart host. When a user edits the Web Part properties, the properties are changed in the host Web Part, but you must explicitly set the changes in the user control.

    // Set the Web Part custom properties.
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
    
        sqlServerName = HostWebPart.SqlServerName;
        rdbName = HostWebPart.RdbName;
        sprocName = HostWebPart.SprocName;
        sprocParamName = HostWebPart.SprocParam;
    }
    

Requiring Configuration on First Use

Before the ListProjects Web Part can call the ListProjects PSI extension, it must have correct values for the SQL Server parameters. To require configuration on initial use, you can add an JavaScript function to a hyperlink control that displays the editor part when the SQL Server name is the default value. After the user enters and saves the correct SQL Server name and other property values, the values are stored in the SharePoint content database. The hyperlink control for Web Part initialization is not displayed on subsequent uses.

Procedure 4. To require configuration on first use of the Web Part

  • In the CreateChildControls method (ListProjectsPart.cs file), replace the Controls.Add line with the following:

    if (this.SqlServerName == DEFAULT_SERVER)
    {
        HyperLink configLink = new HyperLink();
        string url = string.Format(
            "javascript:ShowToolPane2Wrapper('Edit', '129', '{0}');", this.ID);
    
        configLink.href = url;
        configLink.ID = string.Format("MsoFrameworkToolpartDefmsg_{0}", this.ID);
    
        string configText = string.Format(
            "<b>Click here to configure this Web Part:</b> see the <b>{0}</b> section.",
            PROPERTY_PANEL_NAME);
        configLink.Text = configText;
    
        Literal lineBreaks = new Literal();
        lineBreaks.ID = "LineBreak1";
        lineBreaks.Text = "<br /><br />";
        Controls.Add(configLink);
        Controls.Add(lineBreaks);
    }
    else
    {
        Controls.Add(control);
    }
    // Ensure that all child controls are set. Otherwise, because of the added ECMAScript 
    // command, you must click Apply twice in the editor part.
    EnsureChildControls();
    

Initializing the JS Grid Control

The column and row properties of the JS Grid control are set in a GridUtilities class. The GridUtilities class in this article is based on the example in How to: Create a Basic JS Grid Control, with modifications that are specific for displaying values of the ProjectDataSet.Project table.

Procedure 5. To initialize the JS Grid control

  1. Add a class to the ListProjects project; name the class file GridUtilities.cs.

  2. The GetGridColumns method uses the SetColumnProperties method to show only the PROJ_UID, PROJ_NAME, PROJ_PROP_AUTHOR, and PROJ_INFO_START_DATE properties in the grid, with appropriate column widths and friendly column titles.

    Note

    The IsSortable property of each column is true by default. However, the code in this example does not implement column sorting. The IsHideable property is true by default. The PROJ_NAME column is set so that it cannot be hidden.

    using System;
    using System.Collections.Generic;
    using System.Data;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.JSGrid;
    
    namespace ListProjects
    {
        public static class GridUtilities
        {
            // Get a collection of the grid columns.
            public static IList<GridColumn> GetGridColumns(DataTable table)
            {
                List<GridColumn> r = new List<GridColumn>();
    
                foreach (DataColumn iterator in table.Columns)
                {
                    // Specify which columns to show in the grid.
                    if (iterator.ColumnName == "PROJ_UID"
                            || iterator.ColumnName == "PROJ_NAME"
                            || iterator.ColumnName == "PROJ_PROP_AUTHOR"
                            || iterator.ColumnName == "PROJ_INFO_START_DATE")
                    {
                        GridColumn col = SetColumnProperties(iterator);
    
                        // Add the column.
                        r.Add(col);
                    }
                }
                return r;
            }
    
            // Set the column width, header name, and other properties.
            private static GridColumn SetColumnProperties(DataColumn dataCol)
            {
                GridColumn col = new GridColumn();
                col.FieldKey = dataCol.ColumnName;
    
                switch (col.FieldKey)
                {
                    case "PROJ_UID":
                        col.Name = "Project GUID";
                        col.Width = 256;
                        col.IsSortable = false;
                        break;
                    case "PROJ_NAME":
                        col.Name = "Project Name";
                        col.IsHidable = false;
                        col.IsSortable = true;
                        col.Width = 218;
                        break;
                    case "PROJ_PROP_AUTHOR":
                        col.Name = "Author";
                        col.IsHidable = true;
                        col.Width = 140;
                        break;
                    case "PROJ_INFO_START_DATE":
                        col.Name = "Start Date";
                        col.Width = 140;
                        break;
                    default:
                        col.Name = dataCol.ColumnName;
                        col.Width = 210;
                        break;
                }
                return col;
            }
    
        }
    }
    
  3. The GetGridFields method adds a formatted field for each column in the grid. The FormatGridField method sets field properties based on data type. For fields that can be localized, for example strings, dates, and monetary values, the FormatGridField method sets the GridField.Localizer property with an inline delegate method to convert the value.

    // Get a collection of the grid fields.
    public static IList<GridField> GetGridFields(DataTable table)
    {
        List<GridField> r = new List<GridField>();
    
        foreach (DataColumn iterator in table.Columns)
        {
            GridField field = new GridField();
            field = FormatGridField(field, iterator);
    
            r.Add(field);
        }
        return r;
    }
    
    // Match the propertyTypeId of the grid field with the column type.
    public static GridField FormatGridField(GridField gf, DataColumn dc)
    {
        // Set field key name.
        gf.FieldKey = dc.ColumnName;
    
        // When in doubt, serialize the data value.
        gf.SerializeDataValue = true; 
    
        if (dc.ColumnName != GridSerializer.DefaultGridRowStyleIdColumnName)
        {
            // Determine whether the field is timephased.
            if (dc.ColumnName.Substring(0, 5) == "Quarter")
            {
                // Ensure that the timephased column headers are 
                // always read-only, despite the grid settings.
                gf.EditMode = EditMode.ReadOnly;
            }
    
            // Add properties based on the type.
            if (dc.DataType == typeof(String))
            {
                gf.PropertyTypeId = "String";
    
                // The Localizer determines how to render the underlying data.
                gf.Localizer = (ValueLocalizer)delegate(DataRow row, object toConvert)
                {
                    return toConvert == null ? "" : toConvert.ToString();
                };
    
                // The Serialization type is a required property. 
                gf.SerializeLocalizedValue = true;
                gf.SerializeDataValue = false;
            }
            else if (dc.DataType == typeof(Int16)
                || dc.DataType == typeof(Int32) 
                || dc.DataType == typeof(Int64)
                || dc.DataType == typeof(Decimal)
                || dc.DataType == typeof(Double))
            {
                gf.PropertyTypeId = "JSNumber";
    
                // The Localizer determines how to render the underlying data.
                gf.Localizer = (ValueLocalizer)delegate(DataRow row, object toConvert)
                {
                    return toConvert == null ? "" : toConvert.ToString();
                };
    
                // The Serialization type is a required property.
                gf.SerializeLocalizedValue = true;
                gf.SerializeDataValue = false;
            }
            else if (dc.DataType == typeof(Hyperlink))
            {
                gf.PropertyTypeId = "Hyperlink";
                gf.Localizer = (ValueLocalizer)delegate(DataRow row, object toConvert)
                {
                    return toConvert == null ? "" : toConvert.ToString();
                };
                gf.SerializeLocalizedValue = false;
                gf.SerializeDataValue = true;
            }
            else if (dc.DataType == typeof(bool))
            {
                gf.PropertyTypeId = "CheckBoxBoolean";
                gf.SerializeDataValue = true;
                gf.SerializeLocalizedValue = false;
            }
            else if (dc.DataType == typeof(DateTime))
            {
                gf.PropertyTypeId = "JSDateTime";
                gf.Localizer = (ValueLocalizer)delegate(DataRow row, object toConvert)
                {
                    return toConvert == null ? "" : toConvert.ToString();
                };
                gf.SerializeDataValue = true;
                gf.SerializeLocalizedValue = true;
            }
            else if (dc.DataType == typeof(Guid))
            {
                gf.PropertyTypeId = "String";
                gf.Localizer = (ValueLocalizer)delegate(DataRow row, object toConvert)
                {
                    return toConvert == null ? "" : toConvert.ToString();
                };
                gf.SerializeDataValue = true;
                gf.SerializeLocalizedValue = true;
            }
            else
                throw new Exception("GridUtilities.FormatGridField: No PropertyTypeId is defined for this datatype: "
                    + dc.DataType.ToString());
        }
        return gf;
    }
    

    The GUID property is simply set as a string. It is possible to create a custom property type; that is beyond the scope of this article. Following are valid values of PropertyTypeId:

    • JSNumber

    • JSDateTime

    • String

    • CheckBoxBoolean

    • Hyperlink

  4. In the code-behind file for ListProjectsPartUserControl, add code in the ButtonListProjects_Click event handler to populate the grid with data and initialize the grid with the GridManager function that you defined in the GridManager.js file.

    Tip

    When you are writing code to initialize and configure a JS Grid control, it is useful to save the data that the grid uses to a separate file for easy examination. The commented-out code shows how to save a file named ProjectData4JSGrid.xml.

    protected void ButtonListProjects_Click(object sender, EventArgs e)
    {
        cfValue = TextBoxCFValue.Text.Trim();
    
        if (cfValue != string.Empty)
        {
            SvcListProjects.ProjectDataSet projectDs =
                listProjectsClient.GetProjectsWithCustomFieldValue(
                    sqlServerName, rdbName, sprocName, sprocParamName, cfValue);
    
            _keyColumn = projectDs.Project.PROJ_UIDColumn.Caption;
    
            // If the directory does not exist, create it.
            // Assign the path where the output XML file will be saved.
            // Uncomment the following lines to create an XML file of the Project table.
    
            //if (!Directory.Exists(OUTPUT_FILES))
            //{
            //    Directory.CreateDirectory(OUTPUT_FILES);
            //}
            //string outFilePath = OUTPUT_FILES + "ProjectData4JSGrid.xml";
            //projectDs.Project.WriteXml(outFilePath);
    
            string[] fieldOrderColumns = { _keyColumn };
            FieldOrderCollection sortedColumns =
                new FieldOrderCollection(fieldOrderColumns);
    
            GridSerializer gds = new GridSerializer(SerializeMode.Full,
                projectDs.Project,
                _keyColumn,
                sortedColumns,
                GridUtilities.GetGridFields(projectDs.Project),
                GridUtilities.GetGridColumns(projectDs.Project));
    
            // Set the grid data serializer to the grid serializer object.
            _grid.GridDataSerializer = gds;
    
            // Tell the grid to listen to the GridManager controller.
            _grid.JSControllerClassName = "GridManager";
        }
    }
    

Testing the ListProjects Web Part

Visual Studio 2010 makes debugging a Web Part a straightforward process. Set a breakpoint in one of the .cs files, and then press F5. Visual Studio compiles the project, installs the ListProjects.dll in the global assembly cache, deploys and activates the Web Part in the site that is specified by the Site URL property in the ListProjects project, starts Internet Explorer, and then attaches to the correct w3wp.exe and iexplore.exe processes. Detailed procedures for debugging Web Parts or the JS Grid control are beyond the scope of this article.

Adding ListProjects to a Project Web App Page

You can add the ListProjects Web part to any editable Project Web App page that includes Web Part zones. Pages that are not editable do not include the Edit Page item in the Site Actions menu.

Procedure 6. To add the ListProjects Web Part to a Project Web App page

  1. In Project Web App, click Site Actions, click Edit Page, and then click Add a Web Part in the Web Part zone that you choose. Figure 2 shows that the List Projects with Custom Field Value Web Part is in the JS Grid Examples group, as specified in the Elements.xml file. The friendly name of the Web Part is specified in the ListProjectsPart.webpart file.

    Figure 2. Adding a Web Part to the Header zone in Project Web App

    Adding a Web Part to the Header zone

  2. Configure the Web Part. When you first run the ListProjects Web Part (Figure 3), the SQL Server Name property has the default value, so the Web Part shows the configuration message and hyperlink to the editor part. The List Projects Data Source panel name in the editor part is specified by the PROPERTY_PANEL_NAME constant in the ListProjectsPart.cs file.

    Figure 3. Configuring the Web Part on the first run

    Configuring the Web Part on the first run

  3. In the SQL Server Name field, type the name of the Microsoft SQL Server installation where the Reporting database is located. Ensure that the other fields in the List Projects Data Source panel are correct, and then click OK.

When you save the correct property values for the data source, the Web Part sets the properties in the OnPreRender event handler, refreshes, and then displays the child controls that you added in the ListProjectsPartUserControl.ascx file. Finally, type a value that matches a custom field value, press the Enter key twice, and the grid shows the list of projects that have that custom field value (Figure 4). For example, type Optical Fabrication in the text box.

Because the custom Web Part properties are stored in the SharePoint content database, on subsequent runs, the ListProjects Web Part shows the label, textbox, button, and JS Grid control, rather than the configuration hyperlink control. To show the configuration control again, edit the Web Part, and then change the SQL Server Name field back to SERVER_NAME. If you delete the Web Part on the page and then add it again, you also get the default property values.

Note

Figure 4 shows the results of the ListProjects Web Part sample in the Project 2010 SDK download that implements column sorting. In the ListProjects sample for this article, clicking on the Project Name column shows only the Configure Columns option. If you click the heading for any of the other columns, the options include Hide Column.

Figure 4. Using the ListProjects Web Part in a Project Web App page

Using the ListProjects Web Part in Project Web App

When you click a column heading and then click Configure Columns, the JS Grid control displays the Configure Columns modal dialog box (Figure 5). Because the IsHidable property is set to false in the GridUtilities.SetColumnProperties method, the Project Name column cannot be hidden.

Figure 5. Configuring the JS Grid columns

Configuring the JS Grid columns

Deploying the ListProjects Web Part

When you close Internet Explorer or press Shift+F5 in Visual Studio, Visual Studio retracts the Web Part and resets IIS (if the Auto-retract after debugging check box is selected on the SharePoint tab of the ListProjects property page). If you click Deploy Solution on the Build menu, Visual Studio deploys the solution on the development computer so that you can use the Web Part independently from Visual Studio.

You can also deploy the ListProjects Web Part on the test computer or on another Project Server computer by creating a SharePoint solution package (.wsp file). The DeployListProject.cmd file and the DeploySolution.ps1 file in Procedure 7 are adapted from deployment files in Microsoft Project 2010 Solution Starters.

Procedure 7. To deploy the List Projects Web Part

  1. On the Build menu in Visual Studio, click Package. Visual Studio builds the installation package in the pkg subdirectory, and then creates the ListProjects.wsp file in the bin\Debug subdirectory. Because the ListProjects.wsp file is actually a .cab format file, you can open the file with WinZip.exe to examine the contents.

  2. In Solution Explorer, create a folder to contain the deployment files. For example, add a folder named Deployment in the ListProjects project node. Copy the ListProjects.wsp file to the Deployment folder.

  3. Create the DeployListProjects.cmd file in the Deployment folder, and then copy in the following script. Change the value of SiteUrl to your Project Web App URL.

    @echo off
    Set DeploymentPackageFolder="."
    
    Set SiteUrl="https://ServerName/ProjectServerName"
    Set SolutionFolder="./"
    Set SolutionName="ListProjects.wsp"
    Set FeatureName="ListProjects_Feature1"
    
    cd %DeploymentPackageFolder%
    powershell "& set-executionpolicy remotesigned"
    PowerShell -file .\DeploySolution.ps1 %SiteUrl% %SolutionFolder% %SolutionName% %FeatureName%
    
    pause
    
  4. Create the DeploySolution.ps1 file, and then copy in the following Windows PowerShell commands:

    $SiteUrl = $args[0]
    $SolutionFolder = $args[1]
    $SolutionName = $args[2]
    $FeatureName = $args[3]
    
    $AdminServiceName = "SPAdminV4"
    $IsAdminServiceRunning = $true;
    
    
    Add-PSSnapin microsoft.sharepoint.powershell
    
    if ($(Get-Service $AdminServiceName).Status -eq "Stopped")
    {
        $IsAdminServiceRunning = $false;
        Start-Service $AdminServiceName       
    }
    
    $SingleSiteCollection = Get-SPSite $SiteUrl
    
    Write-Host
    
    if ($FeatureName -ne $null)
    {
        Write-Host "Deactivating feature: $FeatureName" -NoNewline
            $WebAppsFeatureId = $(Get-SPFeature -limit all | ? {($_.displayname -eq $FeatureName)}).Id
            if (($WebAppsFeatureId -ne $null) -and ($SingleSiteCollection.Features | ? {$_.DefinitionId -eq $WebAppsFeatureId}))
            {
                Disable-SPFeature $FeatureName -Url $SiteUrl -Confirm:$false
            }
        Write-Host " - Done."
    }
    
    Write-Host "Rectracting solution: $SolutionName" -NoNewline
        $Solution = Get-SPSolution | ? {($_.Name -eq $SolutionName) -and ($_.Deployed -eq $true)}
        if ($Solution -ne $null)
        {
            if($Solution.ContainsWebApplicationResource)
            {
                Uninstall-SPSolution $SolutionName -AllWebApplications -Confirm:$false
            }
            else
            {
                Uninstall-SPSolution $SolutionName -Confirm:$false
            }
        }
    
        while ($Solution.JobExists)
        {
            Start-Sleep 2
        }
    Write-Host " - Done."
    
    Write-Host "Removing solution: $SolutionName" -NoNewline
        if ($(Get-SPSolution | ? {$_.Name -eq $SolutionName}).Deployed -eq $false)
        {
            Remove-SPSolution $SolutionName -Confirm:$false
        } 
    Write-Host " - Done."
    Write-Host
    
    Write-Host "Adding solution: $SolutionName" -NoNewline
        $SolutionPath = $SolutionFolder + $SolutionName
        Add-SPSolution $SolutionPath | Out-Null
    Write-Host " - Done."
    
    Write-Host "Deploying solution: $SolutionName" -NoNewline
        $Solution = Get-SPSolution | ? {($_.Name -eq $SolutionName) -and ($_.Deployed -eq $false)}
        if(($Solution -ne $null) -and ($Solution.ContainsWebApplicationResource))
        {
            Install-SPSolution $SolutionName -AllWebApplications -GACDeployment -Confirm:$false
        }
        else
        {
            Install-SPSolution $SolutionName -GACDeployment -Confirm:$false
        }
        while ($Solution.Deployed -eq $false)
        {
            Start-Sleep 2
        }
    Write-Host " - Done."
    
    if (-not $IsAdminServiceRunning)
    {
        Stop-Service $AdminServiceName     
    }
    Echo Finished
    
  5. Open a Command Prompt window as an administrator, and then run DeployListProjects.cmd. The script sets the execution policy for Windows PowerShell, and then runs the DeploySolution.ps1 file.

For more information about SharePoint solutions, see Installation and Deployment of a Farm Solution in SharePoint 2010.

Adding ListProjects to a Project Details Page

Adding a Web Part to a PDP is essentially the same procedure as adding the Web Part to another Project Web App page.

Procedure 8. To add the ListProjects Web Part to a PDP

  1. To edit a PDP, open the Server Settings page in Project Web App, and then click Project Detail Pages in the Workflow and Project Detail Pages section to display the library of PDP pages.

    Note

    Because Project Web App pages and PDPs use Project Server assemblies, project context data, and other features that Microsoft SharePoint Designer 2010 does not support, the pages are not editable in SharePoint Designer. Therefore, if you select a page in the PDP Library and then click Edit Document on the Library Tools\Documents tab, SharePoint Designer shows the Web Site Editing is Disabled dialog box.

  2. Click the PDP name in the library. For example, click ProjectInformation to show the Project Information page. You can edit the page by using the Site Actions menu on the page, as previously described.

  3. To use the edited Project Information PDP, on the Project Center page in Project Web App, click New, and then click Sample Proposal. The Sample Proposal workflow shows the Project Information PDP (Figure 6).

    Figure 6. Using the ListProjects Web Part in the Project Information PDP

    Using the ListProjects Web Part in a PDP

Adding ListProjects to a Project Site Page

Because the GetPWASiteUrl method in the ListProjectsPartUserControl class can return the URL of the Project Web App site that sponsors the project site, you can add the same ListProjects Web Part in a project site (Figure 7). In the SetClientEndpoints method, the EndpointAddress of the ListProjects PSI extension includes the base Project Web App URL.

The procedure to add the Web Part to a project site is the same as previously described, by using the Site Actions menu on the page.

Figure 7. Using the ListProjects Web Part in a project site

Using the ListProjects Web Part in a project site

Web Parts that are built on the ASP.NET framework can use the built-in PSI services in addition to PSI extensions. By correctly setting the WCF endpoint address, the same Web Part can be added to Project Web App pages, project detail pages, and project sites.

Next Steps

Programming the JS Grid control can be complex, because some features (such as column sorting) require you to use JavaScript to set a callback delegate and to use Microsoft Visual C# or Visual Basic to implement the callback. However, the JS Grid control has advantages over other grid controls because it is extremely flexible and can perform many functions without a post-back to the server. For a complete solution of the ListProjects Web Part that implements column sorting, see the Samples\WebParts\ListProjects_Sorting subdirectory in the Project 2010 SDK download.

There are many other features of the JS Grid control that can add to the value of a Web Part. For information about using events in the JS Grid control, see Walkthrough: Customizing the PWA Ribbon and Accessing the JS Grid. For more information, see JS Grid Control in the SharePoint 2010 SDK.

See Also

Tasks

How to: Create a PSI Extension to Read Custom Fields in the RDB

Walkthrough: Customizing the PWA Ribbon and Accessing the JS Grid

Other Resources

Developing Project Server Web Parts

JS Grid Control

Tips for Implementing the JS Grid Control

How to: Create a Basic JS Grid Control

Microsoft Project 2010 Solution Starters

Developing Custom Solutions in PWA

User Interface Customization Resource Center | SharePoint 2010