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("name"); 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 = "Chapter "+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("name"); return fullname; } function ResetClickedBook() { if (clickedbook != null) { var previous = document.getElementById(clickedbook); previous.parentNode.setAttribute("class", "book"); while(previous.parentNode.childNodes.length > 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("class", "chapter"); while(previous.parentNode.childNodes.length > 1) { previous.parentNode.removeChild(previous.parentNode.lastChild); } ToggleFullChapterName(clickedchapter); } } function ToggleSelector() { var selector = document.getElementById("bible_hierarchy"); if (selector.getAttribute("style") == "display: none;") { selector.setAttribute("style", ""); } else { selector.setAttribute("style", "display: none;"); } } 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; } }
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!