Amazon.com Widgets JavaScript

WilliaBlog.Net

I dream in code

About the author

Robert Williams is an internet application developer for the Salem Web Network.
E-mail me Send mail
Code Project Associate Logo
Go Daddy Deal of the Week: 30% off your order at GoDaddy.com! Offer expires 11/6/12

Recent comments

Archive

Authors

Tags

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.


Blog Engine Save Button not working on large post

I recently wrote a really long (about 20,000 words) blog post. I had been saving it periodically as I typed, but it suddenly reached a point where the Save button just didn't work anymore. Bringing up Firebug I could see that the Ajax request was being sent and a 500 sytem Error was being returned by the server. When I tried to insert a breakpoint in the method it was never being hit.

This made me think that this size limitation was probably in ASP.Net, not in the BlogEngine.Net codebase, and sure enough that is what it is. jQuery doesn't limit the size, but ASP.NET does by default.  You can adjust it with this web.config setting:

 

<system.web.extensions>
 
<scripting>
     
<webServices>
       
<jsonSerializationmaxJsonLength="x">
       
</jsonSerialization>
     
</webServices>
 
</scripting>
</system.web.extensions>


Where x is the size limit in bytes.

If you insert this section into your web.config and start seeing errors, you will need to install ASP.NET AJAX. Visit www.asp.net/ajax for more details.

 


Categories: Ajax | JavaScript
Posted by Williarob on Wednesday, April 10, 2013 9:32 AM
Permalink | Comments (0) | Post RSSRSS comment feed

JQuery Code only working correctly when Firebug is open

If you ever come across a situation where a page on your site is not working as it should, but when you open Firebug to see what is happening it all works fine, chances are that somewhere in your Javascript/JQuery code there is a console.log() statement. If Firebug is not open, console will be undefined and the rest of the scripts may not run. In my case, the problem was reported that clicking the save button on an html form resulted in the Json results prompting “Save As” dialog like the one below, instead of being processed:

 

Json results prompting “Save As” dialog in browser instead of being processed

Naturally, I spent most of the afternoon looking into why JSON results might prompt you with a Save As Dialog in an MVC application, and none of the reported fixes was working. I opened Firebug to look at some html and noticed that suddenly the page looked different - there was a lot more formatting, and it just looked like this was how the page was supposed to look. Not only that, but with Firebug open, the Save button correctly used Ajax to submit the form and the Save as Dialog didn't popup. So I closed Firebug and refreshed the page. Broken Again. Luckily for me, a collegue had recently had the same problem last week, and pointed out that a console.log statement had been responsible. There were no console.log statements on the page in question, but a global Find operation located one on the master page. Commenting it out made everything work as it was supposed to.

The moral of the story is that if you use console.log statements while debugging, make sure you remove them all when you are done, or better yet, create a global Javascript function that you can call from anywhere which first ensures that console is not undefined. Something like this should work:

    function debug(text) {
        if ((typeof(Debug) !== 'undefined') && Debug.writeln) {
            Debug.writeln(text);
        }
        if (window.console && window.console.log) {
            window.console.log(text);
        }
        if (window.opera) {
            window.opera.postError(text);
        }
        if (window.debugService) {
            window.debugService.trace(text);
        }
    }


Categories: JavaScript | JQuery
Posted by Williarob on Monday, November 01, 2010 6:20 AM
Permalink | Comments (0) | Post RSSRSS comment feed

How to disable Full Screen Mode (Silverlight 1.0)

Is there any way to disable the fullscreen mode or disable the double click event for a Media Player built with Microsoft Expression Encoder?

Yes. Edit BasePlayer.js:

Change:

_onFullScreen:function(){var a=this.get_element().content;a.fullScreen=!a.fullScreen} 

To:

_onFullScreen:function(){var a=this.get_element().content;a.fullScreen=false}

I tried various methods to overide this method in the inherited Extended Player Class, but without success. Perhaps there are private member variables that the inherited class cannot access, or perhaps I was doing something wrong. In any case, this method works. If you know of a better one, let me know.


Posted by Williarob on Saturday, December 27, 2008 6:33 PM
Permalink | Comments (1) | Post RSSRSS comment feed

A Virtual Form Web Custom Control

