Introduction
This article discusses an approach to build ASP.NET pages with a client side edit mode. Edit mode means that when the user starts to change data on the page, then something happens visually on the page.
In this screenshot of my little sample web application, you can see a page where a user can enter data. Note that I have placed every kind of basic server control of ASP.NET 2.0 on that page (TextBox
, ComboBox
, RadioButton
, ...).
In the next screenshot, the user begins to enter data. The application signals now that there are unsaved changes on this page by showing a big red Editing text on top of the page. Furthermore, the Save button is enabled.
Because everything happens on the client side, there are no postback artifacts present:
- no delay when typing in a
TextBox
- no reset of the caret position in a
TextBox
- no flickering
When the user clicks on the Save button, the big red Editing text will disappear and the Save button will be disabled again.
Latest Update
Built with AJAX Control Toolkit, Release 10618.
The Basics
The basis for this behaviour are three Extenders (derived from AjaxControlToolkit.ExtenderControlBase
):
EditExtender
: This control extends an input control (TextBox
, CheckBox
, ...) by adding client side JavaScript that reacts on user input and notifies an EditAnimationExtender
that changes has occurred.ButtonEndEditExtender
: This control extends a button and notifies an EditAnimationExtender
that editing should be ended.EditAnimationExtender
: This control defines the actions taken when edit mode is started and ended.
The screenshot below shows the page of my sample application in the Visual Studio Designer:
The Extenders
This section gives you a short summary of the used extenders.
EditExtender
The EditExtender
control is used to extend a control in a way that entering data will start the edit mode.
[TargetControlType(typeof(Control))] EditExtender : AjaxControlToolkit.ExtenderControlBase |
---|
Property | Description |
---|
string EditAnimationID | Which EditAnimationExtender to notify when user modifies data on extended control. |
The EditExtender
can either extend a single input control or a container control (Panel
, UserControl
, ...). Depending on the type of the extendee, the extender registers for the event that signals modification of data:
Control Type | Event |
---|
text | keyup |
select-one | change |
radio | click |
checkbox | click |
When the corresponding event is fired, then the associated EditAnimationExtender
is notified that data was modified.
ButtonEndEditExtender
The ButtonEndEditExtender
control is used to extend a Button
in a way that clicking it will end the edit mode.
[TargetControlType(typeof(IButtonControl))] ButtonEndEditExtender : AjaxControlToolkit.ExtenderControlBase |
---|
Property | Description |
---|
string EditAnimationID | Which EditAnimationExtender to notify when user clicks on extended Button . |
bool SuppressPostBack | Whether the postback of the extended control should be suppressed. If set to true, then no postback will occur. |
The extender registers the click event of the extendee. When the user clicks the extended Button
, the associated EditAnimationExtender
is notified that the edit mode should be ended.
EditAnimationExtender
This is the main extender that is responsible for taking the actions necessary to start or end the edit mode.
[TargetControlType(typeof(Control))] EditAnimationExtender : AjaxControlToolkit.ExtenderControlBase |
---|
Property | Description |
---|
AjaxControlToolkit.Animation OnEditing | The animation that is played when starting edit mode. |
AjaxControlToolkit.Animation OnEdited | The animation that is played when ending edit mode. |
bool ShowHideTargetControl | Whether the extended control is shown/hidden depending on the edit mode. Set to true when you want to show a control when in edit mode. |
bool NotifyOtherEditAnimationExtenders | Whether other EditAnimationExtender s on the same page should be notified about starting/ending edit mode. Useful to synchronize UserControls with the hosting page. |
The first time the extender is notified by an EditExtender
about modifications, the extender plays the OnEditing
animation and displays the extendee (if ShowHideTargetControl
is equal to true
).
When the extender is notified by a ButtonEndEditExtender
that the user wishes to end the end mode (normally by clicking on either the Save or Cancel button), the OnEdited
animation is played and the extendee is hidden (if ShowHideTargetControl
is equal to true
).
The extender maintains its state. That means that the animations are only played when the edit mode switches and that the state is maintained over postbacks. The state can be requested or set in server code, too (for more information see section "Survive Postbacks" below).
When NotifyOtherEditAnimationExtenders
is set to true
, on each edit mode switch, all other EditAnimationExtender
s on the page are notified about the state switch and will play their associated animations. This is very useful when you have nested UserControls on your page and want that modifying data anywhere on the page will cause all EditAnimationExtender
s to switch their edit mode accordingly.
Advanced Topics
Listen to the User!
There exists only one type of EditExtender
. Therefore, it has to take care of all the different input controls. Additionally, it can handle container controls by registering input events on all the children of the container. This minimizes the number of needed EditExtender
s on a Page or UserControl.
The JavaScript code that is used to register the input events is shown below:
registerEvents : function(root)
{
switch (root.type)
{
case 'text':
$addHandler(root, 'keyup', Function.createDelegate(this, this._onedit));
break;
case 'select-one':
$addHandler(root, 'change', Function.createDelegate(this, this._onedit));
break;
case 'radio':
case 'checkbox':
$addHandler(root, 'click', Function.createDelegate(this, this._onedit));
break;
default:
var child = root.firstChild;
while (child)
{
this.registerEvents(child);
child = child.nextSibling;
}
break;
}
},
As shown above, the _onedit
function is registered as a handler for the input events. When an input event is fired, the EditExtender
reacts by notifying the EditAnimationExtender
.
As shown in the code below, the EditAnimationExtender
is accessed by using the $find
function.
_onedit : function(e) {
if (e.keyCode != Sys.UI.Key.tab)
{
[... some code ...]
var animation = $find(this._editAnimationIDValue);
if (animation) {
animation.startEdit(true);
}
}
},
Survive Postbacks or Passing Information About the Current Mode from Client to Server and Vice Versa
After a postback, the whole or a part of the page is re-rendered. This can undo the changes that the animation defined as OnEditing
in an EditAnimationExtender
. Therefore, you should take care that the corresponding EditAnimationExtender
is re-rendered, too. You can achieve this by simply putting the extender in the same UpdatePanel
as the re-rendered content.
What happens now is that when the EditAnimationExtender
is re-rendered, it will restore its last EditMode
state: If it was editing, then the OnEditing
animation is played.
In order to maintain the editing state over postbacks, the extender uses the ClientState
functionality of the ExtenderControlBase
class from the AJAX Toolkit.
First, we need to enable and initialize the ClientState
in the EditAnimationExtender
:
public class EditAnimationExtender : AnimationExtenderControlBase
{
public EditAnimationExtender()
{
this.EnableClientState = true;
this.ClientState = "false";
[... other code ...]
}
[... other code ...]
}
Then we can access the ClientState
on the client side when the extender is initialized and react accordingly to the current state:
initialize : function() {
bbv.Ajax.ControlExtender.Edit.EditAnimationBehavior.callBaseMethod(this, 'initialize');
this._onEditing.initialize();
this._onEdited.initialize();
var state = this.get_ClientState();
if (state)
{
state = Boolean.parse(state);
}
if (state)
{
if (this._showHideTargetControlValue) {
this.get_element().style.visibility = 'Visible'
}
this.startEdit(true);
}
else
{
this.endEdit(true);
}
},
Finally, we have to set the state when it changes on the client side:
startEdit : function(relay)
{
if (!this._isEditing)
{
[... other code ...]
this._isEditing = true;
this.set_ClientState('true');
[... other code ...]
}
},
Additionally, we can start editing on the server now:
private void StartEditing()
{
this.EditAnimationExtender.IsEditing = true;
this.UpdatePanel.Update();
}
Synchronize Nested UserControls
We have some complex UIs in our web applications that are built from several UserControls. Each UserControl has its own EditAnimationExtender
to keep the scope of control references local to the UserControl. Therefore, the different EditAnimationExtender
s have to communicate with each other to get the whole page to take part in the edit mode.
When an EditAnimationExtender
switches its edit mode, it notifies all other EditAnimationExtender
s on the page:
startEdit : function(relay)
{
if (!this._isEditing)
{
[... other code ...]
if (relay && this._notifyOtherEditAnimationExtendersValue) {
var c = Sys.Application.getComponents();
for (var i = 0; i < c.length; i++) {
var id = c[i].get_id();
var type = Object.getType(c[i]).getName();
if (type == "bbv.Ajax.ControlExtender.Edit.EditAnimationBehavior") {
$find(id).startEdit(false);
}
}
}
}
},
The relay
parameter is used so that only one EditAnimationExtender
sends notifications. Otherwise this would lead to an infinite loop.
Finally, you can set the NotifyOtherEditAnimationExtenders
property to false
to get an isolated EditAnimationExtender
that does not notify other EditAnimationExtender
s.
Known Issues
Starting EditMode From Server-side Needs UpdatePanel Update
When you want to switch to EditMode
in code on the server-side, you have to set the IsEditing
property of an EditAnimationExtender
on your page and update the UpdatePanel
hosting this EditAnimationExtender
. This way the extender will be initialized again and will enter EditMode
on the client-side.
Animation Re-runs
EditAnimationExtender
s resets their state after a (partial) re-rendering of the page and will play either the starting or ending animation. Therefore you should take care that these animations can be played and stopped without interfering with each other.
Conclusion
This article presented a way to maintain a client-side editing mode that can be used to get a better user experience.
This code is taken from a project that is currently under development. If you find any issues, problems, or have ideas for extensions, please let me now.
Source of the Code
The sample code is extracted from a web application that I wrote for bbv Software Services AG.
History
- 2007-06-06: Initial version.
- 2007-08-10: Built with AJAX Control Toolkit, Release 10618.