In this chapter:
XML Script is a declarative language for creating instances of JavaScript objects at runtime, setting their properties, and specifying their behavior, using an XML-like syntax similar to the ASP.NET markup code.
In an HTML page, you can separate content (the markup code) from style by embedding the style information in a CSS file. Similarly, in an ASP.NET page, you usually define the page layout using declarative markup code in an ASPX page. Then, you can use a separate code-behind file to specify the behavior of server controls and how they’re wired together, using the classic imperative syntax. XML Script lets you achieve this kind of separation and instantiate JavaScript components using a declarative script language embedded in a web page.
XML Script, like declarative languages, has a number of advantages over the imperative syntax. Building designers for markup is easier than building them for code. Great visual tools, like the Visual Studio Designer, take care of generating markup code for you. If a client can parse declarative markup, you can make server controls render the markup more easily than rendering imperative code. In addition, declarative markup carries semantics. For example, an application that parses a TextBox tag knows that it has to instantiate a text field, but it’s up to the application to decide to instantiate a simple text field rather than a more complex auto-complete text box—for example, based on browser capabilities. Finally, declarative code can be more expressive and less verbose than imperative code. Features like bindings help keep the values exposed by object properties synchronized, without the need to deal with multiple event handlers.
This chapter illustrates these aspects of XML Script, beginning with the basics of the language and moving to advanced features like actions, bindings, and transformers. Keep in mind that because they’re part of the ASP.NET Futures package, the features illustrated in this chapter aren’t currently documented or supported by Microsoft.
Your first goal is learning how to write XML Script code and understanding how it’s turned into instances of client objects at runtime. As we’ll explain in a moment, writing XML Script code is similar to writing ASP.NET declarative code. The main difference is that whereas you use ASP.NET markup to create instances of server-side classes, you use XML Script code to create JavaScript objects.
Before you begin using XML Script, you need to enable it in a web page. This turns out to be an easy job, because you have to reference the PreviewScript.js file in the ScriptManager control, as shown in listing 11.1. This file is embedded as a web resource in the Microsoft.Web.Preview assembly, which is shipped with the ASP.NET Futures package. You can find more information on how to install this package in appendix A.
<asp:ScriptManager ID="TheScriptManager" runat="server"> <Scripts> <asp:ScriptReference Assembly="Microsoft.Web.Preview" Name="PreviewScript.js" /> </Scripts> </asp:ScriptManager>
XML Script code is embedded in script tags with the type attribute set to text/xml-script. This custom type was defined to distinguish blocks of XML Script code from other script code such as JavaScript. This is what the typical container of an XML Script code block looks like:
<script type="text/xml-script"> <!-- Insert xml-script here --> </script>
As you can see, XML Script comments have the same syntax as XML comments. You can have multiple blocks of XML Script code in the same page, and they can appear in any order and position. Unlike JavaScript code, though, at the moment XML Script can only appear inline in the page and can’t be saved to separate files.
As with any programming language, a “Hello, World!” example is the ideal ice-breaker for introducing basic XML Script features. It’s also a good starting point for learning how XML Script code is structured and to give you confidence with its syntax.
This example shows how a block of XML Script code is structured and how you can deal with client objects using declarative code. You’ll see how to handle an event raised by a client component using XML Script code. Normally, you’d accomplish this task by retrieving a reference to the component and writing the necessary JavaScript code to add an event handler. Listing 11.2 shows how to declaratively hook up the init event raised by Sys.Application, the Application object introduced in chapter 2. As promised, the event handler is a JavaScript function that displays a “Hello XML Script!” message onscreen.
Let’s have a closer look at the block of XML Script code contained in the page. It has a root element called page and a single child element called components. The page element defines a global XML namespace associated with the following Uniform Resource Identifier (URI):
http://schemas.microsoft.com/xml-script/2005
The page/components structure is the basic form of an XML Script code block. All the blocks of XML Script code in the page must have this structure in order to be parsed correctly. In section 11.1.5 we’ll return to the use of XML namespaces with XML Script.
An XML namespace is a collection of names, identified by a URI reference, used in XML documents as element types and attribute names. For more information on XML namespaces, check http://www.w3schools.com/xml/xml_namespaces.asp.
The components tag always contains the list of client objects declared in the page. These objects are represented by XML elements and are instances of classes created with the Microsoft Ajax Library. In this chapter, we’ll focus on client components, which are classes that derive from Sys.Component. The reason is that the XML Script engine already knows how to properly parse and instantiate such classes. If you recall, the creation process for a client component is rather elaborate, as we discussed in chapter 8.
In listing 11.2, application is the unique child node of components. In XML Script—as in the ASP.NET markup, for example—a tag is mapped to a class, and the element represents an instance of that class. The application tag is always mapped to the Application object, stored in the global Sys.Application variable. When the XML Script parser processes the application tag, it retrieves a reference to the Application object. Then, it recognizes the init attribute as the name of an event raised by the Application object. As a consequence, its value—pageInit—is treated as the name of the event handler.
The pageInit function declared in the JavaScript code block at the bottom of the page is invoked when the Application object raises the init event. This causes the greeting message to be displayed in a message box onscreen, as shown in figure 11.1.
The page still includes some JavaScript code, but you were able to perform the logic for attaching the event handler using only declarative code. Later, you’ll see how to make the JavaScript code disappear.
So far, you know how to access the Application object using XML Script. Usually, a rich web application hosts multiple components and even controls associated with DOM elements. Can you access them in declarative code and hook up their events? The answer is that all the kinds of client components can be instantiated and accessed using XML Script.
As you may have noticed while typing the first listing in Visual Studio, code completion isn’t available at the moment for XML Script. In addition, no support is provided for debugging XML Script code and for the Visual Studio Designer. As we said in the introduction, XML Script is part of the ASP.NET Futures package and is still under development.
In chapter 8, we introduced client controls and promised that they would be useful when dealing with XML Script. Client controls, when created as element wrappers, are the way to reach DOM elements using XML Script. An element wrapper is a control associated with a DOM element. As a wrapper, the client control exposes properties, methods, and events to deal with the associated element and enhance its functionality. As a client component, a control can be used in XML Script with little effort. The Microsoft Ajax Library ships with a collection of ready-to-use controls associated with the most-used DOM elements, such as labels and input elements.
Listing 11.3 is a slight variation on listing 11.2. It uses a button and a label to display the greeting message, instead of accessing the Application object.
The page’s HTML contains an input and a span element. The goal is to display a message in the label when the button is clicked. To accomplish this task, you don’t access the DOM elements directly. Instead, you deal with the corresponding controls, represented by the label and button elements in the XML Script code. Note that the value of their id attributes is set to the IDs of the DOM elements; this way, you associate the DOM elements with the client controls. We say that the DOM elements have been upgraded to client controls.
At runtime, the XML Script engine creates an instance of the Sys.Preview.UI.Label control and passes the span element as the associated element. Similarly, it creates an instance of the Sys.Preview.UI.Button control and passes the input element as the associated element. The value of the id attribute becomes the value of the id property exposed by the controls. This allows them to be referenced in XML Script code.
The click attribute of the button tag is mapped to the click event raised by the Button control. Its value is the name of the JavaScript function that handles the event. The function onGreetButtonClick uses the $find method to access the Label control and set the text of the associated span element through the set_text method.
Table 11.1 lists the element wrappers defined in the Sys.Preview.UI namespace. Note that you obtain the name of the associated tag—which is case insensitive—by removing the namespace prefix from the class name.
Class name |
Description |
Tag name |
---|---|---|
Sys.Preview.UI.Button | Wraps an input element of type button | button |
Sys.Preview.UI.Label | Wraps a span element | label |
Sys.Preview.UI.CheckBox | Wraps an input element of type checkbox | checkbox |
Sys.Preview.UI.HyperLink | Wraps an anchor element | hyperlink |
Sys.Preview.UI.Image | Wraps an img element | image |
Sys.Preview.UI.Selector | Wraps a select element | selector |
Sys.Preview.UI.TextBox | Wraps an input element of type text | textbox |
What if you need to target an element like div, which doesn’t have an associated wrapper control? You have to write an XML Script-enabled custom control that wraps it. But if you only need to wrap an element and access the base functionality provided by the Sys.UI.Control class, the easiest way is to use a control element. The control tag wraps a DOM element with a given id, with an instance of the Sys.UI.Control class, like so:
<control id="elementID" />
In XML Script, the markup code is always mapped to the properties of controls, not to the properties of the associated DOM elements. You need client components to interact with DOM elements using declarative code.
So far, we’ve talked about the components shipped with the Microsoft Ajax Library. You’ll probably want to use custom components in XML Script. In the following section, you’ll see how XML namespaces help the XML Script engine locate custom client classes.
An XML namespace declaration tells XML Script where to find the client class corresponding to an element declared in the markup code. In XML Script, you usually declare a global namespace in the page element, with the following code:
<page xmlns="http://schemas.microsoft.com/xml-script/2005" />
When you declare the global namespace, the XML Script parser tries to map a tag name to a client class contained in one of the following namespaces:
If you want to use, in XML Script, a component defined in a different namespace, you have to declare an XML namespace that tells where to find it.
In general, you declare an XML namespace by associating a URI with a prefix. The URI identifies the location of a certain resource, which isn’t necessarily associated with a browsable address: It acts as a unique identifier. The prefix is used as a shortcut that refers to the URI. For example, suppose you have a custom component declared as SomeSpace.SomeComponent. Because SomeSpace is a custom namespace, you have to declare an XML namespace if you want to use the component in XML Script. To do that, you have to act on the page element as follows:
<page xmlns="http://schemas.microsoft.com/xml-script/2005" xmlns:cc="javascript:SomeSpace" />
You declare an XML namespace with an xmlns attribute followed by a colon and the prefix that you’ll use in the XML Script code. The value of the xmlns attribute is the URI (javascript:SomeSpace in the example). The string javascript: at the beginning of the URI is called the scheme, which is required in order to obtain a valid URI. In this case, the scheme suggests that what follows is a list of one or more client namespaces, separated by commas. You can associate multiple client namespaces with a single prefix, like so:
<page xmlns="http://schemas.microsoft.com/xml-script/2005" xmlns:cc="javascript:SomeSpace, SomeSpace.ChildSpace">
This code tells the XML Script parser which namespaces to search for the class corresponding to an element declared with the cc prefix. The prefix should be a short and, if possible, meaningful string. In this case, cc stands for custom c ontrol. Assuming it exposes a proper type descriptor, you can use the custom component in XML Script as follows:
<cc:SomeComponent />
As explained in the previous section, the type descriptor maps an element’s attributes to properties of the component. The rules for writing XML Script code apply to custom components.
You may have noticed that the global namespace doesn’t have a prefix. Elements without a prefix belong to the global namespace. Under the hood, the global XML namespace is associated with the script prefix; so, this prefix refers to the namespaces listed earlier.
Before you learn how to use the custom classes in XML Script, you should understand how XML Script code is parsed and turned into JavaScript code. This will give you some insight into how things work under the hood of the XML Script engine. Then, you’ll be ready to explore some of the powerful features of the declarative language.
The process of converting the XML Script declarative code into JavaScript imperative code starts when a web page is loaded in the browser. If XML Script is enabled in the page, the Microsoft Ajax runtime instructs the XML Script parser to filter all the script tags with the type attribute set to text/xml-script. The XML Script parser is a JavaScript object stored in the Sys.Preview.MarkupParser variable. It exposes a group of methods for extracting and processing the XML Script code.
As the XML Script blocks are extracted, they’re collected in an array and processed sequentially. For each block, a sanity check is performed on its structure, to ensure that a root element called page exists. Also, the page element must have a child node called components. The parser ignores all the other tags in the page element.
The parser performs an additional check to see if a references element is declared in the page node. The references element was used in previous CTPs to provide a list of paths to script files to load in the page, but it’s not supported in the latest CTP. If a references tag is found, the XML Script parser throws an error.
As the current XML Script block is processed, all the child elements of the components tag are extracted and stored in an array. These are all the client objects that need to be instantiated. The instantiation process is performed by the parseNode method, which is called by the parser on each tag to parse the markup code and create an instance of the object.
First, the parseNode method needs to determine the fully qualified name of the class to instantiate. To locate the class, it extracts the tag name and the namespace prefix from the markup code. The tag name is the case-insensitive name of the class; it’s turned to uppercase. The information on the namespace is retrieved from the XML namespace prefix used in the tag. Finally, the fully qualified name of the class is obtained by appending the class name to its containing namespace.
If the class exists, the parser checks whether it exposes a static method called parseFromMarkup. This method must be defined in a class in order to be used in XML Script. It receives the markup code and is responsible for parsing it and creating a new instance of the class. This process is repeated for each XML Script block and for each tag extracted from the components node. When all the markup has been processed, all the client objects have been instantiated and can be safely accessed in the application code. This process is illustrated in figure 11.2.
Luckily, you can avoid writing the logic needed to parse the markup code and create an instance of a client class. The Sys.Component class exposes and implements the parseFromMarkup method; it’s a good choice to derive the custom classes from the base Sys.Component class. This way, you can take advantage of the features offered by the client component model and also use the custom class in XML Script with little effort. The only requirement is that every component must expose a type descriptor in order to be used in XML Script.
A type descriptor is an object that provides information about a particular type. In the .NET framework, you can perform reflection on objects to learn about their structure. For example, you can extract every sort of information about the fields, properties, methods, and events exposed by a class. On the server side, type descriptors can be used to provide additional reflection capabilities. On the client side, type descriptors have been introduced to achieve the same goal.
In chapter 3, we introduced the enhanced type system provided by the Microsoft Ajax Library, together with the methods used to reflect on client classes. Reflection on client objects is less powerful because many object-oriented constructs are simulated by extending function objects. You can use client type descriptors to partially fill this gap and provide information about the properties, methods, and events defined in a client class.
In the .NET framework, type descriptors are used to enhance reflection capabilities, especially for components that take advantage of the Visual Studio Designer. For more information on .NET type descriptors, browse to http://msdn2.microsoft.com/en-us/library/ms171819.aspx.
A client class can expose a type descriptor by storing it in a static descriptor property added directly to the constructor. For example, you would store and retrieve the type descriptor of a class called SomeSpace.SomeClass with the following statements:
SomeSpace.SomeClass.descriptor = {}; var descriptor = SomeSpace.SomeClass.descriptor;
Another way to expose a type descriptor is by implementing the Sys.Preview.ITypeDescriptorProvider interface. This interface defines a single method called getDescriptor, which must be implemented as an instance method of the client class. The implementation of the method should return the type descriptor associated with the client class, as in the following code:
SomeSpace.SomeClass.prototype.getDescriptor = function() { return {}; }
Client type descriptors aren’t strictly tied to XML Script and can be leveraged by every client class. But the XML Script engine needs a type descriptor in order to parse the markup code into an instance of a client component, so only client components that provide a type descriptor can be used in XML Script code.
In the previous code snippets, you returned {}—an empty object—as the type descriptor. The information should be packaged following specific rules that we’ll explain in a moment. In general, a client type descriptor is a JavaScript object that can provide custom information about the client type. The XML Script engine recognizes the following properties:
Each array, in turn, holds objects, each of which describes a property, a method, or an event exposed by the client class. To help you understand what a type descriptor looks like, listing 11.4 shows the one exposed by the Sys.Preview.UI.Button class.
Sys.Preview.UI.Button.descriptor = { properties: [ { name: 'command', type: String }, { name: 'argument', type: String } ], events: [ { name: 'click' } ] }
The type descriptor of the Button class exposes two property descriptors and one event descriptor. The first property is called command, and the corresponding property descriptor is an object with two properties: name and type. The name property returns a string with the name of the property you’re describing. The type property returns the type of the value exposed by the property. Here’s the property descriptor extracted from the type descriptor:
{ name: 'command', type: String }
Similarly, the second property descriptor tells you that the Button class exposes a property called argument, of type String. We’re talking about client properties as defined by the Microsoft Ajax Library, which we discussed in chapter 3. The unique event descriptor in listing 11.4 is relative to a click event. It’s an object with a name property that returns a string with the name of the event:
{ name: 'click' }
The Button class doesn’t provide any method descriptors. Describing a method requires additional work because you also have to describe its parameters. Figure 11.3 shows a method descriptor extracted from the type descriptor exposed by the Sys.Preview.InvokeMethodAction class. The method is called invoke, and it accepts a single parameter called userContext, of type Object. You’ll encounter this method again when we talk about actions in section 11.2.
In general, a method descriptor is an object with two properties, name and params. The name property returns a string with the name of the method. The params property returns an array of parameter descriptors. A list of the properties that can be used in a parameter descriptor can be found in chapter 13, where we explain the parameter-validation mechanism. Method parameters are described in the same way in type descriptors and in validation routines.
Thanks to type descriptors, the XML Script engine can discover which members are exposed by a client class and map them to attributes declared in the markup code. For example, by querying the type descriptor of the Button class, the XML Script parser knows that the value of the click attribute of a button tag should be treated as an event handler for the click event exposed by the class. By iterating the same processing to all the elements, the XML Script code can easily be converted into JavaScript code.
So far, we have presented the syntax and the main rules to write XML Script code. You also possess the skills to enable XML Script usage in the custom components. This is the right moment to examine the main features of the language. You must understand concepts like actions, bindings, and transformers to run complex client code without writing a single line of JavaScript.
In the previous examples, you saw how to hook up an event raised by a client component using XML Script code. Things went smoothly, and you didn’t have to write any client code to attach the handler to the event. But you did have to declare the function that handles the event, so some JavaScript code is still present in the page. Our main goal was to demonstrate that XML Script can effectively replace JavaScript code in many situations. Now we’re ready to introduce actions, which are classes that encapsulate portions of JavaScript code. This code can be executed in response to events raised by client components. Actions are perfectly suited for handling events declaratively.
As usual, examples will help to clarify this concept. Let’s start with an overview of the built-in actions in the Microsoft Ajax Library. Later, you’ll create custom actions and use them in XML Script.
A typical task performed when handling an event is to set one or more properties of an object. For example, you can intercept the click event of a button object and display some text in a label. To do the same thing using declarative code, you need the help of the SetProperty action. The SetPropertyAction class encapsulates the client code needed to set the value of a property exposed by a client object. Like all actions, this class can be used in XML Script. The code in listing 11.5 handles the click event of a button with the SetProperty action, in order to display a greeting message in a label.
<div><input type="button" id="greetButton" value="Click Me" /></div> <div><h1><span id="msgLabel"></span></h1></div> <script type="text/xml-script"> <page xmlns="http://schemas.microsoft.com/xml-script/2005"> <components> <label id="msgLabel" /> <button id="greetButton"> <click> <setPropertyAction target="msgLabel" property="text" value="Hello XML-script!" /> </click> </button> </components> </page> </script>
To handle an event with XML Script, you have to do two things: First you turn the name of the event into an XML element; then, you declare one or more actions in the event element. The code encapsulated by each action is executed in response to the event.
The code has a click element in the button tag. This element represents the click event raised by the Button control. In the click element, you declare a setPropertyAction element, which represents a SetProperty action. The target attribute specifies the ID of the client component that exposes the property you want to set. The property attribute holds the name of the property you’re interested in. The value attribute is set to the value you want to assign to the property. As a consequence, the text “Hello XML-script!” is displayed in the label.
With the SetProperty action, you can also access the properties of the DOM element associated with a control. Add the following markup in the click node in listing 11.5, just after the first setPropertyAction tag:
<setPropertyAction target="msgLabel" property="element" propertyKey="style.backgroundColor" value="#FFFF00" />
In this case, you have an additiona propertyKey attribute that contains the path to the backgroundColor property of the span element associated with the msgLabel control. Let’s compare the markup code with the equivalent imperative code:
$find('msgLabel').get_element().style.backgroundColor = '#FFFF00';
The property attribute, in this case, refers to the get_element method, which returns the associated DOM element. The value of the propertyKey attribute is appended to the object returned by get_element, and the result is the property to set. This causes the background color of the span element to become yellow. Figure 11.4 shows the example in listing 11.5 running in Firefox.
Did you see any JavaScript code in listing 11.5? With actions, you can wrap any kind of JavaScript code and execute it declaratively. The next built-in action we’ll examine is PostBack; it’s used to trigger a postback of the page.
ASP.NET pages use a JavaScript function called __doPostBack to post form data back to the server. The PostBack action wraps the call to __doPostBack to trigger the postback of the page from XML Script code. Let’s change the behavior of the button declared in listing 11.5. If you replace the button tag with the following code, you can make it trigger a postback when it’s clicked:
<button id="greetButton"> <click> <postBackAction target="greetButton" eventArgument="" /> </click> </button>
The target and eventArgument attributes set the corresponding arguments in the __doPostBack function. The previous markup code executes the following JavaScript code:
__doPostBack('greetButton', ''),
Another typical task performed by event handlers is invoking an object method. The Microsoft Ajax Library provides the InvokeMethod action to invoke a method declaratively in XML Script.
The InvokeMethod action is powerful because it invokes a method exposed by a client component and makes it possible to process the results using only declarative code. To demonstrate the InvokeMethod action, we’ll introduce a built-in client component called Sys.Preview.Net.ServiceMethodRequest. You can use this class to invoke a web method and process the results in a callback function. To add some spice, you do so using only XML Script code. In listing 11.6, you declare the Web Service used in the example. The only web method, GetTimeAsString, returns the current date and time on the web server. In the example, you retrieve this information and display it in a label.
<%@ WebService Language="C#" Class="DateTimeService" %> using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using Microsoft.Web.Script.Services; [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [ScriptService] public class DateTimeService : System.Web.Services.WebService { [WebMethod] [ScriptMethod(UseHttpGet=true)] public string GetTimeAsString() { return DateTime.Now.ToShortTimeString(); } }
The DateTimeService class represents an ASP.NET web service configured for ASP.NET AJAX. As usual, the class is decorated with the ScriptService attribute, which instructs ASP.NET AJAX to generate a JavaScript proxy for the web service. The GetTimeAsString web method returns a string with the current date and time. The ScriptMethod attribute that decorates the web method is used to change the way it’s invoked. Because the ServiceMethodRequest class uses GET as the default HTTP verb for making the request, you set the UseHttpGet parameter to true in the ScriptMethod attribute. You can find all the information needed to access Web Services with ASP.NET AJAX in chapter 5.
The Web Service is configured, so we can move on to the XML Script code. Listing 11.7 shows how to make a declarative call to the web method defined in the Web Service and access the returned string.
The HTML markup contains a span element associated with a Label control . You use the label to display the string returned by the web method. The serviceMethodRequest element is used to set up the call to the web method . The url attribute contains the path to the ASP.NET web service, and the methodName attribute specifies the name of the web method to invoke. The string returned by the web method is stored in the ServiceMethodRequest instance. You need to assign it an id in order to be able to access the instance in XML Script.
The ServiceMethodRequest class raises a completed event when the result of the web method call is available. This is a good occasion to handle the event declaratively and access the result property, which stores the returned string. You use a SetProperty action and a new tag (a binding) to extract the value of the result property and display it in the label. We’ll introduce bindings in section 12.3. For now, it’s enough to say that a binding can be used to synchronize the value exposed by two properties. In this case, you’re synchronizing the result property with the label’s text property. As soon as the result property is set, the same happens with the text property.
The InvokeMethod action , which is the action you’re interested in, is used to handle the load event of Sys.Application. As soon as the load event is raised, the InvokeMethod action calls the invoke method on the ServiceMethodRequest instance . Note that the instance can be referenced because you assigned it an id. Figure 11.5 shows the date string displayed in the label.
The built-in actions provided by the Microsoft Ajax Library enable you to perform the most common tasks in declarative event handlers. But the real fun begins when you encapsulate the JavaScript code in custom actions. This can dramatically decrease the amount of JavaScript code you write, while expanding the number of tasks that can be accomplished with XML Script code. Before we discuss the base classes for creating custom actions, we need to talk briefly about how to set complex properties using the XML Script syntax.
Suppose you have a client component that exposes a property whose value is a simple JavaScript object (represented by an object literal) or an array. A good question would be whether you can expand the simple object or add elements to the array declaratively, using XML Script. The answer is positive.
Consider the example of a declarative web service call, from listing 11.7. You know the ServiceMethodRequest class can be used to call a method defined in a Web Service and process its results. It’s a client component, and it exposes a type descriptor. As a consequence, you can use the class in XML Script. But what happens if the web method that you want to invoke accepts parameters? How can you pass them using XML Script code?
If you look at the type descriptor exposed by the ServiceMethodRequest class, you’ll see that the class has a property called parameters, which is of type Object. Here’s the property descriptor we’re talking about:
{name: 'parameters', type: Object, readOnly: true}
The property is marked read-only because it returns a reference to a simple object that you can further expand. Whenever you add a property to this object, you add a parameter that will be passed to the web method. The name of the property represents the name of the parameter, and the property’s value is the value of the parameter. For example, let’s say you’ve rewritten (or overloaded) the GetTimeAsString method as follows:
[WebMethod] [ScriptMethod(UseHttpGet=true)] public string GetTimeAsString(string formatString) { return DateTime.Now.ToString(formatString); }
The method accepts a single parameter called formatString, which is used to customize the format of the string with the current date. If the instance of the ServiceMethodRequest class is called instance, you can write the following imperative code to pass the formatString parameter:
instance.get_parameters().formatString = 'ddd MMM yyyy hh:mm:ss';
The previous imperative code can be obtained from the following declarative code:
<serviceMethodRequest id="timeServiceMethod" url="DateTimeService.asmx" methodName="GetTimeAsString"> <parameters formatString="ddd MMM yyyy hh:mm:ss" /> </serviceMethodRequest>
You’ve added a parameters element with the same name of the property that returns the parameters object. At this point, each attribute that follows is added as a property of the exposed object, and its value becomes the value of the property. You can also declare multiple parameters elements, each with attributes that are parsed as properties of the parameters object.
In the case of the ServiceMethodRequest class, the names of the properties added to the parameters object must match the names and the case of the parameters declared in the web method. Otherwise, an exception will be raised at runtime.
A similar notation is used for properties that return arrays. In XML Script, the property is represented, as usual, by a tag with the same name as the property. The difference is that every child element is parsed as an instance of a client object and then added to the array. For example, suppose the client component exposes a property called someArray, which returns a simple array literal. In XML Script, you can write the following code:
<someComponent> <someArray> <label id="myLabel" /> <textbox id="myTextBox" /> </someArray> </someComponent>
When this code is parsed, the array returned by the someArray property contains two items: an instance of the Label control and an instance of the TextBox control.
Now, we’ll again focus on XML Script features. Our goal was to leverage client actions. So, let’s have some fun with them.
Creating a custom action is a straightforward process. First, you have to create a client class that derives from the base Sys.Preview.Action class. Then, you must override the performAction method. In this method, you insert the JavaScript code that the action encapsulates.
Let’s examine two examples of custom actions. The first example is an action called AlertAction, which wraps a call to the JavaScript alert function that displays a string in a message box onscreen. In listing 11.8, the AlertAction class not only overrides the performAction method but also exposes a custom message property and a type descriptor.
The performAction method contains the call to the alert function. The string passed to the function is returned by the message property. Because every action is a client component (because the base Sys.Preview.Action class derives from Sys.Component), you need a type descriptor to use the custom action in declarative code. You only need to describe the unique message property exposed by the class.
Now, look at listing 11.9, which shows an example use of the Alert action. The example displays a greeting message as soon as the Application object raises its load event.
<script type="text/xml-script"> <page xmlns=http://schemas.microsoft.com/xml-script/2005 xmlns:cc="javascript:Samples"> <components> <application> <load> <cc:alertAction message="Hello Xml-script!" /> </load> </application> </components> </page> </script>
As we explained in section 11.1.5, if the custom component is located in a different namespace than those listed in table 11.1, you should declare a new XML namespace. In the page element, you associate the cc prefix with a URI that contains the namespace to which the component belongs.
The next custom action you’ll create extends the existing PostBack action and prompts the user with a confirmation message before triggering the postback of the page. If the user answers Yes, the postback is performed; otherwise, it’s aborted. The code is shown in listing 11.10.
The message property sets the text to display in the confirmation window. The performAction method contains the JavaScript code to execute when the action is triggered. You ask the user whether to perform a postback of the page. If the user agrees, you fire the base PostBack action by invoking the performAction method on the base class. As you learned in chapter 3, this can be done with the callBaseMethod method.
Let’s write some code to test the new ConfirmPostBack action. Listing 11.11 shows how to use the custom action to handle the click of a button element.
<div> <input type="button" id="myButton" value="Click Me" /> </div> <script type="text/xml-script"> <page xmlns="http://schemas.microsoft.com/xml-script/2005" xmlns:cc="javascript:Samples"> <components> <button id="postBackButton"> <click> <cc:confirmPostBackAction target="postBackButton" message="Do you want to perform a postback?" /> </click> </button> </components> </page> </script>
Note that in the confirmPostBackAction element, you set the target attribute that belongs to the base PostBackAction class. Then, you set the message attribute, which is mapped to the message property of the custom action class. The result is shown in figure 11.6.
Actions are useful to encapsulate and reuse the imperative code needed to perform common tasks in response to events raised by client components. The ability to execute multiple actions in sequence makes it possible to execute big portions of JavaScript code without writing a single line of imperative code.
The next feature we’ll introduce is bindings, a powerful mechanism for synchronizing the value of two properties. A binding is a relationship between two properties of the same object or of different objects. This relationship is encapsulated by a specialized binding object, which has a fundamental mission: It detects when the value of one property changes and automatically reflects the change on the other property. You saw a binding in listing 11.7, which was about the InvokeMethod action. In the code, a binding was used to synchronize the result property of a ServiceMethodRequest instance to the text property of a Label control. You didn’t have to write all the JavaScript code required to access one property and set the value of the other one. This is interesting functionality, so let’s take a deep breath and examine bindings.
Suppose you have a text box and a label in a page, and you want to synchronize the text of the label with the text in the text box. With JavaScript, you would intercept the change event of the text box, and then retrieve its text and set it as the text of the label. This requires writing all the logic for subscribing to the event and accessing the DOM elements involved.
With a binding, the synchronization between the two properties is performed automatically. This is possible because a binding can detect changes in the values exposed by properties. To do that, a binding object relies on a mechanism called property change notification, which we discussed in chapter 8.
You can use bindings to create relationships between the properties exposed by client components. As we’ll demonstrate in this section, bindings add expressiveness to declarative languages and make it possible to perform complex tasks using only declarative code.
To take your first steps with bindings, you’ll use one to synchronize the text of a text box with the text of a label. Listing 11.12 has a text box and a span element, both associated with the corresponding wrapper controls. Whenever you change the text typed in the text box, the binding automatically updates the text displayed in the label.
In the code, the binding is declared in a bindings tag, which is a child node of label. Client components expose a property called bindings, which returns an array with all the bindings hosted in the component. Later, you’ll see that bindings can also be defined outside of any components.
Client components support bindings only in the Futures CTP. The Sys.Component class as defined in the Microsoft Ajax Library 1.0 doesn’t offer support for bindings at the moment.
Bindings are supposed to have a source and a target. The source is a component that provides some data, and the target is a component that receives the data. Once you’ve defined the source and the target of a binding, you have to choose which property of the source and which property of the target you want to bind. Whenever the value of the source property changes, the binding takes it and assigns it to the target property. In this way, the two properties remain always synchronized.
In listing 11.12, you declare the binding in the target component , the label. The source component is determined by assigning its id to the dataContext attribute, and the name of the source property is the value of the dataPath attribute . The property attribute specifies the name of the target property.
If you run the example, the text of the label is set to the text in the text box. This means the binding has been evaluated to keep the two properties synchronized. If you modify the text in the text box and then press the Tab key, the binding is re-evaluated because a change in the source property has been detected.
However, life isn’t so simple. Bindings in XML Script can be declared outside of any components. They’re also able to work the opposite way by swapping the source and the target. They can even work in a bidirectional way. In the following sections, we’ll clarify these concepts one by one.
In the first example, you saw how to define a binding between the text of a text box (the source of the data) and the text of a label (the target of the data). By doing that, you implicitly defined a direction for the binding. Every time the source property is modified, the binding is evaluated and the target property is updated; the data involved in the binding goes from the source property to the target property. Now, let’s try the following experiment. In listing 11.12, replace the binding declaration with this one:
<binding id="binding1" dataContext="txtSource" dataPath="text" property="text" direction="Out" />
The declaration is almost the same, but you add a direction attribute and set it to Out. To complete the experiment, add the following JavaScript block after the XML Script code:
<script type="text/javascript"> <!-- function pageLoad() { $find('dstLabel').set_text("Label's text"); } //--> </script>
The JavaScript code programmatically sets the label’s text when the page is loaded. If you run the example again, you’ll discover that this time, the text in the text box is synchronized to the text in the label. Setting the direction attribute to Out swaps the source and target components of the binding. The direction attribute determines the direction in which the binding is evaluated.
If direction is set to In (the default value), the binding listens to changes in the value of the source property. When the value of the property changes, the target property is updated automatically. If direction is set to Out, the binding listens to changes in the value of the target property and updates the source property accordingly.
The direction attribute can be set to a third value, InOut, which is called bidirectional mode. In this case, the binding listens for changes in both the source and target properties, and updates the other one correspondingly.
Because things have become more complicated, you need a clear view of the other properties of bindings. In the next sections, we’ll shed some light on the data path and target properties.
When you declare a binding in XML Script, the dataContext attribute determines the source component for the binding, assuming the binding direction is set to In. The counterpart of the dataContext attribute is the target attribute, which determines the target component for the binding. If a binding is declared “in” a component—as a child node of the bindings element—the component automatically becomes the target of the binding. This is what happens, for example, in listing 11.12, where you declare the binding in the label element.
Data context lets child elements inherit from their parent elements information about the data source used for binding, as well as other characteristics of the binding such as the path. If you bind the parent element to a data source, the child elements automatically inherit the data-source information from the parent control. We’ll present an example of declarative data binding in chapter 13.
A binding can also be declared outside of any components. To declare a stand-alone binding, you have to explicitly set the target attribute. For example, you can move the binding defined in listing 11.12 outside the label and declare it under the components node, as shown in listing 11.13.
<binding id="binding1" target="dstLabel" dataContext="srcText" dataPath="text" property="text" />
Once you’ve specified the source component of a binding, you use the dataPath attribute to reach a particular property in the source component. This can be one of the properties exposed by the source component, but it can also be a property exposed by a child object of the source component. If the child object is a DOM element, you can also reach one of its properties.
To reach a property exposed by a child object of the source component, you can use the traditional dotted notation, as follows:
dataPath="childObject.childProperty"
The binding object can interpret the previous value in two ways. If get_childObject returns a component, then the data path is evaluated as follows:
var propertyValueToBind = sourceComponent.get_childObject().get_childProperty();
If get_childObject returns a JavaScript object, the remaining parts are interpreted as properties of the object, and the data path is evaluated like so:
var propertyValueToBind = sourceComponent.get_childObject().childProperty;
The same reasoning applies to the target component. In this case, you obtain the data path for the target component by concatenating the values of the property and propertyKey attributes. Then, it’s evaluated the same way as the value of the dataPath attribute.
As a quick example, add the following binding to the bindings element in listing 11.2. It binds the background color of the text box to the background color of the label:
<binding id="binding1" target="dstLabel" dataContext="srcText" dataPath="element.style.backgroundColor" property="element" propertyKey="style.backgroundColor" />
In this case, the data paths of both the source and target components are evaluated as follows:
var valueToBind = component.get_element().style.backgroundColor;
The behavior of a binding changes if you reference a property of a client object or DOM element rather than a property exposed by a client component. Properties exposed by client components can raise the propertyChanged event. We discussed this event in chapter 8. Shortly, you’ll see that a binding relies on this event to perform the automatic synchronization of two properties.
If the data path references a property of a simple object or of a DOM element, then the binding must be evaluated explicitly to synchronize the values. To better understand the concept of binding evaluation, we need to take a quick look at how bindings work.
Bindings are client components like controls and actions. Specifically, a binding is an instance of the Sys.Preview.Binding class. As a consequence, bindings can be instantiated programmatically without necessarily using them in XML Script.
One of the powerful features of bindings is that they can keep two properties synchronized automatically, without the need for you to explicitly evaluate the binding. This can be done thanks to the property notification mechanism discussed in chapter 8. A binding uses the propertyChanged event exposed by client components to track changes in the values exposed by properties. If a property involved in a binding calls the raisePropertyChanged method, the propertyChanged event is raised and the binding can detect a change in the value of the property. As a consequence, the change can be propagated to the target property, as shown in figure 11.7. When a property involved in a binding doesn’t implement the notification mechanism, the binding must be explicitly evaluated.
Evaluating a binding means synchronizing the values of the bound properties. The evaluation is performed by two methods of the Binding object: evaluateIn and evaluateOut. The evaluateIn method synchronizes the value of the target property to the value of the source property; the evaluateOut method does the opposite job. To evaluate the binding in a bidirectional way, you call both the evaluateIn and evaluateOut methods.
Let’s see what JavaScript code is generated when the markup code of a binding is parsed. The following code shows the imperative code corresponding to the stand-alone binding declared in listing 11.13:
var binding = new Sys.Preview.Binding(); binding.set_id('binding1'), binding.set_target($find('dstLabel')); binding.set_dataContext($find('srcText')); binding.set_dataPath('text'), binding.set_property('text'), binding.set_direction(Sys.Preview.BindingDirection.Out);
It’s easy to translate the declarative code into imperative code, because of the correspondence that exists between attributes in the markup code and properties of the client component. The direction property receives one of the values defined in the Sys.Preview.BindingDirection enumeration; this value can be In, Out, or InOut.
So far, we’ve presented the evaluation of a binding as a process that synchronizes the values of two properties. But the value of the source property can be transformed to obtain the value of the target property. This is done with special functions called transformers. A transformer is a function that takes an input value and uses it to produce the output value. As you’ll see, bindings can use transformers to obtain the actual value that will be bound to the target property.
To illustrate the concept of transformers, look at listing 11.14, which shows an example of a counter built using XML Script. To display the value of the counter, you use a simple Label control. Interestingly, to update the counter’s value, you bind the text property of the Label control to itself. Then, you use a transformer that takes the current value of the text property and computes the next value of the counter. Finally, you assign the new value back to the text property.
The HTML markup contains a span element, which is associated to the label control in the XML Script code. In the label is a binding in which the source and the target property are the same. Because the binding is added to the bindings collection of the label, the target is the label itself. In addition, the data context is set to the label control.
The transformer to use is specified in the transform attribute of the binding tag. It’s a JavaScript function that handles the transform event that a Binding object raises before setting the value of the target property. This event gives external objects a chance to modify the value of the target property based on the value of the source property. The function that handles the transform event is called the transformer.
In listing 11.14, you use the Add transformer, which is one of the built-in transformers available as methods of the Sys.Preview.BindingBase.Transformers object. The Add transformer returns the value of the source property incremented by one. Note that in the binding tag, the automatic attribute is set to false. This means the binding is evaluated only when you explicitly call the evaluateIn or evaluateOut method. The default value for the automatic property is true, which means the binding is evaluated automatically every time a change in one of the properties is detected. In this case, the evaluation method to call is determined by the value of the direction property.
The code introduces a new and useful component: a timer . The timer is an instance of the Sys.Preview.Timer class. The interval attribute specifies the timer’s interval, and the enabled property specifies whether the timer should be started as soon as the instance is created. When the timer’s interval elapses, a tick event is raised. In listing 11.14, you handle the tick event with an InvokeMethod action that calls the evaluateIn method of the binding. The new value of the counter is explicitly computed on every tick and then displayed in the label.
The Add transformer is just one of the many built-in transformers available. In the next section, we’ll look at the other transformers and show you a couple of tricks and tips. We’ll also explain how to create custom transformers and leverage them in XML Script.
In general, a transformer can behave differently based on the value of two different parameters:
To understand how you can tweak a transformer, let’s try to replace the binding declared in listing 11.14 with the following one:
<binding id="lblBinding" dataContext="myLabel" dataPath="text" property="text" transform="Add" transformerArgument="2" automatic="false" />
If you run the counter again, you’ll see that its value is incremented by 2 on every tick.
Now, let’s introduce another built-in transformer called Multiply. In listing 11.14, set the text attribute of the label to 1 and replace the binding with this one:
<binding id="lblBinding" dataContext="myLabel" dataPath="text" property="text" transform="Multiply" transformerArgument="2" automatic="false" />
If you run the example, the counter multiplies the current value by 2, and the factor is determined by the transformerArgument attribute. Like the Add transformer, Multiply behaves differently if the direction of the binding changes. Add the following attribute to the previous binding declaration:
direction="Out"
Now, the current value is divided by 2 instead of multiplied. The same thing works for the Add transformer. If the binding’s direction is set to Out, the input value is decremented by 1 or by the quantity specified in the transformerArgument. Table 11.2 lists the built-in transformers available in the PreviewScript.js file.
Description |
Transformer argument |
Binding direction |
|
---|---|---|---|
Invert | Performs a Boolean NOT of the input value. | - | In |
ToString | Formats the input value into a string. The input value replaces the {0} placeholder. | The format string with a {0} placeholder. | In |
ToLocaleString | Same as ToString, but the input value is formatted using the toLocaleString method. | The format string with a {0} placeholder. | In |
Add | Adds a number to or subtracts a number from the input value. | The number to add or subtract. | If the direction is In, performs an addition. If the direction is Out, performs a subtraction. |
Multiply | Multiplies a number by or divides a number from the input value. | The factor or divisor. | If the direction is In, performs a multiplication. If the direction is Out, performs a division. |
Compare | Returns the result of the comparison between the input value and the transformer argument using the identity operator. | The comparand. | In |
CompareInverted | Same as Compare, but the comparison is performed with the !== operator. | The comparand. | In |
RSSTransform | Parses an XmlDom object with an RSS feed into a DataTable. | - | In |
Transformers become powerful when you start creating custom ones. With a custom transformer, new scenarios open, such as performing data binding with declarative code. In chapter 13, you’ll see an example of declarative data binding with XML Script. In the next section, you’ll learn how to create custom transformers.
A custom transformer is a JavaScript function used to handle the transform event raised by a binding object. When the transform event is raised, the transformer is called, and the event arguments contain an instance of the Sys.Preview.BindingEventArgs class. This instance contains all the properties you need to compute the transformed value and pass it to the binding. The BindingEventArgs class exposes the following methods:
Creating a custom transformer is straightforward. A transformer retrieves the input value with the get_value method and then computes the transformed value based on the binding’s direction and the transformer argument. Finally, it calls the set_value method, passing the transformed value as an argument.
The code in listing 11.15 shows a custom transformer called ScrollMessage, which simulates a simple scroll effect on a string displayed in a label. Believe it or not, you use the transformer from the counter example, thus turning the counter into a scrolling message.
The GreetMessage function is declared with the sender and e parameters, because it’s a handler for the transform event. In the code, you retrieve the message to scroll through the get_transformerArgument method and the input value through the get_value method . Then, you trim the first letter to obtain the transformed text. The transformed text is saved by passing it to the set_value method.
If you replace the binding in listing 11.14 with the following one, and add the ScrollMessage function in a JavaScript code block in the test page, you can see the new transformer in action:
<binding id="lblBinding" dataContext="myLabel" dataPath="text" property="text" transform="GreetMessage" transformerArgument="2" automatic="false" />
In this chapter, we introduced XML Script, a powerful declarative language for creating instances of client components. In a manner similar to what happens with ASP.NET markup code, the Microsoft Ajax Library can parse the XML Script contained in a web page, instantiate the client components, and wire them together. With XML Script, you can benefit from many features available to declarative languages and dramatically decrease the quantity of JavaScript code you have to write.
Among the features provided by XML Script are actions, which are objects that encapsulate a portion of reusable JavaScript code. You can use actions to handle events in declarative code without writing a single line of imperative code.
In XML Script, you can also declare bindings. A binding is an object that can synchronize the values of two properties of the same object or of different objects. The synchronization can be performed automatically when a change in the value of a property is detected. Bindings can use transformers to change the value that will be bound to the target property. A transformer is a function that takes an input value and uses it to produce the transformed value.
In the next chapter, we’ll talk about the drag-and-drop engine.
3.15.168.199