var addImageViewModel = { // other viewmodel members here…. saveItem: function () { var that = this; $newPicture = $newPicture || $("#newPicture"); AppHelper.getImageFileObject( $newPicture[0].files[0], function (err, fileObj) { if (err) { navigator.notification.alert(err); return; } $.ajax({ type: "POST", url: 'https://api.everlive.com/v1/wEx9wdnIcxxehNty/Files', contentType: "application/json", data: JSON.stringify(fileObj), error: function (error) { navigator.notification.alert(JSON.stringify(error)); } }).done(function (data) { var item = imagesViewModel.images.add(); item.Title = that.get('picTitle'); item.Picture = data.Result.Id; imagesViewModel.images.one('sync', function () { mobileApp.navigate('#:back'); }); imagesViewModel.images.sync(); // reset the form that.set("picSelected", false); $newPicture.replaceWith($newPicture = $newPicture.clone(true)); }); } ); } };
In the saveItem
method above, you can see that we're AppHelper.getImageFileObject
done
callback to be executed when the upload completes successfully. It's inside that done
callback argument that we finally save the original "record" that the image is associated with. The flow is straightforward:
For just one upload at a time, this isn't horrible. However, if we're writing an application that could allow the user to upload several photos at a time, this not-too-awful-but-could-improve code will exponentially increase, becoming Dreaded Boilerplate™.
I hate boilerplate - so let's look at one way to reduce it. While I plan to investigate other options in the future - batching file uploads or using Everlive "cloud code" hooks to process all the items from one request (record + images) – for this post we're going to focus on something simpler: iterating over any "file" input elements, uploading the chosen files, and compiling an object containing the IDs Everlive generates for each saved file. I've created a proof-of-concept utility
Let's look at how it might help out. The snippet below is our new saveItem
method (you can see the sample project here):
var addImageViewModel = { // other viewmodel members here…. saveItem: function () { var that = this; everloader .upload() .then(function (data) { var item = imagesViewModel.images.add(); item.Title = that.get('picTitle'); item.Picture = Object.keys(data.newPicture)[0]; imagesViewModel.images.one('sync', function () { mobileApp.navigate('#:back'); }); imagesViewModel.images.sync(); // reset the form that.set("picSelected", false); $newPicture.replaceWith($newPicture = $newPicture.clone(true)); }, function (data) { var msg = JSON.stringify(data.errors, null, 2); alert("There was a problem:\n" + msg); }); } }
We managed to shave off some code bloat! Notice that instead of the call to AppHelper.getImageFileObject
and the subsequent $.ajax
request to upload the file, we just everloader.upload()
data
argument that gets passed into our then
callback will contain the IDs for any images uploaded to Everlive as part of the save operation. Notice the line:
item.Picture = Object.keys(data.newPicture)[0];
You can infer from that line that the data
argument will have a property for every file input element id, and the value will be an object whose members correspond to file IDs returned from Everlive (and the values contain additional file metadata). In the example above, we just uploaded one file. Uploading multiple files would only require us to use the additional IDs that uploading multiple files would return from the server.
So - what have we gained?
The nice thing is, we've not only moved the original file upload logic everloader.upload()
type="file"
upload()
method then returns a promise, allowing us to continue saving our
getFileInputs
method to determine which input elements are used for uploading files (as mentioned above)mimeTypes
array (leaving this array empty means any file type can be uploaded)While reducing the amount of code is a nice win, we've gained something more in taking this kind of approach. We've abstracted infrastructure code away from app logic, and made our app logic the focus in the example above. We just want our addImageViewModel
to do saveItem
method's intent clearer, and we've gained the use of specialized behavior we might want to share between apps.
The
Jim Cowart is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks.