Creating Custom Client Script by Using the Microsoft AJAX Library

The AJAX features in ASP.NET help you create client script and integrate it into ASP.NET applications. This includes a type system for ECMAScript (JavaScript) and extensions to existing ECMAScript (JavaScript) objects to give them the richness of .NET Framework classes. ASP.NET also includes the ScriptManager control to manage these script libraries and any custom script in your application.

This topic contains the following sections:

  • Scenarios

  • Using the Type System

  • Using Extensions to the JavaScript Base Types

  • Integrating Client Script into ASP.NET Web Applications

Scenarios

You can use features of the Microsoft AJAX Library when you want to do the following:

  • Add object-oriented capabilities to your JavaScript code to increase code reuse, flexibility, and maintainability.

  • Use reflection to examine the structure and components of client script at run time.

  • Use enumerations to provide an easily readable alternative to integer representations.

  • Use extensions to the JavaScript base types to cut down development time for common scripting tasks.

  • Use debugging extensions and a trace feature for faster and more informative debugging than with traditional JavaScript debugging techniques.

Using the Type System

The Microsoft AJAX Library adds a type system and extensions to JavaScript objects to provide frequently used object-oriented features that resemble features in the .NET Framework. They enable you to write AJAX-enabled ASP.NET applications in a structured way that improves maintainability, makes it easier to add features, and makes it easier to layer functionality. Microsoft AJAX Library extensions add the following capabilities to JavaScript:

  • Classes

  • Namespaces

  • Inheritance

  • Interfaces

  • Enumerations

  • Reflection

The library also provides helper functions for strings and arrays.

Classes, Members, and Namespaces

The Microsoft AJAX Library includes base classes, and objects and components that derive from them. Together, these classes enable you to use an object-oriented programming model for writing client script. 

The Type class adds object-oriented features such as namespaces, classes, and inheritance to JavaScript programming. Any JavaScript object that is registered by using the Type class automatically has access to this functionality. The following example shows how to use the Type class to create and register a namespace and a class in a JavaScript file:

Type.registerNamespace("Demo");

Demo.Person = function(firstName, lastName, emailAddress) {
    this._firstName = firstName;
    this._lastName = lastName;
    this._emailAddress = emailAddress;
}

Demo.Person.prototype = {

    getFirstName: function() {
        return this._firstName;
    },

    getLastName: function() {
        return this._lastName;
    },

    getName: function() {
        return this._firstName + ' ' + this._lastName;
    },

    dispose: function() {
        alert('bye ' + this.getName());
    }
}
Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

// Notify ScriptManager that this is the end of the script.
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Classes can have four kinds of members: fields, properties, methods, and events. Fields and properties are name/value pairs that describe characteristics of an instance of a class. Fields are composed of primitive types and are accessed directly, as in the following example:

myClassInstance.name="Fred"

Properties can represent any primitive or reference type. You access property values with get and set accessor methods. In the Microsoft AJAX Library, the get and set accessors are functions, which by convention use the prefix "get_" or "set_" in the function name. For example, to get or set a value for a property such as cancel, you call the get_cancel or set_cancel methods.

The Microsoft AJAX Library raises events in response to actions that occur during the life-cycle of an AJAX client application. The Microsoft AJAX Library also provides a standard way for you to create custom events for AJAX client components. For more information see Creating Custom Client Events and AJAX Client Life-Cycle Events.

The Microsoft AJAX Library provides a way to register namespaces so that you can group common functionality. The following example demonstrates how to add a Person class to the Demo namespace using the Type.registerNamespace and .registerClass methods.

To enable AJAX functionality for an ASP.NET Web page, you must add a ScriptManager control to the page. When the page is rendered, the appropriate script references to AJAX client-script libraries are generated automatically. The following example shows a page with a ScriptManager control.

<asp:ScriptManager runat="server" ID="scriptManager" />

The following example shows how to register the namespace, create the class, and then register the class.

Type.registerNamespace("Demo");

