castaway In case you’ve been shipwrecked on a desert island for the last few years jQuery is a javascript library that simplifies the querying of HTML elements with a web page and greatly simplifies the implementation of Ajax techniques within a web page. The functionality that jQuery represents isn’t anything new but the effort level to implement some of the same facets of functionality has been greatly reduced and simplified by the use of the jQuery library. Additionally many offshoot projects have been created that add functionality on top of the jQuery library. One of my favorites is the jQuery User Interface library that adds new rather slick UI elements onto the library. These range from Accordions, Date Pickers, and Progress Bars to Sliders, Tabs, and Dialogs.  Of those elements provided by the jQuery UI Library I have found the Dialog has been very helpful in several of my projects as of late.  A modal dialog that isn’t caught by popup blockers can be a great tool to quickly collect information or complex confirmations from the user.

At a high level a dialog typically collects information from the user, performs some action based on the information collected, and optionally updates user interface elements in response to that action. In our context this functionality should be able to be encapsulated within a reusable WebPart with minimal external dependencies (or more desirable – no external dependencies). Unfortunately implementing client script based solutions within custom components, specifically WebParts, can often be confusing until you get the technique down. Keep in mind these are not asp.net server controls that you can instantiate in your WebPart or drop onto an asp.net form. These are purely script based solution. The solution that I demonstrate here is one possible technique that I found to be a good compromise between a pure client script based model and the classic asp.net postback model found commonly in WebParts and asp.net pages. 

In the context of a webpart we run into some of the following challenges:

  1. The jQuery and jQuery UI library isn’t intrinsically aware of the asp.net postback model. Executing server side logic can become more problematic and often requires additional interfaces such as web services.
  2. More then one instance of this component may exist on a web page so any script logic would need to understand this implication and be very specific when it comes to querying for specific UI elements. Due to this fact we can’t always make use of some of the powerful selectors in jQuery.
  3. Combing the process of creating server side controls and getting the power that asp.net provides and still letting it support the great UI enhancement that the jQuery libraries can be a challenge.

To address these concerns we’ll make some compromises in our implementation that allows to get close to a best of both worlds.

GetPostBackReference
One of the first compromises we’re going to make to get a best of breed approach is the Save behavior. As I mentioned in a native scripting solution you would likely interact with server side logic via web service. To reduce the deployment overhead of this solution we want to be able to use some standard postback behavior to execute server side logic. In our case we’re going to reuse the postback event reference to execute some code in a button click event. Not only does this save us from creating the web service it also saves some client side complexity when it comes to calling the web service and also updating any appropriate UI elements from the script. Our solutions will let the page (and control) render again.

Of course this key compromise isn’t ideal in all scenarios. If you need a page update without a postback then you’ll have some extra work. This provide our desired dialog experience but the final page refresh may be a deal breaker.  Not as clean of an Ajax solution that some may expect after you save.

Script Client ID References
For those developers new to the client scripting model from the asp.net server side model the first important distinction is how controls are referenced. On the server side we can reference a control through its server ID. On the client side the asp.net framework dynamically generates IDs based on where the control lives in the control hierarchy to ensure unique IDs. JavaScript of course works at the client level. This can make it tricky to interact with scripts in the client when they have different IDs. The key to this is the ClientID property on a webcontrol (textbox, panel, dropdowns,etc). The ClientID represents the ID located on the client side and any references in scripts will require this reference.  Due to this nature in the lifecycle of asp.net controls we’ll need to combine scripts and apply the appropriate ID to the script before its rendered to the page.

You may be asking yourself “wait doesn’t jQuery make it easy to find a reference to an HTML element?”. That is true but because the nature of a WebPart the framework allows you to place multiple instances on a WebPart page which will mean that you are required to uniquely identify each instance within your JavaScript. In this case a selector based on the other attributes will still very likely give you multiple results, definitely not desirable.

The Solution


Using WSPBuilder we’re going to create a new WebPart project. This will give us our basic WebPart plumbing and supporting feature. WSPBuilder isn’t a requirement just my tool preference for SharePoint Feature development. You’ll also need to make sure you have references in your masterpage to the jQuery and jQuery UI libraries. For our purposes we’ve included the libraries and supporting in our project and deployed to the layouts folder. Some folks prefer to reference google’s hosted versions of the libraries found at http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js and http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js respectively. Lastly we’ve included resource files that will contain our custom JavaScript.

Our solution explorer should look like the following:

image

Building the Dialog and Hide\Show Button

First off we want to handle building the dialog. In the CreateChildControls we’ll need to build out the dialog that will be presented to the user and the button that will be used to hide and show the dialog. It’s worth noting that we’ll be using the same control to both hide and show the dialog using client side script AND execute server side logic on the postback event. I’ve included some standard jQuery UI CSS with the button attributes as well.

//Create the Dialog
pnlDialog = new Panel();
pnlDialog.ID = "pnlDialog";

