Part 7: Preferences, Cookies and Themes

As always, make a copy of the project you've already created. You also should have completed part 1, part 2, part 3, part 4, and part 5 to do this tutorial. (Part 6 really isn't required for this tutorial.)

In this tutorial, we'll examine how to create a basic preferences scene with a List Selector to change the theme (either palm-default or palm-dark) and save this information to a cookie.

First, we need to create a new scene called Preferences. (See part 2 if you don't recall how to do this.) This will create a place for us to configure preferences. Granted, it's only going to keep track of one thing, setting the theme, but we could easily add other items in the future.

 Now, let's modify first-assistant.js to look like the following:  (Bold text is new)

function FirstAssistant() {
/* this is the creator function for your scene assistant object. It will be passed all the
additional parameters (after the scene name) that were passed to pushScene. The reference
to the scene controller (this.controller) has not be established yet, so any initialization
that needs the scene controller should be done in the setup function below. */
}

FirstAssistant.prototype.handleButtonPress = function(event){
// set a variable to the value of the textbox
this.amount=Number(this.amountModel.value); //This also converts the text to a number

// increment the total and update the display
this.total = this.total + this.amount;
this.controller.get('count').update(this.total);
Mojo.Log.info("The user tapped Add and now the count is:" + this.total);
}

FirstAssistant.prototype.handleA = function(event){
// set a variable to the value of the textbox
this.amount=Number(this.amountModel.value); //This also converts the text to a number

// subtract the total and update the display
this.total = this.total - this.amount;
this.controller.get('count').update(this.total);
}

FirstAssistant.prototype.handleC = function(event){

// clear the total and update the display

this.total=0;
this.controller.get('count').update(this.total);
}

FirstAssistant.prototype.handleCommand = function(event) {
if(event.type == Mojo.Event.command) {
switch(event.command)
{
case 'openCalculator':
this.controller.serviceRequest('palm://com.palm.applicationManager', {
method:'launch',
parameters: {
id: 'com.palm.app.calculator',
}
});
break;

case 'openEmail':
this.controller.serviceRequest('palm://com.palm.applicationManager', {
method:'launch',
parameters: {
id: 'com.palm.app.email',
params: {
summary: 'Add/Subtract Count',
text: 'The count is: ' + this.total,
}
}
});
break;
}
}
}

FirstAssistant.prototype.setup = function() {
/* this function is for setup tasks that have to happen when the scene is first created */
/* use Mojo.View.render to render view templates and add them to the scene, if needed. */
/* setup widgets here */
/* add event handlers to listen to events from widgets */

//Read theme cookie or default info
AddSubcookie = new Mojo.Model.Cookie('addSub'); //Looks for a cookie called 'addSub'
this.oldThemePrefs = AddSubcookie.get(); //Read the cookie
if(this.oldThemePrefs) //If the cookie data exists, read it
{
this.theme = this.oldThemePrefs.theme;
}
else
{
//In this case, the cookie doesn't exist
this.theme = "DEFAULT_THEME";
}

//Setup Theme
if (this.theme == "DEFAULT_THEME"){
$$('body')[0].addClassName('palm-default');
$$('body')[0].removeClassName('palm-dark');
}else{
$$('body')[0].addClassName('palm-dark');
$$('body')[0].removeClassName('palm-default');
}


//Set up Command Menu
this.cmdMenuModel = {
visible: true,
items: [
{items:
[
{label: $L('Email'), command:'openEmail', shortcut: 'e'},
// {label: $L('Email'), icon:'stop', command:'openEmail'},
// {label: $L('Email'), icon:'forward', command:'openEmail'},
// {label: $L('Email'), icon:'refresh', command:'openEmail'},
// {label: $L('Email'), icon:'search', command:'openEmail'},
// {label: $L('Email'), icon:'new', command:'openEmail'},
// {label: $L('Email'), icon:'attach', command:'openEmail'},
// {label: $L('Email'), icon:'compose', command:'openEmail'}, //This is probably the most appropriate for this application
// {label: $L('Email'), icon:'conversation', command:'openEmail'},
// {label: $L('Email'), icon:'delete', command:'openEmail'},
// {label: $L('Email'), icon:'file', command:'openEmail'},
// {label: $L('Email'), icon:'forward-email', command:'openEmail'}, //Or, maybe this one
// {label: $L('Email'), icon:'info', command:'openEmail'},
// {label: $L('Email'), icon:'priority', command:'openEmail'},
// {label: $L('Email'), icon:'reply-all', command:'openEmail'},
// {label: $L('Email'), icon:'reply', command:'openEmail'},
// {label: $L('Email'), icon:'save', command:'openEmail'},
// {label: $L('Email'), icon:'send', command:'openEmail'}, //Possibly even this one
// {label: $L('Email'), icon:'sync', command:'openEmail'},
// {label: $L('Email'), icon:'edit-profile', command:'openEmail'},
// {label: $L('Email'), icon:'make-vip', command:'openEmail'},
// {label: $L('Email'), icon:'new-contact', command:'openEmail'},
// {label: $L('Email'), icon:'remove-vip', command:'openEmail'},
// {label: $L('Email'), icon:'down', command:'openEmail'},
]},
{},
{items:
[
{label: $L('Calculator'), command:'openCalculator', shortcut: 'k'},
]}
],
};

attrib = {
menuClass: 'no-fade' //This removes the 'fade' effect so the palm-dark theme looks good with command buttons
}


this.controller.setupWidget(Mojo.Menu.commandMenu, attrib, this.cmdMenuModel);

// set the initial total and display it
this.total=0;
this.controller.get('count').update(this.total);

// Setup Application Menu
this.controller.setupWidget(Mojo.Menu.appMenu, AddSubMenuAttr, AddSubMenuModel);

//Setup Textfield to be numbers only
this.controller.setupWidget(
"mainTextField",
{//Attributes
maxLength: 1, //sets a maximum number of characters to 1
modifierState: Mojo.Widget.numLock, //Turns on the numLock for the text field
focusMode: Mojo.Widget.focusSelectMode, //If the text box is selected, all of the characters are highlighted
charsAllow: function( charCode ) {

return( ( charCode >= 48 && charCode <= 57) ); //uses ASCII codes to only allow 1 - 9
}
},
this.amountModel = {
value: ""
}
);

// a local object for button attributes
this.buttonAttributes = {};

// a local object for button model
this.buttonModel = {
buttonLabel : 'Add',
buttonClass : 'affirmative',
disabled : false
};

// set up the button
this.controller.setupWidget("MyButton", this.buttonAttributes, this.buttonModel);

this.controller.setupWidget("ButtonSubtract", this.buttonAttributes, this.Model = {buttonLabel : 'Subtract', buttonClass : 'negative', disabled : false});

this.controller.setupWidget("ButtonClear", this.buttonAttributes, this.Model = {buttonLabel : 'Clear', buttonClass : '', disabled : false});
// bind the button to its handler
Mojo.Event.listen(this.controller.get('MyButton'), Mojo.Event.tap, this.handleButtonPress.bind(this));

//Bind event handler for Subract button
Mojo.Event.listen(this.controller.get('ButtonSubtract'), Mojo.Event.tap,this.handleA.bind(this));

//Bind event handler for Clear button
Mojo.Event.listen(this.controller.get('ButtonClear'), Mojo.Event.tap,this.handleC.bind(this));

}

FirstAssistant.prototype.activate = function(event) {
/* put in event handlers here that should only be in effect when this scene is active. For
example, key handlers that are observing the document */
}


FirstAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
}

