Connected WebParts talking without Post-Backs

 

Introduction

imageLately I have started diving into the deep waters of SharePoint 2010 programming. While working through the chapters of Sahil Malik’s book (Building Solutions for SP2010) you could find yourself losing foothold and feel be endangered to drown in those dark development waters. This is especially the case the author challenges the reader while leaving the raised questions unanswered. Then it is up to you either to remain in the safe shore’s vicinity or dive deep in search of the right answers. So here are a couple of those Sahil Malik’s challenges I’m going to muse about.

The issue is about connected WebParts which is mostly well understood. Being new in technology here is a starter article to work through in a few minutes and complete enjoying the success: Connect Web Parts in SharePoint 2010

Challenges

The mentioned book guides the reader in a little bit advanced manner and combines the above story with Silverlight and Ajax as well. To be more precise, Sahil Malik presents in his book’s Chapter 5 one particular solution which consist of a Web-Part Provider and a Web-Part Consumer whereas the Provider hosts a Silverlight object instance which is going to talk to the Consumer Web-Part without post-backs, meaning using purely client-side scripts. The example works fine however it has a few impacts you have to resolve (includes the challenges):

  1. The book presents the example using Telerik’s Scheduler Silverlight-RadControl for which you have to register and download the free trial valid for some 60 days or so. I went through those steps which thereafter I have got a dozen of Telerik follow-up E-Mails inviting you to buy. So the challenge number one is to do this example using the available within Visual Studio IDE Silverlight Controls.
  2. The second problem with this solution is the fact, that only a sole Web-Part Consumer will be served well by the Silverlight Web-Part Provider. To be more precise let me explain this in a detail.
    1. If you create interconnected Web-Parts, there is normally no limitation how many Consumer instances could be served. Let’s add one Provider instance and multiple Consumer instances. If the Web-Parts were developed in a classic server-side control manner (run at server), this works fine; the Provider will post-back and all the Consumer instances will receive the messages raised by that Provider.
    2. The beauty presented by Sahil Malik here is to do the very same without those awful pots-backs which however complicates the matter in an unexpectedly nasty way and it was really challenging to figure the right solution – which at the end of the day is not as hard as I thought it will be.

To start with just refresh the prerequisites. The task’s infrastructure assumes a new SharePoint List created which is based on a Calendar type template. This is the sole information-source our custom Web-Parts will rely on and read the events found in this Calendar-List location. If you later put the Web-Parts pairs onto the Site’s Page, the result will look similarly like depicted in Picture 1. So you select some date in Telerik’s Scheduler which takes the Events from the previously created Calendar-List source. Then you select some Event (see here J.W.G.) and clicking on the wide “Show Detals >>” Silverlight-Button the Consumer Web-Part will display the Details.

 image

Picture 1

So the crazy problem starts while adding more than one Consumer Web-Part. While this would work greatly using posting-back Web-Parts, in this example you will face a confusing situation, whereas only the lastly added Consumer Web-Part will correctly receive the messages sent by the Provider and the rest will go empty (Picture 2). 

image

Picture 2

Let’s start with the easier part and replace first only the Telerik-Control (which looks great) with some pretty good-enough standard Silverlight controls. The code I’m talking here about you can of course download using a link at the bottom of this article. Here the page (Picture 3) which does the very same like the above using a single Calendar control, one ListBox control and one Button control. Start selecting the particular Date in the Calendar control and clicking on “Get from SharePoint” button which will fill the ListBox with the Events found at our mentioned Source-List. Right now selecting some item in the ListBox will trigger a SelectionChanged-Event and Silverlight will engage some JavaScript code in order to send the details to the subscribed Consumer.

image

Picture 3

As the next Picture 4 confirms, this time my solution works even at the conditions of multiple Consumer Web-Parts whereas the higher the red numbers the later the Consumer Web-Part was added. Please note in the Title some random four character suffixes which will play essential role in the resolution.

image

Picture 4

Analysis

So the Problem displayed in Picture 2 is related to the fact that while using JavaScript in order to reach each others without awful post-backs, globally scoped JavaScript entries (variables, functions) must be uniquely identifiable for each particular instance. While using very classic posting-back Web-Parts, the server takes care of all instances and developers are not affected too much by the fact of multiple co-existing instances on a very same ASP.NET Page.

So we have here two problems to address

  1. First of all the Provider has to take care about the list of subscribed Consumers and for each Web-Part the Provider has to inject the appropriate unique JavaScript code intended to be called by the Silverlight Control
  2. The second problem is the uniqueness on the Consumer side as well and whenever the Consumer is going to inject his part of the JavaScript into the HTML markup, it has to be uniquely distinguishable. Furthermore the unique ID must be shared between Provider and Subscriber

