TeamLive Blog

April 27, 2008

Silverlight 2 lightbox

Filed under: Uncategorized — teamlive @ 11:09 pm

TeamLive blends Asp.net, Ajax and Silverlight in a number of places.  E.g. during a multi-file upload we adjust the OBJECT’s styles giving it more screen space for displaying the progress bars.

image

For the next iteration of TeamLive we may introduce a lightbox to help users navigate their image grids. 

I was interested to see how far I could push the html/Silverlight blend and the lightbox seemed like a good component for my experiment.

I’ve just made a start on the research – so just in case the requirement gets canned this is where I got to.

Some of the requirements for the lightbox:

  • Only load the big image if needed (duh!)
  • Small xap (the js lightboxes we’ve used in the past have required a lot of js and/or associated images) – this xap is only 8K
  • Only one Silverlight lightbox object per page (we didn’t want lots of objects being created)
  • Simple to use (drop a xap on a page and call some js)
  • Degrade cleanly when Silverlight 2 is not present

 

Demo

If you have Silverlight 2 installed visit http://www.devprocess.com and click one of the images…

Back? 

I hope you saw something like this:

image

If you just got an ugly window.open then you don’t have Silverlight 2.

 

The code

Firstly, the PageBlanket silverlight class.  A simple class that can be used to disable the page with an overlay.

A similar technique is used by the modal popup overlay (see the ModalPopup on the AjaxControlToolkit: http://www.asp.net/AJAX/AjaxControlToolkit/Samples/ModalPopup/ModalPopup.aspx)

/// <summary>
/// PageBlanket covers a page with a partially transparent div
/// to prevent users interacting with the page - can be used to simulate a modal
/// dialog
/// </summary>
public class PageBlanket
{
    HtmlElement _blanket = null;
    /// <summary>
    /// Lazy initialization on first call
    /// </summary>
    private void Initialize()
    {
        if (_blanket == null)
        {
            //create a div that will take up the whole page and cover the content underneath
            _blanket = HtmlPage.Document.CreateElement("div");

            HtmlPage.Document.Body.AppendChild(_blanket);
            _blanket.SetStyleAttribute("position", "fixed");
            _blanket.SetStyleAttribute("top", "0px");
            _blanket.SetStyleAttribute("left", "0px");
            _blanket.SetStyleAttribute("height", "100%");
            _blanket.SetStyleAttribute("width", "100%");
            _blanket.SetStyleAttribute("backgroundColor", "#808080");
            _blanket.SetStyleAttribute("filter", "alpha(opacity=50)");
            _blanket.SetStyleAttribute("opacity", "0.5");
            _blanket.SetStyleAttribute("z-index", "3");
            _blanket.SetStyleAttribute("display", "none");
        }
    }

    /// <summary>
    /// Show the blanket and cover the page
    /// </summary>
    public void Show()
    {
        //just in case not init'ed
        Initialize();
        //clear display attribute to show the blanket
        _blanket.SetStyleAttribute("display", "");
    }

    /// <summary>
    /// Hide the blanket again
    /// </summary>
    public void Hide()
    {
        //hide the blanket
        _blanket.SetStyleAttribute("display", "none");
    }
}

Page blanket creates a div, sets the style, jams it in the page, and toggles the display style to show/hide the div.  This effectively disables the page while we do the Silverlight stuff.

Dynamically showing/hiding the Silverlight control turned out to be harder. 

Firefox gets upset if you start moving the control around, changing zIndex, altering display style dynamically. 

I was getting working code in IE7 and “Trying to get unsupported property on scriptable plugin object!” errors in Firefox.

The solution I found (works on IE7, Firefox2 and Safari (not tested on anything else yet)) was to set a small Width and Height (yeah, yeah – I know).    I make sure I set the style of the object at the start (changing position and zIndex dynamically in the C# screwed up the scriptableobject):

The lightbox code

Note it positions the lightbox in the center of the screen.  Originally the code positioned the lightbox over the item that launched it – but it never looked quite right so I tried centering the lightbox.  I’m not sure it’s much better in the center – so I’m waiting for inspiration.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using System.Windows.Media.Imaging;

namespace dpLightbox
{
    [ScriptableType]
    public partial class Page : UserControl
    {

        public Page()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Page_Loaded);
            //keep the lightbox in the center if page is resized
            HtmlPage.Window.AttachEvent("onresize", new EventHandler(Window_Resize));
        }

        void Window_Resize(object sender, EventArgs e)
        {
            PositionToCenter();
        }

        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            //register for javascript control
            HtmlPage.RegisterScriptableObject("LightboxController", this);

            //the close button
            bClose.MouseEnter += new MouseEventHandler(bClose_MouseEnter);
            bClose.MouseLeave += new MouseEventHandler(bClose_MouseLeave);
            bClose.MouseLeftButtonUp += new MouseButtonEventHandler(bClose_MouseLeftButtonUp);
        }

        bool closing = false;
        /// <summary>
        /// Close the lightbox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void bClose_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            closing = true;
            rCloseGlow.Opacity = 0.0;
            sbLightbox.Completed += new EventHandler(sbLightbox_Completed);
            sbLightbox.AutoReverse = true;
            sbLightbox.Stop();
            sbLightbox.Begin();
            sbLightbox.Seek(new TimeSpan(0, 0, 0, 0, 500));

        }

        void sbLightbox_Completed(object sender, EventArgs e)
        {
            if (closing)
            {
                _blanket.Hide();

                Width = 0;
                Height = 0;
                HtmlPage.Plugin.SetStyleAttribute("width", "1px");
                HtmlPage.Plugin.SetStyleAttribute("height", "1px");
            }

            closing = false;
        }

        void bClose_MouseLeave(object sender, MouseEventArgs e)
        {
            rCloseGlow.Opacity = 0.0;

        }

        void bClose_MouseEnter(object sender, MouseEventArgs e)
        {
            rCloseGlow.Opacity = 1.0;
        }

        double paddingTop = 55;
        double padding = 25;
        PageBlanket _blanket = new PageBlanket();
        [ScriptableMember]
        public void showLightbox(string imgTitle, string imgSrc, double imgWidth, double imgHeight)
        {
            _blanket.Show();

            txtTitle.Text = imgTitle;
            imgMain.Source = new BitmapImage(new Uri(imgSrc, UriKind.RelativeOrAbsolute));
            imgMain.ImageFailed += new ExceptionRoutedEventHandler(imgMain_ImageFailed);

            //setup the lightbox size
            double dWidth = imgWidth + (padding * 2);
            double dHeight = imgHeight + (padding + paddingTop);

            Width = dWidth;
            Height = dHeight;

            kfWidth.Value = dWidth;
            kfHeight.Value = dHeight;

            HtmlPage.Plugin.SetStyleAttribute("height", Height.ToString() + "px");
            HtmlPage.Plugin.SetStyleAttribute("width", Width.ToString() + "px");

            sbLightbox.Stop();
            sbLightbox.Seek(new TimeSpan(0));
            sbLightbox.AutoReverse = false;
            sbLightbox.Begin();

            HtmlPage.Plugin.SetStyleAttribute("display", "");

            PositionToCenter();
        }

        void imgMain_ImageFailed(object sender, ExceptionRoutedEventArgs e)
        {
            //the developer needs to decide what should happen here
            //empty lightbox?  don't show the lightbox?

        }

        private void PositionToCenter()
        {
            Size clientBounds = BrowserHelper.GetClientBounds();
            Point ptScrollPos = BrowserHelper.GetScrollPosition();

            double dTop = ptScrollPos.Y + ((clientBounds.Height - Height) / 2);
            double dLeft = ptScrollPos.X + ((clientBounds.Width - Width) / 2);

            HtmlPage.Plugin.SetStyleAttribute("top", dTop.ToString() + "px");
            HtmlPage.Plugin.SetStyleAttribute("left", dLeft.ToString() + "px");
        }

    }

}

