Developing Components Using the AJAX Library Framework

Among the challenges in developing AJAX applications are how to package applications into libraries and how to build true object-oriented frameworks. Although it’s easy to build scripts that respond to events with functional programming, a functional approach can lead to unmaintainable and nonportable scripts. A design goal and best practice of AJAX programming is to create a number of reusable components that can work with many different back-end services. If developed with this goal in mind, your AJAX application can be deployed in many applications as long as the supporting Web services are also deployed.

For example, let’s say your company wants to develop rich Internet applications but is also interested in the growing portal market and understands the need to develop applications for Microsoft Office SharePoint Server. Monolithic application frameworks are difficult to migrate from a controlled environment to a heterogeneous portal environment where they need to coexist with Web parts from multiple vendors. But, if you develop loosely coupled, component-based frameworks that are written against a portable Web service API, it is far simpler to port application components from a pure ASP.NET environment to a portal environment.

Component development is supported in the Microsoft AJAX framework through the Sys.Component base class, which provides support for properties and events and offers simple life-cycle management. The object Sys.Application is an implementation of the component class Sys._Application, a framework component class that is derived directly from Sys.Component. The following code sample from the AJAX library demonstrates how the Sys.Application object derives from Sys.Component. As the framework is loaded, the Sys.Application instance is created using the Sys_Application class, which fires events and controls the page life cycle.

Sys._Application.registerClass('Sys._Application', Sys.Component, Sys.IContainer);
Sys.Application = new Sys._Application();

To develop a custom component, you need to create a JavaScript class that derives from Sys.Component. Example 7-5 defines a simple component with a text property. The register-Class method creates an inheritance chain so that SOAjax.Examples.MyComponent inherits from Sys.Component(as covered in Chapter 4).

Example 7-5. A component is based on the AJAX class Sys.Component(Web/Script/Examples.js).

Type.registerNamespace("SOAjax.Examples");
SOAjax.Examples.MyComponent = function() {
    /// <summary>
    /// An sample component class that demonstrates basic component implementation.
    /// </summary>
}
SOAjax.Examples.MyComponent.prototype = {
    initialize: function() {
        SOAjax.Examples.MyComponent.callBaseMethod(this, 'initialize'),
    },

    _text: null,
    get_text: function() {
        /// <value>Gets or sets the component's text.</value>
        return this._text;
    },
    set_text: function(text) {
        this.raisePropertyChanged('text'),
        this._text = text;
    },
    dispose: function() {
        // Use this to clear any handlers.
        SOAjax.Examples.MyComponent.callBaseMethod(this, 'dispose'),
    }
}

SOAjax.Examples.MyComponent.registerClass('SOAjax.Examples.MyComponent',
    Sys.Component);

As Example 7-5 demonstrates, the component is based on the AJAX class Sys.Component. Registering the class by using the registerClass method of the custom component type enables it to be derived from Sys.Component and inherit the base class’s functionality. The advantage of using a component rather than a standard JavaScript class definition is that the component has a built-in event framework and creation support through the Sys.Component.create method ($create) that includes event and property binding. The $create method is kind of like a factory method that is used to create component instances.

Tip

Tip

If your class does not include properties or events, you probably don’t need to implement a component.

Instead of using the standard constructor to create a component, you use the static create method of Sys.Component. The alias for Sys.Component.create is $create, which is the method that we’ll use in the sample code. The method’s signature is $create(type, properties, events, references, element), where type is the JavaScript type and properties, events, and references are JSON-formatted objects containing bindable properties and events. By using $create and setting initial property and event bindings, you can build loosely coupled components that deliver functionality without sacrificing object-oriented design in JavaScript code. The following example creates a component using the $create method and sets the text property to "Hello, AJAX Nation!":

var example = $create(SOAjax.Examples.MyComponent, {text : 'Hello, AJAX Nation!'});

For more complicated components, you might need to use more verbose syntax, like the following code, which can be more readable when you have a larger number of properties to set:

var exampleProperties = {text : 'Hello, AJAX Nation!'};
var example = $create(SOAjax.Examples.MyComponent, exampleProperties);

$create performs eight tasks during the creation of the component. These tasks can be seen in Example 7-6, which contains abbreviated source code from the AJAX library. First the component is created using the standard constructor, as in the following sample. The element variable is nullable and is used to assign a DOM element to the control if the component is an implementation of Sys.UI.Control. The $create method is used for components and controls because controls inherit from Component but include support for DOM elements.