FirstAssistant.prototype.cleanup = function(event) {
/* this function should do any cleanup needed before the scene is destroyed as
a result of being popped off the scene stack */
this.controller.stopListening(this.controller.get('MyButton'), Mojo.Event.tap, this.handleButtonPress.bind(this));
//Cleanup Subtraction button
this.controller.stopListening(this.controller.get('ButtonSubtract'),Mojo.Event.tap,this.handleA.bind(this));

//Cleanup Clear button
this.controller.stopListening(this.controller.get('ButtonClear'),Mojo.Event.tap,this.handleC.bind(this));
}

Let's examine what this code does:

We now need to add a menu item to launch the Preferences scene. Since we've done this before, we'll modify the stage-assistant file. In the stage-assistant.js file, add the following code:
 
function StageAssistant () {
}

StageAssistant.prototype.setup = function() {

// Setup Application Menu with an About entry
//
AddSubMenuAttr = {omitDefaultItems: true};
AddSubMenuModel = {
visible: true,
items: [
{label: "About Add/Subtract...", command: 'do-aboutAddSub'},
Mojo.Menu.editItem,
{label: "Preferences...", command: 'do-preferencesAddSub', shortcut:'p'},
{label: "Help...", command: 'do-helpAddSub', shortcut: 'h'}
]
};
this.controller.pushScene('first');
};