Using the lightbox

In your aspx.

            <asp:Silverlight ID="slLb" runat="server" Source="~/ClientBin/dpLightbox.xap" Version="2.0" 
Width="1px" Height="1px" PluginBackground="Transparent" Windowless="true" 
style="position:absolute;z-index:4;">
            <PluginNotInstalledTemplate></PluginNotInstalledTemplate>
           </asp:Silverlight>
 

The PluginNotInstalledTemplate gets rid of the install Silverlight 2 prompt when Silverlight 2 isn’t installed – which is nice as the Lightbox isn’t essential functionality.

Put this bit of Javascript in the head to simplify usage.  This gets the Silverlight xap to show the lightbox or (shudder) a window.open if Silverlight 2 isn’t there.

  <script type=”text/javascript”>
    function
lightbox(imgTitle,imgSrc,imgWidth,imgHeight)
    {
        var lbContent=document.getElementById(“slLb”).Content;
        if(lbContent!=null)
        {
            lbContent.LightboxController.showLightbox(imgTitle,imgSrc,imgWidth,imgHeight);
        }
        else
       
{
            window.open(imgSrc,“noSilverlight”,“menubar=no,toolbar=no,height=”+imgHeight+“,width=”+imgWidth);
        }
    }   
  
    </script>

Showing a lightbox – to show a lightbox call the function above.

e.g.

