This has been done before here, but I would like to do this in a different way: by actually showing you how to write a web control that utilizes ASP.Net AJAX in a step-by-step manner. Let's start.
First thing is a bit of cursory-level knowledge of what it means to be an IScriptControl. As it were, the ASP.Net AJAX is - just like everything else in the .Net runtime - strictly object-oriented. As a result of this object-oriented'ness, everything is done as a class - and a control that can utilize the ASP.Net AJAX runtime, therefore, is best done as its own, independent type. Therefore, an IScriptControl is the interface that a control type must implement if it is to take part in the rich AJAX experience (or just utilize the client script libraries supplied by ASP.Net AJAX). My goal is to write some stupid, useless control; but while doing so teach you (and probably me) something - so I'm writing a button that cannot be clicked
. I told you it was useless.
First, the shell:
namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
return;
}
public IEnumerable<ScriptReference> GetScriptReferences()
{
return;
}
public String InactiveCSSClass
{
get
{
return null;
}
set
{
}
}
public String ActiveCSSClass
{
get
{
return null;
}
set
{
}
}
}
}
Here you see the very elementary basics behind my control. I simply implement the button by inheriting from it; and also implement the whole IScriptControl interface. First you notice the IEnumerable methods, these are implemented to implement the IScriptControl interface. Second, you'll notice the properties, these are accessor methods that will be used to grab hold of the CSS style sheets needed to implement my unclickable button. Let's proceed...
...we have the server-side code done. But no AJAX application would be complete without associated client-side code. When building web controls for use in ASP.Net AJAX, careful attention was made to the fact that whatever settings, options, properties, etc. you set from the server should be reflected on the client; and so you must implement a client-side object which corresponds to your server-side object and the two will be knitted together thanks to the ASP.net AJAX runtime.
My server-side object has two accessor methods InactiveCSSClass and ActiveCSSClass, so my script-version of thisobject should too. Even though my script object is written in ECMA script (JavaScript), thanks to the ASP.Net AJAX runtime it can be object-oriented, and so requires a namespace to be programmable. The following script can then be written
within a new JS file:
Type.registerNamespace('BensSamples');
BensSamples.UnclickableButton = function(element){
BensSamples.UnclickableButton.initializeBase(this,[element]);
this._ActiveCSSClass = null;
this._InactiveCSSClass = null;
}
Here you see I registered a rather self-serving namespace and created what can be likened to as my client object's constructor. JavaScript is a prototype-based language, and so the actual type needs to be designed as a prototype. To fill in the datatype, you must fill in the prototype for your object as such:
BensSamples.UnclickableButton.prototype = {
initialize : function() {
// initialize the base
BensSamples.UnclickableButton.callBaseMethod(this,'initialize');
// register delegates
this._onActiveHandler = Function.createDelegate(this,this._onBlur);
this._onInactiveHandler = Function.createDelegate(this,this._onFocus);
// register handlers
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlue},
this);
// set default css style
this.get_element().className = this._ActiveCSSClass;
}
dispose : function() {
// release the handlers
$clearHandlers(this.get_element());
// call to the base to do its dispose
BensSamples.UnclickableButton.callBaseMethod(this,'dipose');
}
// flesh out the actual handlers
_onFocus : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// disabled class type.
this.get_element().className = this._InactiveCSSClass;
return;
}
}
_onBlur : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// enabled class type.
this.get_element().className = this._ActiveCSSClass;
return;
}
}
// create accessor methods corresponding to the server control
get_ActiveCSSClass : function(){
return this._ActiveCSSClass;
}
set_ActiveCSSClass : function(value){
if(this._ActiveCSSClass!=value){
this._ActiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('ActiveCSSClass');
}
}
get_InactiveCSSClass : function(){
return this._InactiveCSSClass;
}
set_InactiveCSSClass : function(value){
if(this._InactiveCSSClass!=value){
this._InactiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('InactiveCSSClass');
}
}
}
// register the class
BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();
We now have a ECMA script class that lays out all of the definitions for the client type corresponding to the server type. We now need to hook the two together. To do this, you utilize the methods of the IScriptControl interface. The two methods have the following purpose:
- GetScriptDescriptors(): returns a ScriptDescriptor object that identifies the metadata or properties/events/etc. of the client type that will be instantiated and used on the client. This is what effectively tells the ASP.Net AJAX runtime about your class and what it will do.
- GetScriptReferences(): this returns a ScriptReference object that contains a reference to an external script file that fleshes out the functionality of your object.
The two methods will return all that is required for the AJAX runtime to fully instantiate, utilize and give you power over your ASP.Net AJAX control, on the client. Let's go back to our C# code and fill in the methods:
namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
"BensSamples.UnclickableButton", this.ClientID);
descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass);
descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);
return new ScriptDescriptor[] { descriptor };
}
public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = "UnclickableButton.js";
return new ScriptReference[] { reference };
}
private String _InactiveCSSClassPath = String.Empty;
public String InactiveCSSClass
{
get
{
return _InactiveCSSClassPath;
}
set
{
_InactiveCSSClassPath = value;
}
}
private String _ActiveCSSClassPath = String.Empty;
public String ActiveCSSClass
{
get
{
return _ActiveCSSClassPath;
}
set
{
_ActiveCSSClassPath = value;
}
}
}
}
Now, let's create a CSS file and add it to our project and the webform atop which our UnclickableButton will be placed:
(StyleSheet.css Contents:)
body {}
.InactiveButton{visibility:hidden;}
.ActiveButton{visibility:visible; }
Let's reference our new control from the .ASPX page:
<%
@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="UnclickableButton" TagPrefix="BensSample" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<br />
<BensSample:TheUnclickableButton Text="Click Me!" ID="Button1" runat="server"
InActiveCSSClass="InactiveButton" ActiveCSSClass="ActiveButton" />
</form>
</body>
</html>
Now, what happens when we run this? Unfortunately, nothing...why? The ScriptManager is the arbiter of all things ASP.Net AJAX; and for any control to participate within the ASP.net AJAX runtime, it must first be registered with the ScriptManager. So, we add the following.....
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
_scriptManager = ScriptManager.GetCurrent(this.Page);
if (_scriptManager != null)
{
_scriptManager.RegisterScriptControl(this);
}
else
throw new ApplicationException("You must have a ScriptManager!");
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
_scriptManager.RegisterScriptDescriptors(this);
base.Render(writer);
}
We should now be able to run our software....the complete source code follows:
Server:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web;
using System.Web.UI.WebControls;
namespace UnclickableButton
{
public class TheUnclickableButton : Button, IScriptControl
{
public TheUnclickableButton()
{
return;
}
private ScriptManager _scriptManager = null;
protected override void OnPreRender(EventArgs e)
{
if (!this.DesignMode)
{
_scriptManager = ScriptManager.GetCurrent(this.Page);
if (_scriptManager != null)
{
_scriptManager.RegisterScriptControl(this);
}
else
throw new ApplicationException("You must have a ScriptManager!");
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (!this.DesignMode)
_scriptManager.RegisterScriptDescriptors(this);
base.Render(writer);
}
public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
"BensSamples.UnclickableButton", this.ClientID);
descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass);
descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);
return new ScriptDescriptor[] { descriptor };
}
public IEnumerable<ScriptReference> GetScriptReferences()
{
ScriptReference reference = new ScriptReference();
reference.Path = "/AJAXTest/UnclickableButton.js";
return new ScriptReference[] { reference };
}
private String _InactiveCSSClassPath = String.Empty;
public String InactiveCSSClass
{
get
{
return _InactiveCSSClassPath;
}
set
{
_InactiveCSSClassPath = value;
}
}
private String _ActiveCSSClassPath = String.Empty;
public String ActiveCSSClass
{
get
{
return _ActiveCSSClassPath;
}
set
{
_ActiveCSSClassPath = value;
}
}
}
}
Client
Type.registerNamespace('BensSamples');
BensSamples.UnclickableButton = function(element){
BensSamples.UnclickableButton.initializeBase(this,[element]);
this._ActiveCSSClass = null;
this._InactiveCSSClass = null;
}
BensSamples.UnclickableButton.prototype = {
initialize : function() {
// initialize the base
BensSamples.UnclickableButton.callBaseMethod(this,'initialize');
// register delegates
this._onActiveHandler = Function.createDelegate(this,this._onBlur);
this._onInactiveHandler = Function.createDelegate(this,this._onFocus);
// register handlers
$addHandlers(this.get_element(),
{ 'focus' : this._onFocus,
'blur' : this._onBlur},
this);
// set default css style
this.get_element().className = this._ActiveCSSClass;
},
dispose : function() {
// release the handlers
$clearHandlers(this.get_element());
// call to the base to do its dispose
BensSamples.UnclickableButton.callBaseMethod(this,'dispose');
},
// flesh out the actual handlers
_onFocus : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// disabled class type.
this.get_element().className = this._InactiveCSSClass;
return;
}
},
_onBlur : function(e) {
if(this.get_element() && !this.get_element().disabled){
// if we have something real and it's enabled, set it to the
// enabled class type.
this.get_element().className = this._ActiveCSSClass;
return;
}
},
// create accessor methods corresponding to the server control
get_ActiveCSSClass : function(){
return this._ActiveCSSClass;
},
set_ActiveCSSClass : function(value){
if(this._ActiveCSSClass!=value){
this._ActiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('ActiveCSSClass');
}
},
get_InactiveCSSClass : function(){
return this._InactiveCSSClass;
},
set_InactiveCSSClass : function(value){
if(this._InactiveCSSClass!=value){
this._InactiveCSSClass = value;
// let everyone know the property changed
this.raisePropertyChanged('InactiveCSSClass');
}
}
}
// register the class
BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();