// handleCommand - Setup handlers for menus:
//
StageAssistant.prototype.handleCommand = function(event) {
// var currentScene = this.controller.activeScene();
if(event.type == Mojo.Event.command) {
switch(event.command) {
case 'do-aboutAddSub':
this.controller.pushScene("about");
break;

case 'do-preferencesAddSub':
this.controller.pushScene("Preferences");
break;


case 'do-helpAddSub':
this.controller.pushAppSupportInfoScene();
break;

}
}
};

 

We can create the JavaScript code for the Preferences scene. Write the following into the preferences-assistant.js file:

function PreferencesAssistant() {
/* this is the creator function for your scene assistant object. It will be passed all the
additional parameters (after the scene name) that were passed to pushScene. The reference
to the scene controller (this.controller) has not be established yet, so any initialization
that needs the scene controller should be done in the setup function below. */
}
PreferencesAssistant.prototype.handleThemeChange = function(event){
//This function changes the theme, based on what was selected from the List Selector
this.theme = this.themeModel.value;
if (this.theme == "DEFAULT_THEME"){
$$('body')[0].addClassName('palm-default');
$$('body')[0].removeClassName('palm-dark');
}else{
$$('body')[0].addClassName('palm-dark');
$$('body')[0].removeClassName('palm-default');
}
}

PreferencesAssistant.prototype.setup = function() {
/* this function is for setup tasks that have to happen when the scene is first created */
/* use Mojo.View.render to render view templates and add them to the scene, if needed */
/* setup widgets here */
/* add event handlers to listen to events from widgets */

//Read theme cookie or default info
AddSubcookie = new Mojo.Model.Cookie('addSub'); //Looks for a cookie called 'addSub'
this.oldThemePrefs = AddSubcookie.get(); //Read the cookie
if(this.oldThemePrefs) //If the cookie data exists, read it
{
this.theme = this.oldThemePrefs.theme;
}
else
{
//In this case, the cookie doesn't exist
this.theme = "DEFAULT_THEME";
}


//Setup ListSelector
this.themeAttributes = {
label: $L('Theme'),
choices: [
{label: "Default", value: "DEFAULT_THEME"},
{label: "Dark", value: "DARK_THEME"}
]};
this.themeModel = {
value: $L(this.theme),
disabled: false
};
this.controller.setupWidget("themeListId", this.themeAttributes, this.themeModel);

/* add event handlers to listen to events from widgets */
this.themeChanged = this.handleThemeChange.bindAsEventListener(this);
Mojo.Event.listen(this.controller.get('themeListId'), Mojo.Event.propertyChange, this.themeChanged.bind(this));

};

PreferencesAssistant.prototype.activate = function(event) {
/* put in event handlers here that should only be in effect when this scene is active. For
example, key handlers that are observing the document */
};

PreferencesAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
//When the scene is deactivated, write the theme information to the cookie
AddSubcookie = new Mojo.Model.Cookie('addSub');
dataField = this.themeModel.value;
AddSubcookie.put({
theme:dataField,
})
};

PreferencesAssistant.prototype.cleanup = function(event) {
/* this function should do any cleanup needed before the scene is destroyed as
a result of being popped off the scene stack */

//Clean up ListSelector - IMPORTANT!
Mojo.Event.stopListening(this.controller.get('themeListId'), Mojo.Event.propertyChange, this.themeChanged.bind(this));
};

Let's examine what this code does:

In the prefereneces-scene.html file, add the following code:
 
<div id="main" class="palm-hasheader">
<div class="palm-header">Preferences</div>
<div class="palm-group">
<div class="palm-group-title" id="Contact"><span x-mojo-loc="">Theme</span></div>
<div class="palm-list">
<div class="palm-row single">
<div id="themeListId" class="listselectorClass" x-mojo-element="ListSelector" name="Theme"></div>
</div>
</div>
</div>

</div>

Not really too much exciting here. We're just adding a List Selector to the scene with the standard heading and group. The only important part here is making sure the name= part so that it names the control Theme, which is referenced in the .js code.

And that wraps it up! Lots of hopefully useful stuff in this tutorial. Most apps will have a preferences scene and it's also very nice, and rather simple, to allow the user to change the theme. If you have problems or still need help, I'd recommend asking in the following forums:

Back to Home | Download the v7 Source (.zip) | Download the v7 IPK


© 2011 Richard Neff