Implementing an Ajax Live Form with NetAdvantage WebClient Controls

Craig Shoemaker / Friday, November 21, 2008

This article teaches you how to implement the Live Form Ajax pattern using NetAdvantage web client controls.

The Live Form pattern is an approach to building web forms that does not require the user to make an explicit submission to the server to make "something happen". The page created for this article, allows users to discover editable parts of the page, edit the content and enjoy automatic persistence.

Take a Look

Some screen shots are in order to help explain what is going on. First, a user will encounter a page that at first glance seems to be a normal display page.

Form when intially loaded

However as the user begins to interact with the content the page begins to respond to user actions. When the user hovers the mouse over the title element, the border changes color to indicate that this portion of the page is editable.

How the forms changes when the user hovers the mouse over the title element.

At this point editing the content is as simple as clicking on the editable region. When the text is clicked, the UI updates further to change the border once again giving a clue that you have entered an edit mode.

How th UI changes when the element is in edit mode.

Once focus is left from the control a request is made to the server to process the change. While the change is in progress the UI is updated again to signify that there are pending changes.

How the UI changes when the data is dirty and the server is busy processing the request.

Finally once a response is received from the server, the UI is updated one last time to return the page to its original visual state with the latest data.

How the page returns to an unaltered visual state with the updated data.

From here you may refresh the page or even open the page in a new browser to verify the changes are persisted.

The same pattern is used to make the Publish Date editable. Here is a screen shot of what the editing experience looks like when setting a new date.

What the page looks like when the publish date is being edited.

The Illusion of Persistence

One of the main features of this solution the fact that the page persists the data without explicit submission. In an attempt to side-step the many peripheral issues surrounding data layers and other persistence mechanisms, this sample uses the BookRepsitory’s persistence features.

The illusion of saving data is achieved by maintaining state in an object loaded into an ASP.NET cache entry. For more information read about the BookRepository: The Mock Data Repository for Testing and Demos and The Illusion of Persistence: Saving Test Data.

The Title Editor

Now that you have context for the project, the first step is to create the title editor. Between the page’s FORM tags enter the following markup:

<asp:ScriptManager EnablePageMethods="true" runat="server" />
<input type="hidden" id="bookID" value="<%= this.book.ID.ToString() %>" />
<div><igtxt:WebTextEdit 
    ID="txtTitle" 
    onfocus="txtTitle_Focus();"
    onblur="txtTitle_Blur();"
    onclick="txtTitle_Click();"
    CssClass="liveInput big"
    Width="800" 
    runat="server" /></div>

What you see on this page is:

  • ScriptManager: The ScriptManager is a usual suspect for doing just about anything Ajax. Note that the property enablePageMethods is set to "true". This is required as so that ASP.NET AJAX will create proxy functions for methods in the codebehind marked with the WebMethod attribute.
  • Hidden HTML field: This hidden control acts as a container for the current record’s primary key value. This value is used in the Ajax calls back to the server so the system can reliably find the current record.
  • WebTextEdit control: This is the control that is responsible for the editing of the book title. The page uses style sheets to hide the fact in the beginning that you are looking at an input control. Notice the JavaScript functions that the control points to for focus, click and blur. You will implement these functions to make the Ajax calls to the server and keep the UI up-to-date with the current edit state.

The Style Sheet

The CSS for this page is simple yet vital to providing the necessary feedback to the user.

Open a style block on your page and enter the following CSS:

body
{
    font-family:Arial, Helvetica, Sans-Serif;        
    margin-top:25px;  
    font-size:1.8em;  
    text-align:center;
}
#published
{
    position:absolute;
    top:159px;
    width:140px;
    left:405px;
}
#date, #dateControl
{
    position:relative;
    width:150px;
    margin-left:auto;
    margin-right:auto;
}
.big
{
    font-size:4em;
}
.none
{
    display:none;
}
.liveInput, .liveInputDirty, .liveInputEdit
{
    border:1px solid #fff;
    text-align:center;
}
.liveInput:hover
{
    border:1px solid #6c3;
}
.liveInputDirty, .liveInputDirty *
{
    color:#ccc;
    border:1px solid #fcc;
}
.liveInputEdit, .liveInputEdit *
{
    border:1px dashed #666;
}

We’ll skip a detailed explanation of the style sheet in hopes that if you are reading this far your CSS skills are sophisticated enough to handle the simple entries in the above style sheet.

The only detail that is perhaps noteworthy here is that the classes will follow the progression of liveInput for an untouched state, liveInputEdit for when users are editing the data and liveInputDirty to update the UI while the server is processing changes.

Supporting JavaScript

A common script action required to update the UI is to change the CSS properties on a given element. In the tradition of DOM manipulation heavyweight jQuery, the script features some helper methods that make it easy to add and remove CSS classes from an element. These methods are optimized to work with plain DOM objects instantiated by either calling $get, getObjectById and for the NetAdvantage specific notation igedit_getById.

To begin, open a script block and enter the following code:

