Disappearing Silverlight controls and SharePoint modal dialogs

If you’ve spent anytime developing Silverlight controls for SharePoint that utilise SP.UI.ModalDialog dialogs you may have noticed a idiosyncrasy with the way Silverlight behaves when a modal dialog is thrown from the page that hosts the Silverlight control. The behaviour in IE is that the Silverlight is hidden whilst the modal is visible:

Before throwing a modal – Silverlight control is visible on a page:

image

During – modal is thrown and Silverlight is hidden:

image

Once the modal dialog has been closed, the control is made visible again. This presents two issues as far as I can tell, firstly the Silverlight control is actually reloaded when the modal is closed due to a bug. I’ve blogged about this issue including a workaround on this post. Secondly, user context is broken. Depending on the operation your controls is performing, it could be important that the Silverlight controls remain visible whilst a modal dialog is open.

One thing to note is that the behaviour described above is different in Firefox. Here, the Silverlight control remains visible but is not masked by the opaque div that is used to dim the background:

During – Firefox keeps the Silverlight control visible:

image

So why is the Silverlight control hidden by IE but not by Firefox? The answer is in the out-of-the-box SP.UI.Dialog.js file, or in this example, the SP.UI.Dialog.debug,js file:

image

The out-of-the-box JavaScript  checks the browser agent (line 1158) and, if you’re running IE, it loops through every object tag on the page and sets their style visibility attribute to hidden (line 1164). It certainly looks like the disappearing Silverlight controls in IE is the intended behaviour – more about is in a moment. In the meantime, if you need to work around this issue we can use a little additional JavaScript after we’ve opened our modal to show the Silverlight control in IE again.

The script extract below is actually taken from lines 1180 to 1190 of the SP.UI.Dialog.debug.js file and is what gets executed out-of-the-box when a modal dialog is closed. We’re simply running the same JavaScript immediately after opening the modal dialog. Walking through the code below we can see just before we open the modal, we set the browser focus away from our Silverlight control (this stops the control reloading when the modal is closed – see this post for details). Next, we show our modal as usual via a call to SP.UI.ModalDialog.showModalDialog. Finally we immediately loop through all the object tags on the page and set them to back visible:

image

Why do this? Well, we’ve worked around the out-of-the-box behaviour of SharePoint modals, Silverlight and IE that hides object tags and our user experience in IE now behaves just like Firefox:

So now in IE and Firefox we see the same behaviour – the Silverlight control remains visible but is not covered by the opaque div used to dim the modal background. 1 line of XAML and 2 lines of C# will fix that.

Next, open the source code of your Silverlight control and append to the end of your control stack (so that it appears over the top of all other controls), a canvas that is the same size as your entire usercontrol:

image

The important attributes of this canvas are highlighted. The opacity and background colour attributes match those of the out of the box opaque div and the initial visibility is set to collapsed (hidden). Next, in our Silverlight control just before we throw the modal dialog, we make our new canvas visible:

image

This approach assumes you’re initiating the modal dialogs from the Silverlight control. If not, it would be easy enough to call a ScriptableMember from the JavaScript that calls a Silverlight method that changes the canvas visibility to visible.

Lastly, once the modal is closed, I’m using another ScriptableMember  that is called from the JavaScript that collapses the canvas:

image

The end result is a visible Silverlight control that has a canvas applied over the top of it that matches the opacity and colour of the out-of-the-box modal background. This ‘fix’ works in both IE and Firefox:

image

Now the downside, and possibly why objects tags are hidden in IE by the out-of-the-box JavaScript shipped with SharePoint. With the Silverlight control visible, they can be clicked on and when you do so, they gain focus and are displayed above the modal dialog (this only happens in IE). In my environment, this is less of an issue than the whole control being hidden but you need to know about this side effect:

image

Luckily by adding a canvas to the top of the control stack of the Silverlight control I’m stopping any Silverlight buttons or controls from being clicked. I’m looking at a possible fix for this last issue and will blog further if it works.

I hope this helps someone…

Pass event data from a Silverlight control to a SharePoint page

There are many examples out there of how to pass data from a SharePoint page or web part to a Silverlight control. For example, you could use the an HTML bridge, initParams or even the client object model -see How to pass data to Silverlight control for loads of examples.

However, I could not find an example of how to pass data from Silverlight to SharePoint that also dealt with SP.UI.ModalDialog’s so this post will describe what I came up with.

Note: This post is not about how to fetch data to and from Silverlight via the object model. What I’m attempting to describe here is how an event in a Silverlight control can be used to pass information back to SharePoint via a Modal Dialog.

The example scenario I’m using is a follows: you have a source SharePoint page or web part (blue) that needs to call a Silverlight control to perform some advanced function. The source page wants to open the Silverlight control in an SP.UI.ModalDialog. The target of the modal dialog is another SharePoint page or web part (green). The target page in turn renders the Silverlight control (red).

image

Here are the scenarios I’m trying to answer:

  • How does the Silverlight control close the modal dialog that it is contained within?
  • How does the Silverlight control pass data back to the source page?
    To answer these questions, changes are required to be made to the source page, the target page and the Silverlight control. However, these changes are fairly simple to apply and the results work seamlessly.

    Source page changes

    We need to update the source page to include the necessary JavaScript to open an SP.UI.ModalDialog and to respond to its close event. I’m assuming you’ve got access to the code behind for the source page, if not, all the code shown can be place into a web part that is then placed on the source page or (a version of) the JavaScript can be directly added to the source page.

    First we create a variable to hold the unique name for our source page script:
    // create a unique name for our JavaScript
    string scriptName = "ThrowAModal" + DateTime.Now.Ticks.ToString();

    Next we start a new StringBuilder and write the open tag for our script element:

    // construct the required script contents
    StringBuilder modal = new StringBuilder();
    modal.Append("<script language='javascript' type='text/javascript'>");

    Next we create the JavaScript function that will get called when our ModalDialog is finally closed – this is our CallBack function. In this example I’m testing the results of the dialog (e.g. was it cancelled or closed gracefully) and then I’m displaying the returnValue than will eventually come from the Silverlight control in a notification message. When you see the notification message you’ll know that the Silverlight control has successfully passed data back to the source page:

// this will get called when the modal is closed
#region CallBack Javascript
modal.Append("function " + scriptName + "CallBack(dialogResult, returnValue)");
modal.Append("{");
modal.Append(" alert(dialogResult + ' - ' + returnValue); ");
modal.Append(" if(dialogResult==1) ");
modal.Append("  {");
modal.Append("  var myNotifyId = SP.UI.Notify.addNotification(returnValue, false);");
modal.Append("  }");
modal.Append("}");
#endregion

Next we create the JavaScript that opens our target page in a ModalDialog. What’s important to note here is that were passing into the options for the ModalDialog, the name of our CallBack function we defined above:

// this will open the modal
#region Dialog Javascript
modal.Append("function " + scriptName + "() { ");
modal.Append("var options = { ");
modal.Append("url: '/yourURLhere.aspx', ");
modal.Append("width: 800, ");
modal.Append("height: 500, ");
modal.Append("title: 'Throw a Modal sample', ");
modal.Append("allowMaximize: false, ");
modal.Append("showClose: true, ");
modal.Append("dialogReturnValueCallback: " + scriptName + "CallBack");
modal.Append("};");
modal.Append("SP.UI.ModalDialog.showModalDialog(options); ");
modal.Append("}");

Next we complete our script by actually calling our function that will open the ModalDialog:

// cause the modal to be opened 
modal.Append("ExecuteOrDelayUntilScriptLoaded(" + scriptName + ", \"sp.js\"); ;");
#endregion

Finally, our source page JavaScript is complete so we can close the script block and add the script to the page:

modal.Append("</script>");

// add the script to the page
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), scriptName, modal.ToString());

Target page changes

Now we need to make a minor change to our target page. Again, this could be done in the code behind, in a web part or by adding the JavaScript directly into the page.

First we use a StringBuilder to create our script block:

// construct the required script contents
StringBuilder targetScript = new StringBuilder();
targetScript.Append("<script language='javascript' type='text/javascript'>");

Next we create the function that our Silverlight will be calling and that in turn will close the ModalDialog and pass the results from Silverlight to the source page CallBack function:

// this will be called by Silverlight
#region Invoke Javascript
targetScript.Append("function CancelPressed(results)");
targetScript.Append("{");
// show the data passed out of Silverlight
targetScript.Append(" alert(results);"); 
// close the dialog and return the results
targetScript.Append(" window.frameElement.commonModalDialogClose(1,results);");  
targetScript.Append("}");
#endregion

Notice the above function is called ‘CancelPressed’ we’ll need to reference this script by name in our Silverlight control.

We’re done with the target page scripting so we can close our script block and add the script to our page:

targetScript.Append("</script>");

// add the script to the page
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CancelPressedScript", targetScript.ToString() );

Silverlight control changes

The changes required to be made to the Silverlight control are the simplest of all. On the event you want to use to trigger closing the ModalDialog (and with it the Silverlight control) and pass data back to the source page CallBack function simply add the following code:

private void btnCancel_Click(object sender, System.Windows.RoutedEventArgs e)
{
    // call our target page function
    System.Windows.Browser.HtmlPage.Window.Invoke("CancelPressed", 
        "Hello from Silverlight - cancel has just been pressed.");
}

The System.Windows.Browser.HtmlPage.Window.Invoke method takes two parameters. The first is the name of the script object we wish to call. This needs to be the name of the function you added to the target page. The second parameter is a params object[] array that can be used to send data back to the target page and onto the source page. In this example I’m simply passing a string message but this same technique will work for all primitive data types.

Result

Here the overall process flow the above code helps achieve:

image

I hope this helps…