/**
* plupload.html5.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
// JSLint defined globals
/*global plupload:false, File:false, window:false, atob:false */
(function(plupload) {
function scaleImage(image_data_url, max_width, max_height, mime, callback) {
var canvas, context, img, data, scale;
// Setup canvas and context
canvas = document.createElement("canvas");
canvas.style.display = 'none';
document.body.appendChild(canvas);
context = canvas.getContext('2d');
// Load image
img = new Image();
img.onload = function() {
var width, height, percentage;
scale = Math.min(max_width / img.width, max_height / img.height);
if (scale < 1) {
width = Math.round(img.width * scale);
height = Math.round(img.height * scale);
} else {
width = img.width;
height = img.height;
}
// Scale image and canvas
canvas.width = width;
canvas.height = height;
context.drawImage(img, 0, 0, width, height);
// Remove data prefix information and grab the base64 encoded data and decode it
data = canvas.toDataURL(mime);
data = data.substring(data.indexOf('base64,') + 7);
data = atob(data);
// Remove canvas and execute callback with decoded image data
canvas.parentNode.removeChild(canvas);
callback({success : true, data : data});
};
img.src = image_data_url;
}
/**
* HMTL5 implementation. This runtime supports these features: dragdrop, jpgresize, pngresize.
*
* @static
* @class plupload.runtimes.Html5
* @extends plupload.Runtime
*/
plupload.runtimes.Html5 = plupload.addRuntime("html5", {
/**
* Initializes the upload runtime.
*
* @method init
* @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.
* @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.
*/
init : function(uploader, callback) {
var html5files = {}, dataAccessSupport;
function addSelectedFiles(native_files) {
var file, i, files = [], id;
// Add the selected files to the file queue
for (i = 0; i < native_files.length; i++) {
file = native_files[i];
// Store away gears blob internally
id = plupload.guid();
html5files[id] = file;
// Expose id, name and size
files.push(new plupload.File(id, file.fileName, file.fileSize));
}
// Trigger FilesAdded event if we added any
if (files.length) {
uploader.trigger("FilesAdded", files);
}
}
function isSupported() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
return !!(xhr.sendAsBinary || xhr.upload);
}
return false;
}
// No HTML5 upload support
if (!isSupported()) {
callback({success : false});
return;
}
uploader.bind("Init", function(up) {
var inputContainer, mimes = [], i, y, filters = up.settings.filters, ext, type, container = document.body;
// Create input container and insert it at an absolute position within the browse button
inputContainer = document.createElement('div');
inputContainer.id = up.id + '_html5_container';
// Convert extensions to mime types list
for (i = 0; i < filters.length; i++) {
ext = filters[i].extensions.split(/,/);
for (y = 0; y < ext.length; y++) {
type = plupload.mimeTypes[ext[y]];
if (type) {
mimes.push(type);
}
}
}
plupload.extend(inputContainer.style, {
position : 'absolute',
background : uploader.settings.shim_bgcolor || 'transparent',
width : '100px',
height : '100px',
overflow : 'hidden',
zIndex : 99999,
opacity : uploader.settings.shim_bgcolor ? '' : 0 // Force transparent if bgcolor is undefined
});
inputContainer.className = 'plupload html5';
if (uploader.settings.container) {
container = document.getElementById(uploader.settings.container);
container.style.position = 'relative';
}
container.appendChild(inputContainer);
// Insert the input inide the input container
inputContainer.innerHTML = '';
document.getElementById(uploader.id + '_html5').onchange = function() {
// Add the selected files from file input
addSelectedFiles(this.files);
// Clearing the value enables the user to select the same file again if they want to
this.value = '';
};
});
// Add drop handler
uploader.bind("PostInit", function() {
var dropElm = document.getElementById(uploader.settings.drop_element);
if (dropElm) {
// Block browser default drag over
plupload.addEvent(dropElm, 'dragover', function(e) {
e.preventDefault();
});
// Attach drop handler and grab files from Gears
plupload.addEvent(dropElm, 'drop', function(e) {
var dataTransfer = e.dataTransfer;
// Add dropped files
if (dataTransfer && dataTransfer.files) {
addSelectedFiles(dataTransfer.files);
}
e.preventDefault();
});
}
});
uploader.bind("Refresh", function(up) {
var browseButton, browsePos, browseSize;
browseButton = document.getElementById(uploader.settings.browse_button);
browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));
browseSize = plupload.getSize(browseButton);
plupload.extend(document.getElementById(uploader.id + '_html5_container').style, {
top : browsePos.y + 'px',
left : browsePos.x + 'px',
width : browseSize.w + 'px',
height : browseSize.h + 'px'
});
});
uploader.bind("UploadFile", function(up, file) {
var xhr = new XMLHttpRequest(), upload = xhr.upload, resize = up.settings.resize, nativeFile, multipartSize = 0;
// Sends the binary blob to server and multipart encodes it if needed this code will
// only be executed on Gecko since it's currently the only browser that supports direct file access
function sendBinaryBlob(blob) {
var boundary = '----pluploadboundary' + plupload.guid(), dashdash = '--', crlf = '\r\n', multipartBlob = '';
// Build multipart request
if (up.settings.multipart) {
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
// Append mutlipart parameters
plupload.each(up.settings.multipart_params, function(value, name) {
multipartBlob += dashdash + boundary + crlf +
'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf;
multipartBlob += value + crlf;
});
// Build RFC2388 blob
multipartBlob += dashdash + boundary + crlf +
'Content-Disposition: form-data; name="file"; filename="' + file.name + '"' + crlf +
'Content-Type: application/octet-stream' + crlf + crlf +
blob + crlf +
dashdash + boundary + dashdash + crlf;
multipartSize = multipartBlob.length - blob.length;
blob = multipartBlob;
}
// Send blob or multipart blob depending on config
xhr.sendAsBinary(blob);
}
// File upload finished
if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {
return;
}
// Do we have upload progress support
if (upload) {
upload.onprogress = function(e) {
file.loaded = e.loaded - multipartSize;
up.trigger('UploadProgress', file);
};
}
xhr.onreadystatechange = function() {
var httpStatus = xhr.status;
if (xhr.readyState == 4) {
file.status = plupload.DONE;
file.loaded = file.size;
up.trigger('UploadProgress', file);
up.trigger('FileUploaded', file, {
response : xhr.responseText,
status : httpStatus
});
// Response isn't 200 ok
if (httpStatus != 200) {
up.trigger('Error', {
code : plupload.HTTP_ERROR,
message : 'HTTP Error.',
file : file,
status : httpStatus
});
}
}
};
xhr.open("post", plupload.buildUrl(up.settings.url, {name : file.target_name || file.name}), true);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
nativeFile = html5files[file.id];
if (xhr.sendAsBinary) {
// Resize image if it's a supported format and resize is enabled
if (resize && /\.(png|jpg|jpeg)$/i.test(file.name)) {
scaleImage(nativeFile.getAsDataURL(), resize.width, resize.height, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg', function(res) {
// If it was scaled send the scaled image if it failed then
// send the raw image and let the server do the scaling
if (res.success) {
file.size = res.data.length;
sendBinaryBlob(res.data);
} else {
sendBinaryBlob(nativeFile.getAsBinary());
}
});
} else {
sendBinaryBlob(nativeFile.getAsBinary());
}
} else {
xhr.send(nativeFile);
}
});
// Do we have direct data access Gecko has it but WebKit doesn't yet
dataAccessSupport = !!(File && File.prototype.getAsDataURL);
uploader.features = {
// Detect drag/drop file support by sniffing, will try to find a better way
dragdrop : window.mozInnerScreenX !== undefined,
jpgresize : dataAccessSupport,
pngresize : dataAccessSupport
};
callback({success : true});
}
});
})(plupload);