The Resolution

Provider part

Sahil Malik gives us some hints, which however by far do not provide the complete solution. One of the problems to resolve is maintaining the list of subscribed clients (Consumers). Here I’m going to compare the book’s original example with my multiple Consumer instances aware version. In Code 1 (original) the Provider takes care of a single Consumer client using the displayClientID variable. Also note, the RenderContents method injects the JavaScript function “sendmessage” into the Page which is intended to be called by the Silverlight control.

In this original version (Code 1) the book author already gives to the readers the generic idea concerning JavaScript-Code uniqueness in the form of the  displayClientID variable which is sent by the subscribed Consumer and here the Provider uses this ID to make the DisplayData JavaScript function uniquely distinguishable. So make sure you understand how the prefix is built and will replace the [clientID] placeholder.

[ToolboxItemAttribute(false)]
public class ScheduleOverview : WebPart, IAppointmentDisplay
{
    private string displayClientID = String.Empty;
    private string clientScript =
       
@”function sendmessage(selectedID)
        {
        var methodName = ‘[clientID]’ + ‘DisplayData’;
        eval(methodName + ‘(‘ + selectedID + ‘)’);
        }”
;

        protected override void RenderContents(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(“…”);
            sb.AppendLine(“…”);
            …
            sb.AppendLine(“</object>”);

            // Injecting javascript;
            if (displayClientID.Length != 0)
            {
                sb.AppendLine(“<script language=\”javascript\”>”);
                sb.Append(clientScript.Replace(“[clientID]”, displayClientID));
                sb.AppendLine(“</script>”);
            }
            writer.Write(sb.ToString());
            base.RenderContents(writer);
        }
        public void AddListener(string clientID) {

            displayClientID = clientID;
        }
        [ConnectionProvider(“Appointment Display Provider”)]
        public IAppointmentDisplay GetAppointmentDisplayCommunicationPoint()
        {
            return this as IAppointmentDisplay;
        }

}
 
Code 1
 
In the effort making the Provider multiple Consumer aware first of all replace the single string variable holding the client’s identity with a generic list of client string IDs (Code 2). Furthermore we have to inject for each subscribed Consumer the appropriate function within the single sendmessage JavaScript function which is done looping over the generic list of clients (see the RenderContents method).
 
Furthermore note the GetAppointmentDisplayCommunicationPoint method’s extended list of ConectionProvider-Attribute parameters allowing explicitly to accept multiple incoming connection requests.
 
[ToolboxItemAttribute(false)]
public class Scheduler2Overview : WebPart, IAppointmentDisplay
{
    private List<string> displayClientIds = new List<string>();
    protected override void RenderContents(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(“…”);
            …
            sb.AppendLine(“</object>”);

            // Injecting javascript
            if (displayClientIds.Count > 0)
            {
                sb.AppendLine(“<script language=\”javascript\”>”);
                sb.AppendLine(“function sendmessage(selectedID){“);
                foreach (string displayClientID in displayClientIds)
                {
                    if (displayClientID.Length != 0)
                    {
                        sb.Append(displayClientID);
                        sb.Append(“DisplayData(selectedID);”);
                        sb.Append(Environment.NewLine);
                    }
                }
                sb.AppendLine(“}”);
                sb.AppendLine(“</script>”);
            }

            writer.Write(sb.ToString());
            base.RenderContents(writer);
        }
        public void AddListener(string clientID){
            displayClientIds.Add(clientID);
        }
        [ConnectionProvider(“Display Details New Provider”, “DetailsNewProvider”, AllowsMultipleConnections = true)]
        public IAppointmentDisplay GetAppointmentDisplayCommunicationPoint()
        {
            return this as IAppointmentDisplay;
        }

}

 
Code 2
 

Consumer part

The original Consumer named “IndividualAppointment” consists of a Web-Part and a coupled SharePoint-Module (Picture 5) which sole purpose is to keep the appointment.js file in the file-system. Instantiating the Consumer module will load that appointment.js and inject the contained JavaScript code into the Page.

image

Picture 5

My version of the Solution abandons this coupling and rejects the module. The question is why? I do not have any bad feelings against a module, however in this particular case it turns out to be an additional burden for making the Consumer multiple instances aware. The exact reason is, that the appointment.js contains at least two global variables and three functions. At the end of the day we have to guarantee the overall uniqueness all of them. For the shake of simplicity therefore it is more convenient to keep the JavaScript code within the Web-Part, which has to be later tailored appropriately. So the structure of my Project changes slightly (Picture 6).

image

Picture 6

