JavaScript Bridge for Silverlight

I've been using JavaScript to send simple messages to and from Silverlight for quite some time now. I have even talked about it in my ASP.NET MVC and Silverlight Playing Nice talk at the Pittsburgh .NET meeting and most recently at Pittsburgh Code Camp 2010.2.

I added a simple JavaScript/jQuery bridge to the MVCSilverlight project I have used in previous posts. If you have not read the other posts, MVCSilverlight demonstrates basic Silverlight integration into a ASP.NET MVC project. This demonstration could also apply to a web forms project with a few minor changes.

Let's start off with the view. My index view for my ASP.NET MVC project looks like this:

<h2>Javascript Bridge Example</h2>
<input id="message" type="text" value="Message" />
<input id="send-message" type="button" value="Send Message" />

<h2>Silverlight Integration</h2>
<p>
    <%: Html.DisplayFor(m => m.SilverightSample) %>
</p>

JavascriptBridge

I want to start with communication from Silverlight to the web because it is relatively simple. Don't be afraid of the DisplayFor in the Silverlight integration. That is the display template I have setup to show my Silverlight Plugin. The idea is that I want to write a message in the Silverlight message input, click the send button and Silverlight sends that message to the web page and processes the message.

My Silverlight page is still called MainPage and no, this sample is not using MVVM so we will be using good ol' code behind. On our button click from the Silverlight Send Message button we want to communicate with the html page and access a JavaScript method if it exists. You can write the code just like that. Here's the Silverlight button click code.

private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
    var script = (ScriptObject)HtmlPage.Window.GetProperty("MessageFromSilverlight");
    script.InvokeSelf(MessageTextbox.Text.Trim());
}

It is pretty close to what I said. Access the System.Windows.Browser.HtmlPage class and get a property (in this case a function) called MessageFromSilverlight. The one non-intuitive part of this is casting the property to ScriptObject. Once you have a reference to the script you can invoke it with any number of arguments you want. In my case, I am passing the text from the message textbox.

On the web page side of this. You can add some JavaScript to the page and define a function that the Silverlight method can see.

<script type="text/javascript">
    function MessageFromSilverlight(message) {
        alert('Silverlight tells Web "' + message + '"');
        $('#message').val(message);
    };
</script>

If you happen to spell the JavaScript function name incorrectly then try to access the script object you will get a NullReferenceException so be sure to handle that properly.

NullReferenceException

That is the entire process for sending information from Silverlight to the Web page.

Web to Silverlight

Communication from the web page to Silverlight requires just a little bit more knowledge of how the two can talk to each other. In the MainPage.xaml, define the method you want the web page to call via JavaScript. You can write this just like any other public method. Once that is written, one of the keys to opening that communication is decorating your method with the attribute [ScriptableMember]

[ScriptableMember]
public void MessageFromWeb(string message)
{
    MessageTextbox.Text = message.Trim();
}

You would think that is all you need to do, but there are two more steps in Silverlight. Even though you told the method it can be called by decorating it with [ScriptableMember], you also need to decorate the class with the attribute [ScriptableType]. If you read the description of these two attributes, you will be even more confused, because I have not found the descriptions to be accurate.

[ScriptableMember] Indicates that a specific property, method, or event is accessible to JavaScript callers.

[ScriptableType] Indicates that all public properties, methods, and events on a managed type are available to JavaScript code.

From the descriptions, you would think you would only need the [ScriptableMember] or only need [ScriptableType] without [ScriptableMember] and JavaScript would be able to access it. I have not found this to be the case. You need both attributes defined. Additionally, you need to register the class with a name so that JavaScript can call it. You do this in the constructor.

public MainPage()
{
    InitializeComponent();
    if (HtmlPage.IsEnabled)
        HtmlPage.RegisterScriptableObject("Communicator", this);
}

Notice the RegisterScriptableObject call. You can name the reference anything you want. This name will be used in JavaScript when you reference the Silverlight plugin.

Let's take a look at the JavaScript side of calling Silverlight. First I am going to use jQuery to wire up my button click on the html page so that it calls a JavaScript function when I click it.

<script type="text/javascript">
    $(document).ready(function () {
        $('#send-message').click(function () {
            var message = $('#message').val();
            MessageToSilverlight(message);
        });
    });
</script>

When I click the button on the html page it will call MessageToSilverlight with the message typed in the html textbox. Now we can define the JavaScript function to call Silverlight. First, we need to reference the html <object> tag that Silverlight uses. I abstract this object tag away so I don't have to deal with it. If you want to see where it is in my code, it is in my DisplayTemplates. Define an id on that tag, something like: <object id="silverlight-plugin" />. Now you can reference that object tag and communicate with it. You have a stylistic choice on how you want to get that object control. If you know JavaScript, you can use:

var control = document.getElementById('silverlight-plugin');

If you are afraid of JavaScript, you have a few options. Either way, you need to use JavaScript to get to that "Communicator" scriptable object we created in Silverlight, but you can get there through jQuery if you understand how jQuery selects elements. jQuery selects a list. Even if you select one element, jQuery will consider that a list of one element. You can achieve the same functionality as JavaScript's getElementById if you use jQuery's get method. It is important to note that once you get a result from jQuery's get method you are no longer in jQuery. You can no longer use jQuery methods on that result. Here is the jQuery equivalent.

var control = $('#silverlight-plugin').get(0);

Once you have the control you would think you could just call: control.Communicator.MessageFromWeb(message);. Unfortunately, there is one more level of complexity. The Communicator scriptable object we defined is off of the control's context property. Your end result method call becomes:

function MessageToSilverlight(message) {
    alert('Web tells Silverlight "' + message + '"');

    // JavaScript selector
    //var control = document.getElementById('silverlight-plugin');

    // jQuery selector
    var control = $('#silverlight-plugin').get(0);

    if (control != null) 
        control.content.Communicator.MessageFromWeb(message);
}

You can view the entire source code for this project at my blog's BitBucket page.