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.

screen capture of working application

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.

Resources

First part?

I can't find the first part : (
Bst Rgds,
Michael B.