Sunday 27 November 2016

Oracle JET offline storage using Cordova SQLite

Most popular way of interacting data within the JET application is by using REST API. What if I want to store data locally/offline within my JET app? Will there be anyway? Off course Yes!
In this blog, I will explain to you how we can store data offline within our Oracle JET application. To store data offline we can use Cordova SQLite which is well supported in Oracle JET.  Before creating actual JET application and integrating with Cordova SQLite, let us try to understand Cordova SQLite plugin.

Note: I have created sample application for android platform.
The Cordova SQLite Plugin: 
The Cordova SQLite plugin lets the  app access the underlying native SQLite database by providing   an API.
SQLite is an embedded DBMS based on the SQL language.  SQLite database with full API is provided by  iOS, Android, and Windows Phone.
The SQLite Cordova plugin provides a simple API to create databases and run queries on SQLite.  This  plugin exposes an API.
Here is how to create a database and run SQL queries on the SQLite database

// Wait for plugin to load document.addEventListener("deviceready", onDeviceReady, false); // Cordova is ready function onDeviceReady() { var db = window.sqlitePlugin.openDatabase({name: "emp.db"}); db.transaction(function(tx) { //create table tx.executeSql("CREATE TABLE IF NOT EXISTS EMP (firstName text primary key, lastName text)", [], function(tx, res){ //insert data tx.executeSql("INSERT INTO EMP (firstName, lastName) VALUES (?,?)", [fName,lName], function (tx, res){ //retrieve data tx.executeSql("SELECT distinct firstName,lastName FROM EMP", [], function (tx, res){ for(var iii = 0; iii < res.rows.length; iii++) { alert(res.rows.item(iii).firstName); alert(res.rows.item(iii).lastName); } }) }); }); }, function(err){ //errors for all transactions are reported here alert("Error: " + err.message) }); }

In the above code snippet, we first create the database, then call the transaction method of the database object with a callback. Inside the callback, we run the SQL queries. The queries are executed using the executeSql function which returns the response asynchronously.
If any of the queries fail, then the second callback passed to the transaction method is invoked. The callback of the executeSql will not be fired if the query fails.

To delete a database, we can write the code as shown below.

    //delete database

     window.sqlitePlugin.deleteDatabase({name: "emp.db"}, function(){
    alert("Successfully deleted database");
}, function(){
    alert("Error while delete database");
});

This is basics of integrating SQLite within an application. If you wanted to know more about Cordova and SQLite then refer below links
Now Let’s see how we can create JET hybrid app and leverage the Cordova SQLite plugin to achieve offline storage within our JET application. In this tutorial, We will create Hybrid Oracle JET application for android platform.
The Oracle JavaScript Extension Toolkit (JET) framework includes support for hybrid mobile application development, providing Android and iOS Alta themes and UI behavior on Oracle JET components, starter applications, design patterns, and tooling support.
Before you can create your first hybrid mobile application, you should become familiar with the Oracle JET mobile features and third-party technologies. You must also install the prerequisite packages and Oracle JET mobile tools.
For installation and setting up the environment, please refer “Getting Started with Oracle JET Hybrid Mobile Application Development” section in the Oracle JET Developer guide.
Along with above mentioned Tooling packages, we need to install Cordova in our development machine.
Node.js (npm) helps to achieve this.
Install Cordova
In order to Install Cordova on your development machine,
As Administrator on Windows or using sudo on Macintosh and Linux systems, enter the following command to install Apache Cordova:
[sudo] npm install -g cordova
To make sure, Cordova installed successfully, just try cordova –version in the command prompt


Once all the necessary tooling packages along with Cordova is installed, we need to install Cordova SQLite plugin within our Oracle JET application. We will do this step, after creating JET Hybrid application.

I believe, you have already installed 'YoOracleJET plugin' in NetBeans IDE and you know how to create Hybrid JET mobile application using NetBeans  IDE.

   Creating a new project


1)     Click on the New Project icon shown below to create a new project.


2)     Select HTML5/JavaScript and from the list select the NavDrawer Template (Hybrid Android Oracle JET), Click Next.



3)     Give a Project Name (CordovaSQLiteJETEmpApp in this example) and provide appropriate Project Location.


4)     Now, the project with be automatically created by Yeoman generator.

5)     Wait till the project is created successfully and make sure the message “Done, without errors.” is displayed in the console as shown below.


To Install SQLite plugin within your JET Hybrid app, navigate to <app root directory>\hybrid  directory in your JET Application and then use below command

cordova plugin add cordova-plugin-sqlite

 Below screen shot shows successful installation of Cordova SQLite plugin

Once it is successfully done, you will see below-mentioned folder structure under plugins directory.



Also, the project structure will be updated and it looks as shown below



Now let's understand important concepts with respect to above JET app.
Initialize App
When the app loads(device ready), I am checking if the table exists or not. If the table doesn’t exist, then create it
Here is the code to initialize the app.
var db = null;
document.addEventListener("deviceready", function(){
    db = window.sqlitePlugin.openDatabase({name: "emp.db"});
    db.transaction(function(tx) {
        tx.executeSql("CREATE TABLE IF NOT EXISTS EMP (firstName text primary key, lastName text)");
    }, function(err){
        alert("An error occurred while initializing the app");
    });
}, false);
Here I have created a table named ‘EMP’ with two columns, firstName and lastName. With firstName as primary key.


The dashboard view model complete code is as follows

/**
 * Copyright (c) 2014, 2016, Oracle and/or its affiliates.
 * The Universal Permissive License (UPL), Version 1.0
 */
/*
 * Your dashboard ViewModel code goes here
 */