I recently ran into an issue where a site I was developing had form fields in the header area of the page (for logging in or searching) and that if I had my cursor in a form field further down the page and I hit the enter key, it it was the click event of the first button on the page (the button in the header, rather than the button I wanted it to use) that fired. The setup was a common one: the master page contains a form tag with a runat="server" attribute and that form wraps the entire content of the page. It all seemed to work fine, until I found myself on the registration page and instead of clicking the register button I just hit enter. Instead of registering me, a message appeared to explain that a username and password were required. Since those fields also exist on the registration form I was puzzled, more so when I used the mouse to click on the 'Register Now' button and it all worked. Stepping through the code I quickly realized what was happening, and rummaged around my code archives until I found some JavaScript I wrote a couple of years ago that would listen for the enter key, cancel the default behavior, and call another method. I added this snippet to the page and all was well, until I found another page with the same issue. It was at that point I thought "wouldn't it be nice if there was a control - like a panel control - that you could simply use to wrap some input controls, set a single property (to the id of the control that should be 'clicked' when the enter key is pushed), and that was all you needed to do?".

Well now there is such a control.

Edit: Actually, the standard asp Panel control has a "DefaultButton" property which implements similar functionality, however it only allows you to use asp button controls as your designated button. If you want to use an asp LinkButton control or some other type of control as your default button it does nothing for you. So if you are using asp Button Controls exclusively, I recommend you use that property. If not, then read on...

Using the control

You can register the control on a page by page basis as needed or you can add it to the <Pages> section of the web.config file to make it available to any page on the site (Listing 1).

Listing 1

  <pages enableSessionState="true" enableViewStateMac="true" enableEventValidation="true">

      <controls>

        <add tagPrefix="WBN" namespace="WilliaBlog.Net.Examples" assembly="WilliaBlog.Net.Examples"/>

      </controls>

  </pages>

Then add the control to the page in such a way that it wraps your inputs:

Listing 2

  <WBN:VirtualForm id="vf1" runat="server" SubmitButtonId="Button1" UseDebug="false">

    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" />

  </WBN:VirtualForm>

