Once your JavaScript code has access to the plug-in, you can find details about
the plug-in and its configuration as well as achieve relatively flexible
access to the Silverlight content. This section features some scenarios
and examples.
1. Determining Plug-in Settings
The first example will determine some of the plug-in's settings.
It will also feature both access methods by using the JavaScript DOM and
the JavaScript code in the XAML code-behind. First, we need the
containing HTML page, where we load the Silverlight content. A button on
the page will be used to trigger the retrieval and display of plug-in
information. Example 1 shows the code; for the sake of
legibility, we shortened the error handler (onError).
Example 1. Displaying plug-in information, the HTML file (Info.html)
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Silverlight</title>
<script type="text/javascript" src="Silverlight.js"></script> <script type="text/javascript" src="Info.js"></script> <style type="text/css"> #errorLocation { font-size: small; color: Gray; } #silverlightControlHost { height: 300px; width: 400px; }
</style> </head>
<body> <!-- Runtime errors from Silverlight will be displayed here. This will contain debugging information and should be removed or hidden when debugging is completed --> <div id='errorLocation'></div> <div id="silverlightPlugInHost"> <script type="text/javascript"> if (!window.Silverlight) window.Silverlight = {};
Silverlight.createObjectEx({ source: 'Info.xaml', parentElement: document.getElementById('silverlightPlugInHost'), id: 'silverlightPlugIn', properties: { width: '100%', height: '300px', background:'white', version: '1.0' }, events: { onError: null }, context: null }); </script> </div> <div> <form action=""> <input type="button" value="Show plugin info" onclick="showInfoJS();" /> </form> </div> </body> </html>
|
In the code-behind JavaScript file Info.js, the showInfoJS()
function accesses the plug-in using the DOM, and then calls another
function called showInfo(). This latter function will be
implemented later on:
function showInfoJS() {
var plugin = document.getElementById('silverlightPlugIn');
showInfo(plugin);
}
Example 2 shows the XAML file without any
complicated XAML markup, but an event handler for the left mouse button
is attached to the canvas.
Example 2. Displaying plug-in information, the XAML file (Info.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" MouseLeftButtonDown="showInfoXaml"> <Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" /> <TextBlock FontFamily="Arial" FontSize="56" Canvas.Left="25" Canvas.Top="40" Foreground="Black" Text="Silverlight" /> </Canvas>
|
The JavaScript file associated with the XAML file, displayed in
Example 3, determines the plug-in and submits it as an
argument to showInfo(), which is the same function that
Info.html.js is using. However,
from the browser's point of view, there is no difference between them
whether JavaScript code is logically tied to the HTML page or to the
XAML data. Therefore, we put both code snippets in one JavaScript
file.
Example 3. Displaying plug-in information, the XAML JavaScript file
(Info.js; excerpt)
function showInfoXaml(sender, eventArgs) { var plugin = sender.getHost(); showInfo(plugin); }
|
Finally, the showInfo() function needs to be
implemented. It takes the plug-in reference, accesses two information
points, and outputs them using the JavaScript alert() function. Example 4 shows the code. When you click on either the
Silverlight content or the HTML button, you will get output similar to
that shown in Figure 1.
Example 4. Displaying plug-in information, the HTML JavaScript file
(Info.js; excerpt)
function showInfoJS() { var plugin = document.getElementById('SilverlightPlugIn'); showInfo(plugin); }
function showInfo(plugin) { var s = 'Background: ' + plugin.settings.background; s += '\nMaxFrameRate: ' + plugin.settings.maxFrameRate; alert(s); }
|
2. Modifying XAML Content
The plug-in's source property (plugin.content.source, to be exact) not only retrieves the XAML markup of the
currently loaded Silverlight content, but also sets this information.
This allows us to create a sample application with a similar concept as
the WPF content viewer XAMLPad.
An HTML text box enables users to enter XAML markup; JavaScript code
then tries to load this markup into the Silverlight plug-in. Let's start with the HTML and the
input field, as shown in Example 5.
Example 5. SilverlightPad, the HTML file (SilverlightPad.html)
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Silverlight</title>
<script type="text/javascript" src="Silverlight.js"></script> <script type="text/javascript" src="SilverlightPad.js"></script> <style type="text/css"> #errorLocation { font-size: small; color: Gray; } #silverlightControlHost { height: 300px; width: 400px; }
</style> </head>
<body> <!-- Runtime errors from Silverlight will be displayed here. This will contain debugging information and should be removed or hidden when debugging is completed --> <div id='errorLocation'></div> <div id="silverlightPlugInHost"> <script type="text/javascript"> if (!window.Silverlight) window.Silverlight = {};
Silverlight.createObjectEx({ source: 'SilverlightPad.xaml', parentElement: document.getElementById('silverlightPlugInHost'), id: 'silverlightPlugIn', properties: { width: '100%', height: '300px', background:'white', version: '1.0' }, events: { onError: null }, context: null }); </script> </div> <div id="InputHost"> <textarea id="XamlMarkup" style="width:600px;" rows="10"></textarea><br /> <input type="button" value="Update!" onclick="update();" /> </div> </body> </html>
|
The initial XAML markup is quite simple. In this example, it is
the user's job to provide some fancy Silverlight content, after all.
Example 6 shows the code.
Example 6. SilverlightPad, the XAML markup (SilverlightPad.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Rectangle Width="600" Height="200" Stroke="Orange" StrokeThickness="15" /> <Canvas Canvas.Left="30" Canvas.Top="30" x:Name="XamlOutput"> <TextBlock FontFamily="Arial" FontSize="32" Foreground="Black" Text="Enter data below ..."/> </Canvas> </Canvas>
|
The most sophisticated part of this example is certainly the
JavaScript code. However, once you know how to do it, you just need a
couple of lines. First, convert the XAML string provided by the user
into a XAML JavaScript object that can work with the Silverlight
plug-in. The plug-in.content.createFromXaml() method does
exactly that:
var plugin = document.getElementById('silverlightPlugIn');
var obj = plugin.content.createFromXaml(
document.getElementById('XamlMarkup').value);
If the XAML is not valid, obj is null,
so we can act accordingly. If, on the other hand, obj is
not null, the user-supplied XAML is valid and can be
applied to the page.
If you have a look back at Example 10-6, you will
see that there is a second <Canvas> element
within the root <Canvas>. The idea is
that the outer (orange) border will always be there, whereas the content
of that rectangle may be changed by the user. Therefore, the JavaScript
code first needs to access the inner <Canvas>. You
can do this by using findName():
var XamlOutput = plugin.content.findName('XamlOutput');
XamlOutput—and any other XAML JavaScript object—does
not support the HTML DOM, but a similar API accesses all child
elements: XamlOutput.children. The following properties and methods
exist:
add()
Adds a child to the end of the list
clear()
Empties the children list
count
Provides the number of children
getItem()
Retrieves the child with a given index
getValue()
Retrieves the value of a given property
insert()
Inserts a child (second argument) at a given index (first
argument)
name
Displays the name of the child (if available)
remove()
Removes a given child from the list
removeAt()
Removes a child with a given index
setValue()
Sets the value of a given property
For this example, we need to do only two things: delete
all children of the inner
<Canvas> element
(imagine that a user has already submitted XAML that has been rendered
in the plug-in) and add the XAML JavaScript object (
obj) to
the children list:
if (obj != null) {
XamlOutput.children.clear();
XamlOutput.children.add(obj);
} else {
alert('Error! XAML might not be valid.');
}
Example 7 shows the complete JavaScript code
(minus the createSilverlight()
function), and Figure 10-2 presents SilverlightPad in
action. The XAML entered in the text field is displayed within the
orange border.
Example 10-7. SilverlightPad, the JavaScript file (SilverlightPad.js)
function update() { var plugin = document.getElementById('silverlightPlugIn'); with (plugin.content) { var XamlOutput = findName('XamlOutput'); var obj = createFromXaml( document.getElementById('XamlMarkup').value); } if (obj != null) { XamlOutput.children.clear(); XamlOutput.children.add(obj); } else { alert('Error! XAML might not be valid.'); } }
|
NOTE
There is a simpler implementation. Instead of using the pseudo
DOM of Silverlight, you can just set the source property
of the plug-in.
3. Dumping Content Information
As a final sample application
for this article, we will create a development helper tool
that dumps information about a given Silverlight application. The sample
features dynamic loading of Silverlight content and also recursive
access of all elements. As usual, we start with the HTML file (see Example 8).
But there is a surprise—there is no
createSilverlight() call, only the empty
container
SilverlightPlugInHost.
Additionally, there are three more HTML UI widgets on the page:
A text field (XamlFile) where users may enter the path to a XAML file.
A button that triggers loading the file.
A button that triggers dumping the file's contents. This
button is initially disabled (why dump data when no data has been
loaded yet?).
The page also contains an empty
element,
called
OutputDiv, where the "data dump" will be later
displayed.
Example 8. Dumping Silverlight content, the HTML file (SilverlightDumper.html)
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Silverlight</title>
<script type="text/javascript" src="Silverlight.js"></script> <script type="text/javascript" src="SilverlightDumper.js"></script> <style type="text/css"> #errorLocation { font-size: small; color: Gray; } #silverlightControlHost { height: 300px; width: 400px; }
</style> </head>
<body> <form> <div> <div id="silverlightPlugInHost"> </div> <div id="LoadXamlFile"> <input type="text" name="XamlFile" /> <input type="button" value="Load file" onclick="loadFile(this.form);" /> — <input type="button" id="DumpButton" value="Dump XAML" onclick="dump();" disabled="disabled" /> </div> <div id="OutputDiv"> </div> </div> </form> </body> </html>
|
In the JavaScript code-behind file, the loadFile() function is responsible for loading the XAML file the user
previously selected. First, the filename needs to be determined:
function loadFile(f) {
var filename = f.elements['XamlFile'].value;
...
}
NOTE
Note that in Example 8, the text field doesn't
have an id attribute, just a name attribute.
So, you can't use document.getElementById() to access the
form element. However, you can use the JavaScript document.forms[0].elements array to read
out the name. In this special case, we saved some typing. When we
called loadFile(this.form) from the HTML page, a
reference to the current form was submitted to loadFile(). Therefore,
f.elements['XamlFile'] grants access to the form field,
and the value property contains the field's
contents.
This filename is then provided to a function named
createSilverlight(). This function actually is a modified implementation of the JavaScript
code that usually loads the Silverlight content. The modified version
accepts the XAML filename to load as a parameter, allowing the
application to dynamically load XAML files. The loadFile()
function also enables the button that takes care of the (not yet
implemented) dumping of Silverlight contents:
function loadFile(f) {
createSilverlight(f.elements['XamlFile'].value);
document.getElementById('DumpButton').disabled = false;
}
Figure 3 shows that the code so far already
works. Enter a (relative) file path to a XAML file, and click the first
button. The XAML content is displayed above the form.
The next step in our application is also the final one: dumping
the contents of the currently loaded XAML file. To implement this, the
code first needs to access the plug-in, as always:
var plugin = document.getElementById('silverlightPlugIn');
We want to dump all elements, so we have to start at the top. The
root property provides us with a reference to the root
(<Canvas>) node:
var rootNode = plugin.content.root;
All that's left to do is to recursively dump the contents starting
with the root node, and then display this information in the output
<div>:
var html = dumpNode(rootNode);
document.getElementById('OutputDiv').innerHTML = html;
Admittedly, that's cheating, since we did not implement the
dumpNode() function yet. But doing so is not that hard:
dumpNode() expects a node as its first argument. The function then notes the
string representation of the node (e.g., <Canvas> has
the string representation Canvas). If the node has a name,
the name is remembered as well:
function dumpNode(node) {
var html = node.toString();
if (node.name) {
html += ' (' + node.name + ')';
}
Now comes the tricky part. What if the node has child nodes? In
that case, recursion comes into play: for every child node,
dumpNode() calls dumpNode() again so that
every child node is dumped as well. To visualize the hierarchy of the
document, all child nodes are indented, using the
<blockquote> HTML element.
There is one catch, however: if a node doesn't have children,
accessing node.chidren will result in a
JavaScript error. So, we need to use a try...catch
statement instead:
try {
if (node.children.count > 1) {
html += '<blockquote>';
for (var i = 0; i < node.children.count; i++) {
html += dumpNode(node.children.getItem(i));
}
html += '</blockquote>';
}
} catch (ex) {
}
Finally, the dumpNode() function adds a line break at
the end and returns the HTML markup:
html += '<br />';
return html;
}
NOTE
For security reasons, you may want to HTML-escape the element
names by replacing &, <,
>, ', and " with
&, <, >,
', and " (in that specific
order!).
Refer to Example 9 for the complete
JavaScript code. Figure 4 shows the data dumped for
a simple "Hello World" file.
Example 9. Dumping Silverlight content, the JavaScript file (SilverlightDumper.html.js)
function createSilverlight(XamlFile) { Silverlight.createObjectEx({ source: XamlFile, parentElement: document.getElementById('silverlightPlugInHost'), id: 'silverlightPlugIn', properties: { width: '600', height: '300', background:'#ffffffff', isWindowless: 'false', version: '1.0' }, events: { onError: null } }); }
function loadFile(f) { createSilverlight(f.elements['XamlFile'].value); document.getElementById('DumpButton').disabled = false; }
function dump() { var plugin = document.getElementById('silverlightPlugIn'); var rootNode = plugin.content.root; var html = dumpNode(rootNode); document.getElementById('OutputDiv').innerHTML = html; }
function dumpNode(node) { var html = node.toString(); if (node.name) { html += ' (' + node.name + ')'; } try { if (node.children.count > 1) { html += ''; for (var i = 0; i < node.children.count; i++) { html += dumpNode(node.children.getItem(i)); } html += ' '; } } catch (ex) { } html += ' '; return html; }
|
Combining HTML, JavaScript, and Silverlight makes sense in many
scenarios. For instance, remember that Silverlight does not offer a text
input control yet, but HTML does. So, you could use HTML to enter data,
and Silverlight to process it.