Label lblDialogNotice = new Label();
lblDialogNotice.CssClass = "dialogNotice";
lblDialogNotice.Text = "Please fill out the following fields to create your site: </br></br>";
pnlDialog.Controls.Add(lblDialogNotice);

 //Title
pnlDialog.Controls.Add(new LiteralControl("Title:<br>"));
txtTitle = new TextBox();
txtTitle.ID = "txtTitle";
pnlDialog.Controls.Add(txtTitle);

//Title
pnlDialog.Controls.Add(new LiteralControl("<br><br>Description:<br>"));
txtDescription = new TextBox();
txtDescription.ID = "txtDescription";
pnlDialog.Controls.Add(txtDescription);

//Title
pnlDialog.Controls.Add(new LiteralControl("<br><br>Site Template:<br>"));
ddSiteTemplates = new DropDownList();
ddSiteTemplates.ID = "ddSiteTemplates";
ddSiteTemplates.Items.Add(new ListItem("Blank", "STS#1"));
ddSiteTemplates.Items.Add(new ListItem("Team", "STS#0"));

pnlDialog.Controls.Add(ddSiteTemplates);
base.Controls.Add(pnlDialog);

//Button that will initiate the dialog
btnShowDialog = new Button();
btnShowDialog.ID = "btnShowDialog";
btnShowDialog.Text = "Create New Site";
btnShowDialog.CssClass = "ui-button ui-state-default";
btnShowDialog.OnClientClick = "return false;";
btnShowDialog.Click += new EventHandler(btnShowDialog_Click);

base.Controls.Add(btnShowDialog);

 

 

 

The Client Script

The client script that will be used is very close to script library samples provided by the jQuery UI project. The only thing of note located in the script are some tokens, indicated by text enclosed in brackets [ ],  that will be replaced at run time with valid client ID values. This is what will allow us to provide context appropriate Client IDs to account for the possibility of multiple instances of WebParts. Finally you’ll notice that I’ve placed the Dialog JavaScript within a resource file. I find this a cleaner solution for including JavaScript inside of a project rather then stitching it together with a stringbuilder or dozens of string concatenations.

JavaScript:

$(document).ready(function() {

    $('#[DialogId]').dialog({
        autoOpen: false,
        height: 500,
        width: 300,
        modal: true,
        buttons: {
            'Close': function() {
                $(this).dialog('close');
            },
            'Save': function() {
                $(this).dialog('close');
                alert("Site Successfully Created");
                [PostBackReference];

            }

        }
    });

    $('#[ShowDialogControlId]').click(function() {
        $('#[DialogId]').dialog('open');
        $('#[DialogId]').parent().appendTo($("form:first"));
    })

});
 

WebPart Script Registration & btnShowDialog Click PostBack Event:

string DialogScript = SiteCreationDialog.WebPartCode.Resources.DialogResources.dialog;

DialogScript = DialogScript.Replace("[ShowDialogControlId]", btnShowDialog.ClientID);
DialogScript = DialogScript.Replace("[DialogId]", pnlDialog.ClientID);
DialogScript = DialogScript.Replace("[PostBackReference]", Page.ClientScript.GetPostBackEventReference(btnShowDialog, string.Empty));

Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "DialogScript", DialogScript, true);


void btnShowDialog_Click(object sender, EventArgs e)
{
            //Assumes the current users privledges are sufficient
            SPContext.Current.Web.Webs.Add(txtTitle.Text, txtTitle.Text, txtDescription.Text, (uint)1033, ddSiteTemplates.SelectedValue, false, false);

}


Of particular importance is the replacement of the PostBackReference token to use the postback of the BtnShowDialog control. This will allow our JavaScript dialog to call our postback event when the user selects the Dialog’s Save Button. Additionally we’re injecting our appropriate client side ID references to the JavaScript.

Conclusion

As you can see with some very reusable boiler plate you can very quickly create custom dialogs using the jQuery \ jQueryUI libraries in combination with the arguably more familiar post back model with the compromise of a single postback. Not useful in all situations but a good tool for toolbox when it’s appropriate.

A couple additional suggestions on usage:

  1. Avoid controls in your dialog that trigger a postback. You’ll lose your dialog. A common example is that of validation controls.
  2. Place validation within the “Save” portion of the dialog JavaScript. My suggestion is to use native client side javascript validation at this point in time. If you need to you can pass additional client IDs in using the technique above. I think you’ll find some of the jquery validation libraries to more advanced as well.
  3. Notifications to the user that occur after the postback may be tricky without losing your dialog. For simplicity sake to keep with the postback model I’ve sometimes included a label above the hide\show button to display messages about issues that occurred during a postback. It is possible to have the dialog box show on postback by making the JavaScript rendering more intelligent but this starts to compromise the quick aspect of this solution.
  4. SharePoint 2010 provides a new SharePoint specific dialog framework that may be more desirable to use then a third party library.

The complete sample code for this example can be downloaded here


Technorati tags: