Better example of PhoneGap, Parse, and uploading files
Join the DZone community and get the full member experience.
Join For FreeA few days back I posted about how Parse's JavaScript API now makes it easy to upload files via their SDK. The demo I built was very quick and simple, and while it made use of PhoneGap, it wasn't a great example of the technologies together. Before I spoke on Parse at PhoneGap Day (apparently videos will be posted soon, I'll share when they are) I whipped up a slightly nicer example. Let's take a look.
My example is (I'm sorry) another example of a Note taking app. However this time I've added the ability to attach pictures to a note. The home screen is a listing of your current notes, sorted by date.
Clicking the plus symbol takes you to a form allowing you to write a new note.
At this point you can select to take a picture. Now - for testing purposes in my iOS Simulator, I set the source to the local file system. In a real world app you would ask for the camera itself (or allow the user to select), but I wanted something quick and dirty.
Once you click Save, we then create a new Note object at Parse. The code has to determine if you've taken a picture or not and if you have, it will handle the upload for you. Now let's look at the code.
First - the home page. I'm using jQuery Mobile for the application and have placed both "pages" in the core index.html. Since there seems to be some confusion about this, let me be absolutely clear. jQuery Mobile does not make you use one html page. Period. In a case like this where I have a small app (2 pages), then it made sense for me to include them in one html file. That was 100% a personal choice and not anything jQuery Mobile forced me to do.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title></title> <meta name = "format-detection" content = "telephone=no"/> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js"></script> <script type="text/javascript" src="cordova.js"></script> <script src="js/parse-1.2.8.min.js"></script> <script src="js/app.js"></script> <style> div[data-role=content] img { max-width: 200px; } </style> </head> <body> <div data-role="page" id="home"> <div data-role="header" data-position="fixed"> <a href="#addNote" data-icon="plus" data-iconpos="notext" class="ui-btn-right">Add</a> <h1>Notebook</h1> </div> <div data-role="content"> </div> </div> <div data-role="page" id="addNote"> <div data-role="header"> <a href="#home" data-icon="home" data-iconpos="notext">Home</a> <h1>Notebook</h1> </div> <div data-role="content"> <h2>Add Note</h2> <textarea id="noteText"></textarea> <button id="takePicBtn">Add Pic</button> <button id="saveNoteBtn">Save</button> </div> </div> </body> </html>
The HTML here is pretty bare since almost all of the content is dynamic. Now let's take a look at app.js.
var parseAPPID = "supersecret"; var parseJSID = "mybankpinis1234"; //Initialize Parse Parse.initialize(parseAPPID,parseJSID); var NoteOb = Parse.Object.extend("Note"); $(document).on("pageshow", "#home", function(e, ui) { $.mobile.loading("show"); var query = new Parse.Query(NoteOb); query.limit(10); query.descending("createdAt"); query.find({ success:function(results) { $.mobile.loading("hide"); var s = ""; for(var i=0; i<results.length; i++) { //Lame - should be using a template s += "<p>"; s += "<h3>Note " + results[i].createdAt + "</h3>"; s += results[i].get("text"); var pic = results[i].get("picture"); if(pic) { s += "<br/><img src='" + pic.url() + "'>"; } s += "</p>"; } $("#home div[data-role=content]").html(s); },error:function(e) { $.mobile.loading("hide"); } }); }); $(document).on("pageshow", "#addNote", function(e, ui) { var imagedata = ""; $("#saveNoteBtn").on("touchend", function(e) { e.preventDefault(); $(this).attr("disabled","disabled").button("refresh"); var noteText = $("#noteText").val(); if(noteText == '') return; /* A bit complex - we have to handle an optional pic save */ if(imagedata != "") { var parseFile = new Parse.File("mypic.jpg", {base64:imagedata}); console.log(parseFile); parseFile.save().then(function() { var note = new NoteOb(); note.set("text",noteText); note.set("picture",parseFile); note.save(null, { success:function(ob) { $.mobile.changePage("#home"); }, error:function(e) { console.log("Oh crap", e); } }); cleanUp(); }, function(error) { console.log("Error"); console.log(error); }); } else { var note = new NoteOb(); note.set("text",noteText); note.save(null, { success:function(ob) { $.mobile.changePage("#home"); }, error:function(e) { console.log("Oh crap", e); } }); cleanUp(); } }); $("#takePicBtn").on("click", function(e) { e.preventDefault(); navigator.camera.getPicture(gotPic, failHandler, {quality:50, destinationType:navigator.camera.DestinationType.DATA_URL, sourceType:navigator.camera.PictureSourceType.PHOTOLIBRARY}); }); function gotPic(data) { console.log('got here'); imagedata = data; $("#takePicBtn").text("Picture Taken!").button("refresh"); } function failHandler(e) { alert("ErrorFromC"); alert(e); console.log(e.toString()); } function cleanUp() { imagedata = ""; $("#saveNoteBtn").removeAttr("disabled").button("refresh"); $("#noteText").val(""); $("#takePicBtn").text("Add Pic").button("refresh"); } });
First take a look at the pageshow event for #home. This is where we get data from Parse. This is done via a simple query that orders by object creation. I limit the count to 10 and if I wanted to could add paging.
The addNote logic is a bit more complex. Saving Parse data is asynchronous so if we need to store a file we have two, not one, async calls to make. Hence the big IF block that checks if we've got an existing selected image. To be honest this could be done a bit nicer perhaps. For example, the initial creation of the Note object could definitely be taken out of the IF clause, as well as the line where I set the text property. But in general I think you get the idea.
Anyway, I hope this is useful for folks. I've zipped up a copy of this application and attached it to the blog entry.
Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments