Ajax webpart: Register scripts on UpdatePanel postbacks

February 25, 2008

These days I’m developing custom ajax enabled webparts for a sharepoint solution. I’ve created a base webpart class named AjaxEnabledWebpart from which all the webparts inherit. This webpart has an updatepanel control where all the child webparts are supposed to create the controls they need so finally, everycontrol is rendered inside the updatepanel and the webpart has the expected asp.net ajax behaviour of partial rendering the pages. Some of the webparts have to render content created dinamically by javascript code as you will see later in the code, in this case, content is rendered this way because we are supposed to give an accessible front end in our webpart (this is supporting non-script enabled browsers) so we are using the typical <noscript><script> tag approach. However, the problem we faced is that the content rendering script code (or any script code at all) was not executed during Ajax postbacks of the webpart. The solution we found to this problem was to register the scripts with the Sys.WebForms.PageRequestManager.getInstance().add_endRequest() function. Following is the code we used. First, for reference only, I’ll post the main code of the AjaxEnabledWebpart which somebody might find useful

protected override void CreateChildControls()
{
    base.CreateChildControls();

    EnsurePanelFix();

    _updatePanel = new UpdatePanel();

    _scriptHandler = ScriptManager.GetCurrent(this.Page);
    if (_scriptHandler == null)
    {
        //no script manager registered
        _scriptHandler = new ScriptManager();
        _scriptHandler.ID = "scriptManager";
        this.Controls.Add(_scriptHandler);
    }

    _updatePanel.ID = "updatePanel_";
    _updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
    _updatePanel.ChildrenAsTriggers = true;

    CreatePanelControls(_updatePanel.ContentTemplateContainer.Controls);

    this.Controls.Add(_updatePanel);
}

protected virtual void CreatePanelControls(ControlCollection PanelControls)
{
}

The only relevant code from the AjaxEnabledWebpart is the CreateChildControls method, which creates the UpdatePanel control and calls the virtual method CreatePanelControls passing as a parameter the control collection where child pages are supposed to add the controls. Also, you can find the EnsurePanelFix implementation on my previous post at this linkAfter that, here is the code of a custom HtmlTable used in some webparts which contents are rendered using javascript code (if scripting is enabled in the client browser). The posted code is a function which returns the html code to be inserted in a cell’s InnerHtml property of a table:

private string _getSelectAllHtml(HtmlTableCell cell)
{
    string ret = "";

    ret += "" +
                "select all" +
             "" +
             "" +
                "// " +
            "" +
           "";

    string fn = "" +
                    "//<!&#91;CDATA&#91; n" +
                        "function _loadSelectAllHeader() { " +
                            "cab = '<a href="#" title="CellTitle">" +
                            "<img src="_layouts/img/selectall.gif" alt="select all">';" +
                            "var capa = document.getElementById("" + cell.Attributes&#91;"Id"&#93; + "");" +
                            "capa.innerHTML = cab;n" +
                        " }" +
                    "//&#93;&#93;> " +
                "";

    Page.ClientScript.RegisterClientScriptBlock(typeof(ResultsTableMod47),
                                "_loadSelectAllHeader",
                                fn);

    Page.ClientScript.RegisterStartupScript(typeof(ResultsTableMod47),
                                "initRequestScriptHandler_loadSelectAllHeader",
                                "Sys.WebForms.PageRequestManager.getInstance().add_endRequest(_loadSelectAllHeader);");

    return ret;
}

The relevant lines of the code are the calls to RegisterClientScriptBlock and RegisterStartupScript methods. The first one register our content-rendering script while the second one adds this function to the end_request handler of the PageRequestManager instance. This will ensure that the script is called after each Ajax postback of the pages.Further scripts can be registered in the end_request handler by calling the add_endRequest method for each script we want to be executed after a postback. However, we have to considered that this script will be executed for all the postbacks and not only for the ones generated by this webpart’s updatepanel control, so if more than one updatepanel is present on the page, we might have to include some checking at the startup of our code to ensure that our script must be executed. In this case this was not necessary since the script could be executed again without any problem.

That’s all for now ! I hope it’s useful…. coments are welcome 🙂


Ajax enabled webpart not working on second postback

February 20, 2008

Trying to develope an Ajax webpart with aspx.net 2.0 I came up with this msdn example  which seemed to be exactly what I was looking for. I followed the steps suggested by the article but I ended up with some weird behaviour from the webpart when finally deployed in my Sharepoint server.

Apparently everything was working as expected and when I clicked the webpart the first time, the ajax method was correctly executed and the “Hello! XXX” text succesfully displayed. However, clicking again the button didn’t seem to do anything, also, other page actions that should submit the form and do some action (like the “Edit Page” item from the “Site Actions” menu) stopped working.

I finally found that the problem was in the EnsurePanelFix function of the example which was not working as expected

