1/* 2 * Mapplic - Custom Interactive Map Plugin by @sekler 3 * Version 7.1.2 4 * https://www.mapplic.com/ 5 */ 6 7;(function($) { 8 "use strict"; 9 10 var Mapplic = function(element) { 11 12 var self = this; 13 14 self.o = { 15 source: 'locations.json', 16 selector: '[id^=MLOC] > *, [id^=landmark] > *, svg > #items > *', 17 external: '.mapplic-external', 18 scope: null, 19 customcss: null, 20 iconfile: null, 21 height: 'auto', 22 minheight: 400, 23 maxheight: 800, 24 csv: false, 25 landmark: false, 26 portrait: 860, 27 minimap: false, 28 deeplinking: true, 29 lightbox: true, 30 fullscreen: false, 31 hovertip: true, 32 defaultstyle: null, 33 moretext: null, 34 action: 'default', 35 marker: '', 36 developer: false, 37 smartip: false, 38 animations: false, 39 hovertipdesc: false, 40 41 // sidebar 42 sidebar: true, 43 sidebartoggle: false, 44 filtersopened: false, 45 search: true, 46 searchlength: 1, 47 searchfields: ['title', 'about', 'category'], 48 searchdescription: false, 49 highlight: true, 50 thumbholder: false, 51 autopopulate: false, 52 sortby: 'title', 53 54 // zoom 55 zoom: true, 56 clearbutton: true, 57 zoombuttons: true, 58 zoomoutclose: false, 59 closezoomout: true, 60 linknewtab: false, 61 mousewheel: true, 62 mapfill: false, 63 zoommargin: 200, 64 maxscale: 3, 65 66 // UI Colors 67 basecolor: null, 68 bgcolor: null, 69 bgcolor2: null, 70 headingcolor: null, 71 textcolor: null, 72 accentcolor: null 73 }; 74 75 self.loc = { 76 more: 'More', 77 search: 'Search', 78 zoomin: 'Zoom in', 79 zoomout: 'Zoom out', 80 resetzoom: 'Reset zoom', 81 levelup: 'Level up', 82 leveldown: 'Level down', 83 clearsearch: 'Clear search', 84 closepopup: 'Close popup', 85 clearfilter: 'Clear filter', 86 iconfile: 'mapplic/images/icons.svg' 87 } 88 89 self.el = element; 90 91 self.init = function(options) { 92 93 // merging options with defaults 94 self.o = $.extend(self.o, options); 95 if (typeof mapplic_localization !== 'undefined') self.loc = $.extend(self.loc, mapplic_localization); 96 if (self.o.iconfile) self.loc.iconfile = self.o.iconfile; 97 98 self.el.addClass('mapplic-element mapplic-loading'); 99 100 // trigger event 101 self.el.trigger('mapload', self); 102 103 // scope 104 if (self.o.scope) self.scope = $(self.o.scope); 105 else self.scope = self.el; 106 107 // process map data 108 var data = self.el.data('mapdata'); 109 if (self.el.data('mapdata')) { 110 var mapdata = self.el.data('mapdata'); 111 self.el.removeAttr('data-mapdata').removeData('mapdata'); 112 processData(mapdata); 113 self.el.removeClass('mapplic-loading'); 114 } 115 else if (typeof self.o.source === 'string') { 116 // loading .json file with AJAX 117 $.getJSON(self.o.source, function(data) { 118 processData(data); 119 self.el.removeClass('mapplic-loading'); 120 }).fail(function() { // Failure: couldn't load JSON file or it is invalid. 121 console.error('Couldn\'t load map data. (Make sure to run the script through web server)'); 122 self.el.removeClass('mapplic-loading').addClass('mapplic-error'); 123 alert('Data file missing or invalid!\nMake sure to run the script through web server.'); 124 }); 125 } 126 else { 127 // inline json object 128 processData(self.o.source); 129 self.el.removeClass('mapplic-loading'); 130 } 131 132 return self; 133 } 134 135 // tooltip 136 function Tooltip() { 137 this.el = null; 138 this.pin = null; 139 this.shift = 6; 140 this.drop = 0; 141 this.location = null; 142 143 this.init = function(location, check) { 144 var s = this; 145 146 // markup 147 this.el = $('<div></div>').addClass('mapplic-tooltip'); 148 this.wrap = $('<div></div>').addClass('mapplic-tooltip-wrap').appendTo(this.el); 149 this.close = $('<button></button>').append(getIcon('icon-cross')).addClass('mapplic-tooltip-close').attr('aria-label', self.loc.closepopup).on('click touchend', function(e) { 150 e.preventDefault(); 151 self.hideLocation(); 152 if (!self.o.zoom || self.o.zoomoutclose) self.moveTo(0.5, 0.5, self.fitscale, 400); 153 }).appendTo(this.wrap); 154 this.image = $('<img>').addClass('mapplic-image').attr('alt', 'Location image').hide().appendTo(this.wrap); 155 this.body = $('<div></div>').addClass('mapplic-tooltip-body').attr('aria-modal','true').appendTo(this.wrap); 156 this.title = $('<h4></h4>').addClass('mapplic-tooltip-title').appendTo(this.body); 157 this.content = $('<div></div>').addClass('mapplic-tooltip-content').appendTo(this.body); 158 this.desc = $('<div></div>').addClass('mapplic-tooltip-description').appendTo(this.content); 159 this.link = $('<a>' + self.loc.more + '</a>').addClass('mapplic-popup-link').attr('href', '#').hide().appendTo(this.body); 160 if (self.o.linknewtab) this.link.attr('target', '_blank'); 161 this.triangle = $('<div></div>').addClass('mapplic-tooltip-triangle').prependTo(this.wrap); 162 163 // no focus on deeplink 164 if (!check) this.body.attr('tabindex', '-1'); 165 166 $('.mapplic-layer.mapplic-visible', self.map).append(this.el); 167 168 if (self.o.smartip) self.el.on('positionchanged', {s: s}, this.repos); 169 170 this.el.css({'transform': 'scale(' + 1/self.scale + ')'}); 171 172 if (location) this.show(location); 173 174 // close tooltip 175 $(document).on('keyup.mapplic', function(e) { 176 e.stopImmediatePropagation(); 177 if ((e.keyCode === 27)) { 178 self.hideLocation(); 179 if (!self.o.zoom || self.o.zoomoutclose) self.moveTo(0.5, 0.5, self.fitscale, 400); 180 } 181 }); 182 183 return this; 184 } 185 186 this.repos = function(e) { 187 var s = e.data.s, 188 tx = '-50%', 189 ty = '-100%'; 190 191 // vertical 192 var rtop = s.el.offset().top - self.container.el.offset().top; 193 if (rtop < s.wrap.outerHeight() + 36) { 194 s.el.addClass('mapplic-tooltip-bottom'); 195 ty = "0%"; 196 } 197 else { 198 s.el.removeClass('mapplic-tooltip-bottom'); 199 ty = "-100%"; 200 } 201 202 // horizontal 203 var rleft = s.el.offset().left - self.container.el.offset().left; 204 if (rleft < s.wrap.outerWidth() / 2) { 205 if (rleft > 12) tx = -(100 + rleft / s.wrap.outerWidth() * 100) + 100 + "%"; 206 else tx = "-10%"; 207 } 208 else if (rleft > self.container.el.outerWidth() - s.wrap.outerWidth() / 2) { 209 if (rleft < self.container.el.outerWidth() - 12) tx = (self.container.el.outerWidth() - rleft) / s.wrap.outerWidth() * 100 - 100 + "%"; 210 else tx = "-90%"; 211 } 212 else tx = "-50%" 213 214 s.wrap.css({ 'transform': 'translate(' + tx + ', ' + ty + ')' }); 215 } 216 217 this.show = function(location) { 218 if (location) { 219 var s = this; 220 221 this.el.attr('data-location', location.id); 222 223 this.location = location; 224 if (self.hovertip) self.hovertip.hide(); 225 226 if (location.image) { 227 this.image.attr('src', ''); 228 this.image.attr('src', location.image).show(); 229 } 230 else this.image.hide(); 231 232 if (location.link) { 233 this.link.attr('href', location.link).css('background-color', '').show(); 234 if (location.color) this.link.css('background-color', location.color); 235 } 236 else this.link.hide(); 237 238 this.title.text(location.title); 239 if (location.description) this.desc.html(location.description); 240 else this.desc.empty(); 241 this.content[0].scrollTop = 0; 242 243 // shift 244 this.pin = $('.mapplic-pin[data-location="' + location.id + '"]'); 245 if (this.pin.length == 0) { 246 this.shift = 20; 247 } 248 else this.shift = Math.abs(parseFloat(this.pin.css('margin-top'))) + 20; 249 250 // making it visible 251 this.el.stop().css({opacity: 1}).show(); 252 setTimeout(function() {s.body.focus() }, 600); 253 this.position(); 254 if (self.o.zoom) this.zoom(location); 255 256 // loading & positioning 257 /* 258 $('img', this.el).off('load').on('load', function() { 259 s.position(); 260 if (self.o.zoom) s.zoom(location); 261 });*/ 262 } 263 } 264 265 this.position = function() { 266 if (this.location) { 267 this.el.css({ 268 left: (this.location.x * 100) + '%', 269 top: (this.location.y * 100) + '%' 270 }); 271 this.drop = this.el.outerHeight() + this.shift; 272 if (self.o.smartip) this.repos({ data: { s: this}}); 273 } 274 } 275 276 this.zoom = function(location) { 277 var ry = 0.5, 278 zoom = location.zoom ? parseFloat(location.zoom)/self.o.maxscale : 1; 279 280 ry = (self.container.el.height()/2 + this.drop/2) / self.container.el.height(); 281 self.moveTo(location.x, location.y, zoom, 600, ry); 282 } 283 284 this.hide = function() { 285 var s = this; 286 287 this.location = null; 288 this.el.stop().fadeOut(200, function() { $(this).remove(); }); 289 if (self.o.smartip) self.el.off('positionchanged', this.repos); 290 } 291 } 292 293 // hover tooltip 294 function HoverTooltip() { 295 this.el = null; 296 this.pin = null; 297 this.shift = 6; 298 299 this.init = function() { 300 var s = this; 301 302 // construct 303 this.el = $('<div></div>').addClass('mapplic-tooltip mapplic-hovertip'); 304 this.wrap = $('<div></div>').addClass('mapplic-tooltip-wrap').appendTo(this.el); 305 this.title = $('<h4></h4>').addClass('mapplic-tooltip-title').appendTo(this.wrap); 306 if (self.o.hovertipdesc) this.desc = $('<div></div>').addClass('mapplic-tooltip-description').appendTo(this.wrap); 307 this.triangle = $('<div></div>').addClass('mapplic-tooltip-triangle').appendTo(this.wrap); 308 309 // events 310 // markers + old svg 311 $(self.map).on('mouseover', '.mapplic-pin', function() { 312 var id = $(this).data('location'); 313 314 s.pin = $('.mapplic-pin[data-location="' + id + '"]'); 315 s.shift = Math.abs(parseFloat(s.pin.css('margin-top'))) + 20; 316 317 var location = self.l[id]; 318 if (location && location.title) s.show(location); 319 }).on('mouseout', function() { 320 s.hide(); 321 }); 322 323 // new svg 324 if (self.o.selector) { 325 $(self.map).on('mouseover touchstart', self.o.selector, function() { 326 var location = self.l[$(this).attr('id')]; 327 s.shift = 20; 328 if (location && location.title) s.show(location); 329 }).on('mouseout touchend', function() { 330 s.hide(); 331 }); 332 } 333 334 self.el.on('positionchanged', {s: s}, this.repos); 335 336 self.map.append(this.el); 337 338 return this; 339 } 340 341 this.repos = function(e) { 342 var s = e.data.s, 343 tx = '-50%', 344 ty = '-100%'; 345 346 // vertical 347 var rtop = s.el.offset().top - self.container.el.offset().top; 348 if (rtop < s.wrap.outerHeight() + 36) { 349 s.el.addClass('mapplic-tooltip-bottom'); 350 ty = "0%"; 351 } 352 else { 353 s.el.removeClass('mapplic-tooltip-bottom'); 354 ty = "-100%"; 355 } 356 357 // horizontal 358 var rleft = s.el.offset().left - self.container.el.offset().left; 359 if (rleft < s.wrap.outerWidth() / 2) { 360 if (rleft > 12) tx = -(100 + rleft / s.wrap.outerWidth() * 100) + 100 + "%"; 361 else tx = "-10%"; 362 } 363 else if (rleft > self.container.el.outerWidth() - s.wrap.outerWidth() / 2) { 364 if (rleft < self.container.el.outerWidth() - 12) tx = (self.container.el.outerWidth() - rleft) / s.wrap.outerWidth() * 100 - 100 + "%"; 365 else tx = "-90%"; 366 } 367 else tx = "-50%" 368 369 s.wrap.css({ 'transform': 'translate(' + tx + ', ' + ty + ')' }); 370 } 371 372 this.show = function(location) { 373 if (self.location != location) { 374 this.title.text(location.title); 375 if (self.o.hovertipdesc) { 376 if (location.description) this.desc.html(location.description); 377 else this.desc.empty(); 378 } 379 this.position(location); 380 381 this.el.stop().fadeIn(100); 382 } 383 this.repos({ data: { s: this}}); 384 } 385 386 this.position = function(location) { 387 if (location) { 388 this.el.css({ 389 left: (location.x * 100) + '%', 390 top: (location.y * 100) + '%' 391 }); 392 393 this.drop = this.el.outerHeight() + this.shift; 394 } 395 } 396 397 this.hide = function() { 398 this.el.stop().fadeOut(200); 399 } 400 } 401 402 // lightbox 403 function Lightbox() { 404 this.el = null; 405 406 this.init = function() { 407 // construct 408 this.el = $('<div></div>').addClass('mapplic-lightbox mfp-hide'); 409 this.title = $('<h4></h4>').addClass('mapplic-lightbox-title').appendTo(this.el); 410 this.desc = $('<div></div>').addClass('mapplic-lightbox-description').appendTo(this.el); 411 this.link = $('<a>' + self.loc.more + '</a>').addClass('mapplic-popup-link').attr('href', '#').hide().appendTo(this.el); 412 if (self.o.linknewtab) this.link.attr('target', '_blank'); 413 414 // append 415 self.el.append(this.el); 416 417 return this; 418 } 419 420 this.show = function(location) { 421 this.location = location; 422 423 this.title.text(location.title); 424 this.desc.html(location.description); 425 426 if (location.link) { 427 this.link.attr('href', location.link).css('background-color', '').show(); 428 if (location.color) this.link.css('background-color', location.color); 429 } 430 else this.link.hide(); 431 432 var s = this; 433 434 $.magnificPopup.open({ 435 items: { src: this.el }, 436 type: 'inline', 437 removalDelay: 300, 438 mainClass: 'mfp-fade', 439 callbacks: { 440 beforeClose: function() { 441 s.hide(); 442 } 443 } 444 }); 445 446 // zoom 447 var zoom = location.zoom ? parseFloat(location.zoom) : 1; 448 if (self.o.zoom) self.moveTo(location.x, location.y, zoom, 600); 449 450 return this.el[0]; 451 } 452 453 this.showImage = function(location) { 454 this.location = location; 455 456 var s = this; 457 458 $.magnificPopup.open({ 459 items: { src: location.image }, 460 type: 'image', 461 removalDelay: 300, 462 mainClass: 'mfp-fade', 463 callbacks: { 464 beforeClose: function() { 465 s.hide(); 466 } 467 } 468 }); 469 470 // zoom 471 var zoom = location.zoom ? parseFloat(location.zoom) : 1; 472 if (self.o.zoom) self.moveTo(location.x, location.y, zoom, 600); 473 } 474 475 this.hide = function() { 476 this.location = null; 477 this.desc.empty(); 478 self.hideLocation(); 479 if (!self.o.zoom || self.o.zoomoutclose) self.moveTo(0.5, 0.5, self.fitscale, 400); 480 } 481 } 482 483 // external 484 function External() { 485 this.el = null; 486 487 this.init = function() { 488 this.el = $('<div></div>').addClass('mapplic-external-content').hide(); 489 this.title = $('<h4></h4>').addClass('mapplic-external-title').appendTo(this.el); 490 this.desc = $('<div></div>').addClass('mapplic-external-description').appendTo(this.el); 491 492 this.initial = $(self.o.external + ' > *:not(.mapplic-external-content)'); 493 494 $(self.o.external).append(this.el); 495 496 return this; 497 } 498 499 this.show = function(location) { 500 this.title.text(location.title); 501 this.desc.html(location.description); 502 503 this.initial.hide(); 504 this.el.show(); 505 } 506 507 this.hide = function() { 508 this.initial.show(); 509 this.el.hide(); 510 } 511 } 512 513 // deeplinking 514 function Deeplinking() { 515 this.param = 'location'; 516 this.resolved = false;
517 518 this.init = function() { 519 var s = this; 520 521 window.onpopstate = function(e) { 522 if (e.state) s.check(600); 523 return false; 524 } 525 } 526 527 this.check = function(duration) { 528 var id = this.getUrlParam(this.param); 529 if (id) { 530 self.showLocation(id, duration, true); 531 this.resolved = true; 532 } 533 } 534 535 this.getUrlParam = function(name) { 536 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 537 var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 538 results = regex.exec(location.search); 539 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 540 } 541 542 this.update = function(id) { 543 var url; 544 if (typeof window.URL !== 'undefined') { 545 url = new URL(window.location.href); 546 url.searchParams.set(this.param, id); 547 url = url.href 548 } else { 549 url = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + this.param + '=' + id; 550 } 551 window.history.pushState({path: url}, '', url); 552 } 553 554 this.clear = function() { 555 var url; 556 if (typeof window.URL !== 'undefined') { 557 url = new URL(window.location.href); 558 url.searchParams.delete(this.param); 559 url = url.href; 560 } else { 561 url = window.location.pathname; 562 } 563 history.pushState('', document.title, url); 564 } 565 } 566 567 // old hash deeplinking method for old browsers (starting IE9) 568 function DeeplinkingHash() { 569 this.param = 'location'; 570 this.resolved = false; 571 572 this.init = function() { 573 var s = this; 574 this.check(0); 575 576 $(window).on('hashchange', function() { 577 //s.check(600); 578 }); 579 } 580 581 this.check = function(duration) { 582 var id = location.hash.slice(this.param.length + 2); 583 if (id) { 584 self.showLocation(id, duration, true); 585 this.resolved = true; 586 } 587 } 588 589 this.update = function(id) { 590 window.location.hash = this.param + '-' + id; 591 } 592 593 this.clear = function() { 594 window.location.hash = this.param; 595 } 596 } 597 598 // minimap 599 function Minimap() { 600 this.el = null; 601 this.opacity = null; 602 603 this.init = function() { 604 this.el = $('<div></div>').addClass('mapplic-minimap').appendTo(self.container.el); 605 this.el.click(function(e) { 606 e.preventDefault(); 607 608 var x = (e.pageX - $(this).offset().left) / $(this).width(), 609 y = (e.pageY - $(this).offset().top) / $(this).height(); 610 611 self.moveTo(x, y, self.scale / self.fitscale, 100); 612 }); 613 614 return this; 615 } 616 617 this.addLayer = function(data) {
618 var layer = $('<div></div>').addClass('mapplic-minimap-layer').attr('data-floor', data.id).appendTo(this.el), 619 s = this; 620 621 $('<img>').attr('src', data.minimap).addClass('mapplic-minimap-background').attr('aria-hidden', 'true').appendTo(layer); 622 $('<div></div>').addClass('mapplic-minimap-overlay').appendTo(layer); 623 $('<img>').attr('src', data.minimap).addClass('mapplic-minimap-active').attr('aria-hidden', 'true').on('load', function() { 624 s.update(); 625 }).appendTo(layer); 626 } 627 628 this.show = function(target) { 629 $('.mapplic-minimap-layer', this.el).hide(); 630 $('.mapplic-minimap-layer[data-floor="' + target + '"]', this.el).show(); 631 } 632 633 this.update = function(x, y) { 634 var active = $('.mapplic-minimap-active', this.el); 635 636 if (x === undefined) x = self.x; 637 if (y === undefined) y = self.y; 638 639 var width = (self.container.el.width() / self.contentWidth / self.scale * this.el.width()), 640 height = (self.container.el.height() / self.contentHeight / self.scale * this.el.height()), 641 top = (-y / self.contentHeight / self.scale * this.el.height()), 642 left = (-x / self.contentWidth / self.scale * this.el.width()), 643 right = left + width, 644 bottom = top + height; 645 646 active.each(function() { 647 $(this)[0].style.clip = 'rect(' + top + 'px, ' + right + 'px, ' + bottom + 'px, ' + left + 'px)'; 648 }); 649 650 // fade out effect 651 var s = this; 652 this.el.show(); 653 this.el.css('opacity', 1.0); 654 clearTimeout(this.opacity); 655 this.opacity = setTimeout(function() { 656 s.el.css('opacity', 0); 657 setTimeout(function() { s.el.hide(); }, 600); 658 }, 2000); 659 } 660 } 661 662 // legend 663 function Legend() { 664 this.el = null; 665 this.nr = 0; 666 667 this.init = function() { 668 this.el = $('<div></div>').addClass('mapplic-legend'); 669 return this; 670 } 671 672 this.build = function(categories) { 673 var s = this; 674 $.each(categories, function(index, category) { 675 if (category.legend == 'true') s.add(category); 676 }); 677 if (this.nr > 0) this.el.appendTo(self.container.el); 678 } 679 680 this.add = function(group) { 681 var toggle = this.newToggle(group, true); 682 if (toggle) toggle.appendTo(this.el); 683 else { 684 var key = $('<span></span>').addClass('mapplic-legend-key'); 685 if (group.color) key.css('background-color', group.color); 686 $('<span></span>').addClass('mapplic-legend-label').text(group.title).prepend(key).appendTo(this.el); 687 } 688 this.nr++; 689 } 690 691 this.toggle = function(group, state) { 692 $('*[id="' + group + '"]', self.map).toggle(state); 693 $('*[data-category*="' + group + '"]', self.map).each(function() { 694 var attr = $(this).attr('data-category'); 695 if (attr == group) $(this).toggle(state); 696 else { // multi-group support 697 var groups = attr.split(','), 698 show = false; 699 groups.forEach(function(g) { if ($('.mapplic-toggle > input[data-group="' + g + '"]')[0] && $('.mapplic-toggle > input[data-group="' + g + '"]')[0].checked) show = true; }); 700 $(this).toggle(show); 701 } 702 }); 703 } 704 705 this.newToggle = function(group, title) { 706 var s = this, 707 toggle = null; 708 709 if (group.toggle == 'true') { 710 toggle = $('<label class="mapplic-toggle"></label>'); 711 var input = $('<input type="checkbox" checked>').attr('data-group', group.id).appendTo(toggle); 712 var circle = $('<span></span>').addClass('mapplic-toggle-circle').appendTo(toggle); 713 if (title) $('<span></span>').addClass('mapplic-legend-label').text(group.title).appendTo(toggle); 714 if (group.switchoff == 'true') input.prop('checked', false); 715 if (group.color) circle.css('background-color', group.color); 716 717 input.change(function() { 718 $('.mapplic-toggle > input[data-group="' + group.id + '"]', self.el).prop('checked', this.checked); 719 s.toggle(group.id, this.checked); 720 }); 721 } 722 return toggle; 723 } 724 725 this.applyToggles = function() { 726 var s = this; 727 $('.mapplic-toggle > input', self.el).each(function() { 728 s.toggle($(this).attr('data-group'), this.checked); 729 }); 730 } 731 } 732 733 // groups 734 function Groups() { 735 736 this.init = function() {} 737 738 this.addGroups = function(groups) { 739 if (groups) { 740 $.each(groups, function(index, group) { 741 self.g[group.id] = group; 742 if (group.style && self.s[group.style] && self.s[group.style].base) group.color = self.s[group.style].base.fill; // overwrite color with style 743 }); 744 } 745 746 if (self.o.sidebar) self.sidebar.addCategories(groups); 747 } 748 } 749 750 // directory 751 function Directory() { 752 this.dirs = []; 753 this.filters = {}; 754 755 this.init = function() { 756 var s = this, 757 event = 'mapready'; 758 759 if (self.o.csv) event = 'csvready'; 760 761 self.el.on(event, function(e, self) { 762 // dir 763 $('.mapplic-dir:not(.mapplic-dir-results)').each(function() { 764 var attribute = $(this).data('attribute'), 765 pattern = $(this).data('pattern'), 766 title = $(this).text(); 767 768 if (title) $('<h3><span>' + title + '</span></h3>').appendTo($(this).empty()); 769 s.getDirectory(attribute, pattern, attribute).appendTo($(this)); 770 771 }); 772 773 // filter 774 $('.mapplic-dir-filter').each(function() { 775 var el = $(this), 776 attribute = el.data('attribute'), 777 options = []; 778 779 // generate options 780 if ($(this).hasClass('mapplic-dir-filter-generate')) { 781 $.each(self.l, function(id, location) { 782 if (location[attribute]) { 783 var atts = location[attribute].toString().split(',');
784 atts.forEach(function(att) { if (options.indexOf(att) === -1) options.push(att); }); 785 } 786 }); 787 788 options.forEach(function(option) { 789 var text = option; 790 791 if (attribute == 'category' && self.g[option]) text = self.g[option].title; 792 $('<option></option>').attr('value', option).text(text).appendTo(el); 793 }); 794 } 795 796 // changed 797 $(this).on('change', function() { 798 if (this.value) s.filters[attribute] = this.value; 799 else delete s.filters[attribute]; 800 801 s.search(); 802 }); 803 }); 804 }); 805 806 807 // search 808 $('.mapplic-dir-search').keyup(function(e) { 809 var val = $(this).val(); 810 811 if (val) s.filters['keyword'] = val; 812 else delete s.filters['keyword']; 813 814 s.search(); 815 816 if (e.keyCode == 13) $('.mapplic-dir-results li > a').filter(':first').click(); 817 else if (e.keyCode == 27) s.clearFilters(); 818 }); 819 820 // results 821 var restitle = $('<h3><span>0</span> found</h3>').appendTo($('.mapplic-dir-results')); 822 $('<ul></ul>').appendTo($('.mapplic-dir-results')); 823 $('<button></button>').text('Clear').append(getIcon('icon-cross')).addClass('mapplic-dir-results-clear').appendTo(restitle).on('click', function() { 824 s.clearFilters(); 825 }); 826 827 // list/grid 828 var directories = $('.mapplic-dir-view').parent().find('.mapplic-dir'); 829 $('.mapplic-dir-view-list').click(function() { 830 $(this).hide(); 831 $('.mapplic-dir-view-grid').show(); 832 directories.removeClass('mapplic-dir-grid'); 833 }); 834 $('.mapplic-dir-view-grid').click(function() { 835 $(this).hide(); 836 $('.mapplic-dir-view-list').show(); 837 directories.addClass('mapplic-dir-grid'); 838 }); 839 840 return this; 841 } 842 843 this.getDirectory = function(attribute, pattern, sortby) { 844 var s = this, 845 dir = $('<ul></ul>'), 846 regex = new RegExp(pattern, 'i'); 847 $.each(self.l, function(id, location) { 848 if (attribute && pattern && !regex.test(location[attribute])) return true; // skip if no match 849 if (String(location.hide) != 'true') dir.append(s.getItem(location)); 850 }); 851 852 if (sortby) this.sortby(dir, sortby); 853 854 this.dirs.push(dir); 855 856 // trigger event 857 self.el.trigger('dirready', dir); 858 859 return dir; 860 } 861 862 this.getItem = function(location) { 863 if (!location.id) location = self.l[location]; 864 865 var item = $('<li></li>').addClass('mapplic-dir-item').attr('data-location', location.id); 866 var link = $('<a></a>').attr('href', '#').click(function(e) { 867 e.preventDefault(); 868 self.showLocation(location.id, 600); 869 870 // scroll back to map on mobile 871 if (($(window).width() < 668) && (location.action || self.o.action) != 'lightbox') { 872 $('html, body').animate({ 873 scrollTop: self.container.el.offset().top 874 }, 400); 875 } 876 }).appendTo(item); 877 878 if (self.o.sortby) item.data('sort', location[self.o.sortby]); 879 880 if (self.o.thumbholder) { 881 var thumbnail = this.thumbnail(location.title, location.thumbnail); 882 if (thumbnail) thumbnail.appendTo(link); 883 } 884 885 $('<h4></h4>').text(location.title).appendTo(link); 886 $('<span></span>').html(location.about).addClass('mapplic-about').appendTo(link); 887 if (location.color) item.css('border-color', location.color); 888 889 location.list = item; 890 891 return item; 892 } 893 894 this.thumbnail = function(name, field) { 895 var elem = null; 896 897 if (field) { 898 if (field.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g)) elem = $('<img>').attr('src', field).attr('alt', name).addClass('mapplic-thumbnail'); 899 else elem = $('<div></div>').addClass('mapplic-thumbnail mapplic-thumbnail-placeholder').attr('aria-hidden', 'true').html(field); 900 } 901 else if (name) { 902 var words = name.split(' '), 903 text = ''; 904 905 if (words[0]) text += words[0][0]; 906 if (words[1]) text += words[1][0]; 907 elem = $('<div></div>').addClass('mapplic-thumbnail mapplic-thumbnail-placeholder').attr('aria-hidden', 'true').text(text.toUpperCase()); 908 } 909 910 return elem; 911 } 912 913 this.search = function() { 914 var s = this; 915 916 $('.mapplic-dir-results ul').empty(); 917 918 if (self.o.highlight) { 919 self.map.removeClass('mapplic-filtered'); 920 $('.mapplic-highlight', self.map).removeClass('mapplic-highlight'); 921 } 922 923 $.each(self.l, function(i, location) { 924 var matched = true; // matched false, current || 925 926 $.each(s.filters, function(attribute, filter) { 927 var current = false;
928 929 if (attribute == 'keyword') $.each(self.o.searchfields, function(i, field) { if (location[field] && !current) current = !(s.normalizeString(location[field]).indexOf(s.normalizeString(filter)) == -1); }); 930 else if (location[attribute] && location[attribute].indexOf(filter) != -1) current = true; 931 932 matched = matched && current; 933 }); 934 935 if (matched) { 936 $('.mapplic-dir-item[data-location="' + location.id + '"]').clone(true).appendTo($('.mapplic-dir-results ul')); // results 937 if (self.o.highlight && !$.isEmptyObject(s.filters) && location.el) location.el.addClass('mapplic-highlight'); // highlight 938 } 939 }); 940 941 s.sortby($('.mapplic-dir-results ul'), 'title'); // alphabetic sort by title 942 943 if ($.isEmptyObject(s.filters)) { 944 $('.mapplic-dir').show(); 945 $('.mapplic-dir-results').hide(); 946 } 947 else { 948 $('.mapplic-dir').hide(); 949 $('.mapplic-dir-results').show(); 950 if (self.o.highlight) self.map.addClass('mapplic-filtered'); 951 } 952 953 $('.mapplic-dir-results h3 span').text($('.mapplic-dir-results .mapplic-dir-item').length); // count 954 } 955 956 this.clearFilters = function() { 957 this.filters = {}; 958 $('.mapplic-dir-search').val(''); 959 $('.mapplic-dir-filter').val(''); 960 961 this.search(); 962 } 963 964 this.normalizeString = function(s) { 965 if (s) return s.toString().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ""); 966 else return ''; 967 } 968 969 this.sort = function(dir, order) { 970 if (order === undefined) order = 1; 971 var items = dir.children('.mapplic-dir-item'); 972 973 items.sort(function(a, b) { 974 var a = $(a).data('sort'), 975 b = $(b).data('sort'); 976 if (isNaN(a) || isNaN(b)) { 977 return (a < b) ? -1 * order : 1 * order; 978 } 979 else return (a - b) * order; 980 }).appendTo(dir); 981 } 982 983 this.sortby = function(dir, attribute, order) { 984 var s = this; 985 986 if (order === undefined) order = 1; 987 var items = dir.children('.mapplic-dir-item'); 988 items.each(function() { 989 $(this).data('sort', s.normalizeString(self.l[$(this).data('location')][attribute])); 990 }); 991 this.sort(dir, order); 992 } 993 } 994 995 // sidebar 996 function Sidebar() { 997 this.el = null; 998 this.header = null; 999 this.clear = null; 1000 this.input = null; 1001 this.list = null; 1002 this.tags = null; 1003 this.toggle = null; 1004 1005 this.init = function() { 1006 var s = this; 1007 1008 this.el = $('<div></div>').addClass('mapplic-sidebar').appendTo(self.el); 1009 1010 if (self.o.filtersopened) this.el.addClass('mapplic-sidebar-header-opened'); 1011 1012 if (self.o.sidebartoggle) { 1013 self.el.addClass('mapplic-dynamic-sidebar'); 1014 1015 this.toggle = $('<button></button>').append(getIcon('icon-sidebar')).addClass('mapplic-button mapplic-sidebar-toggle').appendTo(self.container.el); 1016 this.toggle.on('click touchstart', function(e) { 1017 e.preventDefault(); 1018 self.el.toggleClass('mapplic-hidden-sidebar'); 1019 setTimeout(function() { 1020 var wr = self.container.el.width() / self.contentWidth, 1021 hr = self.container.el.height() / self.contentHeight; 1022 1023 if (self.o.mapfill) { 1024 if (wr > hr) self.fitscale = wr; 1025 else self.fitscale = hr; 1026 } 1027 else { 1028 if (wr < hr) self.fitscale = wr; 1029 else self.fitscale = hr; 1030 } 1031 1032 if (self.container.oldW != self.container.el.width() || self.container.oldH != self.container.el.height()) { 1033 1034 self.container.oldW = self.container.el.width(); 1035 self.container.oldH = self.container.el.height(); 1036 1037 if (self.scale*self.contentWidth < self.container.el.width()) self.moveTo(self.x, self.y, self.scale, 600); 1038 } 1039 1040 }, 400); 1041 }); 1042 } 1043 1044 if (self.o.search) { 1045 this.header = $('<div></div>').addClass('mapplic-sidebar-header').append(getIcon('icon-magnifier')).appendTo(this.el); 1046 this.clear = $('<button></button>').addClass('mapplic-search-clear').append(getIcon('icon-cross')).appendTo(this.header); 1047 this.headerwrap = $('<div></div>').appendTo(this.header); 1048 1049 this.input = $('<input>').attr({'type': 'text', 'spellcheck': 'false', 'placeholder': self.loc.search}).addClass('mapplic-search-input').keyup(function(e) { 1050 var val = $(this).val(); 1051 1052 if (val) self.directory.filters['keyword'] = val; 1053 else delete self.directory.filters['keyword']; 1054 1055 s.el.toggleClass('mapplic-sidebar-header-opened', val.length < 1); 1056 1057 s.search(); 1058 1059 if (e.keyCode == 13) $('.mapplic-list-container li > a').filter(':visible:first').click(); 1060 else if (e.keyCode == 27) $(this).blur(); 1061 }); 1062 this.input.focus(function() { 1063 if ($(this).val().length < 1) s.el.addClass('mapplic-sidebar-header-opened'); 1064 }); 1065 1066 $('body').click(function(e) { 1067 if (!$.contains(s.header[0], e.target) && s.header[0] != e.target) s.el.removeClass('mapplic-sidebar-header-opened'); 1068 }); 1069 1070 this.input.appendTo(this.headerwrap); 1071 1072 // clear search 1073 this.clear.on('click touchstart', function(e) { 1074 e.preventDefault(); 1075 s.input.val(''); 1076 s.tags.empty(); 1077 if (s.tags.children().length < 1) s.el.removeClass('mapplic-sidebar-tagsrow'); 1078 self.directory.filters = {}; 1079 1080 s.search(); 1081 }); 1082 1083 this.toggle = $('<button></button>').append(getIcon('icon-filter')).addClass('mapplic-search-toggle').click(function(e) { 1084 e.preventDefault(); 1085 s.el.toggleClass('mapplic-sidebar-header-opened'); 1086 }).appendTo(this.headerwrap); 1087 1088 // tags 1089 this.tags = $('<div></div>').addClass('mapplic-filter-tags').appendTo(this.headerwrap); 1090 1091 // filters
1092 this.filter = $('<div></div>').addClass('mapplic-filter').appendTo(this.header); 1093 1094 // dim 1095 this.dim = $('<div></div>').addClass('mapplic-sidebar-dim').click(function() { s.el.removeClass('mapplic-sidebar-header-opened'); }).appendTo(this.el); 1096 } 1097 else this.el.addClass('mapplic-sidebar-nosearch'); 1098 1099 // items 1100 var event = 'mapready'; 1101 if (self.o.csv) event = 'csvready'; 1102 1103 self.el.on(event, function(e, self) { 1104 s.dir = self.directory.getDirectory().addClass('mapplic-list-container').appendTo(s.el); 1105 1106 // alphabetic sort by title 1107 if (self.o.alphabetic) self.directory.sortby(s.dir, 'title'); 1108 }); 1109 1110 if (self.o.searchdescription && self.o.searchfields.indexOf('description') === -1) self.o.searchfields.push('description'); 1111 } 1112 1113 this.addTag = function(item) { 1114 var s = this, 1115 categories = []; 1116 1117 if (self.directory.filters['category']) categories = self.directory.filters['category'].split(','); 1118 1119 if (!categories.includes(item.id)) { 1120 categories.push(item.id); 1121 self.directory.filters['category'] = categories.toString(); 1122 1123 this.el.addClass('mapplic-sidebar-tagsrow'); 1124 1125 var tag = $('<button></button>').addClass('mapplic-tag').text(item.title).attr('aria-label', self.loc.clearfilter + ': ' + item.title).prependTo(this.tags); 1126 $('<span></span>').appendTo(tag); 1127 if (item.color) tag.css('background-color', item.color); 1128 if (item.id) tag.attr('data-id', item.id); 1129 1130 tag.click(function() { 1131 tag.remove(); 1132 if (s.tags.children().length < 1) s.el.removeClass('mapplic-sidebar-tagsrow'); 1133 if (self.directory.filters['category'] == item.id) delete self.directory.filters['category']; 1134 else if (self.directory.filters['category']) { 1135 categories = self.directory.filters['category'].split(','); 1136 var i = categories.indexOf(item.id); 1137 if (i > -1) { 1138 categories.splice(i, 1) 1139 self.directory.filters['category'] = categories.toString(); 1140 } 1141 } 1142 s.search(); 1143 }).appendTo(tag); 1144 1145 s.search(); 1146 this.el.removeClass('mapplic-sidebar-header-opened'); 1147 } 1148 } 1149 1150 this.addCategories = function(categories) { 1151 var s = this, 1152 list = $('<ul></ul>'), 1153 add = false; 1154 1155 if (categories) { 1156 $.each(categories, function(index, category) { 1157 category.nr = 0; 1158 1159 if (!(category.hide == 'true')) { 1160 var item = $('<li></li>').addClass('mapplic-list-category').attr('data-category', category.id); 1161 var link = $('<a></a>').attr('href', '#').prependTo(item); 1162 1163 var thumbnail = self.directory.thumbnail(category.title, category.icon).appendTo(link); 1164 if (category.color && thumbnail) thumbnail.css({'background-color': category.color, 'border-color': category.color }); 1165 1166 var title = $('<h4></h4').text(category.title).appendTo(link); 1167 if (!category.about) title.addClass('mapplic-margin'); 1168 else $('<span></span>').addClass('mapplic-about').html(category.about).appendTo(link); 1169 category.count = $('<span></span>').text('(0)').addClass('mapplic-list-count').appendTo(title); 1170 1171 var toggle = self.legend.newToggle(category) 1172 if (toggle) toggle.appendTo(item); 1173 1174 1175 link.on('click', function(e) { 1176 e.preventDefault(); 1177 if (category.nr < 1 && toggle) $('> input', toggle).trigger('click'); 1178 else { 1179 //s.input.val(''); 1180 s.addTag(category); 1181 } 1182 }); 1183 1184 category.list = item; 1185 item.appendTo(list); 1186 add = true; 1187 } 1188 }); 1189 1190 if (add) { 1191 s.el.addClass('mapplic-sidebar-filterable'); 1192 1193 //$('<h5></h5>').text('CATEGORIES').appendTo(s.filter); 1194 1195 list.appendTo(s.filter); 1196 } 1197 } 1198 } 1199 1200 this.countCategory = function() { 1201 $.each(self.g, function(i, group) { 1202 if (group.count) { 1203 group.count.text('(' + group.nr + ')'); 1204 if (group.nr < 1) group.count.hide(); 1205 else group.count.show(); 1206 } 1207 }); 1208 } 1209 1210 // search 1211 this.search = function() { 1212 var s = this; 1213 1214 if (self.o.highlight) { 1215 self.map.removeClass('mapplic-filtered'); 1216 $('.mapplic-highlight', self.map).removeClass('mapplic-highlight'); 1217 } 1218 1219 $.each(self.l, function(i, location) { 1220 var matched = true; // matched false, current || 1221 1222 $.each(self.directory.filters, function(attribute, filter) { 1223 var current = false;
1224 1225 if (attribute == 'keyword') $.each(self.o.searchfields, function(i, field) { if (location[field] && !current) current = !(self.directory.normalizeString(location[field]).indexOf(self.directory.normalizeString(filter)) == -1); }); 1226 else if (location.category && attribute == 'category') { 1227 var categories = location.category; 1228 if (typeof categories == 'string') categories = categories.split(','); 1229 1230 categories.forEach(function(category) { 1231 if (self.directory.filters['category'].split(',').includes(category)) current = true; 1232 }); 1233 } 1234 else if (location[attribute] && location[attribute].indexOf(filter) != -1) current = true; 1235 1236 matched = matched && current; 1237 }); 1238 1239 var item = $('.mapplic-dir-item[data-location="' + location.id + '"]', self.el); 1240 if (matched) { 1241 item.show(); 1242 if (self.o.highlight && !$.isEmptyObject(self.directory.filters) && location.el) location.el.addClass('mapplic-highlight'); // highlight 1243 } 1244 else item.hide(); 1245 }); 1246 1247 if (!$.isEmptyObject(self.directory.filters)) { 1248 if (self.o.highlight) self.map.addClass('mapplic-filtered'); 1249 self.el.addClass('mapplic-search-active'); 1250 } 1251 else self.el.removeClass('mapplic-search-active'); 1252 } 1253 } 1254 1255 // developer tools 1256 function DevTools() { 1257 this.el = null; 1258 1259 this.init = function() { 1260 this.el = $('<div></div>').addClass('mapplic-coordinates').appendTo(self.container.el); 1261 this.el.append('x: '); 1262 $('<code></code>').addClass('mapplic-coordinates-x').appendTo(this.el); 1263 this.el.append(' y: '); 1264 $('<code></code>').addClass('mapplic-coordinates-y').appendTo(this.el); 1265 1266 $('.mapplic-layer', self.map).on('mousemove', function(e) { 1267 var x = (e.pageX - self.map.offset().left) / self.map.width() / self.scale, 1268 y = (e.pageY - self.map.offset().top) / self.map.height() / self.scale; 1269 $('.mapplic-coordinates-x').text(parseFloat(x).toFixed(4)); 1270 $('.mapplic-coordinates-y').text(parseFloat(y).toFixed(4)); 1271 }); 1272 1273 return this; 1274 } 1275 } 1276 1277 // clear button 1278 function ClearButton() { 1279 this.el = null; 1280 1281 this.init = function() { 1282 this.el = $('<button></button>').text(self.loc.resetzoom).append(getIcon('icon-reset')).addClass('mapplic-button mapplic-clear-button').appendTo(self.container.el); 1283 1284 if (!self.o.zoombuttons) this.el.css('bottom', '0'); 1285 1286 this.el.on('click touchstart', function(e) { 1287 e.preventDefault(); 1288 self.hideLocation(); 1289 self.moveTo(0.5, 0.5, self.fitscale, 400); 1290 }); 1291 1292 return this; 1293 } 1294 1295 this.update = function(scale) { 1296 if (scale == self.fitscale) this.el.stop().fadeOut(200); 1297 else this.el.stop().fadeIn(200); 1298 } 1299 } 1300 1301 // zoom buttons 1302 function ZoomButtons() { 1303 this.el = null; 1304 1305 this.init = function() { 1306 this.el = $('<div></div>').addClass('mapplic-zoom-buttons').appendTo(self.container.el); 1307 1308 // zoom-in button 1309 this.zoomin = $('<button></button>').attr('aria-label', self.loc.zoomin).append(getIcon('icon-plus')).addClass('mapplic-button mapplic-zoomin-button').appendTo(this.el); 1310 this.zoomin.on('click touchstart', function(e) { 1311 e.preventDefault(); 1312 self.container.stopMomentum(); 1313 1314 var scale = self.scale; 1315 self.scale = normalizeScale(scale + scale * 0.8); 1316 1317 self.x = normalizeX(self.x - (self.container.el.width() / 2 - self.x) * (self.scale / scale - 1)); 1318 self.y = normalizeY(self.y - (self.container.el.height() / 2 - self.y) * (self.scale / scale - 1)); 1319 1320 zoomTo(self.x, self.y, self.scale, 400, 'ease'); 1321 }); 1322 1323 // zoom-out button 1324 this.zoomout = $('<button></button>').attr('aria-label', self.loc.zoomout).append(getIcon('icon-minus')).addClass('mapplic-button mapplic-zoomout-button').appendTo(this.el); 1325 this.zoomout.on('click touchstart', function(e) { 1326 e.preventDefault(); 1327 self.container.stopMomentum(); 1328 1329 var scale = self.scale; 1330 self.scale = normalizeScale(scale - scale * 0.4); 1331 1332 self.x = normalizeX(self.x - (self.container.el.width() / 2 - self.x) * (self.scale / scale - 1)); 1333 self.y = normalizeY(self.y - (self.container.el.height() / 2 - self.y) * (self.scale / scale - 1)); 1334 1335 zoomTo(self.x, self.y, self.scale, 400, 'ease'); 1336 }); 1337 1338 return this; 1339 } 1340 1341 this.update = function(scale) { 1342 this.zoomin.removeAttr('disabled'); 1343 this.zoomout.removeAttr('disabled'); 1344 if (scale == self.fitscale) this.zoomout.attr('disabled','disabled'); 1345 else if (scale == 1) this.zoomin.attr('disabled','disabled'); 1346 } 1347 } 1348 1349 // fullscreen 1350 function Fullscreen() { 1351 this.el = null; 1352 this.fsh = null; // fullscreen placeholder 1353 this.esh = null; // element placeholder 1354 1355 this.init = function() { 1356 var s = this; 1357 1358 this.fph = $('<div></div>').addClass('mapplic-fsh').prependTo('body'); 1359 this.eph = $('<div></div>').addClass('mapplic-esh').insertBefore(self.el); 1360 1361 // fullscreen button 1362 this.el = $('<button></button>').append(getIcon('icon-fullscreen')).append(getIcon('icon-fullscreen-exit')).addClass('mapplic-button mapplic-fullscreen-button').click(function(e) { 1363 self.el.toggleClass('mapplic-fullscreen'); 1364 1365 if (self.el.hasClass('mapplic-fullscreen')) self.el.insertBefore(s.fph); 1366 else self.el.insertBefore(s.eph); 1367 1368 $(document).resize(); 1369 }).appendTo(self.container.el); 1370 1371 // esc key 1372 $(document).on('keyup.mapplic', function(e) { 1373 if ((e.keyCode === 27) && $('.mapplic-fullscreen')[0]) { 1374 $('.mapplic-element.mapplic-fullscreen').removeClass('mapplic-fullscreen'); 1375 self.el.insertBefore(s.eph); 1376 $(document).resize(); 1377 } 1378 }); 1379 } 1380 } 1381 1382 // styles 1383 function Styles() { 1384 this.data = null; 1385 1386 this.init = function(data) { 1387 this.data = data; 1388 this.process(data); 1389 return this; 1390 } 1391 1392 this.process = function(styles) { 1393 var css = ''; 1394 1395 // basecolor 1396 if (self.o.basecolor) css += this.rule('.mapplic-fullscreen, .mapplic-legend', 'background-color', self.o.basecolor); 1397 1398 // bgcolor 1399 if (self.o.bgcolor) { 1400 css += this.rule( 1401 '.mapplic-button, .mapplic-tooltip-close .mapplic-icon, .mapplic-levels-select, .mapplic-levels button, .mapplic-level-switcher button.mapplic-selected, .mapplic-element *::-webkit-scrollbar-track, .mapplic-list-container, .mapplic-filter, .mapplic-sidebar-header, .mapplic-tooltip-wrap, .mapplic-lightbox, .mapplic-toggle:before', 1402 'background-color', 1403 self.o.bgcolor 1404 ); 1405 css += this.rule('.mapplic-legend-key, .mapplic-element *::-webkit-scrollbar-thumb', 'border-color', self.o.bgcolor); 1406 css += this.rule('.mapplic-tooltip:after', 'border-color', self.o.bgcolor + ' transparent transparent transparent !important'); 1407 css += this.rule('.mapplic-tooltip-bottom.mapplic-tooltip:after', 'border-color', 'transparent transparent ' + self.o.bgcolor + ' transparent !important'); 1408 } 1409 1410 // bgcolor2 1411 if (self.o.bgcolor2) { 1412 css += this.rule( 1413 '.mapplic-thumbnail-placeholder, .mapplic-level-switcher button, .mapplic-sidebar .mapplic-dir-item > a:hover, .mapplic-sidebar-header-opened.mapplic-sidebar-filterable .mapplic-search-toggle, .mapplic-sidebar .mapplic-dir-item > a:focus, .mapplic-sidebar .mapplic-dir-item.mapplic-active > a, .mapplic-list-category > a:hover, .mapplic-zoom-buttons button:disabled, .mapplic-levels button:disabled', 1414 'background-color', 1415 self.o.bgcolor2 1416 ); 1417 css += this.rule('a.mapplic-zoomin-button', 'border-color', self.o.bgcolor2); 1418 } 1419 1420 // headingcolor 1421 if (self.o.headingcolor) { 1422 css += this.rule('.mapplic-search-input, .mapplic-level-switcher button.mapplic-selected, .mapplic-list-category > a, .mapplic-tooltip-title, .mapplic-lightbox-title, .mapplic-sidebar .mapplic-dir-item h4, .mapplic-element strong, .mapplic-levels-select, .mapplic-list-category h4', 'color', self.o.headingcolor); 1423 css += this.rule('.mapplic-icon', 'fill', self.o.headingcolor); 1424 } 1425 1426 // textcolor 1427 if (self.o.textcolor) css += this.rule('.mapplic-element, .mapplic-element a, .mapplic-level-switcher button, .mapplic-about, .mapplic-list-category > a .mapplic-list-count, .mapplic-search-input::placeholder, .mapplic-lightbox-description', 'color', self.o.textcolor); 1428 1429 // accentcolor 1430 if (self.o.accentcolor) css += this.rule('.mapplic-popup-link, .mapplic-accentcolor', 'background-color', self.o.accentcolor); 1431 1432 if (styles) { 1433 self.s = []; 1434
1435 styles.forEach(function(s) { 1436 // styles object 1437 self.s[s.class] = s; 1438 1439 if (s.base) { 1440 css += '.' + s.class + '.mapplic-clickable:not(g), g.' + s.class + '.mapplic-clickable > * { '; 1441 $.each(s.base, function(prop, val) { css += prop + ': ' + val + '; '; }); 1442 css += '}\n'; 1443 1444 css += '.' + s.class + '.mapplic-pin {\n'; 1445 $.each(s.base, function(prop, val) { 1446 css += ' background-color: ' + val + ';\n'; 1447 css += ' border-color: ' + val + ';\n'; 1448 }); 1449 css += '}\n\n'; 1450 } 1451 1452 if (s.hover) { 1453 css += '.' + s.class + '.mapplic-highlight:not(g), g.' + s.class + '.mapplic-highlight > *, .' + s.class + '.mapplic-clickable:not(g):hover, g.' + s.class + '.mapplic-clickable:hover > * { '; 1454 $.each(s.hover, function(prop, val) { css += prop + ': ' + val + '; ' }); 1455 css += '}\n'; 1456 1457 css += '.' + s.class + '.mapplic-pin.mapplic-highlight, .' + s.class + '.mapplic-pin:hover {\n'; 1458 $.each(s.hover, function(prop, val) { 1459 css += ' background-color: ' + val + ';\n'; 1460 css += ' border-color: ' + val + ';\n'; 1461 }); 1462 css += '}\n\n'; 1463 } 1464 1465 if (s.active) { 1466 css += '.'+ s.class + '.mapplic-active:not(g), g.' + s.class + '.mapplic-active > * { '; 1467 $.each(s.active, function(prop, val) { css += prop + ': ' + val + ' !important; ' }); 1468 css += '}\n'; 1469 1470 css += '.' + s.class + '.mapplic-pin.mapplic-active {\n'; 1471 $.each(s.active, function(prop, val) { 1472 css += ' background-color: ' + val + ';\n'; 1473 css += ' border-color: ' + val + ';\n'; 1474 }); 1475 css += '}\n\n'; 1476 } 1477 }); 1478 } 1479 1480 if (self.o.customcss) css += self.o.customcss; 1481 1482 var style = $('<style></style>').html(css).appendTo('body'); 1483 } 1484 1485 this.rule = function(selector, attribute, value) { 1486 var css = selector + ' {\n'; 1487 css += ' ' + attribute + ': ' + value + ';\n'; 1488 css += '}\n\n'; 1489 1490 return css; 1491 } 1492 } 1493 1494 function Container() { 1495 this.el = null; 1496 this.oldW = 0; 1497 this.oldH = 0; 1498 this.position = {x: 0, y: 0}, 1499 this.momentum = null; 1500 this.levels = {}; 1501 1502 this.init = function() { 1503 this.el = $('<div></div>').addClass('mapplic-container').appendTo(self.el); 1504 self.map = $('<div></div>').addClass('mapplic-map').appendTo(this.el); 1505 1506 self.map.css({ 1507 'width': self.contentWidth, 1508 'height': self.contentHeight 1509 }); 1510 1511 return this; 1512 } 1513 1514 // returns container height (px) 1515 this.calcHeight = function(v) { 1516 var val = Math.min(Math.max(this.getHeight(v), this.getHeight(self.o.minheight)), this.getHeight(self.o.maxheight)); 1517 1518 if ($.isNumeric(val)) return val; 1519 else return false; 1520 } 1521 1522 this.getHeight = function(v) { 1523 var val = v.toString().replace('px', ''); 1524 1525 if ((val == 'auto') && (self.container.el)) val = self.container.el.width() * self.contentHeight / self.contentWidth; 1526 else if (val.slice(-1) == '%') val = $(window).height() * val.replace('%', '') / 100; 1527 1528 return val; 1529 } 1530 1531 this.resetZoom = function() { 1532 var init = self.l['init']; 1533 if (init) { 1534 self.switchLevel(init.level); 1535 if (!self.bboxZoom(init.el)) self.zoomLocation(init); 1536 } 1537 else self.moveTo(0.5, 0.5, self.fitscale, 0); 1538 } 1539 1540 this.revealChild = function(parent) { 1541 $('.mapplic-pin[data-location^=' + parent.id + '-]', self.map).addClass('mapplic-revealed'); 1542 $('.mapplic-map-image [id^=' + parent.id + '-]', self.map).addClass('mapplic-revealed'); 1543 } 1544 1545 this.revealZoom = function(zoom) { 1546 $('.mapplic-pin[data-reveal]', self.map).each(function() { 1547 var reveal = $(this).data('reveal'); 1548 if (zoom * self.o.maxscale >= reveal) $(this).addClass('mapplic-revealed'); 1549 else $(this).removeClass('mapplic-revealed'); 1550 }); 1551 } 1552 1553 this.coverAll = function() { 1554 $('.mapplic-revealed', self.map).removeClass('mapplic-revealed'); 1555 } 1556 1557 this.stopMomentum = function() { 1558 cancelAnimationFrame(this.momentum); 1559 if (this.momentum != null) { 1560 self.x = this.position.x; 1561 self.y = this.position.y; 1562 } 1563 this.momentum = null; 1564 } 1565 1566 this.addLevelSwitcher = function() { 1567 if (self.data.levels.length > 1) {
1568 var control = $('<div></div>').addClass('mapplic-level-switcher'); 1569 self.data.levels.forEach(function(level, i) { 1570 var button = $('<button></button>').attr('data-level', level.id).text(level.title).prependTo(control).click(function(e) { 1571 e.preventDefault(); 1572 self.switchLevel(level.id); 1573 }); 1574 if (level.show) button.addClass('mapplic-selected'); 1575 }); 1576 1577 this.el.append(control); 1578 1579 self.el.on('levelswitched', function(e, target) { 1580 $('button', control).removeClass('mapplic-selected'); 1581 $('button[data-level="' + target + '"]', control).addClass('mapplic-selected'); 1582 }); 1583 } 1584 } 1585 1586 this.addControls = function() { 1587 self.map.addClass('mapplic-zoomable'); 1588 1589 document.ondragstart = function() { return false; } // IE drag fix 1590 1591 // momentum 1592 var friction = 0.85, 1593 mouse = {x: 0, y: 0}, 1594 pre = {x: 0, y: 0}, 1595 previous = {x: this.position.x, y: this.position.y}, 1596 velocity = {x: 0, y: 0}; 1597 1598 var s = this; 1599 var momentumStep = function() { 1600 s.momentum = requestAnimationFrame(momentumStep); 1601 1602 if (self.map.hasClass('mapplic-dragging')) { 1603 pre.x = previous.x; 1604 pre.y = previous.y; 1605 1606 previous.x = s.position.x; 1607 previous.y = s.position.y; 1608 1609 s.position.x = mouse.x; 1610 s.position.y = mouse.y; 1611 1612 velocity.x = previous.x - pre.x; 1613 velocity.y = previous.y - pre.y; 1614 } 1615 else { 1616 s.position.x += velocity.x; 1617 s.position.y += velocity.y; 1618 1619 velocity.x *= friction; 1620 velocity.y *= friction; 1621 1622 if (Math.abs(velocity.x) + Math.abs(velocity.y) < 0.1) { 1623 s.stopMomentum(); 1624 self.x = s.position.x; 1625 self.y = s.position.y; 1626 } 1627 } 1628 s.position.x = normalizeX(s.position.x); 1629 s.position.y = normalizeY(s.position.y); 1630 1631 zoomTo(s.position.x, s.position.y); 1632 } 1633 1634 // drag & drop 1635 $('.mapplic-map-image', self.map).on('mousedown', function(e) { 1636 self.dragging = false; 1637 self.map.addClass('mapplic-dragging'); 1638 1639 s.stopMomentum(); 1640 var initial = { 1641 x: e.pageX - self.x, 1642 y: e.pageY - self.y 1643 }; 1644 1645 mouse.x = normalizeX(e.pageX - initial.x); 1646 mouse.y = normalizeY(e.pageY - initial.y); 1647 momentumStep(); 1648 1649 self.map.on('mousemove', function(e) { 1650 self.dragging = true; 1651 1652 mouse.x = normalizeX(e.pageX - initial.x); 1653 mouse.y = normalizeY(e.pageY - initial.y); 1654 }); 1655 1656 $(document).on('mouseup.mapplic', function() { 1657 $(document).off('mouseup.mapplic'); 1658 self.map.off('mousemove'); 1659 self.map.removeClass('mapplic-dragging'); 1660 }); 1661 }); 1662 1663 // mousewheel 1664 if (self.o.mousewheel) $('.mapplic-map-image', self.el).on('mousewheel DOMMouseScroll', self.mouseWheel); 1665 1666 // touch 1667 var init1 = null, 1668 init2 = null, 1669 initD = 0, 1670 initScale = null; 1671 1672 $('.mapplic-map-image', self.map).on('touchstart', function(e) { 1673 e.preventDefault(); 1674 var eo = e.originalEvent; 1675 1676 if (eo.touches.length == 1) { 1677 self.map.addClass('mapplic-dragging'); 1678 self.dragging = false; 1679 1680 s.stopMomentum(); 1681 1682 init1 = { 1683 x: eo.touches[0].pageX - self.x, 1684 y: eo.touches[0].pageY - self.y 1685 }; 1686 1687 self.firstcoord = { x: eo.touches[0].pageX, y: eo.touches[0].pageY }; 1688 1689 mouse = { 1690 x: normalizeX(eo.touches[0].pageX - init1.x), 1691 y: normalizeY(eo.touches[0].pageY - init1.y) 1692 }; 1693 momentumStep(); 1694 1695 self.map.on('touchmove', function(e) { 1696 e.preventDefault(); 1697 self.dragging = true; 1698 var eo = e.originalEvent; 1699 1700 if (eo.touches.length == 1) { 1701 mouse.x = normalizeX(eo.touches[0].pageX - init1.x); 1702 mouse.y = normalizeY(eo.touches[0].pageY - init1.y); 1703 1704 self.lastcoord = { x: eo.touches[0].pageX, y: eo.touches[0].pageY }; 1705 } 1706 else if (eo.touches.length > 1) { 1707 var pos = { 1708 x: (eo.touches[0].pageX + eo.touches[1].pageX)/2, 1709 y: (eo.touches[0].pageY + eo.touches[1].pageY)/2 1710 } 1711 1712 var dist = Math.sqrt(Math.pow(eo.touches[0].pageX - eo.touches[1].pageX, 2) + Math.pow(eo.touches[0].pageY - eo.touches[1].pageY, 2)) / initD; 1713 1714 var scale = self.scale; 1715 self.scale = normalizeScale(initScale * dist); 1716 1717 self.x = normalizeX(self.x - (pos.x - self.container.el.offset().left - self.x) * (self.scale/scale - 1)); 1718 self.y = normalizeY(self.y - (pos.y - self.container.el.offset().top - self.y) * (self.scale/scale - 1)); 1719 1720 zoomTo(self.x, self.y, self.scale, 100, 'ease'); 1721 } 1722 }); 1723 1724 $(document).on('touchend.mapplic', function(e) { 1725 e.preventDefault(); 1726 var dragback = null, 1727 eo = e.originalEvent; 1728 1729 if (eo.touches.length == 0) { 1730 clearTimeout(dragback); 1731 $(document).off('touchend.mapplic'); 1732 self.map.off('touchmove'); 1733 self.map.removeClass('mapplic-dragging'); 1734 self.dragging = false;
1735 } 1736 else if (eo.touches.length == 1) { 1737 dragback = setTimeout(function() { 1738 self.map.addClass('mapplic-dragging'); 1739 self.dragging = true; 1740 1741 s.stopMomentum(); 1742 init1 = { 1743 x: eo.touches[0].pageX - self.x, 1744 y: eo.touches[0].pageY - self.y 1745 }; 1746 1747 mouse = { 1748 x: normalizeX(eo.touches[0].pageX - init1.x), 1749 y: normalizeY(eo.touches[0].pageY - init1.y) 1750 }; 1751 1752 momentumStep(); 1753 }, 60); 1754 } 1755 }); 1756 } 1757 1758 1759 // pinch 1760 else if (eo.touches.length == 2) { 1761 self.dragging = true; 1762 self.map.addClass('mapplic-dragging'); 1763 1764 s.stopMomentum(); 1765 1766 init2 = { x: eo.touches[1].pageX - self.x, y: eo.touches[1].pageY - self.y }; 1767 initD = Math.sqrt(Math.pow(init1.x - init2.x, 2) + Math.pow(init1.y - init2.y, 2)); 1768 initScale = self.scale; 1769 1770 } 1771 }); 1772 } 1773 } 1774 1775 // functions 1776 var processData = function(data) { 1777 self.data = data; 1778 self.g = {}; 1779 self.l = {}; 1780 var shownLevel = null; 1781 1782 // extend options 1783 self.o = $.extend(self.o, data); 1784 $.each(self.el.data(), function(i, v) { self.o[i] = v; }); 1785 self.o.zoommargin = parseFloat(self.o.zoommargin); 1786 self.o.maxscale = parseFloat(self.o.maxscale); 1787 1788 // more text 1789 if (self.o.moretext) self.loc.more = self.o.moretext; 1790 1791 // height of container 1792 if (self.el.data('height')) self.o.height = self.el.data('height'); 1793 self.contentWidth = parseFloat(data.mapwidth); 1794 self.contentHeight = parseFloat(data.mapheight); 1795 1796 // limiting to scale 1 1797 self.contentWidth = self.contentWidth * self.o.maxscale; 1798 self.contentHeight = self.contentHeight * self.o.maxscale; 1799 1800 // create container 1801 self.container = new Container().init(); 1802 1803 // styles 1804 self.styles = new Styles().init(self.o.styles); 1805 1806 // create minimap 1807 if (self.o.minimap) self.minimap = new Minimap().init(); 1808 1809 // create legend 1810 self.legend = new Legend().init(); 1811 self.legend.build(data.groups || data.categories); 1812 1813 // directory 1814 self.directory = new Directory().init(); 1815 1816 // create sidebar 1817 if (self.o.sidebar) { 1818 self.sidebar = new Sidebar(); 1819 self.sidebar.init(); 1820 } 1821 else self.container.el.css('width', '100%'); 1822 1823 // groups 1824 self.groups = new Groups(); 1825 self.groups.addGroups(data.groups || data.categories); 1826 1827 // trigger event 1828 self.el.trigger('mapstart', self); 1829 1830 // iterate through levels 1831 var levelnr = 0, 1832 toload = 0; 1833 1834 if (data.levels) { 1835 $.each(data.levels, function(index, level) { 1836 var source = level.map, 1837 extension = source.substr((source.lastIndexOf('.') + 1)).toLowerCase(); 1838 1839 // new map layer
1840 var layer = $('<div></div>').addClass('mapplic-layer').attr('data-floor', level.id).appendTo(self.map); 1841 switch (extension) { 1842 1843 // image formats 1844 case 'jpg': case 'jpeg': case 'png': case 'gif': 1845 var mapimage = $('<div></div>').addClass('mapplic-map-image').appendTo(layer); 1846 $('<img>').attr('src', source).attr('aria-hidden', 'true').addClass('mapplic-map-image').appendTo(mapimage); 1847 if (level.locations) self.addLocations(level.locations, level.id); 1848 break; 1849 1850 // vector format 1851 case 'svg': 1852 toload++; 1853 var mapimage = $('<div></div>').addClass('mapplic-map-image').appendTo(layer); 1854 1855 $('<div></div>').load(source, function() { 1856 1857 // sanitize svg - XSS protection 1858 $('script', this).remove(); 1859 mapimage.html($(this).html()); 1860 1861 // illustrator duplicate id fix 1862 $(self.o.selector, mapimage).each(function() { 1863 var id = $(this).attr('id'); 1864 if (id) $(this).attr('id', id.replace(/_[1-9]+_$/g, '')); 1865 }); 1866 1867 // add locations 1868 if (level.locations) self.addLocations(level.locations, level.id); 1869 1870 // click event 1871 $(self.o.selector, mapimage).on('click touchend', function(e) { 1872 var shift = Math.abs(self.firstcoord.x - self.lastcoord.x) + Math.abs(self.firstcoord.y - self.lastcoord.y); 1873 if (!self.dragging || shift < 4) self.showLocation($(this).attr('id'), 600); 1874 }); 1875 1876 // autopopulate 1877 if (self.o.autopopulate) { 1878 var ap = []; 1879 $(self.o.selector, mapimage).each(function() { 1880 var id = $(this).attr('id'), 1881 location = self.l[id]; 1882 1883 if (!location) { 1884 location = { 1885 id: id, 1886 title: id.charAt(0).toUpperCase() + id.slice(1), 1887 pin: 'hidden' 1888 }; 1889 ap.push(location); 1890 } 1891 }); 1892 self.addLocations(ap, level.id); 1893 } 1894 1895 // trigger event(s) 1896 self.el.trigger('svgloaded', [mapimage, level.id]); 1897 toload--; 1898 if (toload == 0) mapReady(); 1899 }); 1900 break; 1901 1902 // others 1903 default: 1904 alert('File type ' + extension + ' is not supported!'); 1905 } 1906 1907 // create new minimap layer 1908 if (self.minimap) self.minimap.addLayer(level); 1909 1910 // shown level 1911 if (!shownLevel || level.show) shownLevel = level.id; 1912 1913 levelnr++; 1914 }); 1915 } 1916 1917 // COMPONENTS 1918 self.tooltips = Array(); 1919 if (self.o.lightbox && $.magnificPopup) self.lightbox = new Lightbox().init(); 1920 if (self.o.hovertip) self.hovertip = new HoverTooltip().init(); 1921 if (self.o.external) self.external = new External().init(); 1922 if (self.o.clearbutton) self.clearbutton = new ClearButton().init(); 1923 if (self.o.zoombuttons) self.zoombuttons = new ZoomButtons().init(); 1924 if (self.o.fullscreen) self.fullscreen = new Fullscreen().init(); 1925 if (self.o.developer) self.devtools = new DevTools().init(); 1926 1927 self.container.addLevelSwitcher(); 1928 self.switchLevel(shownLevel); 1929 1930 if (self.o.portrait === 'true') self.o.portrait = true; 1931 1932 // resize 1933 $(window).resize(function() { 1934 if (self.o.portrait == true || $.isNumeric(self.o.portrait) && self.el.width() < parseFloat(self.o.portrait)) { 1935 self.el.addClass('mapplic-portrait'); 1936 if (self.el.hasClass('mapplic-fullscreen')) self.container.el.height($(window).height()); 1937 else self.container.el.height(self.container.calcHeight(self.o.height)); 1938 } 1939 else { 1940 self.el.removeClass('mapplic-portrait'); 1941 self.container.el.height('100%'); 1942 self.el.height(self.container.calcHeight(self.o.height)); 1943 } 1944 1945 var wr = self.container.el.width() / self.contentWidth, 1946 hr = self.container.el.height() / self.contentHeight; 1947 1948 if (self.o.mapfill) { 1949 if (wr > hr) self.fitscale = wr; 1950 else self.fitscale = hr; 1951 } 1952 else { 1953 if (wr < hr) self.fitscale = wr; 1954 else self.fitscale = hr; 1955 } 1956 1957 if (self.container.oldW != self.container.el.width() || self.container.oldH != self.container.el.height()) { 1958 1959 self.container.oldW = self.container.el.width(); 1960 self.container.oldH = self.container.el.height(); 1961 1962 self.container.resetZoom(); 1963 } 1964 }).resize(); 1965 1966 // deeplinking 1967 if (self.o.deeplinking) { 1968 if (history.pushState && (typeof URL == 'function')) self.deeplinking = new Deeplinking(); 1969 else self.deeplinking = new DeeplinkingHash(); 1970
1971 self.deeplinking.init(); 1972 } 1973 1974 // trigger event 1975 if (toload == 0) mapReady(); 1976 1977 // controls 1978 if (self.o.zoom) self.container.addControls(); 1979 self.firstcoord = self.lastcoord = {}; 1980 1981 // link to locations 1982 $(document).on('click', '.mapplic-location', function(e) { 1983 e.preventDefault(); 1984 self.showLocation($(this).attr('href').substr(1), 400); 1985 $('html, body').animate({ scrollTop: self.container.el.offset().top }, 400); 1986 }); 1987 } 1988 1989 var mapReady = function() { 1990 // separate location array 1991 self.addLocations(self.data.locations); 1992 1993 // apply toggle 1994 self.legend.applyToggles(); 1995 1996 // CSV support 1997 if (self.o.csv) { 1998 if (typeof Papa === 'undefined') { 1999 console.warn('CSV parser missing. Please make sure the library is loaded.'); 2000 } 2001 else { 2002 Papa.parse(self.o.csv, { 2003 header: true, 2004 download: true, 2005 encoding: "UTF-8", 2006 skipEmptyLines: true, 2007 complete: function(results, file) { 2008 self.addLocations(results.data); 2009 $('.mapplic-pin', self.map).css({ 'transform': 'scale(' + 1/self.scale + ')' }); 2010 if (self.deeplinking) self.deeplinking.check(0); 2011 2012 self.el.trigger('csvready', self); 2013 } 2014 }); 2015 } 2016 } 2017 2018 self.container.resetZoom(); 2019 if (self.deeplinking) self.deeplinking.check(0); 2020 2021 // landmark mode 2022 if (self.el.data('landmark')) self.o.landmark = self.el.data('landmark'); 2023 if (self.o.landmark) { 2024 // Custom settings 2025 self.o.sidebar = false; 2026 self.o.zoombuttons = false; 2027 self.o.deeplinking = false; 2028 self.showLocation(self.o.landmark, 0, true); 2029 } 2030 2031 // trigger event 2032 self.el.trigger('mapready', self); 2033 } 2034 2035 /* PRIVATE METHODS */ 2036 2037 // Web Mercator (EPSG:3857) lat/lng projection 2038 var latlngToPos = function(lat, lng) { 2039 var deltaLng = self.data.rightLng - self.data.leftLng, 2040 bottomLatDegree = self.data.bottomLat * Math.PI / 180, 2041 mapWidth = ((self.data.mapwidth / deltaLng) * 360) / (2 * Math.PI), 2042 mapOffsetY = (mapWidth / 2 * Math.log((1 + Math.sin(bottomLatDegree)) / (1 - Math.sin(bottomLatDegree)))); 2043 2044 lat = lat * Math.PI / 180; 2045 2046 return { 2047 x: ((lng - self.data.leftLng) * (self.data.mapwidth / deltaLng)) / self.data.mapwidth, 2048 y: (self.data.mapheight - ((mapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY)) / self.data.mapheight 2049 }; 2050 } 2051 2052 var estimatedPosition = function(element) { 2053 if (!element || !(element[0] instanceof SVGElement)) return false; 2054 2055 var bbox = element[0].getBBox(); 2056 2057 var padding = 40, 2058 wr = self.container.el.width() / (bbox.width + padding), 2059 hr = self.container.el.height() / (bbox.height + padding); 2060 2061 return { 2062 x: (bbox.x + bbox.width/2) / self.contentWidth * self.o.maxscale, 2063 y: (bbox.y + bbox.height/2) / self.contentHeight * self.o.maxscale, 2064 scale: Math.min(wr, hr) / self.o.maxscale 2065 } 2066 } 2067 2068 var getIcon = function(name) { 2069 return '<svg class="mapplic-icon mapplic-' + name + '" aria-hidden="true"><use xlink:href="' + self.loc.iconfile + '#' + name + '"></use></svg>'; 2070 } 2071 2072 // normalizing x, y and scale 2073 var normalizeX = function(x) { 2074 var minX = (self.container.el.width() - self.contentWidth * self.scale).toFixed(4); 2075 if (minX < 0) { 2076 if (x > self.o.zoommargin) x = self.o.zoommargin; 2077 else if (x < minX - self.o.zoommargin) x = minX - self.o.zoommargin; 2078 } 2079 else x = minX/2; 2080 2081 return x; 2082 } 2083 2084 var normalizeY = function(y) { 2085 var minY = (self.container.el.height() - self.contentHeight * self.scale).toFixed(4); 2086 if (minY < 0) { 2087 if (y > self.o.zoommargin) y = self.o.zoommargin; 2088 else if (y < minY - self.o.zoommargin) y = minY - self.o.zoommargin; 2089 } 2090 else y = minY/2; 2091 2092 return y; 2093 } 2094 2095 var normalizeScale = function(scale) { 2096 if (self.fitscale > 1) return self.fitscale; // no zoom 2097 2098 if (scale <= self.fitscale) scale = self.fitscale; 2099 else if (scale > 1) scale = 1; 2100 2101 // zoom timeout 2102 clearTimeout(self.zoomTimeout); 2103 self.zoomTimeout = setTimeout(function() { 2104 if (self.zoombuttons) self.zoombuttons.update(scale); 2105 if (self.clearbutton) self.clearbutton.update(scale); 2106 if (scale == self.fitscale) { 2107 self.container.coverAll(); 2108 if (self.o.closezoomout) self.hideLocation(); 2109 } 2110 self.container.revealZoom(scale); 2111 }, 200); 2112 2113 return scale; 2114 } 2115 2116 var moveTimeout = null; 2117 2118 var zoomTo = function(x, y, scale, d) { 2119 d = typeof d !== 'undefined' ? d/1000 : 0; 2120 2121 // move class 2122 self.el.addClass('mapplic-move'); 2123 clearTimeout(moveTimeout); 2124 moveTimeout = setTimeout(function() { 2125 self.el.removeClass('mapplic-move'); 2126 self.el.trigger('positionchanged', location); 2127 }, 400); 2128 2129 // transforms 2130 self.map.css({ 2131 'transition': 'transform ' + d + 's', 2132 'transform': 'translate(' + x.toFixed(3) + 'px ,' + y.toFixed(3) + 'px) scale(' + self.scale.toFixed(3) + ')' 2133 }); 2134 2135 if (scale) { 2136 $('.mapplic-pin, .mapplic-tooltip', self.map).css({ 2137 'transition': 'transform ' + d + 's', 2138 'transform': 'scale(' + 1/scale + ')' 2139 }); 2140 } 2141 2142 if (self.minimap) self.minimap.update(x, y); 2143 2144 // trigger event 2145 self.el.trigger('positionchanged', location); 2146 } 2147 2148 var replaceVars = function(template, location) { 2149 template = template.replace(/\{\{([^}]+)\}\}/g, function (match) { 2150 match = match.slice(2, -2); 2151 var sub = match.split('.'); 2152 if (sub.length > 1) { 2153 var temp = location; 2154 sub.forEach(function (item) { 2155 if (!temp[item]) { 2156 temp = '{{' + match + '}}'; 2157 return; 2158 } 2159 temp = temp[item]; 2160 }); 2161 return temp; 2162 } 2163 else { 2164 if (!location[match]) return '{{' + match + '}}'; 2165 return location[match]; 2166 } 2167 }); 2168 return template; 2169 } 2170 2171 2172 /* PUBLIC METHODS */ 2173 var levelTimeout = null; 2174 self.switchLevel = function(target) { 2175 // no such layer 2176 if (!target) return; 2177 2178 var layer = $('.mapplic-layer[data-floor="' + target + '"]', self.map); 2179 2180 // target layer is already active 2181 if (layer.hasClass('mapplic-visible')) return; 2182 2183 // show target layer 2184 layer.removeClass('mapplic-hidden'); 2185 2186 setTimeout(function() { 2187 var old = $('.mapplic-layer.mapplic-visible', self.map).removeClass('mapplic-visible'); 2188 layer.addClass('mapplic-visible'); 2189 2190 clearTimeout(levelTimeout); 2191 levelTimeout = setTimeout(function() { 2192 $('.mapplic-layer:not([data-floor="' + target + '"])', self.map).addClass('mapplic-hidden'); 2193 }, 300); 2194 2195 // slide animation 2196 if (self.o.animations) { 2197 var found = false;
2198 $('.mapplic-layer', self.map).each(function() { 2199 if ($(this).data('floor') == target) { 2200 $(this).removeClass('mapplic-layer-up').removeClass('mapplic-layer-down'); 2201 found = true; 2202 } 2203 else if (found) $(this).addClass('mapplic-layer-up'); 2204 else $(this).addClass('mapplic-layer-down'); 2205 }); 2206 } 2207 }, 1); 2208 2209 // show target minimap layer 2210 if (self.minimap) self.minimap.show(target); 2211 2212 self.level = target; 2213 2214 // trigger event 2215 self.el.trigger('levelswitched', target); 2216 } 2217 2218 self.mouseWheel = function(e, delta) { 2219 e.preventDefault(); 2220 self.container.stopMomentum(); 2221 2222 var scale = self.scale; 2223 2224 self.scale = normalizeScale(scale + scale * delta / 5); 2225 2226 self.x = normalizeX(self.x - (e.pageX - self.container.el.offset().left - self.x) * (self.scale/scale - 1)); 2227 self.y = normalizeY(self.y - (e.pageY - self.container.el.offset().top - self.y) * (self.scale/scale - 1)); 2228 2229 zoomTo(self.x, self.y, self.scale, 200, 'ease'); 2230 } 2231 2232 self.addTooltip = function(location, check) { 2233 var tooltip = new Tooltip().init(location, check); 2234 self.tooltips.push(tooltip); 2235 2236 return tooltip.wrap[0]; 2237 } 2238 2239 self.closeTooltips = function() { 2240 self.tooltips.forEach(function(t, i) { 2241 t.hide(); 2242 self.tooltips.splice(i, 1); 2243 }); 2244 } 2245 2246 self.moveTo = function(x, y, s, duration, ry) { 2247 duration = typeof duration !== 'undefined' ? duration : 400; 2248 ry = typeof ry !== 'undefined' ? ry : 0.5; 2249 s = typeof s !== 'undefined' ? s : self.scale/self.fitscale; 2250 2251 self.container.stopMomentum(); 2252 2253 self.scale = normalizeScale(s); 2254 self.x = normalizeX(self.container.el.width() * 0.5 - self.scale * self.contentWidth * x); 2255 self.y = normalizeY(self.container.el.height() * ry - self.scale * self.contentHeight * y); 2256 2257 zoomTo(self.x, self.y, self.scale, duration); 2258 } 2259 2260 self.bboxZoom = function(element) { 2261 var pos = estimatedPosition(element); 2262 if (!pos) return false; 2263 2264 self.moveTo(pos.x, pos.y, pos.scale, 600); 2265 return true; 2266 } 2267 2268 // adding locations 2269 self.addLocations = function(locations, levelid) { 2270 $.each(locations, function(index, location) { 2271 if (location.id) self.addLocation(location, levelid); 2272 }); 2273 } 2274 2275 self.addLocation = function(location, levelid) { 2276 // jump if location ID exists 2277 if (self.l[location.id]) return true; 2278 2279 // building the location object 2280 self.l[location.id] = location; 2281 2282 // groups 2283 var groups = location.category; 2284 if (groups) { 2285 if (typeof groups == 'string') groups = groups.toString().split(','); 2286 groups.forEach(function(group) { if (self.g[group]) self.g[group].nr++; }); 2287 } 2288 2289 // cascaded fill 2290 var fill = (location && location.fill) || 2291 (location.category && self.g[location.category] && self.g[location.category].color) || 2292 (location.category && location.category[0] && self.g[location.category[0]] && self.g[location.category[0]].color) || 2293 self.o.fillcolor || false; 2294 2295 // cascade style to attribute styleD! 2296 location.styled = (location && location.style) || 2297 (location.category && self.g[location.category] && self.g[location.category].style) || 2298 (location.category && location.category[0] && self.g[location.category[0]] && self.g[location.category[0]].style) || 2299 self.o.defaultstyle || false; 2300 2301 if (fill) location.color = fill; 2302 else if (location.styled) location.color = location.styled.base; 2303 2304 // interactive element 2305 var elem = $('[id^=MLOC] > *[id="' + location.id + '"], [id^=landmark] > *[id="' + location.id + '"]', self.map); 2306 if (elem.length > 0) { 2307 location.el = elem; 2308 location.el.addClass('mapplic-clickable'); 2309 2310 if (location.styled) { 2311 location.el.addClass(location.styled); 2312 } 2313 else if (fill) { 2314 location.el.css('fill', fill); 2315 $('> *', location.el).css('fill', fill); 2316 } 2317 } 2318 2319 // first level if not set 2320 if (!location.level) { 2321 if (levelid) location.level = levelid; 2322 else if (location.el && location.el.closest('.mapplic-layer').data('floor')) location.level = location.el.closest('.mapplic-layer').data('floor'); 2323 else location.level = self.data.levels[0].id; 2324 } 2325 2326 // description vars 2327 if (location.description) location.description = replaceVars(location.description, location); 2328 2329 // geolocation 2330 if (location.lat && location.lng) { 2331 var pos = latlngToPos(location.lat, location.lng); 2332 location.x = pos.x; 2333 location.y = pos.y; 2334 } 2335 2336 // estimated position 2337 if ((!location.x || !location.y) && elem) { 2338 var pos = estimatedPosition(location.el); 2339 location.x = pos.x; 2340 location.y = pos.y; 2341 } 2342 2343 // marker 2344 if (!location.pin) location.pin = self.o.marker; 2345 location.marker = self.addMarker(location); 2346 2347 // reveal mode 2348 if (location.action == 'reveal') $('.mapplic-pin[data-location^=' + location.id + ']', self.map).css('visibility', 'hidden'); 2349 2350 if (self.sidebar) self.sidebar.countCategory(); 2351 } 2352 2353 self.addMarker = function(location) { 2354 // hidden marker 2355 if (location.pin.indexOf('hidden') != -1) return false;
2356 2357 var parent = $('.mapplic-layer[data-floor=' + location.level + '] .mapplic-map-image', self.el); 2358 var marker = $('<a></a>').addClass('mapplic-pin').addClass(location.pin.replace('hidden', '')).attr('aria-label', location.title + ' marker').css({'top': (location.y * 100) + '%', 'left': (location.x * 100) + '%'}).appendTo(parent); 2359 marker.on('click touchend', function(e) { 2360 if (e.cancelable) e.preventDefault(); 2361 2362 var shift = Math.abs(self.firstcoord.x - self.lastcoord.x) + Math.abs(self.firstcoord.y - self.lastcoord.y); 2363 if (!self.dragging || shift < 4) self.showLocation(location.id, 600); 2364 }); 2365 2366 if (location.label) { 2367 if (location.label.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g)) marker.css('background-image', 'url(' + location.label + ')'); 2368 else $('<span><span>' + location.label + '</span></span>').appendTo(marker); 2369 } 2370 if (location.reveal) marker.attr('data-reveal', location.reveal).css('visibility', 'hidden'); 2371 if (location.category) { 2372 location.category = location.category.toString(); 2373 marker.attr('data-category', location.category); 2374 } 2375 marker.attr('data-location', location.id); 2376 2377 if (self.o.zoom && self.o.mousewheel) marker.on('mousewheel DOMMouseScroll', self.mouseWheel); 2378 if (location.styled) marker.addClass(location.styled); 2379 if (location.color && location.pin.indexOf('pin-text') > -1) marker.css('color', location.color); 2380 else if (location.color) marker.css({'background-color': location.color, 'border-color': location.color }); 2381 2382 location.el = marker; 2383 return marker; 2384 } 2385 2386 // removing locations 2387 self.removeLocations = function() { 2388 $.each(self.l, function(i, location) { 2389 self.removeLocation(location.id); 2390 }); 2391 } 2392 2393 self.removeLocation = function(location) { 2394 if (location.id) location = location.id; 2395 delete self.l[location]; 2396 $('.mapplic-pin[data-location="' + location + '"]', self.map).remove(); 2397 $('.mapplic-tooltip[data-location="' + location + '"]', self.el).remove(); 2398 $('.mapplic-list-location[data-location="' + location + '"]', self.el).remove(); 2399 $('svg #' + location, self.map).removeClass(); 2400 } 2401 2402 self.getLocationData = function(id) { 2403 return self.l[id]; 2404 } 2405 2406 self.showLocation = function(id, duration, check) { 2407 var location = self.location = self.l[id]; 2408 if (!location) return false; 2409 2410 // trigger event 2411 self.el.trigger('locationopen', location); 2412 2413 var action = (location.action && location.action != 'default') ? location.action : self.o.action; 2414 if (action == 'disabled') return false; 2415 2416 var content = null; 2417 self.closeTooltips(); 2418 2419 switch (action) { 2420 case 'open-link': 2421 window.location.href = location.link; 2422 return false; 2423 case 'open-link-new-tab': 2424 window.open(location.link); 2425 self.location = null; 2426 return false; 2427 case 'select': 2428 if (location.el) { 2429 if (location.el.hasClass('mapplic-active')) { 2430 location.el.removeClass('mapplic-active'); 2431 if (location.list) location.list.removeClass('mapplic-active'); 2432 } 2433 else { 2434 location.el.addClass('mapplic-active'); 2435 if (location.list) location.list.addClass('mapplic-active'); 2436 } 2437 } 2438 return false; 2439 case 'none': 2440 self.hideLocation(); 2441 self.switchLevel(location.level); 2442 if (!self.bboxZoom(location.el)) self.zoomLocation(location); 2443 break; 2444 case 'reveal': 2445 self.hideLocation(); 2446 self.switchLevel(location.level); 2447 self.container.revealChild(location); 2448 if (self.o.zoom) self.bboxZoom(location.el); 2449 break; 2450 case 'external': 2451 self.hideLocation(); 2452 self.switchLevel(location.level); 2453 if (!self.bboxZoom(location.el)) self.zoomLocation(location); 2454 if (self.external) self.external.show(location); 2455 break; 2456 case 'lightbox': 2457 self.hideLocation(); 2458 self.switchLevel(location.level); 2459 content = self.lightbox.show(location); 2460 break; 2461 case 'image': 2462 self.hideLocation(); 2463 self.switchLevel(location.level); 2464 self.lightbox.showImage(location); 2465 break; 2466 case 'route': 2467 check = true; 2468 break; 2469 default: 2470 self.hideLocation(); 2471 self.switchLevel(location.level); 2472 setTimeout(function() { content = self.addTooltip(location, check); }, 2); 2473 } 2474 2475 self.location = self.l[id]; 2476 2477 // active state 2478 $('.mapplic-active', self.scope).removeClass('mapplic-active'); 2479 if (location.el) location.el.addClass('mapplic-active'); 2480 if (location.list) location.list.addClass('mapplic-active'); 2481 2482 // deeplinking 2483 if ((self.deeplinking) && (!check)) self.deeplinking.update(id); 2484 2485 // trigger event 2486 self.el.trigger('locationopened', [location, content]); 2487 } 2488 2489 self.zoomLocation = function(loc) { 2490 var zoom = loc.zoom ? parseFloat(loc.zoom)/self.o.maxscale : 1; 2491 if (self.o.zoom) self.moveTo(loc.x, loc.y, zoom, 600); 2492 } 2493 2494 self.hideLocation = function() { 2495 $('.mapplic-active', self.scope).removeClass('mapplic-active'); 2496 if (self.deeplinking && self.deeplinking.resolved) self.deeplinking.clear(); 2497 if (self.external) self.external.hide(); 2498 self.closeTooltips(); 2499 self.location = null; 2500 2501 // trigger event 2502 self.el.trigger('locationclosed'); 2503 } 2504 2505 self.updateLocation = function(id) { 2506 // remove + add 2507 var location = self.l[id]; 2508 2509 if ((location.id == id) && (location.el.is('a'))) { 2510 // Geolocation 2511 if (location.lat && location.lng) { 2512 var pos = latlngToPos(location.lat, location.lng); 2513 location.x = pos.x; 2514 location.y = pos.y; 2515 } 2516 2517 var top = location.y * 100, 2518 left = location.x * 100; 2519 location.el.css({'top': top + '%', 'left': left + '%'}); 2520 } 2521 } 2522 2523 }; 2524 2525 // jQuery plugin 2526 $.fn.mapplic = function(options) { 2527 2528 return this.each(function() { 2529 var element = $(this); 2530 2531 // plugin already initiated on element 2532 if (element.data('mapplic')) return; 2533 2534 var instance = (new Mapplic(element)).init(options); 2535 2536 // store plugin object in element's data 2537 element.data('mapplic', instance); 2538 }); 2539 }; 2540 2541})(jQuery); 2542 2543// call plugin on map instances 2544jQuery(document).ready(function($) { 2545 $('[id^=mapplic-id]').mapplic(); 2546 2547 // dynamic element 2548 var observer = new MutationObserver(function(mutations) { 2549 mutations.forEach(function(mutation) { 2550 var newNodes = mutation.addedNodes; 2551 if (newNodes !== null) { 2552 $(newNodes).each(function() { 2553 var node = $(this); 2554 if (node.is('[id^=mapplic-id]')) node.mapplic(); 2555 }); 2556 } 2557 }); 2558 }); 2559 2560 observer.observe(document, {attributes: false, childList: true, characterData: false, subtree: true}); 2561});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.