Merge with b6d17223140ff462ee13c178855d63131f063752 chunked_upload
authorrougeronj
Tue, 12 Feb 2013 17:30:41 +0100
branchchunked_upload
changeset 1103 ce34461a47b6
parent 1102 946a601ab64a (diff)
parent 1100 b6d17223140f (current diff)
child 1104 d8c1a909910c
Merge with b6d17223140ff462ee13c178855d63131f063752
src/ldt/ldt/ldt_utils/urls.py
src/ldt/ldt/static/ldt/css/ldtform.css
--- a/src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/create_content.html	Sun Feb 10 23:04:28 2013 +0100
+++ b/src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/create_content.html	Tue Feb 12 17:30:41 2013 +0100
@@ -10,12 +10,12 @@
 	{{ block.super }}
     <script type="text/javascript" src="{{ADMIN_MEDIA_PREFIX}}js/core.js" ></script>
     <script type="text/javascript" src="{{ADMIN_MEDIA_PREFIX}}js/jquery.init.js"></script>
-    <script type="text/javascript" src="{{ADMIN_MEDIA_PREFIX}}js/jquery.min.js"></script>
+    <script type="text/javascript" src='{% absstatic "ldt/js/jquery.min.js" %}'></script>
+    <script type="text/javascript" src="{% url ldt.jsi18n packages='ldt' %}" ></script>	
     <script type="text/javascript" src="{% absurl ldt.jsi18n packages='ldt' %}" ></script>
     <script type="text/javascript" src='{% absstatic "ldt/js/projectscontents.js" %}'></script>
     <script type="text/javascript" src='{% absstatic "ldt/js/jquery.textchange.min.js" %}'></script>
     <script type="text/javascript" src='{% absstatic "ldt/js/jquery-ui.min.js" %}'></script>
-    <script type="text/javascript" src='{% absstatic "ldt/js/swfupload/swfupload.js" %}'></script>
 	
 	{{ content_form.media.js }}
 {% endblock %}
@@ -29,26 +29,52 @@
 		post_added_params = {"csrfmiddlewaretoken" : "{{csrf_token}}",
                     		"{{cookie_name}}" : "{{session_key}}"};
 		
-		onCreateContentReady("{% url ldt.ldt_utils.views.content.upload %}",
-							'{% absstatic "ldt" %}',
-							post_added_params,
-							'{% trans "Browse" %}',
-							'{% trans "File uploaded" %}',
-							'{% trans "Please wait, the upload is not finished yet" %}',
-							'{% trans "Cancel upload" %}',
-							"{% url ldt.ldt_utils.views.content.remove_temp_file %}",
-							"{% url ldt.ldt_utils.views.content.get_duration %}");
-		
+		onCreateContentReady('{% absstatic "ldt" %}', post_added_params);
 	});
 	// the function stopRKey prevents from the validation of the form if the user tape enter button while entering a field 
 	function stopRKey(evt) {
 		  var evt = (evt) ? evt : ((event) ? event : null);
 		  var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
 		  if ((evt.keyCode == 13) && (node.type=="text"))  {return false;}
-		}
+	}
 
 	document.onkeypress = stopRKey;
 
+	chunked_uploads_endpoints = {
+		upload_url: '{{WEB_URL}}{% url upload %}',
+		done_url: '{{WEB_URL}}{% url upload_done uuid='00000000-0000-0000-0000-000000000000' %}',
+		username: 'example',
+		api_key: 'secret_example',
+    };
+	
+	$(function() {		
+    	//gets video_url and returns it to the "scr" field
+    	chunked_uploads_video_url = function(video_url) {
+    		$('#id_content-media_input_type').val("create");
+    		$('#id_media-src').val(video_url);
+    		$('#id_media-src').attr('readOnly', true);
+    		$(".media_fields").hide();
+       		$("#media_field_create").show();
+    		$('#upload_url').append(video_url);
+	    };
+	    
+	    //callback of chunked upload start
+		chunked_uploads_start = function(){
+		};
+		
+		//callback of chunked upload stop
+		chunked_uploads_stop = function(){
+		};
+		
+		//callback of chunked upload error
+		chunked_uploads_error = function(){
+			alert("The upload failed. Please reload the page, and try again.");
+		};
+		
+		//callback of chunked upload complete
+		chunked_uploads_complete = function(){
+		};
+    });
 	
     </script>
 {% endblock %}
@@ -60,7 +86,7 @@
 	<link rel="stylesheet" href='{% absstatic "ldt/css/style.css" %}'/>
 	<link rel="stylesheet" href='{% absstatic "ldt/css/blueprint/screen.css" %}' type="text/css" media="screen, projection" />
 	<link rel="stylesheet" href='{% absstatic "ldt/css/blueprint/print.css" %}' type="text/css" media="print" />
-	<link rel="stylesheet" href='{% absstatic "ldt//css/workspace.css" %}'type="text/css"/>
+	<link rel="stylesheet" href='{% absstatic "ldt/css/workspace.css" %}'type="text/css"/>
 	
 	
 {% endblock %}
@@ -114,15 +140,14 @@
 		<div>{{ content_form.media_input_type.errors }}{{ content_form.media_input_type.label_tag }}{{content_form.media_input_type}}</div>
 		<div id="media_fields_div">
 			<div id="media_field_link" class="media_fields">{{ content_form.media_obj.errors }}{{ content_form.media_obj.label_tag }}{{content_form.media_obj}}</div>
+			
 			<div id="media_field_upload" class="media_fields">
-				{{ media_form.media_file.errors }}
-				{% comment %}{{ media_form.media_file.label_tag }}{{media_form.media_file}}{% endcomment %}
-				<div id="upload_btn"></div>
-				<input type="text" name="media-local_file_name" id="id_media-local_file_name" readonly/>
-				<div id="upload_progress_bar"></div>
-				<div id="upload_progress_info"></div>
+				<!-- refers to chunked_upload template -->
+				{% include "ldt/ldt_utils/partial/chunked_uploads.html"%}
 			</div>
+			
 			<div id="media_field_url" class="media_fields">{{ media_form.external_src_url.errors }}{{ media_form.external_src_url.label_tag }}{{ media_form.external_src_url }}</div>
+			
 			<div id="media_field_create" class="media_fields">{{ media_form.src.errors }}{{ media_form.src.label_tag }}{{ media_form.src }}</div>
 		</div>
 	</div>	 
@@ -138,7 +163,6 @@
 		{% endif %}
 	{% include "ldt/ldt_utils/partial/picture.html"%}
 	{% include "ldt/ldt_utils/partial/permissions.html" %}
-
 		
 	<div id="submitcontent" class="span-18 last">
 		<div id="submitcontent-loader" class="span-10">
@@ -155,4 +179,8 @@
 	
 	</form>
 	</div>
+	<script type="text/javascript" src='{% absstatic "ldt/js/jquery.ui.widget.js" %}'></script>
+	<script type="text/javascript" src='{% absstatic "ldt/js/jquery.iframe-transport.js" %}'></script>
+	<script type="text/javascript" src='{% absstatic "ldt/js/jquery.fileupload.js" %}'></script>
+	<script type="text/javascript" src='{% absstatic "ldt/js/jquery.chunked_uploads.js" %}'></script>
 {% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/ldt_utils/templates/ldt/ldt_utils/partial/chunked_uploads.html	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,33 @@
+<!-- {% load absstatic %} -->
+
+<link rel="stylesheet" href='{% absstatic "ldt/css/chunked_uploads.css" %}'/>
+
+<script type="text/javascript" charset="utf-8">    
+    function file_name(element) {
+    	$(element).hide();
+    	$("#file_part").append(element.value);
+    };
+</script>
+
+<span id="file_part" class="fileinput-button">
+	<input type="file" id="fileupload" name="files[]" onchange="file_name(this)"/>
+</span>
+<br/>
+<button id="start_upload" class="action_upload" type="button">
+	<span>Start</span>
+</button>
+<button id="resume_upload" class="action_upload" type="button">
+	<span>Resume</span>
+</button>
+<button id="pause_upload" class="action_upload" type="button">
+	<span>Pause</span>
+</button>
+<button id="cancel_upload" class="action_upload" type="button">
+	<span>Cancel</span>
+</button>
+<div class="bar grey stripes">
+	<span class="progress" style="width: 0%"></span>
+</div>
+
+<div id="upload_url">
+</div>
\ No newline at end of file
--- a/src/ldt/ldt/ldt_utils/urls.py	Sun Feb 10 23:04:28 2013 +0100
+++ b/src/ldt/ldt/ldt_utils/urls.py	Tue Feb 12 17:30:41 2013 +0100
@@ -75,5 +75,4 @@
         url(r'^space/upload/$', 'views.content.upload'),
         url(r'^space/removetempfile/$', 'views.content.remove_temp_file'),
         url(r'^space/getduration/$', 'views.content.get_duration'),
-
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/css/chunked_uploads.css	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,46 @@
+
+.fileinput-button {
+}
+
+.fileinput-button input {
+  opacity: 1;
+  filter: alpha(opacity=0);
+}
+
+.bar {
+background-color: #FFFFFF;
+	height: 12px;
+	padding: 3px;
+	width: 250px;		
+	border-radius: 45px;
+	box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444;	         
+	}
+
+.bar span {
+	display: inline-block;
+	height: 100%;	
+	border-radius: 45px;
+    -webkit-transition: width .4s ease-in-out;  
+}
+
+.grey span {
+	background-color: #838383;   
+}
+
+.stripes span {
+	-webkit-background-size: 30px 30px;
+	background-size: 30px 30px;			
+	background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%,
+						transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%,
+						transparent 75%, transparent);     
+	
+	-webkit-animation: animate-stripes 3s linear infinite;     		
+}
+
+@-webkit-keyframes animate-stripes { 
+	0% {background-position: 0 0;} 100% {background-position: 60px 0;}
+}
+
+@-moz-keyframes animate-stripes {
+	0% {background-position: 0 0;} 100% {background-position: 60px 0;}
+}
\ No newline at end of file
--- a/src/ldt/ldt/static/ldt/css/ldtform.css	Sun Feb 10 23:04:28 2013 +0100
+++ b/src/ldt/ldt/static/ldt/css/ldtform.css	Tue Feb 12 17:30:41 2013 +0100
@@ -163,7 +163,7 @@
 }
 
 #media_fields_div {
