SharePoint 2010 + AJAX + Partial rendering

In one of our team’s recent SharePoint projects I was asked to create friendly-looking
panel which would aggregate system notifications sent to currently logged user plus display some graphical representation
of currently running processes started by this user.

Few problems occured:
– there were large volumes of data to display (hundreds or thounsands of orders)
– clients were using very slow PCs
– the system was implemented in SharePoint 2010 (no lightweight ASP.NET MVC-like partial render options)

It would be terribly time consuming to render HTML for every single order (or even just one page of results)
because of DB queries required to generate view and ammount of HTML code which would be sent to users’ browsers.

So here is the solution. It’s actually an attempt to use ASP.NET MVC -like partial rendering + AJAX in SharePoint 2010 environment.

Part 1. HTML placeholder

<a class="header ajax_enabled corner"href="#"  order_id="<%= Model.OrderId %>" is_loaded="0">
    <%= Model.TitleDescription %><!-- when user clicks this element for the first time, AJAX request will download widget layout from server and place it in 'ajax_process_placeholder' div -->
</a>

<divclass="ajax_process_placeholder">        
    <!-- here goes AJAX-retrieved content -->
</div>

Par2. JavaScript code:

function ConfigureAjaxProcessPlaceholder() {
    //script that initializes AJAX behavior of UI widget

    //when user clicks widget header...
    $("div.panel a.header.ajax_enabled").click(function () {
        var parent = $(this);
        var orderId = parent.attr("order_id"); //get order identifier
        var isLoaded = parent.attr("is_loaded"); //check if this element was already loaded

        var placeholder = parent.parent().next("div.ajax_process_placeholder");

        //if this element has not yet been loaded 
        if (isLoaded != "1") {
            //shows some nice 'pleas wait.. loading' banner - hardcoded url is ugly but it's here just to show some concept
            placeholder.append("<img alt='' src='Style%20Library/images/progressbar.gif' />");

            //loads widgets HTML from server (prepared by HTTP Handler)
            $.ajax({
                type: "GET",
                url: "/_layouts/My.Site.Content/StartPanelHandler.ashx", //hardcoded url is bad practice but it's just an example 
                data: { OrderId: orderId, TimeStamp: new Date().getTime() },
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "html",
                success: function (msg) {

                    //do some jQuery magic: put widget to placeholder
                    var tmp = $("<div/>").html(msg);
                    placeholder.html("");
                    placeholder.append(tmp.find("div.cycle"));

                    //mark placeholder as already loaded. Next time it will be clicked, no AJAX request will be performed. Instead, hidden HTML content will be presented
                    parent.attr("is_loaded", "1");

                    //do animation & show element
                }
            });
        } else {
            //do animation & show element
        }
        return false;
    });
}

Part 3. Handler declaration ASHX file (which goes to SharePoint site “layouts” folder).

<%@ WebHandler Language="C#"  Class="My.Site.Content.ControlTemplates.Widgets.StartPanelHandler, My.Site.Content, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1010101010100101" %>

Part 4. Handler implementation. It does work of handling AJAX request and returning widget HTML.

using System;
using System.Web;
using My.Site.AccessData;
using System.Web.SessionState;
using System.Data.Linq;
using System.Web.UI;
using System.IO;

namespace My.Site.Content.ControlTemplates.Widgets
{
    public class StartPanelHandler : IHttpHandler, IRequiresSessionState
    {
        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return true; }
        }

        //handle AJAX request
        public void ProcessRequest(HttpContext context)
        {
            if (context.Request["OrderId"] == null)
            {
                ReturnObjectNotFound(context);
                return;
            }

            int orderId = Convert.ToInt32(context.Request["OrderId"]);

            using (var ctx = new DataContext(Settings.ConnectionString))
            {
                var feed = new OrdersFeedAdapter(ctx);

                var statusAdapter = new OrderProgressAdapter(feed);
                var statusModel = statusAdapter.GetModel(orderId, false);

                if (statusModel == null)
                    ReturnObjectNotFound(context);
                else
                    ReturnObjectView(statusModel, context);
            }
        }
        #endregion

        //ASP.NET MVC-like partial view render
        private void ReturnObjectView(OrderProgressModel statusModel, HttpContext context)
        {
            context.Response.ContentType = "text/html; charset=utf-8";

            var page = new Page();
            var ctrl = page.LoadControl("~/_CONTROLTEMPLATES/My.Site.Content/Widgets/OrderProgressWidget.ascx") as OrderProgressWidget;
            ctrl.Model = statusModel;
            page.Controls.Add(ctrl);

            using (var stream = new StreamWriter(context.Response.OutputStream))
            {
                HttpContext.Current.Server.Execute(page, stream, false);
                stream.Flush();
                stream.Close();

                context.Response.End();
            }
        }

        private void ReturnObjectNotFound(HttpContext context)
        {
            context.Response.ContentType = "text/html; charset=utf-8";
            context.Response.Write("<span class='error'>Object not found or access is denied</span>");
        }
    }
}

So, what happens here is:

When user clicks widget placeholder for the first time, AJAX request is fired. This request is handled by ASP.NET HTTP Handler which prepares HTML content of widget (depending on Order ID parameter). TimeStamp parameter is added to the request in order to avoid the request result being cached by IE browsers. Then HTML content of widget is returned and put into the corresponding placeholder. Then the placeholder is marked as “loaded” so when user clicks it again no AJAX request will be fired because HTML code is already in there and all we need to do is to show this hidden code.

That did the trick – performance is not a problem, customer is satisfied 😉

Advertisements