Bible Taxonomy – User Interface Data

BibleTax_Featured_2
Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

So the CSS was the eas(ier) part. Then came the Javascript. It had to be snappy and non-clunky.

I started out trying to parse the XML with my own logic. Not a good idea. I used the JQuery framework to get it working. Here’s the resulting JS code snippets with commentary:

1. Declarations + init run:
numColumns limits the default books per row. Others variables are either data objects or placeholders with a larger scope so they can be accessed across methods (or as statics outside of the iterators)

var bibleXML;
var clickedbook;
var clickedchapter;
var clickedverse;

var start_green;
var end_red;

var serviceXML;

var numColumns = 6;
var numRows = 0;
var chapterNumRows = 0;
var verseNumRows = 0;


jQuery.ajax({ type: "GET",
	url: "http://localhost:8888/DiscipleShare-test/wp-content/themes/discipleshare/bible-taxonomy/Bible_min_2.xml",
	dataType: "xml",
	success:
		parseBooks
});

2. parseXML (called from JQuery successful load of file): this is the framework for the whole bible. It visually splits the books into OT and NT. It also requires no hidden fields to hold values since the JS can hold the tags’ ids.

function parseBooks(xml)
{
    bibleXML = xml;
	divoutput = document.getElementById("bible_hierarchy");
	var ul = document.createElement("ul");
	ul.setAttribute("id", "ulOTBooks");
	//find every book and print the english abbreviation
    jQuery(xml).find("book").each(function()
    {
      if (jQuery(this).attr("abbrev-en")!="Mt") {
      	var li = document.createElement("li");
      	li.setAttribute("class", "book");
      	var a = document.createElement("a");
      	a.setAttribute("id", "book_"+jQuery(this).attr("abbrev-en"));
      	a.setAttribute("onClick", "Javascript: BookClick(this, id);");
      	a.innerHTML = jQuery(this).attr("abbrev-en");
      	li.appendChild(a);
      	ul.appendChild(li);
      }
      else { // start the NT
      	// add previous ul
      	divoutput.appendChild(ul);
      	
      	ul = document.createElement("ul");
      	ul.setAttribute("id", "ulNTBooks");
      	
      	var li = document.createElement("li");
      	li.setAttribute("class", "book");
      	var a = document.createElement("a");
      	a.setAttribute("id", "book_"+jQuery(this).attr("abbrev-en"));
      	a.setAttribute("onClick", "Javascript: BookClick(this, id);");
      	a.innerHTML = jQuery(this).attr("abbrev-en");
      	li.appendChild(a);
      	ul.appendChild(li);
      }
    });
    
    
    divoutput.appendChild(ul);

	// Load for current post -- doing it this way because couldn't find a better function to hook into
	getBibleVerseCollectionsForPost();
}

3. Handle when clicks happen. Books, Chapters, Verses — the logic of the methods is similar, but they pull different data from the XML file to create the UI elements and clear out the now irrelevant ones.

function BookClick(e, linkID) {
	ResetClickedBook();
	var sender = (e && e.target) || (window.event && window.event.srcElement);
	if (sender == null || sender == undefined) {
		sender = document.getElementById(linkID);
	}
	var abbrev = sender.id.substring(5);
	var book = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr("name");
	clickedbook = sender.id;
	
	sender.parentNode.setAttribute("class", "clickedbook"); // parent node is <li> of <a>
	ToggleFullBookName(sender.id);
	
	selected = sender;
	UpdateVersesInput(sender, "book");
	
	var chaptercount = -1;		
	chaptercount = jQuery(bibleXML).find('book[name='+book+']').attr("chaptercount");
	
	var ul = document.createElement("ul");
	ul.setAttribute("id", "chapters_"+book);
	
	for (var i = 1; i < (chaptercount*1)+1; i++) { // *1 to explicitly type as numeric
      	 var li = document.createElement("li");
      	 li.setAttribute("class", "chapter");
      	 var a = document.createElement("a");
      	 a.setAttribute("id", "chapter_"+i);
      	 a.setAttribute("onClick", "JavaScript: ChapterClick(this, id);");
      	 a.setAttribute("title", book);
      	 a.innerHTML = i;
      	 li.appendChild(a);
      	 ul.appendChild(li);
	}
	sender.parentNode.appendChild(ul);
}

function ChapterClick(e, linkID) {
	ResetClickedChapter();
	
	var sender = (e && e.target) || (window.event && window.event.srcElement);
	if (sender == null || sender == undefined) {
		sender = document.getElementById(linkID);
	}
	var book = sender.title;
	var chapter = sender.id.substring(8);
	clickedchapter = sender.id;
	
	sender.parentNode.setAttribute("class", "clickedchapter");
	ToggleFullChapterName(sender.id);
	
	selected = sender;
	UpdateVersesInput(sender, "chapter");
			
	var versecount = -1;
	versecount = jQuery(bibleXML).find('book[name='+book+']').find('chapter[name='+chapter+']').attr("versecount");
	
	var ul = document.createElement("ul");
	
	for (var i = 1; i < (versecount*1)+1; i++) { // *1 to force as numeric
		 var li = document.createElement("li");
      	 li.setAttribute("class", "verse");
      	 var a = document.createElement("a");
      	 a.setAttribute("id", "verse_"+i);
      	 a.setAttribute("onClick", "JavaScript: VerseClick(this, id);");
      	 a.setAttribute("title", book);
      	 a.innerHTML = i;
      	 li.appendChild(a);
      	 ul.appendChild(li);		
	}
	sender.parentNode.appendChild(ul);
}

function VerseClick(e, linkID) {
	var sender = (e && e.target) || (window.event && window.event.srcElement);
	if (sender == null || sender == undefined) {
		sender = document.getElementById(linkID);
	}
	var book = clickedbook;
	var chapter = clickedchapter;
	var versenum = sender.id.substring(6);
	
	sender.parentNode.setAttribute("class", "verse_selected");
	selected = sender;
	
	if (clickedverse != null) {
		UnselectClickedVerse();
	}
	clickedverse = sender.id;
	
	UpdateVersesInput(sender, "verse");
}

4. The small details make the biggest difference. Little details, like toggling the book name from the abbreviation (and toggling it back) make for a better user experience. I also have it update a text field that’s used to collect and input the data for the WordPress backend. Here’re the little details in no particular order:

function ToggleFullBookName(link)
{	
	var a = document.getElementById(link);
	var abbrev = link.substring(5);
	if (abbrev == a.innerHTML) { // need to display full name
		var fullname = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr(&quot;name&quot;);
		a.innerHTML = fullname;
	} else { // need to shorten to just abbrev
		a.innerHTML = abbrev;
	}
}	

function ToggleFullChapterName(link)
{	
	var a = document.getElementById(link);
	var chapnum = link.substring(8);
	if (chapnum == a.innerHTML) { // need to display full chapter label
		var fullname = &quot;Chapter &quot;+chapnum;
		a.innerHTML = fullname;
	} else { // need to shorten to just chapnum
		a.innerHTML = chapnum;
	}
}	

function GetBookNameByElementID(link) {
	var a = document.getElementById(link);
	var abbrev = link.substring(5);
	var fullname = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr(&quot;name&quot;);
	return fullname;
}

function ResetClickedBook() {
	if (clickedbook != null) {
		var previous = document.getElementById(clickedbook);
		previous.parentNode.setAttribute(&quot;class&quot;, &quot;book&quot;);
		while(previous.parentNode.childNodes.length &gt; 1) {
			previous.parentNode.removeChild(previous.parentNode.lastChild);
		}
		ToggleFullBookName(clickedbook);
		
		clickedchapter = null; // reset that too so next clickedbook doesn't inherit chpt click
		start_green = null;
		end_red = null;
	}
}

function ResetClickedChapter() {
	if (clickedchapter != null) {
		var previous = document.getElementById(clickedchapter);
		previous.parentNode.setAttribute(&quot;class&quot;, &quot;chapter&quot;);
		while(previous.parentNode.childNodes.length &gt; 1) {
			previous.parentNode.removeChild(previous.parentNode.lastChild);
		}
		ToggleFullChapterName(clickedchapter);
	}
}

function ToggleSelector() {
	var selector = document.getElementById(&quot;bible_hierarchy&quot;);
	if (selector.getAttribute(&quot;style&quot;) == &quot;display: none;&quot;) {
		selector.setAttribute(&quot;style&quot;, &quot;&quot;);
	} else {
		selector.setAttribute(&quot;style&quot;, &quot;display: none;&quot;);
	}
}

function UpdateVersesInput(sender, rangeLocation) {
	var input = document.getElementById("verserange");
	
	var ref;
				
	if (rangeLocation == "book") {
		var book = jQuery(bibleXML).find('book[abbrev-en='+sender.id.substring(5)+']').attr("name");
		ref = book;
		input.value = ref;
		
	} else if (rangeLocation == "chapter") { // appending values to input, not replacing them
		var book = jQuery(bibleXML).find('book[abbrev-en='+clickedbook.substring(5)+']').attr("name");
		var chapter = sender.id.substring(8);
		ref = book+" "+chapter;
		input.value = ref;
	}
	else if (rangeLocation == "verse") {
		var book = jQuery(bibleXML).find('book[abbrev-en='+clickedbook.substring(5)+']').attr("name");
		var chapter = clickedchapter.substring(8);
		var verse = sender.id.substring(6);
	
		ref = book+" "+chapter+":"+verse;
		
		input.value = ref;
	}
	
}

Comments

  1. Hi Adam, I have been looking for a Bible Verse Taxonomy for a WordPress site I’m developing for a pastor. I saw one of your older posts about potentially putting this on WP. Have you implemented it? I’d be interesting in talking to you more about using it for WP. Hope to hear from you!

Speak Your Mind