var component = (element ? new type(element) : new type());

Next the component’s beginUpdate method is called, which in turn sets the component’s isUpdating property to true.

component.beginUpdate();

Next, any properties passed into $create are set. In the example above, we pass in the property text with the value "Hello, AJAX Nation!". This property is then set during the creation of the object and has the same effect as calling example.set_text( ‘Hello, AJAX Nation’) after the object’s creation. However, when properties are set during execution of the $create method, the values are available in the object’s initialization code—in particular the component’s initialize method. This makes it critical to use the $create method and property bindings if you’re creating page components during the page’s init or load events.

After setting the original properties, the component adds event handlers that are passed in the creation method. For example, you might want to add code that gets called in response to component events, much like the init event of the Sys.Application component.

Next, if the component implements an id property (exposed through get_id), the component is registered with Sys.Application so that it can be called using the Sys.Application.find method. This property is usually implemented in control classes but not in pure component classes.

The component then sets any references it might have. With an AJAX component, a reference is a special type of property that is used to refer to another AJAX object and enables reliable creation of composite components. A composite component uses multiple controls and components to implement functionality.

Finally, the endUpdate method is called, which sets the updating property to false and ensures the component is initialized. If the component is not initialized, the endUpdate method will call initialize. Here is the endUpdate method, which is called at the end of the create method:

function Sys$Component$endUpdate() {
    this._updating = false;
    if (!this._initialized) this.initialize();
    this.updated();
}

Examining the entire $create method will help you better understand the component creation process. Example 7-6 contains the debug source code for the $create method from the Microsoft AJAX Library.

Example 7-6. The $create method is used to create and bind Sys.Component classes (excerpt from MicrosoftAjax.debug.js).

// $create component creation method from the Microsoft AJAX Library
var $create = Sys.Component.create = function Sys$Component$create(
     type, properties, events, references, element) {
    var app = Sys.Application;
    var creatingComponents = app.get_isCreatingComponents();
    component.beginUpdate();
    if (properties) {
        Sys$Component$_setProperties(component, properties);
    }
    if (events) {
        for (var name in events) {
            if (!(component["add" + name] instanceof Function))
                throw new Error.invalidOperation(
                    String.format(Sys.Res.undefinedEvent, name));
            if (!(events[name] instanceof Function))
                throw new Error.invalidOperation(Sys.Res.eventHandlerNotFunction);
            component["add" + name](events[name]);
        }
    }
    if (component.get_id()) {
        app.addComponent(component);
    }
    if (creatingComponents) {
        app._createdComponents[app._createdComponents.length] = component;
        if (references) {
            app._addComponentToSecondPass(component, references);
        }
        else {
            component.endUpdate();
        }
    }
    else {
        if (references) {
            Sys$Component$_setReferences(component, references);
        }
        component.endUpdate();
    }
    return component;
}

By examining the source code of the Sys.Component.create method in Example 7-6, you can see how the framework performs property and event binding. You can also see how Sys.Application is used in the component creation process. Components that implement the page runtime should be created during the Sys.Application.Init event, in which case Sys.Application performs a second pass after object creation to set any component references. This step enables component code references for composite components while guaranteeing the creation state of each object.

Passing Context to Event Handlers

Working with object-oriented components requires an approach to JavaScript programming that is different from traditional functional programming in JavaScript. In the traditional model, a static function might be called in response to an event, but instead of stateless static functions, we’ll be using object-oriented methods in which state and object-instance references are important. We want to bind instance methods to event handlers to create an object-oriented framework. The problem we encounter, however, is that by default, code fired by an event in JavaScript is called as an instance of the object that fired the event, even if a handler that is an instance method is added to the event through code such as $addHandler(element, ‘click’, this.eventHandler). Before I demonstrate the correct method using Function.createDelegate, it’s best to examine the default behavior.

A click handler that is bound to a DOM element runs under the context of the DOM element—the this keyword references the DOM element that was clicked to fire the event and not the instance of the class under which you expect to be executing. Consider the code in Example 7-7. When you add an event handler, the following code is executed, passing the this.testFunction method as the handler.

// bad code example: "this" will not be "this"
$addHandler(this._testButton, 'click', this.testHandler);

Unfortunately, in the context of the handler method (testHandler), this no longer refers to the instance of the component that was passed with the method. Instead, this refers to the DOM element that was clicked to fire the event. To demonstrate this behavior, which will cause bugs in your application, examine the SOAjax.Examples.ReferenceDemo component class shown in Example 7-7.

