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