Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Client Side Edit Mode in ASP.NET AJAX Web Applications

3.24/5 (6 votes)
9 Aug 2007Apache7 min read 1   342  
This article describes an approach to build ASP.NET pages with a client-side edit mode: react on changes (by user or code), visualise mode (editing / unchanged).

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, ...).

Screenshot - BeforeEditing.gif

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

Screenshot - Editing.gif

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:

Screenshot - Designer.gif

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
PropertyDescription
string EditAnimationIDWhich 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 TypeEvent
textkeyup
select-onechange
radioclick
checkboxclick

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
PropertyDescription
string EditAnimationIDWhich EditAnimationExtender to notify when user clicks on extended Button.
bool SuppressPostBackWhether 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
PropertyDescription
AjaxControlToolkit.Animation OnEditingThe animation that is played when starting edit mode.
AjaxControlToolkit.Animation OnEditedThe animation that is played when ending edit mode.
bool ShowHideTargetControlWhether 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 NotifyOtherEditAnimationExtendersWhether other EditAnimationExtenders 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 EditAnimationExtenders 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 EditAnimationExtenders 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 EditExtenders on a Page or UserControl.

The JavaScript code that is used to register the input events is shown below:

JavaScript
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.

JavaScript
_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:

C#
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:

C#
initialize : function() {
    bbv.Ajax.ControlExtender.Edit.EditAnimationBehavior.callBaseMethod(this, 'initialize');

    this._onEditing.initialize();
    this._onEdited.initialize();
    
    // restore editing state
    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:

C#
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:

C#
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 EditAnimationExtenders 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 EditAnimationExtenders on the page:

JavaScript
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 EditAnimationExtenders.

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

EditAnimationExtenders 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.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0