Microsoft Silverlight and Truveo Video Search, Part Two
by Kirk Evans
October 19, 2007
In the first article in this series,
"Microsoft Silverlight and Truveo Video Search," we were introduced to the
concept of XAML and interacting with the Silverlight plugin using JavaScript.
We saw how to use the createSilverlight function to create instances of the
plugin, and we saw how to use the findName API to locate specific parts of our
XAML. In this article, we will continue our exploration of the Silverlight
API while looking at a few capabilities that make it easier to develop dynamic
user interfaces.
You can see a working demo of the code in this article by visiting my SharePoint site. See the Resources section at the end of this article to find the downloads for this article, as well as further reading information.
Creating Multiple Silverlight Controls on the Same Page
Let's start by revisiting the createSilverlight function from our previous
article. The createSilverlight function that we created is a simple wrapper
around the Silverlight.createObjectEx function.
function createSilverlight(){
Silverlight.createObjectEx({
source: 'Scene.xaml', //The source XAML document
//The ID of the HTML element that declares the plugin
parentElement: document.getElementById('SilverlightPlugInHost'),
id: 'SilverlightPlugIn', //a unique ID to refer to the plugin
properties: {
width: '400',
height: '400',
background:'#ffffffff',
isWindowless: 'false',
version: '0.8'
},
events: {
onError: null,
onLoad: null
},
context: null
});
}
To create the Silverlight plugin within our HTML, we simply call our createSilverlight wrapper function.
<div id="SilverlightPlugInHost"> <script type="text/javascript"> createSilverlight(); </script> </div>
There are plenty of cases where you will want to create multiple instances of the
Silverlight plugin within a single page. Examples include advertising
banners, blog feeds with embedded Silverlight controls, and portal pages with
personalized web parts, each possibly containing multiple Silverlight controls.
In fact, you should take this into consideration when developing pages that may
host Silverlight controls that you do not directly control. Our function,
in its current form, will not work in these scenarios because each Silverlight control
requires a unique ID. To accommodate this requirement, our createSilverlight function needs to be adjusted so that
we can reuse the function multiple times.
function createSilverlight(source, id, parentId, width, height)
{
Silverlight.createObjectEx({
source: source, //The source XAML document
//The ID of the HTML element that declares the plugin
parentElement: document.getElementById(parentId),
id: id, //a unique ID to refer to the plugin
properties: {
width: width,
height: height,
background:'#ffffffff',
isWindowless: 'false',
version: '0.8'
},
events: {
onError: null,
onLoad: null
},
context: null
});
}
This changes the way that we instantiate the Silverlight control within our HTML page
so that the page author can now assign a unique ID for the Silverlight control.
Using the new function prototype, we now pass the location of the XAML to our Silverlight control
instead of embedding its location within the createObjectEx method call.
<div id="SilverlightPlugInHost" class="results">
<script type="text/javascript">
createSilverlight('scene.xaml','SilverlightPlugIn',
'SilverlightPlugInHost', '400', '400');
</script>
</div>
Embedding Video and Controlling Playback Using Silverlight
We can use this ability to contain multiple Silverlight controls to introduce a
new Silverlight control in our page. Our new control will show a
continuously playing Windows Media-encoded video file using the MediaPlayer
element. We introduce that into the newly designed footer for our page.
The XAML for our new logo is very simple:
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MediaElement x:Name="media" Loaded="logoLoad"
Source="images/silverlightLogoLoop.wmv"
Height="110" Width="110" />
</Canvas>
We point to the WMV file as the source for our MediaElement. We don't have
a means to tell the MediaElement to continuously loop the video; we will use
some more scripting to control this behavior. In the XAML above, notice
that we can introduce an event, Loaded, and specify the function that is called
when the canvas is loaded. We will use that function to wire up another
event to cause the video to restart.
function logoLoad(sender, args)
{
var media = sender.findName("media");
media.addEventListener("mediaEnded", "mediaCompleted");
}
function mediaCompleted(sender, eventArgs)
{
sender.stop();
sender.play();
}
We can add event listeners with a syntax that is very familiar to JavaScript developers, yet the API is exposed by the Silverlight control itself. While the screen grab below does not demonstrate the Silverlight logo in motion, you can see a working example of the application on my SharePoint site.