-	width : 338px;
+	width : 300px;
 	height : 50px;
 	margin-top: 10px 0px;
 	padding-left: 10px;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.chunked_uploads.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,229 @@
+$(function () {
+	'use strict';
+	var is_canceled=false;
+	var is_paused=false;
+	var data_resume = null;
+	var chunked_uploads_jqXHR = null;
+	$('#start_upload').attr('disabled', true);
+	$('#pause_upload').attr('disabled', true);
+	$('#resume_upload').attr('disabled', true);
+	$('#cancel_upload').attr('disabled', true);
+	
+	//authentication send in the header, to log with ApiKey
+	var authentication = {
+			username: chunked_uploads_endpoints.username,
+			api_key: chunked_uploads_endpoints.api_key
+	}
+	
+	$('#fileupload').fileupload({        
+        url: chunked_uploads_endpoints.upload_url,
+	   	dataType: 'json',
+	   	maxChunkSize: 1048576,
+	    singleFileUploads: false,
+	   	maxNumberOfFiles: 1,
+	    multipart: false,
+	    xhrFields: {
+	        withCredentials: true
+	    },
+	    headers: authentication,
+	    add: function (e, data) {
+	    	//Get json from server to know if and upload has already been started
+	    	$.ajax({
+	    		type: "GET",
+	    		dataType: "json",
+	    		data: authentication,
+	    		url: chunked_uploads_endpoints.upload_url,
+	    		xhrFields: {withCredentials: true},
+	    		success: function(current_upload){
+	    			//If not null then an upload has already been started
+	    			if (current_upload != ""){
+	    				//checks if the file selected match with the file currently uploaded on the server 
+	    				if (data.files[0].size == current_upload[0].total_size){
+	    					//the file match, so we'll resume the upload from uploadedBytes
+	    					data.uploadedBytes = current_upload[0].size;
+	    				}
+	    				else{
+	    					//The file doesn't match, so delete the current upload on the server
+	    					$.ajax({
+			    	    		type: current_upload[0].delete_type,
+			    	    		url: current_upload[0].delete_url,
+			    	    		data: authentication,
+			    		  });
+	    				}
+	    			}
+	    		},
+	    	});
+	    	
+	    	$('#start_upload').attr('disabled', false);
+	    	$('#start_upload').one('click', function (e) {
+	            e.preventDefault();
+	            chunked_uploads_jqXHR = data.submit();
+	        });
+	    },
+	    start: function (e, data){
+	    	if (typeof chunked_uploads_start === "function") {
+	    		chunked_uploads_start();
+			}
+	    },
+	    done: function (e, data) {
+	        $('#start_upload').hide();
+    		$('#pause_upload').hide();
+    		$('#resume_upload').hide();
+    		$('#cancel_upload').hide();
+    		
+	    	chunked_uploads_endpoints.done_url = chunked_uploads_endpoints.done_url.replace('00000000-0000-0000-0000-000000000000', data.result[0].upload_uuid);
+	    	//send COMPLETE request
+	    	$.ajax({
+	    		type: "POST",
+	    		dataType: "json",
+	    		headers: authentication,
+	    		url: chunked_uploads_endpoints.done_url,
+	    		xhrFields: {withCredentials: true},
+	    		success: function(current_upload){
+	    			//error callback if upload state is FAIL
+	    			if (current_upload[0].state=="FAIL"){
+	    				if (typeof chunked_uploads_error === "function") {
+	    					chunked_uploads_error();
+		    			}
+	    			}
+	    			//complete and video_url callback if success
+	    			else{
+	    				if (typeof chunked_uploads_complete === "function") {
+	    					chunked_uploads_complete();
+		    			}
+	    				if (typeof chunked_uploads_video_url === "function") {
+		    				chunked_uploads_video_url(current_upload[0].video_url);
+		    			}
+	    			}
+	    		},
+	    		error: function(){
+	    			if (typeof chunked_uploads_error === "function") {
+    					chunked_uploads_error();
+	    			}
+	    		}
+			});    		
+	    },
+	    progressall: function (e, data) {
+	    	var progress = parseInt(data.loaded / data.total * 100, 10);
+	        $('.progress').css(
+	            'width',
+	            progress + '%'
+	        );
+    	},
+	    send: function(e, data) {
+	    	$("#fileupload").hide();
+	    	$('#start_upload').hide();
+            $('#pause_upload').attr('disabled', false);
+        	$('#cancel_upload').attr('disabled', false);
+	    },
+	    fail: function(e, data) {
+	    	data_resume = data;
+	    },
+    });
+	   
+    $('#cancel_upload').click(function (e) {
+    	if (!is_paused){
+    		//if cancel is clicked during the upload
+			is_canceled = true;
+			//stop the download
+			chunked_uploads_jqXHR.abort();
+	    	$('#pause_upload').attr('disabled', true);
+			$('#resume_upload').attr('disabled', true);
+			//wait 1s before sending a DELETE request to the server
+			setTimeout(function() {
+				$.ajax({
+					  dataType: "json",
+					  url: chunked_uploads_endpoints.upload_url,
+					  data: authentication,
+					  xhrFields: {withCredentials: true},
+					  success: function(current_upload){
+						  $.ajax({
+			    	    		type: current_upload[0].delete_type,
+			    	    		url: current_upload[0].delete_url,
+			    	    		data: authentication,
+			    		  });
+					  }
+				});
+			},1000);
+		}
+		else{
+			//if cancel is clicked while pause we send the DELETE request
+			$.ajax({
+				  dataType: "json",
+				  url: chunked_uploads_endpoints.upload_url,
+				  xhrFields: {withCredentials: true},
+				  data: authentication,
+				  success: function(current_upload){
+					  $.ajax({
+		    	    		type: current_upload[0].delete_type,
+		    	    		url: current_upload[0].delete_url,
+		    	    		headers: authentication,
+		    		  });
+				  }
+			});
+		}
+	});
+	
+	$('#pause_upload').click(function (e) {
+		is_paused = true;
+		chunked_uploads_jqXHR.abort();
+		if (typeof chunked_uploads_stop === "function") {
+			chunked_uploads_stop();
+		}		
+		$('#pause_upload').attr('disabled', true);
+		$('#resume_upload').attr('disabled', false);
+	});
+	
+	$('#resume_upload').click(function (e) {
+		is_paused = false;
+		$.ajax({
+			  dataType: "json",
+			  url: chunked_uploads_endpoints.upload_url,
+			  data: authentication,
+			  xhrFields: {withCredentials: true},
+			  success: function(current_upload){
+				  data_resume.uploadedBytes = current_upload[0].size;
+				  chunked_uploads_jqXHR = data_resume.submit();
+			  }
+		});
+		if (typeof chunked_uploads_start === "function") {
+    		chunked_uploads_start();
+		}
+		$('#resume_upload').attr('disabled', true);
+		$('#pause_upload').attr('disabled', false);
+	});
+	
+	//connection listener
+	if ('onLine' in navigator) {
+        //event listener on connection found
+		window.addEventListener('online', function(){
+    		is_paused = false;
+    		$.ajax({
+    			  dataType: "json",
+    			  url: chunked_uploads_endpoints.upload_url,
+    			  data: authentication,
+    			  xhrFields: {withCredentials: true},
+    			  success: function(current_upload){
+    				  data_resume.uploadedBytes = current_upload[0].size;
+    				  chunked_uploads_jqXHR = data_resume.submit();
+    			  }
+    		});
+    		if (typeof chunked_uploads_start === "function") {
+	    		chunked_uploads_start();
+			}
+    		$('#resume_upload').attr('disabled', true);
+    		$('#pause_upload').attr('disabled', false);
+        });
+        
+		//event listener on connection lost
+        window.addEventListener('offline', function(){
+        	if (chunked_uploads_jqXHR != null){
+	    		is_paused = true;
+	    		$('#pause_upload').attr('disabled', true);
+	    		$('#resume_upload').attr('disabled', true);
+	    		chunked_uploads_jqXHR.abort();
+        	}
+        });
+    };
+	
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.fileupload.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,1113 @@
+/*
+ * jQuery File Upload Plugin 5.19.7
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint nomen: true, unparam: true, regexp: true */
+/*global define, window, document, File, Blob, FormData, location */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define([
+            'jquery',
+            'jquery.ui.widget'
+        ], factory);
+    } else {
+        // Browser globals:
+        factory(window.jQuery);
+    }
+}(function ($) {
+    'use strict';
+
+    // The FileReader API is not actually used, but works as feature detection,
+    // as e.g. Safari supports XHR file uploads via the FormData API,
+    // but not non-multipart XHR file uploads:
+    $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
+    $.support.xhrFormDataFileUpload = !!window.FormData;
+
+    // The fileupload widget listens for change events on file input fields defined
+    // via fileInput setting and paste or drop events of the given dropZone.
+    // In addition to the default jQuery Widget methods, the fileupload widget
+    // exposes the "add" and "send" methods, to add or directly send files using
+    // the fileupload API.
+    // By default, files added via file input selection, paste, drag & drop or
+    // "add" method are uploaded immediately, but it is possible to override
+    // the "add" callback option to queue file uploads.
+    $.widget('blueimp.fileupload', {
+
+        options: {
+            // The drop target element(s), by the default the complete document.
+            // Set to null to disable drag & drop support:
+            dropZone: $(document),
+            // The paste target element(s), by the default the complete document.
+            // Set to null to disable paste support:
+            pasteZone: $(document),
+            // The file input field(s), that are listened to for change events.
+            // If undefined, it is set to the file input fields inside
+            // of the widget element on plugin initialization.
+            // Set to null to disable the change listener.
+            fileInput: undefined,
+            // By default, the file input field is replaced with a clone after
+            // each input field change event. This is required for iframe transport
+            // queues and allows change events to be fired for the same file
+            // selection, but can be disabled by setting the following option to false:
+            replaceFileInput: true,
+            // The parameter name for the file form data (the request argument name).
+            // If undefined or empty, the name property of the file input field is
+            // used, or "files[]" if the file input name property is also empty,
+            // can be a string or an array of strings:
+            paramName: undefined,
+            // By default, each file of a selection is uploaded using an individual
+            // request for XHR type uploads. Set to false to upload file
+            // selections in one request each:
+            singleFileUploads: true,
+            // To limit the number of files uploaded with one XHR request,
+            // set the following option to an integer greater than 0:
+            limitMultiFileUploads: undefined,
+            // Set the following option to true to issue all file upload requests
+            // in a sequential order:
+            sequentialUploads: false,
+            // To limit the number of concurrent uploads,
+            // set the following option to an integer greater than 0:
+            limitConcurrentUploads: undefined,
+            // Set the following option to true to force iframe transport uploads:
+            forceIframeTransport: false,
+            // Set the following option to the location of a redirect url on the
+            // origin server, for cross-domain iframe transport uploads:
+            redirect: undefined,
+            // The parameter name for the redirect url, sent as part of the form
+            // data and set to 'redirect' if this option is empty:
+            redirectParamName: undefined,
+            // Set the following option to the location of a postMessage window,
+            // to enable postMessage transport uploads:
+            postMessage: undefined,
+            // By default, XHR file uploads are sent as multipart/form-data.
+            // The iframe transport is always using multipart/form-data.
+            // Set to false to enable non-multipart XHR uploads:
+            multipart: true,
+            // To upload large files in smaller chunks, set the following option
+            // to a preferred maximum chunk size. If set to 0, null or undefined,
+            // or the browser does not support the required Blob API, files will
+            // be uploaded as a whole.
+            maxChunkSize: undefined,
+            // When a non-multipart upload or a chunked multipart upload has been
+            // aborted, this option can be used to resume the upload by setting
+            // it to the size of the already uploaded bytes. This option is most
+            // useful when modifying the options object inside of the "add" or
+            // "send" callbacks, as the options are cloned for each file upload.
+            uploadedBytes: undefined,
+            // By default, failed (abort or error) file uploads are removed from the
+            // global progress calculation. Set the following option to false to
+            // prevent recalculating the global progress data:
+            recalculateProgress: true,
+            // Interval in milliseconds to calculate and trigger progress events:
+            progressInterval: 100,
+            // Interval in milliseconds to calculate progress bitrate:
+            bitrateInterval: 500,
+
+            // Additional form data to be sent along with the file uploads can be set
+            // using this option, which accepts an array of objects with name and
+            // value properties, a function returning such an array, a FormData
+            // object (for XHR file uploads), or a simple object.
+            // The form of the first fileInput is given as parameter to the function:
+            formData: function (form) {
+                return form.serializeArray();
+            },
+
+            // The add callback is invoked as soon as files are added to the fileupload
+            // widget (via file input selection, drag & drop, paste or add API call).
+            // If the singleFileUploads option is enabled, this callback will be
+            // called once for each file in the selection for XHR file uplaods, else
+            // once for each file selection.
+            // The upload starts when the submit method is invoked on the data parameter.
+            // The data object contains a files property holding the added files
+            // and allows to override plugin options as well as define ajax settings.
+            // Listeners for this callback can also be bound the following way:
+            // .bind('fileuploadadd', func);
+            // data.submit() returns a Promise object and allows to attach additional
+            // handlers using jQuery's Deferred callbacks:
+            // data.submit().done(func).fail(func).always(func);
+            add: function (e, data) {
+                data.submit();
+            },
+
+            // Other callbacks:
+            // Callback for the submit event of each file upload:
+            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+            // Callback for the start of each file upload request:
+            // send: function (e, data) {}, // .bind('fileuploadsend', func);
+            // Callback for successful uploads:
+            // done: function (e, data) {}, // .bind('fileuploaddone', func);
+            // Callback for failed (abort or error) uploads:
+            // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+            // Callback for completed (success, abort or error) requests:
+            // always: function (e, data) {}, // .bind('fileuploadalways', func);
+            // Callback for upload progress events:
+            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+            // Callback for global upload progress events:
+            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+            // Callback for uploads start, equivalent to the global ajaxStart event:
+            // start: function (e) {}, // .bind('fileuploadstart', func);
+            // Callback for uploads stop, equivalent to the global ajaxStop event:
+            // stop: function (e) {}, // .bind('fileuploadstop', func);
+            // Callback for change events of the fileInput(s):
+            // change: function (e, data) {}, // .bind('fileuploadchange', func);
+            // Callback for paste events to the pasteZone(s):
+            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+            // Callback for drop events of the dropZone(s):
+            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+            // Callback for dragover events of the dropZone(s):
+            // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+            // The plugin options are used as settings object for the ajax calls.
+            // The following are jQuery ajax settings required for the file uploads:
+            processData: false,
+            contentType: false,
+            cache: false
+        },
+
+        // A list of options that require a refresh after assigning a new value:
+        _refreshOptionsList: [
+            'fileInput',
+            'dropZone',
+            'pasteZone',
+            'multipart',
+            'forceIframeTransport'
+        ],
+
+        _BitrateTimer: function () {
+            this.timestamp = +(new Date());
+            this.loaded = 0;
+            this.bitrate = 0;
+            this.getBitrate = function (now, loaded, interval) {
+                var timeDiff = now - this.timestamp;
+                if (!this.bitrate || !interval || timeDiff > interval) {
+                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+                    this.loaded = loaded;
+                    this.timestamp = now;
+                }
+                return this.bitrate;
+            };
+        },
+
+        _isXHRUpload: function (options) {
+            return !options.forceIframeTransport &&
+                ((!options.multipart && $.support.xhrFileUpload) ||
+                $.support.xhrFormDataFileUpload);
+        },
+
+        _getFormData: function (options) {
+            var formData;
+            if (typeof options.formData === 'function') {
+                return options.formData(options.form);
+            }
+            if ($.isArray(options.formData)) {
+                return options.formData;
+            }
+            if (options.formData) {
+                formData = [];
+                $.each(options.formData, function (name, value) {
+                    formData.push({name: name, value: value});
+                });
+                return formData;
+            }
+            return [];
+        },
+
+        _getTotal: function (files) {
+            var total = 0;
+            $.each(files, function (index, file) {
+                total += file.size || 1;
+            });
+            return total;
+        },
+
+        _onProgress: function (e, data) {
+            if (e.lengthComputable) {
+                var now = +(new Date()),
+                    total,
+                    loaded;
+                if (data._time && data.progressInterval &&
+                        (now - data._time < data.progressInterval) &&
+                        e.loaded !== e.total) {
+                    return;
+                }
+                data._time = now;
+                total = data.total || this._getTotal(data.files);
+                loaded = parseInt(
+                    e.loaded / e.total * (data.chunkSize || total),
+                    10
+                ) + (data.uploadedBytes || 0);
+                this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
+                data.lengthComputable = true;
+                data.loaded = loaded;
+                data.total = total;
+                data.bitrate = data._bitrateTimer.getBitrate(
+                    now,
+                    loaded,
+                    data.bitrateInterval
+                );
+                // Trigger a custom progress event with a total data property set
+                // to the file size(s) of the current upload and a loaded data
+                // property calculated accordingly:
+                this._trigger('progress', e, data);
+                // Trigger a global progress event for all current file uploads,
+                // including ajax calls queued for sequential file uploads:
+                this._trigger('progressall', e, {
+                    lengthComputable: true,
+                    loaded: this._loaded,
+                    total: this._total,
+                    bitrate: this._bitrateTimer.getBitrate(
+                        now,
+                        this._loaded,
+                        data.bitrateInterval
+                    )
+                });
+            }
+        },
+
+        _initProgressListener: function (options) {
+            var that = this,
+                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+            // Accesss to the native XHR object is required to add event listeners
+            // for the upload progress event:
+            if (xhr.upload) {
+                $(xhr.upload).bind('progress', function (e) {
+                    var oe = e.originalEvent;
+                    // Make sure the progress event properties get copied over:
+                    e.lengthComputable = oe.lengthComputable;
+                    e.loaded = oe.loaded;
+                    e.total = oe.total;
+                    that._onProgress(e, options);
+                });
+                options.xhr = function () {
+                    return xhr;
+                };
+            }
+        },
+
+        _initXHRData: function (options) {
+            var formData,
+                file = options.files[0],
+                // Ignore non-multipart setting if not supported:
+                multipart = options.multipart || !$.support.xhrFileUpload,
+                paramName = options.paramName[0];
+            options.headers = options.headers || {};
+            if (options.contentRange) {
+                options.headers['Content-Range'] = options.contentRange;
+            }
+            if (!multipart) {
+                options.headers['Content-Disposition'] = 'attachment; filename="' +
+                    encodeURI(file.name) + '"';
+                options.contentType = file.type;
+                options.data = options.blob || file;
+            } else if ($.support.xhrFormDataFileUpload) {
+                if (options.postMessage) {
+                    // window.postMessage does not allow sending FormData
+                    // objects, so we just add the File/Blob objects to
+                    // the formData array and let the postMessage window
+                    // create the FormData object out of this array:
+                    formData = this._getFormData(options);
+                    if (options.blob) {
+                        formData.push({
+                            name: paramName,
+                            value: options.blob
+                        });
+                    } else {
+                        $.each(options.files, function (index, file) {
+                            formData.push({
+                                name: options.paramName[index] || paramName,
+                                value: file
+                            });
+                        });
+                    }
+                } else {
+                    if (options.formData instanceof FormData) {
+                        formData = options.formData;
+                    } else {
+                        formData = new FormData();
+                        $.each(this._getFormData(options), function (index, field) {
+                            formData.append(field.name, field.value);
+                        });
+                    }
+                    if (options.blob) {
+                        options.headers['Content-Disposition'] = 'attachment; filename="' +
+                            encodeURI(file.name) + '"';
+                        formData.append(paramName, options.blob, file.name);
+                    } else {
+                        $.each(options.files, function (index, file) {
+                            // Files are also Blob instances, but some browsers
+                            // (Firefox 3.6) support the File API but not Blobs.
+                            // This check allows the tests to run with
+                            // dummy objects:
+                            if ((window.Blob && file instanceof Blob) ||
+                                    (window.File && file instanceof File)) {
+                                formData.append(
+                                    options.paramName[index] || paramName,
+                                    file,
+                                    file.name
+                                );
+                            }
+                        });
+                    }
+                }
+                options.data = formData;
+            }
+            // Blob reference is not needed anymore, free memory:
+            options.blob = null;
+        },
+
+        _initIframeSettings: function (options) {
+            // Setting the dataType to iframe enables the iframe transport:
+            options.dataType = 'iframe ' + (options.dataType || '');
+            // The iframe transport accepts a serialized array as form data:
+            options.formData = this._getFormData(options);
+            // Add redirect url to form data on cross-domain uploads:
+            if (options.redirect && $('<a></a>').prop('href', options.url)
+                    .prop('host') !== location.host) {
+                options.formData.push({
+                    name: options.redirectParamName || 'redirect',
+                    value: options.redirect
+                });
+            }
+        },
+
+        _initDataSettings: function (options) {
+            if (this._isXHRUpload(options)) {
+                if (!this._chunkedUpload(options, true)) {
+                    if (!options.data) {
+                        this._initXHRData(options);
+                    }
+                    this._initProgressListener(options);
+                }
+                if (options.postMessage) {
+                    // Setting the dataType to postmessage enables the
+                    // postMessage transport:
+                    options.dataType = 'postmessage ' + (options.dataType || '');
+                }
+            } else {
+                this._initIframeSettings(options, 'iframe');
+            }
+        },
+
+        _getParamName: function (options) {
+            var fileInput = $(options.fileInput),
+                paramName = options.paramName;
+            if (!paramName) {
+                paramName = [];
+                fileInput.each(function () {
+                    var input = $(this),
+                        name = input.prop('name') || 'files[]',
+                        i = (input.prop('files') || [1]).length;
+                    while (i) {
+                        paramName.push(name);
+                        i -= 1;
+                    }
+                });
+                if (!paramName.length) {
+                    paramName = [fileInput.prop('name') || 'files[]'];
+                }
+            } else if (!$.isArray(paramName)) {
+                paramName = [paramName];
+            }
+            return paramName;
+        },
+
+        _initFormSettings: function (options) {
+            // Retrieve missing options from the input field and the
+            // associated form, if available:
+            if (!options.form || !options.form.length) {
+                options.form = $(options.fileInput.prop('form'));
+                // If the given file input doesn't have an associated form,
+                // use the default widget file input's form:
+                if (!options.form.length) {
+                    options.form = $(this.options.fileInput.prop('form'));
+                }
+            }
+            options.paramName = this._getParamName(options);
+            if (!options.url) {
+                options.url = options.form.prop('action') || location.href;
+            }
+            // The HTTP request method must be "POST" or "PUT":
+            options.type = (options.type || options.form.prop('method') || '')
+                .toUpperCase();
+            if (options.type !== 'POST' && options.type !== 'PUT') {
+                options.type = 'POST';
+            }
+            if (!options.formAcceptCharset) {
+                options.formAcceptCharset = options.form.attr('accept-charset');
+            }
+        },
+
+        _getAJAXSettings: function (data) {
+            var options = $.extend({}, this.options, data);
+            this._initFormSettings(options);
+            this._initDataSettings(options);
+            return options;
+        },
+
+        // Maps jqXHR callbacks to the equivalent
+        // methods of the given Promise object:
+        _enhancePromise: function (promise) {
+            promise.success = promise.done;
+            promise.error = promise.fail;
+            promise.complete = promise.always;
+            return promise;
+        },
+
+        // Creates and returns a Promise object enhanced with
+        // the jqXHR methods abort, success, error and complete:
+        _getXHRPromise: function (resolveOrReject, context, args) {
+            var dfd = $.Deferred(),
+                promise = dfd.promise();
+            context = context || this.options.context || promise;
+            if (resolveOrReject === true) {
+                dfd.resolveWith(context, args);
+            } else if (resolveOrReject === false) {
+                dfd.rejectWith(context, args);
+            }
+            promise.abort = dfd.promise;
+            return this._enhancePromise(promise);
+        },
+
+        // Parses the Range header from the server response
+        // and returns the uploaded bytes:
+        _getUploadedBytes: function (jqXHR) {
+            var range = jqXHR.getResponseHeader('Range'),
+                parts = range && range.split('-'),
+                upperBytesPos = parts && parts.length > 1 &&
+                    parseInt(parts[1], 10);
+            return upperBytesPos && upperBytesPos + 1;
+        },
+
+        // Uploads a file in multiple, sequential requests
+        // by splitting the file up in multiple blob chunks.
+        // If the second parameter is true, only tests if the file
+        // should be uploaded in chunks, but does not invoke any
+        // upload requests:
+        _chunkedUpload: function (options, testOnly) {
+            var that = this,
+                file = options.files[0],
+                fs = file.size,
+                ub = options.uploadedBytes = options.uploadedBytes || 0,
+                mcs = options.maxChunkSize || fs,
+                slice = file.slice || file.webkitSlice || file.mozSlice,
+                dfd = $.Deferred(),
+                promise = dfd.promise(),
+                jqXHR,
+                upload;
+            if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
+                    options.data) {
+                return false;
+            }
+            if (testOnly) {
+                return true;
+            }
+            if (ub >= fs) {
+                file.error = 'Uploaded bytes exceed file size';
+                return this._getXHRPromise(
+                    false,
+                    options.context,
+                    [null, 'error', file.error]
+                );
+            }
+            // The chunk upload method:
+            upload = function (i) {
+                // Clone the options object for each chunk upload:
+                var o = $.extend({}, options);
+                o.blob = slice.call(
+                    file,
+                    ub,
+                    ub + mcs,
+                    file.type
+                );
+                // Store the current chunk size, as the blob itself
+                // will be dereferenced after data processing:
+                o.chunkSize = o.blob.size;
+                // Expose the chunk bytes position range:
+                o.contentRange = 'bytes ' + ub + '-' +
+                    (ub + o.chunkSize - 1) + '/' + fs;
+                // Process the upload data (the blob and potential form data):
+                that._initXHRData(o);
+                // Add progress listeners for this chunk upload:
+                that._initProgressListener(o);
+                jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
+                    .done(function (result, textStatus, jqXHR) {
+                        ub = that._getUploadedBytes(jqXHR) ||
+                            (ub + o.chunkSize);
+                        // Create a progress event if upload is done and
+                        // no progress event has been invoked for this chunk:
+                        if (!o.loaded) {
+                            that._onProgress($.Event('progress', {
+                                lengthComputable: true,
+                                loaded: ub - o.uploadedBytes,
+                                total: ub - o.uploadedBytes
+                            }), o);
+                        }
+                        options.uploadedBytes = o.uploadedBytes = ub;
+                        if (ub < fs) {
+                            // File upload not yet complete,
+                            // continue with the next chunk:
+                            upload();
+                        } else {
+                            dfd.resolveWith(
+                                o.context,
+                                [result, textStatus, jqXHR]
+                            );
+                        }
+                    })
+                    .fail(function (jqXHR, textStatus, errorThrown) {
+                        dfd.rejectWith(
+                            o.context,
+                            [jqXHR, textStatus, errorThrown]
+                        );
+                    });
+            };
+            this._enhancePromise(promise);
+            promise.abort = function () {
+                return jqXHR.abort();
+            };
+            upload();
+            return promise;
+        },
+
+        _beforeSend: function (e, data) {
+            if (this._active === 0) {
+                // the start callback is triggered when an upload starts
+                // and no other uploads are currently running,
+                // equivalent to the global ajaxStart event:
+                this._trigger('start');
+                // Set timer for global bitrate progress calculation:
+                this._bitrateTimer = new this._BitrateTimer();
+            }
+            this._active += 1;
+            // Initialize the global progress values:
+            this._loaded += data.uploadedBytes || 0;
+            this._total += this._getTotal(data.files);
+        },
+
+        _onDone: function (result, textStatus, jqXHR, options) {
+            if (!this._isXHRUpload(options)) {
+                // Create a progress event for each iframe load:
+                this._onProgress($.Event('progress', {
+                    lengthComputable: true,
+                    loaded: 1,
+                    total: 1
+                }), options);
+            }
+            options.result = result;
+            options.textStatus = textStatus;
+            options.jqXHR = jqXHR;
+            this._trigger('done', null, options);
+        },
+
+        _onFail: function (jqXHR, textStatus, errorThrown, options) {
+            options.jqXHR = jqXHR;
+            options.textStatus = textStatus;
+            options.errorThrown = errorThrown;
+            this._trigger('fail', null, options);
+            if (options.recalculateProgress) {
+                // Remove the failed (error or abort) file upload from
+                // the global progress calculation:
+                this._loaded -= options.loaded || options.uploadedBytes || 0;
+                this._total -= options.total || this._getTotal(options.files);
+            }
+        },
+
+        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+            this._active -= 1;
+            options.textStatus = textStatus;
+            if (jqXHRorError && jqXHRorError.always) {
+                options.jqXHR = jqXHRorError;
+                options.result = jqXHRorResult;
+            } else {
+                options.jqXHR = jqXHRorResult;
+                options.errorThrown = jqXHRorError;
+            }
+            this._trigger('always', null, options);
+            if (this._active === 0) {
+                // The stop callback is triggered when all uploads have
+                // been completed, equivalent to the global ajaxStop event:
+                this._trigger('stop');
+                // Reset the global progress values:
+                this._loaded = this._total = 0;
+                this._bitrateTimer = null;
+            }
+        },
+
+        _onSend: function (e, data) {
+            var that = this,
+                jqXHR,
+                aborted,
+                slot,
+                pipe,
+                options = that._getAJAXSettings(data),
+                send = function () {
+                    that._sending += 1;
+                    // Set timer for bitrate progress calculation:
+                    options._bitrateTimer = new that._BitrateTimer();
+                    jqXHR = jqXHR || (
+                        ((aborted || that._trigger('send', e, options) === false) &&
+                        that._getXHRPromise(false, options.context, aborted)) ||
+                        that._chunkedUpload(options) || $.ajax(options)
+                    ).done(function (result, textStatus, jqXHR) {
+                        that._onDone(result, textStatus, jqXHR, options);
+                    }).fail(function (jqXHR, textStatus, errorThrown) {
+                        that._onFail(jqXHR, textStatus, errorThrown, options);
+                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+                        that._sending -= 1;
+                        that._onAlways(
+                            jqXHRorResult,
+                            textStatus,
+                            jqXHRorError,
+                            options
+                        );
+                        if (options.limitConcurrentUploads &&
+                                options.limitConcurrentUploads > that._sending) {
+                            // Start the next queued upload,
+                            // that has not been aborted:
+                            var nextSlot = that._slots.shift(),
+                                isPending;
+                            while (nextSlot) {
+                                // jQuery 1.6 doesn't provide .state(),
+                                // while jQuery 1.8+ removed .isRejected():
+                                isPending = nextSlot.state ?
+                                        nextSlot.state() === 'pending' :
+                                        !nextSlot.isRejected();
+                                if (isPending) {
+                                    nextSlot.resolve();
+                                    break;
+                                }
+                                nextSlot = that._slots.shift();
+                            }
+                        }
+                    });
+                    return jqXHR;
+                };
+            this._beforeSend(e, options);
+            if (this.options.sequentialUploads ||
+                    (this.options.limitConcurrentUploads &&
+                    this.options.limitConcurrentUploads <= this._sending)) {
+                if (this.options.limitConcurrentUploads > 1) {
+                    slot = $.Deferred();
+                    this._slots.push(slot);
+                    pipe = slot.pipe(send);
+                } else {
+                    pipe = (this._sequence = this._sequence.pipe(send, send));
+                }
+                // Return the piped Promise object, enhanced with an abort method,
+                // which is delegated to the jqXHR object of the current upload,
+                // and jqXHR callbacks mapped to the equivalent Promise methods:
+                pipe.abort = function () {
+                    aborted = [undefined, 'abort', 'abort'];
+                    if (!jqXHR) {
+                        if (slot) {
+                            slot.rejectWith(options.context, aborted);
+                        }
+                        return send();
+                    }
+                    return jqXHR.abort();
+                };
+                return this._enhancePromise(pipe);
+            }
+            return send();
+        },
+
+        _onAdd: function (e, data) {
+            var that = this,
+                result = true,
+                options = $.extend({}, this.options, data),
+                limit = options.limitMultiFileUploads,
+                paramName = this._getParamName(options),
+                paramNameSet,
+                paramNameSlice,
+                fileSet,
+                i;
+            if (!(options.singleFileUploads || limit) ||
+                    !this._isXHRUpload(options)) {
+                fileSet = [data.files];
+                paramNameSet = [paramName];
+            } else if (!options.singleFileUploads && limit) {
+                fileSet = [];
+                paramNameSet = [];
+                for (i = 0; i < data.files.length; i += limit) {
+                    fileSet.push(data.files.slice(i, i + limit));
+                    paramNameSlice = paramName.slice(i, i + limit);
+                    if (!paramNameSlice.length) {
+                        paramNameSlice = paramName;
+                    }
+                    paramNameSet.push(paramNameSlice);
+                }
+            } else {
+                paramNameSet = paramName;
+            }
+            data.originalFiles = data.files;
+            $.each(fileSet || data.files, function (index, element) {
+                var newData = $.extend({}, data);
+                newData.files = fileSet ? element : [element];
+                newData.paramName = paramNameSet[index];
+                newData.submit = function () {
+                    newData.jqXHR = this.jqXHR =
+                        (that._trigger('submit', e, this) !== false) &&
+                        that._onSend(e, this);
+                    return this.jqXHR;
+                };
+                result = that._trigger('add', e, newData);
+                return result;
+            });
+            return result;
+        },
+
+        _replaceFileInput: function (input) {
+            var inputClone = input.clone(true);
+            $('<form></form>').append(inputClone)[0].reset();
+            // Detaching allows to insert the fileInput on another form
+            // without loosing the file input value:
+            input.after(inputClone).detach();
+            // Avoid memory leaks with the detached file input:
+            $.cleanData(input.unbind('remove'));
+            // Replace the original file input element in the fileInput
+            // elements set with the clone, which has been copied including
+            // event handlers:
+            this.options.fileInput = this.options.fileInput.map(function (i, el) {
+                if (el === input[0]) {
+                    return inputClone[0];
+                }
+                return el;
+            });
+            // If the widget has been initialized on the file input itself,
+            // override this.element with the file input clone:
+            if (input[0] === this.element[0]) {
+                this.element = inputClone;
+            }
+        },
+
+        _handleFileTreeEntry: function (entry, path) {
+            var that = this,
+                dfd = $.Deferred(),
+                errorHandler = function (e) {
+                    if (e && !e.entry) {
+                        e.entry = entry;
+                    }
+                    // Since $.when returns immediately if one
+                    // Deferred is rejected, we use resolve instead.
+                    // This allows valid files and invalid items
+                    // to be returned together in one set:
+                    dfd.resolve([e]);
+                },
+                dirReader;
+            path = path || '';
+            if (entry.isFile) {
+                if (entry._file) {
+                    // Workaround for Chrome bug #149735
+                    entry._file.relativePath = path;
+                    dfd.resolve(entry._file);
+                } else {
+                    entry.file(function (file) {
+                        file.relativePath = path;
+                        dfd.resolve(file);
+                    }, errorHandler);
+                }
+            } else if (entry.isDirectory) {
+                dirReader = entry.createReader();
+                dirReader.readEntries(function (entries) {
+                    that._handleFileTreeEntries(
+                        entries,
+                        path + entry.name + '/'
+                    ).done(function (files) {
+                        dfd.resolve(files);
+                    }).fail(errorHandler);
+                }, errorHandler);
+            } else {
+                // Return an empy list for file system items
+                // other than files or directories:
+                dfd.resolve([]);
+            }
+            return dfd.promise();
+        },
+
+        _handleFileTreeEntries: function (entries, path) {
+            var that = this;
+            return $.when.apply(
+                $,
+                $.map(entries, function (entry) {
+                    return that._handleFileTreeEntry(entry, path);
+                })
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _getDroppedFiles: function (dataTransfer) {
+            dataTransfer = dataTransfer || {};
+            var items = dataTransfer.items;
+            if (items && items.length && (items[0].webkitGetAsEntry ||
+                    items[0].getAsEntry)) {
+                return this._handleFileTreeEntries(
+                    $.map(items, function (item) {
+                        var entry;
+                        if (item.webkitGetAsEntry) {
+                            entry = item.webkitGetAsEntry();
+                            if (entry) {
+                                // Workaround for Chrome bug #149735:
+                                entry._file = item.getAsFile();
+                            }
+                            return entry;
+                        }
+                        return item.getAsEntry();
+                    })
+                );
+            }
+            return $.Deferred().resolve(
+                $.makeArray(dataTransfer.files)
+            ).promise();
+        },
+
+        _getSingleFileInputFiles: function (fileInput) {
+            fileInput = $(fileInput);
+            var entries = fileInput.prop('webkitEntries') ||
+                    fileInput.prop('entries'),
+                files,
+                value;
+            if (entries && entries.length) {
+                return this._handleFileTreeEntries(entries);
+            }
+            files = $.makeArray(fileInput.prop('files'));
+            if (!files.length) {
+                value = fileInput.prop('value');
+                if (!value) {
+                    return $.Deferred().resolve([]).promise();
+                }
+                // If the files property is not available, the browser does not
+                // support the File API and we add a pseudo File object with
+                // the input value as name with path information removed:
+                files = [{name: value.replace(/^.*\\/, '')}];
+            } else if (files[0].name === undefined && files[0].fileName) {
+                // File normalization for Safari 4 and Firefox 3:
+                $.each(files, function (index, file) {
+                    file.name = file.fileName;
+                    file.size = file.fileSize;
+                });
+            }
+            return $.Deferred().resolve(files).promise();
+        },
+
+        _getFileInputFiles: function (fileInput) {
+            if (!(fileInput instanceof $) || fileInput.length === 1) {
+                return this._getSingleFileInputFiles(fileInput);
+            }
+            return $.when.apply(
+                $,
+                $.map(fileInput, this._getSingleFileInputFiles)
+            ).pipe(function () {
+                return Array.prototype.concat.apply(
+                    [],
+                    arguments
+                );
+            });
+        },
+
+        _onChange: function (e) {
+            var that = this,
+                data = {
+                    fileInput: $(e.target),
+                    form: $(e.target.form)
+                };
+            this._getFileInputFiles(data.fileInput).always(function (files) {
+                data.files = files;
+                if (that.options.replaceFileInput) {
+                    that._replaceFileInput(data.fileInput);
+                }
+                if (that._trigger('change', e, data) !== false) {
+                    that._onAdd(e, data);
+                }
+            });
+        },
+
+        _onPaste: function (e) {
+            var cbd = e.originalEvent.clipboardData,
+                items = (cbd && cbd.items) || [],
+                data = {files: []};
+            $.each(items, function (index, item) {
+                var file = item.getAsFile && item.getAsFile();
+                if (file) {
+                    data.files.push(file);
+                }
+            });
+            if (this._trigger('paste', e, data) === false ||
+                    this._onAdd(e, data) === false) {
+                return false;
+            }
+        },
+
+        _onDrop: function (e) {
+            var that = this,
+                dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
+                data = {};
+            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+                e.preventDefault();
+            }
+            this._getDroppedFiles(dataTransfer).always(function (files) {
+                data.files = files;
+                if (that._trigger('drop', e, data) !== false) {
+                    that._onAdd(e, data);
+                }
+            });
+        },
+
+        _onDragOver: function (e) {
+            var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
+            if (this._trigger('dragover', e) === false) {
+                return false;
+            }
+            if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
+                dataTransfer.dropEffect = 'copy';
+                e.preventDefault();
+            }
+        },
+
+        _initEventHandlers: function () {
+            if (this._isXHRUpload(this.options)) {
+                this._on(this.options.dropZone, {
+                    dragover: this._onDragOver,
+                    drop: this._onDrop
+                });
+                this._on(this.options.pasteZone, {
+                    paste: this._onPaste
+                });
+            }
+            this._on(this.options.fileInput, {
+                change: this._onChange
+            });
+        },
+
+        _destroyEventHandlers: function () {
+            this._off(this.options.dropZone, 'dragover drop');
+            this._off(this.options.pasteZone, 'paste');
+            this._off(this.options.fileInput, 'change');
+        },
+
+        _setOption: function (key, value) {
+            var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
+            if (refresh) {
+                this._destroyEventHandlers();
+            }
+            this._super(key, value);
+            if (refresh) {
+                this._initSpecialOptions();
+                this._initEventHandlers();
+            }
+        },
+
+        _initSpecialOptions: function () {
+            var options = this.options;
+            if (options.fileInput === undefined) {
+                options.fileInput = this.element.is('input[type="file"]') ?
+                        this.element : this.element.find('input[type="file"]');
+            } else if (!(options.fileInput instanceof $)) {
+                options.fileInput = $(options.fileInput);
+            }
+            if (!(options.dropZone instanceof $)) {
+                options.dropZone = $(options.dropZone);
+            }
+            if (!(options.pasteZone instanceof $)) {
+                options.pasteZone = $(options.pasteZone);
+            }
+        },
+
+        _create: function () {
+            var options = this.options;
+            // Initialize options set via HTML5 data-attributes:
+            $.extend(options, $(this.element[0].cloneNode(false)).data());
+            this._initSpecialOptions();
+            this._slots = [];
+            this._sequence = this._getXHRPromise(true);
+            this._sending = this._active = this._loaded = this._total = 0;
+            this._initEventHandlers();
+        },
+
+        _destroy: function () {
+            this._destroyEventHandlers();
+        },
+
+        // This method is exposed to the widget API and allows adding files
+        // using the fileupload API. The data parameter accepts an object which
+        // must have a files property and can contain additional options:
+        // .fileupload('add', {files: filesList});
+        add: function (data) {
+            var that = this;
+            if (!data || this.options.disabled) {
+                return;
+            }
+            if (data.fileInput && !data.files) {
+                this._getFileInputFiles(data.fileInput).always(function (files) {
+                    data.files = files;
+                    that._onAdd(null, data);
+                });
+            } else {
+                data.files = $.makeArray(data.files);
+                this._onAdd(null, data);
+            }
+        },
+
+        // This method is exposed to the widget API and allows sending files
+        // using the fileupload API. The data parameter accepts an object which
+        // must have a files or fileInput property and can contain additional options:
+        // .fileupload('send', {files: filesList});
+        // The method returns a Promise object for the file upload call.
+        send: function (data) {
+            if (data && !this.options.disabled) {
+                if (data.fileInput && !data.files) {
+                    var that = this,
+                        dfd = $.Deferred(),
+                        promise = dfd.promise(),
+                        jqXHR,
+                        aborted;
+                    promise.abort = function () {
+                        aborted = true;
+                        if (jqXHR) {
+                            return jqXHR.abort();
+                        }
+                        dfd.reject(null, 'abort', 'abort');
+                        return promise;
+                    };
+                    this._getFileInputFiles(data.fileInput).always(
+                        function (files) {
+                            if (aborted) {
+                                return;
+                            }
+                            data.files = files;
+                            jqXHR = that._onSend(null, data).then(
+                                function (result, textStatus, jqXHR) {
+                                    dfd.resolve(result, textStatus, jqXHR);
+                                },
+                                function (jqXHR, textStatus, errorThrown) {
+                                    dfd.reject(jqXHR, textStatus, errorThrown);
+                                }
+                            );
+                        }
+                    );
+                    return this._enhancePromise(promise);
+                }
+                data.files = $.makeArray(data.files);
+                if (data.files.length) {
+                    return this._onSend(null, data);
+                }
+            }
+            return this._getXHRPromise(false, data && data.context);
+        }
+
+    });
+
+}));
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.iframe-transport.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,172 @@
+/*
+ * jQuery Iframe Transport Plugin 1.5
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint unparam: true, nomen: true */
+/*global define, window, document */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define(['jquery'], factory);
+    } else {
+        // Browser globals:
+        factory(window.jQuery);
+    }
+}(function ($) {
+    'use strict';
+
+    // Helper variable to create unique names for the transport iframes:
+    var counter = 0;
+
+    // The iframe transport accepts three additional options:
+    // options.fileInput: a jQuery collection of file input fields
+    // options.paramName: the parameter name for the file form data,
+    //  overrides the name property of the file input field(s),
+    //  can be a string or an array of strings.
+    // options.formData: an array of objects with name and value properties,
+    //  equivalent to the return data of .serializeArray(), e.g.:
+    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
+    $.ajaxTransport('iframe', function (options) {
+        if (options.async && (options.type === 'POST' || options.type === 'GET')) {
+            var form,
+                iframe;
+            return {
+                send: function (_, completeCallback) {
+                    form = $('<form style="display:none;"></form>');
+                    form.attr('accept-charset', options.formAcceptCharset);
+                    // javascript:false as initial iframe src
+                    // prevents warning popups on HTTPS in IE6.
+                    // IE versions below IE8 cannot set the name property of
+                    // elements that have already been added to the DOM,
+                    // so we set the name along with the iframe HTML markup:
+                    iframe = $(
+                        '<iframe src="javascript:false;" name="iframe-transport-' +
+                            (counter += 1) + '"></iframe>'
+                    ).bind('load', function () {
+                        var fileInputClones,
+                            paramNames = $.isArray(options.paramName) ?
+                                    options.paramName : [options.paramName];
+                        iframe
+                            .unbind('load')
+                            .bind('load', function () {
+                                var response;
+                                // Wrap in a try/catch block to catch exceptions thrown
+                                // when trying to access cross-domain iframe contents:
+                                try {
+                                    response = iframe.contents();
+                                    // Google Chrome and Firefox do not throw an
+                                    // exception when calling iframe.contents() on
+                                    // cross-domain requests, so we unify the response:
+                                    if (!response.length || !response[0].firstChild) {
+                                        throw new Error();
+                                    }
+                                } catch (e) {
+                                    response = undefined;
+                                }
+                                // The complete callback returns the
+                                // iframe content document as response object:
+                                completeCallback(
+                                    200,
+                                    'success',
+                                    {'iframe': response}
+                                );
+                                // Fix for IE endless progress bar activity bug
+                                // (happens on form submits to iframe targets):
+                                $('<iframe src="javascript:false;"></iframe>')
+                                    .appendTo(form);
+                                form.remove();
+                            });
+                        form
+                            .prop('target', iframe.prop('name'))
+                            .prop('action', options.url)
+                            .prop('method', options.type);
+                        if (options.formData) {
+                            $.each(options.formData, function (index, field) {
+                                $('<input type="hidden"/>')
+                                    .prop('name', field.name)
+                                    .val(field.value)
+                                    .appendTo(form);
+                            });
+                        }
+                        if (options.fileInput && options.fileInput.length &&
+                                options.type === 'POST') {
+                            fileInputClones = options.fileInput.clone();
+                            // Insert a clone for each file input field:
+                            options.fileInput.after(function (index) {
+                                return fileInputClones[index];
+                            });
+                            if (options.paramName) {
+                                options.fileInput.each(function (index) {
+                                    $(this).prop(
+                                        'name',
+                                        paramNames[index] || options.paramName
+                                    );
+                                });
+                            }
+                            // Appending the file input fields to the hidden form
+                            // removes them from their original location:
+                            form
+                                .append(options.fileInput)
+                                .prop('enctype', 'multipart/form-data')
+                                // enctype must be set as encoding for IE:
+                                .prop('encoding', 'multipart/form-data');
+                        }
+                        form.submit();
+                        // Insert the file input fields at their original location
+                        // by replacing the clones with the originals:
+                        if (fileInputClones && fileInputClones.length) {
+                            options.fileInput.each(function (index, input) {
+                                var clone = $(fileInputClones[index]);
+                                $(input).prop('name', clone.prop('name'));
+                                clone.replaceWith(input);
+                            });
+                        }
+                    });
+                    form.append(iframe).appendTo(document.body);
+                },
+                abort: function () {
+                    if (iframe) {
+                        // javascript:false as iframe src aborts the request
+                        // and prevents warning popups on HTTPS in IE6.
+                        // concat is used to avoid the "Script URL" JSLint error:
+                        iframe
+                            .unbind('load')
+                            .prop('src', 'javascript'.concat(':false;'));
+                    }
+                    if (form) {
+                        form.remove();
+                    }
+                }
+            };
+        }
+    });
+
+    // The iframe transport returns the iframe content document as response.
+    // The following adds converters from iframe to text, json, html, and script:
+    $.ajaxSetup({
+        converters: {
+            'iframe text': function (iframe) {
+                return $(iframe[0].body).text();
+            },
+            'iframe json': function (iframe) {
+                return $.parseJSON($(iframe[0].body).text());
+            },
+            'iframe html': function (iframe) {
+                return $(iframe[0].body).html();
+            },
+            'iframe script': function (iframe) {
+                return $.globalEval($(iframe[0].body).text());
+            }
+        }
+    });
+
+}));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.postmessage-transport.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,117 @@
+/*
+ * jQuery postMessage Transport Plugin 1.1
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint unparam: true, nomen: true */
+/*global define, window, document */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define(['jquery'], factory);
+    } else {
+        // Browser globals:
+        factory(window.jQuery);
+    }
+}(function ($) {
+    'use strict';
+
+    var counter = 0,
+        names = [
+            'accepts',
+            'cache',
+            'contents',
+            'contentType',
+            'crossDomain',
+            'data',
+            'dataType',
+            'headers',
+            'ifModified',
+            'mimeType',
+            'password',
+            'processData',
+            'timeout',
+            'traditional',
+            'type',
+            'url',
+            'username'
+        ],
+        convert = function (p) {
+            return p;
+        };
+
+    $.ajaxSetup({
+        converters: {
+            'postmessage text': convert,
+            'postmessage json': convert,
+            'postmessage html': convert
+        }
+    });
+
+    $.ajaxTransport('postmessage', function (options) {
+        if (options.postMessage && window.postMessage) {
+            var iframe,
+                loc = $('<a>').prop('href', options.postMessage)[0],
+                target = loc.protocol + '//' + loc.host,
+                xhrUpload = options.xhr().upload;
+            return {
+                send: function (_, completeCallback) {
+                    var message = {
+                            id: 'postmessage-transport-' + (counter += 1)
+                        },
+                        eventName = 'message.' + message.id;
+                    iframe = $(
+                        '<iframe style="display:none;" src="' +
+                            options.postMessage + '" name="' +
+                            message.id + '"></iframe>'
+                    ).bind('load', function () {
+                        $.each(names, function (i, name) {
+                            message[name] = options[name];
+                        });
+                        message.dataType = message.dataType.replace('postmessage ', '');
+                        $(window).bind(eventName, function (e) {
+                            e = e.originalEvent;
+                            var data = e.data,
+                                ev;
+                            if (e.origin === target && data.id === message.id) {
+                                if (data.type === 'progress') {
+                                    ev = document.createEvent('Event');
+                                    ev.initEvent(data.type, false, true);
+                                    $.extend(ev, data);
+                                    xhrUpload.dispatchEvent(ev);
+                                } else {
+                                    completeCallback(
+                                        data.status,
+                                        data.statusText,
+                                        {postmessage: data.result},
+                                        data.headers
+                                    );
+                                    iframe.remove();
+                                    $(window).unbind(eventName);
+                                }
+                            }
+                        });
+                        iframe[0].contentWindow.postMessage(
+                            message,
+                            target
+                        );
+                    }).appendTo(document.body);
+                },
+                abort: function () {
+                    if (iframe) {
+                        iframe.remove();
+                    }
+                }
+            };
+        }
+    });
+
+}));
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.ui.widget.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,528 @@
+/*
+ * jQuery UI Widget 1.9.1+amd
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+
+(function (factory) {
+    if (typeof define === "function" && define.amd) {
+        // Register as an anonymous AMD module:
+        define(["jquery"], factory);
+    } else {
+        // Browser globals:
+        factory(jQuery);
+    }
+}(function( $, undefined ) {
+
+var uuid = 0,
+	slice = Array.prototype.slice,
+	_cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+		try {
+			$( elem ).triggerHandler( "remove" );
+		// http://bugs.jquery.com/ticket/8235
+		} catch( e ) {}
+	}
+	_cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+	var fullName, existingConstructor, constructor, basePrototype,
+		namespace = name.split( "." )[ 0 ];
+
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+	// extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+		// copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+		// track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	});
+
+	basePrototype = new base();
+	// we need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( $.isFunction( value ) ) {
+			prototype[ prop ] = (function() {
+				var _super = function() {
+						return base.prototype[ prop ].apply( this, arguments );
+					},
+					_superApply = function( args ) {
+						return base.prototype[ prop ].apply( this, args );
+					};
+				return function() {
+					var __super = this._super,
+						__superApply = this._superApply,
+						returnValue;
+
+					this._super = _super;
+					this._superApply = _superApply;
+
+					returnValue = value.apply( this, arguments );
+
+					this._super = __super;
+					this._superApply = __superApply;
+
+					return returnValue;
+				};
+			})();
+		}
+	});
+	constructor.prototype = $.widget.extend( basePrototype, {
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: basePrototype.widgetEventPrefix || name
+	}, prototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		// TODO remove widgetBaseClass, see #8155
+		widgetBaseClass: fullName,
+		widgetFullName: fullName
+	});
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+		});
+		// remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+	var input = slice.call( arguments, 1 ),
+		inputIndex = 0,
+		inputLength = input.length,
+		key,
+		value;
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = slice.call( arguments, 1 ),
+			returnValue = this;
+
+		// allow multiple hashes to be passed on init
+		options = !isMethodCall && args.length ?
+			$.widget.extend.apply( null, [ options ].concat(args) ) :
+			options;
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var methodValue,
+					instance = $.data( this, fullName );
+				if ( !instance ) {
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'" );
+				}
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+				}
+				methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue && methodValue.jquery ?
+						returnValue.pushStack( methodValue.get() ) :
+						methodValue;
+					return false;
+				}
+			});
+		} else {
+			this.each(function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} )._init();
+				} else {
+					new object( options, this );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+	options: {
+		disabled: false,
+
+		// callbacks
+		create: null
+	},
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = uuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+
+		if ( element !== this ) {
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			$.data( element, this.widgetName, this );
+			$.data( element, this.widgetFullName, this );
+			this._on( this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			});
+			this.document = $( element.style ?
+				// element within the document
+				element.ownerDocument :
+				// element is window or document
+				element.document || element );
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+		}
+
+		this._create();
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+	_getCreateOptions: $.noop,
+	_getCreateEventData: $.noop,
+	_create: $.noop,
+	_init: $.noop,
+
+	destroy: function() {
+		this._destroy();
+		// we can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.unbind( this.eventNamespace )
+			// 1.9 BC for #7810
+			// TODO remove dual storage
+			.removeData( this.widgetName )
+			.removeData( this.widgetFullName )
+			// support: jquery <1.6.3
+			// http://bugs.jquery.com/ticket/9413
+			.removeData( $.camelCase( this.widgetFullName ) );
+		this.widget()
+			.unbind( this.eventNamespace )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetFullName + "-disabled " +
+				"ui-state-disabled" );
+
+		// clean up events and states
+		this.bindings.unbind( this.eventNamespace );
+		this.hoverable.removeClass( "ui-state-hover" );
+		this.focusable.removeClass( "ui-state-focus" );
+	},
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key,
+			parts,
+			curOption,
+			i;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( value === undefined ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( value === undefined ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+			this.hoverable.removeClass( "ui-state-hover" );
+			this.focusable.removeClass( "ui-state-focus" );
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOption( "disabled", false );
+	},
+	disable: function() {
+		return this._setOption( "disabled", true );
+	},
+
+	_on: function( element, handlers ) {
+		var delegateElement,
+			instance = this;
+		// no element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			// accept selectors, DOM elements
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+				// allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( instance.options.disabled === true ||
+						$( this ).hasClass( "ui-state-disabled" ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^(\w+)\s*(.*)$/ ),
+				eventName = match[1] + instance.eventNamespace,
+				selector = match[2];
+			if ( selector ) {
+				delegateElement.delegate( selector, eventName, handlerProxy );
+			} else {
+				element.bind( eventName, handlerProxy );
+			}
+		});
+	},
+
+	_off: function( element, eventName ) {
+		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+		element.unbind( eventName ).undelegate( eventName );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
+			}
+		});
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
+			}
+		});
+	},
+
+	_trigger: function( type, event, data ) {
+		var prop, orig,
+			callback = this.options[ type ];
+
+		data = data || {};
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+		// the original event may come from any element
+		// so we need to reset the target on the new event
+		event.target = this.element[ 0 ];
+
+		// copy original event properties over to the new event
+		orig = event.originalEvent;
+		if ( orig ) {
+			for ( prop in orig ) {
+				if ( !( prop in event ) ) {
+					event[ prop ] = orig[ prop ];
+				}
+			}
+		}
+
+		this.element.trigger( event, data );
+		return !( $.isFunction( callback ) &&
+			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+		var hasOptions,
+			effectName = !options ?
+				method :
+				options === true || typeof options === "number" ?
+					defaultEffect :
+					options.effect || defaultEffect;
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+		if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue(function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			});
+		}
+	};
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+	$.Widget.prototype._getCreateOptions = function() {
+		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+	};
+}
+
+}));
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ldt/ldt/static/ldt/js/jquery.xdr-transport.js	Tue Feb 12 17:30:41 2013 +0100
@@ -0,0 +1,85 @@
+/*
+ * jQuery XDomainRequest Transport Plugin 1.1.2
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on Julian Aubourg's ajaxHooks xdr.js:
+ * https://github.com/jaubourg/ajaxHooks/
+ */
+
+/*jslint unparam: true */
+/*global define, window, XDomainRequest */
+
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // Register as an anonymous AMD module:
+        define(['jquery'], factory);
+    } else {
+        // Browser globals:
+        factory(window.jQuery);
+    }
+}(function ($) {
+    'use strict';
+    if (window.XDomainRequest && !$.support.cors) {
+        $.ajaxTransport(function (s) {
+            if (s.crossDomain && s.async) {
+                if (s.timeout) {
+                    s.xdrTimeout = s.timeout;
+                    delete s.timeout;
+                }
+                var xdr;
+                return {
+                    send: function (headers, completeCallback) {
+                        function callback(status, statusText, responses, responseHeaders) {
+                            xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
+                            xdr = null;
+                            completeCallback(status, statusText, responses, responseHeaders);
+                        }
+                        xdr = new XDomainRequest();
+                        // XDomainRequest only supports GET and POST:
+                        if (s.type === 'DELETE') {
+                            s.url = s.url + (/\?/.test(s.url) ? '&' : '?') +
+                                '_method=DELETE';
+                            s.type = 'POST';
+                        } else if (s.type === 'PUT') {
+                            s.url = s.url + (/\?/.test(s.url) ? '&' : '?') +
+                                '_method=PUT';
+                            s.type = 'POST';
+                        }
+                        xdr.open(s.type, s.url);
+                        xdr.onload = function () {
+                            callback(
+                                200,
+                                'OK',
+                                {text: xdr.responseText},
+                                'Content-Type: ' + xdr.contentType
+                            );
+                        };
+                        xdr.onerror = function () {
+                            callback(404, 'Not Found');
+                        };
+                        if (s.xdrTimeout) {
+                            xdr.ontimeout = function () {
+                                callback(0, 'timeout');
+                            };
+                            xdr.timeout = s.xdrTimeout;
+                        }
+                        xdr.send((s.hasContent && s.data) || null);
+                    },
+                    abort: function () {
+                        if (xdr) {
+                            xdr.onerror = $.noop();
+                            xdr.abort();
+                        }
+                    }
+                };
+            }
+        });
+    }
+}));
\ No newline at end of file
--- a/src/ldt/ldt/static/ldt/js/projectscontents.js	Sun Feb 10 23:04:28 2013 +0100
+++ b/src/ldt/ldt/static/ldt/js/projectscontents.js	Tue Feb 12 17:30:41 2013 +0100
@@ -1,4 +1,4 @@
-
+
 function init_events_base(base_node, embed_url) {
 
     $('.imageline',base_node).each(function(i) {
@@ -386,7 +386,6 @@
 
 
 function resize_modal_window (element, createcontent) {
-    var nm = parent.$.nmTop();
     var iframe = $("iframe", window.parent.document);
     if ( $('#init_ldt_view', element).size() > 0) { // if contains ldt lpayer
     	var h = 660;
@@ -395,17 +394,9 @@
     	var h = $(element).innerHeight() + 10;
     	var w = $(element).innerWidth() + 10;
 	}
-        
-    nm.sizes.minH = h + 10;
-    nm.sizes.minW = w;
+
     iframe.height(h);
     iframe.width(w);
-    
-    if ($.browser.mozilla && createcontent) {
-    	$(".nyroModalCont", parent.window.document).height(h+10);
-    } else {
-    	nm.resize(true);
-    }
 }
 
 //
@@ -417,11 +408,7 @@
     window.submit_asked = false;
     window.remove_temp_file_url = remove_url;
     window.ask_duration_url = ask_dur_url;
-    
-    $("#close_button").click(function (e) {
-        e.preventDefault();
-        parent.$.nmTop().close();
-    });    
+     
     $("#submit_button_write").click(function(e) {
         $(".submitcontent-loader-content").show();
         resize_modal_window($("#add_content"), true);
@@ -439,103 +426,22 @@
     $('#my_form').bind('submit', function() {
         window.submit_asked = true;
         if($('#id_content-media_input_type').val()=="upload"){
-            if(window.upload_from_local_done==false){
-                // The form has sent but the uploading has not ended.
-                if($('#upload_progress_info').children().size()>0){
-                    $('#progress_info').remove();
-                }
-                $('#upload_progress_info').append('<p id="progress_info"><font color="red"><strong>' + wait_label + '.</strong></font><br/><a href="javascript:cancelSwfUpload()">' + cancel_label + '.</a></p>');
-                return false;
+            /*if(chunked_upload_complete == true){
+                return true;
             }
             else{
-                return true;
-            }
+                return false;
+            }*/
+        	return false;
         }
         else{
-            //alert("2. " + $('#id_content-media_input_type').val());
             return true;
         }
     });
     
-    $("#upload_progress_bar").css({ width:"90%", height:"10" });
-    
     $('#media_fields_div').height(80);
-    
-    // We init the swfupload object
-    swfupload = new SWFUpload({
-        debug: false,
-        
-        upload_url: url_upload,
-        flash_url: media_prefix+"swf/swfupload.swf",
-        
-        post_params: post_added_params,
-        
-        button_placeholder_id: "upload_btn",
-        button_width: "60",
-        button_height: "16",
-        button_cursor: SWFUpload.CURSOR.HAND,
-        button_text : '<span class="btnText">' + btn_label + '</span>',
-        button_text_style : ".btnText { font-size: 12; font-family: Arial; }",
-        
-        file_types : "*.flv;*.f4v;*.mp4;*.mov;*.mp3",
-        file_types_description : "Media Files (flv, f4v, mov H264, mp4, mp3)",
-        file_upload_limit : "1",
-        file_queue_limit : "1",
-        
-        upload_error_handler : uploadError,
-        upload_start_handler : function(file) {
-            try {
-                if($('#upload_progress_info').children().size()>0){
-                    $('#progress_info').remove();
-                }
-                $('#upload_progress_info').append('<p id="progress_info"><a href="javascript:cancelSwfUpload()">' + cancel_label + '.</a></p>');
-            }
-            catch (ex) {
-                //this.debug(ex);
-            }
-        },
-        upload_progress_handler : uploadProgress,
-        upload_success_handler : function() {
-                try {
-                    if($('#upload_progress_info').children().size()>0){
-                        $('#progress_info').remove();
-                    }
-                    $('#upload_progress_info').append('<p id="progress_info">' + success_label + '.</p>');
-                    $("#upload_progress_bar > div").css({ 'background': '#90ffa8' });
-                    window.upload_from_local_done = true;
-                    // Now that the file is uploaded, we submit the form if asked
-                    if(window.submit_asked==true){
-                        $('#my_form').submit();
-                    }
-                    else{
-                        // We ask the uploaded media's duration
-                        if(window.ask_duration_url){
-                            $.ajax({
-                                type: "GET",
-                                url: window.ask_duration_url,
-                                data: "filename="+ $('#id_media-local_file_name').val(),
-                                cache: false,
-                                success: function(data, status, request){
-                                    if(data && data!=""){
-                                        $('#id_content-duration').val(data);
-                                    }
-                                }
-                            });
-                        }
-                    }
-                }
-                catch (ex) {
-                    //this.debug(ex);
-                }
-            },
-        file_queued_handler : displayUploadPath,
-        file_dialog_complete_handler: function() { 
-            window.submit_asked = false;
-            this.startUpload();
-        }
-        //upload_complete_handler: function() { this.startUpload(); },
-    });
 }
+
 function displayUploadPath(file) {
     try {
         $('#id_media-local_file_name').val(file.name);
@@ -544,62 +450,6 @@
         //this.debug(ex);
     }
 }
-function startLocalUpload(){
-    swfupload.startUpload();
-}
-function uploadProgress(file, bytesLoaded, bytesTotal) {
-    try {
-        var percent = Math.ceil((bytesLoaded / bytesTotal) * 100);
-        $("#upload_progress_bar").progressbar({ value: percent });
-    }
-    catch (ex) {
-        //this.debug(ex);
-    }
-}
-function cancelSwfUpload(){
-    swfupload.cancelUpload();
-    if($('#media_field_upload').has($('#cancelupload')).length==0){
-        $('#cancelupload').remove();
-    }
-}
-function uploadError(file, errorCode, message) {
-    try {
-        if($('#upload_progress_info').children().size()>0){
-            $('#progress_info').remove();
-        }
-        switch (errorCode) {
-        case SWFUpload.UPLOAD_ERROR.HTTP_ERROR:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: HTTP Error, File name: " + file.name + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: Upload Failed, File name: " + file.name + ", File size: " + file.size + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.IO_ERROR:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: IO Error, File name: " + file.name + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: Security Error, File name: " + file.name + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: Upload Limit Exceeded, File name: " + file.name + ", File size: " + file.size + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: File Validation Failed, File name: " + file.name + ", File size: " + file.size + ", Message: " + message + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
-            $('#upload_progress_info').append('<p id="progress_info">' + "Error Code: FILE_CANCELLED" + '</p>');
-            break;
-        case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
-            $('#upload_progress_info').append('<p id="progress_info">' + "STOPPED" + '</p>');
-            break;
-        default:
-            $('#upload_progress_info').append('<p id="progress_info">' + "unhandled error: File name: " + file.name + ", File size: " + file.size + ", Message: " + message + '</p>');
-            break;
-        }
-    } catch (ex) {
-        //this.debug(ex);
-    }
-}
 
 // Test the value of the URL from the form to load a picture in case it is a youtube video
 function testUrlValue(e){
@@ -704,3 +554,4 @@
 		$(checkbox_names).trigger("change");
 	});		
 }
+