Where It Was, Part Two
This article is a follow up of Where It Was, Part One.
In the previous post, we got a basic user interface to work on. Now it is time to make it actually do something. We will get the “Remember” button to add whatever we write on the MEdit field to the MList, and the list should persist between sessions, that is, it should be still there when user closes and reopens our application.
JavaScript
In this part of the tutorial, we are going to use some RadPHP mobile components, but we will also start writing code.
When programming for mobile devices, using PHP is possible, but it has its cons. Whenever we can, we will rely on JavaScript programming language, and its jQuery library toguether with jQuery Mobile. Also, we will use PhoneGap library to interact with devices — it can be used for things such as storage, notifications, geolocation…
JavaScript should be easy to understand for any developer, especially for those with some knowledge of a programming language following the C style syntax, including PHP. There are guides to get started all over the Internet.
As for the libraries, they all have great official documentation (follow links above) and are, in general, really intuitive.
Mobile Storage System
When developing mobile applications with RadPHP, you can access device storage as a Web SQL Database, using Hardware Components and PhoneGap’s Storage API. It does not matter which device we are talking about, it works the same way for all of them.
Setup a Database
First, we will need to create a database to work on. To do so, from the Designer, go to the Tool Palette, and under Mobile Hardware find a component called MDB. Now drag it and drop it on our application. With the MDB component selected, go to the Object Inspector and fill in the following properties:
- DBDisplayName: Places. This is the name of the JavaScript variable for our database. In our application, we will use it to perform transactions on the database.
- DBName: places. This is the logic name of the database, the one it will be saved with on mobile devices running our application.
- DBSize: 1000000. This is the maximum size of the database, in bytes. 1000000 should be more than enough.
- DBVersion: 1. This is the version number of our database. We can choose this value freely, like the ones before. It will be used to control changes in the database that might happen in the future as we release new versions of our application with new features.
- Name: Places. This is the name of the component itself. This property is used to tell components apart on the Designer, since the name is displayed along with component’s icon.
With this our database is defined.
Get an Event for When Device Is Ready
Now that we have defined the information of our database, Places, we will get the database initialized when our application is run on a mobile device for the first time. That is, we will create its structure in case it does not exist yet on the device.
This step must be performed as soon as the device loads our application, so first we need an event that gets triggered at that point. We will use an MPageEvents component for that, and we will call it “PageState”, for example. This component has a JavaScript event, OnDeviceReady, which is exactly what we are looking for here.
So, time to drag the component from the Tool Palette (again under Mobile Hardware), drop it on our page, and change its Name property to PageState.
Setup a Transaction
Now, we will prepare a database transaction, which will be responsible for creating the database structure if it does not exist already on the mobile device. To do that, we will use an MDBTransaction component. It is a Mobile Hardware component too, check the Tool Palette, and drag it and drop in on the application. Then it is time to fill its properties:
- DB: Places. This is the database the transaction will work on. It can be chosen from a drop-down list where Places, our MDB component, should be already listed. In fact, you can just double-click the field and Places will be chosen.
- Name: PlacesInitialization. This is the name of the component itself.
Make the Event Trigger the Transaction
It is time to associate the event to the transaction. Select PageState (MPageEvents component) on the Desinger, and from the Object Inspector go to its JavaScript tab. and double-click its OnDeviceReady event. That will open the Code Editor with the text cursor inside the following PHP code, that has been just generated for us:
function PageStateJSDeviceReady($sender, $params)
{
?>
//begin js
//end
<?php
}
If you have been paying attention, you might wonder: “Weren’t we supposed not to use PHP on this tutorial?”. You are right. In fact, we are not using it. RadPHP mobile projects use the RPCL PHP library as base, each page is an MPage container, any mobile component is written in PHP, same for their events. But there will be no trace of PHP on the final application. Once we export our mobile project, it will only be JavaScript. This way, we do not have to renounce to the benefits of the RPCL library and RAD development.
Between the PHP comments above we will place the JavaScript code we want to run when the application is ready. In our case, we want the transaction, PlacesInitialization, to be run. The JavaScript code to run the transaction is this:
PlacesInitializationTransaction();
So we just add this code between the comments. Now, PlacesInitialization transaction will be run whenever our application has been completely loaded on a device. In fact, this is what will happen:
- Application starts, completely.
- OnDeviceReady event is called.
- We call <MDBTransaction>Transaction from the function associated to the OnDeviceReady event.
- <MDBTransaction>’s OnTransaction event is called from <MDBTransaction>Transaction.
You just need to replace <MDBTransaction> with the name of the MDBTransaction component, the one we called PlacesInitialization.
Many mobile hardware components include JavaScript functions like this one you can call. For more information, check the documentation on Hardware Components (there are separated pages for each component which has some of these functions).
Define the Transaction
Now that the transaction gets triggered at the right time, we only need to define what it does. To start with, we will select PlacesInitialization on the Designer, and from the Object Inspector, JavaScript tab, we will double-click OnTransaction event. We will gets this code on the Code Editor then:
function PlacesInitializationJSTransaction($sender, $params)
{
?>
//begin js
//end
<?php
}
Transactions get a database object (which would be our previously defined MDB component) in a variable called event, and we can run queries on it through its executeSql() method. Since for now we will only be storing a text string for each row (the name or short description of the place), we can do like this:
var database = event;database.executeSql('CREATE TABLE IF NOT EXISTS places (id INTEGER PRIMARY KEY ASC, name TEXT)');
This code is enough to create our initial database schema in case there is none yet.
Manage Transaction Errors
Finally, in case for some reason our database can not be created, we want to tell our users about it, so they can either fix the problem (for example, if it is a setting on their mobile device), or report the error to us (be it a bug on our application). Select PlacesInitialization on the Designer again, and from the Object Inspector, JavaScript tab, we will double-click OnTransactionError event. We will get this code on the Code Editor then:
function PlacesInitializationJSTransactionError($sender, $params)
{
?>
//begin js
//end
<?php
}
Now, between the PHP comments, we can use the PhoneGap notification system:
// Define the message: var message = 'There was an error creating places database: ' + event.message + '.'; // Call the notification system: navigator.notification.alert( message, // Message. function(){}, // Callback function, we are not using it here. 'Cannot Store Places', // Title. 'OK' // Button label. );
Note you can still use JavaScript’s alert() function if you want to, but you will find PhoneGap notification system quite more powerful while easy to use. Also, there is a mobile component to manage notifications, MNotification, which we will use later. But for this case, since we need to include the content of event.message on the notification, we will not use the component.
Save Items on the List
Now we have both the graphical representation of the list of places, the MList component, and the logical representation, the database, which is created as soon as the applications starts in case it did not exist already.
It is time to fill the list of places for real. When users press the MButton, ‘Remember’, an item should be added to both the database and the list, containing the text entered on the MEdit control.
First, we select the button from the Designer, and we set its ButtonType property to btNormal. We don’t want to submit any form when using this button — or any other button when working with JavaScript and mobile applications, for that matter. It will be just a normal button, and we will control its behavior through JavaScript.
Then we go to its JavaScript events tab on the Object Inspector, and double-click its OnClick method, getting the following code generated for us:
function MButton1JSClick($sender, $params)
{
?>
//begin js
//end
<?php
}
Between the PHP comments we will write our JavaScript code. This is what we want it to do:
- Get the text from the MEdit control.
- Try to save it to the database (logical list of places).
- In case it works, add it to the MList control (graphical list of places).
- Empty the MEdit and set focus on it.
At the beginning, we should define some variables we will need all across the code: the controls and the name of the place to be saved. We will use jQuery for that. jQuery selectors will help us to easily reference our controls and get the content of the MEdit:
// jQuery controls.
var edit = jQuery('#MEdit1');
var list = jQuery('#MList1');
// Input text.
var place = edit.val(); // We get it from the MEdit control.
Note: When using jQuery selectors on RadPHP mobile applications, use jQuery() syntax instead of $(), since the later will not work.
Then, we’ll define the functions we are going to need.
- A function from which we will start the transaction on the database. We do it calling the transaction() method of our database, which gets three functions as parameters:
- A function to execute the SQL query.
- A function in case the query fails. We will make it print a notification, and give the user the choice to retry.
- A function in case the query succeeds. We will make it write the entry to the MList.
- A function to control the answer of the user when given the choice to retry (when the transaction fails).
Lets start with the first function:
// Try to insert given place into the database.
function insert() {
Places.transaction(performQuery, error, success);
}
Places is the name we gave to our database — the DBDisplayName of our MDB component. We use transaction() method on it, and pass it the names of the three functions we are going to define below:
// Perform SQL Query to insert the place.
function performQuery(database) {
database.executeSql('INSERT INTO places (name) VALUES (?)', [place]);
}
Notice we use ? on the query string, and then include the name or short description of the place in the array we pass as the second argument. This is the safer aproach. In case you have multiple variables to use in the query, use multiple interrogation symbols and use the following syntax on the second argument: [variable1, variable2, variableN].
// Function to run if the new place was added to the database.
function success() {
list.prepend('<li>' + place + '</li>'); // Add the place at the beginning of the list.
list.listview('refresh'); // Refresh the list (updates the style).
edit.val('').focus(); // Empty the MEdit and focus it.
}
// Function to run if the new place was not added to the database.
function error(reason) {
// Construct message.
var message = 'Could not save ' + place + '. ' + reason.message + '. Do you want to retry?';
// Print notification.
navigator.notification.confirm(
message, // Message.
userAnswer, // Callback function.
'Saving Place Failed', // Title.
'Retry,Cancel' // Button labels.
);
}
The function to manage the case where the transaction fails recives an error object (called ‘reason’ above) with two properties:
- code: error code.
- message: error description.
The callback function for the notification, userAnswer, will get the index of whatever button user presses (1 or 2 in our case). We will define that function below so it calls insert() in case user pressed ‘Retry’ button on the notification.
// Manage user answer when asked whether to retry or not. function userAnswer(button) { if(button == 1) { insert(); // Retry place insertion. } else { edit.focus().select(); // Focus MEdit field, and preselect its content. } }
Finally, after defining all the functions we need, we will call the first one, although only if MEdit1 field is not empty:
// Try to insert the new place into the database.
if(place != '') { insert(); }
Load any Existing Database upon Start
When our application runs on a mobile device for the first time, it will create an empty database (database structure), and user will be able to add items to it. But if our application runs on the same device again, it will not load the existing database, and the list will be empty, no matter if user entered places before. We will take care of that now.
To do so, we will add additional JavaScript code after database initialization code. That additional code will be quite close to the code to add an item to the list. We will have the same methods, just with slightly different content.
So go to these lines on your code:
function PageStateJSDeviceReady($sender, $params)
{
?>
//begin js
PlacesInitializationTransaction();
//end
<?php
}
And lets start adding code right after PlacesInitializationTransaction() call:
// JQuery controls.
var list = jQuery('#MList1');
We will also use the MList this time, although not the MEdit.
// Function to load database places into the list.
function loadListFromDatabase() {
Places.transaction(performQuery, error);
}
This time we did not call any function when the transaction as a whole succeeds. We will control transaction success on a per-query basis. This is because the only way to get the results of a SELECT Web SQL query is from the success method of that same query (third argument on the call).
// Perform SQL Query to insert the place. function performQuery(database) { database.executeSql('SELECT name FROM places', [], success); }
Here we called the success() function for the SELECT query. As you will see below, on the success() function we can get the results of the query and work with them.
// Function to run if query goes smooth. function success(database, results) { for (var i=0; i<results.rows.length; i++){ list.prepend('<li>' + results.rows.item(i).name + '</li>'); } list.listview('refresh'); // Refresh the list (updates the style). }
Now our query selects all the names from the places database. Then, on success(), we use the second parameter to retrieve the results, add them to the list and refresh the list. The rest of the code is even closer to the code we used before to save individual places on the database:
// Function to run if there is any issue with the database query. function error(reason) { // Construct message. var message = 'Could not load places list: ' + reason.message + '. Do you want to retry?'; // Print notification. navigator.notification.confirm( message, // Message. userAnswer, // Callback function. 'Loading Places Failed', // Title. 'Retry,Cancel' // Button labels. ); } // Manage user answer when asked whether to retry or not. function userAnswer(button) { if(button == 1) { loadListFromDatabase(); // Retry place insertion. } } // Load database places into the list. loadListFromDatabase();
Run on the Android Emulator
Our application should be functional now, even if not at all feature-complete. We should be able to add items to the list, close the application, open it again and those items we entered before should be there.
But better safe than sorry, so run your application on the Android emulator to be sure. You can do so from the Wizard for Phonegap, from the Tools menu. If you have any doubt, check the documentation.
End of Part Two
As you can check on your Android emulator, or mobile project has gone from a useless interface to a working application. We have taken full advantage of PhoneGap Storage and Notification APIs, learned how to create, modify and load a database on mobile devices, and we have also got in touch with JavaScript, jQuery and jQuery Mobile.
On the next post, we are going to let user open places on a separated page, where for now only the name of the place will be displayed — later we will add a map. From that second page, we will also let the user delete places from the list (and database).
Share This | Email this page to a friend
Posted by achaves on February 8th, 2012 under Mobile, Tutorials |