<a href=”javascript:lightbox(’TeamLive designer’,'http://www.devprocess.com/App_Themes/Default/lb_designer.jpg’,600,296);”>

<img src=”App_Themes/default/designer.jpg” class=”photosmall” width=”264″ height=”130″ alt=”Screenshot from devProcess TeamLive” />

</a>

 

Where next?

The requirement may be scheduled for implementation, in which case this code will be refined, tested and included in the build.  If the code doesn’t end up with too many other dependencies (which needs to be avoided if we’re to keep the size of the xap down) then I’ll try and post the finished lightbox source here later.  I’ll also try and post instructions for users that don’t want to learn Silverlight but just want to drop a lightbox on their page.

The source

If you want to play further then download the source – if you just want the component download the xap.

14 Comments »

  1. Sweet, more modal dialogs!

    Comment by Josh — April 28, 2008 @ 10:20 pm

  2. Is it possible to produce the same output without using ASP.NET? I’m just migrating a blog to WordPress and wondered if it would be possible to use Silverlight in this way with it.

    Thanks.

    Comment by Derek Lakin — April 29, 2008 @ 8:57 am

  3. Let me look into this.

    Comment by teamlive — April 29, 2008 @ 12:34 pm

  4. I don’t think that WordPress allows javascript – so this won’t work for you.

    Besides I wouldn’t recommend using a Silverlight 2 lightbox until Silverlight 2 is more widely installed.

    This is really presented as a article examining merging Silverlight and HTML.

    Many of the other articles I found were fairly simple examples – I wanted to push it a little bit harder, blurring the HTML Silverlight boundary.

    Comment by teamlive — April 29, 2008 @ 1:22 pm

  5. ..but if you did want to use this on a vanilla html page.

    1. Copy the Xap to a ClientBin folder (or whatever)
    2. Add the xap mime type to your web server (google xap and mime)
    3. Look at the source of http://www.devprocess.com/simple.html – this is a very basic html page that uses the xap

    Comment by teamlive — April 29, 2008 @ 4:42 pm

  6. Thanks very much for taking the time to look into this. As a developer looking into things early is what I love :)
    I ended up using Blogger (which does support JavaScript), so I’ll look into a little more. I’m doubt I’ll be able to do anything with the MIME type though :(

    Comment by Derek Lakin — April 30, 2008 @ 7:22 am

  7. No worries – although if the mime type is your only problem then just rename the xap to zip and reference that instead.

    For more info on xap/mime type (and the rename xap->zip suggestion) see http://weblogs.asp.net/mschwarz/archive/2008/03/07/silverlight-2-not-working-on-production-web-server.aspx

    Comment by teamlive — April 30, 2008 @ 7:41 am

  8. I had a go in a test blog and I get the following error: “Error setting property on scriptable plugin object” at the JS line of code that tries to access the Content property of slLB. So, I guess it’s the MIME type thing :(

    Comment by Derek Lakin — April 30, 2008 @ 10:29 am

  9. I had a go in a test blog and I get the following error: “Error setting property on scriptable plugin object” at the JS line of code that tries to access the Content property of slLB. So, I guess it’s the MIME type thing :(

    Comment by Derek Lakin — April 30, 2008 @ 10:31 am

  10. Hmmm … I’ve tried renaming the xap to .dll as Michael Schwarz suggests and also tried changing the extension to .zip as Brad Abrams suggests in the comments and I still get the same error :S The test blog is http://dereklakin.blogspot.com (there’s only one post). Can you see anything I’ve missed?

    Comment by Derek Lakin — April 30, 2008 @ 3:29 pm

  11. It’s not a mime problem.

    I think the problem is that your domain is different to the one the xap is being served from.

    Comment by teamlive — April 30, 2008 @ 6:08 pm

  12. That was the alternative cause of the problem :(
    I wonder what my chances are of getting Blogger to let me upload xap files :P

    Thanks for all your help.

    Comment by Derek Lakin — May 1, 2008 @ 7:03 am

  13. I am trying to use this in my project. My scenario is as below :
    Have many xaml pages. on click of abutton i open a tab control. I need to use the lightbox and make the background modal like using this.

    What i tried was
    1) Copied the xap file into the clientbin folder (web project).
    2) Copied thes lines to the aspx page that is hosting my silverlight app

    Click Me!

    3) When i click on “Click ME” it only display a line of gray border.

    4) Please let me know how do i use it for my requirement
    Any vews ?
    Please help
    Thanks,
    SaSilver

    Comment by SASilver — August 18, 2008 @ 2:00 pm

  14. Thanks! The topic of your site wasn’t why i was here, but your site was one of the many many pages that came up while searching for a solution to a SL problem I’ve been having. I have a SL app that changes in size depending on it’s content. I’ve been using:
    HtmlElement sl_plugin = HtmlPage.Document.GetElementById(”sl_plugin”);
    sl_plugin.SetProperty(”Height”, height);

    to change the width/height so I can let the browser handle the scroll bar. That only seemed to work if I had the width/height of the HTML SL Object set to 100%. All good and all until I tried it in FireFox. In FF the SL object doesn’t even show.

    I found by going back to HTML SL obj fixed width/height and using your:
    HtmlPage.Plugin.SetStyleAttribute(”height”, Height.ToString() + “px”);

    Did the trick!! All is good again!

    Comment by Josh — February 11, 2009 @ 5:35 pm


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.