// You must also add a numLeafs property before calling init().
function BookReader() {
+
+ // Mode constants
+ this.constMode1up = 1;
+ this.constMode2up = 2;
+ this.constModeThumb = 3;
+
this.reduce = 4;
- this.padding = 0;
- this.mode = 1; //1, 2, 3
- this.ui = 'full'; // UI mode
+ this.padding = 10; // Padding in 1up
+
+ this.mode = this.constMode1up;
+ this.ui = 'full'; // UI mode
+ this.uiAutoHide = false; // Controls whether nav/toolbar will autohide
// thumbnail mode
this.thumbWidth = 100; // will be overridden during prepareThumbnailView
this.thumbRowBuffer = 2; // number of rows to pre-cache out a view
this.thumbColumns = 6; // default
this.thumbMaxLoading = 4; // number of thumbnails to load at once
+ this.thumbPadding = 10; // spacing between thumbnails
this.displayedRows=[];
this.displayedIndices = [];
// path in the CSS. Would be better to automagically find that path.
this.imagesBaseURL = '/bookreader/images/';
- // Mode constants
- this.constMode1up = 1;
- this.constMode2up = 2;
- this.constModeThumb = 3;
// Zoom levels
// $$$ provide finer grained zooming
autofit: 'auto'
};
+ // This object/dictionary controls which optional features are enabled
+ // XXXmang in progress
+ this.features = {
+ // search
+ // read aloud
+ // open library entry
+ // table of contents
+ // embed/share ui
+ // info ui
+ };
+
return this;
};
// Autohide nav after showing for awhile
var self = this;
- $(window).bind('load', function() {
- setTimeout(function() { self.hideNavigation(); }, 3000);
- });
+ if (this.uiAutoHide) {
+ $(window).bind('load', function() {
+ setTimeout(function() { self.hideNavigation(); }, 3000);
+ });
+ };
$("#BRcontainer").bind('scroll', this, function(e) {
e.data.loadLeafs();
// Enact other parts of initial params
this.updateFromParams(params);
- //XXXmang window.setTimeout(self.hideNavigation, 3000);
+ // Start AJAX request for OL data
+ if (this.getOpenLibraryRecord) {
+ this.getOpenLibraryRecord(this.gotOpenLibraryRecord);
+ }
}
BookReader.prototype.setupKeyListeners = function() {
// Calculate the position of every thumbnail. $$$ cache instead of calculating on every draw
for (i=0; i<this.numLeafs; i++) {
leafWidth = this.thumbWidth;
- if (rightPos + (leafWidth + this.padding) > viewWidth){
+ if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){
currentRow++;
rightPos = 0;
leafIndex = 0;
if (leafHeight > leafMap[currentRow].height) {
leafMap[currentRow].height = leafHeight;
}
- if (leafIndex===0) { bottomPos += this.padding + leafMap[currentRow].height; }
- rightPos += leafWidth + this.padding;
+ if (leafIndex===0) { bottomPos += this.thumbPadding + leafMap[currentRow].height; }
+ rightPos += leafWidth + this.thumbPadding;
if (rightPos > maxRight) { maxRight = rightPos; }
leafIndex++;
if (i == seekIndex) {
- seekTop = bottomPos - this.padding - leafMap[currentRow].height;
+ seekTop = bottomPos - this.thumbPadding - leafMap[currentRow].height;
}
}
// Determine the thumbnails in view
for (i=0; i<leafMap.length; i++) {
- leafBottom += this.padding + leafMap[i].height;
+ leafBottom += this.thumbPadding + leafMap[i].height;
var topInView = (leafTop >= scrollTop) && (leafTop <= scrollBottom);
var bottomInView = (leafBottom >= scrollTop) && (leafBottom <= scrollBottom);
var middleInView = (leafTop <=scrollTop) && (leafBottom>=scrollBottom);
div.style.position = "absolute";
div.className = "BRpagedivthumb";
- left += this.padding;
+ left += this.thumbPadding;
$(div).css('top', leafTop + 'px');
$(div).css('left', left+'px');
$(div).css('width', leafWidth+'px');
// Note: #BRpageview must already exist since its width is used to calculate the
// thumbnail width
BookReader.prototype.getThumbnailWidth = function(thumbnailColumns) {
- var padding = (thumbnailColumns + 1) * this.padding;
+ var padding = (thumbnailColumns + 1) * this.thumbPadding;
var width = ($('#BRpageview').width() - padding) / (thumbnailColumns + 0.5); // extra 0.5 is for some space at sides
return parseInt(width);
}
for (i=0; i<(index+1); i++) {
leafWidth = this.thumbWidth;
- if (rightPos + (leafWidth + this.padding) > viewWidth){
+ if (rightPos + (leafWidth + this.thumbPadding) > viewWidth){
rightPos = 0;
rowHeight = 0;
leafIndex = 0;
leafHeight = parseInt((this.getPageHeight(leafIndex)*this.thumbWidth)/this.getPageWidth(leafIndex), 10);
if (leafHeight > rowHeight) { rowHeight = leafHeight; }
if (leafIndex==0) { leafTop = bottomPos; }
- if (leafIndex==0) { bottomPos += this.padding + rowHeight; }
- rightPos += leafWidth + this.padding;
+ if (leafIndex==0) { bottomPos += this.thumbPadding + rowHeight; }
+ rightPos += leafWidth + this.thumbPadding;
leafIndex++;
}
this.firstIndex=index;
// could be as simple as not calling this function
BookReader.prototype.initNavbar = function() {
// Setup nav / chapter / search results bar
- $('.chapter').bt({
- contentSelector: '$(this).find(".title")',
- trigger: 'hover',
+
+ // $$$ should make this work inside the BookReader div (self-contained), rather than after
+ $('#BookReader').after(
+ '<div id="BRnav">'
+ + '<div id="BRpage">'
+ + '<button class="BRicon book_left"></button>'
+ + '<button class="BRicon book_right"></button>'
+ + '</div>'
+ + '<div id="BRnavpos">'
+ + '<div id="BRfiller"></div>'
+ + '<div id="BRpager">'
+ + '<div id="BRslider">'
+ + '<div id="slider"></div>'
+ + '<div id="pager"></div>'
+ // XXXmang update code to update pagenum
+ + '<div id="pagenum"><span>n141</span> / 325</div>'
+ + '</div>'
+ + '</div>'
+ + '<div id="BRnavline">'
+ + '<div class="BRnavend" id="BRnavleft"></div>'
+ + '<div class="BRnavend" id="BRnavright"></div>'
+ + '</div>'
+ + '</div>'
+ + '</div>'
+ );
+
+/*
+ <div class="searchChap" style="left:49%;" title="Search result">
+ <div class="query">
+ A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 163</span>
+ <div class="queryChap">IV. The Witch <span>|</span> Page 163</div>
+ </div>
+ </div>
+*/
+
+ /* $$$mang search results and chapters should automatically coalesce
+ $('.searchChap').bt({
+ contentSelector: '$(this).find(".query")',
+ trigger: 'click',
closeWhenOthersOpen: true,
cssStyles: {
- backgroundColor: '#000',
- border: '2px solid #e2dcc5',
+ width: '250px',
+ padding: '10px 10px 15px',
+ backgroundColor: '#fff',
+ border: '3px solid #e2dcc5',
borderBottom: 'none',
- padding: '5px 10px',
- fontFamily: '"Arial", sans-serif',
- fontSize: '11px',
- fontWeight: '700',
- color: '#fff',
- whiteSpace: 'nowrap'
+ fontFamily: '"Lucida Grande","Arial",sans-serif',
+ fontSize: '12px',
+ lineHeight: '18px',
+ color: '#615132'
},
- shrinkToFit: true,
- width: '200px',
+ shrinkToFit: false,
+ width: '230px',
padding: 0,
spikeGirth: 0,
spikeLength: 0,
- overlap: '16px',
+ overlap: '10px',
overlay: false,
killTitle: true,
textzIndex: 9999,
wrapperzIndex: 9997,
offsetParent: null,
positions: ['top'],
- fill: 'black',
+ fill: 'white',
windowMargin: 10,
- strokeWidth: 0,
+ strokeWidth: 3,
+ strokeStyle: '#e2dcc5',
cornerRadius: 0,
centerPointX: 0,
centerPointY: 0,
shadow: false
});
- $('.search').bt({
+ $('.searchChap').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ */
+}
+
+BookReader.prototype.addSearchResult = function(queryString, pageNumber, pageIndex) {
+ var uiStringSearch = "Search result"; // i18n
+ var uiStringPage = "Page"; // i18n
+
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+
+ // $$$mang add click-through to page
+ $('<div class="search" style="left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">'
+ + queryString + '<span>' + uiStringPage + ' ' + pageNumber + '</span></div>')
+ .appendTo('#BRnavpos').bt({
contentSelector: '$(this).find(".query")',
trigger: 'click',
closeWhenOthersOpen: true,
centerPointX: 0,
centerPointY: 0,
shadow: false
- });
- $('.searchChap').bt({
- contentSelector: '$(this).find(".query")',
- trigger: 'click',
+ })
+ .hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ }
+ );
+}
+
+BookReader.prototype.removeSearchResults = function() {
+ $('#BRnavpos .search').remove();
+}
+
+BookReader.prototype.addChapter = function(chapterTitle, pageNumber, pageIndex) {
+ var uiStringPage = 'Page'; // i18n
+
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+
+ $('<div class="chapter" style="left:' + percentThrough + ';"><div class="title">'
+ + chapterTitle + '<span>|</span> ' + uiStringPage + ' ' + pageNumber + '</div></div>')
+ .appendTo('#BRnavpos')
+ .data({'self': this, 'pageIndex': pageIndex })
+ .bt({
+ contentSelector: '$(this).find(".title")',
+ trigger: 'hover',
closeWhenOthersOpen: true,
cssStyles: {
- width: '250px',
- padding: '10px 10px 15px',
- backgroundColor: '#fff',
- border: '3px solid #e2dcc5',
+ backgroundColor: '#000',
+ border: '2px solid #e2dcc5',
borderBottom: 'none',
- fontFamily: '"Lucida Grande","Arial",sans-serif',
- fontSize: '12px',
- lineHeight: '18px',
- color: '#615132'
+ padding: '5px 10px',
+ fontFamily: '"Arial", sans-serif',
+ fontSize: '11px',
+ fontWeight: '700',
+ color: '#fff',
+ whiteSpace: 'nowrap'
},
- shrinkToFit: false,
- width: '230px',
+ shrinkToFit: true,
+ width: '200px',
padding: 0,
spikeGirth: 0,
spikeLength: 0,
- overlap: '10px',
+ overlap: '16px',
overlay: false,
killTitle: true,
textzIndex: 9999,
wrapperzIndex: 9997,
offsetParent: null,
positions: ['top'],
- fill: 'white',
+ fill: 'black',
windowMargin: 10,
- strokeWidth: 3,
- strokeStyle: '#e2dcc5',
+ strokeWidth: 0,
cornerRadius: 0,
centerPointX: 0,
centerPointY: 0,
shadow: false
+ })
+ .hover( function() {
+ $(this).addClass('front');
+ }, function() {
+ $(this).removeClass('front');
+ }
+ )
+ .bind('click', function() {
+ $(this).data('self').jumpToIndex($(this).data('pageIndex'));
});
+}
+
+/*
+ * Remove all chapters.
+ */
+BookReader.prototype.removeChapters = function() {
+ $('#BRnavpos .chapter').remove();
+}
+
+/*
+ * Update the table of contents based on array of TOC entries.
+ */
+BookReader.prototype.updateTOC = function(tocEntries) {
+ this.removeChapters();
+ for (var i = 0; i < tocEntries.length; i++) {
+ this.addChapterFromEntry(tocEntries[i]);
+ }
+}
+
+/*
+ * Example table of contents entry - this format is defined by Open Library
+ * {
+ * "pagenum": "17",
+ * "level": 1,
+ * "label": "CHAPTER I",
+ * "type": {"key": "/type/toc_item"},
+ * "title": "THE COUNTRY AND THE MISSION"
+ * }
+ */
+BookReader.prototype.addChapterFromEntry = function(tocEntryObject) {
+ console.log(tocEntryObject);
+ var pageIndex = this.getPageIndex(tocEntryObject['pagenum']);
+ // Only add if we know where it is
+ if (pageIndex) {
+ this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex);
+ }
$('.chapter').each(function(){
$(this).hover(function(){
$(this).addClass('front');
});
});
$("#BRslider").draggable({axis:'x',containment:'parent'});
+ $("#BRzoombtn").draggable({axis:'y',containment:'parent'});
+ $("#BRslider").hover(
+ function(){
+ $("#pagenum").show();
+ },function(){
+ $("#pagenum").hide();
+ });
}
BookReader.prototype.initToolbar = function(mode, ui) {
+ // $$$mang should be contained within the BookReader div instead of body
$("body").append("<div id='BRtoolbar'>"
+ "<span id='BRtoolbarbuttons'>"
/* XXXmang integrate search */
+ "<form method='get' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search inside'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>"
+ // XXXmang icons incorrect or handlers wrong
+ "<button class='BRicon info' onclick='br.switchMode(3); return false;'></button>"
+ "<button class='BRicon share' onclick='br.switchMode(2); return false;'></button>"
+ "<button class='BRicon read modal'></button>"
+ "<span>"
+ "<a class='logo' href='" + this.logoURL + "'></a>"
+ // XXXmang update
+ "<div id='BRreturn'><span>Back to</span><a href='BOOK URL'>Book Title</a></div>"
+ "</span>"
+ + "</div>"
+ + "<div id='BRzoomer'>"
+ + "<div id='BRzoompos'>"
+ + "<button class='BRicon zoom_out'></button>"
+ + "<div id='BRzoomcontrol'>"
+ + "<div id='BRzoomstrip'></div>"
+ + "<div id='BRzoombtn'></div>"
+ + "</div>"
+ + "<button class='BRicon zoom_in'></button>"
+ + "</div>"
+ "</div>");
this.updateToolbarZoom(this.reduce); // Pretty format
// Handle mousemove related to navigation. Bind at #BookReader level to allow autohide.
BookReader.prototype.navigationMousemoveHandler = function(event) {
// $$$ possibly not great to be calling this for every mousemove
- var navkey = $(document).height() - 75;
- if ((event.pageY < 76) || (event.pageY > navkey)) {
- // inside or near navigation elements
- event.data['br'].hideNavigation();
- } else {
- event.data['br'].showNavigation();
+
+ if (event.data['br'].uiAutoHide) {
+ var navkey = $(document).height() - 75;
+ if ((event.pageY < 76) || (event.pageY > navkey)) {
+ // inside or near navigation elements
+ event.data['br'].hideNavigation();
+ } else {
+ event.data['br'].showNavigation();
+ }
}
}
// $$$ don't hardcode height
$('#BRtoolbar').animate({top:-60});
$('#BRnav').animate({bottom:-60});
+ $('#BRzoomer').animate({right:-26});
}
}
if (!this.navigationIsVisible()) {
$('#BRtoolbar').animate({top:0});
$('#BRnav').animate({bottom:0});
+ $('#BRzoomer').animate({right:0});
}
}
return this.getPageURI(index, reduce, rotate);
}
+/*
+ * Update based on received record from Open Library.
+ */
+BookReader.prototype.gotOpenLibraryRecord = function(self, olObject) {
+ // $$$ could refactor this so that 'this' is available
+ if (olObject) {
+ if (olObject['table_of_contents']) {
+ self.updateTOC(olObject['table_of_contents']);
+ }
+ }
+}
+
// Library functions
BookReader.util = {
disableSelect: function(jObject) {
return Math.min(Math.max(value, min), max);
},
+ // Given value and maximum, calculate a percentage suitable for CSS
+ cssPercentage: function(value, max) {
+ return parseInt(((value + 0.0) / max) * 100) + '%';
+ },
+
notInArray: function(value, array) {
// inArray returns -1 or undefined if value not in array
return ! (jQuery.inArray(value, array) >= 0);