First, we want to create a UI so the user can create a Celebrity object that is stored to the backend. Add the following to the <body>
tag of index.html
:
<div data-role="page" id="AddCelebrity">
<div data-role="header">
<a id="doneButton" class="ui-btn-right" onclick="HoodyoodooViewModel.doneAction()">Done</a>
<h1>Add a Celebrity</h1>
</div> <!-- /header -->
<div data-role="content">
<div data-role="fieldcontain">
<label for="firstNameField">First Name</label>
<input type="text" id="firstNameField"/>
</div>
<div data-role="fieldcontain">
<label for="lastNameField">Last Name</label>
<input type="text" id="lastNameField"/>
</div>
<center id = "fileUploaderContainer">
<div id="fileUploaderDropDiv" style="margin:0; padding:0;">
<img id= "addCelebImage" class="celebImage" src="Images/addphoto.png">
</div>
</center>
</div> <!-- /content -->
<div data-role="footer" data-position="fixed" class="nav-glyphish-example">
<div data-role="navbar" class="nav-glyphish-example">
<ul>
<li><a href="#WouldYa" id="acpwyt" data-icon="custom">WouldYa</a></li>
<li><a href="#" id="acpact" data-icon="custom">Add Celebrity</a></li>
</ul>
</div> <!-- /navbar -->
</div> <!-- /footer -->
<div data-role="popup" data-position-to="window" id="addCelebLoginPopup" data-theme="a" class="ui-content">
<h1>Please Register/Log In</h1>
<div data-role="fieldcontain">
<label for="addCelebUsernameField">Username</label>
<input type="text" id="addCelebUsernameField"/>
</div>
<div data-role="fieldcontain">
<label for="addCelebPasswordField">Password</label>
<input type="password" id="addCelebPasswordField"/>
</div>
<a id="addCelebrityLoginButton" data-role="button" onclick="HoodyoodooViewModel.login('addCelebrity')">Log In</a>
</div> <!-- /Login Popup for AddCelebrity -->
<div data-role="popup" data-position-to="window" id="addGenderPopup" data-theme="a" class="ui-content">
<h1>Select Gender</h1>
<a id="selectMaleButton" data-role="button" onclick="HoodyoodooViewModel.addGender('male')">Male</a>
<a id="selectFemaleButton" data-role="button" onclick="HoodyoodooViewModel.addGender('female')">Female</a>
</div> <!-- /Select Gender Popup for AddCelebrity -->
</div> <!-- /AddCelebrity -->
There are a few things going on here. First, we create a header containing a title for the page and a "Done" button. Next, in content, we create a form for inputting a celebrity's name, and for uploading a picture. Finally, in the footer, we have a navigation bar with two entries, one for this page and one for the "WouldYa" page, which we will add a little later. We also define the interface for two popups: one for user login and one asking for the gender of the celebrity.
The HTML we've just entered depends on a few JavaScript methods. First, let's edit the init
function of HoodyoodooViewModel. Here's the new version of the function, with new lines highlighted:
init: function() {
ff.setDebug(G_DEBUG);
m_doneButton = document.getElementById("doneButton");
m_firstNameField = document.getElementById("firstNameField");
m_lastNameField = document.getElementById("lastNameField");
m_addCelebImage = document.getElementById("addCelebImage");
m_loadingImageElement = document.getElementById("imgAjaxLoader");
m_celebrity = new Celebrity();
loader = new FileUploader();
loader.init();
}
This code fills in some of our member variables with references to some divs in our HTML. It also creates a new Celebrity object. Additionally, it creates a FileUploader object and initializes it. FileUploader is a class we use to enable drag-and-drop image uploading for our celebrities. Add the following to your hoodyoodoo.js file:
function FileUploader() {
// constants
var MAX_FILE_SIZE = 300000;
var CONTAINER_DIV = "fileUploaderContainer";
var DROP_DIV = "fileUploaderDropDiv";
var PREVIEW_IMG = "addCelebImage";
var self = {
containerDiv: null,
containerClass: "",
files: [],
fileBytes: null,
maxFileSize: 300000,
multiple: false,
containerWidth: 190, // 204 + 2*padding + border-top
containerHeight: 241, // 204 + 2*padding + border-top
init: function() {
this.containerDiv = document.getElementById(CONTAINER_DIV);
this.containerDiv.setAttribute("class", + this.containerClass);
var dropDiv = document.getElementById(DROP_DIV);
dropDiv.addEventListener('drop', drop, false);
dropDiv.addEventListener("dragenter", dragEnter, false);
dropDiv.addEventListener("dragexit", dragExit, false);
dropDiv.addEventListener("dragover", dragOver, false);
var previewImg = document.getElementById(PREVIEW_IMG);
previewImg.src = "Images/addphoto.png";
function dragEnter(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragExit(evt) {
evt.stopPropagation();
evt.preventDefault();
}
function dragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}
function drop(evt) {
evt.stopPropagation();
evt.preventDefault();
self.files = evt.dataTransfer.files;
file = self.files[0];
if(file.fileSize > this.maxFileSize) {
return;
}
// Only process image files.
var imageType = /image.*/;
if(!file.type.match(imageType)) {
return;
}
var reader = new FileReader();
reader.onerror = function(e) {
alert('Error code: ' + e.target.error);
};
// Create a closure to capture the file information.
reader.onload = (function(aFile) {
return function(evt) {
//dropDiv.setAttribute("class","hasImage");
previewImg.src = evt.target.result;
HoodyoodooViewModel.addImage();
}
})(file);
reader.readAsDataURL(file);
}
},
uploaderElement: function() {
if(this.containerDiv) return this.containerDiv;
else return false;
},
getFiles: function() {
if(self.files.length > 0) {
return self.files;
}
else return null;
},
getByteArray: function(file) {
if(self.fileBytes > 0) {
return self.fileBytes;
}
else return null;
}
}
return self;
}
Next, we need to add some members to the view model, HoodyoodooViewModel. The following member is called by FileUploader when it has finished, in order to add the file data to the Celebrity object we're building:
addImage:function() {
var picker = new FileUploader();
var reader = new FileReader();
reader.onerror = function(evt) {
var message;
switch(evt.target.error.code) {
case 1:
message = file.name + " not found.";
break;
case 2:
message = file.name + " has changed on disk, please re-try.";
break;
case 3:
messsage = "Upload cancelled.";
break;
case 4:
message = "Cannot read " + file.name + ".";
break;
case 5:
message = "File too large for browser to upload.";
break;
}
}
reader.onload = function(evt){
m_celebrity.imageData = evt.target.result;
}
reader.readAsArrayBuffer(file);
}
Next, we'll add a method for logging in to the API:
login: function(callback) {
if(callback == 'persistWouldYa') {
// implement later
} else if(callback == 'addCelebrity') {
self.showLoading(true);
var userName = document.getElementById("addCelebUsernameField").value;
var password = document.getElementById("addCelebPasswordField").value;
if(userName && password) {
ff.login(userName, password,
function(user) {
self.showLoading(false);
$("#addCelebLoginPopup").popup('close');
self.addCelebrity();
}, function(statusCode, responseText){
self.showLoading(false);
console.error("Error "+ statusCode + ": " + JSON.parse(responseText).statusMessage);
}
);
} else {
alert("You must provide both username and password");
}
} else if(callback == 'getTopCelebrity') {
// implement later
}
}
The method simple grabs the username and password entered into the form, then uses the NoServer JavaScript method login
to log in to the backend.
Next, there are a couple of utility methods that we need to define. The first, addGender
, is used to pop up a query box asking whether a celebrity is male or female, and to store the answer in our Celebrity object. The second, showLoading
, shows an animated graphic while things are loading. Here are the methods:
addGender: function(gender) {
$("#addGenderPopup").popup('close');
if(!gender) {
$("#addGenderPopup").popup('open');
} else if(gender == "male") {
m_celebrity.gender = "male";
self.addCelebrity();
} else if(gender == "female") {
m_celebrity.gender = "female";
self.addCelebrity();
}
},
showLoading:function(show) {
if(m_loadingImageElement == null) {
var i = document.createElement('img');
var b = document.getElementById("body");
i.id = "imgAjaxLoader";
i.setAttribute("src", "https://ajax.aspnetcdn.com/ajax/jquery.mobile/1.1.0/images/ajax-loader.gif");
i.setAttribute("class", "imgAjaxLoader");
b.appendChild(i);
m_loadingImageElement = document.getElementById("imgAjaxLoader");
}
if(show){
m_loadingImageElement.style.display = 'block';
} else {
m_loadingImageElement.style.display = 'none';
}
}
Now, we need to define the doneAction
method called when the "Done" button is pressed:
doneAction: function() {
if(m_firstNameField.value) m_celebrity.firstName = m_firstNameField.value;
if(m_lastNameField.value) m_celebrity.lastName = m_lastNameField.value;
self.addCelebrity();
}
This method simply copies the values provided in the form to the Celebrity object we created in the init
method, then calls a method called addCelebrity
. The addCelebrity
method is the heart of the page, as this is where our Celebrity object gets stored to our backend:
addCelebrity: function() {
// check logged in
if(!ff.loggedIn()) {
$("#addCelebLoginPopup").popup('open');
} else {
// check requisite info exists
if ((m_celebrity.firstName == null) || (m_celebrity.lastName == null)) {
alert("Add Celebrity Failed. You must provide first and last name for this celebrity");
}
else if (m_celebrity.imageData == null) {
alert("Add Celebrity Failed. You must provide an image for this celebrity");
}
else if (m_celebrity.gender == null) {
self.addGender();
}
else {
console.log("CelebrityViewController createCelebrity: all requisite items exist.");
console.log("CelebrityViewController createCelebrity newCelebString = " + JSON.stringify(m_celebrity));
self.showLoading(true);
ff.createObjAtUri(m_celebrity, "/Celebrity",
function(data) {
self.showLoading(false);
m_firstNameField.value = "";
m_lastNameField.value = "";
m_addCelebImage.src = "Images/addphoto.png";
m_celebrity = null;
}, function(statusCode, responseText){
self.showLoading(false);
console.error("Error "+ statusCode + ": " + JSON.parse(responseText).statusMessage);
}
);
}
}
}
After checking that the user is logged in—a requirement on the FatFractal platform if you want to create any backend objects—the code does some verification of the data. If all is well, then the FatFractal method createObjAtUri
is called, which stores our new Celebrity object in a collection called "/Celebrity".
That’s it! Now, we can select an image from your computer, enter the first and last name, add the gender and the new Celebrity is stored to your API.
As mentioned previously, NoServer APIs can store any type of object, not just JSON or text. In this case, we have stored a Celebrity object that contains an image (byte[]) with a single method and can retrieve it the same way. And that is what we will do next.
Go ahead and add a few celebrities to you API as we will need them for the next section.
Final result - successfully added a Celebrity:
NEXT: The WouldYa View