Demo.Person = function(firstName, lastName, emailAddress) {
    this._firstName = firstName;
    this._lastName = lastName;
    this._emailAddress = emailAddress;
}

Demo.Person.prototype = {

    getFirstName: function() {
        return this._firstName;
    },

    getLastName: function() {
        return this._lastName;
    },

    getName: function() {
        return this._firstName + ' ' + this._lastName;
    },

    dispose: function() {
        alert('bye ' + this.getName());
    }
}
Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);

// Notify ScriptManager that this is the end of the script.
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Namespace</title>
</head>

<body>
    <form id="Main" runat="server">
        <asp:ScriptManager runat="server" ID="scriptManager" />
    </form>

    <div>
        <p>This example creates an instance of the Person class 
            and puts it in the "Demo" namespace.</p>

        <input id="Button1" value="Create Demo.Person" 
            type="button" onclick="return OnButton1Click()" />

    </div>

    <script type="text/javascript" src="Namespace.js"></script>
    <script type="text/javascript" language="JavaScript">

    function OnButton1Click() 
    {
        var testPerson = new Demo.Person(  
            'John', 'Smith', 'john.smith@example.com');
        alert(testPerson.getFirstName() + " " +    
            testPerson.getLastName() );

        return false;
    }
    </script>
</body>
</html>

Access Modifiers

Most object-oriented programming languages include the concept of access modifiers, which allow you to specify under what contexts a class or member is available, such as to outside programs, internal classes within the same namespace, or only within a specific code block. There are no access modifiers in JavaScript. However, the Microsoft AJAX Library follows the convention that members with names that start with the underscore character ("_") are considered private and not accessed outside the class that they are a part of.

Inheritance

Inheritance is the ability of one class to derive from another class. A derived class automatically inherits all the fields, properties, methods and events of the base class. A derived class can add new members or override existing members of the base class to change their behavior.

The following example contains two classes defined in script: Person and Employee, where Employee derives from Person. Both classes illustrate the use of private fields, and both have public properties and methods. In addition, Employee overrides the toString implementation of the Person class, and uses the base class functionality.

Type.registerNamespace("Demo");

Demo.Person = function(firstName, lastName, emailAddress) {
    this._firstName = firstName;
    this._lastName = lastName;
    this._emailAddress = emailAddress;
}

Demo.Person.prototype = {
    getFirstName: function() {
        return this._firstName;
    },

    getLastName: function() {
        return this._lastName;
    },

    getEmailAddress: function() {
        return this._emailAddress;
    },
    setEmailAddress: function(emailAddress) {
        this._emailAddress = emailAddress;
    },

    getName: function() {
        return this._firstName + ' ' + this._lastName;
    },

    dispose: function() {
        alert('bye ' + this.getName());
    },

    sendMail: function() {
        var emailAddress = this.getEmailAddress();

        if (emailAddress.indexOf('@') < 0) {
            emailAddress = emailAddress + '@example.com';
        }
        alert('Sending mail to ' + emailAddress + ' ...');
    },

    toString: function() {
        return this.getName() + ' (' + this.getEmailAddress() + ')';
    }
}

Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable);
Demo.Employee = function(firstName, lastName, emailAddress, team, title) {
    Demo.Employee.initializeBase(this, [firstName, lastName, emailAddress]);

    this._team = team;
    this._title = title;
}

Demo.Employee.prototype = {

    getTeam: function() {
        return this._team;
    },
    setTeam: function(team) {
        this._team = team;
    },

    getTitle: function() {
        return this._title;
    },
    setTitle: function(title) {
        this._title = title;
    },
    toString: function() {
        return Demo.Employee.callBaseMethod(this, 'toString') + '\r\n' + this.getTitle() + '\r\n' + this.getTeam();
    }
}
Demo.Employee.registerClass('Demo.Employee', Demo.Person);
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Inheritance</title>
</head>

