In Part 1 I provided the routing files necessary to open up the KeystoneJS API for accepting images. I also showed how to create a simple HTML form for uploading images and pointed out some of the limitations of the API. In Part 2 I'll show how to simulate a form with JavaScript so that images can be uploaded via JavaScript code rather than an HTML form. I'll also show how to use the JavaScript Caman.js library to edit images on a HTML5 canvas element, then upload them to the server.
Uploading Images Via JavaScript
Here is a very simple HTML form. Notice that the Submit button is of type 'button' and not 'submit'. It also has the onclick property set to the JavaScript function uploadImage().
<form>
<input type="file" id="image_upload" onchange="displayUploadImage()">
<br>
<img id="imageToUpload" src="" />
<br>
<input type="button" value="Submit" onclick="uploadImage()">
</form>
Check out this code while I walk you through it:
function uploadImage() {
var selectedFile = $('#image_upload').get(0).files[0];
var fileName = selectedFile.name;
//Create the FormData data object and append the file to it.
var newImage = new FormData();
newImage.append('image_upload', selectedFile); //This is the raw file that was selected
var opts = {
url: 'http://'+serverIp+':3000/api/imageupload/create',
data: newImage,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
console.log('Image upload ID: ' + data.image_upload._id);
}
};
//Execute the AJAX operation.
jQuery.ajax(opts);
}
The simple HTML form provides the user interface for retrieving a file. The first line of the JavaScript function assigns the selected file to to the selectedFile object. The object newImage is of type FormData, which is the JavaScript equivalent of a form. The selectedFile object is appended to it with the value 'image_upload'. This is all creating the JavaScript equivalent to the HTML form in Part 1.
Just as in our HTML form, we can't 'append' any values other than 'image_upload'. They will be ignored. Only the image can be uploaded. We can however update other values in the database with a second AJAX request once the image has been successfully uploaded.
The opts object further fleshes out the settings that our FormData object needs in order to upload the selected image correctly. KeystoneJS is very picky about its settings and it took me several hours to find the right combination above.
In the opts values, you'll see a function assigned to the 'success' key. When the image is uploaded, KeystoneJS will respond with the _id GUID it generates when creating the new database entry. It will return this value in response to the AJAX post request, so I display this value in the browser console. This value will be needed in order to update the image data with a second AJAX request.
The last line in the code above executes the AJAX request for uploading the image.
Displaying a Thumb Nail Image
In order to begin visually editing the image, the first step is to display the selected file in the browser. The code below for doing that is borrowed from this MDN tutorial. It's really weird and I'm not totally comfortable with the way they are doing it, but the code works in Chrome and Firefox and it's very compact, which is nice.
function displayUploadImage() {
var imageFile = $('#image_upload').get(0).files[0];
var imageToUpload = $('#imageToUpload')[0];
//Display the selected image file in the browser
var reader = new FileReader(); //Create a new FileReader object
reader.onload = (function(aImg) { //These functions are executed when the FileReader
return function(e) { //finishes reading the image file.
aImg.src = e.target.result; //It displays the image file.
};
})(imageToUpload);
reader.readAsDataURL(imageFile); //Read the selected file with the FileReader.
//Replace the image with a canvas element and control it with Caman.
Caman("#imageToUpload", function () {
//This is where I would do image manipulation like resizing and cropping.
//For now this is just a place holder.
this.brightness(5).render();
});
}
After the image has been populated with the selected image file, the Caman function gets called to replace the image with an HTML5 canvas. Check out the CamanJS website, especially the example page that displays some of the in-browser editing you can do to an image. In the code above, all I do is adjust the brightness by 5%. The point is that this is the step where you'd edit your image: crop, scale, give the user a UI. Whatever you want.
Converting HTML5 Canvas to a File
So we've got two awesome ingredients: A way to upload image files to KeystoneJS and a way to edit images in the browser. How do we combine them? The answer to that my friends took me several hours to figure out. HTML Canvas elements are brand new and all the specifications around them have not been fully implemented into Chrome. The most common solution I found entailed converting the canvas image to ascii, uploading the ascii string to a server running a php script, and calling base64_decode() in the script. It was literally the only solution I could find after hours and hours of research, but I managed to come up with my own JavaScript based solution that avoids the use of PHP or extra server calls.
One of the HTML5 canvas specifications that haven't been implemented in Chrome yet is the .toBlob() function. This would be the easiest way convert the canvas to a file for upload via our JavaScript FormData object. The .toDataURL() function is widely implemented, which converts the canvas to an ascii string and results in all the nonsense with PHP and the base64_decode() function. However, the .toBlob() functionality is available as an external JavaScript library via this GitHub repository. Simply download the repository and include the canvas-to-blob.js file in your html. Thank you Sebastian Tschan!
Back to the uploadImage() function, Batman!
function uploadImage() {
var selectedFile = $('#image_upload').get(0).files[0];
var fileName = selectedFile.name;
//Below I'll createa file based on the manipulatd Canvas.
var canvas = $('#imageToUpload')[0];
if (canvas.toBlob) { //Ensure the toBlob library is loaded
canvas.toBlob( handleCanvasBlob, selectedFile.type );
} else {
console.error('Could not access toBlob library!');
return;
}
}
function handleCanvasBlob(blob) {
var the_file = new File([blob], fileName, {type: blob.type});
//Create the FormData data object and append the file to it.
var newImage = new FormData();
newImage.append('image_upload', the_file); //This is the raw file that was selected
var opts = {
url: 'http://'+serverIp+':3000/api/imageupload/create',
data: newImage,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
console.log('Image upload ID: ' + data.image_upload._id);
}
};
jQuery.ajax(opts);
}
In the uploadImage() function, I add a call to the .toBlob() function. The handleCanvasBlob() function handles the callback when the browser finishes converting the canvas to a blob. The line new File([blob], fileName, {type: blob.type}); converts the Blob object into a File object. The code below that is just a copy of what was originally inside the uploadImage() function, which handles the file upload to KeystoneJS.
One thing to watch out for is that the filename does not survive this manipulation process. Notice that I call the object fileName in the File() constructor. This is a global variable that I used to store the file name.
Combining the information in Part 1 and Part 2, you can now allow users to upload an image, perform on-the-fly image editing, and then submit that image to KeystoneJS. Have fun!