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.

Loading images last with jQuery

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

There are lots of ways to make your webpages faster and more responsive. YSlow is a great tool to help you find many great ways to make a particular page faster.

One of the best things you can do is reduce the number of requests (css/js/images/etc) to the server. Typically, this would mean that you would combine files – merge all of your JS and CSS (and minify while you are at it), and use CSS Sprites to combine images.

One major problem of using CSS Sprites is that it can be quite painful to maintain. Over time, if you want to add or change some of your images – you basically need to rebuild and replace the combined images and all of the CSS rules specifying coordinates. Sometimes, this makes the CSS Sprite technique unreasonable to implement.

In one such case, we had about 50 images in one application that were causing the page to take a long time to load. These images were previews of some different design choices that the user could make. The design choices themselves (and their previews) were database driven so that we can add new designs through an admin interface. So, CSS Spriteing the previews would seriously hamper that flexibility.

One other design consideration was that the previews weren’t that important – the page was fully functional and usable without the images. In fact, the designs weren’t even visible until you toggled the design menu.

There is a lazy loader plugin for jQuery already available here – but it didn’t fit our needs. Instead of skipping images in order to get the page working as soon as possible (and initiate the load once the page is usable) – it is made to skip loading offscreen images until they are scrolled into view. It might have somewhat worked for our needs – but I thought it was better to load the images as soon as possible, instead of waiting for the design menu to be expanded to initiate the load. That way, most of the time the designs would be visible by the time they open the menu – but it wouldn’t interfere with the rest of the interface.

My solution was to set the src for all of the previews to a single animated loading image – like one you can get here. Then, I set a custom attribute on the image for the real preview’s url. Finally, some jQuery code runs after the page is done loading which replaces each src attribute with the url in the custom attribute, which will load the real image.

Sample HTML:

<ul>
    <li templateid="7bcf8f23-fdd0-45c5-a429-d2ffb59e47f0" class="selected"><span>3D Dots
        Dark</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/7bcf8f23-fdd0-45c5-a429-d2ffb59e47f0/preview.jpg"
            class="deferredLoad" alt="3D Dots Dark" />
    </li>
    <li templateid="b1a09e28-629e-472a-966e-fc98fc269607"><span>3D Dots Lite</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/b1a09e28-629e-472a-966e-fc98fc269607/preview.jpg"
            class="deferredLoad" alt="3D Dots Lite" />
    </li>
    <li templateid="e121d26a-9c8f-466f-acc7-9a79d5e8cfa9"><span>Beauty</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/e121d26a-9c8f-466f-acc7-9a79d5e8cfa9/preview.jpg"
            class="deferredLoad" alt="Beauty" />
    </li>
    <li templateid="322e4c7a-33e7-4e05-bb72-c4076a83a3d0"><span>Black and White</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/322e4c7a-33e7-4e05-bb72-c4076a83a3d0/preview.jpg"
            class="deferredLoad" alt="Black and White" />
    </li>
    <li templateid="57716da9-91ef-4cf0-82f1-722d0770ad7f"><span>Blank</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/57716da9-91ef-4cf0-82f1-722d0770ad7f/preview.jpg"
            class="deferredLoad" alt="Blank" />
    </li>
    <li templateid="a79e1136-db47-4acd-be3e-2daf4522796d"><span>Blue Leaves</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/a79e1136-db47-4acd-be3e-2daf4522796d/preview.jpg"
            class="deferredLoad" alt="Blue Leaves" />
    </li>
    <li templateid="03cb737d-4da7-46d5-b4e4-5ad4b4a3aaf4"><span>Blue Open</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/03cb737d-4da7-46d5-b4e4-5ad4b4a3aaf4/preview.jpg"
            class="deferredLoad" alt="Blue Open" />
    </li>
    <li templateid="899dff2f-38ba-44f7-9fe2-af66e62674a4"><span>Compass</span>
        <img src="/static/img/ajax-loader-small.gif" deferredsrc="/resources/899dff2f-38ba-44f7-9fe2-af66e62674a4/preview.jpg"
            class="deferredLoad" alt="Compass" />
    </li>
</ul>

Sample javascript:

$(function(){
        $("img.deferredLoad").each(function() {
            var $this = $(this);
            $this.attr("src", $this.attr("deferredSrc")).removeClass("deferredLoad");
        });
});

Getting the width of a hidden element with jQuery using width()

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

UPDATE #3: It needs to be noted that the fix introduced in jQuery 1.4.4 is only for the width() and height() methods.  If you need inner/outer dimensions the method below still needs to be used.  I have updated the method to return height/width, outer height/width, and inner height/width.  There is an optional parameter to include margins in the outer dimension calculations.  Thanks Ryan and Fred for the heads up.

UPDATE #2: jQuery 1.4.4 was released today (11/11/2010) and included in the release was an update to the width() and height() methods.z  Each method will now return the correct dimension of the element if it is within a hidden element.  For further information, you can view the bug report.

UPDATE #1: Based on the feedback in the comments regarding the use of the swap method, I am updating this post with a solution from Ryan Wheale.  He created a function to return the dimensions of an element that is hidden or nested within 1 more hidden elements.  Here is the code that he posted below in his comment:

//Optional parameter includeMargin is used when calculating outer dimensions
(function($) {
$.fn.getHiddenDimensions = function(includeMargin) {
    var $item = this,
        props = { position: 'absolute', visibility: 'hidden', display: 'block' },
        dim = { width:0, height:0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 },
        $hiddenParents = $item.parents().andSelf().not(':visible'),
        includeMargin = (includeMargin == null)? false : includeMargin;

    var oldProps = [];
    $hiddenParents.each(function() {
        var old = {};

        for ( var name in props ) {
            old[ name ] = this.style[ name ];
            this.style[ name ] = props[ name ];
        }

        oldProps.push(old);
    });

    dim.width = $item.width();
    dim.outerWidth = $item.outerWidth(includeMargin);
    dim.innerWidth = $item.innerWidth();
    dim.height = $item.height();
    dim.innerHeight = $item.innerHeight();
    dim.outerHeight = $item.outerHeight(includeMargin);

    $hiddenParents.each(function(i) {
        var old = oldProps[i];
        for ( var name in props ) {
            this.style[ name ] = old[ name ];
        }
    });

    return dim;
}
}(jQuery));

This basically performs the same operations as the swap method.  This is safer to use in case the swap method is removed from the jQuery core.

I have tested this in multiple cases and each time the correct results were returned.

Thanks Ryan.

————————————————————————————————————————————-

Original post

I recently ran into a problem with jQuery’s width().  The problem is with a visible element that is inside a hidden element will return a value of 0 instead of its’ actual calculated width.  After messing around with it for a little bit I finally came up with a solution.  The method I used involved adding some CSS properties to the hidden element.  The CSS properties involved are position, visibility, and display.

//$HiddenItem is the element that is wrapping the element that you want the width of
//$Item is the element you want the width of

$HiddenItem.css({
    position: "absolute",
    visibility: "hidden",
    display: "block"
})
$Item.width();

$HiddenItem.css({
    position: "",
    visibility: "",
    display: ""
})

After setting the above CSS properties on the element, you can then call width() and the correct value will be returned. After you call the width() method you should clear the properties in order to return the element to the way it was.

Setting the properties to an empty string is probably not the best way to do it though. What if there was a position value already set? Using this method would clear out that initial values of the CSS properties.

I found the swap() method to be handy in this situation. I found this method while looking through the jQuery source code.

 // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) {      var old = {};       // Remember the old values, and insert the new ones      for ( var name in options ) {           old[ name ] = elem.style[ name ];           elem.style[ name ] = options[ name ];      }       callback.call( elem );       // Revert the old values      for ( var name in options ){          elem.style[ name ] = old[ name ];      } } 

By using the swap method, the old CSS properties will be remembered and reapplied after finding the width of the element. The swap method takes in 3 parameters:

  1. The element that you would like to swap the CSS properties on
  2. CSS key/value pairs that you want to change
  3. A callback function to call after the properties are set

To rewrite the above to use the swap method I would do the following:

var props = { position: "absolute", visibility: "hidden", display: "block" };
var itemWidth = 0;

$.swap($HiddenItem[0], props, function(){
     itemWidth = $Item.width();
});

//Use itemWidth

I coded up a small example on jsbin. Here is the link http://jsbin.com/ofine3/2.

$HiddenItem.width();