This chapter is a mix of the Silverlight JavaScript capabilities introduced in the second part of this book and the JavaScript access to Silverlight content presented in Chapter 15. It features some JavaScript APIs that are part of the Silverlight features but provide advanced possibilities. To be exact, Silverlight exposes a JavaScript API that lets developers download resources. While these resources are being downloaded, you can see the download progress and can process the downloaded data.
The technical background for Silverlight’s downloader API is the XMLHttpRequest
JavaScript object, which
fuels everything Ajax. The Silverlight API does not copy the XMLHttpRequest
API, but provides its own
interfaces. You can trigger the API from both XAML code-behind and HTML
code-behind JavaScript code, but generally you will want to start from
XAML.
When downloading content from within Silverlight, you usually have to follow these steps:
We will go through these step by step. But first we need some XAML backing so that we can wire up the code. Example 16-1 shows a suggestion.
Example 16-1. Downloading content, the XAML file (Download.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="loadFile">
<Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" />
<TextBlock x:Name="DownloadText" FontSize="52"
Canvas.Left="25" Canvas.Top="35" Foreground="Black"
Text="loading ..."/>
</Canvas>
Once the XAML has been loaded (via the Loaded
event in
the <Canvas>
element), we start working on the
download. Step 1 is to create the downloader object. For that, we first
need to access the plug-in (see Chapter 15 for details) and
then call the method:
function loadFile(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'),
Step 2 is to set up event listeners. Three events are available:
For this example, the Completed
event is what we
want:
d.addEventListener('completed', showText);
Step 3 is to configure the downloader object, which we do
using the open()
method (this does not actually
open the connection, but just configures the object). Provide the HTTP
verb (usually 'GET’
), and the URI to request.
d.open('GET', 'Download.txt'),
Step 4 is easy. The send()
method does exactly what it says—it sends the HTTP request:
d.send();
Finally, in step 5, you need to implement the event handlers; in our
case, there’s just one. The first argument for that event handler (we call
it sender
, as always) is conveniently the downloader object.
It provides you with two options to access the data returned from the
server:
In this example, we use the former option and output the text:
function showText(sender, eventArgs) { var textBlock = sender.findName('DownloadText'), textBlock.text = sender.responseText; }
Now all you need is a file called Download.txt (see the open()
method call for the filename) residing in the same directory as your
application, and you can download the file. After a (very) short while,
you will see the content in the <TextBlock>
element.
Example 16-2 contains the complete XAML code-behind
JavaScript code, and Figure 16-1 shows the output. If you
are using a tracing tool such as Firebug (see Figure 16-2), you will see that the text file is actually
downloaded.
Example 16-2. Downloading content, the JavaScript file (Download.js)
function loadFile(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'), d.addEventListener('completed', showText); d.open('GET', 'Download.txt'), d.send(); } function showText(sender, eventArgs) { var textBlock = sender.findName('DownloadText'), textBlock.text = sender.responseText; }
Downloading works only when you’re using the HTTP protocol. You
cannot use the file:
protocol (which means that you have to
run these examples using a web server, even if it is a local one). You
also have to adhere to the same-origin policy of
the browser security system. The URL you are requesting
must reside on the same domain, use the same port, and use the same
protocol.
It’s a good idea to provide the user with information on how long
downloading big files will take. The DownloadProgressChanged
event is an excellent choice to implement this. We start off again with a
simple XAML file (see Example 16-3) that both starts
JavaScript code once the canvas has been loaded and includes a text block
that will hold the progress information.
Example 16-3. Displaying the download progress, the XAML file (DownloadProgress.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="loadFile"> <Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" /> <TextBlock x:Name="ProgressMeter" FontSize="64" Canvas.Left="60" Canvas.Top="35" Foreground="Black" Text="0 %"/> </Canvas>
The best way to simulate a big file is to write a script that does not return that much data, but takes some time to run. The ASP.NET code from Example 16-4 sends only a bit more than 1,000 bytes, but takes a break every few dozen bytes. Therefore, the script is continuously sending data, but taking a few seconds to finish.
Example 16-4. Displaying the download progress, the ASP.NET file simulating a big or slow download (DownloadProgress.aspx)
<%@ Page Language="C#" %> <script runat="server"> void Page_Load() { Response.Clear(); Response.BufferOutput = false; Response.AddHeader("Content-Length", "1003"); for (int i = 0; i < 17; i++) { Response.Write( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); System.Threading.Thread.Sleep(500); } Response.End(); } </script>
It is very important that the script, in whatever language it is
written, does not use output buffering and also sends the correct
Content-Length
HTTP header. Otherwise, Silverlight cannot know how many bytes the
server will send in its response, and thus cannot determine the
percentage of data that has already been sent.
Now we’re ready for the JavaScript part. The beginning is easy:
create a downloader object and send an HTTP request. Only this time, we
are listening to the DownloadProgressChanged
event:
function loadFile(sender, eventArgs) {
var plugin = sender.getHost();
var d = plugin.createObject('downloader'),
d.addEventListener('DownloadProgressChanged', showProgress);
d.open('GET', 'DownloadProgress.aspx'),
d.send();
}
The downloader object exposes a property called downloadProgress
, which returns a value
between 0 and 1, which is a percentage of how much data has already been
transferred. When it is multiplied by 100 (and rounded), you get a nice
percentage value that can be displayed in the
<TextBlock>
element. Without further ado, Example 16-5 contains the complete
JavaScript code.
Example 16-5. Displaying the download progress, the JavaScript file (DownloadProgress.js)
function loadFile(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'), d.addEventListener('DownloadProgressChanged', showProgress); d.open('GET', 'DownloadProgress.aspx'), d.send(); } function showProgress(sender, eventArgs) { var progress = sender.downloadProgress; var textBlock = sender.findName('ProgressMeter'), textBlock.text = Math.round(100 * progress) + ' %'; }
Figure 16-3 shows the result—the file is loading, and loading, and loading.
One of the font support limitations mentioned in Chapter 5 is that only a handful of fonts are supported, but at least they work on all supported browsers and operating systems. With the downloader object and some JavaScript code, we can use additional fonts, as long as they are TrueType (TTF) or OpenType fonts. Another limitation: they need to use the .ttf file extension.
Probably the biggest limitation of them all is a legal one: you have to make the TTF files available to Silverlight so that the application can download them. If the application can download the fonts, any user can. So, you need to make sure you are allowed to distribute the fonts you are using. For this reason, this book’s downloads do not come with the fonts mentioned here—you need to use your own. For registered Visual Studio 2005 users, there is a font download (see Further Reading later) that you might want to experiment with. (But again, you are not licensed to distribute this font to others.)
As usual, we start with a simple XAML file that will be enriched
with JavaScript code. Example 16-6 contains a rectangle
(for visual reasons) and a <TextBlock>
. The text block
uses the Lucida font by default (see Chapter 5). We want
to change this using JavaScript, therefore we set up an event handler for
the Loaded
event so that we can load a font once the XAML has
been loaded.
Example 16-6. Loading a font, the XAML file (TrueTypeFont.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="loadFont"> <Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" /> <TextBlock x:Name="FancyText" FontSize="64" Canvas.Left="20" Canvas.Top="35" Foreground="Black" Text="Silverlight" /> </Canvas>
Loading the font starts the same as loading any file using the
downloader object—it makes an HTTP request and sets up a
Completed
event handler. Make sure you have the calibri.ttf (the Calibri font installed as part
of Office 2007 and Windows Vista) file available or use another font file
and change the code accordingly:
function loadFont(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'), d.addEventListener('Completed', displayFont); d.open('GET', ‘CALIBRI.TTF’); d.send(); }
In the event handler function, we cannot use the HTTP response as a
string. However, the <TextBlock>
object supports a
method called setFontSource()
. If you provide the
downloader object as an argument, you can use the font you just
downloaded!
function displayFont(sender, eventArgs) {
var textBlock = sender.findName('FancyText'),
textBlock.setFontSource(sender);
Remember that the downloader object is automatically the event sender and, therefore, the first argument for any event handler function attached to the object.
The rest is easy: you can now set the fontSize
property of the
<TextBlock>
element to the name of the downloaded
font:
textBlock.fontFamily = ‘Calibri’;
}
Example 16-7 contains the complete code, and you can see the result in Figure 16-4.
Example 16-7. Loading a font, the JavaScript file (TrueTypeFont.js)
function loadFont(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'), d.addEventListener('Completed', displayFont); d.open('GET', 'CALIBRI.TTF'), d.send(); } function displayFont(sender, eventArgs) { var textBlock = sender.findName('FancyText'), textBlock.setFontSource(sender); textBlock.fontFamily = 'Calibri'; }
But that’s not all! You can even download a ZIP file containing several font files, and use every font in there. The XAML file does not change from that shown in Example 16-6, except for one thing: clicking on the canvas executes an event handler. The application will change the currently displayed font whenever the user clicks on it. See Example 16-8 for the code.
Example 16-8. Loading multiple fonts, the XAML file (TrueTypeFonts.xaml)
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="loadFont" MouseLeftButtonDown="displayFont">
<Rectangle Width="300" Height="150" Stroke="Orange" StrokeThickness="15" />
<TextBlock x:Name="FancyText" FontSize="64" Canvas.Left="20" Canvas.Top="35"
Foreground="Black" Text="Silverlight" />
</Canvas>
Now create a ZIP file containing several TTF files; we are using
Consolas and Calibri here. Then load the ZIP file as usual, in the
loadFont()
function. The displayFont()
function will now be called on two occasions: when the font ZIP file has
been loaded and when the user clicks on the rectangle. In the former case,
the font source of the text box needs to be set to the downloader object.
This works only if the event sender (first argument of the event handler
function) is the downloader object, and not the <Canvas>
element (when the user clicks
it). A try...catch
block avoids any JavaScript
errors:
function displayFont(sender, eventArgs) {
var textBlock = sender.findName('FancyText'),
try {
textBlock.setFontSource(sender);
} catch (ex) {
}
//...
}
The rest is easy. Once the font source is set, you can set the
TextBlock
element’s fontFamily
property to the name
of any font within the ZIP file. Example 16-9 shows the
complete code, which also sets the correct font size depending on the font
being used. You can see in Figure 16-5 that the
application now also uses the Consolas font.
Example 16-9. Loading multiple fonts, the XAML JavaScript file (TrueTypeFonts.xaml.js)
var font = 'Calibri'; function loadFont(sender, eventArgs) { var plugin = sender.getHost(); var d = plugin.createObject('downloader'), d.addEventListener('Completed', displayFont); d.open('GET', 'fonts.zip'), d.send(); } function displayFont(sender, eventArgs) { var textBlock = sender.findName('FancyText'), try { textBlock.setFontSource(sender); } catch (ex) { } if (font == 'Calibri') { font = 'Consolas'; var fontSize = '40'; } else { font = 'Calibri'; var fontSize = '64'; } textBlock.fontFamily = font; textBlock.fontSize = fontSize; }
So, you can use the downloader class not only to download text information, but also to provide dynamic resources such as TrueType/OpenType fonts.