JSword on the Web

JSword can be web-enabled fairly simply, using DWR 2.0 to call Java as if it were JavaScript and Sarissa 0.9.9.3 to transform OSIS into HTML locally.

This tutorial assumes that you are able to set up and properly manage a JSP container such as Tomcat and install a servlet into it.

Upon completion this tutorial servlet should have the following structure:

        iBD (You can call it whatever you like)
        |-- index.html
        |-- sarissa.js
        |-- ibd.js
        |-- ibd.css
        |-- ibd.xsl
        |-- WEB-INF
            |-- web.xml
            |-- dwr.xml
            |-- lib  (populate this with all the jsword jars, its dependent jars and dwr)
                |-- jsword-1.6.jar
                |-- javatar-2.5.jar
                |-- jdom-1.1.1.jar
                |-- commons-codec-1.4.jar
                |-- httpcore-4.1.jar
                |-- httpclient-4.1-beta1.jar
                |-- commons-logging-1.1.1.jar
                |-- commons-net-2.2.jar
                |-- lucene-core-3.0.3.jar
                |-- lucene-snowball-3.0.3.jar
                |-- lucene-analyzers-3.0.3.jar
                |-- lucene-smartcn-3.0.3.jar
                |-- icu4j-4_6.jar
                |-- dwr.jar

dwr.xml defines how DWR binds to Java, and for the sake of this tutorial, it binds to JSword's DwrBridge with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
    "http://directwebremoting.org/schema/dwr20.dtd">
    
<dwr>
  <allow>
    <create creator="new" javascript="JSword">
      <param name="class" value="org.crosswire.jsword.bridge.DWRBridge"/>
    </create>
  </allow>
</dwr>

web.xml defines the servlet as a binding to DWR and, for the purposes of this tutorial, has the following content:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app>
  <display-name>JSword</display-name>
  <description>Servlet interface to the Bible</description>
  <servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <display-name>DWR Servlet</display-name>
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>dwr-invoker</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
  </servlet-mapping>
</web-app>

index.html needs to bring in DWR and Sarissa, along with our tutorial JavaScript and CSS StyleSheet. For the purposes of this tutorial, it has the following content:

<!DOCTYPE html PUBLIC
    "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>
  <head>
    <title>iBD</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <-- JSword.js is auto-generated by DWR via dwr.xml -->
    <script type='text/javascript' src='dwr/interface/JSword.js'></script>

    <-- engine.js and util.js are dug out of the dwr.jar -->
    <script type='text/javascript' src='dwr/engine.js'></script>
    <script type='text/javascript' src='dwr/util.js'></script>

    <-- Bring in the ability to transform OSIS XML into HTML in the user's Web Browser. -->
    <script type='text/javascript' src='sarissa.js'></script>

    <-- Bring in our JavaScript and CSS StyleSheet -->
    <script type='text/javascript' src='tutorial.js'></script>
    <link rel="stylesheet" type="text/css" href="tutorial.css"/>
  </head>
  <body onload="init()">

    <!-- The dropdown for books, in this case Bibles, starts out empty -->
    <div id="bibleBox">
      <select id="pick" onchange="pick();"></select>
    </div>

    <!-- A text entry and a button both call locate -->
    <div id="locateBox" align="left">
      <input type="text" id="passageRequest" onkeypress="dwr.util.onReturn(event, locate)"/>
      <button type="button"  id="passageButton" onclick="locate();">Locate</button>
    </div>

    <!-- A text entry and a button both call search -->
    <div id="searchbox">
      <input type="text" id="searchRequest" onclick="dwr.util.onReturn(event, search)"/>
      <button type="button" id="searchButton" onclick="search();">Search</button>
    </div>

    <div id="display"></div>

  </body>
</html>

iBD.js is the glue that connects the HTML to DWR's generated JSword.js, and for the sake of this tutorial it is the following. Note, this should not be a final production representation. It is not error friendly. It doesn't do any caching. It reparses the iBD.xsl repeatedly, but does not need to.

// Define how the OSIS document that is returned from JSword is styled.
var stylesheet = "iBD.xsl";
// Prevent the server from being hammered.
var verseLimit = 100;

/**
 * Prepare the page for use
 */
function init()
{
  // Use a Google styled "Loading" message in the upper right corner
  dwr.util.useLoadingMessage();

  // Display the current SWORD path as a diagnostic.
  //JSword.getSwordPath(loadDiagnostic);

  // Populate the books dropdown.
  // The last argument is an asynchronous callback
  JSword.getInstalledBooks("bookCategory=Bible", loadBooks);

  // Constrain the display area to be within the boundary of the window.
  window.onresize = ibdResize;
}

/*
 * Resize the height of the display area.
 * I tried pixels but it does not work for IE.
 */
function ibdResize()
{
  var top             = $("searchBox");
  var bottom          = $("display");
  var offset          = top.offsetTop + top.offsetHeight + 1;
  var windowHeight    = document.body.clientHeight;
  var newHeight       = windowHeight - offset;
  bottom.style.height = (newHeight / windowHeight) * 100 + "%";
}

/**
 * Load the list of known Books
 */
function loadBooks(data)
{
  // Empty the list.
  dwr.util.removeAllOptions("books");
  // Then populate it with data, using column "0" as the key and "1" as the display value
  // Use "0", "0" to only show the books "initials"
  dwr.util.addOptions("books", data, "0", "1");
}

/**
 * Called when book data has been fetched
 */
function loadDisplay(data)
{
  // Get an XSLT processor that can use xslDoc to do the transform
  var processor = new XSLTProcessor();

  // Load the stylesheet so that we can transform the document
  var xslDoc    = Sarissa.getDomDocument();
  // Synchronously load the stylesheet do that it is immediately available.
  // Otherwise, this will fail.
  xslDoc.async  = false;
  xslDoc.load(stylesheet);

  processor.importStylesheet(xslDoc);

  // Now take the answer from the locate and parse it into DOM
  var parser    = new DOMParser();
  var dom       = parser.parseFromString(data, "text/xml");

  // Finally, transform and display the results in one fell swoop.
  Sarissa.updateContentFromNode(dom, $("display"), processor);
}

/**
 * A Bible has been picked. If there is anything to locate or search, then do it.
 */
function pick()
{
  // When the book changes, take what ever is in locate and get it.
  // If that doesn't work then try what ever is in search.
  locate() || search();
}

/**
 * Locate a passage.
 */
function locate()
{
  var book = getBook();
  var ref  = getPassage();
  if (book && ref)
  {
    // Get the OSIS representation from the book for the reference
    // But limit the number of verses
    // Arrange for asynchronous loading of the display
    JSword.getOSISString(book, ref, verseLimit, loadDisplay);
    return true;
  }
  return false;
}

/**
 * Perform a search
 */
function search()
{
  var book   = getBook();
  var search = getSearch();
  if (book && search)
  {
    // Get the reference for the search
    // and asynchrounously load it in to the locate box
    JSword.search(book, search, setPassage);
    return true;
  }
  return false;
}

/**
 * Get the search request.
 */
function getSearch()
{
  return dwr.util.getValue("searchRequest");
}

/**
 * Perform a search.
 */
function setSearch(query)
{
  // Whenever we stuff a value into search request
  dwr.util.setValue("searchRequest", query);
  // do the search
  search();
  // Allow this to be used in an anchor that ignores its href
  return false;
}

/**
 * Get the passage to locate.
 */
function getPassage()
{
  return dwr.util.getValue("passageRequest");
}

/**
 * Locate a passage
 */
function setPassage(ref)
{
  // whenever we stuff a value in locate
  // Note: search merely stuffs a value here.
  dwr.util.setValue("passageRequest", ref);
  // go get the content.
  locate();
  // Allow this to be used in an anchor that ignores its href
  return false;
}

/**
 * Get the selected book.
 */
function getBook()
{
  return dwr.util.getValue("books");
}

/**
 * Set the book to search or locate against
 */
function setBook(book)
{
  // When ever a book is set
  dwr.util.setValue("books", book);
  // See if there is something we can locate or search.
  pick();
  // Allow this to be used in an anchor that ignores its href
  return false;
}

JSword.js is generated on demand by DWR, reflecting a JavaScript representation of DwrBridge and it has the following content:

// Provide a default path to dwr.engine
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

if (JSword == null) var JSword = {};
JSword._path = '/jsword/dwr';
JSword.search = function(p0, p1, callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'search', p0, p1, callback);
}
JSword.match = function(p0, p1, p2, callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'match', p0, p1, p2, callback);
}
JSword.isIndexed = function(p0, callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'isIndexed', p0, callback);
}
JSword.getInstalledBooks = function(p0, callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'getInstalledBooks', p0, callback);
}
JSword.getOSISString = function(p0, p1, p2, callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'getOSISString', p0, p1, p2, callback);
}
JSword.getSwordPath = function(callback) {
  dwr.engine._execute(JSword._path, 'JSword', 'getSwordPath', callback);
}

About JSword

Getting JSword

Getting Involved

Documentation

Nightly Build

Other Projects