vendor: 143,181 bytes, lines 1-4149
1/* ImageMapster 2 Version: 1.2.10 (2/25/2013) 3 4Copyright 2011-2012 James Treworgy 5 6http://www.outsharked.com/imagemapster 7https://github.com/jamietre/ImageMapster 8 9A jQuery plugin to enhance image maps. 10 11*/ 12 13; 14 15/// LICENSE (MIT License) 16/// 17/// Permission is hereby granted, free of charge, to any person obtaining 18/// a copy of this software and associated documentation files (the 19/// "Software"), to deal in the Software without restriction, including 20/// without limitation the rights to use, copy, modify, merge, publish, 21/// distribute, sublicense, and/or sell copies of the Software, and to 22/// permit persons to whom the Software is furnished to do so, subject to 23/// the following conditions: 24/// 25/// The above copyright notice and this permission notice shall be 26/// included in all copies or substantial portions of the Software. 27/// 28/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 30/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 32/// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 33/// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 34/// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 35/// 36/// January 19, 2011 37 38/** @license MIT License (c) copyright B Cavalier & J Hann */ 39 40/** 41* when 42* A lightweight CommonJS Promises/A and when() implementation 43* 44* when is part of the cujo.js family of libraries (http://cujojs.com/) 45* 46* Licensed under the MIT License at: 47* http://www.opensource.org/licenses/mit-license.php 48* 49* @version 1.2.0 50*/ 51 52/*lint-ignore-start*/ 53 54(function (define) { 55 define(function () { 56 var freeze, reduceArray, slice, undef; 57 58 // 59 // Public API 60 // 61 62 when.defer = defer; 63 when.reject = reject; 64 when.isPromise = isPromise; 65 66 when.all = all; 67 when.some = some; 68 when.any = any; 69 70 when.map = map; 71 when.reduce = reduce; 72 73 when.chain = chain; 74 75 /** Object.freeze */ 76 freeze = Object.freeze || function (o) { return o; }; 77 78 /** 79 * Trusted Promise constructor. A Promise created from this constructor is 80 * a trusted when.js promise. Any other duck-typed promise is considered 81 * untrusted. 82 * 83 * @constructor 84 */ 85 function Promise() { } 86 87 Promise.prototype = freeze({ 88 always: function (alwaysback, progback) { 89 return this.then(alwaysback, alwaysback, progback); 90 }, 91 92 otherwise: function (errback) { 93 return this.then(undef, errback); 94 } 95 }); 96 97 /** 98 * Create an already-resolved promise for the supplied value 99 * @private 100 * 101 * @param value anything 102 * @return {Promise} 103 */ 104 function resolved(value) { 105 106 var p = new Promise(); 107 108 p.then = function (callback) { 109 var nextValue; 110 try { 111 if (callback) nextValue = callback(value); 112 return promise(nextValue === undef ? value : nextValue); 113 } catch (e) { 114 return rejected(e); 115 } 116 }; 117 118 return freeze(p); 119 } 120 121 /** 122 * Create an already-rejected {@link Promise} with the supplied 123 * rejection reason. 124 * @private 125 * 126 * @param reason rejection reason 127 * @return {Promise} 128 */ 129 function rejected(reason) { 130 131 var p = new Promise(); 132 133 p.then = function (callback, errback) { 134 var nextValue; 135 try { 136 if (errback) { 137 nextValue = errback(reason); 138 return promise(nextValue === undef ? reason : nextValue) 139 } 140 141 return rejected(reason); 142 143 } catch (e) { 144 return rejected(e); 145 } 146 }; 147 148 return freeze(p); 149 } 150 151 /** 152 * Returns a rejected promise for the supplied promiseOrValue. If 153 * promiseOrValue is a value, it will be the rejection value of the 154 * returned promise. If promiseOrValue is a promise, its 155 * completion value will be the rejected value of the returned promise 156 * 157 * @param promiseOrValue {*} the rejected value of the returned {@link Promise} 158 * 159 * @return {Promise} rejected {@link Promise} 160 */ 161 function reject(promiseOrValue) { 162 return when(promiseOrValue, function (value) { 163 return rejected(value); 164 }); 165 } 166 167 /** 168 * Creates a new, CommonJS compliant, Deferred with fully isolated 169 * resolver and promise parts, either or both of which may be given out 170 * safely to consumers. 171 * The Deferred itself has the full API: resolve, reject, progress, and 172 * then. The resolver has resolve, reject, and progress. The promise 173 * only has then. 174 * 175 * @memberOf when 176 * @function 177 * 178 * @returns {Deferred} 179 */ 180 function defer() { 181 var deferred, promise, listeners, progressHandlers, _then, _progress, complete; 182 183 listeners = []; 184 progressHandlers = []; 185 186 /** 187 * Pre-resolution then() that adds the supplied callback, errback, and progback 188 * functions to the registered listeners 189 * 190 * @private 191 * 192 * @param [callback] {Function} resolution handler 193 * @param [errback] {Function} rejection handler 194 * @param [progback] {Function} progress handler 195 * 196 * @throws {Error} if any argument is not null, undefined, or a Function 197 */ 198 _then = function unresolvedThen(callback, errback, progback) { 199 var deferred = defer(); 200 201 listeners.push(function (promise) { 202 promise.then(callback, errback) 203 .then(deferred.resolve, deferred.reject, deferred.progress); 204 }); 205 206 progback && progressHandlers.push(progback); 207 208 return deferred.promise; 209 }; 210 211 /** 212 * Registers a handler for this {@link Deferred}'s {@link Promise}. Even though all arguments 213 * are optional, each argument that *is* supplied must be null, undefined, or a Function. 214 * Any other value will cause an Error to be thrown. 215 * 216 * @memberOf Promise 217 * 218 * @param [callback] {Function} resolution handler 219 * @param [errback] {Function} rejection handler 220 * @param [progback] {Function} progress handler 221 * 222 * @throws {Error} if any argument is not null, undefined, or a Function 223 */ 224 function then(callback, errback, progback) { 225 return _then(callback, errback, progback); 226 } 227 228 /** 229 * Resolves this {@link Deferred}'s {@link Promise} with val as the 230 * resolution value. 231 * 232 * @memberOf Resolver 233 * 234 * @param val anything 235 */ 236 function resolve(val) { 237 complete(resolved(val)); 238 } 239 240 /** 241 * Rejects this {@link Deferred}'s {@link Promise} with err as the 242 * reason. 243 * 244 * @memberOf Resolver 245 * 246 * @param err anything 247 */ 248 function reject(err) { 249 complete(rejected(err)); 250 } 251 252 /** 253 * @private 254 * @param update 255 */ 256 _progress = function (update) { 257 var progress, i = 0; 258 while (progress = progressHandlers[i++]) progress(update); 259 }; 260 261 /** 262 * Emits a progress update to all progress observers registered with 263 * this {@link Deferred}'s {@link Promise} 264 * 265 * @memberOf Resolver 266 * 267 * @param update anything 268 */ 269 function progress(update) { 270 _progress(update); 271 } 272 273 /** 274 * Transition from pre-resolution state to post-resolution state, notifying 275 * all listeners of the resolution or rejection 276 * 277 * @private 278 * 279 * @param completed {Promise} the completed value of this deferred 280 */ 281 complete = function (completed) { 282 var listener, i = 0; 283 284 // Replace _then with one that directly notifies with the result. 285 _then = completed.then; 286 287 // Replace complete so that this Deferred can only be completed 288 // once. Also Replace _progress, so that subsequent attempts to issue 289 // progress throw. 290 complete = _progress = function alreadyCompleted() { 291 // TODO: Consider silently returning here so that parties who 292 // have a reference to the resolver cannot tell that the promise 293 // has been resolved using try/catch 294 throw new Error("already completed"); 295 }; 296 297 // Free progressHandlers array since we'll never issue progress events 298 // for this promise again now that it's completed 299 progressHandlers = undef; 300 301 // Notify listeners 302 // Traverse all listeners registered directly with this Deferred 303 304 while (listener = listeners[i++]) { 305 listener(completed); 306 } 307 308 listeners = []; 309 }; 310 311 /** 312 * The full Deferred object, with both {@link Promise} and {@link Resolver} 313 * parts 314 * @class Deferred 315 * @name Deferred 316 */ 317 deferred = {}; 318 319 // Promise and Resolver parts 320 // Freeze Promise and Resolver APIs 321 322 promise = new Promise(); 323 promise.then = deferred.then = then; 324 325 /** 326 * The {@link Promise} for this {@link Deferred} 327 * @memberOf Deferred 328 * @name promise 329 * @type {Promise} 330 */ 331 deferred.promise = freeze(promise); 332 333 /** 334 * The {@link Resolver} for this {@link Deferred} 335 * @memberOf Deferred 336 * @name resolver 337 * @class Resolver 338 */ 339 deferred.resolver = freeze({ 340 resolve: (deferred.resolve = resolve), 341 reject: (deferred.reject = reject), 342 progress: (deferred.progress = progress) 343 }); 344 345 return deferred; 346 } 347 348 /** 349 * Determines if promiseOrValue is a promise or not. Uses the feature 350 * test from http://wiki.commonjs.org/wiki/Promises/A to determine if 351 * promiseOrValue is a promise. 352 * 353 * @param promiseOrValue anything 354 * 355 * @returns {Boolean} true if promiseOrValue is a {@link Promise} 356 */ 357 function isPromise(promiseOrValue) { 358 return promiseOrValue && typeof promiseOrValue.then === 'function'; 359 } 360 361 /** 362 * Register an observer for a promise or immediate value. 363 * 364 * @function 365 * @name when 366 * @namespace 367 * 368 * @param promiseOrValue anything 369 * @param {Function} [callback] callback to be called when promiseOrValue is 370 * successfully resolved. If promiseOrValue is an immediate value, callback 371 * will be invoked immediately. 372 * @param {Function} [errback] callback to be called when promiseOrValue is 373 * rejected. 374 * @param {Function} [progressHandler] callback to be called when progress updates 375 * are issued for promiseOrValue. 376 * 377 * @returns {Promise} a new {@link Promise} that will complete with the return 378 * value of callback or errback or the completion value of promiseOrValue if 379 * callback and/or errback is not supplied. 380 */ 381 function when(promiseOrValue, callback, errback, progressHandler) { 382 // Get a promise for the input promiseOrValue 383 // See promise() 384 var trustedPromise = promise(promiseOrValue); 385 386 // Register promise handlers 387 return trustedPromise.then(callback, errback, progressHandler); 388 } 389 390 /** 391 * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if 392 * promiseOrValue is a foreign promise, or a new, already-resolved {@link Promise} 393 * whose resolution value is promiseOrValue if promiseOrValue is an immediate value. 394 * 395 * Note that this function is not safe to export since it will return its 396 * input when promiseOrValue is a {@link Promise} 397 * 398 * @private 399 * 400 * @param promiseOrValue anything 401 * 402 * @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} 403 * returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} 404 * whose resolution value is: 405 * * the resolution value of promiseOrValue if it's a foreign promise, or 406 * * promiseOrValue if it's a value 407 */ 408 function promise(promiseOrValue) { 409 var promise, deferred; 410 411 if (promiseOrValue instanceof Promise) { 412 // It's a when.js promise, so we trust it 413 promise = promiseOrValue; 414 415 } else { 416 // It's not a when.js promise. Check to see if it's a foreign promise 417 // or a value. 418 419 deferred = defer(); 420 if (isPromise(promiseOrValue)) { 421 // It's a compliant promise, but we don't know where it came from, 422 // so we don't trust its implementation entirely. Introduce a trusted 423 // middleman when.js promise 424 425 // IMPORTANT: This is the only place when.js should ever call .then() on 426 // an untrusted promise. 427 promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress); 428 promise = deferred.promise; 429 430 } else { 431 // It's a value, not a promise. Create an already-resolved promise 432 // for it. 433 deferred.resolve(promiseOrValue); 434 promise = deferred.promise; 435 } 436 } 437 438 return promise; 439 } 440 441 /** 442 * Return a promise that will resolve when howMany of the supplied promisesOrValues 443 * have resolved. The resolution value of the returned promise will be an array of 444 * length howMany containing the resolutions values of the triggering promisesOrValues. 445 * 446 * @memberOf when 447 * 448 * @param promisesOrValues {Array} array of anything, may contain a mix 449 * of {@link Promise}s and values 450 * @param howMany 451 * @param [callback] 452 * @param [errback] 453 * @param [progressHandler] 454 * 455 * @returns {Promise} 456 */ 457 function some(promisesOrValues, howMany, callback, errback, progressHandler) { 458 459 checkCallbacks(2, arguments); 460 461 return when(promisesOrValues, function (promisesOrValues) { 462 463 var toResolve, results, ret, deferred, resolver, rejecter, handleProgress, len, i; 464 465 len = promisesOrValues.length >>> 0; 466 467 toResolve = Math.max(0, Math.min(howMany, len)); 468 results = []; 469 deferred = defer(); 470 ret = when(deferred, callback, errback, progressHandler); 471 472 // Wrapper so that resolver can be replaced 473 function resolve(val) { 474 resolver(val); 475 } 476 477 // Wrapper so that rejecter can be replaced 478 function reject(err) { 479 rejecter(err); 480 } 481 482 // Wrapper so that progress can be replaced 483 function progress(update) { 484 handleProgress(update); 485 } 486 487 function complete() { 488 resolver = rejecter = handleProgress = noop; 489 } 490 491 // No items in the input, resolve immediately 492 if (!toResolve) { 493 deferred.resolve(results); 494 495 } else { 496 // Resolver for promises. Captures the value and resolves 497 // the returned promise when toResolve reaches zero. 498 // Overwrites resolver var with a noop once promise has 499 // be resolved to cover case where n < promises.length 500 resolver = function (val) { 501 // This orders the values based on promise resolution order 502 // Another strategy would be to use the original position of 503 // the corresponding promise. 504 results.push(val); 505 506 if (! --toResolve) { 507 complete(); 508 deferred.resolve(results); 509 } 510 }; 511 512 // Rejecter for promises. Rejects returned promise 513 // immediately, and overwrites rejecter var with a noop 514 // once promise to cover case where n < promises.length. 515 // TODO: Consider rejecting only when N (or promises.length - N?) 516 // promises have been rejected instead of only one? 517 rejecter = function (err) { 518 complete(); 519 deferred.reject(err); 520 }; 521 522 handleProgress = deferred.progress; 523 524 // TODO: Replace while with forEach 525 for (i = 0; i < len; ++i) { 526 if (i in promisesOrValues) { 527 when(promisesOrValues[i], resolve, reject, progress); 528 } 529 } 530 } 531 532 return ret; 533 }); 534 } 535 536 /** 537 * Return a promise that will resolve only once all the supplied promisesOrValues 538 * have resolved. The resolution value of the returned promise will be an array 539 * containing the resolution values of each of the promisesOrValues. 540 * 541 * @memberOf when 542 * 543 * @param promisesOrValues {Array|Promise} array of anything, may contain a mix 544 * of {@link Promise}s and values 545 * @param [callback] {Function} 546 * @param [errback] {Function} 547 * @param [progressHandler] {Function} 548 * 549 * @returns {Promise} 550 */ 551 function all(promisesOrValues, callback, errback, progressHandler) { 552 553 checkCallbacks(1, arguments); 554 555 return when(promisesOrValues, function (promisesOrValues) { 556 return _reduce(promisesOrValues, reduceIntoArray, []); 557 }).then(callback, errback, progressHandler); 558 } 559 560 function reduceIntoArray(current, val, i) { 561 current[i] = val; 562 return current; 563 } 564 565 /** 566 * Return a promise that will resolve when any one of the supplied promisesOrValues 567 * has resolved. The resolution value of the returned promise will be the resolution 568 * value of the triggering promiseOrValue. 569 * 570 * @memberOf when 571 * 572 * @param promisesOrValues {Array|Promise} array of anything, may contain a mix 573 * of {@link Promise}s and values 574 * @param [callback] {Function} 575 * @param [errback] {Function} 576 * @param [progressHandler] {Function} 577 * 578 * @returns {Promise} 579 */ 580 function any(promisesOrValues, callback, errback, progressHandler) { 581 582 function unwrapSingleResult(val) { 583 return callback ? callback(val[0]) : val[0]; 584 } 585 586 return some(promisesOrValues, 1, unwrapSingleResult, errback, progressHandler); 587 } 588 589 /** 590 * Traditional map function, similar to `Array.prototype.map()`, but allows 591 * input to contain {@link Promise}s and/or values, and mapFunc may return 592 * either a value or a {@link Promise} 593 * 594 * @memberOf when 595 * 596 * @param promise {Array|Promise} array of anything, may contain a mix 597 * of {@link Promise}s and values 598 * @param mapFunc {Function} mapping function mapFunc(value) which may return 599 * either a {@link Promise} or value 600 * 601 * @returns {Promise} a {@link Promise} that will resolve to an array containing 602 * the mapped output values. 603 */ 604 function map(promise, mapFunc) { 605 return when(promise, function (array) { 606 return _map(array, mapFunc); 607 }); 608 } 609 610 /** 611 * Private map helper to map an array of promises 612 * @private 613 * 614 * @param promisesOrValues {Array} 615 * @param mapFunc {Function} 616 * @return {Promise} 617 */ 618 function _map(promisesOrValues, mapFunc) { 619 620 var results, len, i; 621 622 // Since we know the resulting length, we can preallocate the results 623 // array to avoid array expansions. 624 len = promisesOrValues.length >>> 0; 625 results = new Array(len); 626 627 // Since mapFunc may be async, get all invocations of it into flight 628 // asap, and then use reduce() to collect all the results 629 for (i = 0; i < len; i++) { 630 if (i in promisesOrValues) 631 results[i] = when(promisesOrValues[i], mapFunc); 632 } 633 634 // Could use all() here, but that would result in another array 635 // being allocated, i.e. map() would end up allocating 2 arrays 636 // of size len instead of just 1. Since all() uses reduce() 637 // anyway, avoid the additional allocation by calling reduce 638 // directly. 639 return _reduce(results, reduceIntoArray, results); 640 } 641 642 /** 643 * Traditional reduce function, similar to `Array.prototype.reduce()`, but 644 * input may contain {@link Promise}s and/or values, and reduceFunc 645 * may return either a value or a {@link Promise}, *and* initialValue may 646 * be a {@link Promise} for the starting value. 647 * 648 * @memberOf when 649 * 650 * @param promise {Array|Promise} array of anything, may contain a mix 651 * of {@link Promise}s and values. May also be a {@link Promise} for 652 * an array. 653 * @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total), 654 * where total is the total number of items being reduced, and will be the same 655 * in each call to reduceFunc. 656 * @param initialValue starting value, or a {@link Promise} for the starting value 657 * 658 * @returns {Promise} that will resolve to the final reduced value 659 */ 660 function reduce(promise, reduceFunc, initialValue) { 661 var args = slice.call(arguments, 1); 662 return when(promise, function (array) { 663 return _reduce.apply(undef, [array].concat(args)); 664 }); 665 } 666 667 /** 668 * Private reduce to reduce an array of promises 669 * @private 670 * 671 * @param promisesOrValues {Array} 672 * @param reduceFunc {Function} 673 * @param initialValue {*} 674 * @return {Promise} 675 */ 676 function _reduce(promisesOrValues, reduceFunc, initialValue) { 677 678 var total, args; 679 680 total = promisesOrValues.length; 681 682 // Skip promisesOrValues, since it will be used as 'this' in the call 683 // to the actual reduce engine below. 684 685 // Wrap the supplied reduceFunc with one that handles promises and then 686 // delegates to the supplied. 687 688 args = [ 689 function (current, val, i) { 690 return when(current, function (c) { 691 return when(val, function (value) { 692 return reduceFunc(c, value, i, total); 693 }); 694 }); 695 } 696 ]; 697 698 if (arguments.length > 2) args.push(initialValue); 699 700 return reduceArray.apply(promisesOrValues, args); 701 } 702 703 /** 704 * Ensure that resolution of promiseOrValue will complete resolver with the completion 705 * value of promiseOrValue, or instead with resolveValue if it is provided. 706 * 707 * @memberOf when 708 * 709 * @param promiseOrValue 710 * @param resolver {Resolver} 711 * @param [resolveValue] anything 712 * 713 * @returns {Promise} 714 */ 715 function chain(promiseOrValue, resolver, resolveValue) { 716 var useResolveValue = arguments.length > 2; 717 718 return when(promiseOrValue, 719 function (val) { 720 if (useResolveValue) val = resolveValue; 721 resolver.resolve(val); 722 return val; 723 }, 724 function (e) { 725 resolver.reject(e); 726 return rejected(e); 727 }, 728 resolver.progress 729 ); 730 } 731 732 // 733 // Utility functions 734 // 735 736 /** 737 * Helper that checks arrayOfCallbacks to ensure that each element is either 738 * a function, or null or undefined. 739 * 740 * @private 741 * 742 * @param arrayOfCallbacks {Array} array to check 743 * @throws {Error} if any element of arrayOfCallbacks is something other than 744 * a Functions, null, or undefined. 745 */ 746 function checkCallbacks(start, arrayOfCallbacks) { 747 var arg, i = arrayOfCallbacks.length; 748 while (i > start) { 749 arg = arrayOfCallbacks[--i]; 750 if (arg != null && typeof arg != 'function') throw new Error('callback is not a function'); 751 } 752 } 753 754 /** 755 * No-Op function used in method replacement 756 * @private 757 */ 758 function noop() { } 759 760 slice = [].slice; 761 762 // ES5 reduce implementation if native not available 763 // See: http://es5.github.com/#x15.4.4.21 as there are many 764 // specifics and edge cases. 765 reduceArray = [].reduce || 766 function (reduceFunc /*, initialValue */) { 767 // ES5 dictates that reduce.length === 1 768 769 // This implementation deviates from ES5 spec in the following ways: 770 // 1. It does not check if reduceFunc is a Callable 771 772 var arr, args, reduced, len, i; 773 774 i = 0; 775 arr = Object(this); 776 len = arr.length >>> 0; 777 args = arguments; 778 779 // If no initialValue, use first item of array (we know length !== 0 here) 780 // and adjust i to start at second item 781 if (args.length <= 1) { 782 // Skip to the first real element in the array 783 for (; ;) { 784 if (i in arr) { 785 reduced = arr[i++]; 786 break; 787 } 788 789 // If we reached the end of the array without finding any real 790 // elements, it's a TypeError 791 if (++i >= len) { 792 throw new TypeError(); 793 } 794 } 795 } else { 796 // If initialValue provided, use it 797 reduced = args[1]; 798 } 799 800 // Do the actual reduce 801 for (; i < len; ++i) { 802 // Skip holes 803 if (i in arr) 804 reduced = reduceFunc(reduced, arr[i], i, arr); 805 } 806 807 return reduced; 808 }; 809 810 return when; 811 }); 812})(typeof define == 'function' 813 ? define 814 : function (factory) { 815 typeof module != 'undefined' 816 ? (module.exports = factory()) 817 : (jQuery.mapster_when = factory()); 818 } 819// Boilerplate for AMD, Node, and browser global 820); 821/*lint-ignore-end*/ 822/* ImageMapster core */ 823 824/*jslint laxbreak: true, evil: true, unparam: true */ 825 826/*global jQuery: true, Zepto: true */ 827 828 829(function ($) { 830 // all public functions in $.mapster.impl are methods 831 $.fn.mapster = function (method) { 832 var m = $.mapster.impl; 833 if ($.isFunction(m[method])) { 834 return m[method].apply(this, Array.prototype.slice.call(arguments, 1)); 835 } else if (typeof method === 'object' || !method) { 836 return m.bind.apply(this, arguments); 837 } else { 838 $.error('Method ' + method + ' does not exist on jQuery.mapster'); 839 } 840 }; 841 842 $.mapster = { 843 version: "1.2.10", 844 render_defaults: { 845 isSelectable: true, 846 isDeselectable: true, 847 fade: false, 848 fadeDuration: 150, 849 fill: true, 850 fillColor: '000000', 851 fillColorMask: 'FFFFFF', 852 fillOpacity: 0.7, 853 highlight: true, 854 stroke: false, 855 strokeColor: 'ff0000', 856 strokeOpacity: 1, 857 strokeWidth: 1, 858 includeKeys: '', 859 altImage: null, 860 altImageId: null, // used internally 861 altImages: {} 862 }, 863 defaults: { 864 clickNavigate: false, 865 wrapClass: null, 866 wrapCss: null, 867 onGetList: null, 868 sortList: false, 869 listenToList: false, 870 mapKey: '', 871 mapValue: '', 872 singleSelect: false, 873 listKey: 'value', 874 listSelectedAttribute: 'selected', 875 listSelectedClass: null, 876 onClick: null, 877 onMouseover: null, 878 onMouseout: null, 879 mouseoutDelay: 0, 880 onStateChange: null, 881 boundList: null, 882 onConfigured: null, 883 configTimeout: 30000, 884 noHrefIsMask: true, 885 scaleMap: true, 886 safeLoad: false, 887 areas: [] 888 }, 889 shared_defaults: { 890 render_highlight: { fade: true }, 891 render_select: { fade: false }, 892 staticState: null, 893 selected: null 894 }, 895 area_defaults: 896 { 897 includeKeys: '', 898 isMask: false 899 }, 900 canvas_style: { 901 position: 'absolute', 902 left: 0, 903 top: 0, 904 padding: 0, 905 border: 0 906 }, 907 hasCanvas: null, 908 isTouch: null, 909 map_cache: [], 910 hooks: {}, 911 addHook: function (name, callback) { 912 this.hooks[name] = (this.hooks[name] || []).push(callback); 913 }, 914 callHooks: function (name, context) { 915 $.each(this.hooks[name] || [], function (i, e) { 916 e.apply(context); 917 }); 918 }, 919 utils: { 920 when: $.mapster_when, 921 defer: $.mapster_when.defer, 922 923 // extends the constructor, returns a new object prototype. Does not refer to the 924 // original constructor so is protected if the original object is altered. This way you 925 // can "extend" an object by replacing it with its subclass. 926 subclass: function (BaseClass, constr) { 927 var Subclass = function () { 928 var me = this, 929 args = Array.prototype.slice.call(arguments, 0); 930 me.base = BaseClass.prototype; 931 me.base.init = function () { 932 BaseClass.prototype.constructor.apply(me, args); 933 }; 934 constr.apply(me, args); 935 }; 936 Subclass.prototype = new BaseClass(); 937 Subclass.prototype.constructor = Subclass; 938 return Subclass; 939 }, 940 asArray: function (obj) { 941 return obj.constructor === Array ? 942 obj : this.split(obj); 943 }, 944 // clean split: no padding or empty elements 945 split: function (text, cb) { 946 var i, el, arr = text.split(','); 947 for (i = 0; i < arr.length; i++) { 948 el = $.trim(arr[i]); 949 if (el === '') { 950 arr.splice(i, 1); 951 } else { 952 arr[i] = cb ? cb(el) : el; 953 } 954 } 955 return arr; 956 }, 957 // similar to $.extend but does not add properties (only updates), unless the 958 // first argument is an empty object, then all properties will be copied 959 updateProps: function (_target, _template) { 960 var onlyProps, 961 target = _target || {}, 962 template = $.isEmptyObject(target) ? _template : _target; 963 964 //if (template) { 965 onlyProps = []; 966 $.each(template, function (prop) { 967 onlyProps.push(prop); 968 }); 969 //} 970 971 $.each(Array.prototype.slice.call(arguments, 1), function (i, src) { 972 $.each(src || {}, function (prop) { 973 if (!onlyProps || $.inArray(prop, onlyProps) >= 0) { 974 var p = src[prop]; 975 976 if ($.isPlainObject(p)) { 977 // not recursive - only copies 1 level of subobjects, and always merges 978 target[prop] = $.extend(target[prop] || {}, p); 979 } else if (p && p.constructor === Array) { 980 target[prop] = p.slice(0); 981 } else if (typeof p !== 'undefined') { 982 target[prop] = src[prop]; 983 } 984 985 } 986 }); 987 }); 988 return target; 989 }, 990 isElement: function (o) { 991 return (typeof HTMLElement === "object" ? o instanceof HTMLElement : 992 o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === "string"); 993 }, 994 // finds element of array or object with a property "prop" having value "val" 995 // if prop is not defined, then just looks for property with value "val" 996 indexOfProp: function (obj, prop, val) { 997 var result = obj.constructor === Array ? -1 : null; 998 $.each(obj, function (i, e) { 999 if (e && (prop ? e[prop] : e) === val) { 1000 result = i; 1001 return false; 1002 } 1003 }); 1004 return result; 1005 }, 1006 // returns "obj" if true or false, or "def" if not true/false 1007 boolOrDefault: function (obj, def) { 1008 return this.isBool(obj) ? 1009 obj : def || false; 1010 }, 1011 isBool: function (obj) { 1012 return typeof obj === "boolean"; 1013 }, 1014 isUndef: function (obj) { 1015 return typeof obj === "undefined"; 1016 }, 1017 // evaluates "obj", if function, calls it with args 1018 // (todo - update this to handle variable lenght/more than one arg) 1019 ifFunction: function (obj, that, args) { 1020 if ($.isFunction(obj)) { 1021 obj.call(that, args); 1022 } 1023 }, 1024 size: function (image, raw) { 1025 var u = $.mapster.utils; 1026 return { 1027 width: raw ? (image.width || image.naturalWidth) : u.imgWidth(image, true), 1028 height: raw ? (image.height || image.naturalHeight) : u.imgHeight(image, true), 1029 complete: function () { return !!this.height && !!this.width; } 1030 }; 1031 }, 1032 1033 1034 /** 1035 * Set the opacity of the element. This is an IE<8 specific function for handling VML. 1036 * When using VML we must override the "setOpacity" utility function (monkey patch ourselves). 1037 * jQuery does not deal with opacity correctly for VML elements. This deals with that. 1038 * 1039 * @param {Element} el The DOM element 1040 * @param {double} opacity A value between 0 and 1 inclusive. 1041 */ 1042 1043 setOpacity: function (el, opacity) { 1044 if ($.mapster.hasCanvas()) { 1045 el.style.opacity = opacity; 1046 } else { 1047 $(el).each(function (i, e) { 1048 if (typeof e.opacity !== 'undefined') { 1049 e.opacity = opacity; 1050 } else { 1051 $(e).css("opacity", opacity); 1052 } 1053 }); 1054 } 1055 }, 1056 1057 1058 // fade "el" from opacity "op" to "endOp" over a period of time "duration" 1059 1060 fader: (function () { 1061 var elements = {}, 1062 lastKey = 0, 1063 fade_func = function (el, op, endOp, duration) { 1064 var index, 1065 cbIntervals = duration / 15, 1066 obj, u = $.mapster.utils; 1067 1068 if (typeof el === 'number') { 1069 obj = elements[el]; 1070 if (!obj) { 1071 return; 1072 } 1073 } else { 1074 index = u.indexOfProp(elements, null, el); 1075 if (index) { 1076 delete elements[index]; 1077 } 1078 elements[++lastKey] = obj = el; 1079 el = lastKey; 1080 } 1081 1082 endOp = endOp || 1; 1083 1084 op = (op + (endOp / cbIntervals) > endOp - 0.01) ? endOp : op + (endOp / cbIntervals); 1085 1086 u.setOpacity(obj, op); 1087 if (op < endOp) { 1088 setTimeout(function () { 1089 fade_func(el, op, endOp, duration); 1090 }, 15); 1091 } 1092 }; 1093 return fade_func; 1094 }()) 1095 }, 1096 getBoundList: function (opts, key_list) { 1097 if (!opts.boundList) { 1098 return null; 1099 } 1100 var index, key, result = $(), list = $.mapster.utils.split(key_list); 1101 opts.boundList.each(function (i, e) { 1102 for (index = 0; index < list.length; index++) { 1103 key = list[index]; 1104 if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { 1105 result = result.add(e); 1106 } 1107 } 1108 }); 1109 return result; 1110 }, 1111 // Causes changes to the bound list based on the user action (select or deselect) 1112 // area: the jQuery area object 1113 // returns the matching elements from the bound list for the first area passed (normally only one should be passed, but 1114 // a list can be passed 1115 setBoundListProperties: function (opts, target, selected) { 1116 target.each(function (i, e) { 1117 if (opts.listSelectedClass) { 1118 if (selected) { 1119 $(e).addClass(opts.listSelectedClass); 1120 } else { 1121 $(e).removeClass(opts.listSelectedClass); 1122 } 1123 } 1124 if (opts.listSelectedAttribute) { 1125 $(e).attr(opts.listSelectedAttribute, selected); 1126 } 1127 }); 1128 }, 1129 getMapDataIndex: function (obj) { 1130 var img, id; 1131 switch (obj.tagName && obj.tagName.toLowerCase()) { 1132 case 'area': 1133 id = $(obj).parent().attr('name'); 1134 img = $("img[usemap='#" + id + "']")[0]; 1135 break; 1136 case 'img': 1137 img = obj; 1138 break; 1139 } 1140 return img ? 1141 this.utils.indexOfProp(this.map_cache, 'image', img) : -1; 1142 }, 1143 getMapData: function (obj) { 1144 var index = this.getMapDataIndex(obj.length ? obj[0] : obj); 1145 if (index >= 0) { 1146 return index >= 0 ? this.map_cache[index] : null; 1147 } 1148 }, 1149 /** 1150 * Queue a command to be run after the active async operation has finished 1151 * @param {MapData} map_data The target MapData object 1152 * @param {jQuery} that jQuery object on which the command was invoked 1153 * @param {string} command the ImageMapster method name 1154 * @param {object[]} args arguments passed to the method 1155 * @return {bool} true if the command was queued, false if not (e.g. there was no need to) 1156 */ 1157 queueCommand: function (map_data, that, command, args) { 1158 if (!map_data) { 1159 return false; 1160 } 1161 if (!map_data.complete || map_data.currentAction) { 1162 map_data.commands.push( 1163 { 1164 that: that, 1165 command: command, 1166 args: args 1167 }); 1168 return true; 1169 } 1170 return false; 1171 }, 1172 unload: function () { 1173 this.impl.unload(); 1174 this.utils = null; 1175 this.impl = null; 1176 $.fn.mapster = null; 1177 $.mapster = null; 1178 $('*').unbind(); 1179 } 1180 }; 1181 1182 // Config for object prototypes 1183 // first: use only first object (for things that should not apply to lists) 1184 /// calls back one of two fuinctions, depending on whether an area was obtained. 1185 // opts: { 1186 // name: 'method name', 1187 // key: 'key, 1188 // args: 'args' 1189 // 1190 //} 1191 // name: name of method (required) 1192 // args: arguments to re-call with 1193 // Iterates through all the objects passed, and determines whether it's an area or an image, and calls the appropriate 1194 // callback for each. If anything is returned from that callback, the process is stopped and that data return. Otherwise, 1195 // the object itself is returned. 1196 1197 var m = $.mapster, 1198 u = m.utils, 1199 ap = Array.prototype; 1200 1201 1202 // jQuery's width() and height() are broken on IE9 in some situations. This tries everything. 1203 $.each(["width", "height"], function (i, e) { 1204 var capProp = e.substr(0, 1).toUpperCase() + e.substr(1); 1205 // when jqwidth parm is passed, it also checks the jQuery width()/height() property 1206 // the issue is that jQUery width() can report a valid size before the image is loaded in some browsers 1207 // without it, we can read zero even when image is loaded in other browsers if its not visible 1208 // we must still check because stuff like adblock can temporarily block it 1209 // what a goddamn headache 1210 u["img" + capProp] = function (img, jqwidth) { 1211 return (jqwidth ? $(img)[e]() : 0) || 1212 img[e] || img["natural" + capProp] || img["client" + capProp] || img["offset" + capProp]; 1213 }; 1214 1215 }); 1216 1217 /** 1218 * The Method object encapsulates the process of testing an ImageMapster method to see if it's being 1219 * invoked on an image, or an area; then queues the command if the MapData is in an active state. 1220 * 1221 * @param {[jQuery]} that The target of the invocation 1222 * @param {[function]} func_map The callback if the target is an imagemap 1223 * @param {[function]} func_area The callback if the target is an area 1224 * @param {[object]} opt Options: { key: a map key if passed explicitly 1225 * name: the command name, if it can be queued, 1226 * args: arguments to the method 1227 * } 1228 */ 1229 1230 m.Method = function (that, func_map, func_area, opts) { 1231 var me = this; 1232 me.name = opts.name; 1233 me.output = that; 1234 me.input = that; 1235 me.first = opts.first || false; 1236 me.args = opts.args ? ap.slice.call(opts.args, 0) : []; 1237 me.key = opts.key; 1238 me.func_map = func_map; 1239 me.func_area = func_area; 1240 //$.extend(me, opts); 1241 me.name = opts.name; 1242 me.allowAsync = opts.allowAsync || false; 1243 }; 1244 m.Method.prototype = { 1245 constructor: m.Method, 1246 go: function () { 1247 var i, data, ar, len, result, src = this.input, 1248 area_list = [], 1249 me = this; 1250 1251 len = src.length; 1252 for (i = 0; i < len; i++) { 1253 data = $.mapster.getMapData(src[i]); 1254 if (data) { 1255 if (!me.allowAsync && m.queueCommand(data, me.input, me.name, me.args)) { 1256 if (this.first) { 1257 result = ''; 1258 } 1259 continue; 1260 } 1261 1262 ar = data.getData(src[i].nodeName === 'AREA' ? src[i] : this.key); 1263 if (ar) { 1264 if ($.inArray(ar, area_list) < 0) { 1265 area_list.push(ar); 1266 } 1267 } else { 1268 result = this.func_map.apply(data, me.args); 1269 } 1270 if (this.first || typeof result !== 'undefined') { 1271 break; 1272 } 1273 } 1274 } 1275 // if there were areas, call the area function for each unique group 1276 $(area_list).each(function (i, e) { 1277 result = me.func_area.apply(e, me.args); 1278 }); 1279 1280 if (typeof result !== 'undefined') { 1281 return result; 1282 } else { 1283 return this.output; 1284 } 1285 } 1286 }; 1287 1288 $.mapster.impl = (function () { 1289 var me = {}, 1290 addMap = function (map_data) { 1291 return m.map_cache.push(map_data) - 1; 1292 }, 1293 removeMap = function (map_data) { 1294 m.map_cache.splice(map_data.index, 1); 1295 for (var i = m.map_cache.length - 1; i >= this.index; i--) { 1296 m.map_cache[i].index--; 1297 } 1298 }; 1299 1300 1301 /** 1302 * Test whether the browser supports VML. Credit: google. 1303 * http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser 1304 * 1305 * @return {bool} true if vml is supported, false if not 1306 */ 1307 1308 function hasVml() { 1309 var a = $('<div />').appendTo('body'); 1310 a.html('<v:shape id="vml_flag1" adj="1" />'); 1311 1312 var b = a[0].firstChild; 1313 b.style.behavior = "url(#default#VML)"; 1314 var has = b ? typeof b.adj === "object" : true; 1315 a.remove(); 1316 return has; 1317 } 1318 1319 /** 1320 * Return a reference to the IE namespaces object, if available, or an empty object otherwise 1321 * @return {obkect} The document.namespaces object. 1322 */ 1323 function namespaces() { 1324 return typeof (document.namespaces) === 'object' ? 1325 document.namespaces : 1326 null; 1327 } 1328 1329 /** 1330 * Test for the presence of HTML5 Canvas support. This also checks to see if excanvas.js has been 1331 * loaded and is faking it; if so, we assume that canvas is not supported. 1332 * 1333 * @return {bool} true if HTML5 canvas support, false if not 1334 */ 1335 1336 function hasCanvas() { 1337 var d = namespaces(); 1338 // when g_vml_ is present, then we can be sure excanvas is active, meaning there's not a real canvas. 1339 1340 return d && d.g_vml_ ? 1341 false : 1342 $('<canvas />')[0].getContext ? 1343 true : 1344 false; 1345 } 1346 1347 /** 1348 * Merge new area data into existing area options on a MapData object. Used for rebinding. 1349 * 1350 * @param {[MapData]} map_data The MapData object 1351 * @param {[object[]]} areas areas array to merge 1352 */ 1353 1354 function merge_areas(map_data, areas) { 1355 var ar, index, 1356 map_areas = map_data.options.areas; 1357 1358 if (areas) { 1359 $.each(areas, function (i, e) { 1360 1361 // Issue #68 - ignore invalid data in areas array 1362 1363 if (!e || !e.key) { 1364 return; 1365 } 1366 1367 index = u.indexOfProp(map_areas, "key", e.key); 1368 1369 if (index >= 0) { 1370 $.extend(map_areas[index], e); 1371 } 1372 else { 1373 map_areas.push(e); 1374 } 1375 ar = map_data.getDataForKey(e.key); 1376 if (ar) { 1377 $.extend(ar.options, e); 1378 } 1379 }); 1380 } 1381 } 1382 function merge_options(map_data, options) { 1383 var temp_opts = u.updateProps({}, options); 1384 delete temp_opts.areas; 1385 1386 u.updateProps(map_data.options, temp_opts); 1387 1388 merge_areas(map_data, options.areas); 1389 // refresh the area_option template 1390 u.updateProps(map_data.area_options, map_data.options); 1391 } 1392 1393 // Most methods use the "Method" object which handles figuring out whether it's an image or area called and 1394 // parsing key parameters. The constructor wants: 1395 // this, the jQuery object 1396 // a function that is called when an image was passed (with a this context of the MapData) 1397 // a function that is called when an area was passed (with a this context of the AreaData) 1398 // options: first = true means only the first member of a jQuery object is handled 1399 // key = the key parameters passed 1400 // defaultReturn: a value to return other than the jQuery object (if its not chainable) 1401 // args: the arguments 1402 // Returns a comma-separated list of user-selected areas. "staticState" areas are not considered selected for the purposes of this method. 1403 1404 me.get = function (key) { 1405 var md = m.getMapData(this); 1406 if (!(md && md.complete)) { 1407 throw ("Can't access data until binding complete."); 1408 } 1409 1410 return (new m.Method(this, 1411 function () { 1412 // map_data return 1413 return this.getSelected(); 1414 }, 1415 function () { 1416 return this.isSelected(); 1417 }, 1418 { 1419 name: 'get', 1420 args: arguments, 1421 key: key, 1422 first: true, 1423 allowAsync: true, 1424 defaultReturn: '' 1425 } 1426 )).go(); 1427 }; 1428 me.data = function (key) { 1429 return (new m.Method(this, 1430 null, 1431 function () { 1432 return this; 1433 }, 1434 { 1435 name: 'data', 1436 args: arguments, 1437 key: key 1438 } 1439 )).go(); 1440 }; 1441 1442 1443 // Set or return highlight state. 1444 // $(img).mapster('highlight') -- return highlighted area key, or null if none 1445 // $(area).mapster('highlight') -- highlight an area 1446 // $(img).mapster('highlight','area_key') -- highlight an area 1447 // $(img).mapster('highlight',false) -- remove highlight 1448 me.highlight = function (key) { 1449 return (new m.Method(this, 1450 function () { 1451 if (key === false) { 1452 this.ensureNoHighlight(); 1453 } else { 1454 var id = this.highlightId; 1455 return id >= 0 ? this.data[id].key : null; 1456 } 1457 }, 1458 function () { 1459 this.highlight(); 1460 }, 1461 { 1462 name: 'highlight', 1463 args: arguments, 1464 key: key, 1465 first: true 1466 } 1467 )).go(); 1468 }; 1469 // Return the primary keys for an area or group key. 1470 // $(area).mapster('key') 1471 // includes all keys (not just primary keys) 1472 // $(area).mapster('key',true) 1473 // $(img).mapster('key','group-key') 1474 1475 // $(img).mapster('key','group-key', true) 1476 me.keys = function (key, all) { 1477 var keyList = [], 1478 md = m.getMapData(this); 1479 1480 if (!(md && md.complete)) { 1481 throw ("Can't access data until binding complete."); 1482 } 1483 1484 1485 function addUniqueKeys(ad) { 1486 var areas, keys = []; 1487 if (!all) { 1488 keys.push(ad.key); 1489 } else { 1490 areas = ad.areas(); 1491 $.each(areas, function (i, e) { 1492 keys = keys.concat(e.keys); 1493 }); 1494 } 1495 $.each(keys, function (i, e) { 1496 if ($.inArray(e, keyList) < 0) { 1497 keyList.push(e); 1498 } 1499 }); 1500 } 1501 1502 if (!(md && md.complete)) { 1503 return ''; 1504 } 1505 if (typeof key === 'string') { 1506 if (all) { 1507 addUniqueKeys(md.getDataForKey(key)); 1508 } else { 1509 keyList = [md.getKeysForGroup(key)]; 1510 } 1511 } else { 1512 all = key; 1513 this.each(function (i, e) { 1514 if (e.nodeName === 'AREA') { 1515 addUniqueKeys(md.getDataForArea(e)); 1516 } 1517 }); 1518 } 1519 return keyList.join(','); 1520 1521 1522 }; 1523 me.select = function () { 1524 me.set.call(this, true); 1525 }; 1526 me.deselect = function () { 1527 me.set.call(this, false); 1528 }; 1529 1530 /** 1531 * Select or unselect areas. Areas can be identified by a single string key, a comma-separated list of keys, 1532 * or an array of strings. 1533 * 1534 * 1535 * @param {boolean} selected Determines whether areas are selected or deselected 1536 * @param {string|string[]} key A string, comma-separated string, or array of strings indicating 1537 * the areas to select or deselect 1538 * @param {object} options Rendering options to apply when selecting an area 1539 */ 1540 1541 me.set = function (selected, key, options) { 1542 var lastMap, map_data, opts = options, 1543 key_list, area_list; // array of unique areas passed 1544 1545 function setSelection(ar) { 1546 if (ar) { 1547 switch (selected) { 1548 case true: 1549 ar.select(opts); break; 1550 case false: 1551 ar.deselect(true); break; 1552 default: 1553 ar.toggle(opts); break; 1554 } 1555 } 1556 } 1557 function addArea(ar) { 1558 if (ar && $.inArray(ar, area_list) < 0) { 1559 area_list.push(ar); 1560 key_list += (key_list === '' ? '' : ',') + ar.key; 1561 } 1562 } 1563 // Clean up after a group that applied to the same map 1564 function finishSetForMap(map_data) { 1565 $.each(area_list, function (i, el) { 1566 setSelection(el); 1567 }); 1568 if (!selected) { 1569 map_data.removeSelectionFinish(); 1570 } 1571 if (map_data.options.boundList) { 1572 m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), selected); 1573 } 1574 } 1575 1576 this.filter('img,area').each(function (i, e) { 1577 var keys; 1578 map_data = m.getMapData(e); 1579 1580 if (map_data !== lastMap) { 1581 if (lastMap) { 1582 finishSetForMap(lastMap); 1583 } 1584 1585 area_list = []; 1586 key_list = ''; 1587 } 1588 1589 if (map_data) { 1590 1591 keys = ''; 1592 if (e.nodeName.toUpperCase() === 'IMG') { 1593 if (!m.queueCommand(map_data, $(e), 'set', [selected, key, opts])) { 1594 if (key instanceof Array) { 1595 if (key.length) { 1596 keys = key.join(","); 1597 } 1598 } 1599 else { 1600 keys = key; 1601 } 1602 1603 if (keys) { 1604 $.each(u.split(keys), function (i, key) { 1605 addArea(map_data.getDataForKey(key.toString())); 1606 lastMap = map_data; 1607 }); 1608 } 1609 } 1610 } else { 1611 opts = key; 1612 if (!m.queueCommand(map_data, $(e), 'set', [selected, opts])) { 1613 addArea(map_data.getDataForArea(e)); 1614 lastMap = map_data; 1615 } 1616 1617 } 1618 } 1619 }); 1620 1621 if (map_data) { 1622 finishSetForMap(map_data); 1623 } 1624 1625 1626 return this; 1627 }; 1628 me.unbind = function (preserveState) { 1629 return (new m.Method(this, 1630 function () { 1631 this.clearEvents(); 1632 this.clearMapData(preserveState); 1633 removeMap(this); 1634 }, 1635 null, 1636 { 1637 name: 'unbind', 1638 args: arguments 1639 } 1640 )).go(); 1641 }; 1642 1643 1644 // refresh options and update selection information. 1645 me.rebind = function (options) { 1646 return (new m.Method(this, 1647 function () { 1648 var me = this; 1649 1650 me.complete = false; 1651 me.configureOptions(options); 1652 me.bindImages().then(function () { 1653 me.buildDataset(true); 1654 me.complete = true; 1655 }); 1656 //this.redrawSelections(); 1657 }, 1658 null, 1659 { 1660 name: 'rebind', 1661 args: arguments 1662 } 1663 )).go(); 1664 }; 1665 // get options. nothing or false to get, or "true" to get effective options (versus passed options) 1666 me.get_options = function (key, effective) { 1667 var eff = u.isBool(key) ? key : effective; // allow 2nd parm as "effective" when no key 1668 return (new m.Method(this, 1669 function () { 1670 var opts = $.extend({}, this.options); 1671 if (eff) { 1672 opts.render_select = u.updateProps( 1673 {}, 1674 m.render_defaults, 1675 opts, 1676 opts.render_select); 1677 1678 opts.render_highlight = u.updateProps( 1679 {}, 1680 m.render_defaults, 1681 opts, 1682 opts.render_highlight); 1683 } 1684 return opts; 1685 }, 1686 function () { 1687 return eff ? this.effectiveOptions() : this.options; 1688 }, 1689 { 1690 name: 'get_options', 1691 args: arguments, 1692 first: true, 1693 allowAsync: true, 1694 key: key 1695 } 1696 )).go(); 1697 }; 1698 1699 // set options - pass an object with options to set, 1700 me.set_options = function (options) { 1701 return (new m.Method(this, 1702 function () { 1703 merge_options(this, options); 1704 }, 1705 null, 1706 { 1707 name: 'set_options', 1708 args: arguments 1709 } 1710 )).go(); 1711 }; 1712 me.unload = function () { 1713 var i; 1714 for (i = m.map_cache.length - 1; i >= 0; i--) { 1715 if (m.map_cache[i]) { 1716 me.unbind.call($(m.map_cache[i].image)); 1717 } 1718 } 1719 me.graphics = null; 1720 }; 1721 1722 me.snapshot = function () { 1723 return (new m.Method(this, 1724 function () { 1725 $.each(this.data, function (i, e) { 1726 e.selected = false; 1727 }); 1728 1729 this.base_canvas = this.graphics.createVisibleCanvas(this); 1730 $(this.image).before(this.base_canvas); 1731 }, 1732 null, 1733 { name: 'snapshot' } 1734 )).go(); 1735 }; 1736 1737 // do not queue this function 1738 1739 me.state = function () { 1740 var md, result = null; 1741 $(this).each(function (i, e) { 1742 if (e.nodeName === 'IMG') { 1743 md = m.getMapData(e); 1744 if (md) { 1745 result = md.state(); 1746 } 1747 return false; 1748 } 1749 }); 1750 return result; 1751 }; 1752 1753 me.bind = function (options) { 1754 1755 return this.each(function (i, e) { 1756 var img, map, usemap, md; 1757 1758 // save ref to this image even if we can't access it yet. commands will be queued 1759 img = $(e); 1760 1761 md = m.getMapData(e); 1762 1763 // if already bound completely, do a total rebind 1764 1765 if (md) { 1766 me.unbind.apply(img); 1767 if (!md.complete) { 1768 // will be queued 1769 img.bind(); 1770 return true; 1771 } 1772 md = null; 1773 } 1774 1775 // ensure it's a valid image 1776 // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr. 1777 // So use raw getAttribute instead. 1778 1779 usemap = this.getAttribute('usemap'); 1780 map = usemap && $('map[name="' + usemap.substr(1) + '"]'); 1781 if (!(img.is('img') && usemap && map.size() > 0)) { 1782 return true; 1783 } 1784 1785 // sorry - your image must have border:0, things are too unpredictable otherwise. 1786 img.css('border', 0); 1787 1788 if (!md) { 1789 md = new m.MapData(this, options); 1790 1791 md.index = addMap(md); 1792 md.map = map; 1793 md.bindImages().then(function () { 1794 md.initialize(); 1795 }); 1796 } 1797 }); 1798 }; 1799 1800 me.init = function (useCanvas) { 1801 var style, shapes; 1802 1803 // for testing/debugging, use of canvas can be forced by initializing 1804 // manually with "true" or "false". But generally we test for it. 1805 1806 m.hasCanvas = function () { 1807 if (!u.isBool(m.hasCanvas.value)) { 1808 m.hasCanvas.value = u.isBool(useCanvas) ? 1809 useCanvas : 1810 hasCanvas(); 1811 } 1812 return m.hasCanvas.value; 1813 }; 1814 m.hasVml = function () { 1815 if (!u.isBool(m.hasVml.value)) { 1816 // initialize VML the first time we detect its presence. 1817 var d = namespaces(); 1818 1819 if (d && !d.v) { 1820 d.add("v", "urn:schemas-microsoft-com:vml"); 1821 style = document.createStyleSheet(); 1822 shapes = ['shape', 'rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group', 'textbox']; 1823 $.each(shapes, 1824 function (i, el) { 1825 style.addRule('v\\:' + el, "behavior: url(#default#VML); antialias:true"); 1826 }); 1827 } 1828 m.hasVml.value = hasVml(); 1829 } 1830 1831 return m.hasVml.value; 1832 }; 1833 1834 m.isTouch = !!document.documentElement.ontouchstart; 1835 1836 $.extend(m.defaults, m.render_defaults, m.shared_defaults); 1837 $.extend(m.area_defaults, m.render_defaults, m.shared_defaults); 1838 1839 }; 1840 me.test = function (obj) { 1841 return eval(obj); 1842 }; 1843 return me; 1844 }()); 1845 1846 $.mapster.impl.init(); 1847 1848 1849}(jQuery)); 1850/* graphics.js 1851 Graphics object handles all rendering. 1852*/ 1853(function ($) { 1854 var p, m = $.mapster, 1855 u = m.utils, 1856 canvasMethods, 1857 vmlMethods; 1858 1859 /** 1860 * Implemenation to add each area in an AreaData object to the canvas 1861 * @param {Graphics} graphics The target graphics object 1862 * @param {AreaData} areaData The AreaData object (a collection of area elements and metadata) 1863 * @param {object} options Rendering options to apply when rendering this group of areas 1864 */ 1865 function addShapeGroupImpl(graphics, areaData, options) { 1866 var me = graphics, 1867 md = me.map_data, 1868 isMask = options.isMask; 1869 1870 // first get area options. Then override fade for selecting, and finally merge in the 1871 // "select" effect options. 1872 1873 $.each(areaData.areas(), function (i, e) { 1874 options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); 1875 me.addShape(e, options); 1876 }); 1877 1878 // it's faster just to manipulate the passed options isMask property and restore it, than to 1879 // copy the object each time 1880 1881 options.isMask = isMask; 1882 1883 } 1884 1885 /** 1886 * Convert a hex value to decimal 1887 * @param {string} hex A hexadecimal toString 1888 * @return {int} Integer represenation of the hex string 1889 */ 1890 1891 function hex_to_decimal(hex) { 1892 return Math.max(0, Math.min(parseInt(hex, 16), 255)); 1893 } 1894 function css3color(color, opacity) { 1895 return 'rgba(' + hex_to_decimal(color.substr(0, 2)) + ',' 1896 + hex_to_decimal(color.substr(2, 2)) + ',' 1897 + hex_to_decimal(color.substr(4, 2)) + ',' + opacity + ')'; 1898 } 1899 /** 1900 * An object associated with a particular map_data instance to manage renderin. 1901 * @param {MapData} map_data The MapData object bound to this instance 1902 */ 1903 1904 m.Graphics = function (map_data) { 1905 //$(window).unload($.mapster.unload); 1906 // create graphics functions for canvas and vml browsers. usage: 1907 // 1) init with map_data, 2) call begin with canvas to be used (these are separate b/c may not require canvas to be specified 1908 // 3) call add_shape_to for each shape or mask, 4) call render() to finish 1909 1910 var me = this; 1911 me.active = false; 1912 me.canvas = null; 1913 me.width = 0; 1914 me.height = 0; 1915 me.shapes = []; 1916 me.masks = []; 1917 me.map_data = map_data; 1918 }; 1919 1920 p = m.Graphics.prototype = { 1921 constructor: m.Graphics, 1922 1923 /** 1924 * Initiate a graphics request for a canvas 1925 * @param {Element} canvas The canvas element that is the target of this operation 1926 * @param {string} [elementName] The name to assign to the element (VML only) 1927 */ 1928 1929 begin: function (canvas, elementName) { 1930 var c = $(canvas); 1931 1932 this.elementName = elementName; 1933 this.canvas = canvas; 1934 1935 this.width = c.width(); 1936 this.height = c.height(); 1937 this.shapes = []; 1938 this.masks = []; 1939 this.active = true; 1940 1941 }, 1942 1943 /** 1944 * Add an area to be rendered to this canvas. 1945 * @param {MapArea} mapArea The MapArea object to render 1946 * @param {object} options An object containing any rendering options that should override the 1947 * defaults for the area 1948 */ 1949 1950 addShape: function (mapArea, options) { 1951 var addto = options.isMask ? this.masks : this.shapes; 1952 addto.push({ mapArea: mapArea, options: options }); 1953 }, 1954 1955 /** 1956 * Create a canvas that is sized and styled for the MapData object 1957 * @param {MapData} mapData The MapData object that will receive this new canvas 1958 * @return {Element} A canvas element 1959 */ 1960 1961 createVisibleCanvas: function (mapData) { 1962 return $(this.createCanvasFor(mapData)) 1963 .addClass('mapster_el') 1964 .css(m.canvas_style)[0]; 1965 }, 1966 1967 /** 1968 * Add a group of shapes from an AreaData object to the canvas 1969 * 1970 * @param {AreaData} areaData An AreaData object (a set of area elements) 1971 * @param {string} mode The rendering mode, "select" or "highlight". This determines the target 1972 * canvas and which default options to use. 1973 * @param {striong} options Rendering options 1974 */ 1975 1976 addShapeGroup: function (areaData, mode, options) { 1977 // render includeKeys first - because they could be masks 1978 var me = this, 1979 list, name, canvas, 1980 map_data = this.map_data, 1981 opts = areaData.effectiveRenderOptions(mode); 1982 1983 if (options) { 1984 $.extend(opts, options); 1985 } 1986 1987 if (mode === 'select') { 1988 name = "static_" + areaData.areaId.toString(); 1989 canvas = map_data.base_canvas; 1990 } else { 1991 canvas = map_data.overlay_canvas; 1992 } 1993 1994 me.begin(canvas, name); 1995 1996 if (opts.includeKeys) { 1997 list = u.split(opts.includeKeys); 1998 $.each(list, function (i, e) { 1999 var areaData = map_data.getDataForKey(e.toString()); 2000 addShapeGroupImpl(me, areaData, areaData.effectiveRenderOptions(mode)); 2001 }); 2002 } 2003 2004 addShapeGroupImpl(me, areaData, opts); 2005 me.render(); 2006 if (opts.fade) { 2007 2008 // fading requires special handling for IE. We must access the fill elements directly. The fader also has to deal with 2009 // the "opacity" attribute (not css) 2010 2011 u.fader(m.hasCanvas() ? 2012 canvas : 2013 $(canvas).find('._fill').not('.mapster_mask'), 2014 0, 2015 m.hasCanvas() ? 2016 1 : 2017 opts.fillOpacity, 2018 opts.fadeDuration); 2019 2020 } 2021 2022 } 2023 2024 // These prototype methods are implementation dependent 2025 }; 2026 2027 function noop() { } 2028 2029 2030 // configure remaining prototype methods for ie or canvas-supporting browser 2031 2032 canvasMethods = { 2033 renderShape: function (context, mapArea, offset) { 2034 var i, 2035 c = mapArea.coords(null, offset); 2036 2037 switch (mapArea.shape) { 2038 case 'rect': 2039 context.rect(c[0], c[1], c[2] - c[0], c[3] - c[1]); 2040 break; 2041 case 'poly': 2042 context.moveTo(c[0], c[1]); 2043 2044 for (i = 2; i < mapArea.length; i += 2) { 2045 context.lineTo(c[i], c[i + 1]); 2046 } 2047 context.lineTo(c[0], c[1]); 2048 break; 2049 case 'circ': 2050 case 'circle': 2051 context.arc(c[0], c[1], c[2], 0, Math.PI * 2, false); 2052 break; 2053 } 2054 }, 2055 addAltImage: function (context, image, mapArea, options) { 2056 context.beginPath(); 2057 2058 this.renderShape(context, mapArea); 2059 context.closePath(); 2060 context.clip(); 2061 2062 context.globalAlpha = options.altImageOpacity || options.fillOpacity; 2063 2064 context.drawImage(image, 0, 0, mapArea.owner.scaleInfo.width, mapArea.owner.scaleInfo.height); 2065 }, 2066 render: function () { 2067 // firefox 6.0 context.save() seems to be broken. to work around, we have to draw the contents on one temp canvas, 2068 // the mask on another, and merge everything. ugh. fixed in 1.2.2. unfortunately this is a lot more code for masks, 2069 // but no other way around it that i can see. 2070 2071 var maskCanvas, maskContext, 2072 me = this, 2073 md = me.map_data, 2074 hasMasks = me.masks.length, 2075 shapeCanvas = me.createCanvasFor(md), 2076 shapeContext = shapeCanvas.getContext('2d'), 2077 context = me.canvas.getContext('2d'); 2078 2079 if (hasMasks) { 2080 maskCanvas = me.createCanvasFor(md); 2081 maskContext = maskCanvas.getContext('2d'); 2082 maskContext.clearRect(0, 0, maskCanvas.width, maskCanvas.height); 2083 2084 $.each(me.masks, function (i, e) { 2085 maskContext.save(); 2086 maskContext.beginPath(); 2087 me.renderShape(maskContext, e.mapArea); 2088 maskContext.closePath(); 2089 maskContext.clip(); 2090 maskContext.lineWidth = 0; 2091 maskContext.fillStyle = '#000'; 2092 maskContext.fill(); 2093 maskContext.restore(); 2094 }); 2095 2096 } 2097 2098 $.each(me.shapes, function (i, s) { 2099 shapeContext.save(); 2100 if (s.options.fill) { 2101 if (s.options.altImageId) { 2102 me.addAltImage(shapeContext, md.images[s.options.altImageId], s.mapArea, s.options); 2103 } else { 2104 shapeContext.beginPath(); 2105 me.renderShape(shapeContext, s.mapArea); 2106 shapeContext.closePath(); 2107 //shapeContext.clip(); 2108 shapeContext.fillStyle = css3color(s.options.fillColor, s.options.fillOpacity); 2109 shapeContext.fill(); 2110 } 2111 } 2112 shapeContext.restore(); 2113 }); 2114 2115 2116 // render strokes at end since masks get stroked too 2117 2118 $.each(me.shapes.concat(me.masks), function (i, s) { 2119 var offset = s.options.strokeWidth === 1 ? 0.5 : 0; 2120 // offset applies only when stroke width is 1 and stroke would render between pixels. 2121 2122 if (s.options.stroke) { 2123 shapeContext.save(); 2124 shapeContext.strokeStyle = css3color(s.options.strokeColor, s.options.strokeOpacity); 2125 shapeContext.lineWidth = s.options.strokeWidth; 2126 2127 shapeContext.beginPath(); 2128 2129 me.renderShape(shapeContext, s.mapArea, offset); 2130 shapeContext.closePath(); 2131 shapeContext.stroke(); 2132 shapeContext.restore(); 2133 } 2134 }); 2135 2136 if (hasMasks) { 2137 // render the new shapes against the mask 2138 2139 maskContext.globalCompositeOperation = "source-out"; 2140 maskContext.drawImage(shapeCanvas, 0, 0); 2141 2142 // flatten into the main canvas 2143 context.drawImage(maskCanvas, 0, 0); 2144 } else { 2145 context.drawImage(shapeCanvas, 0, 0); 2146 } 2147 2148 me.active = false; 2149 return me.canvas; 2150 }, 2151 2152 // create a canvas mimicing dimensions of an existing element 2153 createCanvasFor: function (md) { 2154 return $('<canvas width="' + md.scaleInfo.width + '" height="' + md.scaleInfo.height + '"></canvas>')[0]; 2155 }, 2156 clearHighlight: function () { 2157 var c = this.map_data.overlay_canvas; 2158 c.getContext('2d').clearRect(0, 0, c.width, c.height); 2159 }, 2160 // Draw all items from selected_list to a new canvas, then swap with the old one. This is used to delete items when using canvases. 2161 refreshSelections: function () { 2162 var canvas_temp, map_data = this.map_data; 2163 // draw new base canvas, then swap with the old one to avoid flickering 2164 canvas_temp = map_data.base_canvas; 2165 2166 map_data.base_canvas = this.createVisibleCanvas(map_data); 2167 $(map_data.base_canvas).hide(); 2168 $(canvas_temp).before(map_data.base_canvas); 2169 2170 map_data.redrawSelections(); 2171 2172 $(map_data.base_canvas).show(); 2173 $(canvas_temp).remove(); 2174 } 2175 }; 2176 2177 vmlMethods = { 2178 2179 renderShape: function (mapArea, options, cssclass) { 2180 var me = this, fill, stroke, e, t_fill, el_name, el_class, template, c = mapArea.coords(); 2181 el_name = me.elementName ? 'name="' + me.elementName + '" ' : ''; 2182 el_class = cssclass ? 'class="' + cssclass + '" ' : ''; 2183 2184 t_fill = '<v:fill color="#' + options.fillColor + '" class="_fill" opacity="' + 2185 (options.fill ? 2186 options.fillOpacity : 2187 0) + 2188 '" /><v:stroke class="_fill" opacity="' + 2189 options.strokeOpacity + '"/>'; 2190 2191 2192 stroke = options.stroke ? 2193 ' strokeweight=' + options.strokeWidth + ' stroked="t" strokecolor="#' + 2194 options.strokeColor + '"' : 2195 ' stroked="f"'; 2196 2197 fill = options.fill ? 2198 ' filled="t"' : 2199 ' filled="f"'; 2200 2201 switch (mapArea.shape) { 2202 case 'rect': 2203 template = '<v:rect ' + el_class + el_name + fill + stroke + 2204 ' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:' + 2205 c[0] + 'px;top:' + c[1] + 'px;width:' + (c[2] - c[0]) + 2206 'px;height:' + (c[3] - c[1]) + 'px;">' + t_fill + '</v:rect>'; 2207 break; 2208 case 'poly': 2209 template = '<v:shape ' + el_class + el_name + fill + stroke + ' coordorigin="0,0" coordsize="' + me.width + ',' + me.height 2210 + '" path="m ' + c[0] + ',' + c[1] + ' l ' + c.slice(2).join(',') 2211 + ' x e" style="zoom:1;margin:0;padding:0;display:block;position:absolute;top:0px;left:0px;width:' + me.width + 'px;height:' + me.height + 'px;">' + t_fill + '</v:shape>'; 2212 break; 2213 case 'circ': 2214 case 'circle': 2215 template = '<v:oval ' + el_class + el_name + fill + stroke 2216 + ' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:' + (c[0] - c[2]) + 'px;top:' + (c[1] - c[2]) 2217 + 'px;width:' + (c[2] * 2) + 'px;height:' + (c[2] * 2) + 'px;">' + t_fill + '</v:oval>'; 2218 break; 2219 } 2220 e = $(template); 2221 $(me.canvas).append(e); 2222 2223 return e; 2224 }, 2225 render: function () { 2226 var opts, me = this; 2227 2228 $.each(this.shapes, function (i, e) { 2229 me.renderShape(e.mapArea, e.options); 2230 }); 2231 2232 if (this.masks.length) { 2233 $.each(this.masks, function (i, e) { 2234 opts = u.updateProps({}, 2235 e.options, { 2236 fillOpacity: 1, 2237 fillColor: e.options.fillColorMask 2238 }); 2239 me.renderShape(e.mapArea, opts, 'mapster_mask'); 2240 }); 2241 } 2242 2243 this.active = false; 2244 return this.canvas; 2245 }, 2246 2247 createCanvasFor: function (md) { 2248 var w = md.scaleInfo.width, 2249 h = md.scaleInfo.height; 2250 return $('<var width="' + w + '" height="' + h 2251 + '" style="zoom:1;overflow:hidden;display:block;width:' 2252 + w + 'px;height:' + h + 'px;"></var>')[0]; 2253 }, 2254 2255 clearHighlight: function () { 2256 $(this.map_data.overlay_canvas).children().remove(); 2257 }, 2258 // remove single or all selections 2259 removeSelections: function (area_id) { 2260 if (area_id >= 0) { 2261 $(this.map_data.base_canvas).find('[name="static_' + area_id.toString() + '"]').remove(); 2262 } 2263 else { 2264 $(this.map_data.base_canvas).children().remove(); 2265 } 2266 } 2267 2268 }; 2269 2270 // for all methods with two implemenatations, add a function that will automatically replace itself with the correct 2271 // method on first invocation 2272 2273 $.each(['renderShape', 2274 'addAltImage', 2275 'render', 2276 'createCanvasFor', 2277 'clearHighlight', 2278 'removeSelections', 2279 'refreshSelections'], 2280 function (i, e) { 2281 p[e] = (function (method) { 2282 return function () { 2283 p[method] = (m.hasCanvas() ? 2284 canvasMethods[method] : 2285 vmlMethods[method]) || noop; 2286 2287 return p[method].apply(this, arguments); 2288 }; 2289 }(e)); 2290 }); 2291 2292 2293}(jQuery)); 2294/* mapimage.js 2295 the MapImage object, repesents an instance of a single bound imagemap 2296*/ 2297 2298(function ($) { 2299 2300 var m = $.mapster, 2301 u = m.utils, 2302 ap = []; 2303 /** 2304 * An object encapsulating all the images used by a MapData. 2305 */ 2306 2307 m.MapImages = function (owner) { 2308 this.owner = owner; 2309 this.clear(); 2310 }; 2311 2312 2313 m.MapImages.prototype = { 2314 constructor: m.MapImages, 2315 2316 /* interface to make this array-like */ 2317 2318 slice: function () { 2319 return ap.slice.apply(this, arguments); 2320 }, 2321 splice: function () { 2322 ap.slice.apply(this.status, arguments); 2323 var result = ap.slice.apply(this, arguments); 2324 return result; 2325 }, 2326 2327 /** 2328 * a boolean value indicates whether all images are done loading 2329 * @return {bool} true when all are done 2330 */ 2331 complete: function () { 2332 return $.inArray(false, this.status) < 0; 2333 }, 2334 2335 /** 2336 * Save an image in the images array and return its index 2337 * @param {Image} image An Image object 2338 * @return {int} the index of the image 2339 */ 2340 2341 _add: function (image) { 2342 var index = ap.push.call(this, image) - 1; 2343 this.status[index] = false; 2344 return index; 2345 }, 2346 2347 /** 2348 * Return the index of an Image within the images array 2349 * @param {Image} img An Image 2350 * @return {int} the index within the array, or -1 if it was not found 2351 */ 2352 2353 indexOf: function (image) { 2354 return $.inArray(image, this); 2355 }, 2356 2357 /** 2358 * Clear this object and reset it to its initial state after binding. 2359 */ 2360 2361 clear: function () { 2362 var me = this; 2363 2364 if (me.ids && me.ids.length > 0) { 2365 $.each(me.ids, function (i, e) { 2366 delete me[e]; 2367 }); 2368 } 2369 2370 /** 2371 * A list of the cross-reference IDs bound to this object 2372 * @type {string[]} 2373 */ 2374 2375 me.ids = []; 2376 2377 /** 2378 * Length property for array-like behavior, set to zero when initializing. Array prototype 2379 * methods will update it after that. 2380 * 2381 * @type {int} 2382 */ 2383 2384 me.length = 0; 2385 2386 /** 2387 * the loaded status of the corresponding image 2388 * @type {boolean[]} 2389 */ 2390 2391 me.status = []; 2392 2393 2394 // actually erase the images 2395 2396 me.splice(0); 2397 2398 }, 2399 2400 /** 2401 * Bind an image to the map and add it to the queue to be loaded; return an ID that 2402 * can be used to reference the 2403 * 2404 * @param {Image|string} image An Image object or a URL to an image 2405 * @param {string} [id] An id to refer to this image 2406 * @returns {int} an ID referencing the index of the image object in 2407 * map_data.images 2408 */ 2409 2410 add: function (image, id) { 2411 var index, src, me = this; 2412 2413 if (!image) { return; } 2414 2415 if (typeof image === 'string') { 2416 src = image; 2417 image = me[src]; 2418 if (typeof image === 'object') { 2419 return me.indexOf(image); 2420 } 2421 2422 image = $('<img />') 2423 .addClass('mapster_el') 2424 .hide(); 2425 2426 index = me._add(image[0]); 2427 2428 image 2429 .bind('load', function (e) { 2430 me.imageLoaded.call(me, e); 2431 }) 2432 .bind('error', function (e) { 2433 me.imageLoadError.call(me, e); 2434 }); 2435 2436 image.attr('src', src); 2437 } else { 2438 2439 // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src 2440 2441 index = me._add($(image)[0]); 2442 } 2443 if (id) { 2444 if (this[id]) { 2445 throw (id + " is already used or is not available as an altImage alias."); 2446 } 2447 me.ids.push(id); 2448 me[id] = me[index]; 2449 } 2450 return index; 2451 }, 2452 2453 /** 2454 * Bind the images in this object, 2455 * @param {boolean} retry when true, indicates that the function is calling itself after failure 2456 * @return {Promise} a promise that resolves when the images have finished loading 2457 */ 2458 2459 bind: function (retry) { 2460 var me = this, 2461 promise, 2462 triesLeft = me.owner.options.configTimeout / 200, 2463 2464 /* A recursive function to continue checking that the images have been 2465 loaded until a timeout has elapsed */ 2466 2467 check = function () { 2468 var i; 2469 2470 // refresh status of images 2471 2472 i = me.length; 2473 2474 while (i-- > 0) { 2475 if (!me.isLoaded(i)) { 2476 break; 2477 } 2478 } 2479 2480 // check to see if every image has already been loaded 2481 2482 if (me.complete()) { 2483 me.resolve(); 2484 } else { 2485 // to account for failure of onLoad to fire in rare situations 2486 if (triesLeft-- > 0) { 2487 me.imgTimeout = window.setTimeout(function () { 2488 check.call(me, true); 2489 }, 50); 2490 } else { 2491 me.imageLoadError.call(me); 2492 } 2493 } 2494 2495 }; 2496 2497 promise = me.deferred = u.defer(); 2498 2499 check(); 2500 return promise; 2501 }, 2502 2503 resolve: function () { 2504 var me = this, 2505 resolver = me.deferred; 2506 2507 if (resolver) { 2508 // Make a copy of the resolver before calling & removing it to ensure 2509 // it is not called twice 2510 me.deferred = null; 2511 resolver.resolve(); 2512 } 2513 }, 2514 2515 /** 2516 * Event handler for image onload 2517 * @param {object} e jQuery event data 2518 */ 2519 2520 imageLoaded: function (e) { 2521 var me = this, 2522 index = me.indexOf(e.target); 2523 2524 if (index >= 0) { 2525 2526 me.status[index] = true; 2527 if ($.inArray(false, me.status) < 0) { 2528 me.resolve(); 2529 } 2530 } 2531 }, 2532 2533 /** 2534 * Event handler for onload error 2535 * @param {object} e jQuery event data 2536 */ 2537 2538 imageLoadError: function (e) { 2539 clearTimeout(this.imgTimeout); 2540 this.triesLeft = 0; 2541 var err = e ? 'The image ' + e.target.src + ' failed to load.' : 2542 'The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.'; 2543 throw err; 2544 }, 2545 /** 2546 * Test if the image at specificed index has finished loading 2547 * @param {int} index The image index 2548 * @return {boolean} true if loaded, false if not 2549 */ 2550 2551 isLoaded: function (index) { 2552 var img, 2553 me = this, 2554 status = me.status; 2555 2556 if (status[index]) { return true; } 2557 img = me[index]; 2558 2559 if (typeof img.complete !== 'undefined') { 2560 status[index] = img.complete; 2561 } else { 2562 status[index] = !!u.imgWidth(img); 2563 } 2564 // if complete passes, the image is loaded, but may STILL not be available because of stuff like adblock. 2565 // make sure it is. 2566 2567 return status[index]; 2568 } 2569 }; 2570}(jQuery)); 2571/* mapdata.js 2572 the MapData object, repesents an instance of a single bound imagemap 2573*/ 2574 2575 2576(function ($) { 2577 2578 var m = $.mapster, 2579 u = m.utils; 2580 2581 /** 2582 * Set default values for MapData object properties 2583 * @param {MapData} me The MapData object 2584 */ 2585 2586 function initializeDefaults(me) { 2587 $.extend(me, { 2588 complete: false, // (bool) when configuration is complete 2589 map: null, // ($) the image map 2590 base_canvas: null, // (canvas|var) where selections are rendered 2591 overlay_canvas: null, // (canvas|var) where highlights are rendered 2592 commands: [], // {} commands that were run before configuration was completed (b/c images weren't loaded) 2593 data: [], // MapData[] area groups 2594 mapAreas: [], // MapArea[] list. AreaData entities contain refs to this array, so options are stored with each. 2595 _xref: {}, // (int) xref of mapKeys to data[] 2596 highlightId: -1, // (int) the currently highlighted element. 2597 currentAreaId: -1, 2598 _tooltip_events: [], // {} info on events we bound to a tooltip container, so we can properly unbind them 2599 scaleInfo: null, // {} info about the image size, scaling, defaults 2600 index: -1, // index of this in map_cache - so we have an ID to use for wraper div 2601 activeAreaEvent: null 2602 }); 2603 } 2604 2605 /** 2606 * Return an array of all image-containing options from an options object; 2607 * that is, containers that may have an "altImage" property 2608 * 2609 * @param {object} obj An options object 2610 * @return {object[]} An array of objects 2611 */ 2612 function getOptionImages(obj) { 2613 return [obj, obj.render_highlight, obj.render_select]; 2614 } 2615 2616 /** 2617 * Parse all the altImage references, adding them to the library so they can be preloaded 2618 * and aliased. 2619 * 2620 * @param {MapData} me The MapData object on which to operate 2621 */ 2622 function configureAltImages(me) { 2623 var opts = me.options, 2624 mi = me.images; 2625 2626 // add alt images 2627 2628 if (m.hasCanvas()) { 2629 // map altImage library first 2630 2631 $.each(opts.altImages || {}, function (i, e) { 2632 mi.add(e, i); 2633 }); 2634 2635 // now find everything else 2636 2637 $.each([opts].concat(opts.areas), function (i, e) { 2638 $.each(getOptionImages(e), function (i2, e2) { 2639 if (e2 && e2.altImage) { 2640 e2.altImageId = mi.add(e2.altImage); 2641 } 2642 }); 2643 }); 2644 } 2645 2646 // set area_options 2647 me.area_options = u.updateProps({}, // default options for any MapArea 2648 m.area_defaults, 2649 opts); 2650 } 2651 2652 /** 2653 * Queue a mouse move action based on current delay settings 2654 * (helper for mouseover/mouseout handlers) 2655 * 2656 * @param {MapData} me The MapData context 2657 * @param {number} delay The number of milliseconds to delay the action 2658 * @param {AreaData} area AreaData affected 2659 * @param {Deferred} deferred A deferred object to return (instead of a new one) 2660 * @return {Promise} A promise that resolves when the action is completed 2661 */ 2662 function queueMouseEvent(me, delay, area, deferred) { 2663 2664 deferred = deferred || u.when.defer(); 2665 2666 function cbFinal(areaId) { 2667 if (me.currentAreaId !== areaId && me.highlightId >= 0) { 2668 deferred.resolve(); 2669 } 2670 } 2671 if (me.activeAreaEvent) { 2672 window.clearTimeout(me.activeAreaEvent); 2673 me.activeAreaEvent = 0; 2674 } 2675 if (delay < 0) { 2676 return; 2677 } 2678 2679 if (area.owner.currentAction || delay) { 2680 me.activeAreaEvent = window.setTimeout((function () { 2681 return function () { 2682 queueMouseEvent(me, 0, area, deferred); 2683 }; 2684 }(area)), 2685 delay || 100); 2686 } else { 2687 cbFinal(area.areaId); 2688 } 2689 return deferred; 2690 } 2691 2692 /** 2693 * Mousedown event. This is captured only to prevent browser from drawing an outline around an 2694 * area when it's clicked. 2695 * 2696 * @param {EventData} e jQuery event data 2697 */ 2698 2699 function mousedown(e) { 2700 if (!m.hasCanvas()) { 2701 this.blur(); 2702 } 2703 e.preventDefault(); 2704 } 2705 2706 /** 2707 * Mouseover event. Handle highlight rendering and client callback on mouseover 2708 * 2709 * @param {MapData} me The MapData context 2710 * @param {EventData} e jQuery event data 2711 * @return {[type]} [description] 2712 */ 2713 2714 function mouseover(me, e) { 2715 var arData = me.getAllDataForArea(this), 2716 ar = arData.length ? arData[0] : null; 2717 2718 // mouseover events are ignored entirely while resizing, though we do care about mouseout events 2719 // and must queue the action to keep things clean. 2720 2721 if (!ar || ar.isNotRendered() || ar.owner.currentAction) { 2722 return; 2723 } 2724 2725 if (me.currentAreaId === ar.areaId) { 2726 return; 2727 } 2728 if (me.highlightId !== ar.areaId) { 2729 me.clearEffects(); 2730 2731 ar.highlight(); 2732 2733 if (me.options.showToolTip) { 2734 $.each(arData, function (i, e) { 2735 if (e.effectiveOptions().toolTip) { 2736 e.showToolTip(); 2737 } 2738 }); 2739 } 2740 } 2741 2742 me.currentAreaId = ar.areaId; 2743 2744 if ($.isFunction(me.options.onMouseover)) { 2745 me.options.onMouseover.call(this, 2746 { 2747 e: e, 2748 options: ar.effectiveOptions(), 2749 key: ar.key, 2750 selected: ar.isSelected() 2751 }); 2752 } 2753 } 2754 2755 /** 2756 * Mouseout event. 2757 * 2758 * @param {MapData} me The MapData context 2759 * @param {EventData} e jQuery event data 2760 * @return {[type]} [description] 2761 */ 2762 2763 function mouseout(me, e) { 2764 var newArea, 2765 ar = me.getDataForArea(this), 2766 opts = me.options; 2767 2768 2769 if (me.currentAreaId < 0 || !ar) { 2770 return; 2771 } 2772 2773 newArea = me.getDataForArea(e.relatedTarget); 2774 2775 if (newArea === ar) { 2776 return; 2777 } 2778 2779 me.currentAreaId = -1; 2780 ar.area = null; 2781 2782 queueMouseEvent(me, opts.mouseoutDelay, ar) 2783 .then(me.clearEffects); 2784 2785 if ($.isFunction(opts.onMouseout)) { 2786 opts.onMouseout.call(this, 2787 { 2788 e: e, 2789 options: opts, 2790 key: ar.key, 2791 selected: ar.isSelected() 2792 }); 2793 } 2794 2795 } 2796 2797 /** 2798 * Clear any active tooltip or highlight 2799 * 2800 * @param {MapData} me The MapData context 2801 * @param {EventData} e jQuery event data 2802 * @return {[type]} [description] 2803 */ 2804 2805 function clearEffects(me) { 2806 var opts = me.options; 2807 2808 me.ensureNoHighlight(); 2809 2810 if (opts.toolTipClose 2811 && $.inArray('area-mouseout', opts.toolTipClose) >= 0 2812 && me.activeToolTip) { 2813 me.clearToolTip(); 2814 } 2815 } 2816 2817 /** 2818 * Mouse click event handler 2819 * 2820 * @param {MapData} me The MapData context 2821 * @param {EventData} e jQuery event data 2822 * @return {[type]} [description] 2823 */ 2824 2825 function click(me, e) { 2826 var selected, list, list_target, newSelectionState, canChangeState, cbResult, 2827 that = this, 2828 ar = me.getDataForArea(this), 2829 opts = me.options; 2830 2831 function clickArea(ar) { 2832 var areaOpts, target; 2833 canChangeState = (ar.isSelectable() && 2834 (ar.isDeselectable() || !ar.isSelected())); 2835 2836 if (canChangeState) { 2837 newSelectionState = !ar.isSelected(); 2838 } else { 2839 newSelectionState = ar.isSelected(); 2840 } 2841 2842 list_target = m.getBoundList(opts, ar.key); 2843 2844 if ($.isFunction(opts.onClick)) { 2845 cbResult = opts.onClick.call(that, 2846 { 2847 e: e, 2848 listTarget: list_target, 2849 key: ar.key, 2850 selected: newSelectionState 2851 }); 2852 2853 if (u.isBool(cbResult)) { 2854 if (!cbResult) { 2855 return false; 2856 } 2857 target = $(ar.area).attr('href'); 2858 if (target !== '#') { 2859 window.location.href = target; 2860 return false; 2861 } 2862 } 2863 } 2864 2865 if (canChangeState) { 2866 selected = ar.toggle(); 2867 } 2868 2869 if (opts.boundList && opts.boundList.length > 0) { 2870 m.setBoundListProperties(opts, list_target, ar.isSelected()); 2871 } 2872 2873 areaOpts = ar.effectiveOptions(); 2874 if (areaOpts.includeKeys) { 2875 list = u.split(areaOpts.includeKeys); 2876 $.each(list, function (i, e) { 2877 var ar = me.getDataForKey(e.toString()); 2878 if (!ar.options.isMask) { 2879 clickArea(ar); 2880 } 2881 }); 2882 } 2883 } 2884 2885 mousedown.call(this, e); 2886 2887 if (opts.clickNavigate && ar.href) { 2888 window.location.href = ar.href; 2889 return; 2890 } 2891 2892 if (ar && !ar.owner.currentAction) { 2893 opts = me.options; 2894 clickArea(ar); 2895 } 2896 } 2897 2898 /** 2899 * Prototype for a MapData object, representing an ImageMapster bound object 2900 * @param {Element} image an IMG element 2901 * @param {object} options ImageMapster binding options 2902 */ 2903 m.MapData = function (image, options) { 2904 var me = this; 2905 2906 // (Image) main map image 2907 2908 me.image = image; 2909 2910 me.images = new m.MapImages(me); 2911 me.graphics = new m.Graphics(me); 2912 2913 // save the initial style of the image for unbinding. This is problematic, chrome 2914 // duplicates styles when assigning, and cssText is apparently not universally supported. 2915 // Need to do something more robust to make unbinding work universally. 2916 2917 me.imgCssText = image.style.cssText || null; 2918 2919 initializeDefaults(me); 2920 2921 me.configureOptions(options); 2922 2923 // create context-bound event handlers from our private functions 2924 2925 me.mouseover = function (e) { mouseover.call(this, me, e); }; 2926 me.mouseout = function (e) { mouseout.call(this, me, e); }; 2927 me.click = function (e) { click.call(this, me, e); }; 2928 me.clearEffects = function (e) { clearEffects.call(this, me, e); }; 2929 }; 2930 2931 m.MapData.prototype = { 2932 constructor: m.MapData, 2933 2934 /** 2935 * Set target.options from defaults + options 2936 * @param {[type]} target The target 2937 * @param {[type]} options The options to merge 2938 */ 2939 2940 configureOptions: function (options) { 2941 this.options = u.updateProps({}, m.defaults, options); 2942 }, 2943 2944 /** 2945 * Ensure all images are loaded 2946 * @return {Promise} A promise that resolves when the images have finished loading (or fail) 2947 */ 2948 2949 bindImages: function () { 2950 var me = this, 2951 mi = me.images; 2952 2953 // reset the images if this is a rebind 2954 2955 if (mi.length > 2) { 2956 mi.splice(2); 2957 } else if (mi.length === 0) { 2958 2959 // add the actual main image 2960 mi.add(me.image); 2961 // will create a duplicate of the main image, we need this to get raw size info 2962 mi.add(me.image.src); 2963 } 2964 2965 configureAltImages(me); 2966 2967 return me.images.bind(); 2968 }, 2969 2970 /** 2971 * Test whether an async action is currently in progress 2972 * @return {Boolean} true or false indicating state 2973 */ 2974 2975 isActive: function () { 2976 return !this.complete || this.currentAction; 2977 }, 2978 2979 /** 2980 * Return an object indicating the various states. This isn't really used by 2981 * production code. 2982 * 2983 * @return {object} An object with properties for various states 2984 */ 2985 2986 state: function () { 2987 return { 2988 complete: this.complete, 2989 resizing: this.currentAction === 'resizing', 2990 zoomed: this.zoomed, 2991 zoomedArea: this.zoomedArea, 2992 scaleInfo: this.scaleInfo 2993 }; 2994 }, 2995 2996 /** 2997 * Get a unique ID for the wrapper of this imagemapster 2998 * @return {string} A string that is unique to this image 2999 */ 3000 3001 wrapId: function () { 3002 return 'mapster_wrap_' + this.index; 3003 }, 3004 _idFromKey: function (key) { 3005 return typeof key === "string" && this._xref.hasOwnProperty(key) ? 3006 this._xref[key] : -1; 3007 }, 3008 3009 /** 3010 * Return a comma-separated string of all selected keys 3011 * @return {string} CSV of all keys that are currently selected 3012 */ 3013 3014 getSelected: function () { 3015 var result = ''; 3016 $.each(this.data, function (i, e) { 3017 if (e.isSelected()) { 3018 result += (result ? ',' : '') + this.key; 3019 } 3020 }); 3021 return result; 3022 }, 3023 3024 /** 3025 * Get an array of MapAreas associated with a specific AREA based on the keys for that area 3026 * @param {Element} area An HTML AREA 3027 * @param {number} atMost A number limiting the number of areas to be returned (typically 1 or 0 for no limit) 3028 * @return {MapArea[]} Array of MapArea objects 3029 */ 3030 3031 getAllDataForArea: function (area, atMost) { 3032 var i, ar, result, 3033 me = this, 3034 key = $(area).filter('area').attr(me.options.mapKey); 3035 3036 if (key) { 3037 result = []; 3038 key = u.split(key); 3039 3040 for (i = 0; i < (atMost || key.length) ; i++) { 3041 ar = me.data[me._idFromKey(key[i])]; 3042 ar.area = area.length ? area[0] : area; 3043 // set the actual area moused over/selected 3044 // TODO: this is a brittle model for capturing which specific area - if this method was not used, 3045 // ar.area could have old data. fix this. 3046 result.push(ar); 3047 } 3048 } 3049 3050 return result; 3051 }, 3052 getDataForArea: function (area) { 3053 var ar = this.getAllDataForArea(area, 1); 3054 return ar ? ar[0] || null : null; 3055 }, 3056 getDataForKey: function (key) { 3057 return this.data[this._idFromKey(key)]; 3058 }, 3059 3060 /** 3061 * Get the primary keys associated with an area group. 3062 * If this is a primary key, it will be returned. 3063 * 3064 * @param {string key An area key 3065 * @return {string} A CSV of area keys 3066 */ 3067 3068 getKeysForGroup: function (key) { 3069 var ar = this.getDataForKey(key); 3070 3071 return !ar ? '' : 3072 ar.isPrimary ? 3073 ar.key : 3074 this.getPrimaryKeysForMapAreas(ar.areas()).join(','); 3075 }, 3076 3077 /** 3078 * given an array of MapArea object, return an array of its unique primary keys 3079 * @param {MapArea[]} areas The areas to analyze 3080 * @return {string[]} An array of unique primary keys 3081 */ 3082 3083 getPrimaryKeysForMapAreas: function (areas) { 3084 var keys = []; 3085 $.each(areas, function (i, e) { 3086 if ($.inArray(e.keys[0], keys) < 0) { 3087 keys.push(e.keys[0]); 3088 } 3089 }); 3090 return keys; 3091 }, 3092 getData: function (obj) { 3093 if (typeof obj === 'string') { 3094 return this.getDataForKey(obj); 3095 } else if (obj && obj.mapster || u.isElement(obj)) { 3096 return this.getDataForArea(obj); 3097 } else { 3098 return null; 3099 } 3100 }, 3101 // remove highlight if present, raise event 3102 ensureNoHighlight: function () { 3103 var ar; 3104 if (this.highlightId >= 0) { 3105 this.graphics.clearHighlight(); 3106 ar = this.data[this.highlightId]; 3107 ar.changeState('highlight', false); 3108 this.setHighlightId(-1); 3109 } 3110 }, 3111 setHighlightId: function (id) { 3112 this.highlightId = id; 3113 }, 3114 3115 /** 3116 * Clear all active selections on this map 3117 */ 3118 3119 clearSelections: function () { 3120 $.each(this.data, function (i, e) { 3121 if (e.selected) { 3122 e.deselect(true); 3123 } 3124 }); 3125 this.removeSelectionFinish(); 3126 3127 }, 3128 3129 /** 3130 * Set area options from an array of option data. 3131 * 3132 * @param {object[]} areas An array of objects containing area-specific options 3133 */ 3134 3135 setAreaOptions: function (areas) { 3136 var i, area_options, ar; 3137 areas = areas || []; 3138 3139 // refer by: map_data.options[map_data.data[x].area_option_id] 3140 3141 for (i = areas.length - 1; i >= 0; i--) { 3142 area_options = areas[i]; 3143 if (area_options) { 3144 ar = this.getDataForKey(area_options.key); 3145 if (ar) { 3146 u.updateProps(ar.options, area_options); 3147 3148 // TODO: will not deselect areas that were previously selected, so this only works 3149 // for an initial bind. 3150 3151 if (u.isBool(area_options.selected)) { 3152 ar.selected = area_options.selected; 3153 } 3154 } 3155 } 3156 } 3157 }, 3158 // keys: a comma-separated list 3159 drawSelections: function (keys) { 3160 var i, key_arr = u.asArray(keys); 3161 3162 for (i = key_arr.length - 1; i >= 0; i--) { 3163 this.data[key_arr[i]].drawSelection(); 3164 } 3165 }, 3166 redrawSelections: function () { 3167 $.each(this.data, function (i, e) { 3168 if (e.isSelectedOrStatic()) { 3169 e.drawSelection(); 3170 } 3171 }); 3172 3173 }, 3174 ///called when images are done loading 3175 initialize: function () { 3176 var imgCopy, base_canvas, overlay_canvas, wrap, parentId, css, i, size, 3177 img, sort_func, sorted_list, scale, 3178 me = this, 3179 opts = me.options; 3180 3181 if (me.complete) { 3182 return; 3183 } 3184 3185 img = $(me.image); 3186 3187 parentId = img.parent().attr('id'); 3188 3189 // create a div wrapper only if there's not already a wrapper, otherwise, own it 3190 3191 if (parentId && parentId.length >= 12 && parentId.substring(0, 12) === "mapster_wrap") { 3192 wrap = img.parent(); 3193 wrap.attr('id', me.wrapId()); 3194 } else { 3195 wrap = $('<div id="' + me.wrapId() + '"></div>'); 3196 3197 if (opts.wrapClass) { 3198 if (opts.wrapClass === true) { 3199 wrap.addClass(img[0].className); 3200 } 3201 else { 3202 wrap.addClass(opts.wrapClass); 3203 } 3204 } 3205 } 3206 me.wrapper = wrap; 3207 3208 // me.images[1] is the copy of the original image. It should be loaded & at its native size now so we can obtain the true 3209 // width & height. This is needed to scale the imagemap if not being shown at its native size. It is also needed purely 3210 // to finish binding in case the original image was not visible. It can be impossible in some browsers to obtain the 3211 // native size of a hidden image. 3212 3213 me.scaleInfo = scale = u.scaleMap(me.images[0], me.images[1], opts.scaleMap); 3214 3215 me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); 3216 me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); 3217 3218 // Now we got what we needed from the copy -clone from the original image again to make sure any other attributes are copied 3219 imgCopy = $(me.images[1]) 3220 .addClass('mapster_el ' + me.images[0].className) 3221 .attr({ id: null, usemap: null }); 3222 3223 size = u.size(me.images[0]); 3224 3225 if (size.complete) { 3226 imgCopy.css({ 3227 width: size.width, 3228 height: size.height 3229 }); 3230 } 3231 3232 me.buildDataset(); 3233 3234 // now that we have processed all the areas, set css for wrapper, scale map if needed 3235 3236 css = { 3237 display: 'block', 3238 position: 'relative', 3239 padding: 0, 3240 width: scale.width, 3241 height: scale.height 3242 }; 3243 3244 if (opts.wrapCss) { 3245 $.extend(css, opts.wrapCss); 3246 } 3247 // if we were rebinding with an existing wrapper, the image will aready be in it 3248 if (img.parent()[0] !== me.wrapper[0]) { 3249 3250 img.before(me.wrapper); 3251 } 3252 3253 wrap.css(css); 3254 3255 // move all generated images into the wrapper for easy removal later 3256 3257 $(me.images.slice(2)).hide(); 3258 for (i = 1; i < me.images.length; i++) { 3259 wrap.append(me.images[i]); 3260 } 3261 3262 //me.images[1].style.cssText = me.image.style.cssText; 3263 3264 wrap.append(base_canvas) 3265 .append(overlay_canvas) 3266 .append(img.css(m.canvas_style)); 3267 3268 // images[0] is the original image with map, images[1] is the copy/background that is visible 3269 3270 u.setOpacity(me.images[0], 0); 3271 $(me.images[1]).show(); 3272 3273 u.setOpacity(me.images[1], 1); 3274 3275 if (opts.isSelectable && opts.onGetList) { 3276 sorted_list = me.data.slice(0); 3277 if (opts.sortList) { 3278 if (opts.sortList === "desc") { 3279 sort_func = function (a, b) { 3280 return a === b ? 0 : (a > b ? -1 : 1); 3281 }; 3282 } 3283 else { 3284 sort_func = function (a, b) { 3285 return a === b ? 0 : (a < b ? -1 : 1); 3286 }; 3287 } 3288 3289 sorted_list.sort(function (a, b) { 3290 a = a.value; 3291 b = b.value; 3292 return sort_func(a, b); 3293 }); 3294 } 3295 3296 me.options.boundList = opts.onGetList.call(me.image, sorted_list); 3297 } 3298 3299 me.complete = true; 3300 me.processCommandQueue(); 3301 3302 if (opts.onConfigured && typeof opts.onConfigured === 'function') { 3303 opts.onConfigured.call(img, true); 3304 } 3305 }, 3306 3307 // when rebind is true, the MapArea data will not be rebuilt. 3308 buildDataset: function (rebind) { 3309 var sel, areas, j, area_id, $area, area, curKey, mapArea, key, keys, mapAreaId, group_value, dataItem, href, 3310 me = this, 3311 opts = me.options, 3312 default_group; 3313 3314 function addAreaData(key, value) { 3315 var dataItem = new m.AreaData(me, key, value); 3316 dataItem.areaId = me._xref[key] = me.data.push(dataItem) - 1; 3317 return dataItem.areaId; 3318 } 3319 3320 me._xref = {}; 3321 me.data = []; 3322 if (!rebind) { 3323 me.mapAreas = []; 3324 } 3325 3326 default_group = !opts.mapKey; 3327 if (default_group) { 3328 opts.mapKey = 'data-mapster-key'; 3329 } 3330 3331 // the [attribute] selector is broken on old IE with jQuery. hasVml() is a quick and dirty 3332 // way to test for that 3333 3334 sel = m.hasVml() ? 'area' : 3335 (default_group ? 3336 'area[coords]' : 3337 'area[' + opts.mapKey + ']'); 3338 3339 areas = $(me.map).find(sel).unbind('.mapster'); 3340 3341 for (mapAreaId = 0; mapAreaId < areas.length; mapAreaId++) { 3342 area_id = 0; 3343 area = areas[mapAreaId]; 3344 $area = $(area); 3345 3346 // skip areas with no coords - selector broken for older ie 3347 if (!area.coords) { 3348 continue; 3349 } 3350 // Create a key if none was assigned by the user 3351 3352 if (default_group) { 3353 curKey = String(mapAreaId); 3354 $area.attr('data-mapster-key', curKey); 3355 3356 } else { 3357 curKey = area.getAttribute(opts.mapKey); 3358 } 3359 3360 // conditions for which the area will be bound to mouse events 3361 // only bind to areas that don't have nohref. ie 6&7 cannot detect the presence of nohref, so we have to also not bind if href is missing. 3362 3363 if (rebind) { 3364 mapArea = me.mapAreas[$area.data('mapster') - 1]; 3365 mapArea.configure(curKey); 3366 } else { 3367 mapArea = new m.MapArea(me, area, curKey); 3368 me.mapAreas.push(mapArea); 3369 } 3370 3371 keys = mapArea.keys; // converted to an array by mapArea 3372 3373 3374 // Iterate through each mapKey assigned to this area 3375 for (j = keys.length - 1; j >= 0; j--) { 3376 key = keys[j]; 3377 3378 if (opts.mapValue) { 3379 group_value = $area.attr(opts.mapValue); 3380 } 3381 if (default_group) { 3382 // set an attribute so we can refer to the area by index from the DOM object if no key 3383 area_id = addAreaData(me.data.length, group_value); 3384 dataItem = me.data[area_id]; 3385 dataItem.key = key = area_id.toString(); 3386 } 3387 else { 3388 area_id = me._xref[key]; 3389 if (area_id >= 0) { 3390 dataItem = me.data[area_id]; 3391 if (group_value && !me.data[area_id].value) { 3392 dataItem.value = group_value; 3393 } 3394 } 3395 else { 3396 area_id = addAreaData(key, group_value); 3397 dataItem = me.data[area_id]; 3398 dataItem.isPrimary = j === 0; 3399 } 3400 } 3401 mapArea.areaDataXref.push(area_id); 3402 dataItem.areasXref.push(mapAreaId); 3403 } 3404 3405 href = $area.attr('href'); 3406 if (href && href !== '#' && !dataItem.href) { 3407 dataItem.href = href; 3408 } 3409 3410 if (!mapArea.nohref) { 3411 $area.bind('click.mapster', me.click); 3412 3413 if (!m.isTouch) { 3414 $area.bind('mouseover.mapster', me.mouseover) 3415 .bind('mouseout.mapster', me.mouseout) 3416 .bind('mousedown.mapster', me.mousedown); 3417 3418 } 3419 3420 } 3421 3422 // store an ID with each area. 3423 $area.data("mapster", mapAreaId + 1); 3424 } 3425 3426 // TODO listenToList 3427 // if (opts.listenToList && opts.nitG) { 3428 // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); 3429 // } 3430 3431 // populate areas from config options 3432 me.setAreaOptions(opts.areas); 3433 me.redrawSelections(); 3434 3435 }, 3436 processCommandQueue: function () { 3437 3438 var cur, me = this; 3439 while (!me.currentAction && me.commands.length) { 3440 cur = me.commands[0]; 3441 me.commands.splice(0, 1); 3442 m.impl[cur.command].apply(cur.that, cur.args); 3443 } 3444 }, 3445 clearEvents: function () { 3446 $(this.map).find('area') 3447 .unbind('.mapster'); 3448 $(this.images) 3449 .unbind('.mapster'); 3450 }, 3451 _clearCanvases: function (preserveState) { 3452 // remove the canvas elements created 3453 if (!preserveState) { 3454 $(this.base_canvas).remove(); 3455 } 3456 $(this.overlay_canvas).remove(); 3457 }, 3458 clearMapData: function (preserveState) { 3459 var me = this; 3460 this._clearCanvases(preserveState); 3461 3462 // release refs to DOM elements 3463 $.each(this.data, function (i, e) { 3464 e.reset(); 3465 }); 3466 this.data = null; 3467 if (!preserveState) { 3468 // get rid of everything except the original image 3469 this.image.style.cssText = this.imgCssText; 3470 $(this.wrapper).before(this.image).remove(); 3471 } 3472 3473 me.images.clear(); 3474 3475 this.image = null; 3476 u.ifFunction(this.clearTooltip, this); 3477 }, 3478 3479 // Compelete cleanup process for deslecting items. Called after a batch operation, or by AreaData for single 3480 // operations not flagged as "partial" 3481 3482 removeSelectionFinish: function () { 3483 var g = this.graphics; 3484 3485 g.refreshSelections(); 3486 // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect 3487 g.clearHighlight(); 3488 } 3489 }; 3490}(jQuery)); 3491/* areadata.js 3492 AreaData and MapArea protoypes 3493*/ 3494 3495(function ($) { 3496 var m = $.mapster, u = m.utils; 3497 3498 /** 3499 * Select this area 3500 * 3501 * @param {AreaData} me AreaData context 3502 * @param {object} options Options for rendering the selection 3503 */ 3504 function select(options) { 3505 // need to add the new one first so that the double-opacity effect leaves the current one highlighted for singleSelect 3506 3507 var me = this, o = me.owner; 3508 if (o.options.singleSelect) { 3509 o.clearSelections(); 3510 } 3511 3512 // because areas can overlap - we can't depend on the selection state to tell us anything about the inner areas. 3513 // don't check if it's already selected 3514 if (!me.isSelected()) { 3515 if (options) { 3516 3517 // cache the current options, and map the altImageId if an altimage 3518 // was passed 3519 3520 me.optsCache = $.extend(me.effectiveRenderOptions('select'), 3521 options, 3522 { 3523 altImageId: o.images.add(options.altImage) 3524 }); 3525 } 3526 3527 me.drawSelection(); 3528 3529 me.selected = true; 3530 me.changeState('select', true); 3531 } 3532 3533 if (o.options.singleSelect) { 3534 o.graphics.refreshSelections(); 3535 } 3536 } 3537 3538 /** 3539 * Deselect this area, optionally deferring finalization so additional areas can be deselected 3540 * in a single operation 3541 * 3542 * @param {boolean} partial when true, the caller must invoke "finishRemoveSelection" to render 3543 */ 3544 3545 function deselect(partial) { 3546 var me = this; 3547 me.selected = false; 3548 me.changeState('select', false); 3549 3550 // release information about last area options when deselecting. 3551 3552 me.optsCache = null; 3553 me.owner.graphics.removeSelections(me.areaId); 3554 3555 // Complete selection removal process. This is separated because it's very inefficient to perform the whole 3556 // process for multiple removals, as the canvas must be totally redrawn at the end of the process.ar.remove 3557 3558 if (!partial) { 3559 me.owner.removeSelectionFinish(); 3560 } 3561 } 3562 3563 /** 3564 * Toggle the selection state of this area 3565 * @param {object} options Rendering options, if toggling on 3566 * @return {bool} The new selection state 3567 */ 3568 function toggle(options) { 3569 var me = this; 3570 if (!me.isSelected()) { 3571 me.select(options); 3572 } 3573 else { 3574 me.deselect(); 3575 } 3576 return me.isSelected(); 3577 } 3578 3579 /** 3580 * An AreaData object; represents a conceptual area that can be composed of 3581 * one or more MapArea objects 3582 * 3583 * @param {MapData} owner The MapData object to which this belongs 3584 * @param {string} key The key for this area 3585 * @param {string} value The mapValue string for this area 3586 */ 3587 3588 m.AreaData = function (owner, key, value) { 3589 $.extend(this, { 3590 owner: owner, 3591 key: key || '', 3592 // means this represents the first key in a list of keys (it's the area group that gets highlighted on mouseover) 3593 isPrimary: true, 3594 areaId: -1, 3595 href: '', 3596 value: value || '', 3597 options: {}, 3598 // "null" means unchanged. Use "isSelected" method to just test true/false 3599 selected: null, 3600 // xref to MapArea objects 3601 areasXref: [], 3602 // (temporary storage) - the actual area moused over 3603 area: null, 3604 // the last options used to render this. Cache so when re-drawing after a remove, changes in options won't 3605 // break already selected things. 3606 optsCache: null 3607 }); 3608 }; 3609 3610 /** 3611 * The public API for AreaData object 3612 */ 3613 3614 m.AreaData.prototype = { 3615 constuctor: m.AreaData, 3616 select: select, 3617 deselect: deselect, 3618 toggle: toggle, 3619 areas: function () { 3620 var i, result = []; 3621 for (i = 0; i < this.areasXref.length; i++) { 3622 result.push(this.owner.mapAreas[this.areasXref[i]]); 3623 } 3624 return result; 3625 }, 3626 // return all coordinates for all areas 3627 coords: function (offset) { 3628 var coords = []; 3629 $.each(this.areas(), function (i, el) { 3630 coords = coords.concat(el.coords(offset)); 3631 }); 3632 return coords; 3633 }, 3634 reset: function () { 3635 $.each(this.areas(), function (i, e) { 3636 e.reset(); 3637 }); 3638 this.areasXref = []; 3639 this.options = null; 3640 }, 3641 // Return the effective selected state of an area, incorporating staticState 3642 isSelectedOrStatic: function () { 3643 3644 var o = this.effectiveOptions(); 3645 return u.isBool(o.staticState) ? o.staticState : 3646 this.isSelected(); 3647 }, 3648 isSelected: function () { 3649 return u.isBool(this.selected) ? this.selected : 3650 u.isBool(this.owner.area_options.selected) ? this.owner.area_options.selected : false; 3651 }, 3652 isSelectable: function () { 3653 return u.isBool(this.effectiveOptions().staticState) ? false : 3654 (u.isBool(this.owner.options.staticState) ? false : u.boolOrDefault(this.effectiveOptions().isSelectable, true)); 3655 }, 3656 isDeselectable: function () { 3657 return u.isBool(this.effectiveOptions().staticState) ? false : 3658 (u.isBool(this.owner.options.staticState) ? false : u.boolOrDefault(this.effectiveOptions().isDeselectable, true)); 3659 }, 3660 isNotRendered: function () { 3661 var area = $(this.area); 3662 return area.attr('nohref') || 3663 !area.attr('href') || 3664 this.effectiveOptions().isMask; 3665 3666 }, 3667 3668 3669 /** 3670 * Return the overall options effective for this area. 3671 * This should get the default options, and merge in area-specific options, finally 3672 * overlaying options passed by parameter 3673 * 3674 * @param {[type]} options options which will supercede all other options for this area 3675 * @return {[type]} the combined options 3676 */ 3677 3678 effectiveOptions: function (options) { 3679 3680 var opts = u.updateProps({}, 3681 this.owner.area_options, 3682 this.options, 3683 options || {}, 3684 { 3685 id: this.areaId 3686 } 3687 ); 3688 3689 opts.selected = this.isSelected(); 3690 3691 return opts; 3692 }, 3693 3694 /** 3695 * Return the options effective for this area for a "render" or "highlight" mode. 3696 * This should get the default options, merge in the areas-specific options, 3697 * and then the mode-specific options. 3698 * @param {string} mode 'render' or 'highlight' 3699 * @param {[type]} options options which will supercede all other options for this area 3700 * @return {[type]} the combined options 3701 */ 3702 3703 effectiveRenderOptions: function (mode, options) { 3704 var allOpts, opts = this.optsCache; 3705 3706 if (!opts || mode === 'highlight') { 3707 allOpts = this.effectiveOptions(options); 3708 opts = u.updateProps({}, 3709 allOpts, 3710 allOpts["render_" + mode] 3711 ); 3712 3713 if (mode !== 'highlight') { 3714 this.optsCache = opts; 3715 } 3716 } 3717 return $.extend({}, opts); 3718 }, 3719 3720 // Fire callback on area state change 3721 changeState: function (state_type, state) { 3722 if ($.isFunction(this.owner.options.onStateChange)) { 3723 this.owner.options.onStateChange.call(this.owner.image, 3724 { 3725 key: this.key, 3726 state: state_type, 3727 selected: state 3728 } 3729 ); 3730 } 3731 }, 3732 3733 // highlight this area 3734 3735 highlight: function (options) { 3736 var o = this.owner; 3737 if (this.effectiveOptions().highlight) { 3738 o.graphics.addShapeGroup(this, "highlight", options); 3739 } 3740 o.setHighlightId(this.areaId); 3741 this.changeState('highlight', true); 3742 }, 3743 3744 // select this area. if "callEvent" is true then the state change event will be called. (This method can be used 3745 // during config operations, in which case no event is indicated) 3746 3747 drawSelection: function () { 3748 3749 3750 this.owner.graphics.addShapeGroup(this, "select"); 3751 3752 } 3753 3754 3755 }; 3756 // represents an HTML area 3757 m.MapArea = function (owner, areaEl, keys) { 3758 if (!owner) { 3759 return; 3760 } 3761 var me = this; 3762 me.owner = owner; // a MapData object 3763 me.area = areaEl; 3764 me.areaDataXref = []; // a list of map_data.data[] id's for each areaData object containing this 3765 me.originalCoords = []; 3766 $.each(u.split(areaEl.coords), function (i, el) { 3767 me.originalCoords.push(parseFloat(el)); 3768 }); 3769 me.length = me.originalCoords.length; 3770 me.shape = areaEl.shape.toLowerCase(); 3771 me.nohref = areaEl.nohref || !areaEl.href; 3772 me.configure(keys); 3773 }; 3774 m.MapArea.prototype = { 3775 constructor: m.MapArea, 3776 configure: function (keys) { 3777 this.keys = u.split(keys); 3778 }, 3779 reset: function () { 3780 this.area = null; 3781 }, 3782 coords: function (offset) { 3783 return $.map(this.originalCoords, function (e) { 3784 return offset ? e : e + offset; 3785 }); 3786 } 3787 }; 3788}(jQuery)); 3789/* areacorners.js 3790 determine the best place to put a box of dimensions (width,height) given a circle, rect or poly 3791*/ 3792 3793(function ($) { 3794 var u = $.mapster.utils; 3795 3796 3797 /** 3798 * Compute positions that will place a target with dimensions [width,height] outside 3799 * but near the boundaries of the elements "elements". When an imagemap is passed, the 3800 * 3801 * @param {Element|Element[]} elements An element or an array of elements (such as a jQuery object) 3802 * @param {Element} image The image to which area elements are bound, if this is an image map. 3803 * @param {Element} container The contianer in which the target must be constrained (or document, if missing) 3804 * @param {int} width The width of the target object 3805 * @return {object} a structure with the x and y positions 3806 */ 3807 u.areaCorners = function (elements, image, container, width, height) { 3808 var pos, found, minX, minY, maxX, maxY, bestMinX, bestMaxX, bestMinY, bestMaxY, curX, curY, nest, j, 3809 offsetx = 0, 3810 offsety = 0, 3811 rootx, 3812 rooty, 3813 iCoords, radius, angle, el, 3814 coords = []; 3815 3816 // if a single element was passed, map it to an array 3817 3818 elements = elements.length ? 3819 elements : 3820 [elements]; 3821 3822 container = container ? 3823 $(container) : 3824 $(document.body); 3825 3826 // get the relative root of calculation 3827 3828 pos = container.offset(); 3829 rootx = pos.left; 3830 rooty = pos.top; 3831 3832 // with areas, all we know about is relative to the top-left corner of the image. We need to add an offset compared to 3833 // the actual container. After this calculation, offsetx/offsety can be added to either the area coords, or the target's 3834 // absolute position to get the correct top/left boundaries of the container. 3835 3836 if (image) { 3837 pos = $(image).offset(); 3838 offsetx = pos.left; 3839 offsety = pos.top; 3840 } 3841 3842 // map the coordinates of any type of shape to a poly and use the logic. simpler than using three different 3843 // calculation methods. Circles use a 20 degree increment for this estimation. 3844 3845 for (j = 0; j < elements.length; j++) { 3846 el = elements[j]; 3847 if (el.nodeName === 'AREA') { 3848 iCoords = u.split(el.coords, parseInt); 3849 3850 switch (el.shape) { 3851 case 'circle': 3852 curX = iCoords[0]; 3853 curY = iCoords[1]; 3854 radius = iCoords[2]; 3855 coords = []; 3856 for (j = 0; j < 360; j += 20) { 3857 angle = j * Math.PI / 180; 3858 coords.push(curX + radius * Math.cos(angle), curY + radius * Math.sin(angle)); 3859 } 3860 break; 3861 case 'rect': 3862 coords.push(iCoords[0], iCoords[1], iCoords[2], iCoords[1], iCoords[2], iCoords[3], iCoords[0], iCoords[3]); 3863 break; 3864 default: 3865 coords = coords.concat(iCoords); 3866 break; 3867 } 3868 3869 // map area positions to it's real position in the container 3870 3871 for (j = 0; j < coords.length; j += 2) { 3872 coords[j] = parseInt(coords[j], 10) + offsetx; 3873 coords[j + 1] = parseInt(coords[j + 1], 10) + offsety; 3874 } 3875 } else { 3876 el = $(el); 3877 pos = el.position(); 3878 coords.push(pos.left, pos.top, 3879 pos.left + el.width(), pos.top, 3880 pos.left + el.width(), pos.top + el.height(), 3881 pos.left, pos.top + el.height()); 3882 3883 } 3884 } 3885 3886 minX = minY = bestMinX = bestMinY = 999999; 3887 maxX = maxY = bestMaxX = bestMaxY = -1; 3888 3889 for (j = coords.length - 2; j >= 0; j -= 2) { 3890 curX = coords[j]; 3891 curY = coords[j + 1]; 3892 3893 if (curX < minX) { 3894 minX = curX; 3895 bestMaxY = curY; 3896 } 3897 if (curX > maxX) { 3898 maxX = curX; 3899 bestMinY = curY; 3900 } 3901 if (curY < minY) { 3902 minY = curY; 3903 bestMaxX = curX; 3904 } 3905 if (curY > maxY) { 3906 maxY = curY; 3907 bestMinX = curX; 3908 } 3909 3910 } 3911 3912 // try to figure out the best place for the tooltip 3913 3914 if (width && height) { 3915 found = false; 3916 $.each([[bestMaxX - width, minY - height], [bestMinX, minY - height], 3917 [minX - width, bestMaxY - height], [minX - width, bestMinY], 3918 [maxX, bestMaxY - height], [maxX, bestMinY], 3919 [bestMaxX - width, maxY], [bestMinX, maxY] 3920 ], function (i, e) { 3921 if (!found && (e[0] > rootx && e[1] > rooty)) { 3922 nest = e; 3923 found = true; 3924 return false; 3925 } 3926 }); 3927 3928 // default to lower-right corner if nothing fit inside the boundaries of the image 3929 3930 if (!found) { 3931 nest = [maxX, maxY]; 3932 } 3933 } 3934 return nest; 3935 }; 3936}(jQuery)); 3937/* scale.js: resize and zoom functionality 3938 requires areacorners.js, when.js 3939*/ 3940 3941 3942(function ($) { 3943 var m = $.mapster, u = m.utils, p = m.MapArea.prototype; 3944 3945 m.utils.getScaleInfo = function (eff, actual) { 3946 var pct; 3947 if (!actual) { 3948 pct = 1; 3949 actual = eff; 3950 } else { 3951 pct = eff.width / actual.width || eff.height / actual.height; 3952 // make sure a float error doesn't muck us up 3953 if (pct > 0.98 && pct < 1.02) { pct = 1; } 3954 } 3955 return { 3956 scale: (pct !== 1), 3957 scalePct: pct, 3958 realWidth: actual.width, 3959 realHeight: actual.height, 3960 width: eff.width, 3961 height: eff.height, 3962 ratio: eff.width / eff.height 3963 }; 3964 }; 3965 // Scale a set of AREAs, return old data as an array of objects 3966 m.utils.scaleMap = function (image, imageRaw, scale) { 3967 3968 // stunningly, jQuery width can return zero even as width does not, seems to happen only 3969 // with adBlock or maybe other plugins. These must interfere with onload events somehow. 3970 3971 3972 var vis = u.size(image), 3973 raw = u.size(imageRaw, true); 3974 3975 if (!raw.complete()) { 3976 throw ("Another script, such as an extension, appears to be interfering with image loading. Please let us know about this."); 3977 } 3978 if (!vis.complete()) { 3979 vis = raw; 3980 } 3981 return this.getScaleInfo(vis, scale ? raw : null); 3982 }; 3983 3984 /** 3985 * Resize the image map. Only one of newWidth and newHeight should be passed to preserve scale 3986 * 3987 * @param {int} width The new width OR an object containing named parameters matching this function sig 3988 * @param {int} height The new height 3989 * @param {int} effectDuration Time in ms for the resize animation, or zero for no animation 3990 * @param {function} callback A function to invoke when the operation finishes 3991 * @return {promise} NOT YET IMPLEMENTED 3992 */ 3993 3994 m.MapData.prototype.resize = function (width, height, duration, callback) { 3995 var p, promises, newsize, els, highlightId, ratio, 3996 me = this; 3997 3998 // allow omitting duration 3999 callback = callback || duration; 4000 4001 function sizeCanvas(canvas, w, h) { 4002 if (m.hasCanvas()) { 4003 canvas.width = w; 4004 canvas.height = h; 4005 } else { 4006 $(canvas).width(w); 4007 $(canvas).height(h); 4008 } 4009 } 4010 4011 // Finalize resize action, do callback, pass control to command queue 4012 4013 function cleanupAndNotify() { 4014 4015 me.currentAction = ''; 4016 4017 if ($.isFunction(callback)) { 4018 callback(); 4019 } 4020 4021 me.processCommandQueue(); 4022 } 4023 4024 // handle cleanup after the inner elements are resized 4025 4026 function finishResize() { 4027 sizeCanvas(me.overlay_canvas, width, height); 4028 4029 // restore highlight state if it was highlighted before 4030 if (highlightId >= 0) { 4031 var areaData = me.data[highlightId]; 4032 areaData.tempOptions = { fade: false }; 4033 me.getDataForKey(areaData.key).highlight(); 4034 areaData.tempOptions = null; 4035 } 4036 sizeCanvas(me.base_canvas, width, height); 4037 me.redrawSelections(); 4038 cleanupAndNotify(); 4039 } 4040 4041 function resizeMapData() { 4042 $(me.image).css(newsize); 4043 // start calculation at the same time as effect 4044 me.scaleInfo = u.getScaleInfo({ 4045 width: width, 4046 height: height 4047 }, 4048 { 4049 width: me.scaleInfo.realWidth, 4050 height: me.scaleInfo.realHeight 4051 }); 4052 $.each(me.data, function (i, e) { 4053 $.each(e.areas(), function (i, e) { 4054 e.resize(); 4055 }); 4056 }); 4057 } 4058 4059 if (me.scaleInfo.width === width && me.scaleInfo.height === height) { 4060 return; 4061 } 4062 4063 highlightId = me.highlightId; 4064 4065 4066 if (!width) { 4067 ratio = height / me.scaleInfo.realHeight; 4068 width = Math.round(me.scaleInfo.realWidth * ratio); 4069 } 4070 if (!height) { 4071 ratio = width / me.scaleInfo.realWidth; 4072 height = Math.round(me.scaleInfo.realHeight * ratio); 4073 } 4074 4075 newsize = { 'width': String(width) + 'px', 'height': String(height) + 'px' }; 4076 if (!m.hasCanvas()) { 4077 $(me.base_canvas).children().remove(); 4078 } 4079 4080 // resize all the elements that are part of the map except the image itself (which is not visible) 4081 // but including the div wrapper 4082 els = $(me.wrapper).find('.mapster_el').add(me.wrapper); 4083 4084 if (duration) { 4085 promises = []; 4086 me.currentAction = 'resizing'; 4087 els.each(function (i, e) { 4088 p = u.defer(); 4089 promises.push(p); 4090 4091 $(e).animate(newsize, { 4092 duration: duration, 4093 complete: p.resolve, 4094 easing: "linear" 4095 }); 4096 }); 4097 4098 p = u.defer(); 4099 promises.push(p); 4100 4101 // though resizeMapData is not async, it needs to be finished just the same as the animations, 4102 // so add it to the "to do" list. 4103 4104 u.when.all(promises).then(finishResize); 4105 resizeMapData(); 4106 p.resolve(); 4107 } else { 4108 els.css(newsize); 4109 resizeMapData(); 4110 finishResize(); 4111 4112 } 4113 }; 4114 4115 4116 m.MapArea = u.subclass(m.MapArea, function () { 4117 //change the area tag data if needed 4118 this.base.init(); 4119 if (this.owner.scaleInfo.scale) { 4120 this.resize(); 4121 } 4122 }); 4123 4124 p.coords = function (percent, coordOffset) { 4125 var j, newCoords = [], 4126 pct = percent || this.owner.scaleInfo.scalePct, 4127 offset = coordOffset || 0; 4128 4129 if (pct === 1 && coordOffset === 0) { 4130 return this.originalCoords; 4131 } 4132 4133 for (j = 0; j < this.length; j++) { 4134 //amount = j % 2 === 0 ? xPct : yPct; 4135 newCoords.push(Math.round(this.originalCoords[j] * pct) + offset); 4136 } 4137 return newCoords; 4138 }; 4139 p.resize = function () { 4140 this.area.coords = this.coords().join(','); 4141 }; 4142 4143 p.reset = function () { 4144 this.area.coords = this.coords(1).join(','); 4145 }; 4146 4147 m.impl.resize = function (width, height, duration, callback) { 4148 if (!width && !height) { 4149 return false;
4150 } 4151 var x = (new m.Method(this, 4152 function () { 4153 this.resize(width, height, duration, callback); 4154 }, 4155 null, 4156 { 4157 name: 'resize', 4158 args: arguments 4159 } 4160 )).go(); 4161 return x; 4162 }; 4163 4164 /* 4165 m.impl.zoom = function (key, opts) { 4166 var options = opts || {}; 4167 4168 function zoom(areaData) { 4169 // this will be MapData object returned by Method 4170 4171 var scroll, corners, height, width, ratio, 4172 diffX, diffY, ratioX, ratioY, offsetX, offsetY, newWidth, newHeight, scrollLeft, scrollTop, 4173 padding = options.padding || 0, 4174 scrollBarSize = areaData ? 20 : 0, 4175 me = this, 4176 zoomOut = false; 4177 4178 if (areaData) { 4179 // save original state on first zoom operation 4180 if (!me.zoomed) { 4181 me.zoomed = true; 4182 me.preZoomWidth = me.scaleInfo.width; 4183 me.preZoomHeight = me.scaleInfo.height; 4184 me.zoomedArea = areaData; 4185 if (options.scroll) { 4186 me.wrapper.css({ overflow: 'auto' }); 4187 } 4188 } 4189 corners = $.mapster.utils.areaCorners(areaData.coords(1, 0)); 4190 width = me.wrapper.innerWidth() - scrollBarSize - padding * 2; 4191 height = me.wrapper.innerHeight() - scrollBarSize - padding * 2; 4192 diffX = corners.maxX - corners.minX; 4193 diffY = corners.maxY - corners.minY; 4194 ratioX = width / diffX; 4195 ratioY = height / diffY; 4196 ratio = Math.min(ratioX, ratioY); 4197 offsetX = (width - diffX * ratio) / 2; 4198 offsetY = (height - diffY * ratio) / 2; 4199 4200 newWidth = me.scaleInfo.realWidth * ratio; 4201 newHeight = me.scaleInfo.realHeight * ratio; 4202 scrollLeft = (corners.minX) * ratio - padding - offsetX; 4203 scrollTop = (corners.minY) * ratio - padding - offsetY; 4204 } else { 4205 if (!me.zoomed) { 4206 return; 4207 } 4208 zoomOut = true; 4209 newWidth = me.preZoomWidth; 4210 newHeight = me.preZoomHeight; 4211 scrollLeft = null; 4212 scrollTop = null; 4213 } 4214 4215 this.resize({ 4216 width: newWidth, 4217 height: newHeight, 4218 duration: options.duration, 4219 scroll: scroll, 4220 scrollLeft: scrollLeft, 4221 scrollTop: scrollTop, 4222 // closure so we can be sure values are correct 4223 callback: (function () { 4224 var isZoomOut = zoomOut, 4225 scroll = options.scroll, 4226 areaD = areaData; 4227 return function () { 4228 if (isZoomOut) { 4229 me.preZoomWidth = null; 4230 me.preZoomHeight = null; 4231 me.zoomed = false; 4232 me.zoomedArea = false; 4233 if (scroll) { 4234 me.wrapper.css({ overflow: 'inherit' }); 4235 } 4236 } else { 4237 // just to be sure it wasn't canceled & restarted 4238 me.zoomedArea = areaD; 4239 } 4240 }; 4241 } ()) 4242 }); 4243 } 4244 return (new m.Method(this, 4245 function (opts) { 4246 zoom.call(this); 4247 }, 4248 function () { 4249 zoom.call(this.owner, this); 4250 }, 4251 { 4252 name: 'zoom', 4253 args: arguments, 4254 first: true, 4255 key: key 4256 } 4257 )).go(); 4258 4259 4260 }; 4261 */ 4262}(jQuery)); 4263/* tooltip.js - tooltip functionality 4264 requires areacorners.js 4265*/ 4266 4267(function ($) { 4268 4269 var m = $.mapster, u = m.utils; 4270 4271 $.extend(m.defaults, { 4272 toolTipContainer: '<div style="border: 2px solid black; background: #EEEEEE; width:160px; padding:4px; margin: 4px; -moz-box-shadow: 3px 3px 5px #535353; ' +
vendor: 5,348 bytes, lines 4273-4424
4273 '-webkit-box-shadow: 3px 3px 5px #535353; box-shadow: 3px 3px 5px #535353; -moz-border-radius: 6px 6px 6px 6px; -webkit-border-radius: 6px; ' + 4274 'border-radius: 6px 6px 6px 6px; opacity: 0.9;"></dteniv>', 4275 showToolTip: false, 4276 toolTipFade: true, 4277 toolTipClose: ['area-mouseout', 'image-mouseout'], 4278 onShowToolTip: null, 4279 onHideToolTip: null 4280 }); 4281 4282 $.extend(m.area_defaults, { 4283 toolTip: null, 4284 toolTipClose: null 4285 }); 4286 4287 4288 /** 4289 * Show a tooltip positioned near this area. 4290 * 4291 * @param {string|jquery} html A string of html or a jQuery object containing the tooltip content. 4292 * @param {string|jquery} [template] The html template in which to wrap the content 4293 * @param {string|object} [css] CSS to apply to the outermost element of the tooltip 4294 * @return {jquery} The tooltip that was created 4295 */ 4296 4297 function createToolTip(html, template, css) { 4298 var tooltip; 4299 4300 // wrap the template in a jQuery object, or clone the template if it's already one. 4301 // This assumes that anything other than a string is a jQuery object; if it's not jQuery will 4302 // probably throw an error. 4303 4304 if (template) { 4305 tooltip = typeof template === 'string' ? 4306 $(template) : 4307 $(template).clone(); 4308 4309 tooltip.append(html); 4310 } else { 4311 tooltip = $(html); 4312 } 4313 4314 // always set display to block, or the positioning css won't work if the end user happened to 4315 // use a non-block type element. 4316 4317 tooltip.css($.extend((css || {}), { 4318 display: "block", 4319 position: "absolute" 4320 })).hide(); 4321 4322 $('body').append(tooltip); 4323 4324 // we must actually add the tooltip to the DOM and "show" it in order to figure out how much space it 4325 // consumes, and then reposition it with that knowledge. 4326 // We also cache the actual opacity setting to restore finally. 4327 4328 tooltip.attr("data-opacity", tooltip.css("opacity")) 4329 .css("opacity", 0); 4330 4331 // doesn't really show it because opacity=0 4332 4333 return tooltip.show(); 4334 } 4335 4336 4337 /** 4338 * Show a tooltip positioned near this area. 4339 * 4340 * @param {jquery} tooltip The tooltip 4341 * @param {object} [options] options for displaying the tooltip. 4342 * @config {int} [left] The 0-based absolute x position for the tooltip 4343 * @config {int} [top] The 0-based absolute y position for the tooltip 4344 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4345 * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. 4346 */ 4347 4348 function showToolTipImpl(tooltip, options) { 4349 var tooltipCss = { 4350 "left": options.left + "px", 4351 "top": options.top + "px" 4352 }, 4353 actalOpacity = tooltip.attr("data-opacity") || 0, 4354 zindex = tooltip.css("z-index"); 4355 4356 if (parseInt(zindex, 10) === 0 4357 || zindex === "auto") { 4358 tooltipCss["z-index"] = 9999; 4359 } 4360 4361 tooltip.css(tooltipCss) 4362 .addClass('mapster_tooltip'); 4363 4364 4365 if (options.fadeDuration && options.fadeDuration > 0) { 4366 u.fader(tooltip[0], 0, actalOpacity, options.fadeDuration); 4367 } else { 4368 u.setOpacity(tooltip[0], actalOpacity); 4369 } 4370 } 4371 4372 /** 4373 * Hide and remove active tooltips 4374 * 4375 * @param {MapData} this The mapdata object to which the tooltips belong 4376 */ 4377 4378 m.MapData.prototype.clearToolTip = function () { 4379 if (this.activeToolTip) { 4380 this.activeToolTip.stop().remove(); 4381 this.activeToolTip = null; 4382 this.activeToolTipID = null; 4383 u.ifFunction(this.options.onHideToolTip, this); 4384 } 4385 }; 4386 4387 /** 4388 * Configure the binding between a named tooltip closing option, and a mouse event. 4389 * 4390 * If a callback is passed, it will be called when the activating event occurs, and the tooltip will 4391 * only closed if it returns true. 4392 * 4393 * @param {MapData} [this] The MapData object to which this tooltip belongs. 4394 * @param {String} option The name of the tooltip closing option 4395 * @param {String} event UI event to bind to this option 4396 * @param {Element} target The DOM element that is the target of the event 4397 * @param {Function} [beforeClose] Callback when the tooltip is closed 4398 * @param {Function} [onClose] Callback when the tooltip is closed 4399 */ 4400 function bindToolTipClose(options, bindOption, event, target, beforeClose, onClose) { 4401 var event_name = event + '.mapster-tooltip'; 4402 4403 if ($.inArray(bindOption, options) >= 0) { 4404 target.unbind(event_name) 4405 .bind(event_name, function (e) { 4406 if (!beforeClose || beforeClose.call(this, e)) { 4407 target.unbind('.mapster-tooltip'); 4408 if (onClose) { 4409 onClose.call(this); 4410 } 4411 } 4412 }); 4413 4414 return { 4415 object: target, 4416 event: event_name 4417 }; 4418 } 4419 } 4420 4421 /** 4422 * Show a tooltip. 4423 * 4424 * @param {string|jquery}
4424 [tooltip] A string of html or a jQuery object containing the tooltip content. 4425 * 4426 * @param {string|jquery} [target] The target of the tooltip, to be used to determine positioning. If null, 4427 * absolute position values must be passed with left and top. 4428 * 4429 * @param {string|jquery} [image] If target is an [area] the image that owns it 4430 * 4431 * @param {string|jquery} [container] An element within which the tooltip must be bounded 4432 * 4433 * 4434 * 4435 * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - 4436 * The markup, or a jquery object, containing the data for the tooltip 4437 * 4438 * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip 4439 * closes: 'area-click','tooltip-click','image-mouseout' are valid values 4440 * then no template will be used. 4441 * @config {int} [offsetx] the horizontal amount to offset the tooltip 4442 * @config {int} [offsety] the vertical amount to offset the tooltip 4443 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4444 */ 4445 4446 function showToolTip(tooltip, target, image, container, options) { 4447 var corners, 4448 ttopts = {}; 4449 4450 options = options || {}; 4451 4452 4453 if (target) { 4454 4455 corners = u.areaCorners(target, image, container, 4456 tooltip.outerWidth(true), 4457 tooltip.outerHeight(true)); 4458 4459 // Try to upper-left align it first, if that doesn't work, change the parameters 4460 4461 ttopts.left = corners[0]; 4462 ttopts.top = corners[1]; 4463 4464 } else { 4465 4466 ttopts.left = options.left; 4467 ttopts.top = options.top; 4468 } 4469 4470 ttopts.left += (options.offsetx || 0); 4471 ttopts.top += (options.offsety || 0); 4472 4473 ttopts.css = options.css; 4474 ttopts.fadeDuration = options.fadeDuration; 4475 4476 showToolTipImpl(tooltip, ttopts); 4477 4478 return tooltip; 4479 } 4480 4481 /** 4482 * Show a tooltip positioned near this area. 4483 * 4484 * @param {string|jquery} [content] A string of html or a jQuery object containing the tooltip content. 4485 4486 * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - 4487 * The markup, or a jquery object, containing the data for the tooltip 4488 * @config {string|jquery} [container] An element within which the tooltip must be bounded 4489 * @config {bool} [template] a template to use instead of the default. If this property exists and is null, 4490 * then no template will be used. 4491 * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip 4492 * closes: 'area-click','tooltip-click','image-mouseout' are valid values 4493 * then no template will be used. 4494 * @config {int} [offsetx] the horizontal amount to offset the tooltip 4495 * @config {int} [offsety] the vertical amount to offset the tooltip 4496 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4497 */ 4498 m.AreaData.prototype.showToolTip = function (content, options) { 4499 var tooltip, closeOpts, target, tipClosed, template, 4500 ttopts = {}, 4501 ad = this, 4502 md = ad.owner, 4503 areaOpts = ad.effectiveOptions(); 4504 4505 // copy the options object so we can update it 4506 options = options ? $.extend({}, options) : {}; 4507 4508 content = content || areaOpts.toolTip; 4509 closeOpts = options.closeEvents || areaOpts.toolTipClose || md.options.toolTipClose || 'tooltip-click'; 4510 4511 template = typeof options.template !== 'undefined' ? 4512 options.template : 4513 md.options.toolTipContainer; 4514 4515 options.closeEvents = typeof closeOpts === 'string' ? 4516 closeOpts = u.split(closeOpts) : 4517 closeOpts; 4518 4519 options.fadeDuration = options.fadeDuration || 4520 (md.options.toolTipFade ? 4521 (md.options.fadeDuration || areaOpts.fadeDuration) : 0); 4522 4523 target = ad.area ? 4524 ad.area : 4525 $.map(ad.areas(), 4526 function (e) { 4527 return e.area; 4528 }); 4529 4530 if (md.activeToolTipID === ad.areaId) { 4531 return; 4532 } 4533 4534 md.clearToolTip(); 4535 4536 md.activeToolTip = tooltip = createToolTip(content, 4537 template, 4538 options.css); 4539 4540 md.activeToolTipID = ad.areaId; 4541 4542 tipClosed = function () { 4543 md.clearToolTip(); 4544 }; 4545 4546 bindToolTipClose(closeOpts, 'area-click', 'click', $(md.map), null, tipClosed); 4547 bindToolTipClose(closeOpts, 'tooltip-click', 'click', tooltip, null, tipClosed); 4548 bindToolTipClose(closeOpts, 'image-mouseout', 'mouseout', $(md.image), function (e) { 4549 return (e.relatedTarget && e.relatedTarget.nodeName !== 'AREA' && e.relatedTarget !== ad.area); 4550 }, tipClosed); 4551 4552 4553 showToolTip(tooltip, 4554 target, 4555 md.image, 4556 options.container, 4557 template, 4558 options); 4559 4560 u.ifFunction(md.options.onShowToolTip, ad.area, 4561 { 4562 toolTip: tooltip, 4563 options: ttopts, 4564 areaOptions: areaOpts, 4565 key: ad.key, 4566 selected: ad.isSelected() 4567 }); 4568 4569 return tooltip; 4570 }; 4571 4572 4573 /** 4574 * Parse an object that could be a string, a jquery object, or an object with a "contents" property 4575 * containing html or a jQuery object. 4576 * 4577 * @param {object|string|jQuery} options The parameter to parse 4578 * @return {string|jquery} A string or jquery object 4579 */ 4580 function getHtmlFromOptions(options) { 4581 4582 // see if any html was passed as either the options object itself, or the content property 4583 4584 return (options ? 4585 ((typeof options === 'string' || options.jquery) ? 4586 options : 4587 options.content) : 4588 null); 4589 } 4590 4591 /** 4592 * Activate or remove a tooltip for an area. When this method is called on an area, the 4593 * key parameter doesn't apply and "options" is the first parameter. 4594 *
4595 * When called with no parameters, or "key" is a falsy value, any active tooltip is cleared. 4596 * 4597 * When only a key is provided, the default tooltip for the area is used. 4598 * 4599 * When html is provided, this is used instead of the default tooltip. 4600 * 4601 * When "noTemplate" is true, the default tooltip template will not be used either, meaning only 4602 * the actual html passed will be used. 4603 * 4604 * @param {string|AreaElement} key The area for which to activate a tooltip, or a DOM element. 4605 * 4606 * @param {object|string|jquery} [options] options to apply when creating this tooltip - OR - 4607 * The markup, or a jquery object, containing the data for the tooltip 4608 * @config {string|jQuery} [content] the inner content of the tooltip; the tooltip text or HTML 4609 * @config {Element|jQuery} [container] the inner content of the tooltip; the tooltip text or HTML 4610 * @config {bool} [template] a template to use instead of the default. If this property exists and is null, 4611 * then no template will be used. 4612 * @config {int} [offsetx] the horizontal amount to offset the tooltip. 4613 * @config {int} [offsety] the vertical amount to offset the tooltip. 4614 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4615 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4616 * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. 4617 * @return {jQuery} The jQuery object 4618 */ 4619 4620 m.impl.tooltip = function (key, options) { 4621 return (new m.Method(this, 4622 function mapData() { 4623 var tooltip, target, md = this; 4624 if (!key) { 4625 md.clearToolTip(); 4626 } else { 4627 target = $(key); 4628 if (md.activeToolTipID === target[0]) { 4629 return; 4630 } 4631 md.clearToolTip(); 4632 4633 md.activeToolTip = tooltip = createToolTip(getHtmlFromOptions(options), 4634 options.template || md.options.toolTipContainer, 4635 options.css); 4636 md.activeToolTipID = target[0]; 4637 4638 bindToolTipClose(['tooltip-click'], 'tooltip-click', 'click', tooltip, null, function () { 4639 md.clearToolTip(); 4640 }); 4641 4642 md.activeToolTip = tooltip = showToolTip(tooltip, 4643 target, 4644 md.image, 4645 options.container, 4646 options); 4647 } 4648 }, 4649 function areaData() { 4650 if ($.isPlainObject(key) && !options) { 4651 options = key; 4652 } 4653 4654 this.showToolTip(getHtmlFromOptions(options), options); 4655 }, 4656 { 4657 name: 'tooltip', 4658 args: arguments, 4659 key: key 4660 } 4661 )).go(); 4662 }; 4663}(jQuery));
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.