Thursday, December 2, 2010

Offline Bible on the iPhone with jQuery and Lawnchair


Using jQuery and Lawnchair as well as the new offline capabilities of HTML5 I've developed an offline Bible reader for the iPhone though it should work on other devices as well.

The interface has been kept lean and mean and you can simply swipe to scroll down the chapter, use the <> buttons to navigate a chapter and the dropdown list to select a book.

The first time you start the application it loads all books from the server. So far I've only got the ESV New Testament and when I find a free Bible version and get it converted to JSON I'll publish the app.

The app works offline as well so you can browse the texts using the new offline cache of HTML5. This great new standard allows you to define which scripts and resources the browser should keep available offline (i.e. cache). You include your manifest in the HTML tag as follows:


The DOCTYPE command tells the browser that this is an HTML5 document. Also, my manifest is an ASP file because your web server must serve the manifest with the Content-Type set to "text/cache-manifest". To avoid configuring your web server I added the header in the ASP code so it will run on any unconfigured IIS server:
<% Response.ContentType = "text/cache-manifest" %>CACHE MANIFEST


CACHE:
jquery-1.4.4.js
LawnchairAdaptorHelpers.js
DOMStorageAdaptor.js
Lawnchair.js
bible.js
bible.css
bible.png
Lawnchair is a free JavaScript library which allows you to save data on the client. My application loads the books of the New Testament from simples text files like "Matthew.txt" which contain the chapters and verses in JSON format. Here's 2 John.txt:
{

b:"2 John",
i:63,
c:[
{v:[
{t:"The elder to the elect lady and her children, whom I love in truth, and not only I, but also all who know the truth,"},
{t:"because of the truth that abides in us and will be with us forever:"},
{t:"Grace, mercy, and peace will be with us, from God the Father and from Jesus Christ the Father's Son, in truth and love."},
{t:"I rejoiced greatly to find some of your children walking in the truth, just as we were commanded by the Father."},
{t:"And now I ask you, dear lady - not as though I were writing you a new commandment, but the one we have had from the beginning - that we love one another."},
{t:"And this is love, that we walk according to his commandments; this is the commandment, just as you have heard from the beginning, so that you should walk in it."},
{t:"For many deceivers have gone out into the world, those who do not confess the coming of Jesus Christ in the flesh. Such a one is the deceiver and the antichrist."},
{t:"Watch yourselves, so that you may not lose what we have worked for, but may win a full reward."},
{t:"Everyone who goes on ahead and does not abide in the teaching of Christ, does not have God. Whoever abides in the teaching has both the Father and the Son."},
{t:"If anyone comes to you and does not bring this teaching, do not receive him into your house or give him any greeting,"},
{t:"for whoever greets him takes part in his wicked works."},
{t:"Though I have much to write to you, I would rather not use paper and ink. Instead I hope to come to you and talk face to face, so that our joy may be complete."},
{t:"The children of your elect sister greet you."}
]}
]}
As you can see each book has 3 properties:
  1. The name b:"2 John"
  2. The index order in the canon i:63
  3. The chapter list c:[]
The index order is used for displaying the dropdown list for selecting a book.

These text files are loaded via the jQuery.ajax() function which is then parsed as a JavaScript Object. This object is then stored locally via the Lawnchair library which supports various types of storage including a SQLite database. Although Apple's iPhone supports this "WebSQL" format, FireFox doesn't, so I stuck to localStorage aka. "DOM Storage".

To retrieve a book which is cached, you use the get() function which is a bit unwieldy since it uses callbacks instead of just returning a value:
db.bible = new Lawnchair({table:'bible'});

db.bible.get('2 John', function(r){
db.currentBook = r;
});
The first command basically says I want a new data store or table called "bible" and I use get() to fetch the book object. This r object has the b, i and c[] properties and I can loop through the chapters or the verses in a specific chapter and display them.

To display the chapter list we need a dropdown list in our HTML:


Which we then fill with the cool jQuery function each() which loops through our array:
$('#chapter').children().remove();

$.each(db.currentBook.c, function(index, value) {
$('#chapter').append($("").attr("value",index+1).text(index+1));
});
Listing verses in a chapter is also easy:
 $('#verseList').empty();

$.each(db.currentBook.c[chapter], function(i,v) {
$('#verseList').append(''+(i+1)+''+v.t+' ');
});
window.scrollTo(0,1);

I added the scrollTo command for two reasons: 1) The user should be taken to the top of the screen and 2) That this automatically hides Safari's toolbar on the iPhone which can get in the way.

The little next ">" and previous "<" buttons also use jQuery eventing:
$('#next,#next1').click(nextChapter);

$('#prev,#prev1').click(prevChapter);

And I have a function to disable them when we are at the start or end of a chapter:
function showNextPrev() {

$('#prev1, #prev').attr("disabled",db.lastBook.c==1)
$('#next1, #next').attr("disabled",db.lastBook.c==db.currentBook.c.length)
}
To make the app look sexy you should always add a nice icon and include it in your HTML as follows:


The viewport meta tag is also important because it ensures the iPhone browser displays your app in the correct size in any orientation!


Finally, I tried the jQuery swipe plugin because I wanted to be able to swipe forwards and backwards. Unfortunately the swiping doesn't work well and it disabled the ability to scroll up and down so I've removed it but I may come back to it again because that would be awesome.