Write Once, Run Everywhere: The Comic Books Collection Hybrid - CodeProject



The purpose of this Comic Book CRUD JQuery Mobile Application is to demonstrate how one can build a database app to store details of their comic books collectionand then compile it with PhoneGap Build to make it a hybrid application. The approach with this app will be following a SplitView approach, showing relationships between tables by loading Dropdown Lists with content from other 'files', how the ListView is used, Tables to add a report life feauture and how this report can be exported to Excel using javascript. At the end the app will be compiled with PhoneGap to make it a hybrid app.

I'm assuming you know a little bit of JQuery Mobile and some PhoneGap knowledge for this exercise.

Download My_Comic_Books.zip

Download PhoneGap Build Apk

In summary, the following sections of JQuery Mobile are explored with this article's content code.

1. Headers and Header Buttons

2. Footers

3. ListViews - title, description and count bubble

4. Text input

5. Select input (combobox/dropdownlist)

6. Table & export to Excel

7. Datepicker widget

8. Buttons & anchors

9. Labels

10. Styling with CSS


With JQuery Mobile, one is able to create HTML5 mobile apps that can run on any device. When these apps are compiled with PhoneGap Build, these can be installed on mobile devices just like native apps. With this application we have two 'files' that store grades and book types. These grades and book types get loaded into the comic book screen as they are dynamic, displaying the relational comboboxes mentioned above. The following 8 figures depics how the Comic Books Collection app will look like.

Figure 1: The SpringBoard (on a black berry device)

The SpringBoard provides a user with 4 options, to access the Comics Books screen, the Book Types, the book Grades and Reports.

Figure 2: Comic Books (running on iPad)

The Comic Books is the main screen where the comic book details will be added to the collection. This has been defined as a splitview screen where users can select from a listview and also search from it for a comic book they want. Before a comic book gets captured, the grades and book types should be defined.

Figure 3: Book Types

Book types define the type of Comic Book your book is. There can be graphic novel, serial, trade and other types that you can define. When adding a comic book, the book types will be read from this file and loaded to the dropdown list.

Figure 5: Grades

Grades define the grade that you can allocate to your comic book. It could be fine, fair, good, mint condition etc.

Figure 6: Reports (on another blackberry)

When a user selects Reports, the various types of reports are available from the application. These can be exported to Excel format. Clicking Columns to Display provides one with an option to toggle the columns to display on the report. These however will be exported to Excel when Export to Excel is clicked even if they are hidden on the table.

Figure 7: Comic Books Report

Figure 7.1. Comic Books Excel Report

As soon as you click Export to Excel, an Excel report will be generated from the table contents named ComicBook.xls.

Figure 8: Book Types Report

Figure 8.1: Book Types Excel Report, from Export to Excel from this screen

Figure 9: Grades Report

Figure 9.1: Grades Excel Report:

Using the code

Designing My Comic Books

Starting to design this app you follow the normal boilerplate prototypes to create HTML5 applications. You can refer to the source html and javascripts in the files attached. 

Designing the SpringBoard: html definition - Figure 1

<div id="pgMenu" data-role="page" data-theme="b" class="my-page">
<header id="pgMenuheader" data-role="header" data-position="fixed">
<h1>My Comic Books</h1>
<div id="pgMenucontent" data-role="content">
<ul data-role="listview" data-inset="true" id="sbItems">
<li data-icon="false"><a data-transition="slide" id="sbComicBook" href="#pgAddComicBook"><h2>Comic Books</h2><p>Maintain Comic Books</p><img height="200" width="100%" src="apps80.png" alt="Comic Books" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbBookType" href="#pgAddBookType"><h2>Book Types</h2><p>Maintain Book Types</p><img height="200" width="100%" src="apps80.png" alt="Book Types" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbGrade" href="#pgAddGrade"><h2>Grades</h2><p>Maintain Grades</p><img height="200" width="100%" src="apps80.png" alt="Grades" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbReports" href="#pgReports"><h2>Reports</h2><p>Access Reports</p><img height="200" width="100%" src="apps80.png" alt="Reports" class="ui-li-thumb"></img></a></li>

<footer id="pgMenufooter" data-role="footer" data-position="fixed">
<h1>Powered by JQM.Show © Anele Mbanga 2015</h1>