private void EnsurePanelFix()
{
   if (this.Page.Form != null)
   {
     String fixupScript = @"
     _spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");
     function _initFormActionAjax()
     {
       if (_spEscapedFormAction == document.forms[0].action)
       {
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }
     var RestoreToOriginalFormActionCore =
       RestoreToOriginalFormAction;
     RestoreToOriginalFormAction = function()
     {
       if (_spOriginalFormAction != null)
       {
         RestoreToOriginalFormActionCore();
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }";
   ScriptManager.RegisterStartupScript(this,
     typeof(SayHelloWebPart), "UpdatePanelFixup",
     fixupScript, true);
   }
}

So what I did is to provide my own implementation of that function. What we needed is to invalidate the onSubmit wrapper that Sharepoint calls in order to ensure some callback scenarios, this can be done by setting to true the _spSuppressFormOnSubmitWrapper variable so I replaced the original function by the following implementation

private void EnsurePanelFix()
        {
              ScriptManager.RegisterStartupScript
                (this,
                 typeof(AjaxEnabledWebpart),
                 "UpdatePanelFixup",
                 "_spOriginalFormAction = document.forms[0].action; _spSuppressFormOnSubmitWrapper=true;",
                 true);
}

And that’s it ! my Ajax webpart is working properly no matter how many times we click the Hello World ! button 🙂


Sharepoint Xml feed HttpHandler with Linq

February 7, 2008

Today I’ve been developing an HttpHandler for serving Xml data using a URL-based protocol from a Sharepoint server to the Ajax webparts located in the sites, and I thought it would be a nice experience to share and to start with in my new coding blog !

Currently I am developing a Sharepoint based e-learning portal for a famous university. An ajax webpart had to be developed that enabled teachers to search all the students and get a list filtered by some of the user’s properties (to keep it simple, I’ve limited this example to filter by the user’s name). I decided to use an XmlWebpart from the Sharepoint Ajax Toolkit which would be connected to a filter web part and would get the data from the Xml feed served by an HttpHandler.

In future posts I’ll talk about the ajax webparts, connections and deployment which might be very interesting subjects but for now, I’ll limit to the httpHandler implementation. Following I’ll transcript the code of the ProcessRequest method of the handler comenting all the points that I find interesting and usefull of the implementation.

context.Response.ContentType = "text/xml";
context.Response.Expires = -1;

After setting the response content type to Xml, we open the current SPWeb object to query all the users with the method web.AllUsers. There is no method in Sharepoint object model such as SPUser.IsInRole(rolename) which we all would expect neither there is any way to get a list of the current user’s roles provided the users has Manage Permissions rights in the current site (which is not something likely to happen)What we do have, is a method in SPWeb object called AllRolesForCurrentUser that we can query to check if a user has a particular role. This is what I do in the following code, foreach user in the current web, I open a SPSite object with it’s user token and query if they are in the students role, if so, I store their login name in an array which I finally use to get a SPUserCollection filtered by this array, since there is no way to instantiate a SPUserCollection object and add SPUser objects to it, I thought this approach would be fine. The reason why I want to use the SPUserCollection object is because I want the Xml feed to return the user’s Xml generated by the SPUserCollection.Xml property.

SPWeb web = SPContext.Current.Web;
List userLogins = new List();
foreach (SPUser user in web.AllUsers)
{
using (SPSite site = new SPSite(web.Site.ID, user.UserToken))
{
SPWeb userWeb = site.OpenWeb(web.ID);
SPRoleDefinition studentRole = userWeb.RoleDefinition[SecurityManager.STUDENT_ROLE];
if (userWeb.AllRolesForCurrentUser.Contains(studentRole))
userLogins.Add(user.LoginName);
}
}
SPUserCollection usersInRole = web.AllUsers.GetCollection(userLogins.ToArray());

Now, I still had to filter the SPUserCollection by it’s user name. The filter is passed in the querystring and I decided to use Linq to Xml to do filtering.

string name = string.Empty;
if (!string.IsNullOrEmpty(context.Request["name"]))
name = context.Request["name"];
XDocument usersXml = XDocument.Parse(usersInRole.Xml);
var queryUsers = from u in usersXml.Descendants("User")
where (string.IsNullOrEmpty(name)) (u.Attribute("Name").Value.Contains(name))
select u;

Finally, all what I had to do is return the Xml of the queryUsers objects. I couldn’t find a better way to do it because I didn’t know how to get the Xml from the queryUsers object (keep in mind is my first Linq code!) so following is the choosen method:

context.Response.Write("");
foreach (var u in queryUsers)
context.Response.Write(u.ToString(SaveOptions.None));
context.Response.Write("");

And this is all for this method ! Please leave a comment if you found it useful or wish to suggest any other way to do it ! Although it’s my first post, I promise to keep this blog up to date with many other coding experiences (and I promise I have a lot!)ori