function addCssClass(ctl, cssName)
{
    var c = (ctl.Element) ? ctl.Element : ctl;
    c.className += (" " + cssName + " ");
}

The addCssClass function simply appends the given cssName to the list of classes associated with the element.

function removeCssClass(ctl, className)
{
    var c = (ctl.Element) ? ctl.Element : ctl;        
    var classes = c.className.split(" ");
    var cssClass = "";
 
    for (var i = 0; i < classes.length; i++)
    {
        if (classes[ i ] != className)
        {
            cssClass += classes[ i ] + " "; 
        }
    }
    c.className = cssClass;
}

The removeCssClass function creates an array of the currently assigned CSS classes and then rebuilds the list only to skip over the class requiring removal.

With these helper functions available now you can begin to implement the control specific logic for editing the title.

Live Title Interaction: the JavaScript

Now you are ready to start implementing live interaction with the title control.

Add the following code to your script block:

var oldVal = "";
 
function txtTitle_Focus()
{
    var ctl = igedit_getById("<%= txtTitle.ClientID  %>");
    oldVal = ctl.getValue();
}

When the control come to focus txtTitle_Focus is run and gets a refernce to the edit control using the NetAdvantage client-side object model (CSOM) API. The current value is set aside here into the oldValue variable in order to check to make sure there are changes before the page attempts to call back to the server.

function txtTitle_Click()
{
    var ctl = igedit_getById("<%= txtTitle.ClientID  %>");
    removeCssClass(ctl, "liveInput");
    addCssClass(ctl, "liveInputEdit");
}

The txtTitle_Click function’s job is to simply update the UI so the user knows they’ve entered the edit mode. To accomplish this the liveInput class is removed and the liveInputEdit class is added.

function txtTitle_Blur()
{
    var ctl = igedit_getById("<%= txtTitle.ClientID  %>");
    var val = ctl.getValue();
 
    addCssClass(ctl, "liveInput");
    removeCssClass(ctl, "liveInputEdit");
 
    if (val != oldVal)
    {
        PageMethods.UpdateTitle($get("bookID").value, val, titleSuccess, fail);
        removeCssClass(ctl, "liveInput");
        addCssClass(ctl, "liveInputDirty");
    }
}

The real work on the page happens when the txtTitle_Blur function is run. First step is to get the new value and then update the UI back to the unaltered state. This action may seem counter-intuitive, but if the following conditional block does not run (because the value is not changed) then the control must returned to the unaltered visual state.

If the user has changed the value then the page’s UpdateTitle method is called using ASP.NET AJAX page methods (you will implement this method in the codebehind in the next section). Arguments to the call include a reference to the hidden input control that is holding the current primary key value, the updated title text, and callback pointers of functions to run in the event of a success or failure.

Once the AJAX call is dispatched, the UI is updated to reflect the data’s dirty state.

function titleSuccess(response)
{
    var ctl = igedit_getById("<%= txtTitle.ClientID  %>");
    removeCssClass(ctl, "liveInputDirty");
    addCssClass(ctl, "liveInput");
 
    if (!response)
    {
        ctl.setValue(oldVal);
        alert("The server encountered an error while trying to update the title.");
    }
}

When the page hears back from the server, it will either receive a success or failue notification. In the event that the operation was sucessful the titleSucess function is run.

This function takes the final steps to update the UI to reflect an unaltered state. There is also a chance, though, that the Ajax response returns successfully but the intended action is not complete. As you will see when implementing the code behind, if an exception is encountered the server will log the details and return false to the client.

If the change was not successful the WebTextEdit control’s value is reset back to the original value and the user is notified that something went wrong.

function fail(reponse)
{
    alert("Fail " + reponse);
}

Should the request itself fail the aptly-named fail function is run. As with most Ajax demos this code just stubs-out this section with the stern warning that you should never do something like this in a production environment.

Live Title Interaction: the Codebehind

Open the code behind for your page and enter the following code just after the class definition:

protected Book book = null;

This protected variable will hold the instance of the book once loaded. The field is marked as protected so that the ASPX page can access its members when rendering the page.

protected void Page_Load(object sender, EventArgs e)
{
    if (!this.Page.IsPostBack)
    {
        this.book = BookRepository.Instance.GetByID(1);
        this.txtTitle.Text = this.book.Title;
        this.wdcPublishDate.Value = this.book.PublishDate;
    }
}

When the page loads the book is hydrated from the data repository and the controls are filled with the appropriate data.

[WebMethod]
public static bool UpdateTitle(int id, string title)
{
    try
    {
        System.Threading.Thread.Sleep(3000);
        Book b = BookRepository.Instance.GetByID(id);
        b.Title = title;
        BookRepository.Instance.Update(b);
        return true;
    }
    catch (Exception ex)
    {
        // publish exception
        System.Diagnostics.Debug.WriteLine(ex.Message);
        return false;
    }
}