February 9th, 2012 at 19:02
[...] This article is a follow up of Where It Was, Part Two. [...]
March 12th, 2012 at 10:32
It would help if the final assembled java text code listing were provided, separate, for part 1, part 2, part 3, and part 4. It is hard to tell where your code gets inserted for a 74 YO java learner. Is it available somewhere?
March 13th, 2012 at 15:28
Having problems with Where It Was part two (compiler error). Through MButton1JSClick (was OK). Through function insert() (was OK). But function performQuery(database) (compiler ERROR) displaying: "Parse error: syntax error, unexpected ‘)’, expecting ‘&’ or T_VARIABLE in C:\Users\Tom\Documents\RadPHP\Projects\WhereWeWere.php on line 83". NOTE: This is exactly as cut and pasted from your example. Tried commenting this function out and then function success() comes up with a similar compiler error. I am following your instructions exactly and checked myself for 4 days now to no avail. Suspicious I need to declare variables (like "database" as argument in "function performQuery" — but have no idea how to do this or what to try next … PLEASE HELP. My code as follows:
// A) Try to insert given place into the database.
function insert() {
Places.transaction(performQuery, error, success);
}
// B) Perform SQL Query to insert the place.
function performQuery(database) {
database.executeSql(’INSERT INTO places (name) VALUES (?)’, [place]);
}
// C) Function to run if the new place was added to the database.
function success() {
list.prepend(” + place + ”); // Add the place at the beginning of the list.
list.listview(’refresh’); // Refresh the list (updates the style).
edit.val(”).focus(); // Empty the MEdit and focus it.
}