The Mega App model contains only one JavaScript object that represents the voice and photo memo data, as shown in the following code snippet:
var MemoItem = function(memoItem) { this.id = memoItem.id || "Memo_" + (new Date()).getTime(); this.title = memoItem.title || ""; this.desc = memoItem.desc || ""; this.type = memoItem.type || "voice"; this.location = memoItem.location || ""; this.mtime = memoItem.mtime || ""; };
The MemoItem
object contains the following attributes:
id
: This represents the memo
ID (its default value is unique as it includes a numeric value of the current time in milliseconds)title
: This represents the memo titledesc
: This represents the memo descriptiontype
: This represents the memo type, and it can be "voice"
or "photo"
(its default value is "voice"
)location
: This represents the location of the media (audio or photo) file in the device's filesystemmtime
: This represents the time when the memo was createdWe mainly have one service (MemoManager
) that is used by the app view controllers. The MemoManager
object contains the API needed to:
The MemoManager
object uses FileManager
in order to perform the required file operations, which will be illustrated later.
The following code snippet shows the first part of the
MemoManager
object:
var MemoManager = (function () { var instance; function createObject() { var MEMOS_KEY = "memos"; var APP_BASE_DIRECTORY = "Mega"; var audioMedia; var recordingMedia; var mediaFileName; return { getMemos: function () { var items = window.localStorage.getItem(MEMOS_KEY); if (items) { memoMap = JSON.parse(items); } else { memoMap = {}; } return memoMap; }, getMemoDetails: function (memoID) { var memoMap = this.getMemos(); return memoMap[memoID]; }, saveMemo: function (memoItem) { var memoMap = this.getMemos(); memoMap[memoItem.id] = memoItem; window.localStorage.setItem(MEMOS_KEY, JSON.stringify(memoMap)); }, removeMemo: function(memoID) { var memoMap = this.getMemos(); if (memoMap[memoID]) { delete memoMap[memoID]; } window.localStorage.setItem(MEMOS_KEY, JSON.stringify(memoMap)); }, removeAllMemos: function() { window.localStorage.removeItem(MEMOS_KEY); } // code is omitted for simplicity ... }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
As shown in the preceding code, the first part of MemoManager
is straightforward. It has the following methods, which are used to save, update, delete, and retrieve memos:
saveMemo(memoItem)
: This uses the window.localStorage.setItem()
method to save or update a memo item in the device's Local Storage by adding (updating) it to the memoMap
object. The memoMap
object is a JSON map whose key represents the memo item ID and value represents the memo item object.removeMemo(memoID)
: This removes the memo, whose ID is memoID
, from memoMap
and finally saves the updated memoMap
object in the device's local storage using the window.localStorage.setItem()
method.removeAllMemos()
: This uses the window.localStorage.removeItem()
method to remove memoMap
from the device's local storage.getMemoDetails(memoID)
: This gets the memo details from memoMap
using memoID
.getMemos()
: This gets all the app's memos by returning the stored memoMap
object. The stored memoMap
object is retrieved from the device's local storage using the window.localStorage.getItem()
method.The following code snippet shows the voice recording and playback parts of MemoManager
:
startRecordingVoice: function (recordingCallback) { var recordVoice = function(dirPath) { var basePath = ""; if (dirPath) { basePath = dirPath; } mediaFileName = (new Date()).getTime() + ".wav"; var mediaFilePath = basePath + mediaFileName; var recordingSuccess = function() { recordingCallback.recordSuccess(mediaFilePath); }; recordingMedia = new Media(mediaFilePath, recordingSuccess, recordingCallback.recordError); // Record audio recordingMedia.startRecord(); }; if (device.platform === "Android") { // For Android, store the recording in the app directory under the SD Card root if available ... var callback = {}; callback.requestSuccess = recordVoice; callback.requestError = recordingCallback.recordError; fileManager.requestDirectory(APP_BASE_DIRECTORY, callback); } else if (device.platform === "iOS") { // For iOS, store recording in app documents directory ... recordVoice("documents://"); } else { // Else for Windows Phone 8, store recording under the app directory recordVoice(); } }, stopRecordingVoice: function () { recordingMedia.stopRecord(); recordingMedia.release(); }, playVoice: function (filePath, playCallback) { if (filePath) { this.cleanUpResources(); audioMedia = new Media(filePath, playCallback.playSuccess, playCallback.playError); // Play audio audioMedia.play(); } }, cleanUpResources: function() { if (audioMedia) { audioMedia.stop(); audioMedia.release(); audioMedia = null; } if (recordingMedia) { recordingMedia.stop(); recordingMedia.release(); recordingMedia = null; } }
The
startRecordingVoice(recordingCallback)
method, starts the voice recording action, checks the current device's platform in order to save the audio file properly:
app
directory path ("Mega") in order to save the recorded media file under it. In order to do this, a call to fileManager.requestDirectory(APP_BASE_DIRECTORY, callback)
is performed in order to get the app
directory path and optionally create it if it does not exist under the device's SD card root using the Apache Cordova file API (or create it under the app's private data directory, (/data/data/[app_directory]
, if the SD card is not available). If the app
directory request operation succeeds, then recordVoice(dirPath)
will be called, in this case, and the app
directory path (dirPath
) will be passed as a parameter. The recordVoice()
function starts recording the voice using the Media
object's startRecord()
method. In order to create a Media
object, the following parameters are specified in the Media
object constructor:mediaFilePath
).recordingSuccess
which refers to the callback that will be invoked if the media operation succeeds. recordingSuccess
calls the original recordingCallback.recordSuccess
method specifying mediaFilePath
as a parameter.recordingCallback.recordError
which refers to the callback that will be invoked if the media operation fails.recordVoice()
is called with "documents://"
as the directory path (dirPath
) in order to save our app's media audio file under the Documents
directory of the app's Sandbox directory.In iOS, if we just specify the audio filename without any path in the Media
object constructor:
var recordingMedia = new Media("test.wav" ...);
Then "test.wav"
will be saved under the tmp
directory of our iOS app's Sandbox directory (if you do this, then your app users will be surprised to find, maybe after a while, that their saved audio files have been deleted as the tmp
directory can be cleaned automatically by iOS, so be aware of this).
Specifying the "documents://"
prefix before the media filename in the Media
object constructor will enforce the Media
object to save the "test.wav"
file under the app's Documents
directory:
var recordingMedia = new Media("documents://test.wav" ...);
else
block that represents the Windows Phone 8 case, recordVoice()
is called without specifying any parameters that tell the Media
object to save the audio file under the app
local directory.As you can see, we have to respect the nature of every supported platform in order to get the expected results.
The stopRecordingVoice()
method simply stops recording the voice by calling the stopRecord()
method of the Media
object and finally releases the used media resource by calling the release()
method of the Media
object.
The playVoice(filePath, playCallback)
method creates a Media
object that points to the file specified in filePath
, and then plays the file using the play()
method of the Media
object. The cleanUpResources()
method makes sure that all of the used media resources are cleaned up.
The following code snippet shows the photo capturing and picking part of MemoManager
:
getPhoto: function (capturingCallback, fromGallery) { var source = Camera.PictureSourceType.CAMERA; if (fromGallery) { source = Camera.PictureSourceType.PHOTOLIBRARY; } var captureSuccess = function(filePath) { //Copy the captured image from tmp to app directory ... var fileCallback = {}; fileCallback.copySuccess = function(newFilePath) { capturingCallback.captureSuccess(newFilePath); }; fileCallback.copyError = capturingCallback.captureError; if (device.platform === "Android") { //If it is Android then copy image file to App directory under SD Card root if available ... fileManager.copyFileToDirectory(APP_BASE_DIRECTORY, filePath, true, fileCallback); } else if (device.platform === "iOS") { //If it is iOS then copy image file to Documents directory of the iOS app. fileManager.copyFileToDirectory("", filePath, true, fileCallback); } else { //Else for Windows Phone 8, store the image file in the application's isolated store ... capturingCallback.captureSuccess(filePath); } }; navigator.camera.getPicture(captureSuccess, capturingCallback.captureError, { quality: 30, destinationType: Camera.DestinationType.FILE_URI, sourceType: source, correctOrientation: true }); }
The getPhoto(capturingCallback, fromGallery)
method, is used to get the photo by picking it from the device's gallery or by capturing it using the device's camera, it checks the fromGallery
parameter and if it is set to true
, then the picture source type is set to Camera.PictureSourceType.PHOTOLIBRARY
, and if it is set to false
, the picture source type will be Camera.PictureSourceType.CAMERA
.
A photo is obtained by calling the navigator.camera.getPicture()
method specifying the following parameters in order:
captureSuccess
: This refers to the callback that will be invoked if the getPicture()
operation succeedscapturingCallback.captureError
: This refers to the callback that will be invoked if the getPicture()
operation failsdestinationType
is set to Camera.DestinationType.FILE_URI
to get the image file URI, sourceType
is set to the picture source type, quality is set to 30
, and finally, correctOrientation
is set to true
)
captureSuccess
checks the current device's platform to properly save the picture file:
app
directory. In order to do this, a call to the fileManager.copyFileToDirectory(APP_BASE_DIRECTORY, filePath, true, fileCallback)
method is performed. The fileManager.copyFileToDirectory(dirPath, filePath, enforceUniqueName, fileCallback)
method has the following parameters placed in order:dirPath
: This represents the full path of the destination directory to which the file will be copied.filePath
: This represents the full path of the file to be copied.enforceUniqueName
: If this parameter is set to true
, this will enforce the copied file to have a new name in the destination directory.fileCallback
: This represents a callback object that includes the two callback attributes (copySuccess
, which will be called if the copy file operation succeeds, and copyError
, which will be called if the copy file operation fails).If the file-copy operation succeeds, then fileCallback.copySuccess
will be called. In this case, fileCallback.copySuccess
calls capturingCallback.captureSuccess(newFilePath)
, specifying the new copied file path (newFilePath
) as a parameter.
Documents
directory. In order to do this, a call to fileManager.copyFileToDirectory("", filePath, true, fileCallback)
is performed.In iOS, when any photo is captured using the device camera or picked from the device gallery using navigator.camera.getPicture()
, it is placed under the tmp
directory of the iOS app's Sandbox directory. If you want to access the captured image later, make sure to copy or move the captured image to the app's Documents
directory as shown earlier.
else
block that represents the Windows Phone 8 case, there is no need to do any file copy, as the image file (captured using the camera or picked from the device gallery) is automatically placed under the app
local directory.
FileManager
is very similar to the FileManager
object discussed in Chapter 5, Diving Deeper into the Cordova API (refer to this chapter if you feel uncomfortable with the highlighted code in the following code snippet):
var FileManager = (function () { var instance; function createObject() { var FILE_BASE = "file:///"; return { copyFileToDirectory: function (dirPath, filePath, enforceUniqueName, fileCallback) { var directoryReady = function (dirEntry) { if (filePath.indexOf(FILE_BASE) != 0) { filePath = filePath.replace("file:/", FILE_BASE); } window.resolveLocalFileSystemURL(filePath, function(file) { var filename = filePath.replace(/^.*[\/]/, ''), if (enforceUniqueName) { console.log("file name before: " + filename); filename = (new Date()).getTime() + filename; console.log("file name after: " + filename); } file.copyTo(dirEntry, filename, function(fileEntry) { fileCallback.copySuccess(dirEntry.toURL() + filename); }, fileCallback.copyError); }, fileCallback.copyError); }; var fileSystemReady = function(fileSystem) { fileSystem.root.getDirectory(dirPath, {create: true}, directoryReady); }; window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fileSystemReady, fileCallback.copyError); }, requestDirectory: function (dirPath, callback) { var directoryReady = function (dirEntry) { callback.requestSuccess(dirEntry.toURL()); }; var fileSystemReady = function(fileSystem) { fileSystem.root.getDirectory(dirPath, {create: true}, directoryReady); }; window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fileSystemReady, callback.requestError); } }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
Now that we are done with the model and API code of our Mega App, the next section will illustrate the Mega App's pages and view controllers.
3.145.125.51