As you can see from Listing 2 you should use the Server side ID of the button (this will be automatically converted to the clientside ID by the virtual form control. Test it live.

How It works

The actual server side Virtual Form web custom control is really very simple. It inherits System.Web.UI.WebControls.Panel and contains a property to allow you to set the id of the button or linkbutton you want to push when hitting Enter. I chose to embed the Javascript file into the dll for the sake of portability. (Originally I had it in an external .JS file that was added to the header in the master page, but if I - or somebody else - wanted to use this control on another site, that adds an additional layer of complexity in the setup - they have to remember to load that Javascript file and while we could host it in a central location for all sites to share, embedding the file in the dll seemed the wisest choice.) The downside to this technique is that the js file has to travel over the wire every time the page is requested and cannot therefore be cached on the client, which will negatively impact any low-bandwidth users of the web application. For that reason, I have used the YUI Compressor to strip out all the comments and additional whitespace and included 2 versions of the script in the dll. When you set the property UseDebug to true, it will use the long verbose version which makes it much easier to debug in firebug, but when it is all working, use the compressed version by omitting this property from the control declaration or by setting it to false.

To make files accessible from your server control’s assembly, simply add the file to your project, go to the Properties pane, and set the Build Action to Embedded Resource. To expose the file to a web request, you need to add code like lines 7 and 8 in Listing 3 below. These entries expose the embedded resource so that the ClientScriptManager can both get to the file and know what kind of file it is. You could also embed CSS, images and other types of files in this way. Note: The project’s default namespace (as defined in the Application tab of the project's Properties page) needs to be added as a prefix to the filename of embedded resources.

So, besides exposing these properties, all the control really does is override the onPreRender event and inject some Javascript into the page. Lines 75 to 79 in Listing 3 inject the link to the embedded Javascript file, which appears in the page source as something like this:

<script src="/WebResource.axd?d=j246orv_38DeKtGbza6y6A2&amp;t=633439907968639849" type="text/javascript"></script>

Next it dynamically generates a script to create a new instance of the virtual form object, passing in the clientid of the VirtualForm Server control and the clientid of the button we want it to use, and register this as a startup script on the page.

Listing 3

    1 using System;

    2 using System.Collections.Generic;

    3 using System.ComponentModel;

    4 using System.Text;

    5 using System.Web;

    6 using System.Web.UI;

    7 using System.Web.UI.WebControls;

    8 

    9 // Script Resources

   10 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_Debug.js", "text/javascript")]

   11 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_min.js", "text/javascript")]

   12 

   13 namespace WilliaBlog.Net.Examples

   14 {

   15 

   16     [ToolboxData("<{0}:VirtualForm runat=server></{0}:VirtualForm>")]

   17     public class VirtualForm : System.Web.UI.WebControls.Panel

   18     {

   19         [Bindable(true), DefaultValue("")]

   20         public string SubmitButtonId

   21         {

   22             get

   23             {

   24                 string s = (string)ViewState["SubmitButtonId"];

   25                 if (s == null)

   26                 {

   27                     return string.Empty;

   28                 }

   29                 else

   30                 {

   31                     return s;

   32                 }

   33             }

   34             set { ViewState["SubmitButtonId"] = value; }

   35         }

   36 

   37         [DefaultValue(false)]

   38         public bool UseDebug

   39         {

   40             get

   41             {

   42                 string s = (string)ViewState["UseDebug"];

   43                 if (string.IsNullOrEmpty(s))

   44                 {

   45                     return false;

   46                 }

   47                 else

   48                 {

   49                     return s.ToLower() == "true";

   50                 }

   51             }

   52             set { ViewState["UseDebug"] = value; }

   53         }

   54 

   55         public VirtualForm() : base() { }

   56 

   57         protected override void OnPreRender(System.EventArgs e)

   58         {

   59             if (!string.IsNullOrEmpty(this.SubmitButtonId))

   60             {

   61                 Control theButton = this.FindControl(this.SubmitButtonId);

   62                 if ((theButton != null))

   63                 {

   64                     string resourceName;

   65                     if (this.UseDebug)

   66                     {

   67                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_Debug.js";

   68                     }

   69                     else

   70                     {

   71                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_min.js";

   72                     }

   73 

   74                     ClientScriptManager cs = this.Page.ClientScript;

   75 

   76                     string scriptLocation = cs.GetWebResourceUrl(this.GetType(), resourceName);

   77                     if (!cs.IsClientScriptIncludeRegistered("VirtualFormScript"))

   78                     {

   79                         cs.RegisterClientScriptInclude("VirtualFormScript", scriptLocation);

   80                     }

   81 

   82                     // New script checks for "Sys" Object, if found events will be rewired after updatepanel refresh.

   83                     StringBuilder sbScript = new StringBuilder(333);

   84                     sbScript.AppendFormat("<script type=\"text/javascript\">{0}", Environment.NewLine);

   85                     sbScript.AppendFormat("    // Ensure postback works after update panel returns{0}", Environment.NewLine);

   86                     sbScript.AppendFormat("    function ResetEventsForMoreInfoForm() {{{0}", Environment.NewLine);

   87                     sbScript.AppendFormat("        var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   88                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   89                     sbScript.AppendFormat("    if (typeof(Sys) !== \"undefined\"){{{0}", Environment.NewLine);

   90                     sbScript.AppendFormat("        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(ResetEventsForMoreInfoForm);{0}", Environment.NewLine);

   91                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   92                     sbScript.AppendFormat("    var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   93                     sbScript.AppendFormat("</script>");

   94 

   95                     string scriptKey = string.Format("initVirtualForm_" + this.ClientID);

   96 

   97                     if (!cs.IsStartupScriptRegistered(scriptKey))

   98                     {

   99                         cs.RegisterStartupScript(this.GetType(), scriptKey, sbScript.ToString(), false);

  100                     }

  101                 }

  102             }

  103             base.OnPreRender(e);

  104         }

  105     }

  106 }

The JavaScript

Most of the code is of course JavaScript. Lines 13 to 62 simply create the WilliaBlog Namespace and I 'borrowed' it from the The Yahoo! User Interface Library (YUI). The WilliaBlog.Net.Examples.VirtualForm object begins on line 65. Essentially, it loops through every input control (lines 159-164) within the parent Div (the id of this div is passed as an argument to the contructor function) and assigns an onkeypress event (handleEnterKey) to each of them. All keystrokes other than the enter key pass through the code transparently, but as soon as enter is detected, the default behavior is cancelled and the submitVirtual function is called instead. That function simply checks to see if the button you supplied is an input (image or submit button) or an anchor (Hyperlink or link button), and simulates a click on it, either by calling the click() method of the former or by navigating to the href property of the latter. The removeEvent and stopEvent methods are never actually called, but I included them for good measure.

Listing 4

    1 /****************************************************************************************************************************  

    2 *

    3 * File    : VirtualForm_Debug.js

    4 * Created : April 08

    5 * Author  : Rob Williams

    6 * Purpose : This is the fully annotated, easy to understand and modify version of the file, however I would recommend you use

    7 * something like the YUI Compressor (http://developer.yahoo.com/yui/compressor/) to minimize load time.

    8 * This file has its Build Action Property set to "Embedded Resource" which embeds the file inside the dll, so we never have to

    9 * worry about correctly mapping a path to it.

   10 *

   11 *****************************************************************************************************************************/

   12 

   13 if (typeof WilliaBlog == "undefined" || !WilliaBlog) {

   14     /**

   15     * The WilliaBlog global namespace object.  If WilliaBlog is already defined, the

   16     * existing WilliaBlog object will not be overwritten so that defined

   17     * namespaces are preserved.

   18     * @class WilliaBlog

   19     * @static

   20     */

   21     var WilliaBlog = {};

   22 }

   23 

   24 /**

   25  * Returns the namespace specified and creates it if it doesn't exist

   26  * <pre>

   27  * WilliaBlog.namespace("property.package");

   28  * WilliaBlog.namespace("WilliaBlog.property.package");

   29  * </pre>

   30  * Either of the above would create WilliaBlog.property, then

   31  * WilliaBlog.property.package

   32  *

   33  * Be careful when naming packages. Reserved words may work in some browsers

   34  * and not others. For instance, the following will fail in Safari:

   35  * <pre>

   36  * WilliaBlog.namespace("really.long.nested.namespace");

   37  * </pre>

   38  * This fails because "long" is a future reserved word in ECMAScript

   39  *

   40  * @method namespace

   41  * @static

   42  * @param  {String*} arguments 1-n namespaces to create

   43  * @return {Object}  A reference to the last namespace object created

   44  */

   45 WilliaBlog.RegisterNamespace = function() {

   46     var a=arguments, o=null, i, j, d;

   47     for (i=0; i<a.length; i=i+1) {

   48         d=a[i].split(".");

   49         o=WilliaBlog;

   50 

   51         // WilliaBlog is implied, so it is ignored if it is included

   52         for (j=(d[0] == "WilliaBlog") ? 1 : 0; j<d.length; j=j+1) {

   53             o[d[j]]=o[d[j]] || {};

   54             o=o[d[j]];

   55         }

   56     }

   57 

   58     return o;

   59 };

   60 

   61 //declare the 'WilliaBlog.Net.Examples' Namespace

   62 WilliaBlog.RegisterNamespace("WilliaBlog.Net.Examples");

   63 

   64 //declare Virtual Form Object

   65 WilliaBlog.Net.Examples.VirtualForm = function(formDiv,submitBtnId)

   66 {

   67     this.formDiv = formDiv; //The id of the div that represents our Virtual Form

   68     this.submitBtnId = submitBtnId;   //The id of the button or Linkbutton that should be clicked when pushing Enter

   69 

   70     // When using these functions as event delegates the this keyword no longer points to this object as it is out of context

   71     // so instead, create an alias and call that instead.

   72     var me = this;

   73 

   74     this.submitVirtual = function()

   75     {

   76         var target = document.getElementById(me.submitBtnId);

   77         //check the type of the target: If a button then call the click method.

   78         if(target.tagName.toLowerCase() === 'input')

   79         {

   80             document.getElementById(me.submitBtnId).click();

   81         }

   82         //If a link button then simulate a click.

   83         if(target.tagName === 'A')

   84         {

   85             window.location.href = target.href;

   86         }

   87     };

   88 

   89     this.handleEnterKey = function(event){ 

   90         var moz = window.Event ? true : false;

   91         if (moz) {

   92             return me.MozillaEventHandler_KeyDown(event);

   93         } else {

   94             return me.MicrosoftEventHandler_KeyDown();

   95         }

   96     };

   97 

   98     //Mozilla handler (also Handles Safari)

   99     this.MozillaEventHandler_KeyDown = function(e)

  100     {

  101         if (e.which == 13) {

  102             e.returnValue = false;

  103             e.cancel = true;

  104             e.preventDefault();           

  105             me.submitVirtual(); // call the delegate function that simulates the correct button click

  106             return false;       

  107         }

  108         return true;

  109     };

  110 

  111     //IE Handler

  112     this.MicrosoftEventHandler_KeyDown = function()

  113     {

  114         if (event.keyCode == 13) {

  115             event.returnValue = false;

  116             event.cancel = true;

  117             me.submitVirtual(); // call the delegate function that simulates the correct button click

  118             return false;

  119         }

  120         return true;

  121     };

  122 

  123     this.addEvent = function(ctl, eventType, eventFunction)

  124     {

  125         if (ctl.attachEvent){

  126             ctl.attachEvent("on" + eventType, eventFunction);

  127         }else if (ctl.addEventListener){

  128             ctl.addEventListener(eventType, eventFunction, false);

  129         }else{

  130             ctl["on" + eventType] = eventFunction;

  131         }

  132     };

  133 

  134     this.removeEvent = function(ctl, eventType, eventFunction)

  135     {

  136         if (ctl.detachEvent){

  137             ctl.detachEvent("on" + eventType, eventFunction);

  138         }else if (ctl.removeEventListener){

  139             ctl.removeEventListener(eventType, eventFunction, false);

  140         }else{

  141             ctl["on" + eventType] = function(){};

  142         }

  143     };

  144 

  145     this.stopEvent = function(e)

  146     {

  147         if (e.stopPropagation){

  148         // for DOM-friendly browsers

  149             e.stopPropagation();

  150             e.preventDefault();

  151         }else{

  152         // For IE

  153             e.returnValue = false;

  154             e.cancelBubble = true;

  155         }

  156     };

  157 

  158     //Grab all input elements within virtual form (contents of a div with divID)

  159     this.inputs = this.formDiv.getElementsByTagName("input");

  160 

  161     //loop through them and add the keypress event to each to listen for the enter key

  162     for (var i = 0; i < this.inputs.length; i++){

  163         this.addEvent(this.inputs[i],"keypress",this.handleEnterKey);

  164     }

  165 }


Posted by Williarob on Monday, April 28, 2008 8:31 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Sample Scrolling Silverlight Video Playlist

 A real world example of this technique can be seen at http://www.thejamesbondmovies.com

TAKE ME TO THE VERSION FOR SILVERLIGHT 2

It only took me about an hour to create this example. I started by dragging all 8 videos into Microsoft Expression Encoder, selected the "Expression" player template and clicked encode. This gave me the full player functionality you see here, play, stop, pause, etc. and by default it simply played all 8 videos one after the other. But I wanted my users to be able to pick and chose which videos to play, so I opened the project in Microsoft Expression Blend 2 September preview and resized the outer, root canvas by setting the height to 593 to give me room to place the thumbnails, and gave it a black background color.

The XAML 

Next I created the arrows that move the playlist left and right. If you have arrows as PNG images you can use them, but I chose to create them using XAML by drawing a white square, filling it with white color converting it to a path, and then using the pen tool to delete a corner and thereby converting it to a triangle which I simply rotated, moved and sized until it looked right. Set the cursor property to "Hand" so that users know it is a button. I then copied it, rotated it to point the other way and moved the second arrow into position on the other side. This gave me the XAML below which appeared just before the closing </canvas> tag.

<!-- Playlist region starts here -->
<!-- Navigation Arrows -->
<Path x:Name="LeftArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="11" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="134.119"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path x:Name="RightArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="588" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="314.365"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>

Next I dragged the first thumbnail (for movie # 1) onto the area, roughly where you see it now, resized it and moved it into position. I named it "play0", set its opacity to 74% to make an easy rollover effect that you'll see later on; then went into the XAML editor and simply copied the Image element 3 more times, renaming each one in turn to play1, play2, etc. and updating the Source property of each to point to the right thumbnail image. Next I had to move the new images, since they all sat on top of one another, using the basic formula left=previousImageLeft + Image Width + 4 pixels. Then playing with the spacing between them until it was roughly even. Given the width of my thumbnail and the width of the canvas it turned out to be impossible without further resizing and in the end I decided that for this example it was good enough. So I now had my four thumbnail "buttons" making up my playlist and if you can fit all of your items on the screen you can skip ahead to the JavaScript, but if you want to have them scroll keep reading.

I grouped these four images into a canvas - have Blend do this for you by control + clicking on the object names (in this case play0, play1, etc.) in the Objects and Timeline area and then pressing ctl + G or right clicking and choosing Group Into > Canvas. It is better to have blend create the canvas for you as it will update all the canvas.left, canvas.top properties for you. I called this new canvas "playlist1", then using the XAML editor copied it and created "playlist2", naming the objects play4, play5, etc. and updating the Source of each as before. Then I moved this canvas off the screen by simply setting the canvas.Left Property to a value I knew would push it out of sight. Finally, I grouped playlist1 and playlist2 into another canvas, this time calling it "Library".

Using the timeline editor I created a simple animation that moved the "Library" Canvas 613 pixels to the left over the course of 2 seconds. If you have never done this before it is really easy:

The video content presented here requires JavaScript to be enabled and the latest version of the Macromedia Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by downloading here.

As you can hopefully see on the video, you simply click on "Open, create or manage Storyboards", click "Create new", give it a name, make sure "Create as Resource" is checked so that we can access it through code, move 2 seconds into the timeline and add a keyframe to start the recorder, then make your changes. In this case we are changing the Left poperty so that it moves 613 pixels to the left - just far enough to bring the second "page" of buttons onto the screen. Stop the recorder and as you scrub through the timeline you can see the animation or click the play button to preview it.

Making it animate back the other way was a little trickier to do using the IDE, so I simply copied the XAML and changed the values myself.

Now if you have been trying this yourself you might be wondering why your thumbnails can be seen moving underneath, or even on top of the left and right arrows created earlier, while mine do not. The answer is that I have put my "Library" Canvas inside another Canvas called "ClippedCanvas" which has been "clipped", or cropped if you prefer using RectangleGeometry. Everything that falls outside the geometry you provide is hidden, or "clipped." The numbers represent X coordinates, Y coordinates, Width & Height in that order. X & Y in this case are relative to the container canvas ("ClippedCanvas"). So basically I am cropping an area from the top left of where Clipped Canvas begins, 550 pixels wide and 114 high, anything within the canvas that falls outside that region will not be seen. If you click on "ClippedCanvas" in the Objects and Timeline you will see it outlined in Blend and have a better understanding of where it is drawn.

So, the Final XAML for my Playlist region looks like this:

<!-- The outer canvas here is clipped: only the area defined by the rectangle geometry is visible  -->
<!-- This is necessary as when we animate the 'Library' canvas inside it we do not want to see the thumbnails slide under the navigation arrows and off the screen-->
<Canvas x:Name="ClippedCanvas" Canvas.Top="491" Canvas.Left="43" Width="550" Height="90">
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 550, 114"/>
</Canvas.Clip>
<!-- Animations to move the playlist left and right. They are numbered so that we can call them logically from code -->
<Canvas.Resources>
<Storyboard x:Name="MoveLeft01">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="13" To="-613" Duration="0:0:2" />
</Storyboard>
<Storyboard x:Name="MoveRight02">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="-613" To="13" Duration="0:0:2" />
</Storyboard>
</Canvas.Resources>
<!-- The Library Canvas groups the playlist buttons into a single element that can be easily animated left - right. -->
<Canvas Width="1157.275" Height="82.96" Canvas.Left="0" Canvas.Top="0" x:Name="Library">
<Canvas Width="550.275" Height="82.96" x:Name="playlist1">
<Image x:Name="play0" Opacity="0.74" Width="133.275" Height="82.96" Source="1.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play1" Opacity="0.74" Width="133.275" Height="82.96" Source="2.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play2" Opacity="0.74" Width="133.275" Height="82.96" Source="3.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play3" Opacity="0.74" Width="133.275" Height="82.96" Source="4.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
<Canvas Width="550.275" Height="82.96" x:Name="playlist2" Canvas.Left="607">
<Image x:Name="play4" Opacity="0.74" Width="133.275" Height="82.96" Source="5.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play5" Opacity="0.74" Width="133.275" Height="82.96" Source="6.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play6" Opacity="0.74" Width="133.275" Height="82.96" Source="7.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play7" Opacity="0.74" Width="133.275" Height="82.96" Source="8.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
</Canvas>
</Canvas>

The JavaScript

Code that I added or changed is in bold, the rest is straight from the Encoder's original output.

var curPos = 1; //track the current position of the playlists
var maxPos = 2; //How many pages of clips do we have?
var cVideos = 8; //How many video Clips do we have?
function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {
case 0:
return { "mediaUrl": "Movie1.wmv",
"placeholderImage": "",
"chapters": [
] };
case 1:
return { "mediaUrl": "Movie2.wmv",
"placeholderImage": "",
"chapters": [
] };
case 2:
return { "mediaUrl": "Movie3.wmv",
"placeholderImage": "",
"chapters": [
] };
case 3:
return { "mediaUrl": "Movie4.wmv",
"placeholderImage": "",
"chapters": [
] };
case 4:
return { "mediaUrl": "Movie5.wmv",
"placeholderImage": "",
"chapters": [
] };
case 5:
return { "mediaUrl": "Movie6.wmv",
"placeholderImage": "",
"chapters": [
] };
case 6:
return { "mediaUrl": "Movie7.wmv",
"placeholderImage": "",
"chapters": [
] };
case 7:
return { "mediaUrl": "Movie8.wmv",
"placeholderImage": "",
"chapters": [
] };
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( { source: 'player.xaml',
parentElement: $get(parentId ||"divPlayer_0"),
id:this._hostname,
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' },
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;
}
StartPlayer_0.prototype= {
_handleLoad: function(plugIn) {
this._player = $create( ExtendedPlayer.Player,
{ // properties
autoPlay : true,
volume : 1.0,
muted : false
},
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname) );
//wire up the rollover and click events for each of our play buttons
for (var i = 0; i < cVideos; i++)
{
var element = plugIn.Content.findName('play' + i);
element.addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
element.addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
element.addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._playX));
}
plugIn.Content.findName('LeftArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideLeft));
plugIn.Content.findName('RightArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideRight));

this._playNextVideo();
},
_rollOver: function(sender, eventArgs) {
sender.opacity=1;
},
_rollOut: function(sender, eventArgs) {
sender.opacity=0.74;
},
_playX: function(sender, eventArgs) {
var X = Number(sender.Name.substring(4));
this._currentMediainfo = X;
this._player.set_mediainfo( get_mediainfo( X ));
sender.opacity=1;
},
_slideLeft: function(sender, eventArgs) {
switch(curPos)
{
case 1:
break;
default:
sender.findName("MoveRight0" + curPos).Begin();
curPos--;
}
},
_slideRight: function(sender, eventArgs) {
switch(curPos)
{
case maxPos:
break;
default:
sender.findName("MoveLeft0" + curPos).Begin();
curPos++;
}
},

_onMediaEnded: function(sender, eventArgs) {
//window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );
}
}

First I added a reference to the plug-in the HandleLoad function, as described here. Then, because I had named all of my play buttons sequentially, it was easy to loop though them all adding some event handlers for rollover effects and the click event. Next I added similar event handlers to the navigation arrows. The rollover effect, as hinted at earlier was simply to set the opacity to 100% on mouseover and back to 74% on mouse out.

The click event for the play buttons simply play the selected movie, based on the number parsed from the name of the sender ("play0", "play1", etc.). The navigation arrows call the _slideLeft and _slideRight functions which simply play the animations to move the buttons left and right. if you have more than 2 pages of play buttons, then it gets slightly more complicated, obviously you have to create more animations, and they have to be carefully numbered so that you play the appropriate animation based on which 'page' of buttons you are currently on. Go to TheJamesBondMovies.com and take a look at the StartPlayer.js on that site for a better understanding of how to make this technique work with multiple pages.

Well, that was my solution, I'm sure there are other ways to do this, but I don't think this way is overly complicated and I hope someone finds it helpful.

Download the Project files: PlaylistSample.zip (364.40 kb)

Download the Silverlight 2 version: ScrollingPlaylist2.zip (1.22 mb)


Posted by Williarob on Wednesday, November 21, 2007 12:13 PM
Permalink | Comments (28) | Post RSSRSS comment feed

Customizing Expression Encoder Output

I finished building my first 100% pure Silverlight 1.0 application, the result of which can be viewed at http://www.thejamesbondmovies.com, and I wanted to share a few of the techniques I learned. Anyone who has played with Microsoft Expression Encoder will probably recognize the player as coming from one of the built in templates. Expanding the XAML to include my own elements was not too difficult, the Blend 2 September Preview made that quite easy. When I have more time I will describe in detail how I made the scrollable playlist as I could find no tutorials anywhere on how to do that, but for right now I just want to address the basics of Scripting the output. I am by no means a JavaScript Guru, which is perhaps why I struggled a bit to understand exactly how to interact with my new elements, and exactly where to put my code. When you build a new Silverlight application in Blend 2 or Visual Studio the JavaScript that is provided to you as a starting point appears to be quite different to that outputted by the encoder. Visual Studio gives you the following in a file called (by default) Scene.xaml.js:

if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};
SilverlightJSApplication1.Scene = function() 
{
}
SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement) 
{
this.plugIn = plugIn;
// Sample button event hookup: Find the button and then attach event handlers
this.button = rootElement.children.getItem(0);	
this.button.addEventListener("MouseEnter", Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp", Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave", Silverlight.createDelegate(this, this.handleMouseLeave));
},
// Sample event handlers
handleMouseEnter: function(sender, eventArgs) 
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin(); 
},
handleMouseDown: function(sender, eventArgs) 
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin(); 
},
handleMouseUp: function(sender, eventArgs) 
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin(); 
// Put clicked logic here
alert("clicked");
},
handleMouseLeave: function(sender, eventArgs) 
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin(); 
}
} 

Blend 2 gives you an almost identical file called Page.xaml.js:

if (!window.MyProjName)
window.MyProjName = {};
MyProjName.Page = function() 
{
}
MyProjName.Page.prototype =
{
handleLoad: function(control, userContext, rootElement) 
{
this.control = control;
// Sample event hookup:	
rootElement.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
},
// Sample event handler
handleMouseDown: function(sender, eventArgs) 
{
// The following line of code shows how to find an element by name and call a method on it.
// this.control.content.findName("Timeline1").Begin();
}
} 

Both of these files make it very easy to pick up your own named elements, and add event handlers to them because the handleLoad function passes the plugIn (visual Studio) or control (Blend 2) argument that can be used to easily find a reference to your own XAML element:

	control.content.findName("MyXamlElement"); //or plugIn.content.findName("MyXamlElement");

But having played with that model a few times and become comfortable with it, you then navigate to your Expression encoder's output location and find as many as six JavaScript files. Obviously, Silverlight.js and MicrosoftAjax.js aren't what you're looking for, PlayerStrings.js is pretty empty, and BasePlayer.js clearly wasn't designed for easy editing as it has been compressed. So that just leaves player.js and StartPlayer.js. I'll come back to player.js in a moment, for it is StartPlayer.js that is the Expression Encoder's equivalent to Scene.xaml.js. 

