I thought it would be nice to show examples of how to implement this feature, since we don't cover it in the book.
On iOS, it requires an additional plugin:
cordova plugin add com.photokandy.localstorage
You can look at FilerV7 for an example, these files have the necessary changes:
www/app/main.js
: Registers new notification listenerswww/app/models/noteStorage.js
: Adds an optional parameter tocreateNote
so that we can create a new note with a specific UID (important when loading the note fromlocalStorage
)www/app/views/textNoteEditView.js
: Adds a listener for the globalapplicationPausing
notification (created inmain.js
), and when it occurs, the note is persisted tolocalStorage
inpersistNote
. When the application resumes, the data inlocalStorage
is wipe, and the note is saved normally. However should the application be terminated before resumption,localStorage
will contain the note in progress.www/app/views/noteListView.js
: When the collection is loaded, it checks to see if there is a note in progress inlocalStorage
incheckForPersistedNotes
. If there is one, it is created with the specific UID, added to the collection and saved.
In PathRec/PathRecNative, the changes necessary are simpler, because localStorage
is the backing store:
www/app/main.js
: Registers new notification listenerswww/app/views/pathEditView.js
: Adds listeners for the globalapplicationPausing
notification, and saves the path when it occurs.
main.js
is changed in exactly the same way in FilerV7 or PathRec[Native].
In main.js
, the APP.onPause
and APP.onResume
handlers are modified to send a global notification:
APP.onPause = function ()
{
_y.UI.globalNotifications.notify("applicationPausing");
console.log ( "Application paused" );
};
APP.onResume = function ()
{
_y.UI.globalNotifications.notify("applicationResuming");
console.log ( "Application resumed" );
};
Later, in APP.start
, we determine whether or not we need to use the events that the PKLocalStorage plugin sends (if on iOS) or if we can stick with Android's events:
var notifications = {
"online": { notification: "applicationOnline", handler: APP.onConnectionStatusChanged },
"offline": { notification: "applicationOffline", handler: APP.onConnectionStatusChanged },
"batterycritical": { notification: "batteryCritical", handler: APP.onBatteryStatusChanged },
"batterylow": { notification: "batteryLow", handler: APP.onBatteryStatusChanged },
"batterystatus": { notification: "batteryStatus", handler: APP.onBatteryStatusChanged },
"menubutton": { notification: "menuButtonPressed", handler: APP.onMenuButtonPressed },
"searchbutton": { notification: "searchButtonPressed",handler: APP.onSearchButtonPressed }
};
if (_y.device.platform() === "ios")
{
// if we want to persist localStorage, we need to use PKLocalStorage plugin
window.PKLocalStorage.addPauseHandler ( APP.onPause );
window.PKLocalStorage.addResumeHandler ( APP.onResume );
}
else
{
// add the proper pause/resume handlers
notifications.pause = {notification:"pause", handler: APP.onPause};
notifications.resume = {notification:"resume", handler: APP.onResume};
}
Then, we create two new global events:
gN.registerNotification ( "applicationPausing", false ); // synchronous notifications
gN.registerNotification ( "applicationResuming", false ); // synchronous
It's important to note that these events use YASMF's feature for sending notifications. YASMF sends most notifications wrapped in a setTimeout
, but setTimeout
(or native actions) can't be used during a backgrounding operation. Above, the false
forces YASMF to call each handler directly, which will work as desired.
The following changes are specific to PathRec[Native].
We add two new methods:
self.registerGlobalNotifications = function registerGlobalNotifications() {
_y.UI.globalNotifications.addListenerForNotification( "applicationPausing",
self.savePath );
};
self.deRegisterGlobalNotifications = function deRegisterGlobalNotifications() {
_y.UI.globalNotifications.removeListenerForNotification( "applicationPausing",
self.savePath );
}
These attach/detach a listener for the applicationPausing
event created in main.js
. When it occurs, savePath
is called, which saves the path into localStorage
.
To call these two methods, we add listeners in init
:
self.addListenerForNotification( "viewWasPopped", self.deRegisterGlobalNotifications );
self.addListenerForNotification( "viewWasPopped", self.releaseBackButton );
self.addListenerForNotification( "viewWasPopped", self.destroy );
self.addListenerForNotification( "viewWasPushed", self.registerGlobalNotifications );
And we remove these listeners in destroy
:
self.removeListenerForNotification( "viewWasPopped", self.deregisterGlobalNotifications );
self.removeListenerForNotification( "viewWasPushed", self.registerGlobalNotifications );
The following changes are specific to Filer V7.
In noteStorage
, we alter createNote
to take an additional (optional) parameter like so:
self.createNote = function ( noteType, theNoteUID )
{
// Create a note from the Note object
var aNote = noteFactory.createNote ( noteType || noteFactory.TEXTNOTE );
var noteUID = _generateUID();
if (typeof theNoteUID !== "undefined")
{
noteUID = theNoteUID;
}
...
}
This lets us pass a specific UID during the creation process, which we'll use when we handle restoring notes from localStorage
.
This is the only edit view in which we need to make changes since all other edit views will inherit the functionality by default.
First we define a method to persist a note to localStorage
when the app is going to the background:
self.persistNote = function ()
{
self._note.name = self._nameEditor.innerText;
self._note.textContents = self._contentsEditor.value;
localStorage["noteInProgress"] = self._note.JSON;
localStorage["noteInProgressUID"] = self._note.UID;
localStorage["noteInProgressType"] = self._note.class;
}
Then we alter saveNote
a little to remove these localStorage
entries, should a note be able to be saved in the normal fashion. This just ensures we won't ever accidentally create spurious notes later on.
self.saveNote = function ()
{
localStorage.removeItem("noteInProgress"); // clean up any persisted notes during a pause
localStorage.removeItem("noteInProgressUID");
localStorage.removeItem("noteInProgressType");
...
Then we define two methods that register the above methods for the applicationPausing
and applicationResuming
notifications:
self.registerGlobalNotifications = function registerGlobalNotifications()
{
_y.UI.globalNotifications.addListenerForNotification ( "applicationPausing", self.persistNote );
_y.UI.globalNotifications.addListenerForNotification ( "applicationResuming", self.saveNote );
};
self.deRegisterGlobalNotifications = function deRegisterGlobalNotifications()
{
_y.UI.globalNotifications.removeListenerForNotification ("applicationPausing", self.persistNote );
_y.UI.globalNotifications.removeListenerForNotification ("applicationResuming", self.saveNote );
}
Then we add listeners for viewWasPushed
and viewWasPopped
to handle these settings in init
. We also add appropriate cleanup to destroy
.
The list view is the first view that comes on-screen, so we handle loading a persisted note here. We create a new method, checkForPersistedNotes
that looks like this:
self.checkForPersistedNotes = function ()
{
if (typeof localStorage["noteInProgressType"] !== "undefined")
{
// check if there's a note with our UID already -- if so, we need to overwrite it instead of
// creating a new one.
var anExistingNote = noteStorageSingleton.getNote(localStorage["noteInProgressUID"]);
if (anExistingNote !== undefined)
{
anExistingNote.initWithJSON(localStorage["noteInProgress"]);
noteStorageSingleton.saveNote(anExistingNote);
noteStorageSingleton.saveCollection();
localStorage.removeItem("noteInProgress");
localStorage.removeItem("noteInProgressUID");
localStorage.removeItem("noteInProgressType");
}
else
{
noteStorageSingleton.createNote(localStorage["noteInProgressType"], localStorage["noteInProgressUID"])
.then(function (aNewNote)
{
aNewNote.initWithJSON(localStorage["noteInProgress"]);
localStorage.removeItem("noteInProgress");
localStorage.removeItem("noteInProgressUID");
localStorage.removeItem("noteInProgressType");
noteStorageSingleton.saveNote(aNewNote);
noteStorageSingleton.saveCollection();
})
.catch(function (anError) { console.log(anError) })
.done();
}
}
};
Later, down in init
, we add the above handler as a listener to the collectionLoaded
event.
Of course, we could have gone a step further and actually displayed an edit view for the persisted note, which would have given the user a sense of continuity -- that is, the app would have appeared similar to how it looked when they left. We'll leave this as an exercise to the reader.