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.

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.

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.

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.

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.

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.

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 BLOCKED SCRIPT
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
Posted
11-21-2008 11:23 PM
by
craigshoemaker