Example 7-7. Passing an instance method to an event handler yields unexpected results (Web/Script/ Examples.js).

SOAjax.Examples.ReferenceDemo = function() {
    /// <summary>
    /// An example component class that demonstrates reference passing.
    /// </summary>
}
SOAjax.Examples.ReferenceDemo.prototype = {
    initialize: function() {
        SOAjax.Examples.ReferenceDemo.callBaseMethod(this, 'initialize'),
    },

    _testButton: null,
    get_testButton: function() {
        /// <value>Gets or sets the test button used for the example.</value>
        return this._testButton;
    },
    set_testButton: function(button) {
        this._testButton = button;
        // The handler for 'ButtonTest' will have a bad this reference,
        // referencing the DOM element not this component instance:
        $$addHandler(this._testButton, 'click', this.testFunction);
    },

    dispose: function() {
        $clearHandlers(this._testButton);
        SOAjax.Examples.ReferenceDemo.callBaseMethod(this, 'dispose'),
    }
}
SOAjax.Examples.ReferenceDemo.registerClass(
    'SOAjax.Examples.ReferenceDemo', Sys.Component);

function initComponents() {
    $create(SOAjax.Examples.ReferenceDemo, { testButton: $get('ButtonTest')} );
}

Sys.Application.add_init(initComponents);

When attaching an event handler to a DOM element, you must create a delegate with which to pass context. As in C#, JavaScript delegates are simply references to instance methods. The JavaScript delegate references an instance method and is created with the Function.createDelegate method, which is a method in the Microsoft AJAX Library. The Function.Delegate method is shown in Example 7-8. It takes an instance and method parameter and returns a new function that calls the instance’s method.

Example 7-8. Function.createDelegate creates a reference-aware method pointer to an instance method.

Function.createDelegate = function Function$createDelegate(instance, method) {
    return function() {
        return method.apply(instance, arguments);
    }
}

To create a handler that calls the current object, always use the Function.createDelegate method. The delegate is then used to call the current object instance. Without the delegate, the code in the handler does not execute from the object instance, and the this reference refers to the DOM object rather than the component object that the handler was created for. The code sample in Example 7-9 builds on the earlier code listing of the SOAjax.Examples. ReferenceDemo component and demonstrates the this reference with and without the delegate.

Example 7-9. Function.createDelegate is used to create instance-method references for event handlers (Web/ Script/Examples.js).

SOAjax.Examples.ReferenceDemo = function() {
    /// <summary>
    /// An example component class that demonstrates reference passing.
    /// </summary>
}
SOAjax.Examples.ReferenceDemo.prototype = {
    initialize: function() {
        SOAjax.Examples.ReferenceDemo.callBaseMethod(this, 'initialize'),
    },

    _testButton: null,
    _testButton2: null,

    get_testButton: function() {
        /// <value>Gets or sets the test button used for the example.</value>
        return this._testButton;
    },
    set_testButton: function(button) {
        this._testButton = button;
        // The handler for 'ButtonTest' will have a bad this reference,
        // referencing the DOM element not this component instance:
        $$addHandler(this._testButton, 'click', this.testFunction);
    },
    testFunction: function() {
        debugger;
        Sys.Debug.traceDump(this)
    },

    get_testButton2: function() {
        ///<value>Gets or sets the testButton2</value>
        return this._testButton2;
    },
    set_testButton2: function(button) {
        this._testButton2 = button;
        // The handler for 'ButtonTest2' will have a reference to this component
        // because it is created with the createDelegate method.
        $addHandler(this._testButton2, 'click',
            Function.createDelegate(this, this.testFunction));
    },

    dispose: function() {
        $clearHandlers(this._testButton);
        $clearHandlers(this._testButton2);
        SOAjax.Examples.ReferenceDemo.callBaseMethod(this, 'dispose'),
    }
}

SOAjax.Examples.ReferenceDemo.registerClass(
    'SOAjax.Examples.ReferenceDemo', Sys.Component);

function initComponents() {
    $create(SOAjax.Examples.ReferenceDemo,
        { testButton: $get('ButtonTest'), testButton2: $get('ButtonTest2') });
}

Sys.Application.add_init(initComponents);

Example 7-9 demonstrates the Function.createDelegate method used to create an instance-method reference. Notice that the dispose method of the class is used to clean up any handlers so that handles to components that are removed from the page aren’t kept alive.

Building an Application Services Component

