Click here to Skip to main content
15,890,123 members
Articles / Web Development / ASP.NET

JavaScript and Server Controls 101

Rate me:
Please Sign up or sign in to vote.
4.24/5 (6 votes)
3 Jun 2009CPOL8 min read 26.5K   36   3
A brief discussion of how to bind JavaScript to custom server/ASP.NET Controls using the AJAX client control model.

After my previous post called JavaScript and User Controls 101, I had several people ask me if I could also talk about server controls. That is a great point so I threw together a quick little application to demonstrate server controls.

A common interview question is, "What is the difference between a user and a server control?" Of course, the server control is "server side" but can also have client side script. A server control is a truly atomic control that can be referenced in an external DLL and comes with its own properties and scripts. It can only be manipulated via the properties (both runtime and design-time) that it exposes. A user control, on the other hand, is a composite object that participates explicitly in page life-cycle events (init, load, render, etc) and can contain many other controls.

Server controls are typically used for fine-grained functionality. For example, a form that requests a block of information is most likely a user control, while a field in the form that is designed to allow entry of a file name might be a special server control (these are also referred to as ASP.NET controls).

For our example, we are going to make a new server control called a "BlurTextbox." The purpose of this control is to raise a callback event any time it loses focus. This will send the controls value to the server for some type of processing. Whatever the server returns will be evaluated as JavaScript.

In essence, you could use this to validate something on the server side and return a call to show an error message or an alert. Imagine, for example, entering a record with a unique name, and you want to give the illusion of "client-side validation" by showing if the name violates this constraint before the user even tabs from the form.

The logical way to extend a server control is to provide a client side representation. The AJAX framework provides extensive support for doing this, so let's get started.

First, we are going to inherit our control from a TextBox. I add a local reference to the ScriptManager that I can use in several places (this is the control we use to interact with the AJAX framework). I added a single property to my control called "ControlCallbackID." This property will have the client identifier for the control is going to respond to our callback request. The server control itself cannot plug into this (but it could expose events or delegates, that's beyond the scope of this example). To listen to the callback, we simply set this to the client ID of the control that is listening. In our example, it will be the actual page that hosts the control. Note the standard pattern to persist the property in the ViewState property bag.

C#
public class BlurTextbox : TextBox
{
    private ScriptManager _sm;

    public string CallBackControlID
    {
        get
        {
            if (ViewState[CALLBACKCONTROLID] == null)
            {
                return string.Empty;
            }

            return ViewState[CALLBACKCONTROLID].ToString();
        }

        set
        {
            ViewState[CALLBACKCONTROLID] = value;
        }
    }
   ...

The next step is to implement IScriptControl. This identifies the control as having a client-side counterpart. IScriptControl asks us to implement IEnumerable<ScriptDescriptor> GetScriptDescriptors() and IEnumerable<ScriptReference> GetScriptReference().

Script Descriptors

The script descriptors allow us to bind our server-side properties to the client-side JavaScript object. We start by identifying the fully qualified name of the object (we will have the namespace, OnBlurTextBox.TextBox, match on both the client and server). Then we add our properties by calling AddProperty with the client-side name of the property and the value for the property.

C#
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
   ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
                GetType().FullName, ClientID);
   descriptor.AddProperty(JS_CALLBACKCONTROLID, CallBackControlID);
   return new ScriptDescriptor[] { descriptor };
}

Script References

The next piece to implement is a list of references for script for the control. In this case, I only have one JavaScript to include. It is an embedded resource with the same name as the control itself. Therefore, I can register it with the full namespace and the extension (.js) as well as the assembly. Don't forget that you must go into Properties -> AssemblyInfo.cs and add an [assembly: WebResource...] reference for this. The ScriptReference object has several overloads and can handle a URL, local, or embedded resource for the script (this is a good place, for example, to also include a library if you are using those functions along with your control).

