comparison src/main/webapp/imageServer/resources/js/vendor/diva-old.js @ 203:719475ad0923 iiif_diva

more work on new diva.js in imageServer
author casties
date Fri, 05 Jul 2019 16:05:57 +0200
parents src/main/webapp/imageServer/resources/js/diva.js@764f47286679
children
comparison
equal deleted inserted replaced
202:81f761f9c015 203:719475ad0923
1 window.divaPlugins = [];
2
3 // this pattern was taken from http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
4 (function ($)
5 {
6 var Diva = function (element, options)
7 {
8 // These are elements that can be overridden upon instantiation
9 // See https://github.com/DDMAL/diva.js/wiki/Code-documentation for more details
10 var defaults = {
11 adaptivePadding: 0.05, // The ratio of padding to the page dimension
12 blockMobileMove: true, // Prevent moving or scrolling the page on mobile devices
13 contained: false, // Determines the location of the fullscreen icon
14 objectData: '', // URL to the JSON file that provides the object dimension data - *MANDATORY*
15 enableAutoHeight: false, // Automatically adjust height based on the window size
16 enableAutoTitle: true, // Shows the title within a div of id diva-title
17 enableAutoWidth: true, // Automatically adjust width based on the window size
18 enableCanvas: true, // Used for the canvas plugin
19 enableDownload: true, // Used for the download plugin
20 enableFilename: true, // Uses filenames and not page numbers for links (i=bm_001.tif, not p=1)
21 enableFullscreen: true, // Enable or disable fullscreen icon (mode still available)
22 enableGotoPage: true, // A "go to page" jump box
23 enableGridIcon: true, // A grid view of all the pages
24 enableGridSlider: true, // Slider to control the pages per grid row
25 enableKeyScroll: true, // Scrolling using the page up/down keys
26 enableLinkIcon: true, // Controls the visibility of the link icon
27 enableSpaceScroll: false, // Scrolling down by pressing the space key
28 enableToolbar: true, // Enables the toolbar. Note that disabling this means you have to handle all controls yourself.
29 enableZoomSlider: true, // Enable or disable the zoom slider (for zooming in and out)
30 fixedPadding: 10, // Fallback if adaptive padding is set to 0
31 fixedHeightGrid: true, // So each page in grid view has the same height (only widths differ)
32 goDirectlyTo: 0, // Default initial page to show (0-indexed)
33 iipServerURL: '', // The URL to the IIPImage installation, including the `?FIF=` - *MANDATORY*
34 inFullscreen: false, // Set to true to load fullscreen mode initially
35 inGrid: false, // Set to true to load grid view initially
36 imageDir: '', // Image directory, either absolute path or relative to IIP's FILESYSTEM_PREFIX - *MANDATORY*
37 maxPagesPerRow: 8, // Maximum number of pages per row, grid view
38 maxZoomLevel: -1, // Optional; defaults to the max zoom returned in the JSON response
39 minPagesPerRow: 2, // 2 for the spread view. Recommended to leave it
40 minZoomLevel: 0, // Defaults to 0 (the minimum zoom)
41 onDocumentLoaded: null, // Callback function for when the document is fully loaded
42 onModeToggle: null, // Callback for toggling fullscreen mode
43 onViewToggle: null, // Callback for switching between grid and document view
44 onJump: null, // Callback function for jumping to a specific page (using the gotoPage feature)
45 onPageLoad: null, // Callback function for loading pages
46 onPageLoaded: null, // Callback function for after the page has been loaded
47 onReady: null, // Callback function for initial load
48 onScroll: null, // Callback function for scrolling
49 onScrollDown: null, // Callback function for scrolling down, only
50 onScrollUp: null, // Callback function for scrolling up only
51 onSetCurrentPage: null, // Callback function for when the current page is set
52 onZoom: null, // Callback function for zooming in general
53 onZoomIn: null, // Callback function for zooming in only
54 onZoomOut: null, // Callback function for zooming out only
55 pageLoadTimeout: 200, // Number of milliseconds to wait before loading pages
56 pagesPerRow: 5, // The default number of pages per row in grid view
57 rowLoadTimeout: 50, // Number of milliseconds to wait before loading a row
58 throbberTimeout: 100, // Number of milliseconds to wait before showing throbber
59 tileHeight: 256, // The height of each tile, in pixels; usually 256
60 tileWidth: 256, // The width of each tile, in pixels; usually 256
61 toolbarParentSelector: null, // The toolbar parent selector. If null, it defaults to the primary diva element. Must be a jQuery selector (leading '#')
62 viewerHeightPadding: 15, // Vertical padding when resizing the viewer, if enableAutoHeight is set
63 viewerWidthPadding: 30, // Horizontal padding when resizing the viewer, if enableAutoHeight is set
64 viewportMargin: 200, // Pretend tiles +/- 200px away from viewport are in
65 zoomLevel: 2 // The initial zoom level (used to store the current zoom level)
66 };
67
68 // Apply the defaults, or override them with passed-in options.
69 var settings = $.extend({}, defaults, options);
70
71 // Things that cannot be changed because of the way they are used by the script
72 // Many of these are declared with arbitrary values that are changed later on
73 var globals = {
74 allTilesLoaded: [], // A boolean for each page, indicating if all tiles have been loaded
75 averageHeights: [], // The average page height for each zoom level
76 averageWidths: [], // The average page width for each zoom level
77 currentPageIndex: 0, // The current page in the viewport (center-most page)
78 dimAfterZoom: 0, // Used for storing the item dimensions after zooming
79 firstPageLoaded: -1, // The ID of the first page loaded (value set later)
80 firstRowLoaded: -1, // The index of the first row loaded
81 gridPageWidth: 0, // Holds the max width of each row in grid view. Calculated in loadGrid()
82 hashParamSuffix: '', // Used when there are multiple document viewers on a page
83 heightAbovePages: [], // The height above each page at the current zoom level
84 horizontalOffset: 0, // Used in documentScroll for scrolling more precisely
85 horizontalPadding: 0, // Either the fixed padding or adaptive padding
86 ID: null, // The prefix of the IDs of the elements (usually 1-diva-)
87 innerSelector: '', // settings.selector + 'inner', for selecting the .diva-inner element
88 itemTitle: '', // The title of the document
89 lastPageLoaded: -1, // The ID of the last page loaded (value set later)
90 lastRowLoaded: -1, // The index of the last row loaded
91 leftScrollSoFar: 0, // Current scroll from the left edge of the pane
92 loaded: false, // A flag for when everything is loaded and ready to go.
93 maxWidths: [], // The width of the widest page for each zoom level
94 maxRatio: 0, // The max height/width ratio (for grid view)
95 minHeight: 0, // Minimum height of the .diva-outer element, as defined in the CSS
96 minRatio: 0, // The minimum height/width ratio for a page
97 minWidth: 0, // Minimum width of the .diva-outer element, as defined in the CSS
98 mobileWebkit: false, // Checks if the user is on a touch device (iPad/iPod/iPhone/Android)
99 numPages: 0, // Number of pages in the array
100 numRows: 0, // Number of rows
101 oldPagesPerRow: 0, // Holds the previous number of pages per row after it is changed
102 oldZoomLevel: -1, // Holds the previous zoom level after zooming in or out
103 orientationChange: false, // For handling device orientation changes for touch devices
104 originalHeight: 0, // Stores the original height of the .diva-outer element
105 originalWidth: 0, // Stores the original width of the .diva-outer element
106 outerSelector: '', // settings.selector + 'outer', for selecting the .diva-outer element
107 pages: [], // An array containing the data for all the pages
108 pageLeftOffsets: [], // Offset from the left side of the pane to the edge of the page
109 pageTimeouts: [], // Stack to hold the loadPage timeouts
110 pageTools: '', // The string for page tools
111 panelHeight: 0, // Height of the document viewer pane
112 panelWidth: 0, // Width of the document viewer pane
113 plugins: [], // Filled with the enabled plugins from window.divaPlugins
114 previousTopScroll: 0, // Used to determine vertical scroll direction
115 preZoomOffset: null, // Holds the offset prior to zooming when double-clicking
116 realMaxZoom: -1, // To hold the true max zoom level of the document (needed for calculations)
117 resizeTimer: -1, // Holds the ID of the timeout used when resizing the window (for clearing)
118 rowHeight: 0, // Holds the max height of each row in grid view. Calculated in loadGrid()
119 scaleWait: false, // For preventing double-zoom on touch devices (iPad, etc)
120 selector: '', // Uses the generated ID prefix to easily select elements
121 singleClick: false, // Used for catching ctrl+double-click events in Firefox in Mac OS
122 scrollbarWidth: 0, // Set to the actual scrollbar width in init()
123 throbberTimeoutID: -1, // Holds the ID of the throbber loading timeout
124 toolbar: null, // Holds an object with some toolbar-related functions
125 topScrollSoFar: 0, // Holds the number of pixels of vertical scroll
126 totalHeights: [], // The total height of all pages (stacked together) for each zoom level
127 totalHeight: 0, // The total height for the current zoom level (including padding)
128 verticalOffset: 0, // See horizontalOffset
129 verticalPadding: 0, // Either the fixed padding or adaptive padding
130 viewerXOffset: 0, // Distance between left edge of viewer and document left edge
131 viewerYOffset: 0 // Like viewerXOffset but for the top edges
132 };
133
134 $.extend(settings, globals);
135
136 // Executes a callback function with the diva instance set as the context
137 // Can take an unlimited number to arguments to pass to the callback function
138 var self = this;
139
140 var executeCallback = function (callback)
141 {
142 var args, i, length;
143
144 if (typeof callback === "function")
145 {
146 args = [];
147 for (i = 1, length = arguments.length; i < length; i++)
148 {
149 args.push(arguments[i]);
150 }
151
152 callback.apply(self, args);
153
154 return true;
155 }
156
157 return false;
158 };
159
160 var getPageData = function (pageIndex, attribute)
161 {
162 return settings.pages[pageIndex].d[settings.zoomLevel][attribute];
163 };
164
165 // Returns the page index associated with the given filename; must called after settings settings.pages
166 var getPageIndex = function (filename)
167 {
168 var i,
169 np = settings.numPages;
170
171 for (i = 0; i < np; i++)
172 {
173 if (settings.pages[i].f === filename)
174 {
175 return i;
176 }
177 }
178
179 return -1;
180 };
181
182 // Checks if a tile is within the viewport horizontally
183 var isHorizontallyInViewport = function (left, right)
184 {
185 var panelWidth = settings.panelWidth;
186 var leftOfViewport = settings.leftScrollSoFar - settings.viewportMargin;
187 var rightOfViewport = leftOfViewport + panelWidth + settings.viewportMargin * 2;
188
189 var leftVisible = left >= leftOfViewport && left <= rightOfViewport;
190 var rightVisible = right >= leftOfViewport && right <= rightOfViewport;
191 var middleVisible = left <= leftOfViewport && right >= rightOfViewport;
192
193 return (leftVisible || middleVisible || rightVisible);
194 };
195
196 // Checks if a page or tile is within the viewport vertically
197 var isVerticallyInViewport = function (top, bottom)
198 {
199 var panelHeight = settings.panelHeight;
200 var topOfViewport = settings.topScrollSoFar - settings.viewportMargin;
201 var bottomOfViewport = topOfViewport + panelHeight + settings.viewportMargin * 2;
202
203 var topVisible = top >= topOfViewport && top <= bottomOfViewport;
204 var middleVisible = top <= topOfViewport && bottom >= bottomOfViewport;
205 var bottomVisible = bottom >= topOfViewport && bottom <= bottomOfViewport;
206
207 return (topVisible || middleVisible || bottomVisible);
208 };
209
210 // Check if a tile is near the viewport and thus should be loaded
211 var isTileVisible = function (pageIndex, tileRow, tileCol)
212 {
213 var tileTop = settings.heightAbovePages[pageIndex] + (tileRow * settings.tileHeight) + settings.verticalPadding;
214 var tileBottom = tileTop + settings.tileHeight;
215 var tileLeft = settings.pageLeftOffsets[pageIndex] + (tileCol * settings.tileWidth);
216 var tileRight = tileLeft + settings.tileWidth;
217
218 return isVerticallyInViewport(tileTop, tileBottom) && isHorizontallyInViewport(tileLeft, tileRight);
219 };
220
221 // Check if a tile has been appended to the DOM
222 var isTileLoaded = function (pageIndex, tileIndex)
223 {
224 return document.getElementById(settings.ID + 'tile-' + pageIndex + '-' + tileIndex) === false;
225 };
226
227 // Check if a page index is valid
228 var isPageValid = function (pageIndex)
229 {
230 return pageIndex >= 0 && pageIndex < settings.numPages;
231 };
232
233 // Check if a page is in or near the viewport and thus should be loaded
234 var isPageVisible = function (pageIndex)
235 {
236 var topOfPage = settings.heightAbovePages[pageIndex];
237 var bottomOfPage = topOfPage + getPageData(pageIndex, 'h') + settings.verticalPadding;
238
239 return isVerticallyInViewport(topOfPage, bottomOfPage);
240 };
241
242 // Check if a page has been appended to the DOM
243 var isPageLoaded = function (pageIndex)
244 {
245 return $(document.getElementById(settings.ID + 'page-' + pageIndex)).length > 0;
246 };
247
248 // Appends the page directly into the document body, or loads the relevant tiles
249 var loadPage = function (pageIndex)
250 {
251 // If the page and all of its tiles have been loaded, exit
252 if (isPageLoaded(pageIndex) && settings.allTilesLoaded[pageIndex])
253 {
254 return;
255 }
256
257 // Load some data for this page
258 var filename = settings.pages[pageIndex].f;
259 var width = getPageData(pageIndex, 'w');
260 var height = getPageData(pageIndex, 'h');
261 var heightFromTop = settings.heightAbovePages[pageIndex] + settings.verticalPadding;
262 var pageSelector = settings.selector + 'page-' + pageIndex;
263 var plugin;
264
265 // If the page has not been loaded yet, append the div to the DOM
266 if (!isPageLoaded(pageIndex))
267 {
268 $(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>');
269
270 // Call the callback function
271 executeCallback(settings.onPageLoad, pageIndex, filename, pageSelector);
272 Events.publish("PageHasLoaded", [pageIndex, filename, pageSelector]);
273
274 // @TODO: Replace this with a notification.
275 // Execute the callback functions for any of the enabled plugins
276 for (plugin in settings.plugins) {
277 executeCallback(settings.plugins[plugin].onPageLoad, pageIndex, filename, pageSelector);
278 }
279 }
280
281 // There are still tiles to load, so try to load those (after a delay)
282 settings.pageTimeouts.push(setTimeout(function ()
283 {
284 // If the page is no longer in the viewport, don't load any tiles
285 if (!isPageVisible(pageIndex))
286 {
287 return;
288 }
289
290 var imdir = settings.imageDir + "/";
291 // Load some more data and initialise some variables
292 var rows = getPageData(pageIndex, 'r');
293 var cols = getPageData(pageIndex, 'c');
294 var maxZoom = settings.pages[pageIndex].m;
295 var baseURL = settings.iipServerURL + "?FIF=" + imdir + filename + '&JTL=';
296 var content = [];
297 var allTilesLoaded = true;
298 var tileIndex = 0;
299 var i;
300
301 // Calculate the width and height of outer tiles (non-standard dimensions)
302 var lastHeight = height - (rows - 1) * settings.tileHeight;
303 var lastWidth = width - (cols - 1) * settings.tileWidth;
304
305 // Declare variables used within the loops
306 var row, col, tileHeight, tileWidth, top, left, displayStyle, zoomLevel, imageURL;
307
308 // Adjust the zoom level based on the max zoom level of the page
309 zoomLevel = settings.zoomLevel + maxZoom - settings.realMaxZoom;
310 baseImageURL = baseURL + zoomLevel + ',';
311
312 // Loop through all the tiles in this page
313 row = 0;
314 while (row < rows)
315 {
316 col = 0;
317 while (col < cols)
318 {
319 top = row * settings.tileHeight;
320 left = col * settings.tileWidth;
321
322 // If the tile is in the last row or column, its dimensions will be different
323 tileHeight = (row === rows - 1) ? lastHeight : settings.tileHeight;
324 tileWidth = (col === cols - 1) ? lastWidth : settings.tileWidth;
325
326 imageURL = baseImageURL + tileIndex;
327
328 // this check looks to see if the tile is already loaded, and then if
329 // it isn't, if it should be visible.
330 if (!isTileLoaded(pageIndex, tileIndex)) {
331 if (isTileVisible(pageIndex, row, col)) {
332 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>');
333 } else {
334 // The tile does not need to be loaded - not all have been loaded
335 allTilesLoaded = false;
336 }
337 }
338 tileIndex++;
339 col++;
340 }
341 row++;
342 }
343
344 settings.allTilesLoaded[pageIndex] = allTilesLoaded;
345 $(document.getElementById(settings.ID + 'page-' + pageIndex)).append(content.join(''));
346
347 executeCallback(settings.onPageLoaded, pageIndex, filename, pageSelector);
348
349 }, settings.pageLoadTimeout));
350 };
351
352 // Delete a page from the DOM; will occur when a page is scrolled out of the viewport
353 var deletePage = function (pageIndex)
354 {
355 $(document.getElementById(settings.ID + 'page-' + pageIndex)).empty().remove();
356 };
357
358 // Check if the bottom of a page is above the top of a viewport (scrolling down)
359 // For when you want to keep looping but don't want to load a specific page
360 var pageAboveViewport = function (pageIndex)
361 {
362 var bottomOfPage = settings.heightAbovePages[pageIndex] + getPageData(pageIndex, 'h') + settings.verticalPadding;
363 var topOfViewport = settings.topScrollSoFar;
364
365 return bottomOfPage < topOfViewport;
366 };
367
368 // Check if the top of a page is below the bottom of a viewport (scrolling up)
369 var pageBelowViewport = function (pageIndex)
370 {
371 var topOfPage = settings.heightAbovePages[pageIndex];
372 var bottomOfViewport = settings.topScrollSoFar + settings.panelHeight;
373
374 return topOfPage > bottomOfViewport;
375 };
376
377 // Called by adjust pages - determine what pages should be visible, and show them
378 var attemptPageShow = function (pageIndex, direction)
379 {
380 if (direction > 0)
381 {
382 // Direction is positive - we're scrolling down
383 if (isPageValid(pageIndex))
384 {
385 // If the page should be visible, then yes, add it
386 if (isPageVisible(pageIndex))
387 {
388 loadPage(pageIndex);
389
390 settings.lastPageLoaded = pageIndex;
391
392 // Recursively call this function until there's nothing to add
393 attemptPageShow(settings.lastPageLoaded + 1, direction);
394 }
395 else if (pageAboveViewport(pageIndex))
396 {
397 // If the page is below the viewport. try to load the next one
398 attemptPageShow(pageIndex + 1, direction);
399 }
400 }
401 }
402 else
403 {
404 // Direction is negative - we're scrolling up
405 if (isPageValid(pageIndex))
406 {
407 // If it's near the viewport, yes, add it
408 if (isPageVisible(pageIndex))
409 {
410 loadPage(pageIndex);
411
412 // Reset the first page loaded to this one
413 settings.firstPageLoaded = pageIndex;
414
415 // Recursively call this function until there's nothing to add
416 attemptPageShow(settings.firstPageLoaded - 1, direction);
417 }
418 else if (pageBelowViewport(pageIndex))
419 {
420 // Attempt to call this on the next page, do not increment anything
421 attemptPageShow(pageIndex - 1, direction);
422 }
423 }
424 }
425 };
426
427 // Called by adjustPages - see what pages need to be hidden, and hide them
428 var attemptPageHide = function (pageIndex, direction)
429 {
430 if (direction > 0)
431 {
432 // Scrolling down - see if this page needs to be deleted from the DOM
433 if (isPageValid(pageIndex) && pageAboveViewport(pageIndex))
434 {
435 // Yes, delete it, reset the first page loaded
436 deletePage(pageIndex);
437 settings.firstPageLoaded = pageIndex + 1;
438
439 // Try to call this function recursively until there's nothing to delete
440 attemptPageHide(settings.firstPageLoaded, direction);
441 }
442 }
443 else
444 {
445 // Direction must be negative (not 0 - see adjustPages), we're scrolling up
446 if (isPageValid(pageIndex) && pageBelowViewport(pageIndex))
447 {
448 // Yes, delete it, reset the last page loaded
449 deletePage(pageIndex);
450 settings.lastPageLoaded = pageIndex - 1;
451
452 // Try to call this function recursively until there's nothing to delete
453 attemptPageHide(settings.lastPageLoaded, direction);
454 }
455 }
456 };
457
458 // Handles showing and hiding pages when the user scrolls
459 var adjustPages = function (direction)
460 {
461 var i;
462
463 // Direction is negative, so we're scrolling up
464 if (direction < 0)
465 {
466 attemptPageShow(settings.firstPageLoaded, direction);
467 setCurrentPage(-1);
468 attemptPageHide(settings.lastPageLoaded, direction);
469 }
470 else if (direction > 0)
471 {
472 // Direction is positive so we're scrolling down
473 attemptPageShow(settings.lastPageLoaded, direction);
474 setCurrentPage(1);
475 attemptPageHide(settings.firstPageLoaded, direction);
476 }
477 else
478 {
479 // Horizontal scroll, check if we need to reveal any tiles
480 var lpl = settings.lastPageLoaded;
481 for (i = Math.max(settings.firstPageLoaded, 0); i <= lpl; i++)
482 {
483 if (isPageVisible(i))
484 {
485 loadPage(i);
486 }
487 }
488 }
489
490 executeCallback(settings.onScroll, settings.topScrollSoFar);
491
492 // If we're scrolling down
493 if (direction > 0)
494 {
495 executeCallback(settings.onScrollDown, settings.topScrollSoFar);
496 }
497 else if (direction < 0)
498 {
499 // We're scrolling up
500 executeCallback(settings.onScrollUp, settings.topScrollSoFar);
501 }
502 };
503
504 // Check if a row index is valid
505 var isRowValid = function (rowIndex)
506 {
507 return rowIndex >= 0 && rowIndex < settings.numRows;
508 };
509
510 // Check if a row should be visible in the viewport
511 var isRowVisible = function (rowIndex)
512 {
513 var topOfRow = settings.rowHeight * rowIndex;
514 var bottomOfRow = topOfRow + settings.rowHeight + settings.fixedPadding;
515
516 return isVerticallyInViewport(topOfRow, bottomOfRow);
517 };
518
519 // Check if a row (in grid view) is present in the DOM
520 var isRowLoaded = function (rowIndex)
521 {
522 return $(settings.selector + 'row-' + rowIndex).length > 0;
523 };
524
525 var loadRow = function (rowIndex)
526 {
527 // If the row has already been loaded, don't attempt to load it again
528 if (isRowLoaded(rowIndex))
529 {
530 return;
531 }
532
533 // Load some data for this and initialise some variables
534 var heightFromTop = (settings.rowHeight * rowIndex) + settings.fixedPadding;
535 var content = [];
536
537 // Create the opening tag for the row div
538 content.push('<div class="diva-row" id="' + settings.ID + 'row-' + rowIndex + '" style="height: ' + settings.rowHeight + '; top: ' + heightFromTop + 'px;">');
539
540 // Declare variables used in the loop
541 var i, pageIndex, filename, realWidth, realHeight, pageWidth, pageHeight, leftOffset, imageURL;
542 var imdir = settings.imageDir + "/";
543
544 // Load each page within that row
545 var ppr = settings.pagesPerRow;
546 for (i = 0; i < ppr; i++)
547 {
548 pageIndex = rowIndex * settings.pagesPerRow + i;
549
550 // If this page is the last row, don't try to load a nonexistent page
551 if (!isPageValid(pageIndex))
552 {
553 break;
554 }
555
556 // Calculate the width, height and horizontal placement of this page
557 filename = settings.pages[pageIndex].f;
558 realWidth = getPageData(pageIndex, 'w');
559 realHeight = getPageData(pageIndex, 'h');
560 pageWidth = (settings.fixedHeightGrid) ? (settings.rowHeight - settings.fixedPadding) * realWidth / realHeight : settings.gridPageWidth;
561 pageHeight = (settings.fixedHeightGrid) ? settings.rowHeight - settings.fixedPadding : pageWidth / realWidth * realHeight;
562 leftOffset = parseInt(i * (settings.fixedPadding + settings.gridPageWidth) + settings.fixedPadding, 10);
563
564 // Make sure they're all integers for nice, round numbers
565 pageWidth = parseInt(pageWidth, 10);
566 pageHeight = parseInt(pageHeight, 10);
567
568 // Center the page if the height is fixed (otherwise, there is no horizontal padding)
569 leftOffset += (settings.fixedHeightGrid) ? (settings.gridPageWidth - pageWidth) / 2 : 0;
570 imageURL = settings.iipServerURL + "?FIF=" + imdir + filename + '&amp;HEI=' + (pageHeight + 2) + '&amp;CVT=JPEG';
571
572 // Append the HTML for this page to the string builder array
573 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>');
574
575 // Add each image to a queue so that images aren't loaded unnecessarily
576 addPageToQueue(rowIndex, pageIndex, imageURL, pageWidth, pageHeight);
577 }
578
579 // Append this row to the DOM
580 content.push('</div>');
581 $(document.getElementById(settings.ID + "inner")).append(content.join(''));
582 };
583
584 var deleteRow = function (rowIndex)
585 {
586 $(document.getElementById(settings.ID + 'row-' + rowIndex)).empty().remove();
587 };
588
589 // Check if the bottom of a row is above the top of the viewport (scrolling down)
590 var rowAboveViewport = function (rowIndex)
591 {
592 var bottomOfRow = settings.rowHeight * (rowIndex + 1);
593 var topOfViewport = settings.topScrollSoFar;
594
595 return (bottomOfRow < topOfViewport);
596 };
597
598 // Check if the top of a row is below the bottom of the viewport (scrolling up)
599 var rowBelowViewport = function (rowIndex)
600 {
601 var topOfRow = settings.rowHeight * rowIndex;
602 var bottomOfViewport = settings.topScrollSoFar + settings.panelHeight;
603
604 return (topOfRow > bottomOfViewport);
605 };
606
607 // Same thing as attemptPageShow only with rows
608 var attemptRowShow = function (rowIndex, direction)
609 {
610 if (direction > 0)
611 {
612 if (isRowValid(rowIndex))
613 {
614 if (isRowVisible(rowIndex))
615 {
616 loadRow(rowIndex);
617 settings.lastRowLoaded = rowIndex;
618
619 attemptRowShow(settings.lastRowLoaded + 1, direction);
620 }
621 else if (rowAboveViewport(rowIndex))
622 {
623 attemptRowShow(rowIndex + 1, direction);
624 }
625 }
626 }
627 else
628 {
629 if (isRowValid(rowIndex))
630 {
631 if (isRowVisible(rowIndex))
632 {
633 loadRow(rowIndex);
634 settings.firstRowLoaded = rowIndex;
635
636 attemptRowShow(settings.firstRowLoaded - 1, direction);
637 }
638 else if (rowBelowViewport(rowIndex))
639 {
640 attemptRowShow(rowIndex - 1, direction);
641 }
642 }
643 }
644 };
645
646 var attemptRowHide = function (rowIndex, direction)
647 {
648 if (direction > 0)
649 {
650 if (isRowValid(rowIndex) && rowAboveViewport(rowIndex))
651 {
652 deleteRow(rowIndex);
653 settings.firstRowLoaded++;
654
655 attemptRowHide(settings.firstRowLoaded, direction);
656 }
657 }
658 else
659 {
660 if (isRowValid(rowIndex) && rowBelowViewport(rowIndex))
661 {
662 deleteRow(rowIndex);
663 settings.lastRowLoaded--;
664
665 attemptRowHide(settings.lastRowLoaded, direction);
666 }
667 }
668 };
669
670 var adjustRows = function (direction)
671 {
672 if (direction < 0)
673 {
674 attemptRowShow(settings.firstRowLoaded, -1);
675 setCurrentRow(-1);
676 attemptRowHide(settings.lastRowLoaded, -1);
677 }
678 else if (direction > 0)
679 {
680 attemptRowShow(settings.lastRowLoaded, 1);
681 setCurrentRow(1);
682 attemptRowHide(settings.firstRowLoaded, 1);
683 }
684
685 executeCallback(settings.onScroll, settings.topScrollSoFar);
686
687 // If we're scrolling down
688 if (direction > 0)
689 {
690 executeCallback(settings.onScrollDown, settings.topScrollSoFar);
691 }
692 else if (direction < 0)
693 {
694 // We're scrolling up
695 executeCallback(settings.onScrollUp, settings.topScrollSoFar);
696 }
697 };
698
699 // Used to delay loading of page images in grid view to prevent unnecessary loads
700 var addPageToQueue = function (rowIndex, pageIndex, imageURL, pageWidth, pageHeight)
701 {
702 settings.pageTimeouts.push(setTimeout(function ()
703 {
704 if (isRowVisible(rowIndex))
705 {
706 $(settings.selector + 'page-' + pageIndex).html('<img src="' + imageURL + '" style="width: ' + pageWidth + 'px; height: ' + pageHeight + 'px;" />');
707 }
708 }, settings.rowLoadTimeout));
709 };
710
711 // Determines and sets the "current page" (settings.currentPageIndex); called within adjustPages
712 // The "direction" is either 1 (downward scroll) or -1 (upward scroll)
713 var setCurrentPage = function (direction)
714 {
715 var middleOfViewport = settings.topScrollSoFar + (settings.panelHeight / 2);
716 var currentPage = settings.currentPageIndex;
717 var pageToConsider = settings.currentPageIndex + direction;
718 var changeCurrentPage = false;
719 var pageSelector = settings.selector + 'page-' + pageToConsider;
720
721 // When scrolling up:
722 if (direction < 0)
723 {
724 // If the previous page > middle of viewport
725 if (pageToConsider >= 0 && (settings.heightAbovePages[pageToConsider] + getPageData(pageToConsider, 'h') + (settings.verticalPadding) >= middleOfViewport))
726 {
727 changeCurrentPage = true;
728 }
729 }
730 else if (direction > 0)
731 {
732 // When scrolling down:
733 // If this page < middle of viewport
734 if (settings.heightAbovePages[currentPage] + getPageData(currentPage, 'h') + settings.verticalPadding < middleOfViewport)
735 {
736 changeCurrentPage = true;
737 }
738 }
739
740 if (changeCurrentPage)
741 {
742 // Set this to the current page
743 settings.currentPageIndex = pageToConsider;
744 // Now try to change the next page, given that we're not going to a specific page
745 // Calls itself recursively - this way we accurately obtain the current page
746 if (direction !== 0)
747 {
748 if (!setCurrentPage(direction))
749 {
750 var filename = settings.pages[pageToConsider].f;
751 executeCallback(settings.onSetCurrentPage, pageToConsider, filename);
752 Events.publish("VisiblePageDidChange", [pageToConsider, filename]);
753 }
754 }
755 return true;
756 }
757
758 return false;
759 };
760
761 // Sets the current page in grid view
762 var setCurrentRow = function (direction)
763 {
764 var currentRow = Math.floor(settings.currentPageIndex / settings.pagesPerRow);
765 var rowToConsider = currentRow + parseInt(direction, 10);
766 var middleOfViewport = settings.topScrollSoFar + (settings.panelHeight / 2);
767 var changeCurrentRow = false;
768
769 if (direction < 0)
770 {
771 if (rowToConsider >= 0 && (settings.rowHeight * currentRow >= middleOfViewport || settings.rowHeight * rowToConsider >= settings.topScrollSoFar))
772 {
773 changeCurrentRow = true;
774 }
775 }
776 else if (direction > 0)
777 {
778 if ((settings.rowHeight * (currentRow + 1)) < settings.topScrollSoFar && isRowValid(rowToConsider))
779 {
780 changeCurrentRow = true;
781 }
782 }
783
784 if (changeCurrentRow)
785 {
786 settings.currentPageIndex = rowToConsider * settings.pagesPerRow;
787
788 if (direction !== 0)
789 {
790 if (!setCurrentRow(direction))
791 {
792 var pageIndex = settings.currentPageIndex;
793 var filename = settings.pages[pageIndex].f;
794 Events.publish("VisiblePageDidChange", [pageIndex, filename]);
795 }
796 }
797
798 return true;
799 }
800
801 return false;
802 };
803
804 // Helper function for going to a particular page
805 // Vertical offset: from the top of the page (including the top padding)
806 // Horizontal offset: from the center of the page; can be negative if to the left
807 var gotoPage = function (pageIndex, verticalOffset, horizontalOffset)
808 {
809 verticalOffset = (typeof verticalOffset !== 'undefined') ? verticalOffset : 0;
810 horizontalOffset = (typeof horizontalOffset !== 'undefined') ? horizontalOffset: 0;
811 var desiredTop = settings.heightAbovePages[pageIndex] + verticalOffset;
812 var desiredLeft = (settings.maxWidths[settings.zoomLevel] - settings.panelWidth) / 2 + settings.horizontalPadding + horizontalOffset;
813
814 $(settings.outerSelector).scrollTop(desiredTop);
815 $(settings.outerSelector).scrollLeft(desiredLeft);
816
817 // Pretend that this is the current page
818 settings.currentPageIndex = pageIndex;
819 //settings.toolbar.updateCurrentPage();
820 var filename = settings.pages[pageIndex].f;
821
822 Events.publish("VisiblePageDidChange", [pageIndex, filename]);
823 executeCallback(settings.onSetCurrentPage, pageIndex, filename);
824
825 // Execute the onJump callback
826 executeCallback(settings.onJump, pageIndex);
827 };
828
829 // Calculates the desired row, then scrolls there
830 var gotoRow = function (pageIndex)
831 {
832 var desiredRow = Math.floor(pageIndex / settings.pagesPerRow);
833 var desiredTop = desiredRow * settings.rowHeight;
834 $(settings.outerSelector).scrollTop(desiredTop);
835
836 // Pretend that this is the current page (it probably isn't)
837 settings.currentPageIndex = pageIndex;
838 var filename = settings.pages[pageIndex].f;
839 Events.publish("VisiblePageDidChange", [pageIndex, filename]);
840 };
841
842 // Helper function called by loadDocument to scroll to the desired place
843 var documentScroll = function ()
844 {
845 // If settings.preZoomOffset is defined, the zoom was trigged by double-clicking
846 // We then zoom in on a specific region
847 if (settings.preZoomOffset)
848 {
849 var clickedPage = settings.preZoomOffset.i;
850 var heightAbovePage = settings.heightAbovePages[clickedPage] + settings.verticalPadding;
851 var pageLeftOffset = settings.pageLeftOffsets[clickedPage];
852 var zoomRatio = Math.pow(2, settings.zoomLevel - settings.oldZoomLevel);
853
854 var distanceFromViewport = {
855 x: settings.preZoomOffset.originalX - settings.viewerXOffset,
856 y: settings.preZoomOffset.originalY - settings.viewerYOffset
857 };
858
859 var newDistanceToEdge = {
860 x: settings.preZoomOffset.x * zoomRatio,
861 y: settings.preZoomOffset.y * zoomRatio
862 };
863
864 var newScroll = {
865 x: newDistanceToEdge.x - distanceFromViewport.x + pageLeftOffset,
866 y: newDistanceToEdge.y - distanceFromViewport.y + heightAbovePage
867 };
868
869 $(settings.outerSelector).scrollTop(newScroll.y).scrollLeft(newScroll.x);
870
871 settings.preZoomOffset = undefined;
872 }
873 else
874 {
875 // Otherwise, we just scroll to the page saved in settings.goDirectlyTo (must be valid)
876 // Make sure the value for settings.goDirectlyTo is valid
877 if (!isPageValid(settings.goDirectlyTo))
878 {
879 settings.goDirectlyTo = 0;
880 }
881
882 // We use the stored y/x offsets (relative to the top of the page and the center, respectively)
883 gotoPage(settings.goDirectlyTo, settings.verticalOffset, settings.horizontalOffset);
884 settings.horizontalOffset = 0;
885 settings.verticalOffset = 0;
886 }
887 };
888
889 // Don't call this when not in grid mode please
890 // Scrolls to the relevant place when in grid view
891 var gridScroll = function ()
892 {
893 // Figure out and scroll to the row containing the current page
894 gotoRow(settings.goDirectlyTo);
895 };
896
897 // If the given zoom level is valid, returns it; else, returns the min
898 var getValidZoomLevel = function (zoomLevel)
899 {
900 return (zoomLevel >= settings.minZoomLevel && zoomLevel <= settings.maxZoomLevel) ? zoomLevel : settings.minZoomLevel;
901 };
902
903 var getValidPagesPerRow = function (pagesPerRow)
904 {
905 return (pagesPerRow >= settings.minPagesPerRow && pagesPerRow <= settings.maxPagesPerRow) ? pagesPerRow : settings.maxPagesPerRow;
906 };
907
908 // Reset some settings and empty the viewport
909 var clearViewer = function ()
910 {
911 settings.allTilesLoaded = [];
912 $(settings.outerSelector).scrollTop(0);
913 settings.topScrollSoFar = 0;
914 $(settings.innerSelector).empty();
915 settings.firstPageLoaded = 0;
916 settings.firstRowLoaded = -1;
917 settings.previousTopScroll = 0;
918
919 // Clear all the timeouts to prevent undesired pages from loading
920 clearTimeout(settings.resizeTimer);
921
922 while (settings.pageTimeouts.length)
923 {
924 clearTimeout(settings.pageTimeouts.pop());
925 }
926 };
927
928 // Called when we don't necessarily know which view to go into
929 var loadViewer = function ()
930 {
931 if (settings.inGrid)
932 {
933 loadGrid();
934 }
935 else
936 {
937 loadDocument();
938 }
939 };
940
941 // Called every time we need to load document view (after zooming, fullscreen, etc)
942 var loadDocument = function ()
943 {
944 clearViewer();
945
946 // Make sure the zoom level we've been given is valid
947 settings.zoomLevel = getValidZoomLevel(settings.zoomLevel);
948 var z = settings.zoomLevel;
949
950 // Calculate the horizontal and vertical inter-page padding
951 if (settings.adaptivePadding > 0)
952 {
953 settings.horizontalPadding = settings.averageWidths[z] * settings.adaptivePadding;
954 settings.verticalPadding = settings.averageHeights[z] * settings.adaptivePadding;
955 }
956 else
957 {
958 // It's less than or equal to 0; use fixedPadding instead
959 settings.horizontalPadding = settings.fixedPadding;
960 settings.verticalPadding = settings.fixedPadding;
961 }
962
963 // Make sure the vertical padding is at least 40, if plugin icons are enabled
964 if (settings.pageTools.length)
965 {
966 settings.verticalPadding = Math.max(40, settings.horizontalPadding);
967 }
968
969 // Now reset some things that need to be changed after each zoom
970 settings.totalHeight = settings.totalHeights[z] + settings.verticalPadding * (settings.numPages + 1);
971 settings.dimAfterZoom = settings.totalHeight;
972
973 // Determine the width of the inner element (based on the max width)
974 var maxWidthToSet = settings.maxWidths[z] + settings.horizontalPadding * 2;
975 var widthToSet = Math.max(maxWidthToSet, settings.panelWidth);
976
977 // Needed to set settings.heightAbovePages - initially just the top padding
978 var heightSoFar = 0;
979 var i;
980
981 for (i = 0; i < settings.numPages; i++)
982 {
983 // First set the height above that page by adding this height to the previous total
984 // A page includes the padding above it
985 settings.heightAbovePages[i] = heightSoFar;
986
987 // Has to be done this way otherwise you get the height of the page included too
988 heightSoFar = settings.heightAbovePages[i] + getPageData(i, 'h') + settings.verticalPadding;
989
990 // Figure out the pageLeftOffset stuff
991 settings.pageLeftOffsets[i] = (widthToSet - getPageData(i, 'w')) / 2;
992
993 // Now try to load the page ONLY if the page needs to be loaded
994 // Take scrolling into account later, just try this for now
995 if (isPageVisible(i))
996 {
997 loadPage(i);
998 settings.lastPageLoaded = i;
999 }
1000 }
1001
1002 // If this is not the initial load, execute the zoom callbacks
1003 if (settings.oldZoomLevel >= 0)
1004 {
1005 if (settings.oldZoomLevel < settings.zoomLevel)
1006 {
1007 executeCallback(settings.onZoomIn, z);
1008 }
1009 else
1010 {
1011 executeCallback(settings.onZoomOut, z);
1012 }
1013
1014 executeCallback(settings.onZoom, z);
1015 }
1016
1017 // Set the height and width of documentpane (necessary for dragscrollable)
1018 $(settings.innerSelector).height(Math.round(settings.totalHeight));
1019 $(settings.innerSelector).width(Math.round(widthToSet));
1020
1021 // Scroll to the proper place
1022 documentScroll();
1023
1024 // For the iPad - wait until this request finishes before accepting others
1025 if (settings.scaleWait)
1026 {
1027 settings.scaleWait = false;
1028 }
1029
1030 var fileName = settings.pages[settings.currentPageIndex].f;
1031 executeCallback(settings.onDocumentLoaded, settings.lastPageLoaded, fileName);
1032 Events.publish("DocumentHasFinishedLoading", [settings.lastPageLoaded, fileName]);
1033 };
1034
1035 var loadGrid = function ()
1036 {
1037 clearViewer();
1038
1039 // Make sure the pages per row setting is valid
1040 settings.pagesPerRow = getValidPagesPerRow(settings.pagesPerRow);
1041
1042 var horizontalPadding = settings.fixedPadding * (settings.pagesPerRow + 1);
1043 var pageWidth = (settings.panelWidth - horizontalPadding) / settings.pagesPerRow;
1044 settings.gridPageWidth = pageWidth;
1045
1046 // Calculate the row height depending on whether we want to fix the width or the height
1047 settings.rowHeight = (settings.fixedHeightGrid) ? settings.fixedPadding + settings.minRatio * pageWidth : settings.fixedPadding + settings.maxRatio * pageWidth;
1048 settings.numRows = Math.ceil(settings.numPages / settings.pagesPerRow);
1049 settings.totalHeight = settings.numRows * settings.rowHeight + settings.fixedPadding;
1050
1051 $(settings.innerSelector).height(Math.round(settings.totalHeight));
1052 $(settings.innerSelector).width(Math.round(settings.panelWidth));
1053
1054 // First scroll directly to the row containing the current page
1055 gridScroll();
1056
1057 var i, rowIndex;
1058
1059 // Figure out the row each page is in
1060 var np = settings.numPages;
1061 for (i = 0; i < np; i += settings.pagesPerRow)
1062 {
1063 rowIndex = Math.floor(i / settings.pagesPerRow);
1064
1065 if (isRowVisible(rowIndex))
1066 {
1067 settings.firstRowLoaded = (settings.firstRowLoaded < 0) ? rowIndex : settings.firstRowLoaded;
1068 loadRow(rowIndex);
1069 settings.lastRowLoaded = rowIndex;
1070 }
1071 }
1072 };
1073
1074 // Handles switching in and out of fullscreen mode
1075 // Should only be called after changing settings.inFullscreen
1076 var handleModeChange = function (changeView)
1077 {
1078 // Save some offsets (required for scrolling properly), if it's not the initial load
1079 if (settings.oldZoomLevel >= 0)
1080 {
1081 if (!settings.inGrid)
1082 {
1083 var pageOffset = $(settings.selector + 'page-' + settings.currentPageIndex).offset();
1084 var topOffset = -(pageOffset.top - settings.verticalPadding - settings.viewerYOffset);
1085 var expectedLeft = (settings.panelWidth - getPageData(settings.currentPageIndex, 'w')) / 2;
1086 var leftOffset = -(pageOffset.left - settings.viewerXOffset - expectedLeft);
1087 settings.verticalOffset = topOffset;
1088 settings.horizontalOffset = leftOffset;
1089 }
1090 }
1091
1092 // Change the look of the toolbar
1093 Events.publish("ModeDidSwitch", null);
1094
1095 // Toggle the classes
1096 $(settings.selector + 'fullscreen').toggleClass('diva-in-fullscreen');
1097 $(settings.outerSelector).toggleClass('diva-fullscreen');
1098 $('body').toggleClass('diva-hide-scrollbar');
1099 $(settings.parentSelector).toggleClass('diva-full-width');
1100
1101 // Reset the panel dimensions
1102 settings.panelHeight = $(settings.outerSelector).height();
1103 settings.panelWidth = $(settings.outerSelector).width() - settings.scrollbarWidth;
1104 $(settings.innerSelector).width(settings.panelWidth);
1105
1106 // Recalculate the viewer offsets
1107 settings.viewerXOffset = $(settings.outerSelector).offset().left;
1108 settings.viewerYOffset = $(settings.outerSelector).offset().top;
1109
1110 // Used by setState when we need to change the view and the mode
1111 if (changeView)
1112 {
1113 settings.inGrid = !settings.inGrid;
1114 handleViewChange();
1115 }
1116 else
1117 {
1118 loadViewer();
1119 }
1120
1121 // Execute callbacks
1122 executeCallback(settings.onModeToggle, settings.inFullscreen);
1123 Events.publish("ModeHasChanged", [settings.inFullScreen]);
1124 };
1125
1126 // Handles switching in and out of grid view
1127 // Should only be called after changing settings.inGrid
1128 var handleViewChange = function ()
1129 {
1130 // Switch the slider
1131 // Events.publish("ViewDidSwitch", null);
1132
1133 loadViewer();
1134 executeCallback(settings.onViewToggle, settings.inGrid);
1135 Events.publish("ViewDidSwitch", [settings.inGrid]);
1136 };
1137
1138 // Called when the fullscreen icon is clicked
1139 var toggleFullscreen = function ()
1140 {
1141 settings.goDirectlyTo = settings.currentPageIndex;
1142 settings.inFullscreen = !settings.inFullscreen;
1143 handleModeChange(false);
1144 };
1145
1146 // Called when the grid icon is clicked
1147 var toggleGrid = function ()
1148 {
1149 settings.goDirectlyTo = settings.currentPageIndex;
1150 settings.inGrid = !settings.inGrid;
1151 handleViewChange();
1152 };
1153
1154 // Called after double-click or ctrl+double-click events on pages in document view
1155 var handleDocumentDoubleClick = function (event)
1156 {
1157 var pageOffset = $(this).offset();
1158 var offsetX = event.pageX - pageOffset.left;
1159 var offsetY = event.pageY - pageOffset.top;
1160
1161 // Store the offset information so that it can be used in documentScroll()
1162 settings.preZoomOffset = {
1163 x: offsetX,
1164 y: offsetY,
1165 originalX: event.pageX,
1166 originalY: event.pageY,
1167 i: $(this).attr('data-index')
1168 };
1169
1170 // Hold control to zoom out, otherwise, zoom in
1171 var newZoomLevel = (event.ctrlKey) ? settings.zoomLevel - 1 : settings.zoomLevel + 1;
1172
1173 handleZoom(newZoomLevel);
1174 };
1175
1176 // Called after double-clicking on a page in grid view
1177 var handleGridDoubleClick = function (event)
1178 {
1179 // Figure out the page that was clicked, scroll to that page
1180 var sel = document.getElementById(settings.ID + "outer");
1181 var centerX = (event.pageX - settings.viewerXOffset) + sel.scrollLeft;
1182 var centerY = (event.pageY - settings.viewerYOffset) + sel.scrollTop;
1183 var rowIndex = Math.floor(centerY / settings.rowHeight);
1184 var colIndex = Math.floor(centerX / (settings.panelWidth / settings.pagesPerRow));
1185 var pageIndex = rowIndex * settings.pagesPerRow + colIndex;
1186 settings.goDirectlyTo = pageIndex;
1187
1188 // Leave grid view, jump directly to the desired page
1189 settings.inGrid = false;
1190 handleViewChange();
1191 };
1192
1193 // Handles pinch-zooming for mobile devices
1194 var handlePinchZoom = function (event)
1195 {
1196 var newZoomLevel = settings.zoomLevel;
1197
1198 // First figure out the new zoom level:
1199 if (event.scale > 1 && newZoomLevel < settings.maxZoomLevel)
1200 {
1201 newZoomLevel++;
1202 }
1203 else if (event.scale < 1 && newZoomLevel > settings.minZoomLevel)
1204 {
1205 newZoomLevel--;
1206 }
1207 else
1208 {
1209 return;
1210 }
1211
1212 // Set it to true so we have to wait for this one to finish
1213 settings.scaleWait = true;
1214
1215 // Has to call handleZoomSlide so that the coordinates are kept
1216 handleZoom(newZoomLevel);
1217 };
1218
1219 // Called to handle any zoom level
1220 var handleZoom = function (newValue)
1221 {
1222 var newZoomLevel = getValidZoomLevel(newValue);
1223
1224 // If the zoom level provided is invalid, return false
1225 if (newZoomLevel !== newValue)
1226 {
1227 return false;
1228 }
1229
1230 settings.oldZoomLevel = settings.zoomLevel;
1231 settings.zoomLevel = newZoomLevel;
1232
1233 // Update the slider
1234 Events.publish("ZoomLevelDidChange", null);
1235
1236 loadDocument();
1237
1238 return true;
1239 };
1240
1241 // Called to handle changing the pages per row slider
1242 var handleGrid = function (newValue)
1243 {
1244 var newPagesPerRow = getValidPagesPerRow(newValue);
1245
1246 // If the value provided is invalid, return false
1247 if (newPagesPerRow !== newValue)
1248 {
1249 return false;
1250 }
1251
1252 settings.oldPagesPerRow = settings.zoomLevel;
1253 settings.pagesPerRow = newPagesPerRow;
1254
1255 // Update the slider
1256 Events.publish("GridRowNumberDidChange", null);
1257
1258 loadGrid();
1259 };
1260
1261 var getYOffset = function ()
1262 {
1263 var yScroll = document.getElementById(settings.ID + "outer").scrollTop;
1264 var topOfPage = settings.heightAbovePages[settings.currentPageIndex];
1265
1266 return parseInt(yScroll - topOfPage, 10);
1267 };
1268
1269 var getXOffset = function ()
1270 {
1271 var innerWidth = settings.maxWidths[settings.zoomLevel] + settings.horizontalPadding * 2;
1272 var centerX = (innerWidth - settings.panelWidth) / 2;
1273 var xoff = document.getElementById(settings.ID + "outer").scrollLeft - centerX;
1274 return parseInt(xoff, 10);
1275 };
1276
1277 var getState = function ()
1278 {
1279 var state = {
1280 'f': settings.inFullscreen,
1281 'g': settings.inGrid,
1282 'z': settings.zoomLevel,
1283 'n': settings.pagesPerRow,
1284 'i': (settings.enableFilename) ? settings.pages[settings.currentPageIndex].f : false,
1285 'p': (settings.enableFilename) ? false : settings.currentPageIndex + 1,
1286 'y': (settings.inGrid) ? false : getYOffset(),
1287 'x': (settings.inGrid) ? false : getXOffset(),
1288 'h': (settings.inFullscreen) ? false : settings.panelHeight,
1289 'w': (settings.inFullscreen) ? false : $(settings.outerSelector).width()
1290 };
1291
1292 return state;
1293 };
1294
1295 var getURLHash = function ()
1296 {
1297 var hashParams = getState();
1298 var hashStringBuilder = [];
1299 var param;
1300
1301 for (param in hashParams)
1302 {
1303 if (hashParams[param] !== false)
1304 {
1305 hashStringBuilder.push(param + settings.hashParamSuffix + '=' + hashParams[param]);
1306 }
1307 }
1308
1309 return hashStringBuilder.join('&');
1310 };
1311
1312 // Returns the URL to the current state of the document viewer (so it should be an exact replica)
1313 var getCurrentURL = function ()
1314 {
1315 return location.protocol + '//' + location.host + location.pathname + '#' + getURLHash();
1316 };
1317
1318 // Called in init and when the orientation changes
1319 var adjustMobileWebkitDims = function ()
1320 {
1321 var outerOffset = $(settings.outerSelector).offset().top;
1322 settings.panelHeight = window.innerHeight - outerOffset - settings.viewerHeightPadding;
1323 settings.panelWidth = window.innerWidth - settings.viewerWidthPadding;
1324
1325 // $(settings.parentSelector).width(settings.panelWidth);
1326 // document.getElementById(settings.parentSelector.substring(1)).style.width = settings.panelWidth + "px";
1327 settings.parentSelector.style.width = settings.panelWidth + "px";
1328
1329 if (settings.enableAutoHeight)
1330 {
1331 document.getElementById(settings.ID + "outer").style.height = settings.panelHeight + "px";
1332 }
1333
1334 if (settings.enableAutoWidth)
1335 {
1336 document.getElementById(settings.ID + "outer").style.width = settings.panelWidth + "px";
1337 }
1338 };
1339
1340 // Will return true if something has changed, false otherwise
1341 var adjustBrowserDims = function ()
1342 {
1343 // Only resize if the browser viewport is too small
1344 var newHeight = $(settings.outerSelector).height();
1345 var newWidth = $(settings.parentSelector).width() - settings.scrollbarWidth;
1346 var outerOffset = $(settings.outerSelector).offset().top;
1347
1348 var windowHeight = window.innerHeight || document.documentElement.clientHeight;
1349 var windowWidth = window.innerWidth || document.documentElement.clientWidth;
1350 // 2 or 1 pixels for the border
1351 var desiredWidth = windowWidth - settings.viewerWidthPadding - settings.scrollbarWidth - 2;
1352 var desiredHeight = windowHeight - outerOffset - settings.viewerHeightPadding - 1;
1353
1354 if (settings.enableAutoHeight)
1355 {
1356 if (newHeight + outerOffset + 16 > window.innerHeight)
1357 {
1358 newHeight = desiredHeight;
1359 }
1360 else if (newHeight <= settings.originalHeight)
1361 {
1362 newHeight = Math.min(desiredHeight, settings.originalHeight);
1363 }
1364 }
1365
1366 if (settings.enableAutoWidth)
1367 {
1368 if (newWidth + 32 > window.innerWidth)
1369 {
1370 newWidth = desiredWidth;
1371 }
1372 else if (newWidth <= settings.originalWidth)
1373 {
1374 newWidth = Math.min(desiredWidth, settings.originalWidth);
1375 }
1376
1377 settings.parentSelector[0].style.width = newWidth + settings.scrollbarWidth;
1378 }
1379
1380 if (newWidth !== settings.panelWidth || newHeight !== settings.panelHeight)
1381 {
1382 var el = document.getElementById(settings.ID + "outer");
1383 el.style.height = newHeight + "px";
1384 el.style.width = newWidth + settings.scrollbarWidth + "px";
1385 settings.panelWidth = newWidth;
1386 settings.panelHeight = newHeight;
1387 return true;
1388 }
1389
1390 return false;
1391 };
1392
1393 // Update the panelHeight and panelWidth based on the window size
1394 var adjustFullscreenDims = function ()
1395 {
1396 settings.panelWidth = window.innerWidth - settings.scrollbarWidth;
1397 settings.panelHeight = window.innerHeight;
1398
1399 return true;
1400 };
1401
1402 var resizeViewer = function (newWidth, newHeight)
1403 {
1404 if (newWidth >= settings.minWidth)
1405 {
1406 settings.originalWidth = newWidth;
1407 $(settings.outerSelector).width(newWidth);
1408 document.getElementById(settings.ID + "outer").style.width = newWidth + "px";
1409
1410 settings.panelWidth = newWidth - settings.scrollbarWidth;
1411
1412 // Should also change the width of the container
1413 settings.parentSelector[0].style.width = newWidth + "px";
1414 }
1415
1416 if (newHeight >= settings.minHeight)
1417 {
1418 settings.originalHeight = newHeight;
1419 document.getElementById(settings.ID + "outer").style.height = newHeight + "px";
1420
1421 settings.panelHeight = newHeight;
1422 }
1423 };
1424
1425 // Binds most of the event handlers (some more in createToolbar)
1426 var handleEvents = function ()
1427 {
1428 // Create the fullscreen toggle icon if fullscreen is enabled
1429 if (settings.enableFullscreen)
1430 {
1431 // Event handler for fullscreen toggling
1432 $(settings.selector + 'fullscreen').click(function ()
1433 {
1434 toggleFullscreen();
1435 });
1436 }
1437
1438 // Change the cursor for dragging
1439 $(settings.innerSelector).mouseover(function ()
1440 {
1441 $(this).removeClass('diva-grabbing').addClass('diva-grab');
1442 });
1443
1444 $(settings.innerSelector).mouseout(function ()
1445 {
1446 $(this).removeClass('diva-grab');
1447 });
1448
1449 $(settings.innerSelector).mousedown(function ()
1450 {
1451 $(this).removeClass('diva-grab').addClass('diva-grabbing');
1452 });
1453
1454 $(settings.innerSelector).mouseup(function ()
1455 {
1456 $(this).removeClass('diva-grabbing').addClass('diva-grab');
1457 });
1458
1459 // Set drag scroll on first descendant of class dragger on both selected elements
1460 $(settings.outerSelector + ', ' + settings.innerSelector).dragscrollable({dragSelector: '.diva-dragger', acceptPropagatedEvent: true});
1461
1462 // Handle the scroll
1463 $(settings.outerSelector).scroll(function ()
1464 {
1465 settings.topScrollSoFar = document.getElementById(settings.ID + "outer").scrollTop;
1466 var direction = settings.topScrollSoFar - settings.previousTopScroll;
1467
1468 if (settings.inGrid)
1469 {
1470 adjustRows(direction);
1471 }
1472 else
1473 {
1474 adjustPages(direction);
1475 settings.leftScrollSoFar = $(this).scrollLeft();
1476 }
1477
1478 settings.previousTopScroll = settings.topScrollSoFar;
1479 });
1480
1481 // Double-click to zoom
1482 $(settings.outerSelector).on('dblclick', '.diva-document-page', function (event)
1483 {
1484 handleDocumentDoubleClick.call(this, event);
1485 });
1486
1487 // Handle the control key for macs (in conjunction with double-clicking)
1488 $(settings.outerSelector).on('contextmenu', '.diva-document-page', function (event)
1489 {
1490 if (event.ctrlKey)
1491 {
1492 // In Firefox, this doesn't trigger a double-click, so we apply one manually
1493 clearTimeout(settings.singleClickTimeout);
1494
1495 if (settings.singleClick)
1496 {
1497 handleDocumentDoubleClick.call(this, event);
1498 settings.singleClick = false;
1499 }
1500 else
1501 {
1502 settings.singleClick = true;
1503
1504 // Set it to false again after 500 milliseconds (standard double-click timeout)
1505 settings.singleClickTimeout = setTimeout(function ()
1506 {
1507 settings.singleClick = false;
1508 }, 500);
1509 }
1510
1511 return false;
1512 }
1513 });
1514
1515 $(settings.outerSelector).on('dblclick', '.diva-row', function (event)
1516 {
1517 handleGridDoubleClick.call(this, event);
1518 });
1519
1520 // Check if the user is on a iPhone or iPod touch or iPad
1521 if (settings.mobileWebkit)
1522 {
1523 // Prevent resizing (below from http://matt.might.net/articles/how-to-native-iphone-ipad-apps-in-javascript/)
1524 var toAppend = [];
1525 toAppend.push('<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1" />');
1526
1527 // Eliminate URL and button bars if added to home screen
1528 toAppend.push('<meta name="apple-mobile-web-app-capable" content="yes" />');
1529
1530 // Choose how to handle the phone status bar
1531 toAppend.push('<meta name="apple-mobile-web-app-status-bar-style" content="black" />');
1532 $('head').append(toAppend.join('\n'));
1533
1534 // Block the user from moving the window only if it's not integrated
1535 if (settings.blockMobileMove)
1536 {
1537 $('body').bind('touchmove', function (event)
1538 {
1539 var e = event.originalEvent;
1540 e.preventDefault();
1541
1542 return false;
1543 });
1544 }
1545
1546 // Allow pinch-zooming
1547 $('body').bind('gestureend', function (event)
1548 {
1549 var e = event.originalEvent;
1550
1551 if (!settings.scaleWait)
1552 {
1553 // Save the page we're currently on so we scroll there
1554 settings.goDirectlyTo = settings.currentPageIndex;
1555
1556 if (settings.inGrid)
1557 {
1558 settings.inGrid = false;
1559
1560 handleViewChange();
1561 }
1562 else
1563 {
1564 handlePinchZoom(e);
1565 }
1566 }
1567 return false;
1568 });
1569
1570 // Listen to orientation change event
1571 $(window).bind('orientationchange', function (event)
1572 {
1573 settings.orientationChange = true;
1574 adjustMobileWebkitDims();
1575
1576 // Reload the viewer to account for the resized viewport
1577 settings.goDirectlyTo = settings.currentPageIndex;
1578 loadViewer();
1579 });
1580
1581 // Inertial scrolling
1582 $(settings.outerSelector).kinetic();
1583 }
1584
1585 // Only check if either scrollBySpace or scrollByKeys is enabled
1586 if (settings.enableSpaceScroll || settings.enableKeyScroll)
1587 {
1588 var spaceKey = $.ui.keyCode.SPACE;
1589 var pageUpKey = $.ui.keyCode.PAGE_UP;
1590 var pageDownKey = $.ui.keyCode.PAGE_DOWN;
1591 var homeKey = $.ui.keyCode.HOME;
1592 var endKey = $.ui.keyCode.END;
1593
1594 // Catch the key presses in document
1595 $(document).keydown(function (event)
1596 {
1597 // Space or page down - go to the next page
1598 if ((settings.enableSpaceScroll && event.keyCode === spaceKey) || (settings.enableKeyScroll && event.keyCode === pageDownKey))
1599 {
1600 $(settings.outerSelector).scrollTop(settings.topScrollSoFar + settings.panelHeight);
1601 return false;
1602 }
1603
1604 // Page up - go to the previous page
1605 if (settings.enableKeyScroll && event.keyCode === pageUpKey)
1606 {
1607 $(settings.outerSelector).scrollTop(settings.topScrollSoFar - settings.panelHeight);
1608 return false;
1609 }
1610
1611 // Home key - go to the beginning of the document
1612 if (settings.enableKeyScroll && event.keyCode === homeKey)
1613 {
1614 $(settings.outerSelector).scrollTop(0);
1615 return false;
1616 }
1617
1618 // End key - go to the end of the document
1619 if (settings.enableKeyScroll && event.keyCode === endKey)
1620 {
1621 $(settings.outerSelector).scrollTop(settings.totalHeight);
1622 return false;
1623 }
1624 });
1625
1626 // Handle window resizing events
1627 if (!settings.mobileWebkit)
1628 {
1629 $(window).resize(function ()
1630 {
1631 var adjustSuccess = (settings.inFullscreen) ? adjustFullscreenDims() : adjustBrowserDims();
1632
1633 if (adjustSuccess)
1634 {
1635 // Cancel any previously-set resize timeouts
1636 clearTimeout(settings.resizeTimer);
1637
1638 settings.resizeTimer = setTimeout(function ()
1639 {
1640 settings.goDirectlyTo = settings.currentPageIndex;
1641 loadViewer();
1642 }, 200);
1643 }
1644 });
1645 }
1646 }
1647 };
1648
1649 // Handles all status updating etc (both fullscreen and not)
1650 var createToolbar = function () {
1651 // Prepare the HTML for the various components
1652 var gridIconHTML = (settings.enableGridIcon) ? '<div class="diva-grid-icon' + (settings.inGrid ? ' diva-in-grid' : '') + '" id="' + settings.ID + 'grid-icon" title="Toggle grid view"></div>' : '';
1653 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>' : '';
1654 var zoomSliderHTML = (settings.enableZoomSlider) ? '<div id="' + settings.ID + 'zoom-slider"></div>' : '';
1655 var gridSliderHTML = (settings.enableGridSlider) ? '<div id="' + settings.ID + 'grid-slider"></div>' : '';
1656 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>' : '';
1657 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>' : '';
1658 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>' : '';
1659 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>';
1660
1661 // If the viewer is specified to be "contained", we make room for the fullscreen icon
1662 var otherToolbarClass = '';
1663
1664 if (settings.contained)
1665 {
1666 // Make sure the container element does not have a static position
1667 // (Needed for the fullscreen icon to be contained)
1668 if ($(settings.parentSelector).css('position') === 'static')
1669 {
1670 $(settings.parentSelector).addClass('diva-relative-position');
1671 }
1672
1673 otherToolbarClass = ' diva-fullscreen-space';
1674
1675 // If enableAutoTitle is set to TRUE, move it down
1676 if (settings.enableAutoTitle)
1677 {
1678 $(settings.selector + 'fullscreen').addClass('diva-contained');
1679 }
1680 }
1681
1682 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>';
1683
1684 if (settings.toolbarParentSelector)
1685 {
1686 $(settings.toolbarParentSelector).prepend('<div id="' + settings.ID + 'tools" class="diva-tools">' + toolbarHTML + '</div>');
1687 }
1688 else
1689 {
1690 $(settings.parentSelector).prepend('<div id="' + settings.ID + 'tools" class="diva-tools">' + toolbarHTML + '</div>');
1691 }
1692
1693 // Create the zoom slider
1694 $(settings.selector + 'zoom-slider').slider({
1695 value: settings.zoomLevel,
1696 min: settings.minZoomLevel,
1697 max: settings.maxZoomLevel,
1698 step: 1,
1699 slide: function (event, ui)
1700 {
1701 var i = settings.currentPageIndex;
1702 settings.goDirectlyTo = i;
1703
1704 // Figure out the horizontal and vertical offsets
1705 // (Try to zoom in on the current center)
1706 var zoomRatio = Math.pow(2, ui.value - settings.zoomLevel);
1707 var innerWidth = settings.maxWidths[settings.zoomLevel] + settings.horizontalPadding * 2;
1708 var centerX = $(settings.outerSelector).scrollLeft() - (innerWidth - settings.panelWidth) / 2;
1709 settings.horizontalOffset = (innerWidth > settings.panelWidth) ? centerX * zoomRatio : 0;
1710 settings.verticalOffset = zoomRatio * ($(settings.outerSelector).scrollTop() - settings.heightAbovePages[i]);
1711
1712 handleZoom(ui.value);
1713 },
1714 change: function (event, ui)
1715 {
1716 if (ui.value !== settings.zoomLevel)
1717 {
1718 handleZoom(ui.value);
1719 }
1720 }
1721 });
1722
1723 // Create the grid slider
1724 $(settings.selector + 'grid-slider').slider(
1725 {
1726 value: settings.pagesPerRow,
1727 min: settings.minPagesPerRow,
1728 max: settings.maxPagesPerRow,
1729 step: 1,
1730 slide: function (event, ui)
1731 {
1732 handleGrid(ui.value);
1733 },
1734 change: function (event, ui)
1735 {
1736 if (ui.value !== settings.pagesPerRow)
1737 {
1738 handleGrid(ui.value);
1739 }
1740 }
1741 });
1742
1743 // Handle clicking of the grid icon
1744 $(settings.selector + 'grid-icon').click(function ()
1745 {
1746 toggleGrid();
1747 });
1748
1749 // Handle going to a specific page using the input box
1750 $(settings.selector + 'goto-page').submit(function ()
1751 {
1752 var desiredPage = parseInt($(settings.selector + 'goto-page-input').val(), 10);
1753 var pageIndex = desiredPage - 1;
1754
1755 if (!isPageValid(pageIndex))
1756 {
1757 alert("Invalid page number");
1758 }
1759 else
1760 {
1761 if (settings.inGrid)
1762 {
1763 gotoRow(pageIndex);
1764 }
1765 else
1766 {
1767 gotoPage(pageIndex, 0, 0);
1768 }
1769 }
1770
1771 // Prevent the default action of reloading the page
1772 return false;
1773 });
1774
1775 // Handle the creation of the link popup box
1776 $(settings.selector + 'link-icon').click(function ()
1777 {
1778 $('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>');
1779
1780 if (settings.inFullscreen)
1781 {
1782 $(settings.selector + 'link-popup').addClass('in-fullscreen');
1783 }
1784 else
1785 {
1786 // Calculate the left and top offsets
1787 // Compensate for border, popup width
1788 var leftOffset = $(settings.outerSelector).offset().left + settings.panelWidth;
1789 leftOffset += settings.scrollbarWidth - 240 - 1;
1790 var topOffset = $(settings.outerSelector).offset().top + 1;
1791
1792 $(settings.selector + 'link-popup').removeClass('in-fullscreen').css(
1793 {
1794 'top': topOffset + 'px',
1795 'left': leftOffset + 'px'
1796 });
1797 }
1798
1799 // Catch onmouseup events outside of this div
1800 $('body').mouseup(function (event)
1801 {
1802 var targetID = event.target.id;
1803
1804 if (targetID !== settings.ID + 'link-popup' && targetID !== settings.ID + 'link-popup-input')
1805 {
1806 $(settings.selector + 'link-popup').remove();
1807 }
1808 });
1809
1810 // Also delete it upon scroll and page up/down key events
1811 $(settings.outerSelector).scroll(function ()
1812 {
1813 $(settings.selector + 'link-popup').remove();
1814 });
1815 $(settings.selector + 'link-popup input').click(function ()
1816 {
1817 $(this).focus().select();
1818 });
1819 return false;
1820 });
1821
1822 // Show the relevant slider
1823 var currentSlider = (settings.inGrid) ? 'grid' : 'zoom';
1824 $(settings.selector + currentSlider + '-slider').show();
1825 $(settings.selector + currentSlider + '-slider-label').show();
1826
1827 var switchMode = function ()
1828 {
1829 // Switch from fullscreen to not
1830 $(settings.selector + 'tools').toggleClass('diva-fullscreen-tools');
1831
1832 if (!settings.inFullscreen)
1833 {
1834 // Leaving fullscreen
1835 $(settings.selector + 'tools-left').after($(settings.selector + 'tools-right'));
1836 $(settings.selector + 'tools-left').removeClass('in-fullscreen');
1837 }
1838 else
1839 {
1840 // Entering fullscreen
1841 $(settings.selector + 'tools-right').after($(settings.selector + 'tools-left'));
1842 $(settings.selector + 'tools-left').addClass('in-fullscreen');
1843 }
1844 };
1845
1846 var switchView = function ()
1847 {
1848 // Switch from grid to document view etc
1849 $(settings.selector + currentSlider + '-slider').hide();
1850 $(settings.selector + currentSlider + '-slider-label').hide();
1851 currentSlider = (settings.inGrid) ? 'grid' : 'zoom';
1852 $(settings.selector + currentSlider + '-slider').show();
1853 $(settings.selector + currentSlider + '-slider-label').show();
1854
1855 // Also change the image for the grid icon
1856 $(settings.selector + 'grid-icon').toggleClass('diva-in-grid');
1857 };
1858
1859 var toolbar =
1860 {
1861 updateCurrentPage: function ()
1862 {
1863 $(settings.selector + 'current-page').text(settings.currentPageIndex + 1);
1864 },
1865 setNumPages: function (newNumber)
1866 {
1867 $(settings.selector + 'num-pages').text(newNumber);
1868 },
1869 updateZoomSlider: function ()
1870 {
1871 // Update the position of the handle within the slider
1872 if (settings.zoomLevel !== $(settings.selector + 'zoom-slider').slider('value'))
1873 {
1874 $(settings.selector + 'zoom-slider').slider(
1875 {
1876 value: settings.zoomLevel
1877 });
1878 }
1879
1880 // Update the slider label
1881 $(settings.selector + 'zoom-level').text(settings.zoomLevel);
1882 },
1883 updateGridSlider: function ()
1884 {
1885 // Update the position of the handle within the slider
1886 if (settings.pagesPerRow !== $(settings.selector + 'grid-slider').slider('value'))
1887 {
1888 $(settings.selector + 'grid-slider').slider(
1889 {
1890 value: settings.pagesPerRow
1891 });
1892 }
1893
1894 // Update the slider label
1895 $(settings.selector + 'pages-per-row').text(settings.pagesPerRow);
1896 },
1897 switchView: switchView,
1898 switchMode: switchMode
1899 };
1900 return toolbar;
1901 };
1902
1903 var initPlugins = function ()
1904 {
1905 if (window.divaPlugins)
1906 {
1907 var pageTools = [];
1908
1909 // Add all the plugins that have not been explicitly disabled to settings.plugins
1910 $.each(window.divaPlugins, function (index, plugin)
1911 {
1912 var pluginProperName = plugin.pluginName[0].toUpperCase() + plugin.pluginName.substring(1);
1913
1914 if (settings['enable' + pluginProperName])
1915 {
1916 // Call the init function and check return value
1917 var enablePlugin = plugin.init(settings, self);
1918
1919 // If int returns false, consider the plugin disabled
1920 if (!enablePlugin)
1921 {
1922 return;
1923 }
1924
1925 // If the title text is undefined, use the name of the plugin
1926 var titleText = plugin.titleText || pluginProperName + " plugin";
1927
1928 // Create the pageTools bar if handleClick is set to a function
1929 if (typeof plugin.handleClick === 'function')
1930 {
1931 pageTools.push('<div class="diva-' + plugin.pluginName + '-icon" title="' + titleText + '"></div>');
1932
1933 // Delegate the click event - pass it the settings
1934 $(settings.outerSelector).delegate('.diva-' + plugin.pluginName + '-icon', 'click', function (event)
1935 {
1936 plugin.handleClick.call(this, event, settings);
1937 });
1938 }
1939
1940 // Add it to settings.plugins so it can be used later
1941 settings.plugins.push(plugin);
1942 }
1943 });
1944
1945 // Save the page tools bar so it can be added for each page
1946 if (pageTools.length)
1947 {
1948 settings.pageTools = '<div class="diva-page-tools">' + pageTools.join('') + '</div>';
1949 }
1950 }
1951 };
1952
1953 var hideThrobber = function ()
1954 {
1955 // Clear the timeout, if it hasn't executed yet
1956 clearTimeout(settings.throbberTimeoutID);
1957
1958 // Hide the throbber if it has already executed
1959 $(settings.selector + 'throbber').hide();
1960 };
1961
1962 var setupViewer = function ()
1963 {
1964 // Create the throbber element
1965 var throbberHTML = '<div id="' + settings.ID + 'throbber" class="diva-throbber"></div>';
1966 $(settings.outerSelector).append(throbberHTML);
1967
1968 // If the request hasn't completed after a specified time, show it
1969 settings.throbberTimeoutID = setTimeout(function ()
1970 {
1971 $(settings.selector + 'throbber').show();
1972 }, settings.throbberTimeout);
1973
1974 $.ajax({
1975 url: settings.objectData,
1976 cache: true,
1977 dataType: 'json',
1978 error: function (jqxhr, status, error)
1979 {
1980 hideThrobber();
1981
1982 // Show a basic error message within the document viewer pane
1983 $(settings.outerSelector).text("Invalid URL. Error code: " + status + " " + error);
1984 },
1985 success: function (data, status, jqxhr)
1986 {
1987 hideThrobber();
1988
1989 // Save all the data we need
1990 settings.pages = data.pgs;
1991 settings.maxRatio = data.dims.max_ratio;
1992 settings.minRatio = data.dims.min_ratio;
1993 settings.itemTitle = data.item_title;
1994 settings.numPages = data.pgs.length;
1995
1996 // These are arrays, the index corresponding to the zoom level
1997 settings.maxWidths = data.dims.max_w;
1998 settings.averageWidths = data.dims.a_wid;
1999 settings.averageHeights = data.dims.a_hei;
2000 settings.totalHeights = data.dims.t_hei;
2001
2002 // Make sure the set max and min values are valid
2003 settings.realMaxZoom = data.max_zoom;
2004 settings.maxZoomLevel = (settings.maxZoomLevel >= 0 && settings.maxZoomLevel <= data.max_zoom) ? settings.maxZoomLevel : data.max_zoom;
2005 settings.minZoomLevel = (settings.minZoomLevel >= 0 && settings.minZoomLevel <= settings.maxZoomLevel) ? settings.minZoomLevel : 0;
2006 settings.minPagesPerRow = Math.max(2, settings.minPagesPerRow);
2007 settings.maxPagesPerRow = Math.max(settings.minPagesPerRow, settings.maxPagesPerRow);
2008
2009 // Check that the desired page is in range
2010 if (settings.enableFilename)
2011 {
2012 var iParam = $.getHashParam('i' + settings.hashParamSuffix);
2013 var iParamPage = getPageIndex(iParam);
2014
2015 if (isPageValid(iParamPage))
2016 {
2017 settings.goDirectlyTo = iParamPage;
2018 }
2019 }
2020 else
2021 {
2022 // Not using the i parameter, check the p parameter
2023 // Subtract 1 to get the page index
2024 var pParam = parseInt($.getHashParam('p' + settings.hashParamSuffix), 10) - 1;
2025
2026 if (isPageValid(pParam))
2027 {
2028 settings.goDirectlyTo = pParam;
2029 }
2030 }
2031
2032 // Execute the setup hook for each plugin (if defined)
2033 $.each(settings.plugins, function (index, plugin)
2034 {
2035 executeCallback(plugin.setupHook, settings);
2036 });
2037
2038 // Create the toolbar and display the title + total number of pages
2039 if (settings.enableToolbar)
2040 {
2041 settings.toolbar = createToolbar();
2042 Events.subscribe("VisiblePageDidChange", settings.toolbar.updateCurrentPage);
2043 Events.subscribe("ModeDidSwitch", settings.toolbar.switchMode);
2044 Events.subscribe("ViewDidSwitch", settings.toolbar.switchView);
2045 Events.subscribe("ZoomLevelDidChange", settings.toolbar.updateZoomSlider);
2046 Events.subscribe("GridRowNumberDidChange", settings.toolbar.updateGridSlider);
2047 }
2048
2049 $(settings.selector + 'current label').text(settings.numPages);
2050
2051 if (settings.enableAutoTitle)
2052 {
2053 $(settings.parentSelector).prepend('<div id="' + settings.ID + 'title" class="diva-title">' + settings.itemTitle + '</div>');
2054 }
2055
2056 // Adjust the document panel dimensions for touch devices
2057 if (settings.mobileWebkit)
2058 {
2059 adjustMobileWebkitDims();
2060 }
2061 else
2062 {
2063 settings.originalWidth = $(settings.parentSelector).width() - settings.scrollbarWidth;
2064 settings.originalHeight = $(settings.outerSelector).height();
2065 adjustBrowserDims();
2066 }
2067
2068 // Calculate the viewer x and y offsets
2069 var viewerOffset = $(settings.outerSelector).offset();
2070 settings.viewerXOffset = viewerOffset.left;
2071 settings.viewerYOffset = viewerOffset.top;
2072
2073 if (settings.inFullscreen)
2074 {
2075 handleModeChange(false);
2076 }
2077 else
2078 {
2079 loadViewer();
2080 }
2081
2082 // Execute the callback
2083 executeCallback(settings.onReady, settings);
2084 Events.publish("ViewerHasFinishedLoading", [settings]);
2085
2086 // signal that everything should be set up and ready to go.
2087 settings.loaded = true;
2088 }
2089 });
2090 };
2091
2092 var checkLoaded = function()
2093 {
2094 if (!settings.loaded)
2095 {
2096 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.");
2097 return false;
2098 }
2099 return true;
2100 };
2101
2102 var init = function ()
2103 {
2104 // First figure out the width of the scrollbar in this browser
2105 settings.scrollbarWidth = $.getScrollbarWidth();
2106
2107 // If window.orientation is defined, then it's probably mobileWebkit
2108 settings.mobileWebkit = window.orientation !== undefined;
2109
2110 // Generate an ID that can be used as a prefix for all the other IDs
2111 settings.ID = $.generateId('diva-');
2112 settings.selector = '#' + settings.ID;
2113
2114 // Figure out the hashParamSuffix from the ID
2115 var divaNumber = parseInt(settings.ID, 10);
2116
2117 if (divaNumber > 1)
2118 {
2119 // If this is document viewer #1, don't use a suffix; otherwise, use the document viewer number
2120 settings.hashParamSuffix = divaNumber;
2121 }
2122
2123 // Since we need to reference these two a lot
2124 settings.outerSelector = settings.selector + 'outer';
2125 settings.innerSelector = settings.selector + 'inner';
2126
2127 // Create the inner and outer panels
2128 $(settings.parentSelector).append('<div id="' + settings.ID + 'outer" class="diva-outer"></div>');
2129 $(settings.outerSelector).append('<div id="' + settings.ID + 'inner" class="diva-inner diva-dragger"></div>');
2130
2131 // Create the fullscreen icon
2132 if (settings.enableFullscreen)
2133 {
2134 $(settings.parentSelector).prepend('<div id="' + settings.ID + 'fullscreen" class="diva-fullscreen-icon" title="Toggle fullscreen mode"></div>');
2135 }
2136
2137 // First, n - check if it's in range
2138 var nParam = parseInt($.getHashParam('n' + settings.hashParamSuffix), 10);
2139
2140 if (nParam >= settings.minPagesPerRow && nParam <= settings.maxPagesPerRow)
2141 {
2142 settings.pagesPerRow = nParam;
2143 }
2144
2145 // Now z - check that it's in range
2146 var zParam = $.getHashParam('z' + settings.hashParamSuffix);
2147
2148 if (zParam !== '')
2149 {
2150 // If it's empty, we don't want to change the default zoom level
2151 zParam = parseInt(zParam, 10);
2152
2153 // Can't check if it exceeds the max zoom level or not because that data is not available yet ...
2154 if (zParam >= settings.minZoomLevel)
2155 {
2156 settings.zoomLevel = zParam;
2157 }
2158 }
2159
2160 // y - vertical offset from the top of the relevant page
2161 var yParam = parseInt($.getHashParam('y' + settings.hashParamSuffix), 10);
2162
2163 if (!isNaN(yParam))
2164 {
2165 settings.verticalOffset = yParam;
2166 }
2167
2168 // x - horizontal offset from the center of the page
2169 var xParam = parseInt($.getHashParam('x' + settings.hashParamSuffix), 10);
2170
2171 if (!isNaN(xParam))
2172 {
2173 settings.horizontalOffset = xParam;
2174 }
2175
2176 // If the "fullscreen" hash param is true, go to fullscreen initially
2177 // If the grid hash param is true, go to grid view initially
2178 var gridParam = $.getHashParam('g' + settings.hashParamSuffix);
2179 var goIntoGrid = gridParam === 'true';
2180 var fullscreenParam = $.getHashParam('f' + settings.hashParamSuffix);
2181 var goIntoFullscreen = fullscreenParam === 'true';
2182
2183 settings.inGrid = (settings.inGrid && gridParam !== 'false') || goIntoGrid;
2184 settings.inFullscreen = (settings.inFullscreen && fullscreenParam !== 'false') || goIntoFullscreen;
2185
2186 // Store the height and width of the viewer (the outer div), if present
2187 var desiredHeight = parseInt($.getHashParam('h' + settings.hashParamSuffix), 10);
2188 var desiredWidth = parseInt($.getHashParam('w' + settings.hashParamSuffix), 10);
2189
2190 // Store the minimum and maximum height too
2191 settings.minHeight = parseInt($(settings.outerSelector).css('min-height'), 10);
2192 settings.minWidth = parseInt($(settings.outerSelector).css('min-width'), 10);
2193
2194 // Just call resize, it'll take care of bounds-checking etc
2195 if (desiredHeight > 0 || desiredWidth > 0)
2196 {
2197 resizeViewer(desiredWidth, desiredHeight);
2198 }
2199
2200 // Do the initial AJAX request and viewer loading
2201 setupViewer();
2202
2203 // Do all the plugin initialisation
2204 initPlugins();
2205
2206 handleEvents();
2207 };
2208
2209 // Call the init function when this object is created.
2210 init();
2211
2212 /* PUBLIC FUNCTIONS
2213 ===============================================
2214 */
2215
2216 // Returns the title of the document, based on the directory name
2217 this.getItemTitle = function ()
2218 {
2219 return settings.itemTitle;
2220 };
2221
2222 // Go to a particular page by its page number (with indexing starting at 1)
2223 // returns True if the page number passed is valid; false if it is not.
2224 this.gotoPageByNumber = function (pageNumber)
2225 {
2226 var pageIndex = pageNumber - 1;
2227 if (isPageValid(pageIndex))
2228 {
2229 gotoPage(pageIndex, 0, 0);
2230 return true;
2231 }
2232 return false;
2233 };
2234
2235 // Go to a particular page (with indexing starting at 0)
2236 // returns True if the page index is valid; false if it is not.
2237 this.gotoPageByIndex = function (pageIndex)
2238 {
2239 if (isPageValid(pageIndex))
2240 {
2241 gotoPage(pageIndex, 0, 0);
2242 return true;
2243 }
2244 return false;
2245 };
2246
2247 // Returns the page index (with indexing starting at 0)
2248 this.getCurrentPage = function ()
2249 {
2250 console.warn("Deprecated. Use getCurrentPageIndex instead.");
2251 return settings.currentPageIndex;
2252 };
2253
2254 this.getNumberOfPages = function()
2255 {
2256 if (!checkLoaded())
2257 {
2258 return false;
2259 }
2260
2261 return settings.numPages;
2262 }
2263
2264 // Returns the dimensions of a given page index at a given zoom level
2265 this.getPageDimensionsAtZoomLevel = function(pageIdx, zoomLevel)
2266 {
2267 if (!checkLoaded())
2268 {
2269 return false;
2270 }
2271
2272 var zoomLevel = zoomLevel - 1; // zoom levels are 1-based, but our array is 0-based;
2273 var pg = settings.pages[pageIdx];
2274 var pgAtZoom = pg.d[parseInt(zoomLevel, 10)];
2275 return {'width': pgAtZoom.w, 'height': pgAtZoom.h}
2276 };
2277
2278 // Returns the dimensions of the current page at the current zoom level
2279 this.getCurrentPageDimensionsAtCurrentZoomLevel = function()
2280 {
2281 return this.getPageDimensionsAtZoomLevel(settings.currentPageIndex, settings.zoomLevel);
2282 };
2283
2284 this.isReady = function()
2285 {
2286 return settings.loaded;
2287 };
2288
2289 this.getCurrentPageIndex = function ()
2290 {
2291 return settings.currentPageIndex;
2292 };
2293
2294 this.getCurrentPageFilename = function ()
2295 {
2296 return settings.pages[settings.currentPageIndex].f;
2297 };
2298
2299 this.getCurrentPageNumber = function ()
2300 {
2301 return settings.currentPageIndex + 1;
2302 };
2303
2304 // Returns the current zoom level
2305 this.getZoomLevel = function ()
2306 {
2307 return settings.zoomLevel;
2308 };
2309
2310 // gets the maximum zoom level for the entire document
2311 this.getMaxZoomLevel = function ()
2312 {
2313 return settings.maxZoomLevel;
2314 };
2315
2316 // gets the max zoom level for a given page
2317 this.getMaxZoomLevelForPage = function(pageIdx)
2318 {
2319 if (!checkLoaded)
2320 {
2321 return false;
2322 }
2323
2324 return settings.pages[pageIdx].m;
2325 }
2326
2327 this.getMinZoomLevel = function ()
2328 {
2329 return settings.minZoomLevel;
2330 };
2331
2332 // Use the provided zoom level (will check for validity first)
2333 // Returns false if the zoom level is invalid, true otherwise
2334 this.setZoomLevel = function (zoomLevel)
2335 {
2336 if (settings.inGrid)
2337 {
2338 toggleGrid();
2339 }
2340
2341 return handleZoom(zoomLevel);
2342 };
2343
2344 // Zoom in. Will return false if it's at the maximum zoom
2345 this.zoomIn = function ()
2346 {
2347 return this.setZoomLevel(settings.zoomLevel + 1);
2348 };
2349
2350 // Zoom out. Will return false if it's at the minimum zoom
2351 this.zoomOut = function ()
2352 {
2353 return this.setZoomLevel(settings.zoomLevel - 1);
2354 };
2355
2356 // Uses the isVerticallyInViewport() function, but relative to a page
2357 // Check if something (e.g. a highlight box on a particular page) is visible
2358 this.inViewport = function (pageNumber, topOffset, height)
2359 {
2360 var pageIndex = pageNumber - 1;
2361 var top = settings.heightAbovePages[pageIndex] + topOffset;
2362 var bottom = top + height;
2363
2364 return isVerticallyInViewport(top, bottom);
2365 };
2366
2367 // Toggle fullscreen mode
2368 this.toggleFullscreenMode = function ()
2369 {
2370 toggleFullscreen();
2371 };
2372
2373 // Enter fullscreen mode if currently not in fullscreen mode
2374 // Returns false if in fullscreen mode initially, true otherwise
2375 // This function will work even if enableFullscreen is set to false
2376 this.enterFullscreenMode = function ()
2377 {
2378 if (!settings.inFullscreen)
2379 {
2380 toggleFullscreen();
2381 return true;
2382 }
2383
2384 return false;
2385 };
2386
2387 // Leave fullscreen mode if currently in fullscreen mode
2388 // Returns true if in fullscreen mode intitially, false otherwise
2389 this.leaveFullscreenMode = function ()
2390 {
2391 if (settings.inFullscreen)
2392 {
2393 toggleFullscreen();
2394 return true;
2395 }
2396
2397 return false;
2398 };
2399
2400 // Toggle grid view
2401 this.toggleGridView = function ()
2402 {
2403 toggleGrid();
2404 };
2405
2406 // Enter grid view if currently not in grid view
2407 // Returns false if in grid view initially, true otherwise
2408 this.enterGridView = function ()
2409 {
2410 if (!settings.inGrid) {
2411 toggleGrid();
2412 return true;
2413 }
2414
2415 return false;
2416 };
2417
2418 // Leave grid view if currently in grid view
2419 // Returns true if in grid view initially, false otherwise
2420 this.leaveGridView = function ()
2421 {
2422 if (settings.inGrid)
2423 {
2424 toggleGrid();
2425 return true;
2426 }
2427
2428 return false;
2429 };
2430
2431 // Jump to a page based on its filename
2432 // Returns true if successful and false if the filename is invalid
2433 this.gotoPageByName = function (filename)
2434 {
2435 var pageIndex = getPageIndex(filename);
2436 if (isPageValid(pageIndex))
2437 {
2438 gotoPage(pageIndex, 0, 0);
2439 return true;
2440 }
2441
2442 return false;
2443 };
2444
2445 // Get the page index (0-based) corresponding to a given filename
2446 // If the page index doesn't exist, this will return -1
2447 this.getPageIndex = function (filename)
2448 {
2449 return getPageIndex(filename);
2450 };
2451
2452 // Get the current URL (exposes the private method)
2453 this.getCurrentURL = function ()
2454 {
2455 return getCurrentURL();
2456 };
2457
2458 // Get the hash part only of the current URL (without the leading #)
2459 this.getURLHash = function ()
2460 {
2461 return getURLHash();
2462 };
2463
2464 // Get an object representing the state of this diva instance (for setState)
2465 this.getState = function ()
2466 {
2467 return getState();
2468 };
2469
2470 // Get the instance selector for this instance, since it's auto-generated.
2471 this.getInstanceSelector = function ()
2472 {
2473 return settings.selector;
2474 };
2475
2476 // Get the instance ID -- essentially the selector without the leading '#'.
2477 this.getInstanceId = function()
2478 {
2479 return settings.ID;
2480 };
2481
2482 this.getSettings = function()
2483 {
2484 return settings;
2485 };
2486
2487 // Align this diva instance with a state object (as returned by getState)
2488 this.setState = function (state)
2489 {
2490 var pageIndex;
2491
2492 // If we need to resize the viewer, do that first
2493 resizeViewer(state.w, state.h);
2494
2495 // Only change settings.goDirectlyTo if state.i or state.p is valid
2496 pageIndex = getPageIndex(state.i);
2497
2498 if (isPageValid(pageIndex))
2499 {
2500 settings.goDirectlyTo = pageIndex;
2501 }
2502 else if (isPageValid(state.p))
2503 {
2504 settings.goDirectlyTo = state.p;
2505 }
2506
2507 settings.horizontalOffset = parseInt(state.x, 10);
2508 settings.verticalOffset = parseInt(state.y, 10);
2509
2510 // Only change the zoom if state.z is valid
2511 if (state.z >= settings.minZoomLevel && state.z <= settings.maxZoomLevel)
2512 {
2513 settings.zoomLevel = state.z;
2514 }
2515
2516 // Only change the pages per row setting if state.n is valid
2517 if (state.n >= settings.minPagesPerRow && state.n <= settings.maxPagesPerRow)
2518 {
2519 settings.pagesPerRow = state.n;
2520 }
2521
2522 if (settings.inFullscreen !== state.f)
2523 {
2524 // The parameter determines if we need to change the view as well
2525 settings.inFullscreen = state.f;
2526 handleModeChange(settings.inGrid !== state.g);
2527 }
2528 else
2529 {
2530 // Don't need to change the mode, may need to change view
2531 if (settings.inGrid !== state.g)
2532 {
2533 settings.inGrid = state.g;
2534 handleViewChange();
2535 }
2536 else
2537 {
2538 // Reload the viewer, just in case
2539 loadViewer();
2540 }
2541 }
2542 };
2543
2544 // Resizes the outer div to the specified width and height
2545 this.resize = function (newWidth, newHeight)
2546 {
2547 resizeViewer(newWidth, newHeight);
2548 loadViewer();
2549 };
2550
2551 // Destroys this instance, tells plugins to do the same (for testing)
2552 this.destroy = function ()
2553 {
2554 // Removes the hide-scrollbar class from the body
2555 $('body').removeClass('diva-hide-scrollbar');
2556
2557 // Empty the parent container and remove any diva-related data
2558 $(settings.parentSelector).empty().removeData('diva');
2559
2560 // Call the destroy function for all the enabled plugins (if it exists)
2561 $.each(settings.plugins, function (index, plugin)
2562 {
2563 executeCallback(plugin.destroy);
2564 });
2565
2566 // Remove any additional styling on the parent element
2567 $(settings.parentSelector).removeAttr('style').removeAttr('class');
2568 };
2569 };
2570
2571 $.fn.diva = function (options)
2572 {
2573 return this.each(function ()
2574 {
2575 var element = $(this);
2576
2577 // Return early if this element already has a plugin instance
2578 if (element.data('diva'))
2579 {
2580 return;
2581 }
2582
2583 // Save the reference to the container element
2584 options.parentSelector = element;
2585
2586 // Otherwise, instantiate the document viewer
2587 var diva = new Diva(this, options);
2588 element.data('diva', diva);
2589 });
2590 };
2591
2592 })(jQuery);