In Chapter 6, we developed a login page by creating various methods that called Sys.Services in response to user actions. However, the code we ended up with wasn’t very concise, object-oriented, or reusable. Basically, we had screen elements with JavaScript commands wired to them. While this is fine for demonstrating the Sys.Services Web service model, it isn’t an approach you want to take with production code. Static JavaScript commands that handle DOM events can lead to unmaintainable "spaghetti" code, and you might soon find that you have a messy library of functions that are tied to specific page elements and must be recoded to be used in other contexts. You might also find that your AJAX implementation is slow and buggy compared to object-oriented server code. This doesn’t need to be the case, because a properly developed component library goes far in helping you build a robust application framework.

To develop a component-based application, you need to define the central architecture and divide the client-side application into controls, components, and markup. In the next chapter you’ll see how to build control classes to provide application functionality. First, however, we’ll build an application services component that abstracts the authentication and authorization business rules from the page. In this model, the page JavaScript will tie together the application components and controls and bind them to DOM elements. The page will also bind event handlers of the application runtime to control implementations, letting the page respond to major events such as authentication or navigation. With this strategy, the components and controls can even be developed and tested independently.

Tip

Tip

While it is out of the scope of this book, components and controls are ideally built using a test-driven approach. Christian Gross’s excellent books on AJAX use this approach and are recommended reading for any AJAX application developer. These books are Ajax Patterns and Best Practices (Addison Wesley, 2006) and Ajax and REST Recipes: A Problem-Solution Approach (Addison Wesley, 2006).

For the application component, we’ll develop a run-time component that wraps Sys.Services calls and fires authentication events that the page can respond to. Because we don’t want to implement the HTML in JavaScript, we’ll use static HTML in the page for the login controls, and we’ll bind the controls to the application runtime. To start, the page HTML is included in Example 7-10. The page consists of three main areas: navigation, login controls, and the chief content area, as shown in Figure 7-2. We’ll use this basic page shell in this chapter for the application runtime component example. We’ll build on this shell and application component when we add functional controls in the next chapter.

The application runtime in action.

Figure 7-2. The application runtime in action.

Example 7-10. Simple HTML form elements can be used as login controls (Web/Default.aspx).

<%@ Page %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Service-Oriented AJAX</title>
    <link href="Style/StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <form id="form1" runat="server">
    <asp:ScriptManager ID="AjaxScriptManager" runat="server"
            RoleService-LoadRoles="true"
            ProfileService-LoadProperties="startTopic"
            >
        <Scripts>
            <asp:ScriptReference Path="~/Script/SOAjax.js" />
            <asp:ScriptReference Path="~/Script/ApplicationRuntime.js" />
            <asp:ScriptReference Path="~/Script/Page.js" />
        </Scripts>
    </asp:ScriptManager>
    <div id="pageHeader">
        Service-Oriented AJAX on the Microsoft Platform

        <div id="logoutControl" class="loginControl" style="display: none;" >
            <button id="ButtonLogout" type="button" value="Logout">Logout</button>
            [Logged in]
        </div>

        <div style="display: none;" id="loginControl" class="loginControl">
            <div class="widgetTitle" id="loginLink">Login</div>
            <div class="lightPadding" id="loginUI" style="display: none;">
                Username <br />
                <input type="text" id="usernameInput" /><br /><br />

                Password <br />
                <input type="password" id="passwordInput" /><br /><br />

                <button id="ButtonLogin" value="Login" type="button"
                title="Login">Login</button> &nbsp;
                <button id="ButtonCancelLogin" value="Cancel" type="button"
                title="Cancel">Cancel</button>
                <div id="loginFeedbackDiv">
                </div>
            </div>
        </div>
    </div>

    <div id="pageBody">
        <table style="width:100%; height:100%" cellpadding="0" cellspacing="0">
            <tr valign="top">
                <td width="150px" style="background-color:#EFEFEF; padding:15px;">
                    <div id="_EditControls" style="display:none;">
                        Logged-in controls
                    </div>
                    <div id="NavigationPlaceholder"
                        style="width:150px; background-color:#EFEFEF">
                        Navigation goes here...
                    </div>
                </td>
                <td width="100%">
                    <div id="MainContent" style="overflow:auto;" >
                        Service-Oriented AJAX on the Microsoft Platform:
                        Example Code.
                    </div>
                </td>
            </tr>
        </table>
    </div>
  </form>
</body>
</html>