C#
public IEnumerable<ScriptReference> GetScriptReferences()
{           
   ScriptReference reference = new ScriptReference(string.Format({0}.js",GetType().FullName),
   GetType().Assembly.GetName().FullName);
   return new[] { reference };
        }

Now we need to register these with the AJAX script manager. In PreRender we register the control itself, letting AJAX know it must call our overrides and wire up a client-side object.

C#
protected override void OnPreRender(EventArgs e)
        {
            if (!DesignMode)
            {
                _sm = ScriptManager.GetCurrent(Page);

                if (_sm == null)
                {
                    throw new HttpException(ERROR_SCRIPTMANAGER);
                }
               
                _sm.RegisterScriptControl(this);
            }

            base.OnPreRender(e);
        }

In Render, at the last possible moment, we register the descriptors to emit the properties.

protected override void Render(HtmlTextWriter writer)
       {
           if (!DesignMode)
           {
               _sm.RegisterScriptDescriptors(this);
           }

           base.Render(writer);
       }

That's it for the server control ... the rest is done on the client. Let's take a look at the client-side script:

JavaScript
/// <reference name="MicrosoftAjax.js" />

Type.registerNamespace("OnBlurTextBox");

// we extend a textbox and really just add a property (callbackcontrolid) and 
// an event (onblur)
OnBlurTextBox.BlurTextbox = function(element) {
    OnBlurTextBox.BlurTextbox.initializeBase(this, [element]);
    // this is the property to refer to the corresponding callback control
    this._callBackControlID = null;
    this._oldBlur = null; 
}

// prototype for the clientside representation
OnBlurTextBox.BlurTextbox.prototype = {
    initialize: function() {
        OnBlurTextBox.BlurTextbox.callBaseMethod(this, 'initialize');
        // bind the handler for calling the callback on blur
        this._onblurHandler = Function.createDelegate(this, this._onBlur);
        // store any existing "blur" event
        this._oldBlur = this.get_element().onblur;
        // register the event handler
        $addHandlers(this.get_element(),
            { 'blur': this._onBlur },
            this);
    },
    dispose: function() {
        //Add custom dispose actions here
        $clearHandlers(this.get_element());
        OnBlurTextBox.BlurTextbox.callBaseMethod(this, 'dispose');
    },
    oldBlur: function() {
        if (this._oldBlur) {
            this._oldBlur();
        }
    },
    // getter for callback binding
    get_callBackControlID: function() {
        return this._callBackControlID;
    },
    // setter for callback binding
    set_callBackControlID: function(value) {
        if (this._callBackControlID != value) {
            this._callBackControlID = value;
            this.raisePropertyChanged('callBackControlID');
        }
    },
    // wire into the blur event to trigger the callback
    _onBlur: function(e) {
        if (this.get_element() && !this.get_element().disabled) {
            // callback to the server and then simply evaluate what is returned as javascript
            WebForm_DoCallback(this.get_callBackControlID(), this.get_element().value, function(args, ctx) {
                eval(args);
                ctx.oldBlur(); 
            }, this, null, false);
        }
    }
}

// register the class
OnBlurTextBox.BlurTextbox.registerClass('OnBlurTextBox.BlurTextbox', Sys.UI.Control);

// let 'em know we're loaded
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

There's a lot going on here, but it's not as complicated as it may first appear. First, we register the namespace for our control. Next, we create a prototype for instantiating the object. Notice that it takes a parameter called "element". This is the actual DOM element it will be bound to. Because we are extending a Textbox, the element will be an input element. The prototype is the JavaScript way of typing an object. The object has getters and setters as well as methods and handlers. The first thing to notice is how we initialize and expose getters and setters for the callbackControlID. Next, take a look at the "blur handler" which creates a delegate and registers the event. Finally, note our private field "_oldBlur" that holds the original blur event so we can chain to whatever is done by the consuming page or control.

The _onBlur event is straightforward. We make sure we have a reference to the valid input element and that it is not disabled. Then, we simply invoke WebForm_DoCallback. We pass the callback control (if this isn't set, the callback just gets ignored), the value of the element (input box) we are bound to, and pass an inline function for the callback to evaluate whatever was sent by the server and then call the original "blur" event. Note that we pass the AJAX object itself as "context" (the third to last parameter). This allows us to reference our specific instance on the callback to get the original blur event.

Finally, we register the class with the AJAX framework and notify that this script has been loaded.

Whew! That was a bit, but how do we use it? This is the fun part.

Open up Default.aspx. You'll notice I've inlined a bit of JavaScript. One function sets the focus for the control (using the timeout function for IE 6.0 compatibility). The other inserts text dynamically into an unordered list. We register the control at the top by pointing to the namespace and the assembly and giving it a prefix. Then, we embed two of our custom "blurtextbox" controls. I'll explain the button that does nothing in a minute. We also make sure we have a ScriptManager and that's it.

Now to the code. On the code-behind, I implement ICallbackEventhandler so I can process the events raised by the new controls. In the Page_Load I get a callback reference to make sure the page is listening. I also take the ClientID of the page and set it to the custom controls so they have their reference for the callback. Finally, I add a blur event to the second textbox. I'm basically asking it to set the focus back to the first textbox. This will demonstrate how our custom control does not override the events we set. The reason I put the dummy button on the page is because without it, the last tab on IE would set the focus to the address bar and I'd lose my ability to set focus on the page. By having a button, the default focus would go to the button, then I intercept this and set it back to the first textbox. If you tab through the textboxes, you should find you are alternating between the first and second.

The callback event itself is quite simple: I simply take the value and respond with a call to my function to insert the list item. This will basically build the unordered list on the page each time you tab or click out a text box. You can try typing "This[TAB]Is[TAB]A[TAB]Test" and you should see this:

  • This
  • Is
  • A
  • Test

That's it ... to summarize:

  1. Build your server control
  2. Implement IScriptControl to wire in your properties and script references
  3. Build your javascript to encapsulate whatever client-side functionality you need the control to exhibit
  4. Include the control in a page

That's all there is to JavaScript with server controls.

You can see the working application in action at http://apps.jeremylikness.com/onblurtextbox.

If you view the source, you can learn a lot more about how the AJAX framework interacts with server controls. The start of the page looks pretty much like our .aspx template, with a few added pieces such as the viewstate and event arguments. You'll notice that unlike our original embedded JavaScript (WebResource) we now have some ScriptResource references as well.

Our custom control looks just like an ordinary input box, nothing special. You can even see the blur event that we wired in:

HTML
<div>
     Type something, then TAB out of the textbox<br />
     <input name="_bTextBox" type="text" id="_bTextBox" />
     <input name="_bTextBox2" type="text" id="_bTextBox2" onblur="setNewFocus('_bTextBox');" />
     <input type="button" id="btn" value=" " />
     <br />
     (You can also try clicking between the textboxes)
 </div>

The key is at the bottom. Notice that there are two add_init calls to a function called $create. There you'll see something familiar: the reference to the callback control (pointing to the page as __Page, and the $get function to get a reference to the input boxes. This is where everything is bound (the prototype and actual call are in the script references that were included earlier).

Jeremy Likness

This article was originally posted at http://feeds2.feedburner.com/CSharperImage

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Program Manager Microsoft
United States United States
Note: articles posted here are independently written and do not represent endorsements nor reflect the views of my employer.

I am a Program Manager for .NET Data at Microsoft. I have been building enterprise software with a focus on line of business web applications for more than two decades. I'm the author of several (now historical) technical books including Designing Silverlight Business Applications and Programming the Windows Runtime by Example. I use the Silverlight book everyday! It props up my monitor to the correct ergonomic height. I have delivered hundreds of technical presentations in dozens of countries around the world and love mentoring other developers. I am co-host of the Microsoft Channel 9 "On .NET" show. In my free time, I maintain a 95% plant-based diet, exercise regularly, hike in the Cascades and thrash Beat Saber levels.

I was diagnosed with young onset Parkinson's Disease in February of 2020. I maintain a blog about my personal journey with the disease at https://strengthwithparkinsons.com/.


Comments and Discussions

 
QuestionSource code is missing Pin
User 100063128-Oct-11 2:38
User 100063128-Oct-11 2:38 
GeneralMy vote of 5 Pin
User 100063128-Oct-11 2:37
User 100063128-Oct-11 2:37 
GeneralGood article Pin
Pham Dinh Truong9-Jun-09 18:34
professionalPham Dinh Truong9-Jun-09 18:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.