diff --git a/css/main.css b/css/main.css index 63dc59f..100f160 100755 --- a/css/main.css +++ b/css/main.css @@ -46,6 +46,10 @@ body { font-weight: 600; } +#title-seperator { + display: none; +} + #area { width: 80%; height: 80%; diff --git a/fpjs/reader/app.js b/fpjs/reader/app.js index caa1acd..0c88ca8 100644 --- a/fpjs/reader/app.js +++ b/fpjs/reader/app.js @@ -23,17 +23,23 @@ FPR.app.init = (function($){ }else{ $("#main").width(windowWidth); } - + + + //-- Create a new book object, + // this will create an iframe in the el with the ID provided Book = new FP.Book("area"); + //-- Add listeners to handle book events + //-- Full list of event are at start of book.js Book.listen("book:metadataReady", meta); Book.listen("book:tocReady", toc); Book.listen("book:chapterReady", chapterChange); Book.listen("book:online", goOnline); Book.listen("book:offline", goOffline); - - Book.start(bookURL + "/"); + //-- Start loading / parsing of the book. + // This must be done AFTER adding listeners or hooks + Book.start(bookURL); //-- Wait for Dom ready to handle jquery $(function() { @@ -47,24 +53,44 @@ FPR.app.init = (function($){ var title = Book.getTitle(), author = Book.getCreator(), $title = $("#book-title"), - $author = $("#chapter-title"); + $author = $("#chapter-title"), + $dash = $("#title-seperator"); document.title = title+" – "+author; $title.html(title); $author.html(author); + $dash.show(); } function toc(){ var contents = Book.getTOC(), $toc = $("#toc"), + $links, $items; $toc.empty(); + + //-- Recursively generate TOC levels $items = generateTocItems(contents); $toc.append($items); + $links = $(".toc_link"); + + $links.on("click", function(e){ + var $this = $(this), + url = $this.data("url"); + + $(".openChapter").removeClass("openChapter"); + $this.parent().addClass("openChapter"); + + //-- Provide the Book with the url to show + // The Url must be found in the books manifest + Book.show(url); + + e.preventDefault(); + }); } @@ -75,22 +101,8 @@ FPR.app.init = (function($){ contents.forEach(function(item){ var $subitems, $wrapper = $("
  • "), - $item = $(""+item.label+""); - - $item.on("click", function(e){ - var $this = $(this), - url = $this.data("url"); - //spinepos = $this.data("spinepos"), - //section = $this.data("section") || false; - - $(".openChapter").removeClass("openChapter"); - $this.parent().addClass("openChapter"); - - Book.show(url); - - e.preventDefault(); - }); - + $item = $(""+item.label+""); + $wrapper.append($item); if(item.subitems && item.subitems.length){ @@ -150,15 +162,15 @@ FPR.app.init = (function($){ } }); - $next.on("click swipeleft", function(){ + $next.on("click", function(){ Book.nextPage(); }); - $prev.on("click swiperight", function(){ + $prev.on("click", function(){ Book.prevPage(); }); - + //-- TODO: This doesn't seem to work $window.bind("touchy-swipe", function(event, phase, $target, data){ if(data.direction = "left"){ @@ -221,8 +233,7 @@ FPR.app.init = (function($){ }); } }); - - + $network.on("click", function(){ offline = !offline; diff --git a/fpjs/render/book.js b/fpjs/render/book.js index a81eaf0..7ce5a37 100644 --- a/fpjs/render/book.js +++ b/fpjs/render/book.js @@ -9,6 +9,7 @@ FP.Book = function(elem, bookUrl){ this.events = new FP.Events(this, this.el); + //-- All Book events for listening this.createEvent("book:tocReady"); this.createEvent("book:metadataReady"); this.createEvent("book:spineReady"); @@ -20,6 +21,7 @@ FP.Book = function(elem, bookUrl){ this.createEvent("book:online"); this.createEvent("book:offline"); + //-- All hooks to add functions (with a callback) to this.hooks = { "beforeChapterDisplay" : [] }; @@ -53,6 +55,7 @@ FP.Book.prototype.initialize = function(el){ } +//-- Listeners for browser events FP.Book.prototype.listeners = function(){ var that = this; window.addEventListener("resize", that.onResized.bind(this), false); @@ -67,22 +70,20 @@ FP.Book.prototype.listeners = function(){ that.tell("book:online"); }, false); - //-- TODO: listener for offline } - +//-- Check bookUrl and start parsing book Assets or load them from storage FP.Book.prototype.start = function(bookUrl){ var pathname = window.location.pathname, folder = (pathname[pathname.length - 1] == "/") ? pathname : "/"; - this.bookUrl = bookUrl; + this.bookUrl = (bookUrl[bookUrl.length - 1] == "/") ? bookUrl : bookUrl + "/"; if(this.bookUrl.search("://") == -1){ //-- get full path this.bookUrl = window.location.origin + folder + this.bookUrl; } - //-- TODO: Check what storage types are available //-- TODO: Checks if the url is a zip file and unpack if(this.isContained(bookUrl)){ console.error("Zipped!"); @@ -93,10 +94,11 @@ FP.Book.prototype.start = function(bookUrl){ this.parseContainer(function(){ //-- Gets all setup of the book from xml file //-- TODO: add promise for this instead of callback? - this.parseContents(); + //this.parseContents(); }); }else{ + //-- Events for elements loaded from storage this.tell("book:tocReady"); this.tell("book:metadataReady"); this.tell("book:spineReady"); @@ -108,7 +110,7 @@ FP.Book.prototype.start = function(bookUrl){ } FP.Book.prototype.isSaved = function(force) { - + //-- If url or version has changed invalidate stored data and reset if (localStorage.getItem("bookUrl") != this.bookUrl || localStorage.getItem("fpjs-version") != FP.VERSION || force == true) { @@ -138,7 +140,8 @@ FP.Book.prototype.isSaved = function(force) { this.spine = JSON.parse(localStorage.getItem("spine")); this.spineIndexByURL = JSON.parse(localStorage.getItem("spineIndexByURL")); this.toc = JSON.parse(localStorage.getItem("toc")); - + + //-- Check that retrieved object aren't null if(!this.assets || !this.spine || !this.spineIndexByURL || !this.toc){ this.stored = 0; return false; @@ -184,7 +187,7 @@ FP.Book.prototype.resizeIframe = function(e, cWidth, cHeight){ this.iframe.height = height; if(width % 2 != 0){ - width += 1; + width += 1; //-- Prevent cutting off edges of text in columns } this.iframe.width = width; @@ -199,9 +202,6 @@ FP.Book.prototype.parseContainer = function(callback){ //-- rootfile = container.querySelector("rootfile"); - //-- Should only be one - //rootfile = rootfiles[0]; - fullpath = rootfile.getAttribute('full-path').split("/"); that.basePath = that.bookUrl + fullpath[0] + "/"; @@ -211,7 +211,7 @@ FP.Book.prototype.parseContainer = function(callback){ localStorage.setItem("contentsPath", that.contentsPath); //-- Now that we have the path we can parse the contents - //-- TODO: move this + //-- TODO: move this and handle errors that.parseContents(that.contentsPath); }); @@ -244,6 +244,8 @@ FP.Book.prototype.parseMetadata = function(metadata){ this.metadata["bookTitle"] = title ? title.childNodes[0].nodeValue : ""; this.metadata["creator"] = creator ? creator.childNodes[0].nodeValue : ""; + //-- TODO: add more meta data items, such as ISBN + localStorage.setItem("metadata", JSON.stringify(this.metadata)); this.tell("book:metadataReady"); @@ -259,7 +261,7 @@ FP.Book.prototype.parseManifest = function(manifest){ items.forEach(function(item){ var id = item.getAttribute('id'), href = item.getAttribute('href'); - that.assets[id] = that.basePath + href; + that.assets[id] = that.basePath + href; //-- Absolute URL for loading with a web worker //-- Find NCX: media-type="application/x-dtbncx+xml" href="toc.ncx" if(item.getAttribute('media-type') == "application/x-dtbncx+xml"){ @@ -328,11 +330,8 @@ FP.Book.prototype.parseTOC = function(path){ items.forEach(function(item){ var id = item.getAttribute('id'), content = item.querySelector("content"), - src = content.getAttribute('src'), //that.assets[id], + src = content.getAttribute('src'), split = src.split("#"), - //href = that.basePath + split[0], - //hash = split[1] || false, - //spinePos = parseInt(that.spineIndexByID[id] || that.spineIndexByURL[href]), navLabel = item.querySelector("navLabel"), text = navLabel.textContent ? navLabel.textContent : "", subitems = item.querySelectorAll("navPoint") || false, @@ -349,8 +348,6 @@ FP.Book.prototype.parseTOC = function(path){ "id": id, "href": src, "label": text, - //"spinePos": spinePos, - //"section" : hash || false, "subitems" : subs || false }); @@ -395,10 +392,10 @@ FP.Book.prototype.chapterTitle = function(){ FP.Book.prototype.startDisplay = function(){ - this.tell("book:bookReady"); this.displayChapter(this.spinePos, function(chapter){ + //-- If there is network connection, store the books contents if(this.online){ this.storeOffline(); } @@ -414,18 +411,22 @@ FP.Book.prototype.show = function(url){ section = split[1] || false, absoluteURL = (chapter.search("://") == -1) ? this.basePath + chapter : chapter, spinePos = this.spineIndexByURL[absoluteURL]; - + + //-- If link fragment only stay on current chapter if(!chapter){ spinePos = this.spinePos; } - + + //-- Check that URL is present in the index, or stop if(typeof(spinePos) != "number") return false; if(spinePos != this.spinePos){ + //-- Load new chapter if different than current this.displayChapter(spinePos, function(chap){ if(section) chap.section(section); }); }else{ + //-- Only goto section if(section) this.currentChapter.section(section); } } @@ -452,16 +453,12 @@ FP.Book.prototype.displayChapter = function(pos, callback){ this.currentChapter.afterLoaded = function(chapter) { - //-- TODO: Choose between single and spread - //that.formatSpread(); that.tell("book:chapterReady", chapter.getID()); if(callback){ callback(chapter); - } - //that.preloadNextChapter(); - + } } } @@ -501,16 +498,19 @@ FP.Book.prototype.getTOC = function() { } +/* TODO: Remove, replace by batch queue FP.Book.prototype.preloadNextChapter = function() { var next = this.spinePos + 1, path = this.spine[next].href; file = FP.storage.preload(path); } +*/ FP.Book.prototype.storeOffline = function(callback) { var assets = FP.core.toArray(this.assets); - + + //-- Creates a queue of all items to load FP.storage.batch(assets, function(){ this.stored = 1; localStorage.setItem("stored", 1); @@ -530,13 +530,14 @@ FP.Book.prototype.fromStorage = function(stored) { this.tell("book:online"); }else{ if(!this.availableOffline){ + //-- If book hasn't been cached yet, store offline this.storeOffline(function(){ this.online = false; this.tell("book:offline"); }.bind(this)); + }else{ this.online = false; - console.log("gone offline") this.tell("book:offline"); } } @@ -545,7 +546,7 @@ FP.Book.prototype.fromStorage = function(stored) { FP.Book.prototype.determineStorageMethod = function(override) { var method = 'ram'; - // options: none | ram | websql | indexedDB | filesystem + if(override){ method = override; }else{ @@ -557,24 +558,8 @@ FP.Book.prototype.determineStorageMethod = function(override) { FP.storage.storageMethod(method); } -FP.Book.prototype.triggerHooks = function(type, callback){ - var hooks, count; - - if(typeof(this.hooks[type]) == "undefined") return false; - - hooks = this.hooks[type]; - count = hooks.length; - - function countdown(){ - count--; - if(count <= 0 && callback) callback(); - }; - - hooks.forEach(function(hook){ - hook(countdown); - }); -} - +//-- Hooks allow for injecting async functions that must all complete before continuing +// Functions must have a callback as their first argument. FP.Book.prototype.registerHook = function(type, toAdd){ var that = this; @@ -590,6 +575,25 @@ FP.Book.prototype.registerHook = function(type, toAdd){ }else{ - this.hooks[type] = [func]; //or maybe this should error? + //-- Allows for undefined hooks, but maybe this should error? + this.hooks[type] = [func]; } } + +FP.Book.prototype.triggerHooks = function(type, callback){ + var hooks, count; + + if(typeof(this.hooks[type]) == "undefined") return false; + + hooks = this.hooks[type]; + count = hooks.length; + + function countdown(){ + count--; + if(count <= 0 && callback) callback(); + } + + hooks.forEach(function(hook){ + hook(countdown); + }); +} diff --git a/fpjs/render/chapter.js b/fpjs/render/chapter.js index 244e27f..a5d8371 100644 --- a/fpjs/render/chapter.js +++ b/fpjs/render/chapter.js @@ -39,19 +39,15 @@ FP.Chapter.prototype.loadFromStorage = function(path){ FP.Chapter.prototype.setIframeSrc = function(url){ var that = this; - //-- Not sure if this is the best time to do this, but hide current text - //if(this.bodyEl) this.bodyEl.style.visibility = "hidden"; + //-- Not sure if this is the best time to do this, but hides current text this.visible(false); this.iframe.src = url; this.iframe.onload = function() { - //that.bodyEl = that.iframe.contentDocument.documentElement.getElementsByTagName('body')[0]; - //that.bodyEl = that.iframe.contentDocument.querySelector('body, html'); that.doc = that.iframe.contentDocument; that.bodyEl = that.doc.body; - - //-- TODO: Choose between single and spread + that.formatSpread(); that.afterLoaded(that); @@ -62,7 +58,7 @@ FP.Chapter.prototype.setIframeSrc = function(url){ } FP.Chapter.prototype.afterLoaded = function(chapter){ - console.log("afterLoaded") + //-- This is overwritten by the book object } FP.Chapter.prototype.error = function(err){ @@ -77,7 +73,6 @@ FP.Chapter.prototype.formatSpread = function(){ this.OldcolWidth = this.colWidth; this.OldspreadWidth = this.spreadWidth; } - //this.bodyEl.setAttribute("style", "background: #777"); //-- Check the width and decied on columns //-- Todo: a better place for this? @@ -86,11 +81,14 @@ FP.Chapter.prototype.formatSpread = function(){ this.gap = this.gap || Math.ceil(this.elWidth / 8); if(this.elWidth < cutoff) { + this.spread = false; //-- Single Page + divisor = 1; this.colWidth = Math.floor(this.elWidth / divisor); }else{ - this.colWidth = Math.floor((this.elWidth - this.gap) / divisor); + this.spread = true; //-- Double Page + this.colWidth = Math.floor((this.elWidth - this.gap) / divisor); //-- Must be even for firefox if(this.colWidth % 2 != 0){ this.colWidth -= 1; @@ -109,25 +107,14 @@ FP.Chapter.prototype.formatSpread = function(){ //-- Adjust height this.bodyEl.style.height = this.book.el.clientHeight + "px"; - - + //-- Add columns this.bodyEl.style[FP.core.columnAxis] = "horizontal"; this.bodyEl.style[FP.core.columnGap] = this.gap+"px"; this.bodyEl.style[FP.core.columnWidth] = this.colWidth+"px"; this.calcPages(); - - - // if(!this.book.online){ - // //-- Temp place to parse Links - // this.replaceLinks(function(){ - // this.visible(true); - // }.bind(this)); - // }else{ - // this.visible(true); - // } - + //-- Trigger registered hooks before displaying this.beforeDisplay(function(){ this.book.tell("book:chapterDisplayed"); this.visible(true); @@ -160,12 +147,6 @@ FP.Chapter.prototype.calcPages = function(){ this.displayedPages = Math.ceil(this.totalWidth / this.spreadWidth); //console.log("Pages:", this.displayedPages) - //-- I work for Chrome - //this.iframe.contentDocument.body.scrollLeft = 200; - - //-- I work for Firefox - //this.iframe.contentDocument.documentElement.scrollLeft = 200; - } @@ -174,13 +155,11 @@ FP.Chapter.prototype.nextPage = function(){ this.chapterPos++; this.leftPos += this.spreadWidth; - //this.bodyEl.scrollLeft = this.leftPos; - //this.bodyEl.style.marginLeft = -this.leftPos + "px"; + this.setLeft(this.leftPos); return this.chapterPos; }else{ - //this.nextChapter(); return false; } } @@ -190,27 +169,24 @@ FP.Chapter.prototype.prevPage = function(){ this.chapterPos--; this.leftPos -= this.spreadWidth; - //this.bodyEl.scrollLeft = this.leftPos; - //this.bodyEl.style.marginLeft = -this.leftPos + "px"; + this.setLeft(this.leftPos); return this.chapterPos; }else{ - //this.prevChapter(); return false; } } FP.Chapter.prototype.chapterEnd = function(){ this.page(this.displayedPages); - //this.leftPos = this.spreadWidth * (this.displayedPages - 1);//this.totalWidth - this.colWidth; - //this.setLeft(this.leftPos); } FP.Chapter.prototype.setLeft = function(leftPos){ this.bodyEl.style.marginLeft = -leftPos + "px"; } +//-- Replaces the relative links within the book to use our internal page changer FP.Chapter.prototype.replaceLinks = function(callback){ var hrefs = this.doc.querySelectorAll('[href]'), links = Array.prototype.slice.call(hrefs), @@ -218,7 +194,10 @@ FP.Chapter.prototype.replaceLinks = function(callback){ links.forEach(function(link){ var path, - href = link.getAttribute("href"); + href = link.getAttribute("href"), + relative = href.search("://"); + + if(relative != -1) return; //-- Only replace relative links link.onclick = function(){ that.book.show(href); @@ -229,11 +208,13 @@ FP.Chapter.prototype.replaceLinks = function(callback){ if(callback) callback(); } +//-- Replaces assets src's to point to stored version if browser is offline FP.Chapter.prototype.replaceResources = function(callback){ var srcs, resources, count; - + + //-- No need to replace if there is network connectivity + //-- also Filesystem api links are relative, so no need to replace them if(this.book.online || FP.storage.getStorageType() == "filesystem") { - //-- filesystem api links are relative, so no need to fix if(callback) callback(); return false; } @@ -263,19 +244,21 @@ FP.Chapter.prototype.getID = function(){ FP.Chapter.prototype.page = function(pg){ if(pg >= 1 && pg <= this.displayedPages){ this.chapterPos = pg; - this.leftPos = this.spreadWidth * (pg-1); + this.leftPos = this.spreadWidth * (pg-1); //-- pages start at 1 this.setLeft(this.leftPos); return true; } + //-- Return false if page is greater than the total return false; } +//-- Find a section by fragement id FP.Chapter.prototype.section = function(fragment){ var el = this.doc.getElementById(fragment), - left = this.leftPos + el.offsetLeft, - pg = Math.floor(left / this.spreadWidth) + 1; + left = this.leftPos + el.offsetLeft, //-- Calculate left offset compaired to scrolled position + pg = Math.floor(left / this.spreadWidth) + 1; //-- pages start at 1 this.page(pg); } diff --git a/fpjs/render/storage.js b/fpjs/render/storage.js index d85f4be..cf684a4 100644 --- a/fpjs/render/storage.js +++ b/fpjs/render/storage.js @@ -53,201 +53,4 @@ FP.storage = function(){ "getStorageType" : getStorageType } -}(); - -/* -FP.storage.ram = function() { - var _store = {}, - _blobs = {}, - _queue = new FP.Queue("loader_ram.js", 3); - - - //-- TODO: this should be prototypes? - - //-- Used for preloading - function preload(path) { - var fromCache = check(path); - - if(!fromCache){ - request(path); - } - } - - function batch(group, callback){ - _queue.addGroup(group) - } - - //-- Fetches url - function get(path, callback) { - var fromCache = check(path), - url; - - if(fromCache){ - url = getURL(path, fromCache); - if(typeof(callback) != "undefined"){ - callback(url); - } - }else{ - request(path, function(file){ - url = getURL(path, file); - if(typeof(callback) != "undefined"){ - callback(url); - } - }); - } - - } - - function check(path) { - var file = _store[file]; - - if(typeof(file) != "undefined"){ - return file; - } - - return false; - } - - function request(path, callback) { - var xhr = new FP.core.loadFile(path); - - xhr.succeeded = function(file) { - //console.log("file", file) - cache(path, file); - if(typeof(callback) != "undefined"){ - callback(file); - } - } - - xhr.failed = _error; - - xhr.start(); - } - - function cache(path, file) { - if(_store[path]) return; - - _store[path] = file; - - } - - function getURL(path, file){ - var url; - - if(typeof(_blobs[path]) != "undefined"){ - return _blobs[path]; - } - - url = this._URL.createObjectURL(file); - - //-- need to revokeObjectURL previous urls, but only when cleaning cache - // this.createdURLs.forEach(function(url){ - // this._URL.revokeObjectURL(url); - // }); - - _blobs[path] = url; - - return url; - } - - // this.succeeded = function(){ - // console.log("loaded"); - // } - // - // this.failed = function(){ - // console.log("loaded"); - // } - - function _error(err){ - if(typeof(this.failed) == "undefined"){ - console.log("Error: ", err); - }else{ - this.failed(err); - } - } - - return { - "get" : get, - "preload" : preload - } -} -*/ - -FP.store = FP.store || {}; - -FP.store.none = function() { - var _store = {}; - - //-- Used for preloading - function preload(path) { - var fromCache = check(path); - - if(!fromCache){ - request(path); - } - } - - //-- need to add batch! - - //-- Fetches url - function get(path, callback) { - var fromCache = check(path), - url; - - if(fromCache){ - callback(path); - }else{ - request(path, function(file){ - callback(path); - }); - } - - } - - function check(path) { - var file = _store[path]; - - if(typeof(file) != "undefined"){ - return file; - } - - return false; - } - - function request(path, callback) { - var xhr = new FP.core.loadFile(path); - - xhr.succeeded = function(file) { - cache(path, file); - if(typeof(callback) != "undefined"){ - callback(file); - } - } - - xhr.failed = _error; - - xhr.start(); - } - - function cache(path, file) { - if(_store[path]) return; - - _store[path] = file; - - } - - - function _error(err){ - if(typeof(this.failed) == "undefined"){ - console.log("Error: ", err); - }else{ - this.failed(err); - } - } - - return { - "get" : get, - "preload" : preload - } -} - +}(); \ No newline at end of file diff --git a/fpjs/render/storage_none.js b/fpjs/render/storage_none.js new file mode 100644 index 0000000..9399d92 --- /dev/null +++ b/fpjs/render/storage_none.js @@ -0,0 +1,103 @@ +FP.store = FP.store || {}; + +FP.store.none = function() { + var _store = {}, + _blobs = {}, + _queue = new FP.Queue(loader, 6); + //-- max of 6 concurrent requests: http://www.browserscope.org/?category=network + + + function loader(msg, callback){ + var e = {"data":null}, + fromCache = check(msg); + + if(fromCache){ + e.data = fromCache; + callback(e); + }else{ + request(msg, function(url){ + e.data = url; + callback(e); + }); + } + } + + function preload(path) { + var fromCache = check(path); + + if(!fromCache){ + _queue.add(path); + } + } + + function batch(group, callback){ + _queue.addGroup(group, callback); + } + + //-- Fetches url + function get(path, callback) { + var fromCache = check(path), + url; + + if(fromCache){ + if(typeof(callback) != "undefined"){ + callback(fromCache); + } + }else{ + _queue.add(path, function(file){ + url = getURL(path, file); + if(typeof(callback) != "undefined"){ + callback(url); + } + }, true); + } + + } + + function check(path) { + var url = _store[path]; + + if(typeof(url) != "undefined"){ + return url; + } + + return false; + } + + function request(path, callback) { + var xhr = new FP.core.loadFile(path); + + xhr.succeeded = function(file) { + //console.log("file", file) + cache(path, file); + if(typeof(callback) != "undefined"){ + callback(file); + } + } + + xhr.failed = _error; + + xhr.start(); + } + + function cache(path, file) { + if(_store[path]) return; + + _store[path] = path; + } + + + function _error(err){ + if(typeof(this.failed) == "undefined"){ + console.log("Error: ", err); + }else{ + this.failed(err); + } + } + + return { + "get" : get, + "preload" : preload, + "batch" : batch + } +} \ No newline at end of file diff --git a/index.html b/index.html index f1747da..7ee2893 100755 --- a/index.html +++ b/index.html @@ -60,7 +60,7 @@
    -   –   +   –