XAML and XAML-Related Concepts in the JavaScript API for Silverlight

If you use the JavaScript API for Silverlight, XAML processing and other behavior related to XAML differs significantly in some areas. The event handlers are referenced as HTML-level script includes rather than partial classes. Also, XAML is architecturally the primary instantiation technique for the JavaScript API for Silverlight, and thus you will frequently be defining them as XAML pages or XAML fragments, then accessing the created objects with FindName. This topic explains the XAML behavior and some key techniques for creating or accessing objects from XAML using JavaScript API.

Event Handlers in XAML for JavaScript API

In XAML, the API being used (managed or JavaScript) affects the technique of specifying event handlers. The API is mutually exclusive on a XAML page level. The signal for which API is used is the presence or absence of the x:Class attribute on the root element of a XAML page. If x:Class exists, the page uses the managed API. If x:Class is absent, the page uses the JavaScript API.

The following XAML example shows how to add a handler for the Loaded event for the Canvas, which in this example is the root of a XAML file. The absence of x:Class on this root indicates the JavaScript API. Resolution of the function name is therefore deferred until run time. At run time, when the event occurs, the JavaScript scripting scope is checked for a member named "onLoaded" and that function is executed.

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Loaded="onLoaded" />

The function named onLoaded is defined in a JavaScript file. This JavaScript file is associated with the HTML of the hosting page through the src parameter of the <SCRIPT> tag in HTML. This is necessary because it is the browser host that is really interpreting the script; the Silverlight plug-in is only an intermediary in the processing of the JavaScript API and does not provide the actual script engine. There is nothing in the XAML that must reference a specific JavaScript file; that is only relevant at the browser and DOM level.

<!-- Reference the JavaScript file where the event functions are defined 
     from the plug-in host HTML page. -->
<script type="text/javascript" src="eventfunctions.js">

The Object Tree and FindName

In the JavaScript API, the initial object tree is always constructed by parsing XAML. Except for some type-conversion usages that can parse strings, loading XAML is the only way to "construct" an object in the JavaScript API. After such a tree is constructed, you typically will want to interact with the object tree. In order to do so, you need a scripting object reference to one of the objects in the tree.

The best way to establish such a reference is to assign a Name or x:Name attribute to any object element in the XAML where you plan to reference that object during run time. In the JavaScript API, the name is conceptually just a string, not an object. But the name string is used to retrieve the actual object by then making a call to FindName in any run-time code. FindName exists in the API in such a way that you can call FindName on just about any object in the JavaScript API, including on the object that represents the Silverlight plug-in. You will commonly see that the first line or lines of a user function that is written for JavaScript API for Silverlight makes FindName calls to get object references, then the remainder of the function operates against those objects.

Even if there are no named objects, you can still get into the object tree at run time through the Root property on the Silverlight plug-in. This returns the object that is the root of the loaded XAML.

In addition, any event handler attached to a Silverlight object has access to a sender value from an event. This sender and the relevant event handler are frequently attached in such a way that sender is the object that you want to interact with. If not, sender still provides a convenient object that you can call FindName from. For more information on event handling in the JavaScript API for Silverlight, see Handling Silverlight Events by Using JavaScript.

The Object Tree and CreateFromXaml

The Silverlight content on your Web page is arranged as a hierarchy of objects in a tree structure. The single, topmost object in the structure is the root object, and it corresponds to the root of the XAML file that is loaded as the Source for a Silverlight plug-in. The root object is generally a Canvas object, because Canvas can contain other objects such as TextBlock or MediaElement. (Silverlight 1.0 only supported Canvas as a root; if you use JavaScript API with Silverlight 5 clients, you can use other UIElement objects as the root.)

To add XAML content dynamically to the tree structure after the initial loading of the source XAML, you first create the XAML fragment by using the CreateFromXaml method. At this point, the XAML fragment you create is disconnected from the Silverlight object tree. This means that the fragment is not rendered yet. However, even before adding it to the object tree, you can modify the properties of the objects in the XAML fragment. Object methods cannot be invoked prior to connecting the tree.

After you have created a disconnected object set from your XAML fragment, you generally will want to add it to the active Silverlight object tree. The fragment can either be added as a child object to a parent object, or it can set a property value of an object. When the fragment becomes part of the Silverlight object tree, the objects in the XAML fragment are rendered. Depending on the extent of the XAML provided as CreateFromXaml input, you can create a single Silverlight object, such as a TextBlock, or a complex tree of Silverlight objects.

