Mercurial > hg > ismi-richfaces
diff src/main/webapp/imageServer/resources/js/diva.js @ 7:764f47286679
(none)
author | jurzua |
---|---|
date | Wed, 29 Oct 2014 14:28:34 +0000 |
parents | |
children | 719475ad0923 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/imageServer/resources/js/diva.js Wed Oct 29 14:28:34 2014 +0000 @@ -0,0 +1,2592 @@ +window.divaPlugins = []; + +// this pattern was taken from http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html +(function ($) +{ + var Diva = function (element, options) + { + // These are elements that can be overridden upon instantiation + // See https://github.com/DDMAL/diva.js/wiki/Code-documentation for more details + var defaults = { + adaptivePadding: 0.05, // The ratio of padding to the page dimension + blockMobileMove: true, // Prevent moving or scrolling the page on mobile devices + contained: false, // Determines the location of the fullscreen icon + objectData: '', // URL to the JSON file that provides the object dimension data - *MANDATORY* + enableAutoHeight: false, // Automatically adjust height based on the window size + enableAutoTitle: true, // Shows the title within a div of id diva-title + enableAutoWidth: true, // Automatically adjust width based on the window size + enableCanvas: true, // Used for the canvas plugin + enableDownload: true, // Used for the download plugin + enableFilename: true, // Uses filenames and not page numbers for links (i=bm_001.tif, not p=1) + enableFullscreen: true, // Enable or disable fullscreen icon (mode still available) + enableGotoPage: true, // A "go to page" jump box + enableGridIcon: true, // A grid view of all the pages + enableGridSlider: true, // Slider to control the pages per grid row + enableKeyScroll: true, // Scrolling using the page up/down keys + enableLinkIcon: true, // Controls the visibility of the link icon + enableSpaceScroll: false, // Scrolling down by pressing the space key + enableToolbar: true, // Enables the toolbar. Note that disabling this means you have to handle all controls yourself. + enableZoomSlider: true, // Enable or disable the zoom slider (for zooming in and out) + fixedPadding: 10, // Fallback if adaptive padding is set to 0 + fixedHeightGrid: true, // So each page in grid view has the same height (only widths differ) + goDirectlyTo: 0, // Default initial page to show (0-indexed) + iipServerURL: '', // The URL to the IIPImage installation, including the `?FIF=` - *MANDATORY* + inFullscreen: false, // Set to true to load fullscreen mode initially + inGrid: false, // Set to true to load grid view initially + imageDir: '', // Image directory, either absolute path or relative to IIP's FILESYSTEM_PREFIX - *MANDATORY* + maxPagesPerRow: 8, // Maximum number of pages per row, grid view + maxZoomLevel: -1, // Optional; defaults to the max zoom returned in the JSON response + minPagesPerRow: 2, // 2 for the spread view. Recommended to leave it + minZoomLevel: 0, // Defaults to 0 (the minimum zoom) + onDocumentLoaded: null, // Callback function for when the document is fully loaded + onModeToggle: null, // Callback for toggling fullscreen mode + onViewToggle: null, // Callback for switching between grid and document view + onJump: null, // Callback function for jumping to a specific page (using the gotoPage feature) + onPageLoad: null, // Callback function for loading pages + onPageLoaded: null, // Callback function for after the page has been loaded + onReady: null, // Callback function for initial load + onScroll: null, // Callback function for scrolling + onScrollDown: null, // Callback function for scrolling down, only + onScrollUp: null, // Callback function for scrolling up only + onSetCurrentPage: null, // Callback function for when the current page is set + onZoom: null, // Callback function for zooming in general + onZoomIn: null, // Callback function for zooming in only + onZoomOut: null, // Callback function for zooming out only + pageLoadTimeout: 200, // Number of milliseconds to wait before loading pages + pagesPerRow: 5, // The default number of pages per row in grid view + rowLoadTimeout: 50, // Number of milliseconds to wait before loading a row + throbberTimeout: 100, // Number of milliseconds to wait before showing throbber + tileHeight: 256, // The height of each tile, in pixels; usually 256 + tileWidth: 256, // The width of each tile, in pixels; usually 256 + toolbarParentSelector: null, // The toolbar parent selector. If null, it defaults to the primary diva element. Must be a jQuery selector (leading '#') + viewerHeightPadding: 15, // Vertical padding when resizing the viewer, if enableAutoHeight is set + viewerWidthPadding: 30, // Horizontal padding when resizing the viewer, if enableAutoHeight is set + viewportMargin: 200, // Pretend tiles +/- 200px away from viewport are in + zoomLevel: 2 // The initial zoom level (used to store the current zoom level) + }; + + // Apply the defaults, or override them with passed-in options. + var settings = $.extend({}, defaults, options); + + // Things that cannot be changed because of the way they are used by the script + // Many of these are declared with arbitrary values that are changed later on + var globals = { + allTilesLoaded: [], // A boolean for each page, indicating if all tiles have been loaded + averageHeights: [], // The average page height for each zoom level + averageWidths: [], // The average page width for each zoom level + currentPageIndex: 0, // The current page in the viewport (center-most page) + dimAfterZoom: 0, // Used for storing the item dimensions after zooming + firstPageLoaded: -1, // The ID of the first page loaded (value set later) + firstRowLoaded: -1, // The index of the first row loaded + gridPageWidth: 0, // Holds the max width of each row in grid view. Calculated in loadGrid() + hashParamSuffix: '', // Used when there are multiple document viewers on a page + heightAbovePages: [], // The height above each page at the current zoom level + horizontalOffset: 0, // Used in documentScroll for scrolling more precisely + horizontalPadding: 0, // Either the fixed padding or adaptive padding + ID: null, // The prefix of the IDs of the elements (usually 1-diva-) + innerSelector: '', // settings.selector + 'inner', for selecting the .diva-inner element + itemTitle: '', // The title of the document + lastPageLoaded: -1, // The ID of the last page loaded (value set later) + lastRowLoaded: -1, // The index of the last row loaded + leftScrollSoFar: 0, // Current scroll from the left edge of the pane + loaded: false, // A flag for when everything is loaded and ready to go. + maxWidths: [], // The width of the widest page for each zoom level + maxRatio: 0, // The max height/width ratio (for grid view) + minHeight: 0, // Minimum height of the .diva-outer element, as defined in the CSS + minRatio: 0, // The minimum height/width ratio for a page + minWidth: 0, // Minimum width of the .diva-outer element, as defined in the CSS + mobileWebkit: false, // Checks if the user is on a touch device (iPad/iPod/iPhone/Android) + numPages: 0, // Number of pages in the array + numRows: 0, // Number of rows + oldPagesPerRow: 0, // Holds the previous number of pages per row after it is changed + oldZoomLevel: -1, // Holds the previous zoom level after zooming in or out + orientationChange: false, // For handling device orientation changes for touch devices + originalHeight: 0, // Stores the original height of the .diva-outer element + originalWidth: 0, // Stores the original width of the .diva-outer element + outerSelector: '', // settings.selector + 'outer', for selecting the .diva-outer element + pages: [], // An array containing the data for all the pages + pageLeftOffsets: [], // Offset from the left side of the pane to the edge of the page + pageTimeouts: [], // Stack to hold the loadPage timeouts + pageTools: '', // The string for page tools + panelHeight: 0, // Height of the document viewer pane + panelWidth: 0, // Width of the document viewer pane + plugins: [], // Filled with the enabled plugins from window.divaPlugins + previousTopScroll: 0, // Used to determine vertical scroll direction + preZoomOffset: null, // Holds the offset prior to zooming when double-clicking + realMaxZoom: -1, // To hold the true max zoom level of the document (needed for calculations) + resizeTimer: -1, // Holds the ID of the timeout used when resizing the window (for clearing) + rowHeight: 0, // Holds the max height of each row in grid view. Calculated in loadGrid() + scaleWait: false, // For preventing double-zoom on touch devices (iPad, etc) + selector: '', // Uses the generated ID prefix to easily select elements + singleClick: false, // Used for catching ctrl+double-click events in Firefox in Mac OS + scrollbarWidth: 0, // Set to the actual scrollbar width in init() + throbberTimeoutID: -1, // Holds the ID of the throbber loading timeout + toolbar: null, // Holds an object with some toolbar-related functions + topScrollSoFar: 0, // Holds the number of pixels of vertical scroll + totalHeights: [], // The total height of all pages (stacked together) for each zoom level + totalHeight: 0, // The total height for the current zoom level (including padding) + verticalOffset: 0, // See horizontalOffset + verticalPadding: 0, // Either the fixed padding or adaptive padding + viewerXOffset: 0, // Distance between left edge of viewer and document left edge + viewerYOffset: 0 // Like viewerXOffset but for the top edges + }; + + $.extend(settings, globals); + + // Executes a callback function with the diva instance set as the context + // Can take an unlimited number to arguments to pass to the callback function + var self = this; + + var executeCallback = function (callback) + { + var args, i, length; + + if (typeof callback === "function") + { + args = []; + for (i = 1, length = arguments.length; i < length; i++) + { + args.push(arguments[i]); + } + + callback.apply(self, args); + + return true; + } + + return false; + }; + + var getPageData = function (pageIndex, attribute) + { + return settings.pages[pageIndex].d[settings.zoomLevel][attribute]; + }; + + // Returns the page index associated with the given filename; must called after settings settings.pages + var getPageIndex = function (filename) + { + var i, + np = settings.numPages; + + for (i = 0; i < np; i++) + { + if (settings.pages[i].f === filename) + { + return i; + } + } + + return -1; + }; + + // Checks if a tile is within the viewport horizontally + var isHorizontallyInViewport = function (left, right) + { + var panelWidth = settings.panelWidth; + var leftOfViewport = settings.leftScrollSoFar - settings.viewportMargin; + var rightOfViewport = leftOfViewport + panelWidth + settings.viewportMargin * 2; + + var leftVisible = left >= leftOfViewport && left <= rightOfViewport; + var rightVisible = right >= leftOfViewport && right <= rightOfViewport; + var middleVisible = left <= leftOfViewport && right >= rightOfViewport; + + return (leftVisible || middleVisible || rightVisible); + }; + + // Checks if a page or tile is within the viewport vertically + var isVerticallyInViewport = function (top, bottom) + { + var panelHeight = settings.panelHeight; + var topOfViewport = settings.topScrollSoFar - settings.viewportMargin; + var bottomOfViewport = topOfViewport + panelHeight + settings.viewportMargin * 2; + + var topVisible = top >= topOfViewport && top <= bottomOfViewport; + var middleVisible = top <= topOfViewport && bottom >= bottomOfViewport; + var bottomVisible = bottom >= topOfViewport && bottom <= bottomOfViewport; + + return (topVisible || middleVisible || bottomVisible); + }; + + // Check if a tile is near the viewport and thus should be loaded + var isTileVisible = function (pageIndex, tileRow, tileCol) + { + var tileTop = settings.heightAbovePages[pageIndex] + (tileRow * settings.tileHeight) + settings.verticalPadding; + var tileBottom = tileTop + settings.tileHeight; + var tileLeft = settings.pageLeftOffsets[pageIndex] + (tileCol * settings.tileWidth); + var tileRight = tileLeft + settings.tileWidth; + + return isVerticallyInViewport(tileTop, tileBottom) && isHorizontallyInViewport(tileLeft, tileRight); + }; + + // Check if a tile has been appended to the DOM + var isTileLoaded = function (pageIndex, tileIndex) + { + return document.getElementById(settings.ID + 'tile-' + pageIndex + '-' + tileIndex) === false; + }; + + // Check if a page index is valid + var isPageValid = function (pageIndex) + { + return pageIndex >= 0 && pageIndex < settings.numPages; + }; + + // Check if a page is in or near the viewport and thus should be loaded + var isPageVisible = function (pageIndex) + { + var topOfPage = settings.heightAbovePages[pageIndex]; + var bottomOfPage = topOfPage + getPageData(pageIndex, 'h') + settings.verticalPadding; + + return isVerticallyInViewport(topOfPage, bottomOfPage); + }; + + // Check if a page has been appended to the DOM + var isPageLoaded = function (pageIndex) + { + return $(document.getElementById(settings.ID + 'page-' + pageIndex)).length > 0; + }; + + // Appends the page directly into the document body, or loads the relevant tiles + var loadPage = function (pageIndex) + { + // If the page and all of its tiles have been loaded, exit + if (isPageLoaded(pageIndex) && settings.allTilesLoaded[pageIndex]) + { + return; + } + + // Load some data for this page + var filename = settings.pages[pageIndex].f; + var width = getPageData(pageIndex, 'w'); + var height = getPageData(pageIndex, 'h'); + var heightFromTop = settings.heightAbovePages[pageIndex] + settings.verticalPadding; + var pageSelector = settings.selector + 'page-' + pageIndex; + var plugin; + + // If the page has not been loaded yet, append the div to the DOM + if (!isPageLoaded(pageIndex)) + { + $(document.getElementById(settings.ID + "inner")).append('<div id="' + settings.ID + 'page-' + pageIndex + '" style="top: ' + heightFromTop + 'px; width: ' + width + 'px; height: ' + height + 'px;" class="diva-document-page" title="Page ' + (pageIndex + 1) + '" data-index="' + pageIndex + '" data-filename="' + filename + '">' + settings.pageTools + '</div>'); + + // Call the callback function + executeCallback(settings.onPageLoad, pageIndex, filename, pageSelector); + Events.publish("PageHasLoaded", [pageIndex, filename, pageSelector]); + + // @TODO: Replace this with a notification. + // Execute the callback functions for any of the enabled plugins + for (plugin in settings.plugins) { + executeCallback(settings.plugins[plugin].onPageLoad, pageIndex, filename, pageSelector); + } + } + + // There are still tiles to load, so try to load those (after a delay) + settings.pageTimeouts.push(setTimeout(function () + { + // If the page is no longer in the viewport, don't load any tiles + if (!isPageVisible(pageIndex)) + { + return; + } + + var imdir = settings.imageDir + "/"; + // Load some more data and initialise some variables + var rows = getPageData(pageIndex, 'r'); + var cols = getPageData(pageIndex, 'c'); + var maxZoom = settings.pages[pageIndex].m; + var baseURL = settings.iipServerURL + "?FIF=" + imdir + filename + '&JTL='; + var content = []; + var allTilesLoaded = true; + var tileIndex = 0; + var i; + + // Calculate the width and height of outer tiles (non-standard dimensions) + var lastHeight = height - (rows - 1) * settings.tileHeight; + var lastWidth = width - (cols - 1) * settings.tileWidth; + + // Declare variables used within the loops + var row, col, tileHeight, tileWidth, top, left, displayStyle, zoomLevel, imageURL; + + // Adjust the zoom level based on the max zoom level of the page + zoomLevel = settings.zoomLevel + maxZoom - settings.realMaxZoom; + baseImageURL = baseURL + zoomLevel + ','; + + // Loop through all the tiles in this page + row = 0; + while (row < rows) + { + col = 0; + while (col < cols) + { + top = row * settings.tileHeight; + left = col * settings.tileWidth; + + // If the tile is in the last row or column, its dimensions will be different + tileHeight = (row === rows - 1) ? lastHeight : settings.tileHeight; + tileWidth = (col === cols - 1) ? lastWidth : settings.tileWidth; + + imageURL = baseImageURL + tileIndex; + + // this check looks to see if the tile is already loaded, and then if + // it isn't, if it should be visible. + if (!isTileLoaded(pageIndex, tileIndex)) { + if (isTileVisible(pageIndex, row, col)) { + content.push('<div id="' + settings.ID + 'tile-' + pageIndex + '-' + tileIndex + '" style="display:inline; position: absolute; top: ' + top + 'px; left: ' + left + 'px; background-image: url(\'' + imageURL + '\'); height: ' + tileHeight + 'px; width: ' + tileWidth + 'px;"></div>'); + } else { + // The tile does not need to be loaded - not all have been loaded + allTilesLoaded = false; + } + } + tileIndex++; + col++; + } + row++; + } + + settings.allTilesLoaded[pageIndex] = allTilesLoaded; + $(document.getElementById(settings.ID + 'page-' + pageIndex)).append(content.join('')); + + executeCallback(settings.onPageLoaded, pageIndex, filename, pageSelector); + + }, settings.pageLoadTimeout)); + }; + + // Delete a page from the DOM; will occur when a page is scrolled out of the viewport + var deletePage = function (pageIndex) + { + $(document.getElementById(settings.ID + 'page-' + pageIndex)).empty().remove(); + }; + + // Check if the bottom of a page is above the top of a viewport (scrolling down) + // For when you want to keep looping but don't want to load a specific page + var pageAboveViewport = function (pageIndex) + { + var bottomOfPage = settings.heightAbovePages[pageIndex] + getPageData(pageIndex, 'h') + settings.verticalPadding; + var topOfViewport = settings.topScrollSoFar; + + return bottomOfPage < topOfViewport; + }; + + // Check if the top of a page is below the bottom of a viewport (scrolling up) + var pageBelowViewport = function (pageIndex) + { + var topOfPage = settings.heightAbovePages[pageIndex]; + var bottomOfViewport = settings.topScrollSoFar + settings.panelHeight; + + return topOfPage > bottomOfViewport; + }; + + // Called by adjust pages - determine what pages should be visible, and show them + var attemptPageShow = function (pageIndex, direction) + { + if (direction > 0) + { + // Direction is positive - we're scrolling down + if (isPageValid(pageIndex)) + { + // If the page should be visible, then yes, add it + if (isPageVisible(pageIndex)) + { + loadPage(pageIndex); + + settings.lastPageLoaded = pageIndex; + + // Recursively call this function until there's nothing to add + attemptPageShow(settings.lastPageLoaded + 1, direction); + } + else if (pageAboveViewport(pageIndex)) + { + // If the page is below the viewport. try to load the next one + attemptPageShow(pageIndex + 1, direction); + } + } + } + else + { + // Direction is negative - we're scrolling up + if (isPageValid(pageIndex)) + { + // If it's near the viewport, yes, add it + if (isPageVisible(pageIndex)) + { + loadPage(pageIndex); + + // Reset the first page loaded to this one + settings.firstPageLoaded = pageIndex; + + // Recursively call this function until there's nothing to add + attemptPageShow(settings.firstPageLoaded - 1, direction); + } + else if (pageBelowViewport(pageIndex)) + { + // Attempt to call this on the next page, do not increment anything + attemptPageShow(pageIndex - 1, direction); + } + } + } + }; + + // Called by adjustPages - see what pages need to be hidden, and hide them + var attemptPageHide = function (pageIndex, direction) + { + if (direction > 0) + { + // Scrolling down - see if this page needs to be deleted from the DOM + if (isPageValid(pageIndex) && pageAboveViewport(pageIndex)) + { + // Yes, delete it, reset the first page loaded + deletePage(pageIndex); + settings.firstPageLoaded = pageIndex + 1; + + // Try to call this function recursively until there's nothing to delete + attemptPageHide(settings.firstPageLoaded, direction); + } + } + else + { + // Direction must be negative (not 0 - see adjustPages), we're scrolling up + if (isPageValid(pageIndex) && pageBelowViewport(pageIndex)) + { + // Yes, delete it, reset the last page loaded + deletePage(pageIndex); + settings.lastPageLoaded = pageIndex - 1; + + // Try to call this function recursively until there's nothing to delete + attemptPageHide(settings.lastPageLoaded, direction); + } + } + }; + + // Handles showing and hiding pages when the user scrolls + var adjustPages = function (direction) + { + var i; + + // Direction is negative, so we're scrolling up + if (direction < 0) + { + attemptPageShow(settings.firstPageLoaded, direction); + setCurrentPage(-1); + attemptPageHide(settings.lastPageLoaded, direction); + } + else if (direction > 0) + { + // Direction is positive so we're scrolling down + attemptPageShow(settings.lastPageLoaded, direction); + setCurrentPage(1); + attemptPageHide(settings.firstPageLoaded, direction); + } + else + { + // Horizontal scroll, check if we need to reveal any tiles + var lpl = settings.lastPageLoaded; + for (i = Math.max(settings.firstPageLoaded, 0); i <= lpl; i++) + { + if (isPageVisible(i)) + { + loadPage(i); + } + } + } + + executeCallback(settings.onScroll, settings.topScrollSoFar); + + // If we're scrolling down + if (direction > 0) + { + executeCallback(settings.onScrollDown, settings.topScrollSoFar); + } + else if (direction < 0) + { + // We're scrolling up + executeCallback(settings.onScrollUp, settings.topScrollSoFar); + } + }; + + // Check if a row index is valid + var isRowValid = function (rowIndex) + { + return rowIndex >= 0 && rowIndex < settings.numRows; + }; + + // Check if a row should be visible in the viewport + var isRowVisible = function (rowIndex) + { + var topOfRow = settings.rowHeight * rowIndex; + var bottomOfRow = topOfRow + settings.rowHeight + settings.fixedPadding; + + return isVerticallyInViewport(topOfRow, bottomOfRow); + }; + + // Check if a row (in grid view) is present in the DOM + var isRowLoaded = function (rowIndex) + { + return $(settings.selector + 'row-' + rowIndex).length > 0; + }; + + var loadRow = function (rowIndex) + { + // If the row has already been loaded, don't attempt to load it again + if (isRowLoaded(rowIndex)) + { + return; + } + + // Load some data for this and initialise some variables + var heightFromTop = (settings.rowHeight * rowIndex) + settings.fixedPadding; + var content = []; + + // Create the opening tag for the row div + content.push('<div class="diva-row" id="' + settings.ID + 'row-' + rowIndex + '" style="height: ' + settings.rowHeight + '; top: ' + heightFromTop + 'px;">'); + + // Declare variables used in the loop + var i, pageIndex, filename, realWidth, realHeight, pageWidth, pageHeight, leftOffset, imageURL; + var imdir = settings.imageDir + "/"; + + // Load each page within that row + var ppr = settings.pagesPerRow; + for (i = 0; i < ppr; i++) + { + pageIndex = rowIndex * settings.pagesPerRow + i; + + // If this page is the last row, don't try to load a nonexistent page + if (!isPageValid(pageIndex)) + { + break; + } + + // Calculate the width, height and horizontal placement of this page + filename = settings.pages[pageIndex].f; + realWidth = getPageData(pageIndex, 'w'); + realHeight = getPageData(pageIndex, 'h'); + pageWidth = (settings.fixedHeightGrid) ? (settings.rowHeight - settings.fixedPadding) * realWidth / realHeight : settings.gridPageWidth; + pageHeight = (settings.fixedHeightGrid) ? settings.rowHeight - settings.fixedPadding : pageWidth / realWidth * realHeight; + leftOffset = parseInt(i * (settings.fixedPadding + settings.gridPageWidth) + settings.fixedPadding, 10); + + // Make sure they're all integers for nice, round numbers + pageWidth = parseInt(pageWidth, 10); + pageHeight = parseInt(pageHeight, 10); + + // Center the page if the height is fixed (otherwise, there is no horizontal padding) + leftOffset += (settings.fixedHeightGrid) ? (settings.gridPageWidth - pageWidth) / 2 : 0; + imageURL = settings.iipServerURL + "?FIF=" + imdir + filename + '&HEI=' + (pageHeight + 2) + '&CVT=JPEG'; + + // Append the HTML for this page to the string builder array + content.push('<div id="' + settings.ID + 'page-' + pageIndex + '" class="diva-page" style="width: ' + pageWidth + 'px; height: ' + pageHeight + 'px; left: ' + leftOffset + 'px;" title="Page ' + (pageIndex + 1) + '"></div>'); + + // Add each image to a queue so that images aren't loaded unnecessarily + addPageToQueue(rowIndex, pageIndex, imageURL, pageWidth, pageHeight); + } + + // Append this row to the DOM + content.push('</div>'); + $(document.getElementById(settings.ID + "inner")).append(content.join('')); + }; + + var deleteRow = function (rowIndex) + { + $(document.getElementById(settings.ID + 'row-' + rowIndex)).empty().remove(); + }; + + // Check if the bottom of a row is above the top of the viewport (scrolling down) + var rowAboveViewport = function (rowIndex) + { + var bottomOfRow = settings.rowHeight * (rowIndex + 1); + var topOfViewport = settings.topScrollSoFar; + + return (bottomOfRow < topOfViewport); + }; + + // Check if the top of a row is below the bottom of the viewport (scrolling up) + var rowBelowViewport = function (rowIndex) + { + var topOfRow = settings.rowHeight * rowIndex; + var bottomOfViewport = settings.topScrollSoFar + settings.panelHeight; + + return (topOfRow > bottomOfViewport); + }; + + // Same thing as attemptPageShow only with rows + var attemptRowShow = function (rowIndex, direction) + { + if (direction > 0) + { + if (isRowValid(rowIndex)) + { + if (isRowVisible(rowIndex)) + { + loadRow(rowIndex); + settings.lastRowLoaded = rowIndex; + + attemptRowShow(settings.lastRowLoaded + 1, direction); + } + else if (rowAboveViewport(rowIndex)) + { + attemptRowShow(rowIndex + 1, direction); + } + } + } + else + { + if (isRowValid(rowIndex)) + { + if (isRowVisible(rowIndex)) + { + loadRow(rowIndex); + settings.firstRowLoaded = rowIndex; + + attemptRowShow(settings.firstRowLoaded - 1, direction); + } + else if (rowBelowViewport(rowIndex)) + { + attemptRowShow(rowIndex - 1, direction); + } + } + } + }; + + var attemptRowHide = function (rowIndex, direction) + { + if (direction > 0) + { + if (isRowValid(rowIndex) && rowAboveViewport(rowIndex)) + { + deleteRow(rowIndex); + settings.firstRowLoaded++; + + attemptRowHide(settings.firstRowLoaded, direction); + } + } + else + { + if (isRowValid(rowIndex) && rowBelowViewport(rowIndex)) + { + deleteRow(rowIndex); + settings.lastRowLoaded--; + + attemptRowHide(settings.lastRowLoaded, direction); + } + } + }; + + var adjustRows = function (direction) + { + if (direction < 0) + { + attemptRowShow(settings.firstRowLoaded, -1); + setCurrentRow(-1); + attemptRowHide(settings.lastRowLoaded, -1); + } + else if (direction > 0) + { + attemptRowShow(settings.lastRowLoaded, 1); + setCurrentRow(1); + attemptRowHide(settings.firstRowLoaded, 1); + } + + executeCallback(settings.onScroll, settings.topScrollSoFar); + + // If we're scrolling down + if (direction > 0) + { + executeCallback(settings.onScrollDown, settings.topScrollSoFar); + } + else if (direction < 0) + { + // We're scrolling up + executeCallback(settings.onScrollUp, settings.topScrollSoFar); + } + }; + + // Used to delay loading of page images in grid view to prevent unnecessary loads + var addPageToQueue = function (rowIndex, pageIndex, imageURL, pageWidth, pageHeight) + { + settings.pageTimeouts.push(setTimeout(function () + { + if (isRowVisible(rowIndex)) + { + $(settings.selector + 'page-' + pageIndex).html('<img src="' + imageURL + '" style="width: ' + pageWidth + 'px; height: ' + pageHeight + 'px;" />'); + } + }, settings.rowLoadTimeout)); + }; + + // Determines and sets the "current page" (settings.currentPageIndex); called within adjustPages + // The "direction" is either 1 (downward scroll) or -1 (upward scroll) + var setCurrentPage = function (direction) + { + var middleOfViewport = settings.topScrollSoFar + (settings.panelHeight / 2); + var currentPage = settings.currentPageIndex; + var pageToConsider = settings.currentPageIndex + direction; + var changeCurrentPage = false; + var pageSelector = settings.selector + 'page-' + pageToConsider; + + // When scrolling up: + if (direction < 0) + { + // If the previous page > middle of viewport + if (pageToConsider >= 0 && (settings.heightAbovePages[pageToConsider] + getPageData(pageToConsider, 'h') + (settings.verticalPadding) >= middleOfViewport)) + { + changeCurrentPage = true; + } + } + else if (direction > 0) + { + // When scrolling down: + // If this page < middle of viewport + if (settings.heightAbovePages[currentPage] + getPageData(currentPage, 'h') + settings.verticalPadding < middleOfViewport) + { + changeCurrentPage = true; + } + } + + if (changeCurrentPage) + { + // Set this to the current page + settings.currentPageIndex = pageToConsider; + // Now try to change the next page, given that we're not going to a specific page + // Calls itself recursively - this way we accurately obtain the current page + if (direction !== 0) + { + if (!setCurrentPage(direction)) + { + var filename = settings.pages[pageToConsider].f; + executeCallback(settings.onSetCurrentPage, pageToConsider, filename); + Events.publish("VisiblePageDidChange", [pageToConsider, filename]); + } + } + return true; + } + + return false; + }; + + // Sets the current page in grid view + var setCurrentRow = function (direction) + { + var currentRow = Math.floor(settings.currentPageIndex / settings.pagesPerRow); + var rowToConsider = currentRow + parseInt(direction, 10); + var middleOfViewport = settings.topScrollSoFar + (settings.panelHeight / 2); + var changeCurrentRow = false; + + if (direction < 0) + { + if (rowToConsider >= 0 && (settings.rowHeight * currentRow >= middleOfViewport || settings.rowHeight * rowToConsider >= settings.topScrollSoFar)) + { + changeCurrentRow = true; + } + } + else if (direction > 0) + { + if ((settings.rowHeight * (currentRow + 1)) < settings.topScrollSoFar && isRowValid(rowToConsider)) + { + changeCurrentRow = true; + } + } + + if (changeCurrentRow) + { + settings.currentPageIndex = rowToConsider * settings.pagesPerRow; + + if (direction !== 0) + { + if (!setCurrentRow(direction)) + { + var pageIndex = settings.currentPageIndex; + var filename = settings.pages[pageIndex].f; + Events.publish("VisiblePageDidChange", [pageIndex, filename]); + } + } + + return true; + } + + return false; + }; + + // Helper function for going to a particular page + // Vertical offset: from the top of the page (including the top padding) + // Horizontal offset: from the center of the page; can be negative if to the left + var gotoPage = function (pageIndex, verticalOffset, horizontalOffset) + { + verticalOffset = (typeof verticalOffset !== 'undefined') ? verticalOffset : 0; + horizontalOffset = (typeof horizontalOffset !== 'undefined') ? horizontalOffset: 0; + var desiredTop = settings.heightAbovePages[pageIndex] + verticalOffset; + var desiredLeft = (settings.maxWidths[settings.zoomLevel] - settings.panelWidth) / 2 + settings.horizontalPadding + horizontalOffset; + + $(settings.outerSelector).scrollTop(desiredTop); + $(settings.outerSelector).scrollLeft(desiredLeft); + + // Pretend that this is the current page + settings.currentPageIndex = pageIndex; + //settings.toolbar.updateCurrentPage(); + var filename = settings.pages[pageIndex].f; + + Events.publish("VisiblePageDidChange", [pageIndex, filename]); + executeCallback(settings.onSetCurrentPage, pageIndex, filename); + + // Execute the onJump callback + executeCallback(settings.onJump, pageIndex); + }; + + // Calculates the desired row, then scrolls there + var gotoRow = function (pageIndex) + { + var desiredRow = Math.floor(pageIndex / settings.pagesPerRow); + var desiredTop = desiredRow * settings.rowHeight; + $(settings.outerSelector).scrollTop(desiredTop); + + // Pretend that this is the current page (it probably isn't) + settings.currentPageIndex = pageIndex; + var filename = settings.pages[pageIndex].f; + Events.publish("VisiblePageDidChange", [pageIndex, filename]); + }; + + // Helper function called by loadDocument to scroll to the desired place + var documentScroll = function () + { + // If settings.preZoomOffset is defined, the zoom was trigged by double-clicking + // We then zoom in on a specific region + if (settings.preZoomOffset) + { + var clickedPage = settings.preZoomOffset.i; + var heightAbovePage = settings.heightAbovePages[clickedPage] + settings.verticalPadding; + var pageLeftOffset = settings.pageLeftOffsets[clickedPage]; + var zoomRatio = Math.pow(2, settings.zoomLevel - settings.oldZoomLevel); + + var distanceFromViewport = { + x: settings.preZoomOffset.originalX - settings.viewerXOffset, + y: settings.preZoomOffset.originalY - settings.viewerYOffset + }; + + var newDistanceToEdge = { + x: settings.preZoomOffset.x * zoomRatio, + y: settings.preZoomOffset.y * zoomRatio + }; + + var newScroll = { + x: newDistanceToEdge.x - distanceFromViewport.x + pageLeftOffset, + y: newDistanceToEdge.y - distanceFromViewport.y + heightAbovePage + }; + + $(settings.outerSelector).scrollTop(newScroll.y).scrollLeft(newScroll.x); + + settings.preZoomOffset = undefined; + } + else + { + // Otherwise, we just scroll to the page saved in settings.goDirectlyTo (must be valid) + // Make sure the value for settings.goDirectlyTo is valid + if (!isPageValid(settings.goDirectlyTo)) + { + settings.goDirectlyTo = 0; + } + + // We use the stored y/x offsets (relative to the top of the page and the center, respectively) + gotoPage(settings.goDirectlyTo, settings.verticalOffset, settings.horizontalOffset); + settings.horizontalOffset = 0; + settings.verticalOffset = 0; + } + }; + + // Don't call this when not in grid mode please + // Scrolls to the relevant place when in grid view + var gridScroll = function () + { + // Figure out and scroll to the row containing the current page + gotoRow(settings.goDirectlyTo); + }; + + // If the given zoom level is valid, returns it; else, returns the min + var getValidZoomLevel = function (zoomLevel) + { + return (zoomLevel >= settings.minZoomLevel && zoomLevel <= settings.maxZoomLevel) ? zoomLevel : settings.minZoomLevel; + }; + + var getValidPagesPerRow = function (pagesPerRow) + { + return (pagesPerRow >= settings.minPagesPerRow && pagesPerRow <= settings.maxPagesPerRow) ? pagesPerRow : settings.maxPagesPerRow; + }; + + // Reset some settings and empty the viewport + var clearViewer = function () + { + settings.allTilesLoaded = []; + $(settings.outerSelector).scrollTop(0); + settings.topScrollSoFar = 0; + $(settings.innerSelector).empty(); + settings.firstPageLoaded = 0; + settings.firstRowLoaded = -1; + settings.previousTopScroll = 0; + + // Clear all the timeouts to prevent undesired pages from loading + clearTimeout(settings.resizeTimer); + + while (settings.pageTimeouts.length) + { + clearTimeout(settings.pageTimeouts.pop()); + } + }; + + // Called when we don't necessarily know which view to go into + var loadViewer = function () + { + if (settings.inGrid) + { + loadGrid(); + } + else + { + loadDocument(); + } + }; + + // Called every time we need to load document view (after zooming, fullscreen, etc) + var loadDocument = function () + { + clearViewer(); + + // Make sure the zoom level we've been given is valid + settings.zoomLevel = getValidZoomLevel(settings.zoomLevel); + var z = settings.zoomLevel; + + // Calculate the horizontal and vertical inter-page padding + if (settings.adaptivePadding > 0) + { + settings.horizontalPadding = settings.averageWidths[z] * settings.adaptivePadding; + settings.verticalPadding = settings.averageHeights[z] * settings.adaptivePadding; + } + else + { + // It's less than or equal to 0; use fixedPadding instead + settings.horizontalPadding = settings.fixedPadding; + settings.verticalPadding = settings.fixedPadding; + } + + // Make sure the vertical padding is at least 40, if plugin icons are enabled + if (settings.pageTools.length) + { + settings.verticalPadding = Math.max(40, settings.horizontalPadding); + } + + // Now reset some things that need to be changed after each zoom + settings.totalHeight = settings.totalHeights[z] + settings.verticalPadding * (settings.numPages + 1); + settings.dimAfterZoom = settings.totalHeight; + + // Determine the width of the inner element (based on the max width) + var maxWidthToSet = settings.maxWidths[z] + settings.horizontalPadding * 2; + var widthToSet = Math.max(maxWidthToSet, settings.panelWidth); + + // Needed to set settings.heightAbovePages - initially just the top padding + var heightSoFar = 0; + var i; + + for (i = 0; i < settings.numPages; i++) + { + // First set the height above that page by adding this height to the previous total + // A page includes the padding above it + settings.heightAbovePages[i] = heightSoFar; + + // Has to be done this way otherwise you get the height of the page included too + heightSoFar = settings.heightAbovePages[i] + getPageData(i, 'h') + settings.verticalPadding; + + // Figure out the pageLeftOffset stuff + settings.pageLeftOffsets[i] = (widthToSet - getPageData(i, 'w')) / 2; + + // Now try to load the page ONLY if the page needs to be loaded + // Take scrolling into account later, just try this for now + if (isPageVisible(i)) + { + loadPage(i); + settings.lastPageLoaded = i; + } + } + + // If this is not the initial load, execute the zoom callbacks + if (settings.oldZoomLevel >= 0) + { + if (settings.oldZoomLevel < settings.zoomLevel) + { + executeCallback(settings.onZoomIn, z); + } + else + { + executeCallback(settings.onZoomOut, z); + } + + executeCallback(settings.onZoom, z); + } + + // Set the height and width of documentpane (necessary for dragscrollable) + $(settings.innerSelector).height(Math.round(settings.totalHeight)); + $(settings.innerSelector).width(Math.round(widthToSet)); + + // Scroll to the proper place + documentScroll(); + + // For the iPad - wait until this request finishes before accepting others + if (settings.scaleWait) + { + settings.scaleWait = false; + } + + var fileName = settings.pages[settings.currentPageIndex].f; + executeCallback(settings.onDocumentLoaded, settings.lastPageLoaded, fileName); + Events.publish("DocumentHasFinishedLoading", [settings.lastPageLoaded, fileName]); + }; + + var loadGrid = function () + { + clearViewer(); + + // Make sure the pages per row setting is valid + settings.pagesPerRow = getValidPagesPerRow(settings.pagesPerRow); + + var horizontalPadding = settings.fixedPadding * (settings.pagesPerRow + 1); + var pageWidth = (settings.panelWidth - horizontalPadding) / settings.pagesPerRow; + settings.gridPageWidth = pageWidth; + + // Calculate the row height depending on whether we want to fix the width or the height + settings.rowHeight = (settings.fixedHeightGrid) ? settings.fixedPadding + settings.minRatio * pageWidth : settings.fixedPadding + settings.maxRatio * pageWidth; + settings.numRows = Math.ceil(settings.numPages / settings.pagesPerRow); + settings.totalHeight = settings.numRows * settings.rowHeight + settings.fixedPadding; + + $(settings.innerSelector).height(Math.round(settings.totalHeight)); + $(settings.innerSelector).width(Math.round(settings.panelWidth)); + + // First scroll directly to the row containing the current page + gridScroll(); + + var i, rowIndex; + + // Figure out the row each page is in + var np = settings.numPages; + for (i = 0; i < np; i += settings.pagesPerRow) + { + rowIndex = Math.floor(i / settings.pagesPerRow); + + if (isRowVisible(rowIndex)) + { + settings.firstRowLoaded = (settings.firstRowLoaded < 0) ? rowIndex : settings.firstRowLoaded; + loadRow(rowIndex); + settings.lastRowLoaded = rowIndex; + } + } + }; + + // Handles switching in and out of fullscreen mode + // Should only be called after changing settings.inFullscreen + var handleModeChange = function (changeView) + { + // Save some offsets (required for scrolling properly), if it's not the initial load + if (settings.oldZoomLevel >= 0) + { + if (!settings.inGrid) + { + var pageOffset = $(settings.selector + 'page-' + settings.currentPageIndex).offset(); + var topOffset = -(pageOffset.top - settings.verticalPadding - settings.viewerYOffset); + var expectedLeft = (settings.panelWidth - getPageData(settings.currentPageIndex, 'w')) / 2; + var leftOffset = -(pageOffset.left - settings.viewerXOffset - expectedLeft); + settings.verticalOffset = topOffset; + settings.horizontalOffset = leftOffset; + } + } + + // Change the look of the toolbar + Events.publish("ModeDidSwitch", null); + + // Toggle the classes + $(settings.selector + 'fullscreen').toggleClass('diva-in-fullscreen'); + $(settings.outerSelector).toggleClass('diva-fullscreen'); + $('body').toggleClass('diva-hide-scrollbar'); + $(settings.parentSelector).toggleClass('diva-full-width'); + + // Reset the panel dimensions + settings.panelHeight = $(settings.outerSelector).height(); + settings.panelWidth = $(settings.outerSelector).width() - settings.scrollbarWidth; + $(settings.innerSelector).width(settings.panelWidth); + + // Recalculate the viewer offsets + settings.viewerXOffset = $(settings.outerSelector).offset().left; + settings.viewerYOffset = $(settings.outerSelector).offset().top; + + // Used by setState when we need to change the view and the mode + if (changeView) + { + settings.inGrid = !settings.inGrid; + handleViewChange(); + } + else + { + loadViewer(); + } + + // Execute callbacks + executeCallback(settings.onModeToggle, settings.inFullscreen); + Events.publish("ModeHasChanged", [settings.inFullScreen]); + }; + + // Handles switching in and out of grid view + // Should only be called after changing settings.inGrid + var handleViewChange = function () + { + // Switch the slider + // Events.publish("ViewDidSwitch", null); + + loadViewer(); + executeCallback(settings.onViewToggle, settings.inGrid); + Events.publish("ViewDidSwitch", [settings.inGrid]); + }; + + // Called when the fullscreen icon is clicked + var toggleFullscreen = function () + { + settings.goDirectlyTo = settings.currentPageIndex; + settings.inFullscreen = !settings.inFullscreen; + handleModeChange(false); + }; + + // Called when the grid icon is clicked + var toggleGrid = function () + { + settings.goDirectlyTo = settings.currentPageIndex; + settings.inGrid = !settings.inGrid; + handleViewChange(); + }; + + // Called after double-click or ctrl+double-click events on pages in document view + var handleDocumentDoubleClick = function (event) + { + var pageOffset = $(this).offset(); + var offsetX = event.pageX - pageOffset.left; + var offsetY = event.pageY - pageOffset.top; + + // Store the offset information so that it can be used in documentScroll() + settings.preZoomOffset = { + x: offsetX, + y: offsetY, + originalX: event.pageX, + originalY: event.pageY, + i: $(this).attr('data-index') + }; + + // Hold control to zoom out, otherwise, zoom in + var newZoomLevel = (event.ctrlKey) ? settings.zoomLevel - 1 : settings.zoomLevel + 1; + + handleZoom(newZoomLevel); + }; + + // Called after double-clicking on a page in grid view + var handleGridDoubleClick = function (event) + { + // Figure out the page that was clicked, scroll to that page + var sel = document.getElementById(settings.ID + "outer"); + var centerX = (event.pageX - settings.viewerXOffset) + sel.scrollLeft; + var centerY = (event.pageY - settings.viewerYOffset) + sel.scrollTop; + var rowIndex = Math.floor(centerY / settings.rowHeight); + var colIndex = Math.floor(centerX / (settings.panelWidth / settings.pagesPerRow)); + var pageIndex = rowIndex * settings.pagesPerRow + colIndex; + settings.goDirectlyTo = pageIndex; + + // Leave grid view, jump directly to the desired page + settings.inGrid = false; + handleViewChange(); + }; + + // Handles pinch-zooming for mobile devices + var handlePinchZoom = function (event) + { + var newZoomLevel = settings.zoomLevel; + + // First figure out the new zoom level: + if (event.scale > 1 && newZoomLevel < settings.maxZoomLevel) + { + newZoomLevel++; + } + else if (event.scale < 1 && newZoomLevel > settings.minZoomLevel) + { + newZoomLevel--; + } + else + { + return; + } + + // Set it to true so we have to wait for this one to finish + settings.scaleWait = true; + + // Has to call handleZoomSlide so that the coordinates are kept + handleZoom(newZoomLevel); + }; + + // Called to handle any zoom level + var handleZoom = function (newValue) + { + var newZoomLevel = getValidZoomLevel(newValue); + + // If the zoom level provided is invalid, return false + if (newZoomLevel !== newValue) + { + return false; + } + + settings.oldZoomLevel = settings.zoomLevel; + settings.zoomLevel = newZoomLevel; + + // Update the slider + Events.publish("ZoomLevelDidChange", null); + + loadDocument(); + + return true; + }; + + // Called to handle changing the pages per row slider + var handleGrid = function (newValue) + { + var newPagesPerRow = getValidPagesPerRow(newValue); + + // If the value provided is invalid, return false + if (newPagesPerRow !== newValue) + { + return false; + } + + settings.oldPagesPerRow = settings.zoomLevel; + settings.pagesPerRow = newPagesPerRow; + + // Update the slider + Events.publish("GridRowNumberDidChange", null); + + loadGrid(); + }; + + var getYOffset = function () + { + var yScroll = document.getElementById(settings.ID + "outer").scrollTop; + var topOfPage = settings.heightAbovePages[settings.currentPageIndex]; + + return parseInt(yScroll - topOfPage, 10); + }; + + var getXOffset = function () + { + var innerWidth = settings.maxWidths[settings.zoomLevel] + settings.horizontalPadding * 2; + var centerX = (innerWidth - settings.panelWidth) / 2; + var xoff = document.getElementById(settings.ID + "outer").scrollLeft - centerX; + return parseInt(xoff, 10); + }; + + var getState = function () + { + var state = { + 'f': settings.inFullscreen, + 'g': settings.inGrid, + 'z': settings.zoomLevel, + 'n': settings.pagesPerRow, + 'i': (settings.enableFilename) ? settings.pages[settings.currentPageIndex].f : false, + 'p': (settings.enableFilename) ? false : settings.currentPageIndex + 1, + 'y': (settings.inGrid) ? false : getYOffset(), + 'x': (settings.inGrid) ? false : getXOffset(), + 'h': (settings.inFullscreen) ? false : settings.panelHeight, + 'w': (settings.inFullscreen) ? false : $(settings.outerSelector).width() + }; + + return state; + }; + + var getURLHash = function () + { + var hashParams = getState(); + var hashStringBuilder = []; + var param; + + for (param in hashParams) + { + if (hashParams[param] !== false) + { + hashStringBuilder.push(param + settings.hashParamSuffix + '=' + hashParams[param]); + } + } + + return hashStringBuilder.join('&'); + }; + + // Returns the URL to the current state of the document viewer (so it should be an exact replica) + var getCurrentURL = function () + { + return location.protocol + '//' + location.host + location.pathname + '#' + getURLHash(); + }; + + // Called in init and when the orientation changes + var adjustMobileWebkitDims = function () + { + var outerOffset = $(settings.outerSelector).offset().top; + settings.panelHeight = window.innerHeight - outerOffset - settings.viewerHeightPadding; + settings.panelWidth = window.innerWidth - settings.viewerWidthPadding; + + // $(settings.parentSelector).width(settings.panelWidth); + // document.getElementById(settings.parentSelector.substring(1)).style.width = settings.panelWidth + "px"; + settings.parentSelector.style.width = settings.panelWidth + "px"; + + if (settings.enableAutoHeight) + { + document.getElementById(settings.ID + "outer").style.height = settings.panelHeight + "px"; + } + + if (settings.enableAutoWidth) + { + document.getElementById(settings.ID + "outer").style.width = settings.panelWidth + "px"; + } + }; + + // Will return true if something has changed, false otherwise + var adjustBrowserDims = function () + { + // Only resize if the browser viewport is too small + var newHeight = $(settings.outerSelector).height(); + var newWidth = $(settings.parentSelector).width() - settings.scrollbarWidth; + var outerOffset = $(settings.outerSelector).offset().top; + + var windowHeight = window.innerHeight || document.documentElement.clientHeight; + var windowWidth = window.innerWidth || document.documentElement.clientWidth; + // 2 or 1 pixels for the border + var desiredWidth = windowWidth - settings.viewerWidthPadding - settings.scrollbarWidth - 2; + var desiredHeight = windowHeight - outerOffset - settings.viewerHeightPadding - 1; + + if (settings.enableAutoHeight) + { + if (newHeight + outerOffset + 16 > window.innerHeight) + { + newHeight = desiredHeight; + } + else if (newHeight <= settings.originalHeight) + { + newHeight = Math.min(desiredHeight, settings.originalHeight); + } + } + + if (settings.enableAutoWidth) + { + if (newWidth + 32 > window.innerWidth) + { + newWidth = desiredWidth; + } + else if (newWidth <= settings.originalWidth) + { + newWidth = Math.min(desiredWidth, settings.originalWidth); + } + + settings.parentSelector[0].style.width = newWidth + settings.scrollbarWidth; + } + + if (newWidth !== settings.panelWidth || newHeight !== settings.panelHeight) + { + var el = document.getElementById(settings.ID + "outer"); + el.style.height = newHeight + "px"; + el.style.width = newWidth + settings.scrollbarWidth + "px"; + settings.panelWidth = newWidth; + settings.panelHeight = newHeight; + return true; + } + + return false; + }; + + // Update the panelHeight and panelWidth based on the window size + var adjustFullscreenDims = function () + { + settings.panelWidth = window.innerWidth - settings.scrollbarWidth; + settings.panelHeight = window.innerHeight; + + return true; + }; + + var resizeViewer = function (newWidth, newHeight) + { + if (newWidth >= settings.minWidth) + { + settings.originalWidth = newWidth; + $(settings.outerSelector).width(newWidth); + document.getElementById(settings.ID + "outer").style.width = newWidth + "px"; + + settings.panelWidth = newWidth - settings.scrollbarWidth; + + // Should also change the width of the container + settings.parentSelector[0].style.width = newWidth + "px"; + } + + if (newHeight >= settings.minHeight) + { + settings.originalHeight = newHeight; + document.getElementById(settings.ID + "outer").style.height = newHeight + "px"; + + settings.panelHeight = newHeight; + } + }; + + // Binds most of the event handlers (some more in createToolbar) + var handleEvents = function () + { + // Create the fullscreen toggle icon if fullscreen is enabled + if (settings.enableFullscreen) + { + // Event handler for fullscreen toggling + $(settings.selector + 'fullscreen').click(function () + { + toggleFullscreen(); + }); + } + + // Change the cursor for dragging + $(settings.innerSelector).mouseover(function () + { + $(this).removeClass('diva-grabbing').addClass('diva-grab'); + }); + + $(settings.innerSelector).mouseout(function () + { + $(this).removeClass('diva-grab'); + }); + + $(settings.innerSelector).mousedown(function () + { + $(this).removeClass('diva-grab').addClass('diva-grabbing'); + }); + + $(settings.innerSelector).mouseup(function () + { + $(this).removeClass('diva-grabbing').addClass('diva-grab'); + }); + + // Set drag scroll on first descendant of class dragger on both selected elements + $(settings.outerSelector + ', ' + settings.innerSelector).dragscrollable({dragSelector: '.diva-dragger', acceptPropagatedEvent: true}); + + // Handle the scroll + $(settings.outerSelector).scroll(function () + { + settings.topScrollSoFar = document.getElementById(settings.ID + "outer").scrollTop; + var direction = settings.topScrollSoFar - settings.previousTopScroll; + + if (settings.inGrid) + { + adjustRows(direction); + } + else + { + adjustPages(direction); + settings.leftScrollSoFar = $(this).scrollLeft(); + } + + settings.previousTopScroll = settings.topScrollSoFar; + }); + + // Double-click to zoom + $(settings.outerSelector).on('dblclick', '.diva-document-page', function (event) + { + handleDocumentDoubleClick.call(this, event); + }); + + // Handle the control key for macs (in conjunction with double-clicking) + $(settings.outerSelector).on('contextmenu', '.diva-document-page', function (event) + { + if (event.ctrlKey) + { + // In Firefox, this doesn't trigger a double-click, so we apply one manually + clearTimeout(settings.singleClickTimeout); + + if (settings.singleClick) + { + handleDocumentDoubleClick.call(this, event); + settings.singleClick = false; + } + else + { + settings.singleClick = true; + + // Set it to false again after 500 milliseconds (standard double-click timeout) + settings.singleClickTimeout = setTimeout(function () + { + settings.singleClick = false; + }, 500); + } + + return false; + } + }); + + $(settings.outerSelector).on('dblclick', '.diva-row', function (event) + { + handleGridDoubleClick.call(this, event); + }); + + // Check if the user is on a iPhone or iPod touch or iPad + if (settings.mobileWebkit) + { + // Prevent resizing (below from http://matt.might.net/articles/how-to-native-iphone-ipad-apps-in-javascript/) + var toAppend = []; + toAppend.push('<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1" />'); + + // Eliminate URL and button bars if added to home screen + toAppend.push('<meta name="apple-mobile-web-app-capable" content="yes" />'); + + // Choose how to handle the phone status bar + toAppend.push('<meta name="apple-mobile-web-app-status-bar-style" content="black" />'); + $('head').append(toAppend.join('\n')); + + // Block the user from moving the window only if it's not integrated + if (settings.blockMobileMove) + { + $('body').bind('touchmove', function (event) + { + var e = event.originalEvent; + e.preventDefault(); + + return false; + }); + } + + // Allow pinch-zooming + $('body').bind('gestureend', function (event) + { + var e = event.originalEvent; + + if (!settings.scaleWait) + { + // Save the page we're currently on so we scroll there + settings.goDirectlyTo = settings.currentPageIndex; + + if (settings.inGrid) + { + settings.inGrid = false; + + handleViewChange(); + } + else + { + handlePinchZoom(e); + } + } + return false; + }); + + // Listen to orientation change event + $(window).bind('orientationchange', function (event) + { + settings.orientationChange = true; + adjustMobileWebkitDims(); + + // Reload the viewer to account for the resized viewport + settings.goDirectlyTo = settings.currentPageIndex; + loadViewer(); + }); + + // Inertial scrolling + $(settings.outerSelector).kinetic(); + } + + // Only check if either scrollBySpace or scrollByKeys is enabled + if (settings.enableSpaceScroll || settings.enableKeyScroll) + { + var spaceKey = $.ui.keyCode.SPACE; + var pageUpKey = $.ui.keyCode.PAGE_UP; + var pageDownKey = $.ui.keyCode.PAGE_DOWN; + var homeKey = $.ui.keyCode.HOME; + var endKey = $.ui.keyCode.END; + + // Catch the key presses in document + $(document).keydown(function (event) + { + // Space or page down - go to the next page + if ((settings.enableSpaceScroll && event.keyCode === spaceKey) || (settings.enableKeyScroll && event.keyCode === pageDownKey)) + { + $(settings.outerSelector).scrollTop(settings.topScrollSoFar + settings.panelHeight); + return false; + } + + // Page up - go to the previous page + if (settings.enableKeyScroll && event.keyCode === pageUpKey) + { + $(settings.outerSelector).scrollTop(settings.topScrollSoFar - settings.panelHeight); + return false; + } + + // Home key - go to the beginning of the document + if (settings.enableKeyScroll && event.keyCode === homeKey) + { + $(settings.outerSelector).scrollTop(0); + return false; + } + + // End key - go to the end of the document + if (settings.enableKeyScroll && event.keyCode === endKey) + { + $(settings.outerSelector).scrollTop(settings.totalHeight); + return false; + } + }); + + // Handle window resizing events + if (!settings.mobileWebkit) + { + $(window).resize(function () + { + var adjustSuccess = (settings.inFullscreen) ? adjustFullscreenDims() : adjustBrowserDims(); + + if (adjustSuccess) + { + // Cancel any previously-set resize timeouts + clearTimeout(settings.resizeTimer); + + settings.resizeTimer = setTimeout(function () + { + settings.goDirectlyTo = settings.currentPageIndex; + loadViewer(); + }, 200); + } + }); + } + } + }; + + // Handles all status updating etc (both fullscreen and not) + var createToolbar = function () { + // Prepare the HTML for the various components + var gridIconHTML = (settings.enableGridIcon) ? '<div class="diva-grid-icon' + (settings.inGrid ? ' diva-in-grid' : '') + '" id="' + settings.ID + 'grid-icon" title="Toggle grid view"></div>' : ''; + var linkIconHTML = (settings.enableLinkIcon) ? '<div class="diva-link-icon" id="' + settings.ID + 'link-icon" style="' + (settings.enableGridIcon ? 'border-left: 0px' : '') + '" title="Link to this page"></div>' : ''; + var zoomSliderHTML = (settings.enableZoomSlider) ? '<div id="' + settings.ID + 'zoom-slider"></div>' : ''; + var gridSliderHTML = (settings.enableGridSlider) ? '<div id="' + settings.ID + 'grid-slider"></div>' : ''; + var gotoPageHTML = (settings.enableGotoPage) ? '<form id="' + settings.ID + 'goto-page" class="diva-goto-form"><input type="text" id="' + settings.ID + 'goto-page-input" / class="diva-input"> <input type="submit" value="Go" style="margin-top: 0px;" /></form>' : ''; + var zoomSliderLabelHTML = (settings.enableZoomSlider) ? '<div id="' + settings.ID + 'zoom-slider-label" class="diva-slider-label">Zoom level: <span id="' + settings.ID + 'zoom-level">' + settings.zoomLevel + '</span></div>' : ''; + var gridSliderLabelHTML = (settings.enableGridSlider) ? '<div id="' + settings.ID + 'grid-slider-label" class="diva-slider-label">Pages per row: <span id="' + settings.ID + 'pages-per-row">' + settings.pagesPerRow + '</span></div>' : ''; + var pageNumberHTML = '<div class="diva-page-label">Page <span id="' + settings.ID + 'current-page">1</span> of <span id="' + settings.ID + 'num-pages">' + settings.numPages + '</span></div>'; + + // If the viewer is specified to be "contained", we make room for the fullscreen icon + var otherToolbarClass = ''; + + if (settings.contained) + { + // Make sure the container element does not have a static position + // (Needed for the fullscreen icon to be contained) + if ($(settings.parentSelector).css('position') === 'static') + { + $(settings.parentSelector).addClass('diva-relative-position'); + } + + otherToolbarClass = ' diva-fullscreen-space'; + + // If enableAutoTitle is set to TRUE, move it down + if (settings.enableAutoTitle) + { + $(settings.selector + 'fullscreen').addClass('diva-contained'); + } + } + + var toolbarHTML = '<div id="' + settings.ID + 'tools-left" class="diva-tools-left' + otherToolbarClass + '">' + zoomSliderHTML + gridSliderHTML + zoomSliderLabelHTML + gridSliderLabelHTML + '</div><div id="' + settings.ID + 'tools-right" class="diva-tools-right">' + linkIconHTML + gridIconHTML + '<div class="diva-page-nav">' + gotoPageHTML + pageNumberHTML + '</div></div>'; + + if (settings.toolbarParentSelector) + { + $(settings.toolbarParentSelector).prepend('<div id="' + settings.ID + 'tools" class="diva-tools">' + toolbarHTML + '</div>'); + } + else + { + $(settings.parentSelector).prepend('<div id="' + settings.ID + 'tools" class="diva-tools">' + toolbarHTML + '</div>'); + } + + // Create the zoom slider + $(settings.selector + 'zoom-slider').slider({ + value: settings.zoomLevel, + min: settings.minZoomLevel, + max: settings.maxZoomLevel, + step: 1, + slide: function (event, ui) + { + var i = settings.currentPageIndex; + settings.goDirectlyTo = i; + + // Figure out the horizontal and vertical offsets + // (Try to zoom in on the current center) + var zoomRatio = Math.pow(2, ui.value - settings.zoomLevel); + var innerWidth = settings.maxWidths[settings.zoomLevel] + settings.horizontalPadding * 2; + var centerX = $(settings.outerSelector).scrollLeft() - (innerWidth - settings.panelWidth) / 2; + settings.horizontalOffset = (innerWidth > settings.panelWidth) ? centerX * zoomRatio : 0; + settings.verticalOffset = zoomRatio * ($(settings.outerSelector).scrollTop() - settings.heightAbovePages[i]); + + handleZoom(ui.value); + }, + change: function (event, ui) + { + if (ui.value !== settings.zoomLevel) + { + handleZoom(ui.value); + } + } + }); + + // Create the grid slider + $(settings.selector + 'grid-slider').slider( + { + value: settings.pagesPerRow, + min: settings.minPagesPerRow, + max: settings.maxPagesPerRow, + step: 1, + slide: function (event, ui) + { + handleGrid(ui.value); + }, + change: function (event, ui) + { + if (ui.value !== settings.pagesPerRow) + { + handleGrid(ui.value); + } + } + }); + + // Handle clicking of the grid icon + $(settings.selector + 'grid-icon').click(function () + { + toggleGrid(); + }); + + // Handle going to a specific page using the input box + $(settings.selector + 'goto-page').submit(function () + { + var desiredPage = parseInt($(settings.selector + 'goto-page-input').val(), 10); + var pageIndex = desiredPage - 1; + + if (!isPageValid(pageIndex)) + { + alert("Invalid page number"); + } + else + { + if (settings.inGrid) + { + gotoRow(pageIndex); + } + else + { + gotoPage(pageIndex, 0, 0); + } + } + + // Prevent the default action of reloading the page + return false; + }); + + // Handle the creation of the link popup box + $(settings.selector + 'link-icon').click(function () + { + $('body').prepend('<div id="' + settings.ID + 'link-popup" class="diva-link-popup"><input id="' + settings.ID + 'link-popup-input" class="diva-input" type="text" value="' + getCurrentURL() + '"/></div>'); + + if (settings.inFullscreen) + { + $(settings.selector + 'link-popup').addClass('in-fullscreen'); + } + else + { + // Calculate the left and top offsets + // Compensate for border, popup width + var leftOffset = $(settings.outerSelector).offset().left + settings.panelWidth; + leftOffset += settings.scrollbarWidth - 240 - 1; + var topOffset = $(settings.outerSelector).offset().top + 1; + + $(settings.selector + 'link-popup').removeClass('in-fullscreen').css( + { + 'top': topOffset + 'px', + 'left': leftOffset + 'px' + }); + } + + // Catch onmouseup events outside of this div + $('body').mouseup(function (event) + { + var targetID = event.target.id; + + if (targetID !== settings.ID + 'link-popup' && targetID !== settings.ID + 'link-popup-input') + { + $(settings.selector + 'link-popup').remove(); + } + }); + + // Also delete it upon scroll and page up/down key events + $(settings.outerSelector).scroll(function () + { + $(settings.selector + 'link-popup').remove(); + }); + $(settings.selector + 'link-popup input').click(function () + { + $(this).focus().select(); + }); + return false; + }); + + // Show the relevant slider + var currentSlider = (settings.inGrid) ? 'grid' : 'zoom'; + $(settings.selector + currentSlider + '-slider').show(); + $(settings.selector + currentSlider + '-slider-label').show(); + + var switchMode = function () + { + // Switch from fullscreen to not + $(settings.selector + 'tools').toggleClass('diva-fullscreen-tools'); + + if (!settings.inFullscreen) + { + // Leaving fullscreen + $(settings.selector + 'tools-left').after($(settings.selector + 'tools-right')); + $(settings.selector + 'tools-left').removeClass('in-fullscreen'); + } + else + { + // Entering fullscreen + $(settings.selector + 'tools-right').after($(settings.selector + 'tools-left')); + $(settings.selector + 'tools-left').addClass('in-fullscreen'); + } + }; + + var switchView = function () + { + // Switch from grid to document view etc + $(settings.selector + currentSlider + '-slider').hide(); + $(settings.selector + currentSlider + '-slider-label').hide(); + currentSlider = (settings.inGrid) ? 'grid' : 'zoom'; + $(settings.selector + currentSlider + '-slider').show(); + $(settings.selector + currentSlider + '-slider-label').show(); + + // Also change the image for the grid icon + $(settings.selector + 'grid-icon').toggleClass('diva-in-grid'); + }; + + var toolbar = + { + updateCurrentPage: function () + { + $(settings.selector + 'current-page').text(settings.currentPageIndex + 1); + }, + setNumPages: function (newNumber) + { + $(settings.selector + 'num-pages').text(newNumber); + }, + updateZoomSlider: function () + { + // Update the position of the handle within the slider + if (settings.zoomLevel !== $(settings.selector + 'zoom-slider').slider('value')) + { + $(settings.selector + 'zoom-slider').slider( + { + value: settings.zoomLevel + }); + } + + // Update the slider label + $(settings.selector + 'zoom-level').text(settings.zoomLevel); + }, + updateGridSlider: function () + { + // Update the position of the handle within the slider + if (settings.pagesPerRow !== $(settings.selector + 'grid-slider').slider('value')) + { + $(settings.selector + 'grid-slider').slider( + { + value: settings.pagesPerRow + }); + } + + // Update the slider label + $(settings.selector + 'pages-per-row').text(settings.pagesPerRow); + }, + switchView: switchView, + switchMode: switchMode + }; + return toolbar; + }; + + var initPlugins = function () + { + if (window.divaPlugins) + { + var pageTools = []; + + // Add all the plugins that have not been explicitly disabled to settings.plugins + $.each(window.divaPlugins, function (index, plugin) + { + var pluginProperName = plugin.pluginName[0].toUpperCase() + plugin.pluginName.substring(1); + + if (settings['enable' + pluginProperName]) + { + // Call the init function and check return value + var enablePlugin = plugin.init(settings, self); + + // If int returns false, consider the plugin disabled + if (!enablePlugin) + { + return; + } + + // If the title text is undefined, use the name of the plugin + var titleText = plugin.titleText || pluginProperName + " plugin"; + + // Create the pageTools bar if handleClick is set to a function + if (typeof plugin.handleClick === 'function') + { + pageTools.push('<div class="diva-' + plugin.pluginName + '-icon" title="' + titleText + '"></div>'); + + // Delegate the click event - pass it the settings + $(settings.outerSelector).delegate('.diva-' + plugin.pluginName + '-icon', 'click', function (event) + { + plugin.handleClick.call(this, event, settings); + }); + } + + // Add it to settings.plugins so it can be used later + settings.plugins.push(plugin); + } + }); + + // Save the page tools bar so it can be added for each page + if (pageTools.length) + { + settings.pageTools = '<div class="diva-page-tools">' + pageTools.join('') + '</div>'; + } + } + }; + + var hideThrobber = function () + { + // Clear the timeout, if it hasn't executed yet + clearTimeout(settings.throbberTimeoutID); + + // Hide the throbber if it has already executed + $(settings.selector + 'throbber').hide(); + }; + + var setupViewer = function () + { + // Create the throbber element + var throbberHTML = '<div id="' + settings.ID + 'throbber" class="diva-throbber"></div>'; + $(settings.outerSelector).append(throbberHTML); + + // If the request hasn't completed after a specified time, show it + settings.throbberTimeoutID = setTimeout(function () + { + $(settings.selector + 'throbber').show(); + }, settings.throbberTimeout); + + $.ajax({ + url: settings.objectData, + cache: true, + dataType: 'json', + error: function (jqxhr, status, error) + { + hideThrobber(); + + // Show a basic error message within the document viewer pane + $(settings.outerSelector).text("Invalid URL. Error code: " + status + " " + error); + }, + success: function (data, status, jqxhr) + { + hideThrobber(); + + // Save all the data we need + settings.pages = data.pgs; + settings.maxRatio = data.dims.max_ratio; + settings.minRatio = data.dims.min_ratio; + settings.itemTitle = data.item_title; + settings.numPages = data.pgs.length; + + // These are arrays, the index corresponding to the zoom level + settings.maxWidths = data.dims.max_w; + settings.averageWidths = data.dims.a_wid; + settings.averageHeights = data.dims.a_hei; + settings.totalHeights = data.dims.t_hei; + + // Make sure the set max and min values are valid + settings.realMaxZoom = data.max_zoom; + settings.maxZoomLevel = (settings.maxZoomLevel >= 0 && settings.maxZoomLevel <= data.max_zoom) ? settings.maxZoomLevel : data.max_zoom; + settings.minZoomLevel = (settings.minZoomLevel >= 0 && settings.minZoomLevel <= settings.maxZoomLevel) ? settings.minZoomLevel : 0; + settings.minPagesPerRow = Math.max(2, settings.minPagesPerRow); + settings.maxPagesPerRow = Math.max(settings.minPagesPerRow, settings.maxPagesPerRow); + + // Check that the desired page is in range + if (settings.enableFilename) + { + var iParam = $.getHashParam('i' + settings.hashParamSuffix); + var iParamPage = getPageIndex(iParam); + + if (isPageValid(iParamPage)) + { + settings.goDirectlyTo = iParamPage; + } + } + else + { + // Not using the i parameter, check the p parameter + // Subtract 1 to get the page index + var pParam = parseInt($.getHashParam('p' + settings.hashParamSuffix), 10) - 1; + + if (isPageValid(pParam)) + { + settings.goDirectlyTo = pParam; + } + } + + // Execute the setup hook for each plugin (if defined) + $.each(settings.plugins, function (index, plugin) + { + executeCallback(plugin.setupHook, settings); + }); + + // Create the toolbar and display the title + total number of pages + if (settings.enableToolbar) + { + settings.toolbar = createToolbar(); + Events.subscribe("VisiblePageDidChange", settings.toolbar.updateCurrentPage); + Events.subscribe("ModeDidSwitch", settings.toolbar.switchMode); + Events.subscribe("ViewDidSwitch", settings.toolbar.switchView); + Events.subscribe("ZoomLevelDidChange", settings.toolbar.updateZoomSlider); + Events.subscribe("GridRowNumberDidChange", settings.toolbar.updateGridSlider); + } + + $(settings.selector + 'current label').text(settings.numPages); + + if (settings.enableAutoTitle) + { + $(settings.parentSelector).prepend('<div id="' + settings.ID + 'title" class="diva-title">' + settings.itemTitle + '</div>'); + } + + // Adjust the document panel dimensions for touch devices + if (settings.mobileWebkit) + { + adjustMobileWebkitDims(); + } + else + { + settings.originalWidth = $(settings.parentSelector).width() - settings.scrollbarWidth; + settings.originalHeight = $(settings.outerSelector).height(); + adjustBrowserDims(); + } + + // Calculate the viewer x and y offsets + var viewerOffset = $(settings.outerSelector).offset(); + settings.viewerXOffset = viewerOffset.left; + settings.viewerYOffset = viewerOffset.top; + + if (settings.inFullscreen) + { + handleModeChange(false); + } + else + { + loadViewer(); + } + + // Execute the callback + executeCallback(settings.onReady, settings); + Events.publish("ViewerHasFinishedLoading", [settings]); + + // signal that everything should be set up and ready to go. + settings.loaded = true; + } + }); + }; + + var checkLoaded = function() + { + if (!settings.loaded) + { + console.warn("The viewer is not completely initialized. This is likely because it is still downloading data. To fix this, only call this function if the isReady() method returns true."); + return false; + } + return true; + }; + + var init = function () + { + // First figure out the width of the scrollbar in this browser + settings.scrollbarWidth = $.getScrollbarWidth(); + + // If window.orientation is defined, then it's probably mobileWebkit + settings.mobileWebkit = window.orientation !== undefined; + + // Generate an ID that can be used as a prefix for all the other IDs + settings.ID = $.generateId('diva-'); + settings.selector = '#' + settings.ID; + + // Figure out the hashParamSuffix from the ID + var divaNumber = parseInt(settings.ID, 10); + + if (divaNumber > 1) + { + // If this is document viewer #1, don't use a suffix; otherwise, use the document viewer number + settings.hashParamSuffix = divaNumber; + } + + // Since we need to reference these two a lot + settings.outerSelector = settings.selector + 'outer'; + settings.innerSelector = settings.selector + 'inner'; + + // Create the inner and outer panels + $(settings.parentSelector).append('<div id="' + settings.ID + 'outer" class="diva-outer"></div>'); + $(settings.outerSelector).append('<div id="' + settings.ID + 'inner" class="diva-inner diva-dragger"></div>'); + + // Create the fullscreen icon + if (settings.enableFullscreen) + { + $(settings.parentSelector).prepend('<div id="' + settings.ID + 'fullscreen" class="diva-fullscreen-icon" title="Toggle fullscreen mode"></div>'); + } + + // First, n - check if it's in range + var nParam = parseInt($.getHashParam('n' + settings.hashParamSuffix), 10); + + if (nParam >= settings.minPagesPerRow && nParam <= settings.maxPagesPerRow) + { + settings.pagesPerRow = nParam; + } + + // Now z - check that it's in range + var zParam = $.getHashParam('z' + settings.hashParamSuffix); + + if (zParam !== '') + { + // If it's empty, we don't want to change the default zoom level + zParam = parseInt(zParam, 10); + + // Can't check if it exceeds the max zoom level or not because that data is not available yet ... + if (zParam >= settings.minZoomLevel) + { + settings.zoomLevel = zParam; + } + } + + // y - vertical offset from the top of the relevant page + var yParam = parseInt($.getHashParam('y' + settings.hashParamSuffix), 10); + + if (!isNaN(yParam)) + { + settings.verticalOffset = yParam; + } + + // x - horizontal offset from the center of the page + var xParam = parseInt($.getHashParam('x' + settings.hashParamSuffix), 10); + + if (!isNaN(xParam)) + { + settings.horizontalOffset = xParam; + } + + // If the "fullscreen" hash param is true, go to fullscreen initially + // If the grid hash param is true, go to grid view initially + var gridParam = $.getHashParam('g' + settings.hashParamSuffix); + var goIntoGrid = gridParam === 'true'; + var fullscreenParam = $.getHashParam('f' + settings.hashParamSuffix); + var goIntoFullscreen = fullscreenParam === 'true'; + + settings.inGrid = (settings.inGrid && gridParam !== 'false') || goIntoGrid; + settings.inFullscreen = (settings.inFullscreen && fullscreenParam !== 'false') || goIntoFullscreen; + + // Store the height and width of the viewer (the outer div), if present + var desiredHeight = parseInt($.getHashParam('h' + settings.hashParamSuffix), 10); + var desiredWidth = parseInt($.getHashParam('w' + settings.hashParamSuffix), 10); + + // Store the minimum and maximum height too + settings.minHeight = parseInt($(settings.outerSelector).css('min-height'), 10); + settings.minWidth = parseInt($(settings.outerSelector).css('min-width'), 10); + + // Just call resize, it'll take care of bounds-checking etc + if (desiredHeight > 0 || desiredWidth > 0) + { + resizeViewer(desiredWidth, desiredHeight); + } + + // Do the initial AJAX request and viewer loading + setupViewer(); + + // Do all the plugin initialisation + initPlugins(); + + handleEvents(); + }; + + // Call the init function when this object is created. + init(); + + /* PUBLIC FUNCTIONS +=============================================== +*/ + + // Returns the title of the document, based on the directory name + this.getItemTitle = function () + { + return settings.itemTitle; + }; + + // Go to a particular page by its page number (with indexing starting at 1) + // returns True if the page number passed is valid; false if it is not. + this.gotoPageByNumber = function (pageNumber) + { + var pageIndex = pageNumber - 1; + if (isPageValid(pageIndex)) + { + gotoPage(pageIndex, 0, 0); + return true; + } + return false; + }; + + // Go to a particular page (with indexing starting at 0) + // returns True if the page index is valid; false if it is not. + this.gotoPageByIndex = function (pageIndex) + { + if (isPageValid(pageIndex)) + { + gotoPage(pageIndex, 0, 0); + return true; + } + return false; + }; + + // Returns the page index (with indexing starting at 0) + this.getCurrentPage = function () + { + console.warn("Deprecated. Use getCurrentPageIndex instead."); + return settings.currentPageIndex; + }; + + this.getNumberOfPages = function() + { + if (!checkLoaded()) + { + return false; + } + + return settings.numPages; + } + + // Returns the dimensions of a given page index at a given zoom level + this.getPageDimensionsAtZoomLevel = function(pageIdx, zoomLevel) + { + if (!checkLoaded()) + { + return false; + } + + var zoomLevel = zoomLevel - 1; // zoom levels are 1-based, but our array is 0-based; + var pg = settings.pages[pageIdx]; + var pgAtZoom = pg.d[parseInt(zoomLevel, 10)]; + return {'width': pgAtZoom.w, 'height': pgAtZoom.h} + }; + + // Returns the dimensions of the current page at the current zoom level + this.getCurrentPageDimensionsAtCurrentZoomLevel = function() + { + return this.getPageDimensionsAtZoomLevel(settings.currentPageIndex, settings.zoomLevel); + }; + + this.isReady = function() + { + return settings.loaded; + }; + + this.getCurrentPageIndex = function () + { + return settings.currentPageIndex; + }; + + this.getCurrentPageFilename = function () + { + return settings.pages[settings.currentPageIndex].f; + }; + + this.getCurrentPageNumber = function () + { + return settings.currentPageIndex + 1; + }; + + // Returns the current zoom level + this.getZoomLevel = function () + { + return settings.zoomLevel; + }; + + // gets the maximum zoom level for the entire document + this.getMaxZoomLevel = function () + { + return settings.maxZoomLevel; + }; + + // gets the max zoom level for a given page + this.getMaxZoomLevelForPage = function(pageIdx) + { + if (!checkLoaded) + { + return false; + } + + return settings.pages[pageIdx].m; + } + + this.getMinZoomLevel = function () + { + return settings.minZoomLevel; + }; + + // Use the provided zoom level (will check for validity first) + // Returns false if the zoom level is invalid, true otherwise + this.setZoomLevel = function (zoomLevel) + { + if (settings.inGrid) + { + toggleGrid(); + } + + return handleZoom(zoomLevel); + }; + + // Zoom in. Will return false if it's at the maximum zoom + this.zoomIn = function () + { + return this.setZoomLevel(settings.zoomLevel + 1); + }; + + // Zoom out. Will return false if it's at the minimum zoom + this.zoomOut = function () + { + return this.setZoomLevel(settings.zoomLevel - 1); + }; + + // Uses the isVerticallyInViewport() function, but relative to a page + // Check if something (e.g. a highlight box on a particular page) is visible + this.inViewport = function (pageNumber, topOffset, height) + { + var pageIndex = pageNumber - 1; + var top = settings.heightAbovePages[pageIndex] + topOffset; + var bottom = top + height; + + return isVerticallyInViewport(top, bottom); + }; + + // Toggle fullscreen mode + this.toggleFullscreenMode = function () + { + toggleFullscreen(); + }; + + // Enter fullscreen mode if currently not in fullscreen mode + // Returns false if in fullscreen mode initially, true otherwise + // This function will work even if enableFullscreen is set to false + this.enterFullscreenMode = function () + { + if (!settings.inFullscreen) + { + toggleFullscreen(); + return true; + } + + return false; + }; + + // Leave fullscreen mode if currently in fullscreen mode + // Returns true if in fullscreen mode intitially, false otherwise + this.leaveFullscreenMode = function () + { + if (settings.inFullscreen) + { + toggleFullscreen(); + return true; + } + + return false; + }; + + // Toggle grid view + this.toggleGridView = function () + { + toggleGrid(); + }; + + // Enter grid view if currently not in grid view + // Returns false if in grid view initially, true otherwise + this.enterGridView = function () + { + if (!settings.inGrid) { + toggleGrid(); + return true; + } + + return false; + }; + + // Leave grid view if currently in grid view + // Returns true if in grid view initially, false otherwise + this.leaveGridView = function () + { + if (settings.inGrid) + { + toggleGrid(); + return true; + } + + return false; + }; + + // Jump to a page based on its filename + // Returns true if successful and false if the filename is invalid + this.gotoPageByName = function (filename) + { + var pageIndex = getPageIndex(filename); + if (isPageValid(pageIndex)) + { + gotoPage(pageIndex, 0, 0); + return true; + } + + return false; + }; + + // Get the page index (0-based) corresponding to a given filename + // If the page index doesn't exist, this will return -1 + this.getPageIndex = function (filename) + { + return getPageIndex(filename); + }; + + // Get the current URL (exposes the private method) + this.getCurrentURL = function () + { + return getCurrentURL(); + }; + + // Get the hash part only of the current URL (without the leading #) + this.getURLHash = function () + { + return getURLHash(); + }; + + // Get an object representing the state of this diva instance (for setState) + this.getState = function () + { + return getState(); + }; + + // Get the instance selector for this instance, since it's auto-generated. + this.getInstanceSelector = function () + { + return settings.selector; + }; + + // Get the instance ID -- essentially the selector without the leading '#'. + this.getInstanceId = function() + { + return settings.ID; + }; + + this.getSettings = function() + { + return settings; + }; + + // Align this diva instance with a state object (as returned by getState) + this.setState = function (state) + { + var pageIndex; + + // If we need to resize the viewer, do that first + resizeViewer(state.w, state.h); + + // Only change settings.goDirectlyTo if state.i or state.p is valid + pageIndex = getPageIndex(state.i); + + if (isPageValid(pageIndex)) + { + settings.goDirectlyTo = pageIndex; + } + else if (isPageValid(state.p)) + { + settings.goDirectlyTo = state.p; + } + + settings.horizontalOffset = parseInt(state.x, 10); + settings.verticalOffset = parseInt(state.y, 10); + + // Only change the zoom if state.z is valid + if (state.z >= settings.minZoomLevel && state.z <= settings.maxZoomLevel) + { + settings.zoomLevel = state.z; + } + + // Only change the pages per row setting if state.n is valid + if (state.n >= settings.minPagesPerRow && state.n <= settings.maxPagesPerRow) + { + settings.pagesPerRow = state.n; + } + + if (settings.inFullscreen !== state.f) + { + // The parameter determines if we need to change the view as well + settings.inFullscreen = state.f; + handleModeChange(settings.inGrid !== state.g); + } + else + { + // Don't need to change the mode, may need to change view + if (settings.inGrid !== state.g) + { + settings.inGrid = state.g; + handleViewChange(); + } + else + { + // Reload the viewer, just in case + loadViewer(); + } + } + }; + + // Resizes the outer div to the specified width and height + this.resize = function (newWidth, newHeight) + { + resizeViewer(newWidth, newHeight); + loadViewer(); + }; + + // Destroys this instance, tells plugins to do the same (for testing) + this.destroy = function () + { + // Removes the hide-scrollbar class from the body + $('body').removeClass('diva-hide-scrollbar'); + + // Empty the parent container and remove any diva-related data + $(settings.parentSelector).empty().removeData('diva'); + + // Call the destroy function for all the enabled plugins (if it exists) + $.each(settings.plugins, function (index, plugin) + { + executeCallback(plugin.destroy); + }); + + // Remove any additional styling on the parent element + $(settings.parentSelector).removeAttr('style').removeAttr('class'); + }; + }; + + $.fn.diva = function (options) + { + return this.each(function () + { + var element = $(this); + + // Return early if this element already has a plugin instance + if (element.data('diva')) + { + return; + } + + // Save the reference to the container element + options.parentSelector = element; + + // Otherwise, instantiate the document viewer + var diva = new Diva(this, options); + element.data('diva', diva); + }); + }; + +})(jQuery); \ No newline at end of file