
Chapter 5. Diving Deeper into the Cordova API
In this chapter, we will continue our journey in the Apache Cordova API by exploring the remaining main features of the Cordova Exhibition app. You will learn how to work with Cordova's media, file, capture, notification, and storage APIs. You will also learn how to utilize the Apache Cordova events in your Cordova mobile app.
Media, file, and capture
The media plugin provides the ability to record and play back audio files on a device.
In order to use the media plugin in our Apache Cordova project, we need to use the following cordova plugin add
command:
> cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git
The capture plugin provides access to the device's audio, image, and video capture capabilities. In order to use the capture plugin in our Apache Cordova project, we need to use the following cordova plugin add
command:
> cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-media-capture.git
The file plugin provides access to the device's filesystem. In order to use the file plugin in our Apache Cordova project, we need to use the following cordova plugin add
command:
> cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git
Demo
In order to access the media, file, and capture demo, you can click on the Media, File, and Capture list item, respectively. You will then be introduced to the Media / File / Capture page. You can click on the Record Sound button in order to start recording. Once you complete recording, you can click on the Stop Recording button, as shown in the following screenshot, and you will be able to play back your recorded sound by clicking on the Playback button:

Record your voice
You also have the option to click on Record Sound Externally, which will open your device's default recording application in order to perform recording. Once you are done, you will return to the page, and then, you can use the Playback button to play back your recorded sound again.
The HTML page
The following code snippet shows the media page ("mediaFC"
):
<div data-role="page" id="mediaFC"> <div data-role="header"> <h1>Media / Capture</h1> <a href="#" data-role="button" data-rel="back" data-icon="back">Back</a> </div> <div data-role="content"> <h1>Welcome to the Media / Capture Gallery</h1> <p>Click 'Record Sound' or 'Record Sound Externally' button below to start recording your voice.</p> <input type="hidden" id="location"/> <div class="center-wrapper"> <input type="button" id="recordSound" data-icon="audio" value="Record Sound"/> <input type="button" id="recordSoundExt" data-icon="audio" value="Record Sound Externally"/> <input type="button" id="playSound" data-icon="refresh" value="Playback"/><br/> </div> <div data-role="popup" id="recordSoundDialog" data-dismissible="false" style="width:250px"> <div data-role="header"> <h1>Recording</h1> </div> <div data-role="content"> <div class="center-wrapper"> <div id="soundDuration"></div> <input type="button" id="stopRecordingSound" value="Stop Recording" class="center-button" data-inline="true"/> </div> </div> </div> </div> </div>
As shown in the preceding "mediaFC"
page, it contains the following:
- A page header that includes a back button
- Page content that includes the following elements:
"recordSound"
: This button is used to record sound using our app interface. Clicking on this button will show the"recordSoundDialog"
pop up to allow the user to stop the recording when the operation is finished."recordSoundExt"
: This button is used to record sound externally using the device's default recording app."playSound"
: This button is used to play the recorded sound."recordSoundDialog"
: This is a custom pop up that will be shown when the user clicks on the"recordSound"
button. It contains the"stopRecordingSound"
button, which is used to stop recording sound when the recording is finished.
View controller
The following code snippet shows the first main part of the "mediaFC"
page view controller JavaScript object:
(function() { var mediaManager = MediaManager.getInstance(), recInterval; $(document).on("pageinit", "#mediaFC", function(e) { e.preventDefault(); $("#recordSound").on("tap", function(e) { e.preventDefault(); disableActionButtons(); var callback = {}; callback.onSuccess = handleRecordSuccess; callback.onError = handleRecordError; mediaManager.startRecording(callback); var recTime = 0; $("#soundDuration").html("Duration: " + recTime + " seconds"); $("#recordSoundDialog").popup("open"); recInterval = setInterval(function() { recTime = recTime + 1; $("#soundDuration").html("Duration: " + recTime + " seconds"); }, 1000); }); $("#recordSoundExt").on("tap", function(e) { e.preventDefault(); disableActionButtons(); var callback = {}; callback.onSuccess = handleRecordSuccess; callback.onError = handleRecordError; mediaManager.recordVoiceExternally(callback); }); $("#recordSoundDialog").on("popupafterclose", function(e, ui) { e.preventDefault(); clearInterval(recInterval); mediaManager.stopRecording(); }); $("#stopRecordingSound").on("tap", function(e) { e.preventDefault(); $("#recordSoundDialog").popup("close"); }); $("#playSound").on("tap", function(e) { e.preventDefault(); disableActionButtons(); var callback = {}; callback.onSuccess = handlePlaySuccess; callback.onError = handlePlayError; mediaManager.playVoice($("#location").val(), callback); }); initPage(); }); $(document).on("pagebeforehide", "#mediaFC", function(e) { mediaManager.cleanUpResources(); enableActionButtons(); }); // code is omitted for simplicity ... })();
The "pageinit"
event handler registers the "tap"
event handler on the "recordSound"
, "recordSoundExt"
, "playSound"
, and "stopRecordingSound"
buttons.
In the "tap"
event handler of the "recordSound"
button:
- Sound recording and playing buttons are disabled by calling the
disableActionButtons()
method - In order to start recording sound:
- A call to
mediaManager.startRecording(callback)
is performed specifying a callback parameter with the success and error callbacks - The
"recordSoundDialog"
pop up is shown, and its"soundDuration"
div is updated every second with the current recording duration using thewindow's
setInterval()
method
- A call to
In the "tap"
event handler of the "recordSoundExt"
button:
- Sound recording and playing buttons are disabled by calling the
disableActionButtons()
method - In order to start recording sound externally, a call to
mediaManager.recordVoiceExternally(callback)
is performed specifying a callback parameter with the success and error callbacks
In the "tap"
event handler of the "stopRecordingSound"
button, it closes the "recordSoundDialog"
pop up in order to trigger the "popupafterclose"
event of the "recordSoundDialog"
pop up in the "popupafterclose"
event handler of the "recordSoundDialog"
pop up:
- The recording timer is stopped using the
window's
clearInterval()
method - In order to stop recording sound, a call to
mediaManager.stopRecording()
is performed
In the "tap"
event handler of the "playSound"
button:
- Sound recording and playing buttons are disabled by calling the
disableActionButtons()
method - In order to start playing the recorded sound, a call to
mediaManager.playVoice(filePath, callback)
is performed specifying afilePath
parameter with the media file location to play (media file location is stored in the"location"
hidden field when the recording operation succeeds) and a callback parameter with the success and error callbacks
The "pageinit"
event handler also calls initPage()
, whose code will be shown in the following code snippet. Finally, in the "pagebeforehide"
event handler, which will be called every time, we are transitioning away from the page. A call to mediaManager.cleanUpResources()
is performed in order to stop any playing sounds and clean up any used media resources when the media page is left.
The following code snippet shows the second main part of the "mediaFC"
page view controller, which mainly includes the callback handlers and the initPage()
method:
(function() { // code is omitted here for simplicity function initPage() { $("#playSound").closest('.ui-btn').hide(); } function handleRecordSuccess(filePath) { $("#location").val(filePath); enableActionButtons(); $("#playSound").closest('.ui-btn').show(); } function handleRecordError(error) { console.log("An error occurs during recording: " + error.code); enableActionButtons(); } function handlePlaySuccess() { console.log("Sound file is played successfully ..."); enableActionButtons(); } function handlePlayError(error) { if (error.code) { console.log("An error happens when playing sound file ..."); enableActionButtons(); } } // Code is omitted here for simplicity ... })();
As shown in the preceding code, we have the following methods:
initPage()
: This is called in the"pageinit"
event. It initially hides the"playSound"
button.handleRecordSuccess(filePath)
: This represents the success callback ofmediaManager.startRecording(callback)
andmediaManager.recordVoiceExternally(callback)
. It does the following:- It receives
filePath
of the recorded file as a parameter and saves it in the"location"
hidden field in order to be used by the playback operation - It enables the sound recording (
"recordSound"
and"recordSoundExt"
) and playback ("playsound"
) buttons - It shows the
"playSound"
button
- It receives
handleRecordError(error)
: This represents the error callback ofmediaManager.startRecording(callback)
andmediaManager.recordVoiceExternally(callback)
. It does the following:- It receives an
error
object as a parameter and the error code is logged in the console - It enables the sound recording and playback buttons
- It receives an
handlePlaySuccess()
: This represents the success callback ofmediaManager.playVoice(filePath, callback)
. It does the following:- It logs a successful message in the console
- It enables the sound recording and playing buttons
handlePlayError(error)
: This represents the error callback ofmediaManager.playVoice(filePath, callback)
. It does the following:- It logs an error message in the console
- It enables the sound recording and playing buttons
API
The following code snippet shows the first part of MediaManager.js
that interacts with the Cordova media and capture APIs:
var MediaManager = (function () { var instance; function createObject() { var fileManager = FileManager.getInstance(); var recordingMedia; var audioMedia; return { startRecording : function (callback) { var recordVoice = function(dirEntry) { var basePath = ""; if (dirEntry) { basePath = dirEntry.toURL() + "/"; } var mediaFilePath = basePath + (new Date()).getTime() + ".wav"; var recordingSuccess = function() { callback.onSuccess(mediaFilePath); }; recordingMedia = new Media(mediaFilePath, recordingSuccess, callback.onError); // Record audio recordingMedia.startRecord(); }; if (device.platform === "Android") { var cb = {}; cb.requestSuccess = recordVoice; cb.requestError = callback.onError; fileManager.requestApplicationDirectory(cb); } else { recordVoice(); } }, stopRecording : function () { if (recordingMedia) { recordingMedia.stopRecord(); recordingMedia.release(); recordingMedia = null; } }, playVoice : function (filePath, callback) { if (filePath) { this.cleanUpResources(); audioMedia = new Media(filePath, callback.onSuccess, callback.onError); // Play audio audioMedia.play(); } }, recordVoiceExternally: function (callback) { // code is omitted for simplicity ... }, cleanUpResources : function () { // code is omitted for simplicity ... } }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
As you can see in the preceding highlighted code, MediaManager
is a singleton object that has five methods. In order to record audio files using Apache Cordova, we can create a Media
object as follows:
recordingMedia = new Media(src, [mediaSuccess], [mediaError], [mediaStatus]);
The Media
object constructor has the following parameters in order:
src
: This refers to the URI of the media filemediaSuccess
: This is an optional parameter that refers to the callback, which will be called if the media operation (play/record or stop function) succeedsmediaError
: This is an optional parameter that refers to the callback, which will be called if the media operation (play/record or stop function) failsmediaStatus
: This is an optional parameter that executes to indicate status changes
In order to start recording an audio file, a call to the startRecord()
method of the Media
object must be performed. When the recording is finished, a call to the stopRecord()
method of the Media
object must be performed. Now, let's check out the details of the MediaManager
methods:
startRecording(callback)
: This starts the audio recording by doing the following:- Getting the current device platform by calling
device.platform
. - If the current platform is Android, then a call to
fileManager.requestApplicationDirectory(cb)
is performed in order to create an application directory (if it hasn't already been created) under the device SD card's root directory using thefileManager
object. If the directory creation operation succeeds, thencb.requestSuccess
will be called, in this case, and the application directory path will be passed as a parameter. TherecordVoice()
method starts recording the sound and saves the result audio file under the application directory. Note that if there is no SD card in your Android device, then the application directory will be created under the app's private data directory (/data/data/[app_directory]
), and the audio file will be saved under it. - In the
else
block, which refers to the other supported platforms (Windows Phone 8 and iOS),recordVoice()
is called without creating an application-specific directory. As you know from Chapter 2, Developing Your First Cordova Application, in iOS and Windows Phone 8, every application has a private directory, and applications cannot store their files in any place other than this directory using Apache Cordova APIs. In the case of iOS, the application audio files will be stored under thetmp
directory of the applicationsandbox
directory (the application private directory). In the case of Windows Phone 8, the audio files will be stored under the application's local directory. As you know from Chapter 2, Developing Your First Cordova Application, using the Windows Phone 8 native API (Window.Storage
), you can read and write files in an SD card with some restrictions; however, until this moment, you cannot do this using the Apache Cordova API. - In
recordVoice()
,startRecording(callback)
starts creating a media file using theMedia
object's (recordingMedia
)startRecord()
method. After calling therecordingMedia
object'sstopRecord()
method and if the recording operation succeeds, thencallback.onSuccess
will be called and the audio file's full path,mediaFilePath
will be passed as a parameter. If the recording operation fails, thencallback.onError
will be called.
- Getting the current device platform by calling
stopRecording()
: This stops the audio recording by doing the following:- Calling
stopRecord()
ofrecordingMedia
in order to stop recording - Calling
release()
ofrecordingMedia
in order to release the underlying operating system's audio resources
- Calling
playVoice(filePath, callback)
: This plays an audio file by doing the following:- Cleaning up resources before playing the audio file by calling the
cleanUpResources()
method, which will be shown in the following code snippet - Creating a
Media
object (audioMedia
) specifyingfilePath
as the media source,callback.onSuccess
as the media success callback, andcallback.onError
as the media error callback - Calling the
play()
method of theaudioMedia
object
- Cleaning up resources before playing the audio file by calling the
The following code snippet shows the second part of MediaManager.js
:
var MediaManager = (function () { var instance; function createObject() { // ... return { // ... recordVoiceExternally: function (callback) { var onSuccess = function (mediaFiles) { if (mediaFiles && mediaFiles[0]) { var currentFilePath = mediaFiles[0].fullPath; if (device.platform === "Android") { var fileCopyCallback = {}; fileCopyCallback.copySuccess = function(filePath) { callback.onSuccess(filePath); }; fileCopyCallback.copyError = callback.onError; fileManager.copyFileToAppDirectory(currentFilePath, fileCopyCallback); } else { callback.onSuccess(currentFilePath); } } }; navigator.device.capture.captureAudio(onSuccess, callback.onError, {limit: 1}); }, cleanUpResources : function () { if (audioMedia) { audioMedia.stop(); audioMedia.release(); audioMedia = null; } if (recordingMedia) { recordingMedia.stop(); recordingMedia.release(); recordingMedia = null; } } }; }; // ... })();
In order to record the audio files using the device's default audio recording app, we can use the captureAudio
method of Cordova's capture
object as follows:
navigator.device.capture.captureAudio(captureSuccess, captureError, [options])
The captureAudio()
method has the following parameters:
captureSuccess
: This will be called when the audio capture operation is performed successfully. It receives an array ofMediaFile
as a parameter. As shown in the following table, these are the attributes ofMediaFile
:captureError
: This will be called when the audio capture operation fails. It receives aCaptureError
object as a parameter. TheCaptureError
object has acode
attribute, which represents the error code.options
: This represents the options of capture configuration. The following table shows theoptions
attributes:

The preceding code snippet shows the other methods of the MediaManager
object as follows:
recordVoiceExternally
(callback
): This starts audio recording using the device's default recording app by doing the following:- In order to start audio recording using the device's default recording app,
navigator.device.capture.captureAudio(onSuccess, callback.onError, {limit: 1})
is called. This means thatonSuccess
is set as the success callback,callback.onError
is set as the error callback, and finally,options
is set to{limit: 1}
in order to limit the maximum number of audio clips that the device user can record in a single capture to1
. - In the
onSuccess
callback, if the current platform is Android, then a call tofileManager.copyFileToAppDirectory(currentFilePath, fileCopyCallback)
is performed in order to copy the recorded file to theapp
directory using thefileManager
object. If the copy operation succeeds, then the originalrecordVoiceExternally()
method'scallback.onSuccess(filePath)
will be called in this case and the new copied file path under theapp
directory (filePath
) will be passed as a parameter. - If the current platform is not Android (in our case, Windows Phone 8 and iOS),
callback.onSuccess(currentFilePath)
will be called and the current filepath (currentFilePath
) will be passed as a parameter.
- In order to start audio recording using the device's default recording app,
cleanUpResources()
: This makes sure that all resources are cleaned up by callingstop()
andrelease()
methods of all theMedia
objects.
Tip
As the current implementation of the media plugin does not adhere to the W3C specification for media capture, a future implementation is considered for compliance with the W3C specification, and the current APIs might be deprecated.
Before going into the details of the FileManager.js
file, note that the Media
object has more methods that you can check out in the Apache Cordova Documentation at https://github.com/apache/cordova-plugin-media/blob/master/doc/index.md.
Cordova Capture also has more objects and methods that you can look at in the Apache Cordova Documentation at https://github.com/apache/cordova-plugin-media-capture/blob/master/doc/index.md.
The following code snippet shows the first part of FileManager.js
, which is used by MediaManager.js
:
var FileManager = (function () { var instance; function createObject() { var BASE_DIRECTORY = "CExhibition"; var FILE_BASE = "file:///"; return { copyFileToAppDirectory: function (filePath, cb) { var callback = {}; callback.requestSuccess = function (dirEntry) { if (filePath.indexOf(FILE_BASE) != 0) { filePath = filePath.replace("file:/", FILE_BASE); } window.resolveLocalFileSystemURL(filePath, function(file) { var filename = filePath.replace(/^.*[\\\/]/, ''); var copyToSuccess = function (fileEntry) { console.log("file is copied to: " + fileEntry.toURL()); cb.copySuccess(fileEntry.toURL()); }; file.copyTo(dirEntry, filename, copyToSuccess, cb.copyError); }, cb.copyError); }; callback.requestError = function (error) { console.log(error); }; this.requestApplicationDirectory(callback); }, requestApplicationDirectory: function (callback) { var fileSystemReady = function(fileSystem) { fileSystem.root.getDirectory(BASE_DIRECTORY, {create: true}, callback.requestSuccess); }; window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fileSystemReady, callback.requestError); } }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
As you can see in the preceding highlighted code, FileManager
is a singleton object that has two methods. In order to work with directories or files using Apache Cordova, we first need to request a filesystem using the requestFileSystem()
method as window.requestFileSystem(type, size, successCallback, errorCallback)
.
The window.requestFileSystem
method has the following parameters in order:
type
: This refers to the local filesystem typeSize
: This indicates how much storage space, in bytes, the application expects to needsuccessCallback
: This will be called if the operation succeeds, and it will receive aFileSystem
object as a parametererrorCallback
: This will be called if an operation error occurs
In order to create a directory after getting the FileSystem
object, we can use the getDirectory()
method of the DirectoryEntry
object as fileSystem.root.getDirectory(path, options, successCallback, errorCallback)
.
The directoryEntry.getDirectory
method takes the following parameters:
path
: This is either a relative or absolute path of the directory in which we can look up or create a directoryoptions
: This refers to anoptions
JSON object that specifies thecreate
directory using{create: true}
or exclusively creates the directory using{create: true, exclusive: true}
successCallback
: This will be called if the operation succeeds, and it receives the new or existingDirectoryEntry
as a parametererrorCallback
: This will be called if an operation error occurs
If you look at the first method requestApplicationDirectory(callback)
of the FileManager
object, you will find that it creates a directory called "CExhibition"
if it has not already been created (in the case of an Android device with an SD card, "CExhibition"
will be created under the SD card root).
In order to get an Entry
object of a specific URI to perform a file or directory operation, we need to use resolveLocalFileSystemURL()
as window.resolveLocalFileSystemURL(uri, successCallback, errorCallback)
.
The window.resolveLocalFileSystemURL
method takes the following parameters:
uri
: This is a URI that refers to a local file or directorysuccessCallback
: This will be called if the operation succeeds, and it will receive anEntry
object that corresponds to the specified URI (it can beDirectoryEntry
orFileEntry
) as a parametererrorCallback
: This will be called if an operation error occurs
In order to copy a file, we need to use the copyTo()
method of the Entry
object as fileEntry.copyTo(parent, newName, successCallback, errorCallback)
the Entry
object.
The fileEntry.copyTo
method takes the following parameters:
parent
: This represents the directory to which the entry will be copiednewName
: This represents the new name of the copied file, and it defaults to the current namesuccessCallback
: This will be called if the operation succeeds, and it will receive the new entry object as a parametererrorCallback
: This will be called if an operation error occurs
If you look at the second method copyFileToAppDirectory (filePath, cb)
of the FileManager
object, you will find that it creates an app
directory called "CExhibition"
if it has not already been created. Then, it copies the file specified in filePath
under the app
directory using the copyTo()
method of the fileEntry
object. Finally, if the copy operation succeeds, then the cb.copySuccess()
callback will be called and the new copied file path will be passed as a parameter.
Tip
The Cordova file has more objects and methods that you can have a look at in the Apache Cordova Documentation at https://github.com/apache/cordova-plugin-file/blob/master/doc/index.md.
Now, we are done with the media, file, and capture functionalities in the Cordova Exhibition app.
Notification
The notification plugin provides the ability to create visual, audible, and tactile device notifications. In order to use the notification plugin in our Apache Cordova project, we need to use the following cordova plugin add
command:
> cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-vibration.git > cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-dialogs.git
Demo
In order to access the notification demo, you can click on the Notification list item. You will be introduced to the Notification page. You can click on one of the available buttons to see, hear, and feel the different available notifications. The following screenshot shows the result of clicking on the Show Prompt button, which shows a prompt dialog to have the user input:

The notification prompt
You also have the option to show an alert message and confirmation dialog. You can vibrate the device by clicking on the Vibrate button, and finally, you can make the device beep by clicking on the Beep button.
The HTML page
The following code snippet shows the "notification"
page:
<div data-role="page" id="notification"> <div data-role="header"> <h1>Notification</h1> <a href="#" data-role="button" data-rel="back" data-icon="back">Back</a> </div> <div data-role="content"> <h1>Welcome to the Notification Gallery</h1> <p>Click the buttons below to check notifications.</p> <input type="button" id="showAlert" value="Show Alert"/> <input type="button" id="showConfirm" value="Show Confirm"/> <input type="button" id="showPrompt" value="Show Prompt"/> <input type="button" id="vibrate" value="Vibrate"/> <input type="button" id="beep" value="Beep"/> <div id="notificationResult"> </div> </div> </div>
The preceding "notification"
page contains the following:
- A page header that includes a back button.
- Page content that includes five buttons:
"showAlert"
to show an alert,"showConfirm"
to show a confirmation dialog,"showPrompt"
to show a prompt dialog,"vibrate"
to vibrate the device, and finally,"beep"
to make the device beep. It also has a"notificationResult"
div to display the notification result.
View controller
The following code snippet shows the "notification"
page view controller JavaScript object, which includes the event handlers of the page (notification.js
):
(function() { var notificationManager = NotificationManager.getInstance(); $(document).on("pageinit", "#notification", function(e) { e.preventDefault(); $("#showAlert").on("tap", function(e) { e.preventDefault(); notificationManager.showAlert("This is an Alert", onOk, "Iam an Alert", "Ok"); }); $("#showConfirm").on("tap", function(e) { e.preventDefault(); notificationManager.showConfirm("This is a confirmation", onConfirm, "Iam a confirmation", "Ok,Cancel"); }); $("#showPrompt").on("tap", function(e) { e.preventDefault(); notificationManager.showPrompt("What is your favorite food?", onPrompt, "Iam a prompt", ["Ok", "Cancel"], "Pizza"); }); $("#vibrate").on("tap", function(e) { e.preventDefault(); notificationManager.vibrate(2000); }); $("#beep").on("tap", function(e) { e.preventDefault(); notificationManager.beep(3); }); }); function onOk() { $("#notificationResult").html("You clicked Ok<br/>"); } function onConfirm(index) { $("#notificationResult").html("You clicked " + ((index == 1) ? "Ok":"Cancel") + "<br/>"); } function onPrompt(result) { if (result.buttonIndex == 1) { $("#notificationResult").html("You entered: " + result.input1); } } })();
As shown in the preceding code snippet, the "pageinit"
event handler registers the "tap"
event handlers on the "showAlert
", "showConfirm
", "showPrompt
", "vibrate
", and "beep"
buttons.
In the "tap"
event handler of the "showAlert"
button, an alert is shown by calling the notificationManager.showAlert(message, callback, title, buttonName)
method specifying the message to display ("This is an Alert"
), the callback to be called when the dialog is dismissed (onOk
), the dialog title ("Iam an Alert"
), and finally, the button name (Ok
). In onOk
, the "You clicked Ok"
message is displayed in the "notificationResult"
div.
In the "tap"
event handler of the "showConfirm"
button, a confirmation dialog is shown by calling the notificationManager.showConfirm(message, callback, title, buttonLabels)
method specifying the message to display ("This is a confirmation"
), the callback to be called when the dialog is dismissed or if any of the confirmation dialog buttons is clicked (onConfirm
), the dialog title ("I am a confirmation"
), and finally, the button labels, which are represented using a comma-separated string that specify button labels ("Ok,Cancel"
). In onConfirm(index)
, the clicked button is displayed in the notificationResult
div using the received index
callback parameter, which represents the index of the pressed button. Note that index
uses one-based indexing, which means that the "Ok"
button has the index 1
and the "Cancel"
button has the index 2
.
In the "tap"
event handler of the "showPrompt"
button, a confirmation dialog is shown by calling the notificationManager.showPrompt(message, callback, title, buttonLabels, defaultText)
method specifying the message to display ("What is your favorite food?"
), the callback to be called when any of the prompt dialog buttons is clicked (onPrompt
), the dialog title ("Iam a prompt"
), the button labels, which are represented as an array of strings that specify button labels (["Ok", "Cancel"]
), and finally, the default input text ("Pizza"
). In onPrompt(result)
, result.buttonIndex
represents the button index (which is one-based indexing) that is clicked. If the "Ok"
button (which has the index 1
) is clicked, then the user input is obtained using result.input1
.
In the "tap"
event handler of the "vibrate"
button, the device is vibrated by calling the notificationManager.vibrate(milliseconds)
method specifying milliseconds to vibrate the device (2,000 milliseconds).
In the "tap"
event handler of the "beep"
button, the device is made to beep by calling the notificationManager.beep(times)
method specifying the times to repeat the beep (three times).
API
The following code snippet shows NotificationManager.js
:
var NotificationManager = (function () { var instance; function createObject() { return { showAlert: function (message, callback, title, buttonName) { navigator.notification.alert(message, callback, title, buttonName); }, showConfirm: function (message, callback, title, buttonLabels) { navigator.notification.confirm(message, callback, title, buttonLabels); }, showPrompt: function (message, callback, title, buttonLabels, defaultText) { navigator.notification.prompt(message, callback, title, buttonLabels, defaultText); }, beep: function (times) { navigator.notification.beep(times); }, vibrate: function (milliseconds) { navigator.notification.vibrate(milliseconds); } }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
As shown, NotificationManager
is a singleton object that does a simple wrapping for the Cordova Notification API. It has the following methods:
showAlert(message, callback, title, buttonName)
: This shows an alert by calling thenavigator.notification.alert()
method. Thenavigator.notification.alert(message, callback, [title], [buttonName])
method has the following parameters:message
: This represents the alert messageCallback
: This represents the callback to be called when the alert is dismissedTitle
: This is an optional parameter that represents the alert title (the default value is"Alert"
)buttonName
: This represents the button name (the default value is"Ok"
)
showConfirm(message, callback, title, buttonLabels)
: This shows a confirmation dialog by calling thenavigator.notification.confirm()
method. Thenavigator.notification.confirm(message, callback, [title], [buttonLabels])
method has the following parameters:message
: This represents the dialog message.callback(index)
: This represents the callback to be called when the user presses one of the buttons in the confirmation dialog. It receives anindex
parameter that represents the pressed button's index, which starts from1
.title
: This is an optional parameter that represents the dialog title (the default value is"Confirm"
).buttonLabels
: This is an optional parameter that represents a comma-separated string that specifies button labels (the default value is"Ok", Cancel"
).
showPrompt(message, callback, title, buttonLabels, defaultText)
: This shows a prompt dialog by calling thenavigator.notification.prompt()
method. Thenavigator.notification.prompt(message, promptCallback, [title], [buttonLabels], [defaultText])
method has the following parameters:Message
: This represents the dialog message.promptCallback(results)
: This represents the callback to be called when the user presses one of the buttons in the prompt dialog. It receives aresults
parameter that has the following attributes:buttonIndex
, which represents the pressed button's index, which starts from1
andinput1
, which represents the text entered by the user in the prompt dialog box.title
: This is an optional parameter that represents the dialog title (the default value is"Prompt"
).buttonLabels
: This is an optional parameter that represents a string array, which specifies button labels (the default value is["OK","Cancel"]
).defaultText
: This is an optional parameter that represents the default text input value of the prompt dialog (the default value is an empty string).
beep(times)
: This makes the device beeps by calling thenavigator.notification.beep()
method. Thenavigator.notification.beep(times)
method has the following parameter:times
: This represents the number of times to repeat the beep.
vibrate(milliseconds)
: This vibrates the device by calling thenavigator.notification.vibrate()
method. Thenavigator.notification.vibrate(milliseconds)
method has the following parameter:milliseconds
: This represents the milliseconds to vibrate the device.
Now, we are done with the notification functionality in the Cordova Exhibition app.
Storage
The Cordova Storage API provides the ability to access the device storage options based on three popular W3C specifications:
- Web Storage API Specification, which allows you to access data using simple key/value pairs (which we will demonstrate in our
"Storage"
demo). - Web SQL Database Specification, which offers full-featured database tables, which can be accessed using SQL. Note that this option is only available in Android, iOS, BlackBerry 10, and Tizen and not supported on other platforms.
- IndexedDB Specification is an API for the client-side storage and high performance. It searches on the stored data using indexes. Note that this option is available in Windows Phone 8 and BlackBerry 10.
Demo
In order to use the Storage API, there is no need for a CLI command to run, as it is built in Cordova. In order to access the Storage demo, you can do it by clicking on the Storage list item. You will be introduced to the Storage page. On the Storage page, the users can enter their names and valid e-mails and then click on the Save button in order to save the information, as shown in the following screenshot:

Saving user information
You can exit the app and then open the Storage page again; you will find that your saved information is reflected in the Name and Email fields. At any point, you can click on the Reload button in order to reload input fields with your saved data.
The HTML page
The following code snippet shows the "storage"
page:
<div data-role="page" id="storage"> <div data-role="header"> <h1>Storage</h1> <a href="#" data-role="button" data-rel="back" data-icon="back">Back</a> </div> <div data-role="content"> <h1>Welcome to the Storage Gallery</h1> <p>Persist your information using Cordova Web Storage API.</p> <form id="storageForm"> <div class="ui-field-contain"> <label for="userName">Name</label> <input type="text" id="userName" name="userName"></input> </div> <div class="ui-field-contain"> <label for="userEmail">Email</label> <input type="text" id="userEmail" name="userEmail"></input> </div> <div class="center-wrapper"> <input type="button" id="saveInfo" data-icon="action" value="Save" data-inline="true"/> <input type="button" id="reloadInfo" data-icon="refresh" value="Reload" data-inline="true"/> </div> <ul id="storageMessageBox"></ul> <div id="storageResult"> </div> </form> </div> </div>
The preceding "storage"
page contains the following:
- A page header that includes a back button
- Page content that includes the
"storageForm"
form, which includes the following elements:- "
userName
": This is the user's name text field "userEmail"
: This is the user's email text field"saveInfo"
: This button is used to persist user information"reloadInfo"
: This button is used to reload saved user information in the"userName"
and"userEmail"
fields"messageBox"
: This is an unordered list that displays form validation errors"storageResult"
: This is a div that displays the storage operation result
- "
View controller
The following code snippet shows the "storage"
page view controller JavaScript object that includes the event handlers of the page (storage.js
):
(function() { var storageManager = StorageManager.getInstance(); var INFO_KEY = "cordovaExhibition.userInfo"; $(document).on("pageinit", "#storage", function(e) { e.preventDefault(); $("#saveInfo").on("tap", function(e) { e.preventDefault(); if (! $("#storageForm").valid()) { return; } storageManager.set(INFO_KEY, JSON.stringify({ userName: $("#userName").val(), userEmail: $("#userEmail").val() }) ); $("#storageResult").html("User Information are saved"); }); $("#reloadInfo").on("tap", function(e) { e.preventDefault(); reloadUserInfo(); $("#storageResult").html("Reloading completes"); }); }); $(document).on("pageshow", "#storage", function(e) { e.preventDefault(); $("#storageForm").validate({ errorLabelContainer: "#storageMessageBox", wrapper: "li", rules: { userName: "required", userEmail: { required: true, email: true } }, messages: { userName: "Please specify user name", userEmail: { required: "Please specify email", email: "Please enter valid email" } } }); reloadUserInfo(); }); function reloadUserInfo() { var userInfo = JSON.parse(storageManager.get(INFO_KEY)); populateFormFields(userInfo); } function populateFormFields(userInfo) { if (userInfo) { $("#userName").val(userInfo.userName); $("#userEmail").val(userInfo.userEmail); } } })();
As shown in the preceding highlighted code snippet, the "pageinit"
event handler registers the "tap"
event handlers on the "saveInfo"
and "reloadInfo"
buttons.
Tip
In order to validate our "storage"
form, we use the jQuery validation plugin, which can be found at http://jqueryvalidation.org. In order to use the plugin, all we need to do is include the jquery.validate.min.js
file below the jquery.js
file that will be shown in the index.html
file in Finalizing the Cordova Exhibition App section. After including the jQuery validation plugin JS file, we can simply use the plugin by defining the validation rules on the form fields using the form's validate()
method and then validate the form using the form's valid()
method, as shown in the "storage"
page view controller code.
In the "tap"
event handler of the "saveInfo"
button:
- The
"storageForm"
is validated using the$("#storageForm").valid()
method. - If the form is valid, then both the
"userName"
and"userEmail"
valid input text values are set as attributes in a JSON object, which is converted to a string usingJSON.stringify()
. Finally, thestringified
JSON object is persisted in the device storage by calling thestorageManager.set(key, value)
specifying key to beINFO_KEY
and the value to be thestringified
JSON object.
In the "tap"
event handler of the "reloadInfo"
button:
- The user information is retrieved by calling
reloadUserInfo()
. ThereloadUserInfo()
method callsstorageManager.get(INFO_KEY)
in order to get the storedstringified
JSON object and then useJSON.parse()
in order to convert thestringified
JSON object to a JSON object (userInfo
). - Using
populateFormFields(userInfo)
,userInfo
is populated to both"userName"
and"userEmail"
input text elements.
In the "pageshow"
event of the "storage"
page, our "storageForm"
form validation is constructed by specifying the options
parameter of the form's validate()
method as follows:
errorLabelContainer
: This is set to"storageMessageBox"
to display the validation errorswrapper
: This is set to"li"
to wrap the error messages in list itemsrules
object is set as follows:userName
: This is set to requireduserEmail
: This is set to be an e-mail and required
messages
object specifiesuserName
anduserEmail
validation error messages
Finally, in the "pageshow"
event of the "storage"
page, reloadUserInfo()
is called to reload the user information in the "userName"
and "userEmail"
input text elements.
API
The following code snippet shows StorageManager.js
that does a simple wrapping for two localStorage
methods:
var StorageManager = (function () { var instance; function createObject() { return { set: function (key, value) { window.localStorage.setItem(key, value); }, get: function (key) { return window.localStorage.getItem(key); } }; }; return { getInstance: function () { if (!instance) { instance = createObject(); } return instance; } }; })();
As you can see in the preceding highlighted code, StorageManager
is a singleton object that has the following methods:
set(key, value)
: This persists the key/value pair in the local storage by calling thewindow.localStorage.setItem(key, value)
methodget(key)
: This gets the stored value using the passed key parameter by calling thewindow.localStorage.getItem(key)
method
Tip
The complete W3C Web Storage specification is available at at at http://www.w3.org/TR/IndexedDB/.
Now, we are done with the storage functionality in the Cordova Exhibition app.
Finalizing the Cordova Exhibition app
The last part we need to check is index.html
; the following code snippet shows this part, which is the most important, of the index.html
page:
<!DOCTYPE html> <html> <head> <!-- omitted code ... --> <link rel="stylesheet" type="text/css" href="css/app.css" /> <link rel="stylesheet" href="jqueryMobile/jquery.mobile-1.4.0.min.css"> <script src="jqueryMobile/jquery-1.10.2.min.js"></script> <script src="jqueryMobile/jquery.mobile-1.4.0.min.js"></script> <script> var deviceReadyDeferred = $.Deferred(); var jqmReadyDeferred = $.Deferred(); $(document).ready(function() { document.addEventListener("deviceready", function() { deviceReadyDeferred.resolve(); }, false); }); $(document).on("mobileinit", function () { jqmReadyDeferred.resolve(); }); $.when(deviceReadyDeferred, jqmReadyDeferred).then(function () { //Now everything loads fine, you can safely go to the app home ... $.mobile.changePage("#features"); }); </script> <script src="jqueryMobile/jqm.page.params.js"></script> <script src="jqueryMobile/jquery.validate.min.js"></script> <script src="js/common.js"></script> <title>Cordova Exhibition</title> </head> <body> <div id="loading" data-role="page"> <div class="center-screen">Please wait ...</div> </div> <!-- Other pages are placed here ... --> <script type="text/javascript" src="cordova.js"></script> <!-- API JS files --> <script type="text/javascript" src="js/api/AccelerometerManager.js"></script> <script type="text/javascript" src="js/api/FileManager.js"></script> <script type="text/javascript" src="js/api/CameraManager.js"></script> <script type="text/javascript" src="js/api/CompassManager.js"></script> <script type="text/javascript" src="js/api/ConnectionManager.js"></script> <script type="text/javascript" src="js/api/ContactsManager.js"></script> <script type="text/javascript" src="js/api/DeviceManager.js"></script> <script type="text/javascript" src="js/api/GeolocationManager.js"></script> <script type="text/javascript" src="js/api/GlobalizationManager.js"></script> <script type="text/javascript" src="js/api/InAppBrowserManager.js"></script> <script type="text/javascript" src="js/api/MediaManager.js"></script> <script type="text/javascript" src="js/api/NotificationManager.js"></script> <script type="text/javascript" src="js/api/StorageManager.js"></script> <!-- View controller files --> <script type="text/javascript" src="js/vc/accelerometer.js"></script> <script type="text/javascript" src="js/vc/camera.js"></script> <script type="text/javascript" src="js/vc/compass.js"></script> <script type="text/javascript" src="js/vc/connection.js"></script> <script type="text/javascript" src="js/vc/contacts.js"></script> <script type="text/javascript" src="js/vc/contactDetails.js"></script> <script type="text/javascript" src="js/vc/device.js"></script> <script type="text/javascript" src="js/vc/geolocation.js"></script> <script type="text/javascript" src="js/vc/globalization.js"></script> <script type="text/javascript" src="js/vc/inAppBrowser.js"></script> <script type="text/javascript" src="js/vc/media.js"></script> <script type="text/javascript" src="js/vc/notification.js"></script> <script type="text/javascript" src="js/vc/storage.js"></script> </body> </html>
As shown in the preceding code, index.html
includes the following:
- App custom CSS file (
app.css
) - jQuery Mobile library files
- A jQuery Page Params plugin file (
jqm.page.params.js
) - A jQuery Validation plugin file (
jquery.validate.min.js
) - A Common JS (
common.js
) file, app manager JS files, and finally, app view controller JS files
The preceding highlighted code shows you how to make sure that Apache Cordova and jQuery Mobile are loaded correctly (using the jQuery Deferred object) before proceeding to the app pages. Doing this step is important to make sure that our app's code will not access any API that is not ready yet to avoid any unexpected errors. If Apache Cordova and jQuery Mobile are loaded correctly, then the user will leave the "loading"
page and will be forwarded to the app's home page (the "features"
page) to start exploring the Cordova features.
Tip
To learn the jQuery Deferred object by example, check out http://learn.jquery.com/code-organization/deferreds/examples/.
It's worth mentioning that in order to boost the performance of jQuery Mobile 1.4 with Apache Cordova, it is recommended that you disable transition effects. The common.js
file applies this tip in the Cordova Exhibition app as follows:
$.mobile.defaultPageTransition = 'none'; $.mobile.defaultDialogTransition = 'none'; $.mobile.buttonMarkup.hoverDelay = 0;
Finally, in order to exit the application when the user clicks on the back button (which exists in the Android and Windows Phone 8 devices) on the app's home page, common.js
also implements this behavior, as shown in the following code snippet:
var homePage = "features"; //Handle back buttons decently for Android and Windows Phone 8 ... function onDeviceReady() { document.addEventListener("backbutton", function(e){ if ($.mobile.activePage.is('#' + homePage)){ e.preventDefault(); navigator.app.exitApp(); } else { history.back(); } }, false); } $(document).ready(function() { document.addEventListener("deviceready", onDeviceReady, false); });
We create an event listener on the device's "backbutton"
after Cordova is loaded. If the user clicks on the back button, we check whether the user is on the home page using $.mobile.activePage.is()
. If the user is on the home page, then the app exits using navigator.app.exitApp()
; otherwise, we simply use history.back()
to forward the user to the previous page.
Tip
The complete source code of our Cordova Exhibition app with all the three supported platforms can be downloaded from the course web page, or you can access the code directly from GitHub at https://github.com/hazems/cordova-exhibition.
Cordova events
Cordova allows listening and creating handlers for its life cycle events. The following table shows the description of these events:

Tip
Access to all of the events, which are not related to the battery status, are enabled by default. In order to use the events related to the battery status, use the following CLI cordova plugin add
command:
> cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-battery-status.git
We can create our Cordova event listener using the document.addEventListener()
method once DOM is loaded as follows:
document.addEventListener("eventName", eventHandler, false)
Let's see an example; let's assume that we have the following div element in our HTML page, which displays the log of our Cordova app pause and resume events:
<div id="results"></div>
In our JavaScript code, once the DOM is loaded, we can define our Cordova event listeners for the "pause"
and "resume"
events once the "deviceready"
event is triggered, as follows:
function onPause() { document.getElementById("results").innerHTML += "App is paused ...<br/>"; }; function onResume() { document.getElementById("results").innerHTML += "App is resumed ...<br/>"; }; function onDeviceReady() { document.addEventListener("pause", onPause, false); document.addEventListener("resume", onResume, false); }; $(document).ready(function() { document.addEventListener("deviceready", onDeviceReady, false); });
Summary
In this chapter, you learned how to utilize the most important features in Apache Cordova API by understanding the Cordova Exhibition app. You learned how to work with Cordova media, file, capture, notification, and storage APIs. You also learned how to utilize the Apache Cordova events in your mobile app. In the next chapter, you will learn the advanced part of Apache Cordova, which is building your own custom Cordova plugin on the different mobile platforms (Android, iOS, and Windows Phone 8).