<body>
    <form id="Main" runat="server">
        <asp:ScriptManager runat="server" ID="scriptManager" />
    <script type="text/javascript" src="Inheritance.js"></script>
    </form>

    <h2>Inheritance</h2>
    <p />

    <div>
        This file contains two classes defined in script: Person and Employee, where
        Employee derives from Person.
        <p />

        Each class has private fields, and public properties and methods. In addition,
        Employee overrides the toString implementation, and in doing so, it uses the
        base class functionality.
        <p />

        This example puts the Person class in the "Demo" namespace.
        <p />
    </div>


    <div>
        <ul>
            <li><a href="#" onclick="return OnTestNewClick()">Object Creation</a></li>
            <li><a href="#" onclick="return OnTestDisposeClick()">Object Dispose</a></li>
            <li><a href="#" onclick="return OnTestPrivatePropertyClick()">Public vs. Private Properties</a></li>
            <li><a href="#" onclick="return OnTestInstanceMethodClick()">Instance Methods</a></li>
            <li><a href="#" onclick="return OnTestOverrideMethodClick()">Overriden Methods</a></li>
            <li><a href="#" onclick="return OnTestInstanceOfClick()">Instance Of Check</a></li>
        </ul>
    </div>

    <script type="text/javascript" language="JavaScript">

    function GetTestPerson() 
    {
        return new Demo.Person('Jane', 'Doe', 'jane.doe@example.com');
    }

    function GetTestEmployee() 
    {
        return new Demo.Employee('John', 'Doe', 'john.doe@example.com', 'Platform', 'Programmer');
    }

    function OnTestNewClick() {
        var aPerson = GetTestPerson();

        alert(aPerson.getFirstName());
        alert(aPerson);
        alert(Object.getType(aPerson).getName());

        var testPerson = GetTestPerson();
        alert(testPerson.getFirstName());
        alert(testPerson);

        return false;
    }

    function OnTestDisposeClick() {
        var aPerson = GetTestEmployee();
        alert(aPerson.getFirstName());
        aPerson.dispose();
    }

    function OnTestPrivatePropertyClick() {
        var aPerson = GetTestEmployee();
        alert('aPerson._firstName = ' + aPerson._firstName);
        alert('aPersona.getFirstName() = ' + aPerson.getFirstName());

        return false;
    }

    function OnTestInstanceMethodClick() {
        var aPerson = GetTestEmployee();
        aPerson.sendMail('Hello', 'This is a test mail.');

        return false;
    }

    function OnTestOverrideMethodClick() {
        var testPerson = GetTestEmployee();
        alert(testPerson);

        return false;
    }

    function OnTestInstanceOfClick() {
        var aPerson = GetTestEmployee();
        if (Demo.Employee.isInstanceOfType(aPerson)) {
            alert(aPerson.getName() + ' is an Employee instance.\r\nTitle property: ' + aPerson.getTitle());
        }

        return false;
    }

    </script>
</body>
</html>

Interfaces

An interface defines the input and output requirements of classes that implement it. This enables a function to interact with classes that implement the same interface regardless of what other functionality the class implements.

The following example defines a Tree base class and an IFruitTree interface. Apple and Banana, two derived classes, implement the IFruitTree interface, but the Pine class does not. Any class that implements the IFruitTree interface makes sure that a bearFruit method is a member of that class.

Type.registerNamespace("Demo.Trees");

Demo.Trees.IFruitTree = function() {}
Demo.Trees.IFruitTree.Prototype = {
    bearFruit: function(){}
}
Demo.Trees.IFruitTree.registerInterface('Demo.Trees.IFruitTree');


Demo.Trees.Tree = function(name) {
    this._name = name;
}
Demo.Trees.Tree.prototype = {
    returnName: function() {
        return this._name;
    },

    toStringCustom: function() {
        return this.returnName();
    },

    makeLeaves: function() {}
}
Demo.Trees.Tree.registerClass('Demo.Trees.Tree');


Demo.Trees.FruitTree = function(name, description) {
    Demo.Trees.FruitTree.initializeBase(this, [name]);
    this._description = description;
}
Demo.Trees.FruitTree.prototype.bearFruit = function() {
        return this._description;
}
Demo.Trees.FruitTree.registerClass('Demo.Trees.FruitTree', Demo.Trees.Tree, Demo.Trees.IFruitTree);

Demo.Trees.Apple = function() {
    Demo.Trees.Apple.initializeBase(this, ['Apple', 'red and crunchy']);
}
Demo.Trees.Apple.prototype = {
    makeLeaves: function() {
        alert('Medium-sized and desiduous');
    },
    toStringCustom: function() {
        return 'FruitTree ' + Demo.Trees.Apple.callBaseMethod(this, 'toStringCustom');
    }
}
Demo.Trees.Apple.registerClass('Demo.Trees.Apple', Demo.Trees.FruitTree);

Demo.Trees.GreenApple = function() {
    Demo.Trees.GreenApple.initializeBase(this);
    // You must set the _description feild after initializeBase
    // or you will get the base value.
    this._description = 'green and sour';
}
Demo.Trees.GreenApple.prototype.toStringCustom = function() {
    return Demo.Trees.GreenApple.callBaseMethod(this, 'toStringCustom') + ' ... its GreenApple!';
}
Demo.Trees.GreenApple.registerClass('Demo.Trees.GreenApple', Demo.Trees.Apple);


Demo.Trees.Banana = function(description) {
    Demo.Trees.Banana.initializeBase(this, ['Banana', 'yellow and squishy']);
}
Demo.Trees.Banana.prototype.makeLeaves = function() {
    alert('Big and green');
}
Demo.Trees.Banana.registerClass('Demo.Trees.Banana', Demo.Trees.FruitTree);



Demo.Trees.Pine = function() {
    Demo.Trees.Pine.initializeBase(this, ['Pine']);
}
Demo.Trees.Pine.prototype.makeLeaves = function() {
    alert('Needles in clusters');
}
Demo.Trees.Pine.registerClass('Demo.Trees.Pine', Demo.Trees.Tree);

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Interface</title>
</head>

<body>
    <form id="Main" runat="server">
        <asp:ScriptManager runat="server" ID="scriptManager" />
    </form>

    <h2>Interface</h2>
    <p />

    <div>
        This file contains a Tree base class, and an IFruitTree interface.
        Apple and Banana, two derived classes implement that interface, whereas,
        Pine does not implement that interface.
        <p />
    </div>

    <script type="text/javascript" src="Interface.js"></script>

    <div>
        <ul>
                <li><a href="#" onclick="return OnTestNewClick()">Object Creation</a></li>
                <li><a href="#" onclick="return OnTestImplementsClick()">Implements Check</a></li>
                <li><a href="#" onclick="return OnTestInterfaceMethodClick()">Call interface method</a></li>
        </ul>
    </div>

    <script type="text/javascript" language="JavaScript">

    function OnTestNewClick() {
        var apple = new Demo.Trees.Apple('Apple');
        alert(apple.returnName());
        apple.makeLeaves();

        return false;
    }

    function OnTestImplementsClick() {
        var apple = new Demo.Trees.Apple();
        if (Demo.Trees.IFruitTree.isImplementedBy(apple)) {
            alert('Apple implements IFruitTree');
        }
        else {
            alert('Apple does not implement IFruitTree');
        }

        var pine = new Demo.Trees.Pine();
        if (Demo.Trees.IFruitTree.isImplementedBy(pine)) {
            alert('Pine implements IFruitTree');
        }
        else {
            alert('Pine does not implement IFruitTree');
        }

        return false;
    }

    function OnTestInterfaceMethodClick() {
        var apple = new Demo.Trees.Apple();
        ProcessTree(apple);

        var pine = new Demo.Trees.Pine();
        ProcessTree(pine);

        var banana = new Demo.Trees.Banana();
        ProcessTree(banana);

        var g = new Demo.Trees.GreenApple();
        ProcessTree(g);

        return false;
    }

    function ProcessTree(tree) {
        alert('Current Tree ' + tree.returnName());
        alert(tree.toStringCustom());
        if (Demo.Trees.IFruitTree.isImplementedBy(tree)) {
            alert(tree.returnName() + ' implements IFruitTree; Fruit is ' + tree.bearFruit());
        }
    }
    </script>