There are a few details to note about the UpdateTitle method. First, the method is decorated as a "WebMethod" (found in the System.Web.Services namespace) which triggers ASP.NET AJAX to create a client-side proxy function. Second, the method is marked as static as this is a requirement for page methods. Third, note the call to the current thread to sleep. This is required so you are able to see the changes in the UI as the page is waiting for a response. On most development machines the response time would be so fast that the UI updates would take place immediately. The call to sleep the thread mimics more what the request/response time might be on the web.

You may choose to handle the state of your objects any number of ways, but for this demo, we are simply getting the latest version of the object from the repository, updating the title and sending the changes back.

If everything goes as planned, the method returns true to the client.

If an exception is encountered the code here stubs out how you may handle the situation in the real world. First we add a comment reminding you to always log or otherwise publish any exceptions that may bubble up to the user. Next, the call to Debug.WriteLine is a handy place to set a breakpoint and inspect the error. Finally the method returns false to the client. The approach you see here is better than simply allowing the exception to bubble all the way up to the client because you are now able to do something valuable with the error information before the server relinquishes control of the operation.

Try it Out

Launch the web page and edit the title. You should be able to see the changes in the UI for each step in the editing process.

The Publish Date Editor

The date editor is implemented much the same as the title editor. The following explanation will cover any of the divergent concepts from what was necessary to build the title editor.

Return to the ASPX page and enter the following mark up directly after the closing DIV tag for the title editor:

<div id="published">Published:</div>
<div id="dateControl" class="none">
    <igsch:WebDateChooser Width="300px" ID="wdcPublishDate" runat="server" >
        <ClientSideEvents ValueChanged="wdcPublishDate_ValueChanged" />
    </igsch:WebDateChooser>
</div>
<div id="date" class="liveInput" onclick="date_Click(this);">
    <%= this.book.PublishDateShort %>
</div>

There are two main differences in how the UI elements are constructed for the publish date. Rather than the editing control being styled for the display state, this page uses a text literal to give the UI clues that the data is editable. When the user clicks on the DIV with the date ID the element will disappear and the input control will appear in its place.

Notice the ClientSideEvents node nested under the WebDateChooser control. This is how you associate a function with the client-side event for ValueChanged.

Live Publish Date Interaction: the JavaScript

Return to the script block and enter the following JavaScript:

function date_Click(date)
{
    var dateControl = $get("dateControl");
    addCssClass(date, "none");
    removeCssClass(dateControl, "none");
    addCssClass(dateControl, "liveInputEdit");
}

When the user clicks on the date literal, the element is hidden and the WebDateChooser is shown in the edit state.

var newDate = "";
 
function wdcPublishDate_ValueChanged(oDateChooser, newValue, oEvent)
{
    var dt = new Date(newValue);
    newDate = (dt.getMonth()+1) + "/" + dt.getDate() + "/" + dt.getFullYear();
 
    var dateControl = $get("dateControl");
    removeCssClass(dateControl, "liveInputEdit");
    addCssClass(dateControl, "liveInputDirty");
 
    PageMethods.UpdatePublishDate($get("bookID").value, newValue, dateSuccess, fail);
}

Much like the blur function for the title, the wdcPublishDate_ValueChanged function holds most of the processing logic for this element. When the ValueChanged event fires this function is run and the newValue argument holds the user’s newly chosen value. This value, however, is not in the same format that you might expect. In order to display the chosen value in the same format as seen in the previous state you must manipulate the data with the JavaScript date object.

Then next step is to switch CSS classes to update the controls to reflect the data’s dirty state.

Finally the UpdatePublishDate method is called in much the same manner as when you were updating the title.

function dateSuccess(response)
{
    var dateControl = $get("dateControl");
    var date = $get("date");
 
    removeCssClass(dateControl, "liveInputDirty");
    removeCssClass(date, "none");
    addCssClass(dateControl, "none");
 
    if (response)
    {
        date.innerText = newDate;        
    }
    else
    {
        alert("The server encountered an error while trying to update the publish date.");
    }
}

The dateSuccess function should look familiar to you as well. First order of business is to get references to both the date literal and date control elements on the page. Then the stylesheets are updated and if the operation was a success the display literal is updated with the new value.

Live Publish Date Interaction: the Codebehind

The codebehind that updates the publish date is nearly identical to what is implemented to edit the title:

[WebMethod]
public static bool UpdatePublishDate(int id, DateTime publishDate)
{
    try
    {
        System.Threading.Thread.Sleep(3000);
        Book b = BookRepository.Instance.GetByID(id);
        b.PublishDate = publishDate;
        BookRepository.Instance.Update(b);
        return true;
    }
    catch (Exception ex)
    {
        // publish exception
        System.Diagnostics.Debug.WriteLine(ex.Message);
        return false;
    }
}

Try it Out

Launch the web page and edit the publish date. You should be able to see the changes in the UI for each step in the editing process.

Conclusion

Building pages that use the Live Form pattern are simple to execute and create a great user experience. Be careful though, this approach is only recommended for forms that have a few editable items. Creating long forms that use this pattern will create unnecessary network traffic and could bring rise to some interesting concurrency issues.

Resources

Watch the Videos

Part 1

Part 2

Part 3

Part 4

Part 5