Telerik blogs

A nice feature of Icenium Everlive is that it can store files for you. However - if you've had any experience uploading files, you know the headaches it can bring. But don't worry about grabbing those Advil - by the end of this post you should have no issues uploading files to your Everlive back end.

First - Some Explanation

When you need to store data in an Everlive back end, you create a "Content Type" (really, a schema…similar to a table in a relational database, but not as strict). Lucky for us, we don't have to create a content type to store files – a "Files" content type is provided for you when you create a new Everlive project. (Everlive also provides "Users" and "Roles" content types out-of-the-box.) You'll see this content type listed on your Everlive project dashboard:

You'll see any files already uploaded when you click on "Files". The screen shot below shows the list of files we have uploaded already (on left). The top file has been selected, and we see it's details on the right:

We can quickly see all the fields that are part of the content type by clicking the "more" icon (I'm referring to the icon at the top right of the table – with three vertical bars…which we meant to represent columns). Clicking on it allows you to add more columns to the table listing:

In addition to what you can already see in the table listing (Filename, Content-Type, TotalSize and ModifiedAt), the Files content type includes fields for Uri, Id, Created by, Modified by and Created At. We can infer a few things from this:

  • The Files content type stores file metadata, while the file itself is stored to disk outside the back end store.
  • We need to specify a MIME type of the file when we upload it.
  • The Id field is used to link the file metadata (and the actual file via the Uri) to any other content type that has field of type "File"
  • Clients using our backend will use the Uri to access/download the file once they've retrieved the file metadata

So - what does it look like for another content type to have a "File" field type? Let's look at a quick example app I threw together that allows us to upload images. This app only has one custom content type - "Images". Here's the structure:

The circled field ("Picture") is the one we're interested in. The fact that it has a type of "File" tells Everlive that any "Images" record will link (potentially) to a "Files" record. To prove this, let's look at the results of an AJAX request to retrieve one "Images" record:

$.ajax({
    url: 'https://api.everlive.com/v1/wEx9wdnIcxxehNty/Images/b381a350-d2d9-11e2-b65f-2dcf34e70f16',
    type: "GET",
    success: function(data){
        console.log(JSON.stringify(data, null, 2));
    },
    error: function(error){
        console.log(JSON.stringify(error, null, 2));
    }
});

/* The response:
{
  "Result": {
    "Title": "Yoda Dance!",
    "Picture": "b3511d70-d2d9-11e2-b65f-2dcf34e70f16",
    "CreatedAt": "2013-06-11T20:58:43.589Z",
    "ModifiedAt": "2013-06-11T20:58:43.589Z",
    "CreatedBy": "00000000-0000-0000-0000-000000000000",
    "ModifiedBy": "00000000-0000-0000-0000-000000000000",
    "Id": "b381a350-d2d9-11e2-b65f-2dcf34e70f16"
  }
}
*/

Wondering what in the world those 2 extra arguments in JSON.stringify are? Check this out to learn more….

The response tells us that our "Yoda Dance" picture has an ID of b3511d70-d2d9-11e2-b65f-2dcf34e70f16. Let's make a request to get the "Files" record with that ID and see what we get:

$.ajax({
    url: 'https://api.everlive.com/v1/wEx9wdnIcxxehNty/Files/b3511d70-d2d9-11e2-b65f-2dcf34e70f16',
    type: "GET",
    success: function(data){
        console.log(JSON.stringify(data, null, 2));
    },
    error: function(error){
        console.log(JSON.stringify(error));
    }
});

/* The response:
{
  "Result": {
    "Id": "b3511d70-d2d9-11e2-b65f-2dcf34e70f16",
    "Filename": "Gif-Yoda-Dance.gif",
    "CreatedBy": "00000000-0000-0000-0000-000000000000",
    "Length": 511141,
    "ContentType": "image/gif",
    "CreatedAt": "2013-06-11T20:58:43.477Z",
    "ModifiedAt": "2013-06-11T20:58:43.477Z",
    "ModifiedBy": "00000000-0000-0000-0000-000000000000",
    "Uri": "https://api.everlive.com/v1/wEx9wdnIcxxehNty/Files/b3511d70-d2d9-11e2-b65f-2dcf34e70f16/Download"
  }
}
*/

Sweet - these most definitely are the droids we're looking for. If we used the Uri field above to include the image in our app, we'd see something similar to:

But Jim, we had to make TWO requests before we could actually include the image – one to get the Images record and another to get the Files record!

Yep - you're exactly right. And I definitely plan to explore ways we can optimize this in my next post. Let's stick to the plan for now…

The Code to Upload

Now that we've briefly looked at how Everlive stores our files (and links them to other content types), let's take a peek at the example I've put together that shows how we can upload images. You can clone your own copy here, but be aware you'll have to create your own Everlive back end and change the API key, etc.

It's not much to look at (but it gets the job done). We have two views, a list of images (and their titles), and a form to add an image to the list:

 


Helpers

Let's get to the interesting stuff right away! In the main.js file (on lines 42-92) we have a mimeMap lookup object and an AppHelper object which contains some helper methods we'll be using to both upload and display images:

// Lookup object we'll be using to map file
// extension to mime type values
var mimeMap = {
  jpg : "image/jpeg",
  jpeg: "image/jpeg",
  png : "image/png",
  gif : "image/gif"
};

var AppHelper = {
  // produces the 'download' url for a given
  // file record id. This allows us, for ex,
  // to src an image in an img tag, etc.
  resolveImageUrl: function (id) {
    if (id) {
      return el.Files.getDownloadUrl(id);
    } else {
      return '';
    }
  },
  // helper function to produce the base64
  // for a given file input item
  getBase64ImageFromInput: function (input, cb) {
    var reader = new FileReader();
    reader.onloadend = function (e) {
      if (cb) cb(e.target.result);
    };
    reader.readAsDataURL(input);
  },
  // produces the appropriate object structure
  // necessary for Everlive to store our file
  getImageFileObject: function (input, cb) {
    var name = input.name;
    var ext = name.substr(name.lastIndexOf('.') + 1).toLowerCase();
    var mimeType = mimeMap[ext];
    if (mimeType) {
      this.getBase64ImageFromInput(input, function (base64) {
        var res = {
          "Filename": name,
          "ContentType": mimeType,
          "base64": base64.substr(base64.lastIndexOf('base64,') + 7)
        };
        cb(null, res);
      });
    } else {
      cb("File type not supported: " + ext);
    }
  }
};
  • The AppHelper.resolveImageUrl method is simple enough. Give it an ID of a file (for example, b3511d70-d2d9-11e2-b65f-2dcf34e70f16) and you get back the download URL for the file: https://api.everlive.com/v1/wEx9wdnIcxxehNty/Files/b3511d70-d2d9-11e2-b65f-2dcf34e70f16/Download.
  • The AppHelper.getBase64ImageFromInput method uses a FileReader instance to read in the file which we want to upload and produce a base64 encoded result. This method isn't invoked directly by app code - instead it's used by the next method.
  • The AppHelper.getImageFileObject method takes one file of a file input form element (i.e. - fileInputElement.files[0] to get the first file) and constructs an object containing the base64 encoded image, MIME type and file name (which we'll serialize and include in the request to save it to Everlive). We find the MIME type by using our mimeMap lookup object (we only allow jpg, gif and png at the moment). The callback passed in as the second argument is invoked, with our 'image' metadata object passed in as the success argument (second argument…the first arg would be used if there is an error).

All that is just infrastructural code. The actual addImageViewModel contains the code that invokes these helpers. Let's look at that next….

Saving/Uploading an Image

In our main.js file (lines 138-190), you'll see the following code:

var addImageViewModel = {
  picName: '',
  picTitle: '',
  picSelected: false,
  onPicSet: function (e) {
    this.set('picSelected', true);
    this.set('picName', e.target.files[0].name);
  },
  onRemovePic: function () {
    this.set("picSelected", false);
    // reset the file upload selector
    $newPicture = $newPicture || $("#newPicture");
    $newPicture.replaceWith($newPicture = $newPicture.clone(true));
  },
  onAddPic: function () {
    $newPicture = $newPicture || $("#newPicture");
    $newPicture.click();
  },
  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));
        });
      });
  }
};

Nevermind the fact that I'm cheating in just alerting errors. I'm reigning in the desire to make this full-fledged and, instead, focusing on show the bare necessities in uploading files.

The important method to examine is saveItem. You'll see that it invokes our AppHelper.getImageFileObject method, passing in the first file from our file input as the first argument. The second argument - our callback function - handles making the calls to upload the file. Assuming no errors, the second argument – fileObj – is the object containing our file name, MIME type and base64 encoded image. We POST a request to Everlive, passing in the serialized fileObj as the request body data. Since jQuery's AJAX call returns a promise, our done callback is invoked when the request completes. The data argument in our done callback will contain the response data from uploading the file to Everlive. This is critical, since Everlive will return the ID of the newly created File record in this response – we'll need to update our view model with this ID to link it to the image.

So - inside the done callback we're doing the following:

  • adding a new "Image" to our local images collection
  • setting the Title and Picture properties on our new image
  • setting up a callback to navigate back to our image list once we've sync'ed the data source
  • sync-ing the data source (causing our new image record to be persisted to Everlive)
  • we reset the Add Image form by replacing the file input with a new clone

Displaying the Image

In our main.js file, we have an "imagesViewModel" for our list of images, and it uses the 'imageModel' for each record that appears in the list:

var imageModel = {
    id: 'Id',
    fields: {
        Title: {
            field: 'Title',
            defaultValue: ''
        },
        Picture: {
            fields: 'Picture',
            defaultValue: ''
        }
    },
    PictureUrl: function () {
        return AppHelper.resolveImageUrl(this.get('Picture'));
    }
};

Note that the PictureUrl method on our imageModel calls the AppHelper.resolveImageUrl method, passing in the ID of the Picture which we uploaded. This enables our template used to populate the list to bind the src attribute of an img tag to the result of this method call. You can see this in the template below, where we use the attr:{src: PictureUrl} for the img tag's data-bind value.

<script type="text/x-kendo-template" id="imageTemplate">
    <div>
        <div class="user-share">
            <img data-align="left" id="Picture" data-bind="attr:{src: PictureUrl}" width="75" />
            <a class="img-title">${Title}</a>
        </div>
    </div>
</script>

We'll Stop There…For Now

So far we've covered:

  • how Everlive stores files
  • how Everlive links files to other content types when you use the File field type
  • how to upload a file to Everlive
  • how to display/download a file saved to Everlive

This is enough to get you off the ground with storing files, but stay tuned since I plan to cover some of the options you have available in optimizing the retrieval of files, instead of increasing the number of HTTP roundtrips unecessarily.


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.