function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {        
case 0:
return  { "mediaUrl": "MyVideo.wmv",
"placeholderImage": "",
"chapters": [               
] };                                                                
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( {   source: 'player.xaml', 
parentElement: $get(parentId ||"divPlayer_0"), 
id:this._hostname, 
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' }, 
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;      
}
StartPlayer_0.prototype= {
_handleLoad: function() {
this._player = $create(   ExtendedPlayer.Player, 
{ // properties
autoPlay    : true, 
volume      : 1.0,
muted       : false
}, 
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname)  ); 
this._playNextVideo();     
},    
_onMediaEnded: function(sender, eventArgs) {
window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
var cVideos = 1;
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );    
}        
}
 

The first thing I noticed was that unlike the Visual Studio output, the _handleLoad function from Expression Encoder does not pass in a control or PlugIn reference, so how do you get one? Well the way I did it (and I could find no documentation anywhere on how to do this) was simply to add my own (new code is bold):

 StartPlayer_0.prototype= {
_handleLoad: function(control) {
this._player = $create(   ExtendedPlayer.Player, 
{ // properties
autoPlay    : true, 
volume      : 1.0,
muted       : false
}, 
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname)  ); 
control.Content.findName("MyXamlElement"); 
this._playNextVideo();     
},

 

That's all there is to it. Now go ahead and add your events as before. But wait, so what is player.js for then? Glad you asked. Player.js allows you to override the code in the BasePlayer.js file that so clearly was not meant for editing. For example, suppose you had created an animation in your XAML that made the video screen appear, perhaps from behind theatre style curtains that parted, or from behind some other element. You could override the play() function in BasePlayer.js to play your animation before the video:

