Wesley Bakker

Interesting things I encounter doing my job...

Sponsors

News

Wesley Bakker
motion10
Rivium Quadrant 151
2909 LC Capelle aan den IJssel
Region of Rotterdam
The Netherlands
Phone: +31 10 2351035

(feel free to chat with me)
 

Add to Technorati Favorites

IListConsumer

This is part three in a series of post on how to create a Virtual Earth Web Part. In this post I’ll show you how to connect your web parts to SharePoint list view web parts. It’s not that simple but we’ll manage.

IWebPartTable

The first thing you might ask yourselves is: “Why use the obsolete IListConsumer interface instead of its replacement called IWebPartTable?”. Well there is a very good reason for this. If I would create both the connection provider and the connection consumer I would use the newer interface indeed. Unfortunately most of my data in SharePoint obviously comes from SharePoint lists. So the way to connect my web part to my lists is by a connection to a list view web part. And the list view web part does not implement the IWebPartTable interface but the IListProvider instead.

So we need to implement the IListConsumer interface if we want to connect to a SharePoint list view web part.

IListConsumer

The IListConsumer interface consists of just three methods:

ListReady
Event handler that gets called when the list is delivered.
PartialListReady
Event handler that gets called when the list is ready to be delivered. Comes in handy if you have great volumes of data.
ListProviderInit
Event handler that gets called when the list provider is initiated.

In our case we need only the ListReady. We are simply going to add all items we get to our list of pushpins.

Register the interface

We do have to take care of some things though before we can even connect to our web part. Implementing the interface is not enough, we need to register our list consumer web part to the page. This can be done by overriding the public virtual void EnsureInterfaces:

/// <summary>
/// Provides notification for a connectable Web Part that it should ensure all its interfaces are registered using the <see cref="Overload:Microsoft.SharePoint.WebPartPages.WebPart.RegisterInterface"></see> method.
/// </summary>
[Obsolete("You got to use this so called 'Obsolete' interfaces if you want to connect to default Sharepoint list webparts")]
public override void EnsureInterfaces() {
    base.EnsureInterfaces();
    RegisterInterface(_listConsumerInterfaceName,                           //InterfaceName    
            InterfaceTypes.IListConsumer,                                   //InterfaceType
            Microsoft.SharePoint.WebPartPages.WebPart.LimitOneConnection,   //MaxConnections
            ConnectionRunAt.Server,                                         //RunAtOptions
            this,                                                           //InterfaceObject
            "",                                                             //InterfaceClientReference
            "Consume List From",                                            //MenuLabel
            "Consumes a list from another Web Part.");                      //Description
}
 
/// <summary>
/// Returns a value that indicates where the implementation of a connection interface can run.
/// </summary>
/// <returns>
/// A <see cref="T:Microsoft.SharePoint.WebPartPages.Communication.ConnectionRunAt"></see> enumeration value.
/// </returns>
[Obsolete("You got to use this so called 'Obsolete' interfaces if you want to connect to default Sharepoint list webparts")]
public override ConnectionRunAt CanRunAt() {
    return ConnectionRunAt.Server;
}

We do this to get let the page know that we have a connection consumer. The CanRunAt override tells the page that our web part needs a post back because it runs at the server. Once we implemented the interface and registered our interface web part to the page it will show up in the web part menus.

ConnectionsMenu

 

Working with the DataTable

The goal of all this we are doing is to retrieve a DataTable from a connected web part. The data table contains columns which contain the values we need so badly in our virtual earth web part. Let us first store the data table somewhere and make sure we know it when we are connected:

private static readonly string _listConsumerInterfaceName = "VirtualEarthListConsumerInterface";
private bool _connectedToDataTable = false;
private DataTable _connectedWebPartDataTable;
 