define(['ojs/ojcore', 'knockout', 'jquery', 'appController', 'ojs/ojinputtext', 'ojs/ojtable', 'ojs/ojarraytabledatasource'],
        function (oj, ko, $, app) {

            function DashboardViewModel() {

                var self = this;
                var deptArray = [];
                self.datasource = ko.observableArray();
                var db = null;
                // On device ready, create the table if it does not exists.
                document.addEventListener("deviceready", function () {
                    db = window.sqlitePlugin.openDatabase({name: "emp.db"});
                    db.transaction(function (tx) {
                        tx.executeSql("CREATE TABLE IF NOT EXISTS EMP (firstName text primary key, lastName text)");
                    }, function (err) {
                        alert("An error occured while initializing the app");
                    });
                }, false);


                this.firstName = ko.observable(" ");
                this.lastName = ko.observable("");

                // function to fetch data from EMP Table and push it to view
                self.getEmployeeData = function () {
                    db.transaction(function (tx) {

                        tx.executeSql("SELECT distinct firstName,lastName FROM EMP", [], function (tx, res) {

                            deptArray = [];
                            for (var i = 0; i < res.rows.length; i++)
                            {
                                var row = res.rows.item(i);

                                deptArray.push(
                                        {
                                            firstName: row['firstName'],
                                            lastName: row['lastName']
                                        }
                                );



                            }

                            self.datasource(new oj.ArrayTableDataSource(deptArray, {idAttribute: 'firstName'}));

                        });
                    }, function (err) {
                        alert("An error occured while displaying saved records");
                    });
                };


                // insert/add function and this is called on click of add button click on the page.
                self.add = function () {
                    var fName = this.firstName();
                    var lName = this.lastName();

                    alert('First Name:' + fName);
                    alert('Last Name:' + lName);

                    if (fName === "")
                    {
                        alert("Please enter first Name");
                        return;
                    }

                    if (lName === "")
                    {
                        alert("Please enter last Name");
                        return;
                    }

                    db.transaction(function (tx) {
                        tx.executeSql("INSERT INTO EMP (firstName, lastName) VALUES (?,?)", [fName, lName], function (tx, res) {
                            alert("Record Added Successfully!..");
                        });
                    }, function (err) {
                        alert("Ooops!..An error occured during add operation!");
                    });

                    // Refresh employee data
                    self.getEmployeeData();
                };

                // computes full name based on firstName and lastName and returns to view
                this.fullName = ko.computed(function () {
                    return this.firstName() + " " + this.lastName();
                }, this);



                // Header Config
                self.headerConfig = {'viewName': 'header', 'viewModelFactory': app.getHeaderModel()};

                self.handleActivated = function (info) {
                    // Implement if needed
                };


                self.handleAttached = function (info) {
                    // Implement if needed
                };



                self.handleBindingsApplied = function (info) {
                    // Implement if needed
                };


                self.handleDetached = function (info) {
                    // Implement if needed
                };


            }


            return new DashboardViewModel();
        }
);




In the above View Model code,

 On device ready, creating a table named EMP, if it does not exist.
 add function is responsible for inserting user entered firstName and lastName values  into EMP table.
 getEmployeeData() function is responsible for fetching data from EMP table and passing it back to view. Used ko observable array to push data to a table in view section.


In the dashboard.html, I have put below-mentioned HTML code

               <div>
    <div class="oj-applayout-fixed-top">
        <header role="banner" class="oj-hybrid-applayout-header" data-bind="ojModule: headerConfig">
        </header>
    </div>
    <div class="oj-applayout-content">
        <div role="main" class="oj-hybrid-applayout-content">
            <div class="oj-hybrid-padding">
                <h3>Dashboard Content Area</h3>


                <p>First Name: <input data-bind="value: firstName" /></p>
                <p>Last Name: <input data-bind="value: lastName" /></p>

                <button id="buttonOpener" data-bind="click: add, ojComponent: {component: 'ojButton', label: 'Add'}"></button>

                <p>Full name: <strong data-bind="text: fullName"></strong></p>
                <div id="tableId">

                    <table id="table" summary="Department List" aria-label="Departments Table" class="dept-table"
                           data-bind="ojComponent: {component: 'ojTable',
                                    data: datasource,
                                    selectionMode: {row: 'single'},
                                    columnsDefault: {sortable: 'none'},
                                columns: [{headerText: 'First Name',
                                                 headerStyle:'background-color:red', style:'width:30px;',
                                               field: 'firstName'},
                                              {headerText: 'Last Name',
                                                   headerStyle:'background-color:red', style:'width:30px;',
                                               field: 'lastName'},
                                              
                                              ],
                                    rootAttributes: {'style':'width: 100%; height:100%;'}}">
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>


As you see , the above code is self-explanatory. I have 2 input text fields which are bind to firstName and lastName.
Created ojTable to show entered firstName and lastName information on click of add button and table fetches data from the data source.

Build the application for android and Test the application.


Now, our application is ready. Next step is to build the project using grunt for android platform. To do so,We can configure grunt build commands in our project properties as shown below




 As you see in the above screenshot, I have configured for android platform.

After a successful build, apk file is generated. You can find the apk file by navigating

<application root directory>\ hybrid\platforms\android\build\outputs\apk as shown in below screen shot


Copy the apk file to your android device and install the app.

Once the app started, home page looks as shown below


Provide value for First Name, Last Name and click on ‘Add’ button.

Record will get inserted into table and shows success message as shown below.


Table will show the entered record as shown below



Added couple of records, table refreshed and shows recently added records.


References

1.  Getting Started with Oracle JET Hybrid Mobile Application Development

2.  Storing local data in a Cordova App