464 lines
21 KiB
JavaScript
464 lines
21 KiB
JavaScript
/**
|
|
* Mapbox, the jQuery Map
|
|
* jQuery Map Plugin
|
|
* Version 0.6.0 beta
|
|
* Author Abel Mohler
|
|
* Released with the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
*/
|
|
(function($) {// jQuery.noConflict compliant
|
|
$.fn.mapbox = function(o, callback) {
|
|
var defaults = {
|
|
zoom: true, // does map zoom?
|
|
pan: true, // does map move side to side and up to down?
|
|
defaultLayer: 0, // starting at 0, which layer shows up first
|
|
layerSplit: 4, // how many times to split each layer as a zoom level
|
|
mapContent: ".mapcontent", // the name of the class on the content inner layer
|
|
defaultX: null, // default positioning on X-axis
|
|
defaultY: null, // default positioning on Y-axis
|
|
zoomToCursor: true, // if true, position on the map where the cursor is set will stay the same relative distance from the edge when zooming
|
|
doubleClickZoom: false, // if true, double clicking zooms to mouse position
|
|
clickZoom: false, // if true, clicking zooms to mouse position
|
|
doubleClickZoomOut: false, // if true, double clicking zooms out to mouse position
|
|
clickZoomOut: false, // if true, clicking zooms out to mouse position
|
|
doubleClickMove: false, // if true, double clicking moves the map to the cursor position
|
|
clickMove: false, // if true, clicking moves the map to the cursor position
|
|
doubleClickDistance: 1, // number of positions (determined by layerSplit) to move on a double-click zoom event
|
|
clickDistance: 1, // number of positions (determined by layerSplit) to move on a click zoom event
|
|
callBefore: function(layer, xcoord, ycoord, viewport) {}, // this callback happens before dragging of map starts
|
|
callAfter: function(layer, xcoord, ycoord, viewport) {}, // this callback happens at end of drag after map is released "mouseup"
|
|
beforeZoom: function(layer, xcoord, ycoord, viewport) {}, // callback before a zoom happens
|
|
afterZoom: function(layer, xcoord, ycoord, viewport) {}, // callback after zoom has completed
|
|
mousewheel: false // requires mousewheel event plugin: http://plugins.jquery.com/project/mousewheel
|
|
}
|
|
|
|
if(typeof callback == "function") {
|
|
o.callAfter = callback;
|
|
}
|
|
var command, arg = arguments;
|
|
if(typeof o == "string") {
|
|
command = o;//command passes "methods" such as "zoom", "left", etc.
|
|
}
|
|
|
|
o = $.extend(defaults, o || {});//inherit properties
|
|
|
|
$(this).css({
|
|
overflow: "hidden",
|
|
position: "relative"
|
|
});
|
|
|
|
function _zoom(distance) {
|
|
if(!o.zoom) return false;
|
|
|
|
if(distance === 0) distance = 0;
|
|
else distance = distance || 1;
|
|
|
|
var layers = $(this).find(">div"), limit = layers.length - 1, current = $(this).find(".current-map-layer");
|
|
if(typeof o.beforeZoom == "function") {
|
|
o.beforeZoom(current[0], this.xPos, this.yPos, this);
|
|
}
|
|
|
|
var move = this.visible, eq = move;
|
|
move += (distance / o.layerSplit);
|
|
if(move < 0) move = 0;
|
|
if(move > limit) move = limit;
|
|
eq = Math.ceil(move);
|
|
var movement = (this.visible == move) ? false : true;
|
|
this.visible = move;
|
|
|
|
var oldWidth = current.width(), oldHeight = current.height();
|
|
var xPercent = (($(this).width() / 2) + this.xPos) / oldWidth,
|
|
yPercent = (($(this).height() / 2) + this.yPos) / oldHeight;
|
|
|
|
if ((o.layerSplit > 1 && eq > 0)) {
|
|
var percent = move - (eq -1), thisX = layers.eq(eq)[0].defaultWidth, thisY = layers.eq(eq)[0].defaultHeight, lastX = layers.eq(eq - 1).width(), lastY = layers.eq(eq - 1).height();
|
|
var differenceX = thisX - lastX, differenceY = thisY - lastY, totalWidth = lastX + (differenceX * percent), totalHeight = lastY + (differenceY * percent);
|
|
}
|
|
if(o.layerSplit > 1 && eq > 0) {
|
|
layers.eq(eq).width(totalWidth).find(".map-layer-mask").width(totalWidth).height(totalHeight);
|
|
layers.eq(eq).height(totalHeight).find(o.mapContent).width(totalWidth).height(totalHeight);
|
|
}
|
|
|
|
//left and top adjustment for new zoom level
|
|
var newLeft = (layers.eq(eq).width() * xPercent) - ($(this).width() / 2),
|
|
newTop = (layers.eq(eq).height() * yPercent) - ($(this).height() / 2);
|
|
|
|
newLeft = 0 - newLeft;
|
|
newTop = 0 - newTop;
|
|
|
|
var limitX = $(this).width() - layers.eq(eq).width(),
|
|
limitY = $(this).height() - layers.eq(eq).height();
|
|
|
|
if(newLeft > 0) newLeft = 0;
|
|
if(newTop > 0) newTop = 0;
|
|
if(newLeft < limitX) newLeft = limitX;
|
|
if(newTop < limitY) newTop = limitY;
|
|
|
|
this.xPos = 0 - newLeft;
|
|
this.yPos = 0 - newTop;
|
|
|
|
function doCallback() {
|
|
if(typeof o.afterZoom == "function") {
|
|
o.afterZoom(layers.eq(eq)[0], this.xPos, this.yPos, this);
|
|
}
|
|
}
|
|
|
|
layers.removeClass("current-map-layer").hide();
|
|
layers.eq(eq).css({
|
|
left: newLeft + "px",
|
|
top: newTop + "px",
|
|
display: "block"
|
|
}).addClass("current-map-layer");
|
|
doCallback();
|
|
|
|
return movement;
|
|
}
|
|
|
|
function _move(x, y, node) {
|
|
node = node || $(this).find(".current-map-layer");
|
|
var limitX = 0, limitY = 0, mapWidth = $(this).width(), mapHeight = $(this).height(),
|
|
nodeWidth = $(node).width(), nodeHeight = $(node).height();
|
|
|
|
if(mapWidth < nodeWidth) limitX = mapWidth - nodeWidth;
|
|
if(mapHeight < nodeHeight) limitY = mapHeight - nodeHeight;
|
|
|
|
var left = 0 - (this.xPos + x), top = 0 - (this.yPos + y);
|
|
|
|
left = (left > 0) ? 0 : left;
|
|
left = (left < limitX) ? limitX : left;
|
|
top = (top > 0) ? 0 : top;
|
|
top = (top < limitY) ? limitY : top;
|
|
|
|
this.xPos = 0 - left;
|
|
this.yPos = 0 - top;
|
|
|
|
$(node).css({
|
|
left: left + "px",
|
|
top: top + "px"
|
|
});
|
|
}
|
|
|
|
function _position(x, y, node) {
|
|
node = node || $(this).find(".current-map-layer");
|
|
|
|
x = 0 - x;
|
|
y = 0 - y;
|
|
|
|
var limitX = 0 - ($(node).width() - $(this).width());
|
|
var limitY = 0 - ($(node).height() - $(this).height());
|
|
|
|
if(x > 0) x = 0;
|
|
if(y > 0) y = 0;
|
|
if(x < limitX) x = limitX;
|
|
if(y < limitY) y = limitY;
|
|
|
|
this.xPos = 0 - x;
|
|
this.yPos = 0 - y;
|
|
|
|
$(node).css({
|
|
left: x + "px",
|
|
top: y + "px"
|
|
});
|
|
}
|
|
|
|
function _makeCoords(s) {
|
|
s = s.replace(/px/, "");
|
|
s = 0 - s;
|
|
return s;
|
|
}
|
|
|
|
var method = {//public methods
|
|
zoom: function(distance) {
|
|
distance = distance || 1;
|
|
_zoom.call(this, distance);
|
|
},
|
|
back: function(distance) {
|
|
distance = distance || 1;
|
|
_zoom.call(this, 0 - distance);
|
|
},
|
|
left: function(amount) {
|
|
amount = amount || 10;
|
|
_move.call(this, 0 - amount, 0);
|
|
},
|
|
right: function(amount) {
|
|
amount = amount || 10;
|
|
_move.call(this, amount, 0);
|
|
},
|
|
up: function(amount) {
|
|
amount = amount || 10;
|
|
_move.call(this, 0, 0 - amount);
|
|
},
|
|
down: function(amount) {
|
|
amount = amount || 10;
|
|
_move.call(this, 0, amount);
|
|
},
|
|
center: function(coords) {
|
|
coords = coords || {
|
|
x: $(this).find(".current-map-layer").width() / 2,
|
|
y: $(this).find(".current-map-layer").height() / 2
|
|
}
|
|
var node = $(this).find(".current-map-layer");
|
|
var newX = coords.x - ($(this).width() / 2), newY = coords.y - ($(this).height() / 2);
|
|
_position.call(this, newX, newY, node[0]);
|
|
},
|
|
zoomTo: function(level) {
|
|
var distance = Math.round((level - this.visible) / (1 / this.layerSplit));
|
|
_zoom.call(this, distance);
|
|
}
|
|
}
|
|
|
|
return this.each(function() {
|
|
if(typeof command == "string") {//execute public methods if called
|
|
var execute = method[command];
|
|
o.layerSplit = this.layerSplit || o.layerSplit;
|
|
execute.call(this, callback);
|
|
}
|
|
else {
|
|
this.visible = o.defaultLayer, this.layerSplit = o.layerSplit;//magic
|
|
var viewport = this, layers = $(this).find(">div"), mapHeight = $(this).height(), mapWidth = $(this).width(), mapmove = false, first = true;
|
|
layers.css({
|
|
position: "absolute"
|
|
}).eq(o.defaultLayer).css({
|
|
display: "block",
|
|
left: "",
|
|
top: ""
|
|
}).addClass("current-map-layer").find(o.mapContent).css({
|
|
position: "absolute",
|
|
left: "0",
|
|
top: "0",
|
|
height: mapHeight + "px",
|
|
width: "100%"
|
|
});
|
|
|
|
layers.each(function() {
|
|
this.defaultWidth = $(this).width();
|
|
this.defaultHeight = $(this).height();
|
|
$(this).find(o.mapContent).css({
|
|
position: "absolute",
|
|
top: "0",
|
|
left: "0"
|
|
});
|
|
if($(this).find(o.mapContent).length > 0) $(this).find(">img").css({
|
|
width: "100%",
|
|
position: "absolute",
|
|
left: "0",
|
|
top: "0"
|
|
}).after('<div class="map-layer-mask"></div>')
|
|
});
|
|
|
|
$(this).find(".map-layer-mask").css({
|
|
position: "absolute",
|
|
left: "0",
|
|
top: "0",
|
|
background: "white",// omg, horrible hack,
|
|
opacity: "0",// but only way IE will not freak out when
|
|
filter: "alpha(opacity=0)"// mouseup over IMG tag occurs after mousemove event
|
|
});
|
|
|
|
if (o.defaultLayer > 0) {
|
|
layers.eq(o.defaultLayer).find(".map-layer-mask").width(layers.eq(o.defaultLayer).width()).height(layers.eq(o.defaultLayer).height());
|
|
layers.eq(o.defaultLayer).find(o.mapContent).width(layers.eq(o.defaultLayer).width()).height(layers.eq(o.defaultLayer).height());
|
|
}
|
|
|
|
$(this).find(">div:not(.current-map-layer)").hide();
|
|
if(o.defaultX == null) {
|
|
o.defaultX = Math.floor((mapWidth / 2) - ($(this).find(".current-map-layer").width() / 2));
|
|
if(o.defaultX > 0) o.defaultX = 0;
|
|
}
|
|
if(o.defaultY == null) {
|
|
o.defaultY = Math.floor((mapHeight / 2) - ($(this).find(".current-map-layer").height() / 2));
|
|
if(o.defaultY > 0) o.defaultY = 0;
|
|
}
|
|
|
|
this.xPos = 0 - o.defaultX;
|
|
this.yPos = 0 - o.defaultY;
|
|
this.layerSplit = o.layerSplit;
|
|
|
|
var mapStartX = o.defaultX;
|
|
var mapStartY = o.defaultY;
|
|
var clientStartX;
|
|
var clientStartY;
|
|
|
|
$(this).find(".current-map-layer").css({
|
|
left: o.defaultX + "px",
|
|
top: o.defaultY + "px"
|
|
});
|
|
|
|
/**
|
|
* Event Handling and Callbacks
|
|
*/
|
|
|
|
var weveMoved = false;
|
|
|
|
$(this).mousedown(function() {
|
|
var layer = $(this).find(".current-map-layer");
|
|
var x = layer[0].style.left, y = layer[0].style.top;
|
|
x = _makeCoords(x);
|
|
y = _makeCoords(y);
|
|
o.callBefore(layer, x, y, viewport);
|
|
mapmove = true;
|
|
first = true;
|
|
return false;//otherwise dragging on IMG elements etc inside the map will cause problems
|
|
});
|
|
|
|
$(document).mouseup(function() {
|
|
var layer = $(viewport).find(".current-map-layer");
|
|
var x = layer[0].style.left, y = layer[0].style.top;
|
|
x = _makeCoords(x);
|
|
y = _makeCoords(y);
|
|
o.callAfter(layer, x, y, viewport);
|
|
mapmove = false;
|
|
if(weveMoved) {
|
|
clickDefault = false;
|
|
}
|
|
weveMoved = false;
|
|
return false;
|
|
});
|
|
|
|
$(document).mousemove(function(e) {
|
|
var layer = $(viewport).find(".current-map-layer");
|
|
if(mapmove && o.pan) {
|
|
if(first) {
|
|
clientStartX = e.clientX;
|
|
clientStartY = e.clientY;
|
|
mapStartX = layer[0].style.left.replace(/px/, "");
|
|
mapStartY = layer[0].style.top.replace(/px/, "");
|
|
first = false;
|
|
}
|
|
else {
|
|
weveMoved = true;
|
|
}
|
|
var limitX = 0, limitY = 0;
|
|
if(mapWidth < layer.width()) limitX = mapWidth - layer.width();
|
|
if(mapHeight < layer.height()) limitY = mapHeight - layer.height();
|
|
var mapX = mapStartX - (clientStartX - e.clientX);
|
|
mapX = (mapX > 0) ? 0 : mapX;
|
|
mapX = (mapX < limitX) ? limitX : mapX;
|
|
var mapY = mapStartY - (clientStartY - e.clientY);
|
|
mapY = (mapY > 0) ? 0 : mapY;
|
|
mapY = (mapY < limitY) ? limitY : mapY;
|
|
layer.css({
|
|
left: mapX + "px",
|
|
top: mapY + "px"
|
|
});
|
|
viewport.xPos = _makeCoords(layer[0].style.left);
|
|
viewport.yPos = _makeCoords(layer[0].style.top);
|
|
}
|
|
});
|
|
|
|
if(o.mousewheel && typeof $.fn.mousewheel != "undefined") {
|
|
$(viewport).mousewheel(function(e, distance) {
|
|
if(o.zoomToCursor) {
|
|
//should probably DRY this.
|
|
var layer = $(this).find('.current-map-layer'),
|
|
positionTop = e.pageY - layer.offset().top,//jQuery normalizes pageX and pageY for us.
|
|
positionLeft = e.pageX - layer.offset().left,
|
|
//recalculate this position on current layer as a percentage
|
|
relativeTop = e.pageY - $(this).offset().top,
|
|
relativeLeft = e.pageX - $(this).offset().left,
|
|
percentTop = positionTop / layer.height(),
|
|
percentLeft = positionLeft / layer.width();
|
|
}
|
|
if(_zoom.call(this, distance) && o.zoomToCursor/* && distance > 0*/) {
|
|
//only center when zooming in, since it feels weird on out. Don't center if we've reached the floor
|
|
//convert percentage to pixels on new layer
|
|
layer = $(this).find('.current-map-layer');
|
|
var x = layer.width() * percentLeft,
|
|
y = layer.height() * percentTop;
|
|
//and set position
|
|
_position.call(this, x - relativeLeft, y - relativeTop, layer[0]);
|
|
}
|
|
return false;//don't scroll the window
|
|
});
|
|
}
|
|
|
|
var clickTimeoutId = setTimeout(function(){},0), clickDefault = true;
|
|
|
|
if(o.doubleClickZoom || o.doubleClickZoomOut || o.doubleClickMove) {
|
|
$(viewport).dblclick(function(e) {
|
|
//TODO: DRY this
|
|
//prevent single-click default
|
|
clearTimeout(clickTimeoutId);
|
|
clickDefault = false;
|
|
var layer = $(this).find('.current-map-layer'),
|
|
positionTop = e.pageY - layer.offset().top,//jQuery normalizes pageX and pageY for us.
|
|
positionLeft = e.pageX - layer.offset().left,
|
|
//recalculate this position on current layer as a percentage
|
|
percentTop = positionTop / layer.height(),
|
|
percentLeft = positionLeft / layer.width();
|
|
if(o.doubleClickZoom) {
|
|
distance = o.doubleClickDistance;
|
|
}
|
|
else if (o.doubleClickZoomOut) {
|
|
distance = 0 - o.doubleClickDistance;
|
|
}
|
|
else {
|
|
distance = 0;
|
|
}
|
|
_zoom.call(this, distance);
|
|
//convert percentage to pixels on new layer
|
|
layer = $(this).find('.current-map-layer');
|
|
var x = layer.width() * percentLeft,
|
|
y = layer.height() * percentTop;
|
|
//and center
|
|
method.center.call(this,{x: x, y: y});
|
|
return false;
|
|
});
|
|
}
|
|
|
|
if(o.clickZoom || o.clickZoomOut || o.clickMove) {
|
|
$(viewport).click(function(e) {
|
|
function clickAction() {
|
|
if(clickDefault) {
|
|
//TODO: DRY this
|
|
var layer = $(this).find('.current-map-layer'),
|
|
positionTop = e.pageY - layer.offset().top,//jQuery normalizes pageX and pageY for us.
|
|
positionLeft = e.pageX - layer.offset().left,
|
|
//recalculate this position on current layer as a percentage
|
|
percentTop = positionTop / layer.height(),
|
|
percentLeft = positionLeft / layer.width();
|
|
var distance;
|
|
if(o.clickZoom) {
|
|
distance = o.clickDistance;
|
|
}
|
|
else if (o.clickZoomOut) {
|
|
distance = 0 - o.clickDistance;
|
|
}
|
|
else {
|
|
distance = 0;
|
|
}
|
|
_zoom.call(this, distance);
|
|
//convert percentage to pixels on new layer
|
|
layer = $(this).find('.current-map-layer');
|
|
var x = layer.width() * percentLeft,
|
|
y = layer.height() * percentTop;
|
|
//and center
|
|
method.center.call(this,{x: x, y: y});
|
|
}
|
|
clickDefault = true;
|
|
}
|
|
if(o.doubleClickZoom || o.doubleClickZoomOut || o.doubleClickMove) {
|
|
//if either of these are registered we need to set the clickAction
|
|
//into a timeout so that a double click clears it
|
|
clickTimeoutId = setTimeout(function(){clickAction.call(viewport)}, 400);
|
|
}
|
|
else {
|
|
clickAction.call(this);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* End Event Handling and Callbacks
|
|
*/
|
|
|
|
//deferred, load images in hidden layers
|
|
$(window).load(function() {
|
|
layers.each(function() {
|
|
var img = $(this).find("img")[0];
|
|
if(typeof img == "object") $("<img>").attr("src", img.src);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
})(jQuery); |