Dynamically Adding Silverlight Content
If you recall from the previous article, our old XAML was pretty redundant. I cheated a little, hard-coding the structure of the XAML based on the number of results that I knew would be displayed. As a reminder, here is the old XAML structure.
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
x:Name="Page"
RenderTransformOrigin="0,0"
>
<Canvas x:Name="canvas0" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text0" Width="100" Height="100"
Canvas.ZIndex="1" TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image0" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas1" Canvas.Left="103" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text1" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image1" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas2" Canvas.Left="204" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text2" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image2" Width="100" Height="100" />
</Canvas>
<Canvas x:Name="canvas3" Canvas.Left="304" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text3" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image3" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas4" Canvas.Left="403" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text4" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image4" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas5" Canvas.Top="125" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text5" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image5" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas6" Canvas.Left="103" Canvas.Top="125" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text6" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image6" Width="100" Height="100"/>
</Canvas>
<Canvas x:Name="canvas7" Canvas.Left="204" Canvas.Top="125" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text7" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image7" Width="100" Height="100" />
</Canvas>
<Canvas x:Name="canvas8" Canvas.Left="304" Canvas.Top="125" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text8" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image8" Width="100" Height="100" />
</Canvas>
<Canvas x:Name="canvas9" Canvas.Left="403" Canvas.Top="125" MouseEnter="mouse_enter"
MouseLeave="mouse_leave" MouseLeftButtonDown="mouse_down">
<TextBlock x:Name="text9" Width="100" Height="100" Canvas.ZIndex="1"
TextWrapping="Wrap" Visibility="Collapsed"/>
<Image x:Name="image9" Width="100" Height="100" />
</Canvas>
</Canvas>
Here you can see that I structured the main canvas (also called
the root) to contain ten child canvas objects, where each one differs
only by its name and position. This static structure approach works, but what if you want to
dynamically add child content based an unknown number of items? For
instance, what if you want to allow the user to specify the number of results? We have seen several ways to interact with the Silverlight control tree using
JavaScript. We can find particular elements, respond to events, and set
properties of existing elements. Silverlight also provides the ability to
add children to existing content through the createFromXaml method. To
give you a better idea of how this works, we will create a drop-down menu in the
HTML page and allow the user to choose how many results they want to return.
<div class="criteria">
Number of results to show:
<select id="resultsCount">
<option>5</option>
<option selected="selected">10</option>
<option>15</option>
<option>20</option>
</select>
Search:
<input id="Text1" type="text" />
<input id="Button2" type="button" onclick="return Button1_onclick();" value="Go" />
</div>
When the user clicks the input button, we fire the Button1_onclick event. Inside that method, the Truveo Video Search AJAX API allows us to specify the number of desired results to return using the .results property.
function Button1_onclick()
{
var slplugin = document.getElementById("SilverlightPlugIn");
var root = slplugin.content.root;
root.children.clear();
var resultsCount = document.getElementById("resultsCount");
if(resultsCount.selectedIndex >= 0)
{
TVS.results = resultsCount.options[resultsCount.selectedIndex].innerText;
}
TVS.getVideos(document.getElementById("Text1").value);
}
When the user clicks the button, we want to clear the existing results on the
page. We use the HTML DOM to locate our Silverlight control, and set a
reference called slplugin. Once located, we can then use Silverlight's
API to manipulate its contents. Here, we are using the .root property to
specify which XAML element that we intend to manipulate. The .root is
analogous to the documentElement property of the W3C DOM. Using the .root
property, which corresponds to the outermost canvas element in our XAML
structure, we can add new children, modify existing elements, and even remove all of the
child nodes using the .clear() method, as we have done in this example.
Once the existing results are cleared, we query to see if the user specified the
number of results to return from the Truveo search and set the TVS.results
property accordingly (the default number of results returned from Truveo is ten).
Finally, we issue an asynchronous call to Truveo Video Search by passing the
user's search query string to the Truveo service through the getVideos API call.
The user has specified the number of results to return, so we will use the
createFromXaml method to dynamically create the UI. When the Truveo search
call completes, it calls the delegate function handleUpdate. Our
handleUpdate method will display the results with up to five images in each row.
Just like we did in the Button1_onclick() event handler, we obtain a reference
to the root and then add contents to it. Each canvas child element
specifies its own left and top properties for absolute positioning, so we use
the modulus operator to determine if we should display the content on a new row
or not.
function handleUpdate()
{
var slplugin = document.getElementById("SilverlightPlugIn");
var top = -125;
var left = 0;
var root = slplugin.content.root;
for (var i=0; i < TVS.VideoSet.totalResultsReturned; i++)
{
if (i % 5 == 0)
{
left = 0;
top += 125;
}
else
{
left += 125;
}
var xaml = createResultsXaml(i, TVS.VideoSet.Video[i].title,
TVS.VideoSet.Video[i].thumbnailUrl, top, left);
var child = slplugin.content.createFromXaml(xaml);
root.children.add(child);
}
}
The createFromXaml method accepts a string of XAML content. This content is
obtained from a helper function called createResultsXaml. This function
uses the values passed into it to concatenate an XML string representing the
dynamically parsed XAML.
function createResultsXaml(index, title, thumbnailUrl, top, left)
{
title = title.replaceAll('\"','');
var xaml = '<Canvas ' +
'xmlns="http://schemas.microsoft.com/client/2007" ' +
'xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ' +
'x:Name="canvas' + index + '" ' +
'Canvas.Top="' + top + '" ' +
'Canvas.Left="' + left + '" ' +
'MouseEnter="mouse_enter" ' +
'MouseLeave="mouse_leave" ' +
'MouseLeftButtonDown="mouse_down">' +
'<TextBlock x:Name="text' + index + '" Width="100" Height="100"
Canvas.ZIndex="1" Text="' + title + '" TextWrapping="Wrap"
Visibility="Collapsed"/>' +
'<Image x:Name="image' + index + '" Width="100" Height="100" Source="'
+ thumbnailUrl + '"/> ' +
'</Canvas>';
return xaml;
}
String.prototype.replaceAll = function(target, subString)
{
var text = this;
var matchIndex = text.indexOf( target );
while (matchIndex != -1)
{
text = text.replace( target, subString )
matchIndex = text.indexOf( target );
}
return( text );
}
It may not be pretty, but it works quite well for our needs. The XAML
doesn't need to come from JavaScript; we could just as easily use XMLHTTPRequest
to obtain the XML from a remote location. In fact, you can imagine APIs
that return XAML as the result format for service calls, similar to how Truveo
exposes an XML API. The data passed as parameters to the function is
simply concatenated inline into the XML string. The results from the
Truveo Video Search might contain double quotes, so I wrote a helper function
that extends String types with a new method, replaceAll. This allows us
to strip the double quotes from the values, making our string concatenation work
a little easier. Consider that a little extra bonus, if you didn't already
have this beauty in your personal function library.
As you can see, the Silverlight API is quite flexible, allowing you to modify, add, and remove content dynamically according to the amount of data that is returned from a service. We can host multiple Silverlight controls on the same page, and you should take this into consideration when developing page frameworks such as CMS systems that may contain user-generated content.
Silverlight 1.0 is the current version of Silverlight, and enables media content delivered over the Web with a scriptable API. The next version of Silverlight, currently in the alpha stage, will introduce the ability to leverage .NET-managed code. We will build upon the last two examples in our final article in this series by rewriting the application using managed code.