There are several requirements for adding XAML content dynamically to the Silverlight object tree:

  • There must be existing XAML content associated with the Silverlight plug-in. You cannot use CreateFromXaml to replace the entire tree of content; you must at least preserve the original root element. If you want to replace the entire tree, you can set Source, although note that Source requires an actual file to exist at a URI, and cannot be initialized with just a string. However, you can work around this issue by using inline XAML as the Source, and using the HTML DOM to document.write the contents of the SCRIPT block that contains the intended source XAML.

  • The CreateFromXaml method can be invoked only by the Silverlight plug-in. However, it is easy in the JavaScript API to get an object reference to the plug-in: just call GetHost on any Silverlight object.

  • XAML content that is created by using the CreateFromXaml method can be assigned to only one object. If you want to add objects created from identical XAML to different areas of the application, you must invoke CreateFromXaml multiple times and use different variables for the return value. (If you do this, be careful of name collisions; see Creating Namescopes with CreateFromXAML later in this topic.)

  • The Silverlight object that will contain the new XAML content must be capable of encapsulating an object, either as a child element or as a property value.

  • The string you specify for xamlContent of CreateFromXaml must specify well-formed XML (in particular, it must have a single root element). CreateFromXaml will fail and cause an error if the provided XML string is not well formed.

  • The root element is implicitly using the Silverlight XML namespace. You can set the Silverlight namespace explicitly, but do not set the default XML namespace to any other value other than the Silverlight namespace.

Creating XAML Namescopes with CreateFromXAML

XAML used for CreateFromXaml can have values defined for Name or x:Name. As part of the CreateFromXaml call, a preliminary XAML namescope is created, based on the root of the provided XAML. This preliminary namescope evaluates any defined names in the provided XAML for uniqueness. If names in the provided XAML are not internally unique at this point, CreateFromXaml throws an error. However, if names in the provided XAML collide with names that are already in the primary XAML namescope, no errors occur immediately. This is because when the CreateFromXaml method is called, the created object tree that is returned is disconnected. You must explicitly connect your created object tree, either by adding it to a content property collection such as Canvas.Children or by setting some other property that takes an object value (such as specifying a new ImageBrush for a Fill property value). You will see name collisions causing errors only after you attempt to connect the disconnected object tree to the application's main object tree. The method or property used for the attempted connection will be the source of the error, and the attempted connection will fail (your disconnected tree will remain disconnected).

CreateFromXaml has an optional parameter, createNameScope, which normally defaults to false. Explicitly setting this value to true in the CreateFromXaml call that you use to create a disconnected object tree still creates a preliminary XAML namescope that evaluates any defined names in the provided XAML for uniqueness. If names in the provided XAML are not internally unique at this point, CreateFromXaml throws an error. The difference in the behavior is that the disconnected object tree is now flagged to not attempt to merge its XAML namescope with the primary XAML namescope when it is connected to the main application object tree. After you connect the trees, in effect, your application will have a unified object tree but discrete XAML namescopes. A name defined at the CreateFromXaml root or on any object in the previously disconnected tree is not associated with the primary XAML namescope; it has its own discrete XAML namescope.

The complication with having discrete XAML namescopes is that calls to the FindName method no longer operate against a unified namescope. Instead, the particular object that FindName is called on will imply the scope, with the scope being the XAML namescope that the calling object is within. Therefore, if you attempt to call FindName to get a named object in the primary XAML namescope, it will not find the objects from a discrete XAML namescope created by CreateFromXaml. Conversely, calling FindName from the discrete XAML namescope will not find named objects in the primary XAML namescope. The FindName method defined on the Silverlightplug-in object does not entirely work around this issue; its XAML namescope is always the primary XAML namescope.

This discrete XAML namescope issue only affects XAML namescopes and the FindName call. You can still walk the object tree structure upward in some cases by calling the GetParent method, or downward by calling into the relevant collection properties or properties (such as the collection returned by Canvas.Children).

To get to objects that are defined in a discrete XAML namescope, you can use several techniques:

  • Walk the object tree with GetParent and/or collection properties.

  • If you are calling from a discrete XAML namescope and want the primary XAML namescope, it is always easy to get a reference to the Silverlight plug-in and then call FindName on it. The concatenated script call for doing this in a single line is as follows:

    returnedObject = object.GetHost().content.FindName("nameToFind");

    where object is the calling object in a discrete XAML namescope. The .content in this syntax is the step through to the specific Silverlight plug-in sub-object that defines the FindName method.

  • If you are calling from the primary XAML namescope and want an object within a discrete XAML namescope, you should plan ahead in your code and retain a reference to the object that was returned by CreateFromXaml. This object is now a valid object for calling FindName within the discrete XAML namescope. You could keep this object as a global variable or otherwise pass it by using method parameters.