</body>
</html>

Enumerations

An enumeration is a class that contains a set of named integer constants. You access the values like properties, as in the following example:

myObject.color = myColorEnum.red

Enumerations provide an easily readable alternative to integer representations. For more information about enumerations in the Microsoft AJAX Library, see Type.registerEnum Method (ASP.NET AJAX).

The following example defines an enumeration of named colors that represent hexadecimal values.

Type.registerNamespace("Demo");

// Define an enumeration type and register it.
Demo.Color = function(){};
Demo.Color.prototype = 
{
    Red:    0xFF0000,
    Blue:   0x0000FF,
    Green:  0x00FF00,
    White:  0xFFFFFF 
}
Demo.Color.registerEnum("Demo.Color");
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Enumeration</title>
</head>

<body>
    <form id="Main" runat="server">
        <asp:ScriptManager runat="server" ID="scriptManager" />
    </form>

    <div>
        <p>This example creates an Enumeration of colors
            and applies them to page background.</p>

        <select id="ColorPicker" 
            onchange="ChangeColor(options[selectedIndex].value)">
            <option value="Red" label="Red" />
            <option value="Blue" label="Blue" />
            <option value="Green" label="Green" />
            <option value="White" label="White" />
        </select>

    </div>

    <script type="text/javascript" src="Enumeration.js"></script>
    <script type="text/javascript" language="JavaScript">

    function ChangeColor(value) 
    {
         document.body.bgColor = eval("Demo.Color." + value + ";");
    }

    </script>

</body>
</html>

Reflection

Reflection is the ability to examine the structure and components of a program at run time. The APIs that implement reflection are extensions of the Type class. These methods enable you to collect information about an object, such as what it inherits from, whether it implements a particular interface, and whether it is an instance of a particular class.

The following example uses reflection APIs to test the GreenApple class from the previous interface example.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Reflection</title>
</head>

<body>
    <form id="Main" runat="server">
        <asp:ScriptManager runat="server" ID="scriptManager" />
    </form>

    <div>
        <p>This example tests the Demo.Trees.GreenApple class 
            against various reflection APIs.</p>

        <input id="Button1" value="Check Type" 
            type="button" onclick="return OnButton1Click()" />
        <input id="Button2" value="Check Inheritance" 
            type="button" onclick="return OnButton2Click()" />
        <input id="Button3" value="Check Interface" 
            type="button" onclick="return OnButton3Click()" />

    </div>

    <script type="text/javascript" src="Interface.js"></script>
    <script type="text/javascript" language="JavaScript">

    var g = new Demo.Trees.GreenApple();
    var gt = Demo.Trees.GreenApple;
    var a = new Array(
        Demo.Trees.Apple, 
        Demo.Trees.Tree, 
        Demo.Trees.Pine,
        Demo.Trees.IFruitTree,
        Sys.IContainer);

    function OnButton1Click() 
    {
        for (var i = 0; i < a.length; i ++)
        {
            if (a[i].isInstanceOfType(g))
            {
                alert(gt.getName() + " is a " + a[i].getName() + ".");
            }
            else alert(gt.getName() + " is not a " + a[i].getName() + ".");
        }
    }

    function OnButton2Click() 
    {
        for (var i = 0; i < a.length; i ++)
        {
            if (gt.inheritsFrom(a[i]))
            {
                alert(gt.getName() + " inherits from " + a[i].getName() + ".");
            }
            else alert(gt.getName() + " does not inherit from " + a[i].getName() + ".");
        }
    }

    function OnButton3Click() 
    {
        for (var i = 0; i < a.length; i ++)
        {
            if (Type.isInterface(a[i]))
            {
                if (gt.implementsInterface(a[i]))
                {
                    alert(gt.getName() + " implements the " + a[i].getName() + " interface.");
                }
                else alert(gt.getName() + " does not implement the " + a[i].getName() + " interface.");
            }
            else alert(a[i].getName() + " is not an interface.");
        }
    }
    </script>