Perhaps the first thing you’ll notice when looking at the page is that it contains no embedded JavaScript. No onclick events are defined on buttons, and the login controls are implemented as simple form elements. We’ll wire the HTML controls to the application component through the $create method after developing the component. The component itself fires the authenticated event, which can be handled by external event handlers, and contains properties that are used to bind the component to DOM elements on the HTML page.

In this manner, the application runtime is contained in a single component class, and it is bound to a set of DOM elements that make up the login user interface. When a user is successfully authenticated or logged out, the application runtime object fires events that the page can handle. For example, we want to display edit controls only when the user is authenticated.

For controls, we want to define the following properties, each of which corresponds to a DOM element on the page. Because the DOM elements aren’t implementing the behavior, we don’t need to create controls for these elements. We can simply access them from the application runtime component.

To create the login functionality, we’ll define the following properties: usernameInput, passwordInput, loginButton, cancelLoginButton, logoutButton, loginUI, and loggedinUI. The loginUI and loggedinUI DOM elements represent DIV elements on the page that show a user interface for an anonymous user and a user who is logged in. These elements contain the controls for logging in or out, but they are used only to hide or show a group of controls. The input controls contain the user input values that the component accesses, and the buttom elements have event handlers added and bound to the component. Each property is defined with get and set methods that are called from the Sys.Component’s create method ($create) if properties are passed. These methods are commonly referred to as getters and setters, where the get_ and set_ prefixes are used with the property name to define the method names.

To begin converting the authentication code to a component, we need to create a simple component called SOAjax.Application. This application component abstracts the authentication logic from the page, wraps the Web service authentication calls, and fires bindable events that the page can handle. Within the component, we start off by defining the private fields and public properties that reference DOM elements.

To define a property in the component’s prototype, use the following syntax to create get and set methods, following the naming convention used by the Microsoft AJAX library:

propertyName = null,
get_propertyName: function() { return this._propertyName; },
set_ propertyName: function(val) { this._propertyName = val; },

To create events, all that is needed are add and remove methods, which are used to add or remove handler methods to events. Again, you must follow the naming convention used by the Microsoft AJAX library. The following syntax is used to create an event, where the name eventName is an arbitrarily named custom event:

add_eventName: function(handler) {
    this.get_events().addHandler("eventName", handler);
},
remove_eventName: function(handler) {
    this.get_events().removeHandler("eventName", handler);
}

Keep in mind that for both handlers and callbacks, Function.createDelegate is required to create instance references. To fire the event, a handler is obtained from the Sys.EventHandlerList instance (returned by calling this.get_events within a component) as discussed earlier in this chapter. The following code reviews the syntax to use for firing an arbitrary event from the class that contains the event:

// The following code fires the event eventName
var handler = this.get_events().getHandler("eventName");
if (handler) {
    handler(this, Sys.EventArgs.Empty);
}

Example 7-11 defines a simple component class that contains properties for the relevant login controls on the page. The controls we have defined are usernameInput and passwordInput for credential input, loginButton and logoutButton (which we’ll add click handlers to), and the loginUI and loggedinUI elements that can contain child DOM controls, including the login feedback element. We’re not defining an AJAX library control itself for the authentication component because we’ll use various DOM controls on the page within a single component.

Example 7-11. The application runtime component defines properties for bindable controls (Web/Script/1.ApplicationRuntime.js).

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

Type.registerNamespace('SOAjax'),
SOAjax.Application = function() {
    /// <summary>
    /// A component that controls the page lifecycle, integrated with Sys.Services.
    /// </summary>
    SOAjax.Application.initializeBase(this);
}

SOAjax.Application.prototype = {
    usernameInput: null,
    passwordInput: null,
    loginUI: null,
    loggedinUI: null,
    logoutButton: null,
    loginButton: null,
    loginFeedbackDiv: null,

    initialize: function() {
        SOAjax.Application.callBaseMethod(this, 'initialize'),
    },

    get_usernameInput: function() {
        ///<value>The authentication username text input element</value>
        return this.usernameInput;
    },
    set_usernameInput: function(control) { this.usernameInput = control; },

    get_passwordInput: function() {
        ///<value>The authentication password input element</value>
        return this.passwordInput;
    },
    set_passwordInput: function(control) { this.passwordInput = control; },

    get_loginButton: function() {
        ///<value>The login button element</value>
        return this.loginButton;
    },
    set_loginButton: function(control) {
        this.loginButton = control;
    },
    get_cancelLoginButton: function() {
        ///<value>The cancel login button element</value>
        return this.cancelLoginButton;
    },
    set_cancelLoginButton: function(control) {
        this.cancelLoginButton = control;
    },

    get_logoutButton: function() {
        ///<value>The logout button element</value>
        return this.logoutButton;
    },
    set_logoutButton: function(control) {
        this.logoutButton = control;
    },

    get_loginUI: function() {
        ///<value>The login UI DOM element (div or span)</value>
        return this.loginUI;
    },
    set_loginUI: function(control) {
        this.loginUI = control;
    },

    get_loggedinUI: function() {
        ///<value>The logged in UI DOM element (div or span)</value>
        return this.loggedinUI;
    },
    set_loggedinUI: function(control) {
        this.loggedinUI = control;
    },

    get_loginFeedbackDiv: function() {
        ///<value>A feedback element, used for authentication failure</value>
        return this.loginFeedbackDiv;
    },
    set_loginFeedbackDiv: function(control) {
        this.loginFeedbackDiv = control;
    },

    cancelLogin: function(domEvent) {
        if (this.loginUI != null)
            this.loginUI.style.display = 'none';
    },

    dispose: function() {
        // Use this to clear any handlers.
        SOAjax.Application.callBaseMethod(this, 'dispose'),
    }
}

SOAjax.Application.registerClass('SOAjax.Application', Sys.Component);
Sys.Application.notifyScriptLoaded();

The initial application runtime isn’t too useful. All it does is bind DOM elements to a component class. To make the application runtime more useful, we want to integrate authentication logic using Sys.Services Web services, bind to DOM events, and fire events such as authenticated. By firing the authenticated event, we can let the page class evolve according to the logged-in status of the user.

To add DOM event handlers to the component, use the initialize method to add handler methods to the DOM properties. You can also provide some basic validation logic to ensure required properties have been set. The following code demonstrates the addition of event handlers using Function.createDelegate within the initialize method:

