A nice feature of
When you need to store data in an Everlive back end, you create a "
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
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.stringifyare? Check this out to learn more….
The response tells us that our "Yoda Dance" picture has an ID b3511d70-d2d9-11e2-b65f-2dcf34e70f16
$.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…
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
It's not much to look at (but it gets the job done). We have two views, a list of images (and their
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);
}
}
};
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 https://api.everlive.com/v1/wEx9wdnIcxxehNty/Files/b3511d70-d2d9-11e2-b65f-2dcf34e70f16/DownloadAppHelper.getBase64ImageFromInput method uses 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.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, All that is just infrastructural code. The actual addImageViewModel contains the code that invokes these helpers. Let's look at that next….
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 saveItemAppHelper.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
So - inside the done callback we're doing the following:
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>
So far we've covered:
File field typeThis 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.
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.