Type.registerNamespace('ExtendedPlayer');
ExtendedPlayer.Player = function(domElement) {
ExtendedPlayer.Player.initializeBase(this, [domElement]);  
}
ExtendedPlayer.Player.prototype =  {
play: function() {    
this.get_element().content.findName('OpenCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'play');
},
stop: function() {    
this.get_element().content.findName('CloseCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'stop');
},
 	pause: function() {
 		alert('You clicked Pause');
ExtendedPlayer.Player.callBaseMethod(this, 'pause');
} 
}
ExtendedPlayer.Player.registerClass('ExtendedPlayer.Player', EePlayer.Player);

Posted by Williarob on Monday, November 19, 2007 11:21 AM
Permalink | Comments (0) | Post RSSRSS comment feed

RegisterStartUpScript not working

If, like me, you have just spent an hour or so trying to figure out why your simple alert box isn't popping up on page load after registering the script with Page.ClientScript.RegisterStartUpScript; you might find you are missing a form with a runat="server" tag on your page. It doesn't matter where it is, but it does have to be on the page somewhere, in order for the Javascript to be injected into the page. In the end I figured it out the hard way - close examination of pages where it worked vs pages where it didn't work.


Posted by Williarob on Wednesday, October 31, 2007 6:04 AM
Permalink | Comments (7) | Post RSSRSS comment feed

How to Make a Media Element Loop Indefinitely

I was surprised to see that the XAML media element did not have a loop property built into it. Surely having a clip loop is basic functionality? In any case I came up with a JavaScript solution that seemed much simpler to implement than the pure XAML solution offered on the MSDN website

JavaScript Solution

    handleLoad: function(control, userContext, rootElement)
    {
        this.control = control;

        //  Get a reference to your media element (mine is called "MainMovie")         
        this.movie = control.content.findName("MainMovie");
        // Add an Event Listener for the "MediaEnded" Event
       this.movie.addEventListener("MediaEnded", Silverlight.createDelegate(this,this.movieMediaEnded));
    },

    //When the end of the movie is reached, return the movie to the start and play it again
    movieMediaEnded: function(sender, eventArgs)
    {
        sender.Position = "00:00:00";
        sender.play();
    }

See my solution in action at The Daily Prophet Online.

MSDN's Pure XAML Solution

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

<StackPanel>

<!-- The MediaElement control plays the sound. -->

<MediaElement Name="myMediaElement" >

<MediaElement.Triggers>

<EventTrigger RoutedEvent="MediaElement.Loaded">

<EventTrigger.Actions>

<BeginStoryboard>

<Storyboard>

<!-- The MediaTimeline has a RepeatBehavior="Forever" which makes the media play

over and over indefinitely.-->

<MediaTimeline Source="media\tada.wav" Storyboard.TargetName="myMediaElement"

RepeatBehavior="Forever" />

</Storyboard>

</BeginStoryboard>

</EventTrigger.Actions>

</EventTrigger>

</MediaElement.Triggers>

</MediaElement>

</StackPanel>

</Page>


Posted by Williarob on Tuesday, October 30, 2007 12:27 PM
Permalink | Comments (0) | Post RSSRSS comment feed