/// <summary>
/// Used to notify a Web Part that it has been connected to another Web Part.
/// </summary>
/// <param name="interfaceName">Specifies the name of the interface on this Web Part that is being connected.</param>
/// <param name="connectedPart">A <see cref="T:Microsoft.SharePoint.WebPartPages.WebPart"></see> that specifies the other Web Part that is being connected to.</param>
/// <param name="connectedInterfaceName">Specifies the name of the interface that is being connected to on the other Web Part.</param>
/// <param name="runAt">A <see cref="T:Microsoft.SharePoint.WebPartPages.Communication.ConnectionRunAt"></see>   value that specifies where the interface on this Web Part should run.</param>
[Obsolete("You got to use this so called 'Obsolete' interfaces if you want to connect to default Sharepoint list webparts")]
public override void PartCommunicationConnect(string interfaceName, Microsoft.SharePoint.WebPartPages.WebPart connectedPart, string connectedInterfaceName, ConnectionRunAt runAt) {
    if (interfaceName == _listConsumerInterfaceName) {
        _connectedToDataTable = true;
    }
}
 
/// <summary>
/// Provides an event handler for the <see cref="E:Microsoft.SharePoint.WebPartPages.Communication.IListProvider.ListReady"></see> event of a Web Part that implements the <see cref="T:Microsoft.SharePoint.WebPartPages.Communication.IListProvider"></see> interface.
/// </summary>
/// <param name="sender">A Web Part that implements the IListProvider interface.</param>
/// <param name="listReadyEventArgs">A ListReadyEventArgs that provides the list contents.</param>
[Obsolete("You got to use this so called 'Obsolete' interfaces if you want to connect to default Sharepoint list webparts")]
public void ListReady(object sender, ListReadyEventArgs listReadyEventArgs) {
    if (listReadyEventArgs.List != null) {
        _connectedWebPartDataTable = listReadyEventArgs.List;
    }
}

For brevity I left the PartialListReady and ListProviderInit methods which are empty.

Working with the Fields

Well, we do have the data table know. Unfortunately we still can’t do a lot with it. That’s because the interface doesn’t define the names of the columns that contain the data. We don’t know the names of these columns during compile time so we need a way to give the client the option to define which columns contain which data. Because the clients know these columns as Fields in SharePoint we refer to them as fields. So our goal with the next code is to give the end user the possibility to define which field contains the latitude, which field contains the longitude etc.:

private string _defaultImage;
private string _titleFieldName = "LinkTitle";
private string _descriptionFieldName = "InfoBoxHtml";
private string _imageFieldName = "PushpinImage";
private string _latitudeFieldName = "Latitude";
private string _longitudeFieldName = "Longitude";
 
/// <summary>
/// Gets or sets the default image.
/// </summary>
/// <value>The default image.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("Default Image")]
[Description("The default image that's used for pushpins that get added by the connection.")]
public string DefaultImage {
    get {
        return _defaultImage;
    }
    set {
        _defaultImage = value;
    }
}
 
/// <summary>
/// Gets or sets the name of the title field.
/// </summary>
/// <value>The name of the title field.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("Title Field Name")]
[Description("The name of the field that contains the title for the pushpins.")]
public string TitleFieldName {
    get {
        return _titleFieldName;
    }
    set {
        _titleFieldName = value;
    }
}
 
/// <summary>
/// Gets or sets the name of the info window HTML field.
/// </summary>
/// <value>The name of the info window HTML field.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("InfoBoxHtml Field Name")]
[Description("The name of the field that contains the info box HTML for the pushpins.")]
public string InfoBoxHtmlFieldName {
    get {
        return _descriptionFieldName;
    }
    set {
        _descriptionFieldName = value;
    }
}
 
/// <summary>
/// Gets or sets the name of the latitude field.
/// </summary>
/// <value>The name of the latitude field.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("Latitude Field Name")]
[Description("The name of the field that contains the latitude for the pushpins.")]
public string LatitudeFieldName {
    get {
        return _latitudeFieldName;
    }
    set {
        if (string.IsNullOrEmpty(value)) {
            throw new WebPartPageUserException("The Latitude Field Name property cannot be null or empty");
        }
 
        _latitudeFieldName = value;
    }
}
 
/// <summary>
/// Gets or sets the name of the longitude field.
/// </summary>
/// <value>The name of the longitude field.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("Longitude Field Name")]
[Description("The name of the field that contains the longitude for the pushpins.")]
public string LongitudeFieldName {
    get {
        return _longitudeFieldName;
    }
    set {
        if (string.IsNullOrEmpty(value)) {
            throw new WebPartPageUserException("The Longitude Field Name property cannot be null or empty");
        }
 
        _longitudeFieldName = value;
    }
}
 