Take a second look on the JavaScript code (Code 3) which was extended with a quite large number of [clientID] placeholders in order to insert later the Consumer clients real identity. Also note how this script is injected into the page using RegisterClientScriptBlock() within the CreateChildControls method (Code 4). The important parts of the code are highlighted. Make sure you understand how the placeholders are replaced with the content of the pseudoRandomID variable.

string baseScript = @”
var [clientID]idToFetch = 1;
var [clientID]appointments;

function [clientID]FetchAppointment() {
    var context = SP.ClientContext.get_current();
    var site = context.get_web();
    var list = context.get_web().get_lists().getByTitle(“”Appointment””);
    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml(“”<View><Query><Where><Eq><FieldRef Name=’ID’/><Value Type=’Number’>”” + [clientID]idToFetch + “”</Value></Eq></Where></Query></View>””)
    [clientID]appointments = list.getItems(camlQuery);
    context.load([clientID]appointments);
    context.executeQueryAsync([clientID]onSucceeded, [clientID]onFailed);
}

function [clientID]onSucceeded(sender, args) {
    var listItemEnumerator = [clientID]appointments.getEnumerator();
    while (listItemEnumerator.moveNext()) {
        var listItem = listItemEnumerator.get_current();
        $get(“”[clientID]appointmentDetails””).innerHTML = “”<b>”” + listItem.get_item(‘Title’) + “”</b><br/>”” + listItem.get_item(‘Description’);
    }
}

function [clientID]onFailed(sender, args) {
    alert(‘request failed’ + args.get_message() + ‘\n’ + args.get_stackTrace());
}”;

Code 3

private string individualScriptID = “[clientID]IndividualAppointmentScript”;
        protected override void CreateChildControls()
        {
            // This next line adds Framework Suppoprt for scripting
            this.Controls.Add(
                        new ScriptLink()
                        {
                            ID = “SPScriptLink”,
                            Localizable = false,
                            LoadAfterUI = true,
                            Name = “sp.js”
                        }
            );

            this.Page.ClientScript.RegisterClientScriptBlock(
                    this.GetType(),
                    individualScriptID.Replace(“[clientID]”, pseudoRandomID),
                    baseScript.Replace(“[clientID]”, pseudoRandomID), true);

            this.Title = “DetailsViewWP_” + pseudoRandomID;
        }

 

Code 4

Each time the Server instantiates a new DetailsView2WebPart class (Consumer) a new pseudo random prefix named pseudoRandomID is created  and sent to the Provider. I have a little bit poked around the original pseudo random string generation algorithm, as I noted, that under some circumstances the Random class tended to generate the very same variables in subsequent turns again and again (being truly just pseudo-random). So in my version I’m feeding the Random class’s constructor with a randomly generated hash value, which helped greatly generating distinguishable strings (Code 5). 

Random random = new Random(Guid.NewGuid().GetHashCode());

Code 5

Next take a look on how the Consumer’s RenderContents method have changed (Code 6) with special care on the Id of the “div” markup. It is overall important to note, that each newly instantiated Consumer has to provide it’s uniquely distinguishable markup for the same reason like it is in the case of JavScript code. Please compare the highlighted with green color parts in Code 3 and Code 6.  Note that Code 3 addresses in case of success the div-markup in order to render the success-results and Code 6 is the part which prepares the target for that action.

        protected override void RenderContents(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            sb = new StringBuilder(
              “<div id=\”[clientID]appointmentDetails\”/>”);
            sb.AppendLine(“<script language=\”javascript\”>”);
            sb.Append(
@”
                function [clientID]DisplayData(appointmentID)
                {
                    [clientID]idToFetch = appointmentID ;
                    [clientID]FetchAppointment() ;
                }”

                );
            sb.AppendLine(“</script>”);
            writer.Write(sb.ToString().Replace(“[clientID]”, pseudoRandomID));
            base.RenderContents(writer);
        }

Code 6

Running the code

The succeeded run of the above code depicts Picture 4. It is however perhaps interesting to check what exactly has been injected into the page. For this reason we could scrutinize the source of the page and look for the sendmessage JavaScript function (Picture 7). Please note the three generated xxxxDisplayData calls prefixed with that four character wide pseudo random identities. Also note (Picture 8) the div-markup and the exact content one of those xxxxDisplayData function calling xxxxFetchAppointment().

image

Picture 7

image

Picture 8

Here is the link to download the complete Solution:

http://cid-8d365142bc4869ab.office.live.com/self.aspx/.Documents/SP2010%5E_SilvScheduler3.zip

Advertisements
This entry was posted in SharePoint 2010. Bookmark the permalink.

One Response to Connected WebParts talking without Post-Backs

  1. Pingback: Cleaning up features | Stefan's musings

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s