</body>
</html>

Using Extensions to the JavaScript Base Types

Extensions to JavaScript base types provide additional functionality for those types. For more information about these extensions, see the following topics:

The Sys.Debug class provides extensive debugging capabilities. For more information, see Debugging and Tracing AJAX Applications Overview and the Sys.Debug class overview.

If you create components that are based on the Microsoft AJAX Library, you can create can create debug and release versions of script files that are automatically managed by the ScriptManager control. You can identify debug versions of script files by including ".debug" as part of the script file name. For example, the following script file names identify retail and debug versions of a file:

  • MyScript.js (retail)

  • MyScript.debug.js (debug)

Integrating Client Script into ASP.NET Web Applications

Any ASP.NET Web page can access a script file by referring to it in a <script> block, as in the following example:

<script type="text/javascript" src="MyScript.js"></script>

However, a script invoked in this manner cannot participate in partial-page rendering or access certain components of the Microsoft AJAX Library. To make a script file available for partial-page rendering in an AJAX-enabled ASP.NET Web application, the script must be registered with the ScriptManager control on the page. To register a script file, create a ScriptReference object that points to the file question and that adds it to the Scripts collection. The following example shows how to do this in markup:

<asp:ScriptManager ID="SMgr" runat="server">
  <Scripts>
    <asp:ScriptReference path="MyScript.js" />
  </Scripts>
</asp:ScriptManager> 

For script files to be processed correctly by the ScriptManager control, each file must include a call to the Sys.Application.notifyScriptLoaded method at the end of the file. This call notifies the application that the file has finished loading. The following example shows the code to use for this purpose:

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

You can also embed .js files as resources in managed-code assemblies. (You might do this if create an ASP.NET server control with AJAX functionality that is implemented in client script.) If your script is embedded in an assembly, you do not have to include a notification statement in the script. You also do not have to specify a path attribute in the script reference. However, you must provide the name of the assembly without the file name extension, as shown in the following example:

<asp:ScriptManager ID="SMgr" runat="server">
  <Scripts>
    <asp:ScriptReference 
        Name="MyScript.js" Assembly="MyScriptAssembly"/>
  </Scripts>
</asp:ScriptManager> 

Note

This scenario is not common for page developers, because most controls with embedded script libraries reference their scripts internally. For more information, see Walkthrough: Embedding a JavaScript File as a Resource in an Assembly.

You can also register scripts programmatically by creating script references in code and then adding them to the Scripts collection. For more information, see Dynamically Assigning Script References.

You can register scripts that are required for partial-page updates by using the registration methods of the ScriptManager control. You can use these methods in the following ways:

  • To generate client script in code, build a block of script as a string and pass it to the RegisterClientScriptBlock method.

  • To add stand-alone script files that have no Microsoft AJAX Library dependencies, use the RegisterClientScriptInclude method. 

  • To add script files that are embedded in an assembly, use the RegisterClientScriptInclude method.

    Note

    Scripts that are registered by using these methods do not have localization support.

For a complete list of script-registration methods and their uses, see the ScriptManager control overview.

Any script blocks or inline script that you are registering must be inside the page's <form> element. Otherwise, the script is not registered with the ScriptManager control and cannot access ASP.NET AJAX functionality. For more information, see Sys.Application.initialize Method.

See Also

Tasks

Sample AJAX Application

Concepts

Partial-Page Rendering Overview

ASP.NET AJAX Overview

Client Script in ASP.NET Web Pages

Creating Client Components and Controls

Client Reference

Reference

Type Class