Plupload Custom File Upload UI

Plupload is a tool that allows you to upload files using HTML5 Gears, Silverlight, Flash, BrowserPlus, normal forms, and provides features such as upload progress, image resizing and chunked uploads.

So I’m gonna discuss the process I went through to implement a custom Plupload UI.

Here is a video off it in action:

Now I’ll go into details on how it was built using the Plupload framework.
I modeled my version of the plupload custom example here.

So first of all I Init an instance of plupload and set the options. I set the drop area, and the container that the file progress will be displayed in.
Note: Only FF and Chrome support drag’n drop in Plupload.

$(document).ready(function(){
var uploader = new plupload.Uploader({
	// General settings
	runtimes: 'html5,flash,gears,browserplus,silverlight,html4',
	url:<%= SaveUrl %>,
	browse_button : "pickfiles",
	button_browse_hover : true,
	drop_element : "dropArea",
	autostart : true,
	max_file_size: '100mb',
	container: "FileContainer",
	chunk_size: '1mb',
	unique_names: true,
	// Flash settings
	flash_swf_url: <%= "'"+  Page.ResolveUrl("~/plugins/plupload/plupload.flash.swf") +"'" %>,
	// Silverlight settings
	silverlight_xap_url: <%= '"'+ Page.ResolveUrl("~/plugins/plupload/plupload.silverlight.xap") +'"' %>
});

Plupload doesn’t automatically hide the drop area when the current browser doesn’t support it so I bind some custom logic to the plupload “Init” event below. Drag and drop support requires that the browser support FileReader and will not work under the flash or silverlight runtimes.

uploader.bind('Init', function(up, params){
try{
    if(!!FileReader && !((params.runtime == "flash") || (params.runtime == "silverlight")))
         $("#dropArea").show();
         $("#fileSelectMsg").hide();
    }
catch(err){}});
uploader.init();

So now we need to be able to display files in our “FileContainer” div the script below fires on the FilesAdded event. It is called when a file is added or dropped in the drop area. When this event is fired I create a div that contains the elements we will need to display upload progress and the name of the file.

uploader.bind('FilesAdded', function(up, files) {
	$.each(files, function(i, file) {
		$('#filelist').append(
			'<div id="' + file.id + '" class="fileItem"><div class="name">' +
			file.name + '</div><a href="#" id="cancel'+file.id+'" class="cancel">cancel</a><div class="fileInfo"><span class="size">' + plupload.formatSize(file.size) + '</span>' +
			'<div class="plupload_progress"><div class="plupload_progress_container"><div class="plupload_progress_bar"></div></div></div>'+
			'<span class="percentComplete"></span></div></div>');

		//Fire Upload Event
		up.refresh(); // Reposition Flash/Silverlight
		uploader.start();

		//Bind cancel click event
		$('#cancel'+file.id).click(function(){
			$fileItem = $('#' + file.id);
			$fileItem.addClass("cancelled");
			uploader.removeFile(file);
			$(this).unbind().remove();
		});

		//Set ico_Ok to Uploading
		$confirmNext.attr("disabled", "disabled");
		$confirmNext.html("Uploading..");
		$confirmNext.unbind("click").click(function(e){e.preventDefault();});
	});
});

Now that we have files uploading we need to update the progress bars for each file div in the “FileContainer”. So yet again Plupload conviently has an event called “UploadProgress” that fires after each chunk has been successfully uploaded.

uploader.bind('UploadProgress', function(up, file) {
	var  $fileWrapper = $('#' + file.id);
	$fileWrapper.find(".plupload_progress").show();
	$fileWrapper.find(".plupload_progress_bar").attr("style", "width:"+ file.percent + "%");
	$fileWrapper.find(".percentComplete").html(file.percent+"%");
	$fileWrapper.find('#cancel'+file.id).remove();
});

So now we have files uploading and being added to the container and updating the upload progress in real time. So now I want to grey out the div once the file has been completed. In order to do this I add a class to that file’s div. The “FileUploaded” event gets fired after each file is successfully uploaded.

uploader.bind('FileUploaded', function(up, file) {
	$fileItem = $('#' + file.id);
	$fileItem.addClass("completed");

	$confirmNext.removeAttr("disabled");
	$confirmNext.html("Ok");
	$confirmNext.unbind().click(function(e){window.location.href = window.location.href;});

	$('#cancel'+file.id).unbind().remove();
});

Last but not least I’ll show you how to display errors that happen server side when a chunk is uploaded. On “ChunkUploaded” event I check the response message if it contains “Error:” in the response string. I kill the current upload and display that error message to the user and also log the error in my application. The following snippet performs just this.

uploader.bind("ChunkUploaded", function(up, file, response){
	//Should return a status 200 if the chunk was uploaded successfully
	if(response.status != null)
	{
		if(response.status != "200" || (response.response.indexOf("Error") != -1))
		{
			if(response.response.indexOf("Error") != -1)
			{
				//Prompt the user with the custom error message
				$("div.error:first").show().html('<p>'+response.response+'</p>');
			}
			else
			{
				//Log this as an error
				//Custom line of code to log error on server would go here
				
				//Notify user of error
				$("div.error:first").show().html('<p>There was an error uploading your file '+ file.name +' Support has been notified.</p>');
			}
			$('#' + file.id).addClass("cancelled");
			uploader.stop();
		}
	}    
});

So thats how I built my custom UI wrapper around the Plupload core. Hopefully that will help get you going in the right direction.

Advertisements

Selenium Regression Testing Part II – Tips and Tricks

Add to FacebookAdd to DiggAdd to Del.icio.usAdd to StumbleuponAdd to RedditAdd to BlinklistAdd to TwitterAdd to TechnoratiAdd to Yahoo BuzzAdd to Newsvine

In my last post, I talked about how you can use Selenium to do real regressions tests for web applications. It’s a great way to automate testing the real user experience, and not just the backend stuff.

That said, Selenium is a relatively new technology, and it’s not without its issues. When building your first test you might find a lot of times where it’s a trial and error process. There are so many different ways to do the same test, it can be overwhelming. Often times, one or more of the ways you try won’t work. I’m going to try to list some of the common problems I ran into and tips I found below.

  • Selenium Commands
    • waitForElementPresent
      • this is will block your test proceeding until it finds the identified element.  It checks once a second for 30 seconds.
      • this was the most common way I dealt with ‘ajax’ type interactions where it takes an unknown period of time for something to show up
      • I also use it generally instead of verifyElementPresent – it does basically the same thing with a little wiggle room
    • mouseDown/mouseUp/mousePressed/click
      • mostly equivalent, but sometimes you need to match the desired event to a javascript handler
      • try click first.  If it doesn’t work the way you want, move on to mouseDown and so on.
    • waitForFrameToLoad/selectFrame
      • important if you use iFrames (modal dialog, etc.)
      • the selenium selectors only hit the current frame, otherwise you have to select the correct frame
      • an easy way to get back to the root window is to do selectFrame null
    • type vs. typeKeys
      • type fills out an input in code – if it works, use this.  You can use this, and then fire a single typeKeys for the last character if you need an event to be triggered.
      • typeKeys fires the key events on the element
        • has some idiosyncracies – certain letters (I’m looking at you, ‘y’) are reserved to do other special keypresses
        • necessary if you are using a wysiwyg ‘designmode’ type box instead of a standard input
    • verifyX vs. assertX
      • if verify fails, the test continues (and is marked as errored).  If assert fails, the test aborts.
      • Usually verify is better, unless one task blocks a future one from functioning correctly
    • runScript vs. Eval vs. Expression
      • runScript inserts the javascript you provide into the current frame/window.  Useful for those things selenium doesn’t support – like moving a cursor around and selecting text in a wysiwyg
      • Eval runs javascript in the context of selenium. Handy for complex checks – use waitForEval (for instance, checking the css background image property of a particular element)
        • Use this.browserbot.findeElement(“selenium selector”) to find elements the way selenium would
        • Use window.X To access the current frame/window context objects
      • Expression is similar to Eval, but uses Selenium’s custom expression format instead of javascript (but you can combine with javascript by using  javascript{}
        • storedVars[‘key’] allows you to get to a variable you created with a Selenium ‘store’ expression
    • selectPopUp
      • useful for checking stuff in a popup that was initiated
      • Easiest to get by the html title of the popup, but do a ‘pause’ first to let it load
  • Selenium Selectors and XPath
    • In general, be as abstract as possible.
      • Don’t select individual server generated ids (hand crafted html ids are ok if you don’t expect them to change)
      • Don’t select on complicated relationships ( /div[0]/div[2]/a[4] ) – your html structure will change and you’ll have to maintain it
      • Select links by the simple link=text when possible – easy to read/maintain, unlikely to change
      • Use //that (any decendant) instead of /this/that where possible
      • .  references ‘this’ element.  Helps to select something with a particular text:   //div[@id=’publish-private-shares’]//p[.=’This is pretty cool.’]
      • Contains() is useful if you don’t know the exact text (for instance, when an element has multiple css classes):     //div[@id=’pageContent’ and contains(@class,’contenteditable’) and h2=’Goals’]/p[1]
  • Selenium RC
    • While you can use Selenium IDE to create a c# version of your tests – if you do so, you have two tests to maintain.  You can run your ‘selenese’ tests directly with RC, too.
      • JAVAPATH\java.exe –jar SELENIUMPATH\selenium-server.jar –htmlSuite “*BROWSER” “BASESITEURL” “SUITEFILEPATH” “RESULTSFILEPATH”
      • I’ve written a simple csharp console project that automatically finds the correct javapath and fires up the test when you run it.  If people ask in the comments, I’ll post it.
    • Last I checked, Chrome and Safari-Windows don’t work.  Chrome is supposed to be fixed in Selenium RC 1.0.4
  • Sauce RC
    • This is a great UI to help test multiple browsers, but there are a couple of issues
      • Firefox works, but only in dual window mode
      • IE works, but only in single window mode.
      • The ‘timeout’ setting implies a default timeout per action in your test, but it is actually the timeout for your entire test run.  Since it defaults to 30 seconds, you’ll probably want to change it, or else your tests will suddenly die for no reason with no explanation/log.

I’m sure there is probably more I’ve forgotten, so leave a comment if you get stuck and I’ll try to help out if I can.

bindWithDelay jQuery Plugin

Sometimes, I want to have a JavaScript event that doesn’t fire until the native event stops firing for a short timeout. I’ve needed to use that pattern in almost every project I have worked on.

For example, you want to use JavaScript to resize an iframe to 100% height when the window resizes. The resize() event can fire dozens of times, and calculating and setting the new height can slow down your page. I used to implement it like this:

var timeout;
function doResize(e) {
   clearTimeout(timeout);
   timeout = setTimeout(function() {
      // run some code
   }, 200);
}
$(function() {
   $(window).bind("resize",doResize);
});

Notice that there are extra variables that you have to deal with, and extra indentation. You could at least clean up the global variable using closures, but you get the idea.

I wrote a plugin to make this pattern easier, it is called “bindWithDelay”. The source code is online, as is a mini project page with a demo.

This is what the same code looks like with the plugin:

function doResize(e) {
      // run some code
}
$(function() {
   $(window).bindWithDelay("resize", doResize, 200);
})

Radio button within a repeater problem

Recently I was developing a system to create tests and test questions.  For these tests our client wanted multiple choice questions.  To implement this I decided to have a list of textboxes for the answer text, and a radio button for each textbox to select the correct answer.   I knew that a RadioButtonList couldn’t have anything other than a radio button and text, so I went with a repeater.

<asp:Repeater ID="rptRadios" runat="server">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:RadioButton ID="rbRadio" runat="server" GroupName="RadioGroup" />
            &nbsp;
            <asp:TextBox runat="server" ID="txtRadio"></asp:TextBox>
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

Doing it this way caused the group name of each radio button to be inconsistent, because of the repeater. After a while of researching and not finding any good solutions I decided to try changing the group name of the radio buttons using jQuery.

$("input:radio").attr('name', 'RadioGroup');

That gave me the radio button functionality that I wanted, but it prevented me from getting the selected radio button on postback. So I decided to just implement the radio button functionality manually.

var radios = $("input:radio");
radios.click(function() {
     radios.removeAttr('checked');
     $(this).attr('checked', 'checked');
     return true;
});

Which gave me the correct functionality and I could still get the selected radio button and textbox on postback. Probably not the most elegant solution, but I couldn’t find any other way to do it.

Also I needed to make sure at least one of the radio buttons was selected so I added a CustomValidator that called a javascript function.

function ValidateRadioButtons(sender, args) {
     args.IsValid = $("input:radio:checked").size() > 0;
}

Handy ASP.NET Debug Extension Method

Add to FacebookAdd to DiggAdd to Del.icio.usAdd to StumbleuponAdd to RedditAdd to BlinklistAdd to TwitterAdd to TechnoratiAdd to Yahoo BuzzAdd to Newsvine

Most of the programmers I know (myself included) don’t bother with the built in Visual Studio debugging tools. They are slow and resource intensive. Usually, its more efficient to just do one or more Response.Write calls to see key data at key steps.

That can be a hassle, though. Most objects don’t print very well. You have to create a loop or write some LINQ/String.Join to write items in a collection.

Inspiration struck – couldn’t I write an extension method on object to write out a reasonable representation of pretty much anything? I could write out html tables for lists with columns for properties, etc.

Then I thought – I love the javascript debug console in firebug. I can drill down into individual items without being overwhelmed by all of the data at once. Why not have my debug information spit out javascript to write to the debug console? That also keeps it out of the way of the rest of the interface.

Here’s the code:

public static void Debug(this object value)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.Response.Debug(value);
            }

        }

        public static void Debug(this HttpResponse Response, params object[] args)
        {

            new HttpResponseWrapper(Response).Debug(args);
        }
        public static void Debug(this HttpResponseBase Response, params object[] args)
        {

            ((HttpResponseWrapper)Response).Debug(args);
        }
        public static void Debug(this HttpResponseWrapper Response, params object[] args)
        {

            if (Response != null && Response.ContentType == "text/html")
            {
                Response.Write("<script type='text/javascript'>");
                Response.Write("if(console&&console.debug){");

                Response.Write("console.debug(" +
                              args.SerializeToJSON() +
                               ");");
                Response.Write("}");
                Response.Write("</script>");
            }
        }

The various overloads allow:

myObject.Debug();
new {message="test",obj=myObject}.Debug();
Response.Debug("some message",myObject,myObject2);
//etc

The only other thing you’ll need is the awesome JSON.NET library for the .SerializeToJSON() call to work (which turns the .NET object into the form javascript can deal with). Get it here. FYI, the library does choke serializing some complex objects, so occasionally you’ll need to simplify before calling debug.

Using jQuery and YQL to get an RSS feed from a site

Add to FacebookAdd to DiggAdd to Del.icio.usAdd to StumbleuponAdd to RedditAdd to BlinklistAdd to TwitterAdd to TechnoratiAdd to Yahoo BuzzAdd to Newsvine

Finding an RSS feed on a site can sometimes be a hassle.  Browsers have started introducing a feature that will include an RSS icon in the address bar when the browser finds the site has an RSS feed.  I thought it would be nice to allow users to just enter in a web address and let us do the work finding the RSS feed for the site.  I looked into how browsers were accomplishing this and found that browsers look for a <link> tag in the <head> of the document with the type attribute of “application/rss+xml”.

Now that we know what to look for, how do we go about determining if the website provided has the correct <link> tag?  I decided to let YQL do the work for me.  I loaded up the YQL Query Console and began testing some queries.  On the right hand side, there are different Data Tables you can use.  I scrolled down to “data” and selected the “html” option which allows you to query the html of any site.

The example that you start out with looks similar to this:

select * from html where url="http://finance.yahoo.com/q?s=yhoo" and xpath='//div[@id="yfi_headlines"]/div[2]/ul/li/a'

From this example it is easy to see which URL is being queried and the XPath that is being used to find elements on the page.  If you aren’t familiar with  XPath, check out the W3 XPath documentation.  So now we just plug in the URL of the site we want to search and change the XPath to whatwe need to use to find the <link> elements. Now the query looks like this:

select * from html where url="https://lanitdev.wordpress.com/" and xpath='//link[@type="application/rss+xml"]'

In the query console, be sure to select the JSON radio button and then click the “Test” button.  You will see JSON output that looks like the following:

feeds({
    "query":{
    "count":"1",
    "created":"2010-02-26T09:08:43Z",
    "lang":"en-US",
    "updated":"2010-02-26T09:08:43Z",
    "uri":"http://query.yahooapis.com/v1/yql?q=select+*+from+html+where+url%3D%22http%3A%2F%2Flanitdev.wordpress.com%22+and%0A++++++xpath%3D%27%2F%2Flink%5B%40type%3D%22application%2Frss%2Bxml%22%5D%27",
    "results":{
        "link":{
            "href":"https://lanitdev.wordpress.com/feed/",
            "rel":"alternate",
            "title":"The Lanit Development Blog RSS Feed",
            "type":"application/rss+xml"
        }
    }
}
});

We can see from the JSON that we found 1 feed and we get all the information to go along with it.

Now that we have a proper YQL query, let’s use some jQuery to create a form that retrieves these feeds for us.  I am going to create a simple form where a user inputs a url and then outputs a list of RSS feeds or a message stating that an RSS feed couldn’t be found.

The form is straightforward.  I just have a textbox and a button.  I also have an unordered list which I’ll use to display the list of RSS feeds.

<table>
    <tr>
        <td>
            <label><input type="text" id="website" /></label>
        </td>
        <td>
            <input id="getrss" type="button" value="Get RSS" />
        </td>
    </tr>
</table>

<ul id="rsslist">
</ul>

Next is the jQuery.  We will attach an event to the button that will hit the YQL URL and return JSON that we can parse and get the data we need.  The YQL URL we will be using is found in the YQL Query Console next to where you entered the query.  There is a box called “The REST Query” which has the query that we will use.  The only modification I made to the rest query was that I took out the URL that was in there and replaced with with the jQuery value from the textbox.

$(function(){
    $("#getrss").click(function(){
        $("#rsslist").empty();
        $.ajax({
            type: "GET",
            url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22"  + $("#website").val() + "%22%20and%0A%20%20%20%20%20%20xpath%3D'%2F%2Flink%5B%40type%3D%22application%2Frss%2Bxml%22%5D'&format=json&diagnostics=false",
            dataType: "json",
            success: function(data){
                if (data.query.count == 1){
                    $("#rsslist").append("<li><a  href='" + data.query.results.link.href + "'>" + data.query.results.link.title + "</a></li>");
                }
                else if (data.query.count > 1){
                    for (var i = 0; i < data.query.results.link.length; i++)
                    {
                        var link = data.query.results.link[i];
                        $("#rsslist").append("<li><a  href='" + link.href + "'>" + link.title + "</a></li>");
                    }
                } else {
                    $("#rsslist").append("<li>No RSS feed found</li>");
                }
             },
             error: function(){
                 console.log("error");
             }
        });
     });
})

When the AJAX request finishes successfully we check to see if any feeds are returned and if there are, we loop through them and display them.

I created a demo page using jsbin.com so you can see this in action. View Demo

I hope this small example gives you an idea how you can use jQuery and YQL to accomplish other interesting challenges.

Bing Maps API – Getting Started

Lanit had recently been researching getting a mapping API set up for use in various projects. Ultimately, due to ease of use and licensing, we settled on the Bing Maps API.

The goal of the first project using these maps was to plot a fairly large number of items with known locations on a simple map of Missouri.

Starting out, you must first include a reference to the bing maps in your page:

<script type='text/javascript' src='http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&mkt=en-us'/>

After that, create some objects that you’re going to plot. For our implementation, all the needed information is stored on the page -so these properties are just getting values from these.

function Item($container) {
    var item = this;
    var lngBox = $container.find("input.lng");
    var latBox = $container.find("input.lat");
    item.getLat = function() { return latBox.val(); };
    item.getLng = function() { return lngBox.val(); };
    item.Icon = “img/iconhere”;
    item.Title = “Title Here”;
}

Item.prototype.plot = function(map) {
    var item = this;
    item.getPosition(function(latLng) {
    var thisPin = new VEShape(VEShapeType.Pushpin, latLng);
    thisPin.SetCustomIcon(item.Icon);
    thisPin.SetTitle(Item.Title);
    var pinPoint = map.AddShape(thisPin);
});};

It’s worth noting here the “plot” is expecting an object of type “VEMap”, and will then position a point of type VEShape at the position of the item.

For our implementation, we had static addresses with Latitudes and Longitudes already saved. If your implementation will need to geocode addresses, the “GetLat” and “GetLng” functions will need to be replaced with calls to the geocode service.

After creating these objects, we’ll load the map on page load and plot the objects

var settings = {
              mapOpts: {container: "map", // id of map 
             containerheight: 550, 
           width: 600, 
zoom: 7, 
center: new VELatLong(38.208142, -92.381362) },
elementClick: function() { }// this code is run when a plotted item is clicked
};

$(function() {

    $("#" + settings.mapOpts.container).css({
        'width': settings.mapOpts.width, // the API will not automatically set the height/width of the container object – this must be done before the maps are loaded.
        'height': settings.mapOpts.height,
        'position': 'relative'
    });

    // creates a map in the dom object by ID
    var map = new VEMap(settings.mapOpts.container);

    // attach an event to the item clicks -
    map.AttachEvent("onclick", function(e) {
        if (e.elementID != null) { // call the click function using the Pin element
            var shape = map.GetShapeByID(e.elementID);
            settings.elementClick(shape);
        }
    });

    // this renders the map to the page
    map.LoadMap(settings.mapOpts.center, settings.mapOpts.zoom, null, false, null, false);

    // disable zooming on mouse wheel
    map.AttachEvent("onmousewheel", function(e) {
        var mouseWheel = -e.mouseWheelChange / 2;
        window.scrollBy(0,mouseWheel);
        return true;
    });

    // plot the items on the map
    var items = $("#itemContainer").find("div.itemContainer");
    items.each(function() {
        var item = new Item($(this));
        item.plot(map);
   });
});

You can see some implementation decisions here – the pages were pretty unusable with the default functionality to scroll on mouse wheel, so that was disabled.