Telerik blogs

Icenium Everlive – Telerik's backend-as-a-service platform – makes storing data in the cloud very simple. I recently wrote a post about uploading files to Everlive - and we did so by using Everlive's HTTP API. Here's a snippet from the sample app created for that post, showing the behavior executed when an image viewmodel (which includes a title and file, etc.) is saved to Everlive:

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 calling AppHelper.getImageFileObject. That method ultimately takes the image, reads it, base64 encodes it, and creates an object to be used as the data of a POST back to Everlive. That object is passed into the callback argument (second arg) - from there we make an AJAX call to upload the file, and we chain a 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:

  • upload the image and get the ID back from the server
  • update our viewmodel with the ID of the saved image
  • persist our viewmodel
  • clear the form

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™.

One Possible Future

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 lib to help manage this: everloader.

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 have everloader.upload(). The 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?

Gains

Improved Upload Functionality

The nice thing is, we've not only moved the original file upload logic behind everloader.upload(), but we've done it in such a way to allow any file input to be processed. (You can configure which input elements are processed, but the default is any with type="file".) The upload() method then returns a promise, allowing us to continue saving our viewmodel. You can customize everloader along these lines:

  • override the getFileInputs method to determine which input elements are used for uploading files (as mentioned above)
  • filter MIME types by providing a whitelist of allowed types in the mimeTypes array (leaving this array empty means any file type can be uploaded)
  • applying a timeout value to throw an error if uploads take too long
  • and more…

Clarity and Separation of Concerns

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 it's thing: save the record we created. The 'narrative' of our code shouldn't have to be crowded with the how of uploading files. By moving the how into an "infrastructure" layer, we're able to make the saveItem method's intent clearer, and we've gained the use of specialized behavior we might want to share between apps.

OK, Go!

The everloder lib is only one example of the kinds of utilities enabled by great backend services like Icenium Everlive. I'd love your feedback, but I'd also love to hear about the kinds of utilities you envision being useful working with cloud-based backends.


About the Author

Jim Cowart

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. 

Comments

Comments are disabled in preview mode.