/**
* Mapplic - Custom Interactive Map Plugin by @sekler
* http://www.mapplic.com
*/
(function($) {
var Mapplic = function() {
var self = this;
self.o = {
source: 'locations.json',
height: 420,
locations: true,
minimap: true,
sidebar: true,
deeplinking: true,
search: true,
clearbutton: true,
hovertip: true,
fullscreen: false,
developer: false,
animate: true,
maxscale: 4
};
self.init = function(el, params) {
// Extend options
self.o = $.extend(self.o, params);
self.x = 0;
self.y = 0;
self.scale = 1;
self.el = el.addClass('mapplic-element mapplic-loading').height(self.o.height);
// Process JSON file
$.getJSON(self.o.source, function(data) { // Success
processData(data);
self.el.removeClass('mapplic-loading');
// Controls
addControls();
}).fail(function() { // Failure: couldn't load JSON file, or it is invalid.
console.error('Couldn\'t load map data. (Make sure you are running the script through a server and not just opening the html file with your browser)');
alert('Data file missing or invalid!');
});
return self;
}
self.goToLocation = function(location,duration){
showLocation(location, duration);
}
// Tooltip
function Tooltip() {
this.el = null;
this.shift = 6;
this.drop = 0;
this.init = function() {
var s = this;
// Construct
this.el = $('
').addClass('mapplic-tooltip');
$('').addClass('mapplic-tooltip-close').attr('href', '#').click(function(e) {
e.preventDefault();
self.deeplinking.clear();
s.hide();
}).appendTo(this.el);
this.image = $('
').addClass('mapplic-tooltip-image').hide().appendTo(this.el);
this.title = $('').addClass('mapplic-tooltip-title').appendTo(this.el);
this.content = $('').addClass('mapplic-tooltip-content').appendTo(this.el);
this.desc = $('').addClass('mapplic-tooltip-description').appendTo(this.content);
this.link = $('More').addClass('mapplic-tooltip-link').attr('href', '#').hide().appendTo(this.el);
$('').addClass('mapplic-tooltip-triangle').prependTo(this.el);
// Append
self.map.append(this.el);
}
this.set = function(location) {
if (location) {
if (location.action == 'none') {
this.el.stop().fadeOut(300);
return;
}
var s = this;
if (location.image) this.image.attr('src', location.image).show();
else this.image.hide();
if (location.link) this.link.attr('href', location.link).show();
else this.link.hide();
this.title.text(location.title);
this.desc.html(location.description);
// Shift
var pinselect = $('.mapplic-pin[data-location="' + location.id + '"]');
if (pinselect.length == 0) {
this.shift = 6;
}
else this.shift = pinselect.height() + 6;
// Loading & positioning
$('img', this.desc).load(function() {
s.position(location);
});
this.position(location);
}
}
this.show = function(location) {
if (location) {
if (location.action == 'none') {
this.el.stop().fadeOut(300);
return;
}
var s = this;
if (location.image) this.image.attr('src', location.image).show();
else this.image.hide();
if (location.link) this.link.attr('href', location.link).show();
else this.link.hide();
this.title.text(location.title);
this.desc.html(location.description);
// Shift
var pinselect = $('.mapplic-pin[data-location="' + location.id + '"]');
if (pinselect.length == 0) {
this.shift = 6;
}
else this.shift = pinselect.height() + 6;
// Loading & positioning
$('img', this.desc).load(function() {
s.position(location);
});
this.position(location);
// Making it visible
this.el.stop().fadeIn(200).show();
}
}
this.position = function(location) {
var x = location.x * 100;
y = location.y * 100;
mt = -this.el.outerHeight() - this.shift,
ml = -this.el.outerWidth() / 2;
this.el.css({
left: x + '%',
top: y + '%',
marginTop: mt,
marginLeft: ml
});
this.drop = this.el.outerHeight() + this.shift;
}
this.hide = function() {
var s = this;
this.el.stop().fadeOut(300, function() {
s.desc.empty();
});
}
}
// Deeplinking
function Deeplinking() {
this.init = function() {
// Check hash for location
var id = location.hash.slice(1);
if (id) {
var locationData = getLocationData(id);
self.tooltip.set(locationData);
showLocation(id, 0);
self.tooltip.show(locationData);
}
else zoomTo(0.5, 0.5, 1, 0);
// Hashchange
$(window).on('hashchange', function() {
var id = location.hash.slice(1);
if (id) {
var locationData = getLocationData(id);
self.tooltip.set(locationData);
showLocation(id, 800);
self.tooltip.show(locationData);
}
});
}
this.clear = function() {
// if IE 6-8, else normal browsers
if (history.pushState) history.pushState('', document.title, window.location.pathname);
else window.location.hash = '';
}
}
// HoverTooltip
function HoverTooltip() {
this.el = null;
this.shift = 6;
this.init = function() {
var s = this;
// Construct
this.el = $('').addClass('mapplic-tooltip mapplic-hovertip');
this.title = $('').addClass('mapplic-tooltip-title').appendTo(this.el);
$('').addClass('mapplic-tooltip-triangle').appendTo(this.el);
// Events
$(self.map).on('mouseover', '.mapplic-layer a', function() {
var data = '';
if ($(this).hasClass('mapplic-pin')) {
data = $(this).data('location');
s.shift = $(this).height() + 6;
}
else {
data = $(this).attr('xlink:href').slice(1);
s.shift = 6;
}
var location = getLocationData(data);
if (location) s.show(location);
}).on('mouseout', function() {
s.hide();
});
self.map.append(this.el);
}
this.show = function(location) {
this.title.text(location.title);
var x = location.x * 100,
y = location.y * 100,
mt = -this.el.outerHeight() - this.shift,
ml = -this.el.outerWidth() / 2;
this.el.css({
left: x + '%',
top: y + '%',
marginTop: mt,
marginLeft: ml
});
this.el.stop().fadeIn(100);
}
this.hide = function() {
this.el.stop().fadeOut(200);
}
}
// Minimap
function Minimap() {
this.el = null;
this.init = function() {
this.el = $('').addClass('mapplic-minimap').appendTo(self.container);
this.el.css('height', this.el.width() * self.hw_ratio);
this.el.click(function(e) {
e.preventDefault();
var x = (e.pageX - $(this).offset().left) / $(this).width(),
y = (e.pageY - $(this).offset().top) / $(this).height();
zoomTo(x, y, self.scale / self.fitscale, 100);
});
}
this.addLayer = function(data) {
var layer = $('').addClass('mapplic-minimap-layer').addClass(data.id).appendTo(this.el);
$('
').attr('src', data.minimap).addClass('mapplic-minimap-background').appendTo(layer);
$('').addClass('mapplic-minimap-overlay').appendTo(layer);
$('
').attr('src', data.minimap).addClass('mapplic-minimap-active').appendTo(layer);
}
this.show = function(target) {
$('.mapplic-minimap-layer:visible', this.el).hide();
$('.mapplic-minimap-layer.' + target, this.el).show();
}
this.update = function(x, y) {
var active = $('.mapplic-minimap-active', this.el);
if (x === undefined) x = self.x;
if (y === undefined) y = self.y;
var width = Math.round(self.container.width() / self.contentWidth / self.scale * this.el.width()),
height = Math.round(self.container.height() / self.contentHeight / self.scale * this.el.height()),
top = Math.round(-y / self.contentHeight / self.scale * this.el.height()),
left = Math.round(-x / self.contentWidth / self.scale * this.el.width()),
right = left + width,
bottom = top + height;
active.css('clip', 'rect(' + top + 'px, ' + right + 'px, ' + bottom + 'px, ' + left + 'px)');
}
}
// Sidebar
function Sidebar() {
this.el = null;
this.list = null;
this.init = function() {
var s = this;
this.el = $('').addClass('mapplic-sidebar').appendTo(self.el);
if (self.o.search) {
var form = $('').addClass('mapplic-search-form').submit(function() {
return false;
}).appendTo(this.el);
self.clear = $('').addClass('mapplic-search-clear').click(function() {
input.val('');
input.keyup();
}).appendTo(form);
var input = $('').attr({'type': 'text', 'spellcheck': 'false', 'placeholder': 'Search for location...'}).addClass('mapplic-search-input').keyup(function() {
var keyword = $(this).val();
s.search(keyword);
}).prependTo(form);
}
var listContainer = $('').addClass('mapplic-list-container').appendTo(this.el);
this.list = $('
').addClass('mapplic-list').appendTo(listContainer);
this.notfound = $('').addClass('mapplic-not-found').text('Nothing found. Please try a different search.').appendTo(listContainer);
if (!self.o.search) listContainer.css('padding-top', '0');
}
this.addCategories = function(categories) {
var list = this.list;
$.each(categories, function(index, category) {
var item = $('').addClass('mapplic-list-category').addClass(category.id);
var ol = $('
').css('border-color', category.color).appendTo(item);
if (category.show == 'false') ol.hide();
var link = $('').attr('href', '#').attr('title', category.title).css('background-color', category.color).text(category.title).click(function(e) {
ol.slideToggle(200);
return false;
}).prependTo(item);
if (category.icon) $('
').attr('src', category.icon).addClass('mapplic-list-thumbnail').prependTo(link);
$('').text('0').addClass('mapplic-list-count').prependTo(link);
list.append(item);
});
}
this.addLocation = function(data) {
var item = $('').addClass('mapplic-list-location').addClass('mapplic-list-shown');
var link = $('').attr('href', '#' + data.id).appendTo(item);
if (data.thumbnail) $('
').attr('src', data.thumbnail).addClass('mapplic-list-thumbnail').appendTo(link);
$('').text(data.title).appendTo(link)
$('').html(data.about).appendTo(link);
var category = $('.mapplic-list-category.' + data.category);
if (category.length) $('ol', category).append(item);
else this.list.append(item);
// Count
$('.mapplic-list-count', category).text($('.mapplic-list-shown', category).length);
}
this.search = function(keyword) {
if (keyword) self.clear.fadeIn(100);
else self.clear.fadeOut(100);
$('.mapplic-list li', self.el).each(function() {
if ($(this).text().search(new RegExp(keyword, "i")) < 0) {
$(this).removeClass('mapplic-list-shown');
$(this).slideUp(200);
} else {
$(this).addClass('mapplic-list-shown');
$(this).show();
}
});
$('.mapplic-list > li', self.el).each(function() {
var count = $('.mapplic-list-shown', this).length;
$('.mapplic-list-count', this).text(count);
});
// Show not-found text
if ($('.mapplic-list > li.mapplic-list-shown').length > 0) this.notfound.fadeOut(200);
else this.notfound.fadeIn(200);
}
}
// Developer tools
function DevTools() {
this.el = null;
this.init = function() {
this.el = $('').addClass('mapplic-coordinates').appendTo(self.container);
this.el.append('x: ');
$('
').addClass('mapplic-coordinates-x').appendTo(this.el);
this.el.append(' y: ');
$('
').addClass('mapplic-coordinates-y').appendTo(this.el);
$('.mapplic-layer', self.map).on('mousemove', function(e) {
var x = (e.pageX - self.map.offset().left) / self.map.width(),
y = (e.pageY - self.map.offset().top) / self.map.height();
$('.mapplic-coordinates-x').text(parseFloat(x).toFixed(4));
$('.mapplic-coordinates-y').text(parseFloat(y).toFixed(4));
});
}
}
// Clear Button
function ClearButton() {
this.el = null;
this.init = function() {
this.el = $('').attr('href', '#').addClass('mapplic-clear-button').click(function(e) {
e.preventDefault();
self.deeplinking.clear();
self.tooltip.hide();
zoomTo(0.5, 0.5, 1);
}).appendTo(self.container);
}
}
// Full Screen
function FullScreen() {
this.el = null;
this.init = function() {
var s = this;
this.element = self.el[0];
$('').attr('href', '#').attr('href', '#').addClass('mapplic-fullscreen-button').click(function(e) {
e.preventDefault();
if (s.isFull()) s.exitFull();
else s.goFull();
}).appendTo(self.container);
}
this.goFull = function() {
if (this.element.requestFullscreen) this.element.requestFullscreen();
else if(this.element.mozRequestFullScreen) this.element.mozRequestFullScreen();
else if(this.element.webkitRequestFullscreen) this.element.webkitRequestFullscreen();
else if(this.element.msRequestFullscreen) this.element.msRequestFullscreen();
}
this.exitFull = function() {
if (document.exitFullscreen) document.exitFullscreen();
else if(document.mozCancelFullScreen) document.mozCancelFullScreen();
else if(document.webkitExitFullscreen) document.webkitExitFullscreen();
}
this.isFull = function() {
if (window.innerHeight == screen.height) {
return true;
} else {
return false;
}
}
}
// Functions
var processData = function(data) {
self.data = data;
var nrlevels = 0;
var shownLevel;
self.container = $('').addClass('mapplic-container').appendTo(self.el);
self.map = $('').addClass('mapplic-map').appendTo(self.container);
self.levelselect = $('').addClass('mapplic-levels-select');
if (!self.o.sidebar) self.container.css('width', '100%');
self.contentWidth = data.mapwidth;
self.contentHeight = data.mapheight;
self.hw_ratio = data.mapheight / data.mapwidth;
if (data.mapheight / self.container.height() > data.mapwidth / self.container.width()) {
self.min_width = self.container.width();
self.min_height = self.container.width() * self.hw_ratio;
}
else {
self.min_height = self.container.height();
self.min_width = self.container.height() / self.hw_ratio;
}
self.map.css({
'width': data.mapwidth,
'height': data.mapheight
});
// Create minimap
if (self.o.minimap) {
self.minimap = new Minimap();
self.minimap.init();
}
// Create sidebar
if (self.o.sidebar) {
self.sidebar = new Sidebar();
self.sidebar.init();
self.sidebar.addCategories(data.categories);
}
// Iterate through levels
if (data.levels) {
$.each(data.levels, function(index, value) {
var source = value.map;
var extension = source.substr((source.lastIndexOf('.') + 1)).toLowerCase();
// Create new map layer
var layer = $('').addClass('mapplic-layer').addClass(value.id).hide().appendTo(self.map);
switch (extension) {
// Image formats
case 'jpg': case 'jpeg': case 'png': case 'gif':
$('
').attr('src', source).addClass('mapplic-map-image').appendTo(layer);
break;
// Vector format
case 'svg':
$('').addClass('mapplic-map-image').load(source).appendTo(layer);
break;
// Other
default:
alert('File type ' + extension + ' is not supported!');
}
// Create new minimap layer
if (self.minimap) self.minimap.addLayer(value);
// Build layer control
self.levelselect.prepend($('').attr('value', value.id).text(value.title));
if (!shownLevel || value.show) {
shownLevel = value.id;
}
/* Iterate through locations */
$.each(value.locations, function(index, value) {
var top = value.y * 100;
var left = value.x * 100;
if (value.pin != 'hidden') {
if (self.o.locations) {
var target = '#' + value.id;
if (value.action == 'redirect') target = value.link;
var pin = $('').attr('href', target).addClass('mapplic-pin').css({'top': top + '%', 'left': left + '%'}).appendTo(layer);
pin.attr('data-location', value.id);
pin.addClass(value.pin);
}
}
if (self.sidebar) self.sidebar.addLocation(value);
});
nrlevels++;
});
}
// Pin animation
if (self.o.animate) {
$('.mapplic-pin').css('opacity', '0');
window.setTimeout(animateNext, 200);
}
function animateNext() {
var select = $('.mapplic-pin:not(.mapplic-animate):visible');
//console.log('enter');
if (select.length > 0) {
select.first().addClass('mapplic-animate');
window.setTimeout(animateNext, 200);
}
else {
$('.mapplic-animate').removeClass('mapplic-animate');
$('.mapplic-pin').css('opacity', '1');
}
}
// COMPONENTS
// Hover Tooltip
if (self.o.hovertip) self.hovertip = new HoverTooltip().init();
// Tooltip
self.tooltip = new Tooltip();
self.tooltip.init();
// Developer tools
if (self.o.developer) self.devtools = new DevTools().init();
// Clear button
if (self.o.clearbutton) self.clearbutton = new ClearButton().init();
// Fullscreen
if (self.o.fullscreen) self.fullscreen = new FullScreen().init();
// Levels
if (nrlevels > 1) {
self.levels = $('').addClass('mapplic-levels');
var up = $('').addClass('mapplic-levels-up').appendTo(self.levels);
self.levelselect.appendTo(self.levels);
var down = $('').addClass('mapplic-levels-down').appendTo(self.levels);
self.container.append(self.levels);
self.levelselect.change(function() {
var value = $(this).val();
level(value);
});
up.click(function(e) {
e.preventDefault();
if (!$(this).hasClass('disabled')) level('+');
});
down.click(function(e) {
e.preventDefault();
if (!$(this).hasClass('disabled')) level('-');
});
}
level(shownLevel);
// Browser resize
$(window).resize(function() {
var wr = self.container.width() / self.contentWidth,
hr = self.container.height() / self.contentHeight;
if (wr > hr) self.fitscale = wr;
else self.fitscale = hr;
self.scale = normalizeScale(self.scale);
self.x = normalizeX(self.x);
self.y = normalizeY(self.y);
moveTo(self.x, self.y, self.scale, 100);
}).resize();
// Deeplinking
if (self.o.deeplinking) {
self.deeplinking = new Deeplinking();
self.deeplinking.init();
}
}
var addControls = function() {
var map = self.map,
mapbody = $('.mapplic-map-image', self.map);
document.ondragstart = function() { return false; } // IE drag fix
// Drag & drop
mapbody.on('mousedown', function(event) {
map.stop();
map.data('mouseX', event.pageX);
map.data('mouseY', event.pageY);
map.data('lastX', self.x);
map.data('lastY', self.y);
map.addClass('mapplic-dragging');
self.map.on('mousemove', function(event) {
var x = event.pageX - map.data('mouseX') + self.x;
y = event.pageY - map.data('mouseY') + self.y;
x = normalizeX(x);
y = normalizeY(y);
moveTo(x, y);
map.data('lastX', x);
map.data('lastY', y);
});
$(document).on('mouseup', function(event) {
self.x = map.data('lastX');
self.y = map.data('lastY');
self.map.off('mousemove');
$(document).off('mouseup');
map.removeClass('mapplic-dragging');
});
});
// Double click
$(document).on('dblclick', '.mapplic-map-image', function(event) {
var mapPos = self.map.offset();
var x = (event.pageX - mapPos.left) / self.map.width();
var y = (event.pageY - mapPos.top) / self.map.height();
var z = self.map.width() / self.min_width * 2;
zoomTo(x, y, z, 600);
});
// Mousewheel
$('.mapplic-layer', this.el).bind('mousewheel DOMMouseScroll', function(event, delta) {
event.preventDefault();
var scale = self.scale;
self.scale = normalizeScale(scale + scale * delta/5);
self.x = normalizeX(self.x - (event.pageX - self.container.offset().left - self.x) * (self.scale/scale - 1));
self.y = normalizeY(self.y - (event.pageY - self.container.offset().top - self.y) * (self.scale/scale - 1));
moveTo(self.x, self.y, self.scale, 100);
});
// Touch support
if (!('ontouchstart' in window || 'onmsgesturechange' in window)) return true;
mapbody.on('touchstart', function(e) {
var orig = e.originalEvent,
pos = map.position();
map.data('touchY', orig.changedTouches[0].pageY - pos.top);
map.data('touchX', orig.changedTouches[0].pageX - pos.left);
mapbody.on('touchmove', function(e) {
e.preventDefault();
var orig = e.originalEvent;
var touches = orig.touches.length;
if (touches == 1) {
self.x = normalizeX(orig.changedTouches[0].pageX - map.data('touchX'));
self.y = normalizeY(orig.changedTouches[0].pageY - map.data('touchY'));
moveTo(self.x, self.y, self.scale, 100);
}
else {
mapbody.off('touchmove');
}
});
mapbody.on('touchend', function(e) {
mapbody.off('touchmove touchend');
});
});
// Pinch zoom
var mapPinch = Hammer(self.map[0], {
transform_always_block: true,
drag_block_horizontal: true,
drag_block_vertical: true
});
var scale=1, last_scale;
mapPinch.on('touch transform', function(ev) {
switch(ev.type) {
case 'touch':
last_scale = scale;
break;
case 'transform':
var center = ev.gesture.center;
scale = Math.max(1, Math.min(last_scale * ev.gesture.scale, 10));
var oldscale = self.scale;
self.scale = normalizeScale(scale * self.fitscale);
self.x = normalizeX(self.x - (center.pageX - self.container.offset().left - self.x) * (self.scale/oldscale - 1));
self.y = normalizeY(self.y - (center.pageY - self.container.offset().top - self.y) * (self.scale/oldscale - 1));
moveTo(self.x, self.y, self.scale, 200);
break;
}
});
}
var level = function(target) {
switch (target) {
case '+':
target = $('option:selected', self.levelselect).removeAttr('selected').prev().prop('selected', 'selected').val();
break;
case '-':
target = $('option:selected', self.levelselect).removeAttr('selected').next().prop('selected', 'selected').val();
break;
default:
$('option[value="' + target + '"]', self.levelselect).prop('selected', 'selected');
}
var layer = $('.mapplic-layer.' + target, self.map);
// Target layer is active
if (layer.is(':visible')) return;
// Hide Tooltip
self.tooltip.hide();
// Show target layer
$('.mapplic-layer:visible', self.map).hide();
layer.show();
// Show target minimap layer
if (self.minimap) self.minimap.show(target);
// Update control
var index = self.levelselect.get(0).selectedIndex,
up = $('.mapplic-levels-up', self.levels),
down = $('.mapplic-levels-down', self.levels);
up.removeClass('disabled');
down.removeClass('disabled');
if (index == 0) {
up.addClass('disabled');
}
else if (index == self.levelselect.get(0).length - 1) {
down.addClass('disabled');
}
}
var getLocationData = function(id) {
var data = null;
$.each(self.data.levels, function(index, layer) {
$.each(layer.locations, function(index, value) {
if (value.id == id) {
data = value;
}
});
});
return data;
}
var showLocation = function(id, duration) {
$.each(self.data.levels, function(index, layer) {
$.each(layer.locations, function(index, value) {
if (value.id == id) {
var zoom = typeof value.zoom !== 'undefined' ? value.zoom : 4,
drop = self.tooltip.drop / self.contentHeight / zoom;
level(layer.id);
zoomTo(value.x, parseFloat(value.y) - drop, zoom, duration, 'easeInOutCubic');
}
});
});
};
var normalizeX = function(x) {
var minX = self.container.width() - self.contentWidth * self.scale;
if (x > 0) x = 0;
else if (x < minX) x = minX;
return x;
}
var normalizeY = function(y) {
var minY = self.container.height() - self.contentHeight * self.scale;
if (y >= 0) y = 0;
else if (y < minY) y = minY;
return y;
}
var normalizeScale = function(scale) {
if (scale < self.fitscale) scale = self.fitscale;
else if (scale > self.o.maxscale) scale = self.o.maxscale;
return scale;
}
var zoomTo = function(x, y, s, duration, easing) {
duration = typeof duration !== 'undefined' ? duration : 400;
self.scale = normalizeScale(self.fitscale * s);
var scale = self.contentWidth * self.scale;
self.x = normalizeX(self.container.width() * 0.5 - self.scale * self.contentWidth * x);
self.y = normalizeY(self.container.height() * 0.5 - self.scale * self.contentHeight * y);
moveTo(self.x, self.y, self.scale, duration, easing);
}
var moveTo = function(x, y, scale, d, easing) {
if (scale !== undefined) {
self.map.stop().animate({
'left': x,
'top': y,
'width': self.contentWidth * scale,
'height': self.contentHeight * scale
}, d, easing);
}
else {
self.map.css({
'left': x,
'top': y
});
}
if (self.minimap) self.minimap.update(x, y);
}
};
// Create a jQuery plugin
$.fn.mapplic = function(params) {
var len = this.length;
return this.each(function(index) {
var me = $(this),
key = 'mapplic' + (len > 1 ? '-' + ++index : ''),
instance = (new Mapplic).init(me, params);
me.data(key, instance).data('key', key);
});
};
})(jQuery);