initialize: function() {
    SOAjax.Application.callBaseMethod(this, 'initialize'),

    if (this.loginButton == null || this.cancelLoginButton == null
        || this.logoutButton == null || this.passwordInput == null
        || this.loginUI == null || this.loggedinUI == null
        || this.loginFeedbackDiv == null) {
        throw Error.invalidOperation( 'The application component requires the DOM
            elements passwordInput,loginUI,loggedinUI,logoutButton,loginButton and
            loginFeedbackDiv'),
    }
    $addHandler(this.loginButton, 'click',
        Function.createDelegate(this, this.login));
    $addHandler(this.cancelLoginButton, 'click',
        Function.createDelegate(this, this.cancelLogin));
    $addHandler(this.logoutButton, 'click',
        Function.createDelegate(this, this.logout));
}

We also want to create login functionality and the authenticated event. Because the application runtime is derived from the Sys.Component class, the event handler list is already defined using the events property. The following code demonstrates the implementation of the authenticated event:

add_authenticated: function(handler) {
    this.get_events().addHandler("authenticated", handler);
},
remove_authenticated: function(handler) {
    this.get_events().removeHandler("authenticated", handler);
}

When the component is successfully authenticated, it can call the following code to fire the authenticated event:

this.raisePropertyChanged('isAuthenticated'),
var handler = this.get_events().getHandler("authenticated");
if (handler) {
    handler(this, Sys.EventArgs.Empty);
}

By firing the authenticated event and raising the propertyChanged event, we’re letting client code handle the event. The event would be fired in response to the actual authentication from the server. Example 7-12 shows the entire source code of the finished SOAjax.ApplicationRuntime component class, integrating the Sys.Services authentication logic, the authenticated event, and event bindings.

Example 7-12. The application runtime component defines events that can be handled from client code (Web/Script/ApplicationRuntime.js).

/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace('SOAjax'),

SOAjax.Application = function() {
    /// <summary>
    /// A component that controls the page lifecycle, integrated with Sys.Services.
    /// </summary>
    SOAjax.Application.initializeBase(this);
}
SOAjax.Application.prototype = {
    usernameInput: null,
    passwordInput: null,
    loginUI: null,
    loggedinUI: null,
    logoutButton: null,
    loginButton: null,
    loginFeedbackDiv: null,

    initialize: function() {
        SOAjax.Application.callBaseMethod(this, 'initialize'),
        // basic property validation:
        if (this.loginButton == null || this.cancelLoginButton == null
        || this.logoutButton == null || this.passwordInput == null
        || this.loginUI == null || this.loggedinUI == null
        || this.loginFeedbackDiv == null) {
        throw Error.invalidOperation( 'The application component requires the DOM
            elements passwordInput,loginUI,loggedinUI,logoutButton,loginButton and
            loginFeedbackDiv'),
    }
        $addHandler(this.loginButton, 'click',
            Function.createDelegate(this, this.login));
        $addHandler(this.cancelLoginButton, 'click',
            Function.createDelegate(this, this.cancelLogin));
        $addHandler(this.logoutButton, 'click',
            Function.createDelegate(this, this.logout));
    },

    get_usernameInput: function() {
        ///<value>The authentication username text input element</value>
        return this.usernameInput;
    },
    set_usernameInput: function(control) { this.usernameInput = control; },

    get_passwordInput: function() {
        ///<value>The authentication password input element</value>
        return this.passwordInput;
    },
    set_passwordInput: function(control) { this.passwordInput = control; },

    get_loginButton: function() {
        ///<value>The login button element</value>
        return this.loginButton;
    },
    set_loginButton: function(control) {
        this.loginButton = control;
    },

    get_cancelLoginButton: function() {
        ///<value>The cancel login button element</value>
        return this.cancelLoginButton;
    },
    set_cancelLoginButton: function(control) {
        this.cancelLoginButton = control;
    },

    get_logoutButton: function() {
        ///<value>The logout button element</value>
        return this.logoutButton;
    },
    set_logoutButton: function(control) {
        this.logoutButton = control;
    },

    get_loginUI: function() {
        ///<value>The login UI DOM element (div or span)</value>
        return this.loginUI;
    },
    set_loginUI: function(control) {
        this.loginUI = control;
    },

    get_loggedinUI: function() {
        ///<value>The logged in UI DOM element (div or span)</value>
        return this.loggedinUI;
    },
    set_loggedinUI: function(control) {
        this.loggedinUI = control;
    },

    get_loginFeedbackDiv: function() {
        ///<value>A feedback element, used for authentication failure</value>
        return this.loginFeedbackDiv;
    },
    set_loginFeedbackDiv: function(control) {
        this.loginFeedbackDiv = control;
    },

    login: function(domEvent) {
        var userName = this.usernameInput.value;
        var password = this.passwordInput.value;
        var loginDelegate =
            Function.createDelegate(this, this.authenticateCallback);
        var faulureDelegate = Function.createDelegate(this, this.failureCallback);
        var context = new Object();
        Sys.Services.AuthenticationService.login(userName, password, true,
            null, null, loginDelegate, faulureDelegate, context);
    },

    cancelLogin: function(domEvent) {
        if (this.loginUI != null)
            this.loginUI.style.display = 'none';
    },

    logout: function(domEvent) {
        Sys.Services.AuthenticationService.logout(
            null,
            Function.createDelegate(this, this.onLogoutCompleted),
            Function.createDelegate(this, this.failureCallback),
            null);
    },

    onLogoutCompleted: function() {
        this.updateControls();
    },

    authenticateCallback: function(isLoggedIn, userContext, methodName) {
        if (this.passwordInput != null)
            this.passwordInput.value = '';
        if (!isLoggedIn) {
            this.get_loginFeedbackDiv().innerHTML = 'Unable to log in.';
            return;
        }
        else {
            this.get_loginFeedbackDiv().innerHTML = '';
            Sys.Services.RoleService.load(
                Function.createDelegate(this, this.rolesCompleted),
                Function.createDelegate(this, this.failureCallback), this);
        }
        this.updateControls();
    },

    rolesCompleted: function(roles, context, foo) {
        if (Sys.Services.AuthenticationService.get_isLoggedIn()) {
            this.raisePropertyChanged('isAuthenticated'),
            var handler = this.get_events().getHandler("authenticated");
            if (handler) {
                handler(this, Sys.EventArgs.Empty);
            }
        }
    },
    rolesFailed: function() { },

    failureCallback: function(error, userContext, methodName) {
        debugger;
    },
    get_isAuthenticated: function() {
        /// <value type="Boolean">
        /// True if user is authenticated, false if anonymous.
        /// </value>
        return Sys.Services.AuthenticationService.get_isLoggedIn();
    },

    // events
    add_authenticated: function(handler) {
        /// <summary>Adds a event handler for the authenticated event.</summary>
        /// <param name="handler" type="Function">
        /// The handler to add to the event.
        /// </param>
        this.get_events().addHandler("authenticated", handler);
    },
    remove_authenticated: function(handler) {
        /// <summary>Removes a event handler for the authenticated event.</summary>
        /// <param name="handler" type="Function">
        /// The handler to remove from the event.
        /// </param>
        this.get_events().removeHandler("authenticated", handler);
    },

    updateControls: function() {
        var isLoggedIn = Sys.Services.AuthenticationService.get_isLoggedIn();
        if (!isLoggedIn) {   // show the login UI
            if (this.loggedinUI != null) this.loggedinUI.style.display = 'none';
            if (this.loginUI != null) this.loginUI.style.display = '';
        } else {            // show the logout UI.
            if (this.loggedinUI != null) this.loggedinUI.style.display = '';
            if (this.loginUI != null) this.loginUI.style.display = 'none';
        }
    },

    dispose: function() {
        // Use this to clear any handlers.
        this.usernameInput = null;
        this.passwordInput = null;
        if (this.loginButton != null)
            $clearHandlers(this.loginButton);
        this.loginButton = null;

        if (this.logoutButton != null)
            $clearHandlers(this.logoutButton);
        this.logoutButton = null;

        SOAjax.Application.callBaseMethod(this, 'dispose'),
    }
}

SOAjax.Application.registerClass('SOAjax.Application', Sys.Component);
Sys.Application.notifyScriptLoaded();

With the code in Example 7-12, we have a fully functional application runtime component that wraps the authentication logic of Sys.Services and fires events, enabling the component to be bound to page controls at run time. Next we’ll create a page library that ties the logic from the page to the application components. Although this step can be performed in the HTML page itself, we’ll define a page-based JavaScript file called Page.js that forms our page runtime. In the page JavaScript, we use the $create method to create an instance of the application run-time component we’ve built, which we bind to the page’s DOM elements through properties.

Example 7-13 contains the Page.js JavaScript file. This code creates an instance of SOAjax. Application and passes property and event objects. Sys.Component sets the properties and events in the $create method. For example, passing { ‘authenticated’ : Page.OnAuthenticated } to the events parameter has the same effect as calling component.add_authenticated(Page.OnAuthenticated) after creating a new component. With this event binding, the page’s OnAuthenticated method is bound to the application runtime’s authenticated event. By using this event-driven approach, the code in the authentication class does not need to know about the application that hosts it, and the actual application implementation can still respond to authentication and authorization events.

Example 7-13. Page.js forms the JavaScript code behind for the application page (Web/Script/Page.js).

/// <reference name="MicrosoftAjax.js"/>
/// <reference path="ApplicationRuntime.js"/>

Type.registerNamespace('Page'),
Page.load = function() {
    var isAuthenticated = Sys.Services.AuthenticationService.get_isLoggedIn();
    var appProperties = {
        usernameInput: $get('usernameInput'),
        passwordInput: $get('passwordInput'),
        loginButton: $get('ButtonLogin'),
        cancelLoginButton: $get('ButtonCancelLogin'),
        logoutButton: $get('ButtonLogout'),
        loginUI: $get('loginControl'),
        loggedinUI: $get('logoutControl'),
        loginFeedbackDiv : $get('loginFeedbackDiv')
    };

    var appEvents = { 'authenticated': Page.OnAuthenticated };
    Page.App = $create(SOAjax.Application, appProperties, appEvents, null, null);
    Page.App.updateControls();
    $addHandler($get('loginLink'), 'mouseover', Page.ShowLoginUI);
}

Page.OnAuthenticated = function(sender, eventArgs) {
    Sys.Debug.trace('Page.OnAuthenticated'),
    if (Sys.Services.RoleService.isUserInRole('contributor')) {
        //show contributor controls on page
        $get('_EditControls').style.display = '';
    }
    if (Sys.Services.RoleService.isUserInRole('moderator')) {
        //instantiate moderator controls on page
    }
}

Page.unload = function() {
    if (Page.App != null)
        Page.App.dispose();
}

Sys.Application.add_load(Page.load);
Sys.Application.add_unload(Page.unload);

Page.ShowLoginUI = function(eventArgs) {
    $get('loginUI').style.display = '';
}

Sys.Application.notifyScriptLoaded();

Example 7-13 demonstrates the basic JavaScript page implementation. Remember that the page doesn’t need to be a component. We simply ran some functions as the application loaded to create our component instance. In the next chapter, when we build AJAX controls using Sys.UI.Control class implementations, we’ll create control instances using this approach and bind the event handlers. This is a common development pattern and a best practice. The page class contains only enough logic to tie the application components together.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset