Merge commit 'lance/newui' into mergelanceui
[bookreader.git] / BookReader / BookReader.js
index a735c55..4f9adbc 100644 (file)
@@ -36,16 +36,25 @@ This file is part of BookReader.
 // 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 = [];
@@ -81,10 +90,6 @@ function BookReader() {
     //     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
@@ -108,6 +113,17 @@ function BookReader() {
         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;
 };
 
@@ -166,9 +182,11 @@ BookReader.prototype.init = function() {
     
     // 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();
@@ -246,7 +264,10 @@ BookReader.prototype.init = function() {
     // 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() {
@@ -496,7 +517,7 @@ BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) {
     // 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;
@@ -516,13 +537,13 @@ BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) {
         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;
         }
     }
 
@@ -549,7 +570,7 @@ BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) {
     
     // 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);
@@ -607,7 +628,7 @@ BookReader.prototype.drawLeafsThumbnail = function( seekIndex ) {
                 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');
@@ -1089,7 +1110,7 @@ BookReader.prototype.zoomThumb = function(direction) {
 // 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);
 }
@@ -1202,7 +1223,7 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY) {
 
         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;
@@ -1211,8 +1232,8 @@ BookReader.prototype.jumpToIndex = function(index, pageX, pageY) {
             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;
@@ -3177,27 +3198,63 @@ BookReader.prototype.jumpIndexForRightEdgePageX = function(pageX) {
 //     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,
@@ -3205,15 +3262,35 @@ BookReader.prototype.initNavbar = function() {
         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,
@@ -3248,28 +3325,49 @@ BookReader.prototype.initNavbar = function() {
         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,
@@ -3277,15 +3375,59 @@ BookReader.prototype.initNavbar = function() {
         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');
@@ -3319,10 +3461,12 @@ BookReader.prototype.initNavbar = function() {
 
 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>"
@@ -3331,6 +3475,7 @@ BookReader.prototype.initToolbar = function(mode, ui) {
         
         + "<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>"
         
@@ -3569,12 +3714,15 @@ BookReader.prototype.unbindNavigationHandlers = function() {
 // 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();
+        }
     }
 }
 
@@ -4028,6 +4176,18 @@ BookReader.prototype._getPageURI = function(index, reduce, rotate) {
     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) {        
@@ -4046,6 +4206,11 @@ BookReader.util = {
         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);