/**
* PanoView Plugin Script
*
* This is a modified version of
* Panoramic JavaScript Image Viewer (PanoJS) 1.0.2
*
*
* Orginal Description and copyright notices follow:
*
* Generates a draggable and zoomable viewer for images that would
* be otherwise too large for a browser window. Examples would include
* maps or high resolution document scans.
*
* Images must be precut into tiles, such as by the accompanying tilemaker.py
* python library.
*
*
*
* The "well" node is where generated IMG elements are appended. It
* should have the CSS rule "overflow: hidden", to occlude image tiles
* that have scrolled out of view.
*
* The "surface" node is the transparent mouse-responsive layer of the
* image viewer, and should match the well in size.
*
* var viewerBean = new PanoJS(element, 'tiles', 256, 3, 1);
*
* To disable the image toolbar in IE, be sure to add the following:
*
*
* Copyright (c) 2005 Michal Migurski
* Dan Allen
*
* Redistribution and use in source form, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Michal Migurski
* @author Dan Allen
*
* NOTE: if artifacts are appearing, then positions include half-pixels
* TODO: additional jsdoc and package jsmin
* TODO: Tile could be an object
*/
function PanoJS(viewer, options) {
// listeners that are notified on a move (pan) event
this.viewerMovedListeners = [];
// listeners that are notified on a zoom event
this.viewerZoomedListeners = [];
if (typeof viewer == 'string') {
this.viewer = document.getElementById(viewer);
}
else {
this.viewer = viewer;
}
if (typeof options == 'undefined') {
options = {};
}
this.tileUrlProvider = new PanoJS.TileUrlProvider(options.tileBaseUri, options.image);
this.tileSize = (options.tileSize ? options.tileSize : PanoJS.TILE_SIZE);
// assign and do some validation on the zoom levels to ensure sanity
this.zoomLevel = (typeof options.initialZoom == 'undefined' ? -1 : parseInt(options.initialZoom));
this.maxZoomLevel = (typeof options.maxZoom == 'undefined' ? 0 : Math.abs(parseInt(options.maxZoom)));
if (this.zoomLevel > this.maxZoomLevel) {
this.zoomLevel = this.maxZoomLevel;
}
this.imgsize = { 'x': 0, 'y': 0 };
this.imgsize.x = (typeof options.imageWidth == 'undefined' ? -1 : parseInt(options.imageWidth));
this.imgsize.y = (typeof options.imageHeight == 'undefined' ? -1 : parseInt(options.imageHeight));
this.scaleSize = { 'x': 0, 'y': 0 };
this.initialized = false;
this.surface = null;
this.well = null;
this.width = 0;
this.height = 0;
this.top = 0;
this.left = 0;
this.x = 0;
this.y = 0;
this.border = -1;
this.mark = { 'x': 0, 'y': 0 };
this.pressed = false;
this.tiles = [];
this.cache = {};
var blankTile = options.blankTile ? options.blankTile : PanoJS.BLANK_TILE_IMAGE;
var loadingTile = options.loadingTile ? options.loadingTile : PanoJS.LOADING_TILE_IMAGE;
this.cache['blank'] = new Image();
this.cache['blank'].src = blankTile;
if (blankTile != loadingTile) {
this.cache['loading'] = new Image();
this.cache['loading'].src = loadingTile;
}
else {
this.cache['loading'] = this.cache['blank'];
}
// employed to throttle the number of redraws that
// happen while the mouse is moving
this.moveCount = 0;
this.slideMonitor = 0;
this.slideAcceleration = 0;
// add to viewer registry
PanoJS.VIEWERS[PanoJS.VIEWERS.length] = this;
}
// project specific variables
PanoJS.PROJECT_NAME = 'PanoJS';
PanoJS.PROJECT_VERSION = '1.0.0';
PanoJS.REVISION_FLAG = '';
// CSS definition settings
PanoJS.SURFACE_STYLE_CLASS = 'surface';
PanoJS.WELL_STYLE_CLASS = 'well';
PanoJS.CONTROLS_STYLE_CLASS = 'controls';
PanoJS.TILE_STYLE_CLASS = 'tile';
// language settings
PanoJS.MSG_BEYOND_MIN_ZOOM = 'Cannot zoom out past the current level.';
PanoJS.MSG_BEYOND_MAX_ZOOM = 'Cannot zoom in beyond the current level.';
// defaults if not provided as constructor options
PanoJS.TILE_BASE_URI = 'tiles';
PanoJS.TILE_PREFIX = 'tile-';
PanoJS.TILE_EXTENSION = 'jpg';
PanoJS.TILE_SIZE = 256;
PanoJS.BLANK_TILE_IMAGE = 'blank.gif';
PanoJS.LOADING_TILE_IMAGE = 'blank.gif';
PanoJS.INITIAL_PAN = { 'x': .5, 'y': .5 };
PanoJS.USE_LOADER_IMAGE = true;
PanoJS.USE_SLIDE = true;
PanoJS.USE_KEYBOARD = true;
// performance tuning variables
PanoJS.MOVE_THROTTLE = 3;
PanoJS.SLIDE_DELAY = 40;
PanoJS.SLIDE_ACCELERATION_FACTOR = 5;
// the following are calculated settings
PanoJS.DOM_ONLOAD = (navigator.userAgent.indexOf('KHTML') >= 0 ? false : true);
PanoJS.GRAB_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'pointer' : (document.attachEvent ? 'url(grab.cur)' : '-moz-grab'));
PanoJS.GRABBING_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'move' : (document.attachEvent ? 'url(grabbing.cur)' : '-moz-grabbing'));
// registry of all known viewers
PanoJS.VIEWERS = [];
// utility functions
PanoJS.isInstance = function (object, clazz) {
// FIXME: can this just be replaced with instanceof operator? It has been reported that __proto__ is specific to Netscape
while (object != null) {
if (object == clazz.prototype) {
return true;
}
object = object.__proto__;
}
return false;
};
PanoJS.prototype = {
/**
* Resize the viewer to fit snug inside the browser window (or frame),
* spacing it from the edges by the specified border.
*
* This method should be called prior to init()
* FIXME: option to hide viewer to prevent scrollbar interference
*/
fitToWindow: function (border) {
if (typeof border != 'number' || border < 0) {
border = 0;
}
this.border = border;
var calcWidth = 0;
var calcHeight = 0;
if (window.innerWidth) {
calcWidth = window.innerWidth;
calcHeight = window.innerHeight;
}
else {
calcWidth = (document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth);
calcHeight = (document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight);
}
calcWidth = Math.max(calcWidth - 2 * border, 0);
calcHeight = Math.max(calcHeight - 2 * border, 0);
if (calcWidth % 2) {
calcWidth--;
}
if (calcHeight % 2) {
calcHeight--;
}
this.width = calcWidth;
this.height = calcHeight;
this.viewer.style.width = this.width + 'px';
this.viewer.style.height = this.height + 'px';
this.viewer.style.top = border + 'px';
this.viewer.style.left = border + 'px';
},
init: function () {
if (document.attachEvent) {
document.body.ondragstart = function () {
return false;
}
}
if (this.width == 0 && this.height == 0) {
this.width = this.viewer.offsetWidth;
this.height = this.viewer.offsetHeight;
}
this.calcScale();
var fullSize = this.tileSize;
// explicit set of zoom level
if (this.zoomLevel >= 0 && this.zoomLevel <= this.maxZoomLevel) {
fullSize = this.tileSize * Math.pow(2, this.zoomLevel);
}
// calculate the zoom level based on what fits best in window
else {
this.zoomLevel = -1;
fullSize = this.tileSize / 2;
do {
this.zoomLevel += 1;
fullSize *= 2;
} while (fullSize < Math.max(this.width, this.height));
// take into account picture smaller than window size
if (this.zoomLevel > this.maxZoomLevel) {
var diff = this.zoomLevel - this.maxZoomLevel;
this.zoomLevel = this.maxZoomLevel;
fullSize /= Math.pow(2, diff);
}
}
//fixme fullsize from above not used anymore?
// calculate the center
this.x = Math.floor(this.scaleSize.x * 0.5 - this.width * 0.5) * -1;
this.y = Math.floor(this.scaleSize.y * 0.5 - this.height * 0.5) * -1;
// offset of viewer in the window
for (var node = this.viewer; node; node = node.offsetParent) {
this.top += node.offsetTop;
this.left += node.offsetLeft;
}
for (var child = this.viewer.firstChild; child; child = child.nextSibling) {
if (child.className == PanoJS.SURFACE_STYLE_CLASS) {
this.surface = child;
child.backingBean = this;
// empty surface
child.innerHTML = '';
}
else if (child.className == PanoJS.WELL_STYLE_CLASS) {
this.well = child;
child.backingBean = this;
}
else if (child.className == PanoJS.CONTROLS_STYLE_CLASS) {
child.style.display = '';
for (var control = child.firstChild; control; control = control.nextSibling) {
if (control.className) {
control.onclick = PanoJS[control.className + 'Handler'];
}
}
}
}
this.viewer.backingBean = this;
this.surface.style.cursor = PanoJS.GRAB_MOUSE_CURSOR;
this.prepareTiles();
this.initialized = true;
},
calcScale: function () {
var inv = Math.max(this.imgsize.x, this.imgsize.y) /
(this.tileSize * Math.pow(2, this.zoomLevel));
this.scaleSize.x = this.imgsize.x / inv;
this.scaleSize.y = this.imgsize.y / inv;
},
prepareTiles: function () {
var rows = Math.ceil(this.height / this.tileSize) + 1;
var cols = Math.ceil(this.width / this.tileSize) + 1;
for (var c = 0; c < cols; c++) {
var tileCol = [];
for (var r = 0; r < rows; r++) {
/**
* element is the DOM element associated with this tile
* posx/posy are the pixel offsets of the tile
* xIndex/yIndex are the index numbers of the tile segment
* qx/qy represents the quadrant location of the tile
*/
var tile = {
'element': null,
'posx': 0,
'posy': 0,
'xIndex': c,
'yIndex': r,
'qx': c,
'qy': r
};
tileCol.push(tile);
}
this.tiles.push(tileCol);
}
this.surface.onmousedown = PanoJS.mousePressedHandler;
this.surface.onmouseup = this.surface.onmouseout = PanoJS.mouseReleasedHandler;
this.surface.ondblclick = PanoJS.doubleClickHandler;
if (PanoJS.USE_KEYBOARD) {
window.onkeypress = PanoJS.keyboardMoveHandler;
window.onkeydown = PanoJS.keyboardZoomHandler;
}
this.positionTiles();
},
/**
* Position the tiles based on the x, y coordinates of the
* viewer, taking into account the motion offsets, which
* are calculated by a motion event handler.
*/
positionTiles: function (motion, reset) {
// default to no motion, just setup tiles
if (typeof motion == 'undefined') {
motion = { 'x': 0, 'y': 0 };
}
for (var c = 0; c < this.tiles.length; c++) {
for (var r = 0; r < this.tiles[c].length; r++) {
var tile = this.tiles[c][r];
tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
var visible = true;
if (tile.posx > this.width) {
// tile moved out of view to the right
// consider the tile coming into view from the left
do {
tile.xIndex -= this.tiles.length;
tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
} while (tile.posx > this.width);
if (tile.posx + this.tileSize < 0) {
visible = false;
}
} else {
// tile may have moved out of view from the left
// if so, consider the tile coming into view from the right
while (tile.posx < -this.tileSize) {
tile.xIndex += this.tiles.length;
tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
}
if (tile.posx > this.width) {
visible = false;
}
}
if (tile.posy > this.height) {
// tile moved out of view to the bottom
// consider the tile coming into view from the top
do {
tile.yIndex -= this.tiles[c].length;
tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
} while (tile.posy > this.height);
if (tile.posy + this.tileSize < 0) {
visible = false;
}
} else {
// tile may have moved out of view to the top
// if so, consider the tile coming into view from the bottom
while (tile.posy < -this.tileSize) {
tile.yIndex += this.tiles[c].length;
tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
}
if (tile.posy > this.height) {
visible = false;
}
}
// initialize the image object for this quadrant
if (!this.initialized) {
this.assignTileImage(tile, true);
tile.element.style.top = tile.posy + 'px';
tile.element.style.left = tile.posx + 'px';
}
// display the image if visible
if (visible) {
this.assignTileImage(tile);
}
// seems to need this no matter what
tile.element.style.top = tile.posy + 'px';
tile.element.style.left = tile.posx + 'px';
}
}
// reset the x, y coordinates of the viewer according to motion
if (reset) {
this.x += motion.x;
this.y += motion.y;
}
},
/**
* Determine the source image of the specified tile based
* on the zoom level and position of the tile. If forceBlankImage
* is specified, the source should be automatically set to the
* null tile image. This method will also setup an onload
* routine, delaying the appearance of the tile until it is fully
* loaded, if configured to do so.
*/
assignTileImage: function (tile, forceBlankImage) {
var tileImgId, src;
var useBlankImage = (forceBlankImage ? true : false);
// check if image has been scrolled too far in any particular direction
// and if so, use the null tile image
if (!useBlankImage) {
var left = tile.xIndex < 0;
var high = tile.yIndex < 0;
var right = tile.xIndex >= Math.ceil(this.scaleSize.x / this.tileSize);
var low = tile.yIndex >= Math.ceil(this.scaleSize.y / this.tileSize);
if (high || left || low || right) {
useBlankImage = true;
}
}
if (useBlankImage) {
tileImgId = 'blank:' + tile.qx + ':' + tile.qy;
src = this.cache['blank'].src;
}
else {
tileImgId = src = this.tileUrlProvider.assembleUrl(tile.xIndex, tile.yIndex, this.zoomLevel);
}
// only remove tile if identity is changing
if (tile.element != null &&
tile.element.parentNode != null &&
tile.element.relativeSrc != src) {
this.well.removeChild(tile.element);
}
var tileImg = this.cache[tileImgId];
// create cache if not exist
if (tileImg == null) {
tileImg = this.cache[tileImgId] = this.createPrototype(src);
}
if (useBlankImage || !PanoJS.USE_LOADER_IMAGE || tileImg.complete || (tileImg.image && tileImg.image.complete)) {
tileImg.onload = function () {
};
if (tileImg.image) {
tileImg.image.onload = function () {
};
}
if (tileImg.parentNode == null) {
tile.element = this.well.appendChild(tileImg);
}
}
else {
var loadingImgId = 'loading:' + tile.qx + ':' + tile.qy;
var loadingImg = this.cache[loadingImgId];
if (loadingImg == null) {
loadingImg = this.cache[loadingImgId] = this.createPrototype(this.cache['loading'].src);
}
loadingImg.targetSrc = tileImgId;
var well = this.well;
tile.element = well.appendChild(loadingImg);
tileImg.onload = function () {
// make sure our destination is still present
if (loadingImg.parentNode && loadingImg.targetSrc == tileImgId) {
tileImg.style.top = loadingImg.style.top;
tileImg.style.left = loadingImg.style.left;
well.replaceChild(tileImg, loadingImg);
tile.element = tileImg;
}
tileImg.onload = function () {
};
return false;
};
// konqueror only recognizes the onload event on an Image
// javascript object, so we must handle that case here
if (!PanoJS.DOM_ONLOAD) {
tileImg.image = new Image();
tileImg.image.onload = tileImg.onload;
tileImg.image.src = tileImg.src;
}
}
},
createPrototype: function (src) {
var img = document.createElement('img');
img.src = src;
img.relativeSrc = src;
img.className = PanoJS.TILE_STYLE_CLASS;
img.style.width = this.tileSize + 'px';
img.style.height = this.tileSize + 'px';
return img;
},
addViewerMovedListener: function (listener) {
this.viewerMovedListeners.push(listener);
},
addViewerZoomedListener: function (listener) {
this.viewerZoomedListeners.push(listener);
},
/**
* Notify listeners of a zoom event on the viewer.
*/
notifyViewerZoomed: function () {
var percentage = (100 / (this.maxZoomLevel + 1)) * (this.zoomLevel + 1);
for (var i = 0; i < this.viewerZoomedListeners.length; i++) {
this.viewerZoomedListeners[i].viewerZoomed(
new PanoJS.ZoomEvent(this.x, this.y, this.zoomLevel, percentage)
);
}
},
/**
* Notify listeners of a move event on the viewer.
*/
notifyViewerMoved: function (coords) {
if (typeof coords == 'undefined') {
coords = { 'x': 0, 'y': 0 };
}
for (var i = 0; i < this.viewerMovedListeners.length; i++) {
this.viewerMovedListeners[i].viewerMoved(
new PanoJS.MoveEvent(
this.x + (coords.x - this.mark.x),
this.y + (coords.y - this.mark.y)
)
);
}
},
zoom: function (direction) {
// ensure we are not zooming out of range
if (this.zoomLevel + direction < 0) {
if (PanoJS.MSG_BEYOND_MIN_ZOOM) {
alert(PanoJS.MSG_BEYOND_MIN_ZOOM);
}
return;
}
else if (this.zoomLevel + direction > this.maxZoomLevel) {
if (PanoJS.MSG_BEYOND_MAX_ZOOM) {
alert(PanoJS.MSG_BEYOND_MAX_ZOOM);
}
return;
}
this.blank();
var coords = { 'x': Math.floor(this.width / 2), 'y': Math.floor(this.height / 2) };
var before = {
'x': (coords.x - this.x),
'y': (coords.y - this.y)
};
var after = {
'x': Math.floor(before.x * Math.pow(2, direction)),
'y': Math.floor(before.y * Math.pow(2, direction))
};
this.x = coords.x - after.x;
this.y = coords.y - after.y;
this.zoomLevel += direction;
this.calcScale();
this.positionTiles();
this.notifyViewerZoomed();
},
/**
* Clear all the tiles from the well for a complete reinitialization of the
* viewer. At this point the viewer is not considered to be initialized.
*/
clear: function () {
this.blank();
this.initialized = false;
this.tiles = [];
},
/**
* Remove all tiles from the well, which effectively "hides"
* them for a repaint.
*/
blank: function () {
for (imgId in this.cache) {
var img = this.cache[imgId];
img.onload = function () {
};
if (img.image) {
img.image.onload = function () {
};
}
if (img.parentNode != null) {
this.well.removeChild(img);
}
}
},
/**
* Method specifically for handling a mouse move event. A direct
* movement of the viewer can be achieved by calling positionTiles() directly.
*/
moveViewer: function (coords) {
this.positionTiles({ 'x': (coords.x - this.mark.x), 'y': (coords.y - this.mark.y) });
this.notifyViewerMoved(coords);
},
/**
* Make the specified coords the new center of the image placement.
* This method is typically triggered as the result of a double-click
* event. The calculation considers the distance between the center
* of the viewable area and the specified (viewer-relative) coordinates.
* If absolute is specified, treat the point as relative to the entire
* image, rather than only the viewable portion.
*/
recenter: function (coords, absolute) {
if (absolute) {
coords.x += this.x;
coords.y += this.y;
}
var motion = {
'x': Math.floor((this.width / 2) - coords.x),
'y': Math.floor((this.height / 2) - coords.y)
};
if (motion.x == 0 && motion.y == 0) {
return;
}
if (PanoJS.USE_SLIDE) {
var target = motion;
var x, y;
// handle special case of vertical movement
if (target.x == 0) {
x = 0;
y = this.slideAcceleration;
}
else {
var slope = Math.abs(target.y / target.x);
x = Math.round(Math.pow(Math.pow(this.slideAcceleration, 2) / (1 + Math.pow(slope, 2)), .5));
y = Math.round(slope * x);
}
motion = {
'x': Math.min(x, Math.abs(target.x)) * (target.x < 0 ? -1 : 1),
'y': Math.min(y, Math.abs(target.y)) * (target.y < 0 ? -1 : 1)
};
}
this.positionTiles(motion, true);
this.notifyViewerMoved();
if (!PanoJS.USE_SLIDE) {
return;
}
var newcoords = {
'x': coords.x + motion.x,
'y': coords.y + motion.y
};
var self = this;
// TODO: use an exponential growth rather than linear (should also depend on how far we are going)
// FIXME: this could be optimized by calling positionTiles directly perhaps
this.slideAcceleration += PanoJS.SLIDE_ACCELERATION_FACTOR;
this.slideMonitor = setTimeout(function () {
self.recenter(newcoords);
}, PanoJS.SLIDE_DELAY);
},
resize: function () {
// IE fires a premature resize event
if (!this.initialized) {
return;
}
var newWidth = this.viewer.offsetWidth;
var newHeight = this.viewer.offsetHeight;
this.viewer.style.display = 'none';
this.clear();
var before = {
'x': Math.floor(this.width / 2),
'y': Math.floor(this.height / 2)
};
if (this.border >= 0) {
this.fitToWindow(this.border);
}
else {
this.width = newWidth;
this.height = newHeight;
}
this.prepareTiles();
var after = {
'x': Math.floor(this.width / 2),
'y': Math.floor(this.height / 2)
};
if (this.border >= 0) {
this.x += (after.x - before.x);
this.y += (after.y - before.y);
}
this.positionTiles();
this.viewer.style.display = '';
this.initialized = true;
this.notifyViewerMoved();
},
/**
* Resolve the coordinates from this mouse event by subtracting the
* offset of the viewer in the browser window (or frame). This does
* take into account the scroll offset of the page.
*/
resolveCoordinates: function (e) {
return {
'x': (e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft))) - this.left,
'y': (e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop))) - this.top
};
},
press: function (coords) {
this.activate(true);
this.mark = coords;
},
release: function (coords) {
this.activate(false);
var motion = {
'x': (coords.x - this.mark.x),
'y': (coords.y - this.mark.y)
};
this.x += motion.x;
this.y += motion.y;
this.mark = { 'x': 0, 'y': 0 };
},
/**
* Activate the viewer into motion depending on whether the mouse is pressed or
* not pressed. This method localizes the changes that must be made to the
* layers.
*/
activate: function (pressed) {
this.pressed = pressed;
this.surface.style.cursor = (pressed ? PanoJS.GRABBING_MOUSE_CURSOR : PanoJS.GRAB_MOUSE_CURSOR);
this.surface.onmousemove = (pressed ? PanoJS.mouseMovedHandler : function () {
});
},
/**
* Check whether the specified point exceeds the boundaries of
* the viewer's primary image.
*/
pointExceedsBoundaries: function (coords) {
return (coords.x < this.x ||
coords.y < this.y ||
coords.x > (this.tileSize * Math.pow(2, this.zoomLevel) + this.x) ||
coords.y > (this.tileSize * Math.pow(2, this.zoomLevel) + this.y));
},
// QUESTION: where is the best place for this method to be invoked?
resetSlideMotion: function () {
// QUESTION: should this be > 0 ?
if (this.slideMonitor != 0) {
clearTimeout(this.slideMonitor);
this.slideMonitor = 0;
}
this.slideAcceleration = 0;
}
};
PanoJS.TileUrlProvider = function (baseUri, image) {
this.baseUri = baseUri;
this.image = image;
};
PanoJS.TileUrlProvider.prototype = {
assembleUrl: function (xIndex, yIndex, zoom) {
return this.baseUri + '?tile=' +
zoom + '-' + xIndex + '-' + yIndex +
'&image=' + encodeURIComponent(this.image);
}
};
PanoJS.mousePressedHandler = function (e) {
e = e ? e : window.event;
// only grab on left-click
if (e.button < 2) {
var self = this.backingBean;
var coords = self.resolveCoordinates(e);
if (self.pointExceedsBoundaries(coords)) {
e.cancelBubble = true;
}
else {
self.press(coords);
}
}
// NOTE: MANDATORY! must return false so event does not propagate to well!
return false;
};
PanoJS.mouseReleasedHandler = function (e) {
e = e ? e : window.event;
var self = this.backingBean;
if (self.pressed) {
// OPTION: could decide to move viewer only on release, right here
self.release(self.resolveCoordinates(e));
}
};
PanoJS.mouseMovedHandler = function (e) {
e = e ? e : window.event;
var self = this.backingBean;
self.moveCount++;
if (self.moveCount % PanoJS.MOVE_THROTTLE == 0) {
self.moveViewer(self.resolveCoordinates(e));
}
};
PanoJS.zoomInHandler = function (e) {
e = e ? e : window.event;
var self = this.parentNode.parentNode.backingBean;
self.zoom(1);
return false;
};
PanoJS.zoomOutHandler = function (e) {
e = e ? e : window.event;
var self = this.parentNode.parentNode.backingBean;
self.zoom(-1);
return false;
};
PanoJS.maximizeHandler = function (e) {
var self = this.parentNode.parentNode.backingBean;
var $viewer = jQuery(self.viewer);
if (self.is_maximized) {
// restore original style
$viewer.css(self.originalCSS);
self.resize();
self.is_maximized = false;
} else {
// remember original style
self.originalCSS = {};
self.originalCSS.position = $viewer.css('position');
self.originalCSS.top = $viewer.css('top');
self.originalCSS.left = $viewer.css('left');
self.originalCSS.width = $viewer.css('width');
self.originalCSS.height = $viewer.css('height');
// set new style
$viewer.css({
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
'z-index': 999
});
self.resize();
self.is_maximized = true;
}
};
PanoJS.doubleClickHandler = function (e) {
e = e ? e : window.event;
var self = this.backingBean;
coords = self.resolveCoordinates(e);
if (!self.pointExceedsBoundaries(coords)) {
self.resetSlideMotion();
self.recenter(coords);
}
};
PanoJS.keyboardMoveHandler = function (e) {
e = e ? e : window.event;
for (var i = 0; i < PanoJS.VIEWERS.length; i++) {
var viewer = PanoJS.VIEWERS[i];
if (e.keyCode == 38)
viewer.positionTiles({'x': 0, 'y': -PanoJS.MOVE_THROTTLE}, true);
if (e.keyCode == 39)
viewer.positionTiles({'x': -PanoJS.MOVE_THROTTLE, 'y': 0}, true);
if (e.keyCode == 40)
viewer.positionTiles({'x': 0, 'y': PanoJS.MOVE_THROTTLE}, true);
if (e.keyCode == 37)
viewer.positionTiles({'x': PanoJS.MOVE_THROTTLE, 'y': 0}, true);
}
};
PanoJS.keyboardZoomHandler = function (e) {
e = e ? e : window.event;
for (var i = 0; i < PanoJS.VIEWERS.length; i++) {
var viewer = PanoJS.VIEWERS[i];
if (e.keyCode == 109)
viewer.zoom(-1);
if (e.keyCode == 107)
viewer.zoom(1);
}
};
PanoJS.MoveEvent = function (x, y) {
this.x = x;
this.y = y;
};
PanoJS.ZoomEvent = function (x, y, level, percentage) {
this.x = x;
this.y = y;
this.percentage = percentage;
this.level = level;
};
jQuery(function () {
var $panos = jQuery('div.panoview_plugin');
for (var i = 0; i < $panos.length; i++) {
var pano = $panos[i];
var opts = jQuery('div.options', pano)[0];
var conf;
eval('conf =' + opts.innerHTML); //JSON
var viewerBean = new PanoJS(pano, conf);
viewerBean.init();
}
});