The springboard is actually a listview and was cloned from this example from the jquery mobile website. This is depicted as Figure 1 above.

Designing the Comic Books Entry Screen: Figure 2

This screen has been created as a split screen to show a listview and details for each comic book. Before this screen is loaded, available grades and book types are loaded from any captured to this screen. The method below is called from app.ComicBookBindings

case 'pgAddComicBook':
// clear the add page form fields
//load related select menus before the page shows

This ensures we have the latest book types and grade types loaded before the screen is shown.

<div id="pgAddComicBook" data-role="page">
<header id="pgAddComicBookheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Add Comic Book</h1>
<div id="pgAddComicBookcontent" data-role="content">
<div style="width:30%;float:left;">
<ul data-role="listview" data-inset="true" id="pgAddComicBookList" data-autodividers="true" data-filter="true" data-filter-placeholder="Search Comic Books" data-filter-reveal="false">
<li data-role="list-divider">Your Comic Books</li>
<li id="noComicBook">You have no comic books</li>
<div style="width:65%;float:left;margin-left:35px;">
<form action="#" method="post" id="pgAddComicBookForm" name="pgAddComicBookForm">
<div data-role="fieldcontain">
<label for="pgAddComicBookTitle" id="lblpgAddComicBookTitle">Title<span style='color:red;'>*</span></label>
<input required title="Enter title here." type="text" name="pgAddComicBookTitle" id="pgAddComicBookTitle" placeholder="Enter title here." autocomplete="off" data-clear-btn="true"></input>
<div data-role="fieldcontain">
<label for="pgAddComicBookIssueNumber" id="lblpgAddComicBookIssueNumber">Issue Number<span style='color:red;'>*</span></label>
<input required title="Enter issue number here." type="number" name="pgAddComicBookIssueNumber" id="pgAddComicBookIssueNumber" placeholder="Enter issue number here." autocomplete="off" data-clear-btn="true"></input>
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddComicBookGrade" id="lblpgAddComicBookGrade">Grade<span style='color:red;'>*</span></label>
<select name="pgAddComicBookGrade" id="pgAddComicBookGrade" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr" class="required">
<option value="null" data-placeholder="true">Select Grade</option>
<option ></option>
<div dir="ltr" data-role="fieldcontain">
<label for="pgAddComicBookBookType" id="lblpgAddComicBookBookType">Book Type<span style='color:red;'>*</span></label>
<select name="pgAddComicBookBookType" id="pgAddComicBookBookType" data-native-menu="false" data-mini="true" data-inline="true" dir="ltr" class="required">
<option value="null" data-placeholder="true">Select Book Type</option>
<option ></option>
<div data-role="fieldcontain">
<label for="pgAddComicBookPublicationDate" id="lblpgAddComicBookPublicationDate">Publication Date<span style='color:red;'>*</span></label>
<input required data-options='{"mode":"flipbox","dateFormat":"%Y-%m-%d","overrideDateFormat":"%Y-%m-%d"}' title="Enter publication date here." type="text" name="pgAddComicBookPublicationDate" id="pgAddComicBookPublicationDate" placeholder="Enter publication date here." autocomplete="off" data-role="datebox"></input>
<div data-role="fieldcontain">
<label for="pgAddComicBookBookValue" id="lblpgAddComicBookBookValue">Book Value<span style='color:red;'>*</span></label>
<input required title="Enter book value here." type="number" name="pgAddComicBookBookValue" id="pgAddComicBookBookValue" placeholder="Enter book value here." autocomplete="off" data-clear-btn="true"></input>
<div data-role="fieldcontain">
<label for="pgAddComicBookPrice" id="lblpgAddComicBookPrice">Price<span style='color:red;'>*</span></label>
<input required title="Enter price here." type="number" name="pgAddComicBookPrice" id="pgAddComicBookPrice" placeholder="Enter price here." autocomplete="off" data-clear-btn="true"></input>
<div data-role="fieldcontain">
<input required title="" type="checkbox" name="pgAddComicBookSigned" id="pgAddComicBookSigned" autocomplete="off" value="Signed"></input>
<label for="pgAddComicBookSigned" id="lblpgAddComicBookSigned">Signed<span style='color:red;'>*</span></label>
<div data-role="fieldcontain">
<input required title="" type="checkbox" name="pgAddComicBookBagged" id="pgAddComicBookBagged" autocomplete="off" value="Bagged"></input>
<label for="pgAddComicBookBagged" id="lblpgAddComicBookBagged">Bagged<span style='color:red;'>*</span></label>
<div><button type="submit" id="pgAddComicBookSave" class="ui-btn ui-corner-all ui-shadow ui-btn-b">Save Comic Book</button>
<div><button id="pgAddComicBookDelete" class="ui-btn ui-corner-all ui-shadow">Delete Comic Book</button>
<div><button id="pgAddComicBookBack" class="ui-btn ui-corner-all ui-shadow">Cancel</button>

This screen does not have header buttons, but as it is defined in a splitview, one is able to CR-eate, U-pdate and D-elete comic books form the same screen. I decided to improve navigation here and have these buttons at the bottom of the screen as its easier for users like that. Going back to the previous screen, one can just click Cancel.

You will note when you run the app, a toast gets show notifiying the user that the record was saved.

We have defined a left div here with

<div style="width:30%;float:left;">

and the right div with

<div style="width:65%;float:left;margin-left:35px;">

The left div has the listview and the right one has the comic book details. Each time a comic book is saved, the listview gets updated with the added record.

// code to run when the Save button is clicked on Add page.
// Save click event on Add page
$('#pgAddComicBookSave').on('click', function(e){
// save the Comic Book
var ComicBookRec;
//get form contents into an object
ComicBookRec = pgAddComicBookGetRec();
//save object to localstorage

The comic book details are read and stored in ComicBookRec by pgAddComicBookGetRec. You will notice the naming conventions in this app. For ease of maintenance and readability, controls are referenced by the page they belong to and the method thereof. pgAdd means a page for Adding as I have defined it.

// save the defined Add page object to local storage
// add a new record to localstorage.
app.addComicBook = function(ComicBookRec){
// get Comic Book records.
var ComicBookObj = app.getComicBook();
// define a record object to store the current details
var Title = ComicBookRec.Title;
// cleanse the record key of spaces.
Title = Title.replace(/ /g,'-');
// update local storage object with new record.
ComicBookObj[Title] = ComicBookRec;
//sort the objects.
var keys = Object.keys(ComicBookObj);
var sortedObject = Object();
var i;
for (i in keys) {
key = keys[i];
sortedObject[key] = ComicBookObj[key];
ComicBookObj = sortedObject;
// save all existing records to localstorage.
localStorage['mycomicbooks-comicbook'] = JSON.stringify(ComicBookObj);
toastr.success('Comic Book record saved.', 'My Comic Books');
//find which page are we coming from, if from sign in go back to it
var pgFrom = $('#pgAddComicBook').data('from');
switch (pgFrom) {
case "pgSignIn":
$.mobile.changePage('#pgSignIn', {transition: 'slide'});
// clear the edit page form fields
//stay in the same page to add more records

I have also ensured that I comment the code as much as possible. Clicking the save button at the end calls a method called:


This basically, reads all available LocalStorage comic books, adds/updates the added comic books, sorts all comic books by title before they are saved back to localstorage, clears the contents of the screen with


and reloads the left div listview by executing


My article here about Creating CRUD JQuery Mobile Apps talks more about these types of applications.

Designing the Reports SpringBoard: html definition - Figure 6

<div id="pgReports" data-role="page" data-theme="b" class="my-page">
<header id="pgReportsheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Reports</h1>
<div id="pgReportscontent" data-role="content">
<ul data-role="listview" data-inset="true" id="sbRptItems">
<li data-icon="false"><a data-transition="slide" id="sbRptBack" href="#pgMenu"><h2>Main Menu</h2><p>Go to Main Menu</p><img height="200" width="100%" src="apps80.png" alt="Main Menu" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptComicBook" href="#pgRptComicBook"><h2>Comic Books Report</h2><p>View & Export Comic Books</p><img height="200" width="100%" src="apps80.png" alt="Comic Books" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptBookType" href="#pgRptBookType"><h2>Book Types Report</h2><p>View & Export Book Types</p><img height="200" width="100%" src="apps80.png" alt="Book Types" class="ui-li-thumb"></img></a></li>
<li data-icon="false"><a data-transition="slide" id="sbRptGrade" href="#pgRptGrade"><h2>Grades Report</h2><p>View & Export Grades</p><img height="200" width="100%" src="apps80.png" alt="Grades" class="ui-li-thumb"></img></a></li>
<footer id="pgReportsfooter" data-role="footer" data-position="fixed">
<h1>Powered by JQM.Show © Anele Mbanga 2015</h1>

The definition as depicted in Figure 6 above is produced by this piece of code. The book types and grades follow the same approach of design.

Designing the Comic Books Table: html definition - Figure 7

<div id="pgRptComicBook" data-role="page">
<header id="pgRptComicBookheader" data-role="header" data-position="fixed">
<h1>My Comic Books > Comic Books</h1>
<a data-role="button" id="pgRptComicBookBack" data-icon="arrow-l" class="ui-btn-left">Back</a>
<a data-role="button" id="pgRptComicBookNew" data-icon="plus" data-theme="b" class="ui-btn-right">New</a>
<div id="pgRptComicBookcontent" data-role="content">
<table id="RptComicBook" data-column-btn-text="Columns To Display" data-column-btn-theme="b" data-role="table" data-mode="columntoggle" data-column-popup-theme="a" class="ui-responsive table-stroke table-stripe ui-shadow ui-body-d">
<caption>Comic Books Report</caption>
<tr class="ui-bar-d">
<th >Title</th>
<th data-priority="2">Issue Number</th>
<th data-priority="3">Grade</th>
<th data-priority="4">Book Type</th>
<th data-priority="5">Publication Date</th>
<th data-priority="6">Book Value</th>
<th data-priority="7">Price</th>
<th data-priority="8">Signed</th>
<th data-priority="9">Bagged</th>
<tr><td colspan="9">Powered by JQM.Show - https://play.google.com/store/apps/details?id=com.b4a.JQMShow</td></tr></tfoot>
<div data-role="fieldcontain">
<a download="ComicBook.xls" onclick="return ExcellentExport.excel(this, 'RptComicBook', 'ComicBook');" id="pgRptComicBookExport" data-corners="true" href="#" class="ui-btn ui-shadow">Export to Excel</a></div>

This page has a back button and also shows a New button to create new comic books. The Title heading of the table is a compulsory column and thus does not show on the toggle column chooser. This is because it is not defined with data-priority, but as 

<th >Title</th>

You can change the text to be displayed on the toggle button chooser by changing the text of this attribute in the table.

data-column-btn-text="Columns To Display"

The button theme can also be changed here between the a-e themes available here.


The theme of the popup that displays when the toggle button is clicked is defined here


We wanted the table to be easily read by the user and this striped the rows by adding this as part of the class


The listview on the add page is clickable and when a user clicks each comic, this method gets called

//listview item click eventt.
$(document).on('click', '#pgAddComicBookList a', function(e){
//get href of selected listview item and cleanse it
var href = $(this)[0].href.match(/\?.*$/)[0];
var Title = href.replace(/^\?Title=/,'');
//read record from local storage and update screen.

This reads the href of each added listitem and calls the app.pgAddComicBookeditComicBook method which reads the comic book details from LocalStorage and displays it on the screen. As this listview is dynamic, it needs to be reloaded each time a comic is added. This dynamic loading is done with this method.

//display records in listview during runtime.
app.pgAddComicBookdisplayComicBook = function(){
// get Comic Book records.
var ComicBookObj = app.getComicBook();
// create an empty string to contain html
var html = '';
// make sure your iterators are properly scoped
var n;
// loop over records and create a new list item for each
//append the html to store the listitems.
for (n in ComicBookObj){
//get the record details
var ComicBookRec = ComicBookObj[n];
//define a new line from what we have defined
var nItem = ComicBookLi;
//update the href to the key
nItem = nItem.replace(/Z2/g,n);
//update the title to display, this might be multi fields
var nTitle = '';
nTitle += ComicBookRec.Title;
nTitle += ', ';
nTitle += ComicBookRec.IssueNumber;
//replace the title;
nItem = nItem.replace(/Z1/g,nTitle);
//there is a count bubble, update list item
var nCountBubble = '';
nCountBubble += ComicBookRec.Price;
//replace the countbubble
nItem = nItem.replace(/COUNTBUBBLE/g,nCountBubble);
//there is a description, update the list item
var nDescription = '';
nDescription += ComicBookRec.Grade;
nDescription += ', ';
nDescription += ComicBookRec.BookType;
nDescription += ', ';
nDescription += ComicBookRec.PublicationDate;
//replace the description;
nItem = nItem.replace(/DESCRIPTION/g,nDescription);
html += nItem;
//update the listview with the newly defined html structure.
$('#pgAddComicBookList').html(ComicBookHdr + html).listview('refresh');

Those are the basics of Figure 7 above.

Exporting to Excel

This is made possible by this js file as defined within the html 5 app.

<script src="excellentexport.min.js" type="text/javascript"></script>

This is called within the button definition in this way for example.

<div data-role="fieldcontain">
<a download="ComicBook.xls" onclick="return ExcellentExport.excel(this, 'RptComicBook', 'ComicBook');" id="pgRptComicBookExport" data-corners="true" href="#" class="ui-btn ui-shadow">Export to Excel</a></div>

This I must say was a very interesting find for me. This however does not export css definitions. I also had to add the following css to make the table more user friendly.

tr { border-bottom: 1px solid #d6d6d6;}
th { border-bottom: 1px solid #d6d6d6;}
caption {font-weight: bold; font-size: 1.1em;}

The Header Titles

I have learned that in some instances, the headers will show a ... at the end where the header text is long. One can eliminate that by adding this style.

.ui-header .ui-title, .ui-footer .ui-title {margin-right: 0 !important; margin-left: 0 !important;}

and this

$('#msgboxheader h1').text('Confirm Delete');

is a very easy method to change a header title for any element with <h1></h1> definition.

We have defined our comic book collection app and created all the relevant code, now its time to compile this app to phonegap.

Making My Comic Books Hybrid - this is commercial however your projects will be open source if you compile for free as they will sit on GitHub.

1. Get GitHub for windows, this allows dradding and dropping your folders to github and you can sync and publish these easily. As you will notice, the config.xml file attached herein has all the nitty grities to enable easily compilation of your app to phonegap. Sign Up with github.com and link your desktop Github with the web one.

Once your desktop has been published, your web version should display it like this: See My-Comic-Books below

2. Sign Up for PhoneGap build on https://build.phonegap.com and link your GitHub account. You can find instructions on how to do this there or just copy your GitHib url to the PhoneGap Build.

Click Ready to build and build your app and your app will be built by the respective compilers for the available platforms. For iPhone/iPad, you need a developer account from apple and they register your profile details here so that you can have an ipa compiled. Currently lets just compile and get the apk for Android. There is also a xap for windows phone.

You can install your new app with MoboRobo to your mobile device.

Installing My Comic Books to actual device (screen shots taken with MoboRobo)

Running My Comic Books on actual device

As this app is made for bigger screens, the input screens do not display properly in this device. I have included a link to a dropbox file here of the apk for you to download and explore on your device. That's it then.

Points of Interest

Whilst it might be a little easy to develop JQuery Mobile apps with superb functionality, the stages of getting everything compiled to a hybrid app with PhoneGap build was a little steep at first to make it work. There is GitHub for Desktop and also Github on the web to check if everything works fine and then PhoneGap build. For anyone wanting to publish an application they however have to go through these steps for a final product.

The most joy is having the output running in an actual mobile device though. Which I think the PhoneGap Build Team has done a very excellent job in making this tool available. The existence of the MoboRobo app also makes this easy to take screen shots of device screens and also install for both iPhone/iPad/Android devices.

Creating the tables in JQ Mobile was a nice exercise also for me, then finding the export to excel feauture was an excellent addition. I am working on an all RAD tool to actually build JQuery Mobile Apps and these additions have been an eye opener. Such a rad tool will create all these mobile apps including the respective scripts without any programming experience and no writing any code at all.

I also want to add permissions in my JQuery Mobile apps using listviews with switches and collapsible views. I dont seem to have seen anything like that yet.