/// <summary>
/// Gets or sets the name of the image field.
/// </summary>
/// <value>The name of the image field.</value>
[Browsable(true)]
[Category("Connection Settings")]
[WebPartStorage(Storage.Shared)]
[FriendlyName("Image Field Name")]
[Description("The name of the field that contains the image for the pushpins.")]
public string ImageFieldName {
    get {
        return _imageFieldName;
    }
    set {
        _imageFieldName = value;
    }
}

After we add this code to our web part we have some new properties in our edit part categorized under “Connection Settings”.

ConnectionSettings

What next?

With the field name properties set we have both the data in our data table as well as the meta data that tells us which columns contain which data. So we can actually add all our pushpins to our pushpins property. We do this by overriding the OnPreRender method:

/// <summary>
/// The event handler for the System.Web.UI.Control.PreRender event that occurs immediately before the Web Part is rendered to the Web Part Page it is contained on.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    
    List<VirtualEarthPushpin> pushpinsToAdd = new List<VirtualEarthPushpin>();
    if (_connectedToDataTable && _connectedWebPartDataTable != null) {
        try {
            pushpinsToAdd = pushpinsToAdd.Union(GetPushpinsFromConnectedDataTableWebPart()).ToList();
        }
        catch (WebPartPageUserException ex) {
            this.Controls.Add(new WebPartErrorControl(this.ID, ex.Message));
            return;
        }
    }
}
 
private List<VirtualEarthPushpin> GetPushpinsFromConnectedDataTableWebPart() {
    List<VirtualEarthPushpin> retVal = new List<VirtualEarthPushpin>();
 
    bool hasTitle = FieldExistsInDataTable(this.TitleFieldName);
    bool hasInfoBoxHtml = FieldExistsInDataTable(this.InfoBoxHtmlFieldName);
    bool hasLatitude = FieldExistsInDataTable(this.LatitudeFieldName);
    bool hasLongitude = FieldExistsInDataTable(this.LongitudeFieldName);
    bool hasImage = FieldExistsInDataTable(this.ImageFieldName);
 
    if (!hasLatitude || !hasLongitude) {
        throw new WebPartPageUserException("The Virtual Earth webpart is connected but the LatitudeFieldName or the LongitudeFieldName is not set properly to an existing field.");
    }
 
    foreach (DataRow row in _connectedWebPartDataTable.Rows) {
        VirtualEarthPushpin pushpin = new VirtualEarthPushpin();
        pushpin.Title = (hasTitle) ? row[this.TitleFieldName].ToString() : null;
        pushpin.InfoBoxHtml = (hasInfoBoxHtml) ? row[this.InfoBoxHtmlFieldName].ToString() : null;
 
        float latitude = 0;
        if (float.TryParse(row[this.LatitudeFieldName].ToString(), out latitude)) {
            pushpin.Latitude = latitude;
        }
 
        float longitude = 0;
        if (float.TryParse(row[this.LongitudeFieldName].ToString(), out longitude)) {
            pushpin.Longitude = longitude;
        }
 
        pushpin.Image = (hasImage) ? row[this.ImageFieldName].ToString() : this.DefaultImage;
 
        retVal.Add(pushpin);
    }
 
    return retVal;
}

The code is pretty straight forward. Bulk of it is is in the GetPushpinsFromConnectedDataTableWebPart. We first check which fields are actually there in the data table. That’s because only latitude and longitude are required and the rest is optional. We check if we do have a latitude and longitude and after that we’ll simply add the pushpins to our list. For brevity the helper methods are not in the code and the registration of JavaScript to the page is omitted as well. We will discuss JavaScript in the next post.

Conclusion

We have a web part now that is connectable to a SharePoint list view web part, because we implemented and registered the IListConsumer interface. We created properties that enables some configuration of the connection and we turned our data table into a generic list.

It was some work and it’s a bit of a tedious job to do. Next week we’ll add the JavaScript to finish up our Virtual Earth web part. So be sure to read next weeks post because it will contain the complete code for our web part. One week later we’ll package it all in a SharePoint solution package.

Cheers,

Wes

Comments

IListConsumer - Wesley Bakker said:

Pingback from  IListConsumer - Wesley Bakker

# February 9, 2009 9:14 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)