vendor: 146,421 bytes, lines 1-4203
1/* ImageMapster 2 Version: 1.2.14-beta1 (6/18/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.14-beta1", 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 /** 995 * Basic indexOf implementation for IE7-8. Though we use $.inArray, some jQuery versions will try to 996 * use a prototpye on the calling object, defeating the purpose of using $.inArray in the first place. 997 * 998 * This will be replaced with the array prototype if it's available. 999 * 1000 * @param {Array} arr The array to search 1001 * @param {Object} target The item to search for 1002 * @return {Number} The index of the item, or -1 if not found 1003 */ 1004 indexOf: function(arr,target){ 1005 for(var i=0; i<arr.length; i++){ 1006 if(arr[i]===target){ 1007 return i; 1008 } 1009 } 1010 return -1; 1011 }, 1012 1013 // finds element of array or object with a property "prop" having value "val" 1014 // if prop is not defined, then just looks for property with value "val" 1015 indexOfProp: function (obj, prop, val) { 1016 var result = obj.constructor === Array ? -1 : null; 1017 $.each(obj, function (i, e) { 1018 if (e && (prop ? e[prop] : e) === val) { 1019 result = i; 1020 return false; 1021 } 1022 }); 1023 return result; 1024 }, 1025 // returns "obj" if true or false, or "def" if not true/false 1026 boolOrDefault: function (obj, def) { 1027 return this.isBool(obj) ? 1028 obj : def || false; 1029 }, 1030 isBool: function (obj) { 1031 return typeof obj === "boolean"; 1032 }, 1033 isUndef: function(obj) { 1034 return typeof obj === "undefined"; 1035 }, 1036 // evaluates "obj", if function, calls it with args 1037 // (todo - update this to handle variable lenght/more than one arg) 1038 ifFunction: function (obj, that, args) { 1039 if ($.isFunction(obj)) { 1040 obj.call(that, args); 1041 } 1042 }, 1043 size: function(image, raw) { 1044 var u=$.mapster.utils; 1045 return { 1046 width: raw ? (image.width || image.naturalWidth) : u.imgWidth(image,true) , 1047 height: raw ? (image.height || image.naturalHeight) : u.imgHeight(image,true), 1048 complete: function() { return !!this.height && !!this.width;} 1049 }; 1050 }, 1051 1052 1053 /** 1054 * Set the opacity of the element. This is an IE<8 specific function for handling VML. 1055 * When using VML we must override the "setOpacity" utility function (monkey patch ourselves). 1056 * jQuery does not deal with opacity correctly for VML elements. This deals with that. 1057 * 1058 * @param {Element} el The DOM element 1059 * @param {double} opacity A value between 0 and 1 inclusive. 1060 */ 1061 1062 setOpacity: function (el, opacity) { 1063 if ($.mapster.hasCanvas()) { 1064 el.style.opacity = opacity; 1065 } else { 1066 $(el).each(function(i,e) { 1067 if (typeof e.opacity !=='undefined') { 1068 e.opacity=opacity; 1069 } else { 1070 $(e).css("opacity",opacity); 1071 } 1072 }); 1073 } 1074 }, 1075 1076 1077 // fade "el" from opacity "op" to "endOp" over a period of time "duration" 1078 1079 fader: (function () { 1080 var elements = {}, 1081 lastKey = 0, 1082 fade_func = function (el, op, endOp, duration) { 1083 var index, 1084 cbIntervals = duration/15, 1085 obj, u = $.mapster.utils; 1086 1087 if (typeof el === 'number') { 1088 obj = elements[el]; 1089 if (!obj) { 1090 return; 1091 } 1092 } else { 1093 index = u.indexOfProp(elements, null, el); 1094 if (index) { 1095 delete elements[index]; 1096 } 1097 elements[++lastKey] = obj = el; 1098 el = lastKey; 1099 } 1100 1101 endOp = endOp || 1; 1102 1103 op = (op + (endOp / cbIntervals) > endOp - 0.01) ? endOp : op + (endOp / cbIntervals); 1104 1105 u.setOpacity(obj, op); 1106 if (op < endOp) { 1107 setTimeout(function () { 1108 fade_func(el, op, endOp, duration); 1109 }, 15); 1110 } 1111 }; 1112 return fade_func; 1113 } ()) 1114 }, 1115 getBoundList: function (opts, key_list) { 1116 if (!opts.boundList) { 1117 return null; 1118 } 1119 var index, key, result = $(), list = $.mapster.utils.split(key_list); 1120 opts.boundList.each(function (i,e) { 1121 for (index = 0; index < list.length; index++) { 1122 key = list[index]; 1123 if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { 1124 result = result.add(e); 1125 } 1126 } 1127 }); 1128 return result; 1129 }, 1130 // Causes changes to the bound list based on the user action (select or deselect) 1131 // area: the jQuery area object 1132 // returns the matching elements from the bound list for the first area passed (normally only one should be passed, but 1133 // a list can be passed 1134 setBoundListProperties: function (opts, target, selected) { 1135 target.each(function (i,e) { 1136 if (opts.listSelectedClass) { 1137 if (selected) { 1138 $(e).addClass(opts.listSelectedClass); 1139 } else { 1140 $(e).removeClass(opts.listSelectedClass); 1141 } 1142 } 1143 if (opts.listSelectedAttribute) { 1144 $(e).attr(opts.listSelectedAttribute, selected); 1145 } 1146 }); 1147 }, 1148 getMapDataIndex: function (obj) { 1149 var img, id; 1150 switch (obj.tagName && obj.tagName.toLowerCase()) { 1151 case 'area': 1152 id = $(obj).parent().attr('name'); 1153 img = $("img[usemap='#" + id + "']")[0]; 1154 break; 1155 case 'img': 1156 img = obj; 1157 break; 1158 } 1159 return img ? 1160 this.utils.indexOfProp(this.map_cache, 'image', img) : -1; 1161 }, 1162 getMapData: function (obj) { 1163 var index = this.getMapDataIndex(obj.length ? obj[0]:obj); 1164 if (index >= 0) { 1165 return index >= 0 ? this.map_cache[index] : null; 1166 } 1167 }, 1168 /** 1169 * Queue a command to be run after the active async operation has finished 1170 * @param {MapData} map_data The target MapData object 1171 * @param {jQuery} that jQuery object on which the command was invoked 1172 * @param {string} command the ImageMapster method name 1173 * @param {object[]} args arguments passed to the method 1174 * @return {bool} true if the command was queued, false if not (e.g. there was no need to) 1175 */ 1176 queueCommand: function (map_data, that, command, args) { 1177 if (!map_data) { 1178 return false; 1179 } 1180 if (!map_data.complete || map_data.currentAction) { 1181 map_data.commands.push( 1182 { 1183 that: that, 1184 command: command, 1185 args: args 1186 }); 1187 return true; 1188 } 1189 return false; 1190 }, 1191 unload: function () { 1192 this.impl.unload(); 1193 this.utils = null; 1194 this.impl = null; 1195 $.fn.mapster = null; 1196 $.mapster = null; 1197 $('*').unbind(); 1198 } 1199 }; 1200 1201 // Config for object prototypes 1202 // first: use only first object (for things that should not apply to lists) 1203 /// calls back one of two fuinctions, depending on whether an area was obtained. 1204 // opts: { 1205 // name: 'method name', 1206 // key: 'key, 1207 // args: 'args' 1208 // 1209 //} 1210 // name: name of method (required) 1211 // args: arguments to re-call with 1212 // Iterates through all the objects passed, and determines whether it's an area or an image, and calls the appropriate 1213 // callback for each. If anything is returned from that callback, the process is stopped and that data return. Otherwise, 1214 // the object itself is returned. 1215 1216 var m = $.mapster, 1217 u = m.utils, 1218 ap = Array.prototype; 1219 1220 1221 // jQuery's width() and height() are broken on IE9 in some situations. This tries everything. 1222 $.each(["width","height"],function(i,e) { 1223 var capProp = e.substr(0,1).toUpperCase() + e.substr(1); 1224 // when jqwidth parm is passed, it also checks the jQuery width()/height() property 1225 // the issue is that jQUery width() can report a valid size before the image is loaded in some browsers 1226 // without it, we can read zero even when image is loaded in other browsers if its not visible 1227 // we must still check because stuff like adblock can temporarily block it 1228 // what a goddamn headache 1229 u["img"+capProp]=function(img,jqwidth) { 1230 return (jqwidth ? $(img)[e]() : 0) || 1231 img[e] || img["natural"+capProp] || img["client"+capProp] || img["offset"+capProp]; 1232 }; 1233 1234 }); 1235 1236 /** 1237 * The Method object encapsulates the process of testing an ImageMapster method to see if it's being 1238 * invoked on an image, or an area; then queues the command if the MapData is in an active state. 1239 * 1240 * @param {[jQuery]} that The target of the invocation 1241 * @param {[function]} func_map The callback if the target is an imagemap 1242 * @param {[function]} func_area The callback if the target is an area 1243 * @param {[object]} opt Options: { key: a map key if passed explicitly 1244 * name: the command name, if it can be queued, 1245 * args: arguments to the method 1246 * } 1247 */ 1248 1249 m.Method = function (that, func_map, func_area, opts) { 1250 var me = this; 1251 me.name = opts.name; 1252 me.output = that; 1253 me.input = that; 1254 me.first = opts.first || false; 1255 me.args = opts.args ? ap.slice.call(opts.args, 0) : []; 1256 me.key = opts.key; 1257 me.func_map = func_map; 1258 me.func_area = func_area; 1259 //$.extend(me, opts); 1260 me.name = opts.name; 1261 me.allowAsync = opts.allowAsync || false; 1262 }; 1263 m.Method.prototype = { 1264 constructor: m.Method, 1265 go: function () { 1266 var i, data, ar, len, result, src = this.input, 1267 area_list = [], 1268 me = this; 1269 1270 len = src.length; 1271 for (i = 0; i < len; i++) { 1272 data = $.mapster.getMapData(src[i]); 1273 if (data) { 1274 if (!me.allowAsync && m.queueCommand(data, me.input, me.name, me.args)) { 1275 if (this.first) { 1276 result = ''; 1277 } 1278 continue; 1279 } 1280 1281 ar = data.getData(src[i].nodeName === 'AREA' ? src[i] : this.key); 1282 if (ar) { 1283 if ($.inArray(ar, area_list) < 0) { 1284 area_list.push(ar); 1285 } 1286 } else { 1287 result = this.func_map.apply(data, me.args); 1288 } 1289 if (this.first || typeof result !== 'undefined') { 1290 break; 1291 } 1292 } 1293 } 1294 // if there were areas, call the area function for each unique group 1295 $(area_list).each(function (i,e) { 1296 result = me.func_area.apply(e, me.args); 1297 }); 1298 1299 if (typeof result !== 'undefined') { 1300 return result; 1301 } else { 1302 return this.output; 1303 } 1304 } 1305 }; 1306 1307 $.mapster.impl = (function () { 1308 var me = {}, 1309 addMap= function (map_data) { 1310 return m.map_cache.push(map_data) - 1; 1311 }, 1312 removeMap = function (map_data) { 1313 m.map_cache.splice(map_data.index, 1); 1314 for (var i = m.map_cache.length - 1; i >= this.index; i--) { 1315 m.map_cache[i].index--; 1316 } 1317 }; 1318 1319 1320 /** 1321 * Test whether the browser supports VML. Credit: google. 1322 * http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser 1323 * 1324 * @return {bool} true if vml is supported, false if not 1325 */ 1326 1327 function hasVml() { 1328 var a = $('<div />').appendTo('body'); 1329 a.html('<v:shape id="vml_flag1" adj="1" />'); 1330 1331 var b = a[0].firstChild; 1332 b.style.behavior = "url(#default#VML)"; 1333 var has = b ? typeof b.adj === "object" : true; 1334 a.remove(); 1335 return has; 1336 } 1337 1338 /** 1339 * Return a reference to the IE namespaces object, if available, or an empty object otherwise 1340 * @return {obkect} The document.namespaces object. 1341 */ 1342 function namespaces() { 1343 return typeof(document.namespaces)==='object' ? 1344 document.namespaces : 1345 null; 1346 } 1347 1348 /** 1349 * Test for the presence of HTML5 Canvas support. This also checks to see if excanvas.js has been 1350 * loaded and is faking it; if so, we assume that canvas is not supported. 1351 * 1352 * @return {bool} true if HTML5 canvas support, false if not 1353 */ 1354 1355 function hasCanvas() { 1356 var d = namespaces(); 1357 // when g_vml_ is present, then we can be sure excanvas is active, meaning there's not a real canvas. 1358 1359 return d && d.g_vml_ ? 1360 false : 1361 $('<canvas />')[0].getContext ? 1362 true : 1363 false; 1364 } 1365 1366 /** 1367 * Merge new area data into existing area options on a MapData object. Used for rebinding. 1368 * 1369 * @param {[MapData]} map_data The MapData object 1370 * @param {[object[]]} areas areas array to merge 1371 */ 1372 1373 function merge_areas(map_data, areas) { 1374 var ar, index, 1375 map_areas = map_data.options.areas; 1376 1377 if (areas) { 1378 $.each(areas, function (i, e) { 1379 1380 // Issue #68 - ignore invalid data in areas array 1381 1382 if (!e || !e.key) { 1383 return; 1384 } 1385 1386 index = u.indexOfProp(map_areas, "key", e.key); 1387 1388 if (index >= 0) { 1389 $.extend(map_areas[index], e); 1390 } 1391 else { 1392 map_areas.push(e); 1393 } 1394 ar = map_data.getDataForKey(e.key); 1395 if (ar) { 1396 $.extend(ar.options, e); 1397 } 1398 }); 1399 } 1400 } 1401 function merge_options(map_data, options) { 1402 var temp_opts = u.updateProps({}, options); 1403 delete temp_opts.areas; 1404 1405 u.updateProps(map_data.options, temp_opts); 1406 1407 merge_areas(map_data, options.areas); 1408 // refresh the area_option template 1409 u.updateProps(map_data.area_options, map_data.options); 1410 } 1411 1412 // Most methods use the "Method" object which handles figuring out whether it's an image or area called and 1413 // parsing key parameters. The constructor wants: 1414 // this, the jQuery object 1415 // a function that is called when an image was passed (with a this context of the MapData) 1416 // a function that is called when an area was passed (with a this context of the AreaData) 1417 // options: first = true means only the first member of a jQuery object is handled 1418 // key = the key parameters passed 1419 // defaultReturn: a value to return other than the jQuery object (if its not chainable) 1420 // args: the arguments 1421 // Returns a comma-separated list of user-selected areas. "staticState" areas are not considered selected for the purposes of this method. 1422 1423 me.get = function (key) { 1424 var md = m.getMapData(this); 1425 if (!(md && md.complete)) { 1426 throw("Can't access data until binding complete."); 1427 } 1428 1429 return (new m.Method(this, 1430 function () { 1431 // map_data return 1432 return this.getSelected(); 1433 }, 1434 function () { 1435 return this.isSelected(); 1436 }, 1437 { name: 'get', 1438 args: arguments, 1439 key: key, 1440 first: true, 1441 allowAsync: true, 1442 defaultReturn: '' 1443 } 1444 )).go(); 1445 }; 1446 me.data = function (key) { 1447 return (new m.Method(this, 1448 null, 1449 function () { 1450 return this; 1451 }, 1452 { name: 'data', 1453 args: arguments, 1454 key: key 1455 } 1456 )).go(); 1457 }; 1458 1459 1460 // Set or return highlight state. 1461 // $(img).mapster('highlight') -- return highlighted area key, or null if none 1462 // $(area).mapster('highlight') -- highlight an area 1463 // $(img).mapster('highlight','area_key') -- highlight an area 1464 // $(img).mapster('highlight',false) -- remove highlight 1465 me.highlight = function (key) { 1466 return (new m.Method(this, 1467 function () { 1468 if (key === false) { 1469 this.ensureNoHighlight(); 1470 } else { 1471 var id = this.highlightId; 1472 return id >= 0 ? this.data[id].key : null; 1473 } 1474 }, 1475 function () { 1476 this.highlight(); 1477 }, 1478 { name: 'highlight', 1479 args: arguments, 1480 key: key, 1481 first: true 1482 } 1483 )).go(); 1484 }; 1485 // Return the primary keys for an area or group key. 1486 // $(area).mapster('key') 1487 // includes all keys (not just primary keys) 1488 // $(area).mapster('key',true) 1489 // $(img).mapster('key','group-key') 1490 1491 // $(img).mapster('key','group-key', true) 1492 me.keys = function(key,all) { 1493 var keyList=[], 1494 md = m.getMapData(this); 1495 1496 if (!(md && md.complete)) { 1497 throw("Can't access data until binding complete."); 1498 } 1499 1500 1501 function addUniqueKeys(ad) { 1502 var areas,keys=[]; 1503 if (!all) { 1504 keys.push(ad.key); 1505 } else { 1506 areas=ad.areas(); 1507 $.each(areas,function(i,e) { 1508 keys=keys.concat(e.keys); 1509 }); 1510 } 1511 $.each(keys,function(i,e) { 1512 if ($.inArray(e,keyList)<0) { 1513 keyList.push(e); 1514 } 1515 }); 1516 } 1517 1518 if (!(md && md.complete)) { 1519 return ''; 1520 } 1521 if (typeof key === 'string') { 1522 if (all) { 1523 addUniqueKeys(md.getDataForKey(key)); 1524 } else { 1525 keyList=[md.getKeysForGroup(key)]; 1526 } 1527 } else { 1528 all = key; 1529 this.each(function(i,e) { 1530 if (e.nodeName==='AREA') { 1531 addUniqueKeys(md.getDataForArea(e)); 1532 } 1533 }); 1534 } 1535 return keyList.join(','); 1536 1537 1538 }; 1539 me.select = function () { 1540 me.set.call(this, true); 1541 }; 1542 me.deselect = function () { 1543 me.set.call(this, false); 1544 }; 1545 1546 /** 1547 * Select or unselect areas. Areas can be identified by a single string key, a comma-separated list of keys, 1548 * or an array of strings. 1549 * 1550 * 1551 * @param {boolean} selected Determines whether areas are selected or deselected 1552 * @param {string|string[]} key A string, comma-separated string, or array of strings indicating 1553 * the areas to select or deselect 1554 * @param {object} options Rendering options to apply when selecting an area 1555 */ 1556 1557 me.set = function (selected, key, options) { 1558 var lastMap, map_data, opts=options, 1559 key_list, area_list; // array of unique areas passed 1560 1561 function setSelection(ar) { 1562 var newState = selected; 1563 if (ar) { 1564 switch (selected) { 1565 case true: 1566 ar.select(opts); break; 1567 case false: 1568 ar.deselect(true); break; 1569 default: 1570 newState = ar.toggle(opts); break; 1571 } 1572 return newState; 1573 } 1574 } 1575 function addArea(ar) { 1576 if (ar && $.inArray(ar, area_list) < 0) { 1577 area_list.push(ar); 1578 key_list+=(key_list===''?'':',')+ar.key; 1579 } 1580 } 1581 // Clean up after a group that applied to the same map 1582 function finishSetForMap(map_data) { 1583 $.each(area_list, function (i, el) { 1584 var newState = setSelection(el); 1585 if (map_data.options.boundList) { 1586 m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), newState); 1587 } 1588 }); 1589 if (!selected) { 1590 map_data.removeSelectionFinish(); 1591 } 1592 1593 } 1594 1595 this.filter('img,area').each(function (i,e) { 1596 var keys; 1597 map_data = m.getMapData(e); 1598 1599 if (map_data !== lastMap) { 1600 if (lastMap) { 1601 finishSetForMap(lastMap); 1602 } 1603 1604 area_list = []; 1605 key_list=''; 1606 } 1607 1608 if (map_data) { 1609 1610 keys = ''; 1611 if (e.nodeName.toUpperCase()==='IMG') { 1612 if (!m.queueCommand(map_data, $(e), 'set', [selected, key, opts])) { 1613 if (key instanceof Array) { 1614 if (key.length) { 1615 keys = key.join(","); 1616 } 1617 } 1618 else { 1619 keys = key; 1620 } 1621 1622 if (keys) { 1623 $.each(u.split(keys), function (i,key) { 1624 addArea(map_data.getDataForKey(key.toString())); 1625 lastMap = map_data; 1626 }); 1627 } 1628 } 1629 } else { 1630 opts=key; 1631 if (!m.queueCommand(map_data, $(e), 'set', [selected, opts])) { 1632 addArea(map_data.getDataForArea(e)); 1633 lastMap = map_data; 1634 } 1635 1636 } 1637 } 1638 }); 1639 1640 if (map_data) { 1641 finishSetForMap(map_data); 1642 } 1643 1644 1645 return this; 1646 }; 1647 me.unbind = function (preserveState) { 1648 return (new m.Method(this, 1649 function () { 1650 this.clearEvents(); 1651 this.clearMapData(preserveState); 1652 removeMap(this); 1653 }, 1654 null, 1655 { name: 'unbind', 1656 args: arguments 1657 } 1658 )).go(); 1659 }; 1660 1661 1662 // refresh options and update selection information. 1663 me.rebind = function (options) { 1664 return (new m.Method(this, 1665 function () { 1666 var me=this; 1667 1668 me.complete=false; 1669 me.configureOptions(options); 1670 me.bindImages().then(function() { 1671 me.buildDataset(true); 1672 me.complete=true; 1673 }); 1674 //this.redrawSelections(); 1675 }, 1676 null, 1677 { 1678 name: 'rebind', 1679 args: arguments 1680 } 1681 )).go(); 1682 }; 1683 // get options. nothing or false to get, or "true" to get effective options (versus passed options) 1684 me.get_options = function (key, effective) { 1685 var eff = u.isBool(key) ? key : effective; // allow 2nd parm as "effective" when no key 1686 return (new m.Method(this, 1687 function () { 1688 var opts = $.extend({}, this.options); 1689 if (eff) { 1690 opts.render_select = u.updateProps( 1691 {}, 1692 m.render_defaults, 1693 opts, 1694 opts.render_select); 1695 1696 opts.render_highlight = u.updateProps( 1697 {}, 1698 m.render_defaults, 1699 opts, 1700 opts.render_highlight); 1701 } 1702 return opts; 1703 }, 1704 function () { 1705 return eff ? this.effectiveOptions() : this.options; 1706 }, 1707 { 1708 name: 'get_options', 1709 args: arguments, 1710 first: true, 1711 allowAsync: true, 1712 key: key 1713 } 1714 )).go(); 1715 }; 1716 1717 // set options - pass an object with options to set, 1718 me.set_options = function (options) { 1719 return (new m.Method(this, 1720 function () { 1721 merge_options(this, options); 1722 }, 1723 null, 1724 { 1725 name: 'set_options', 1726 args: arguments 1727 } 1728 )).go(); 1729 }; 1730 me.unload = function () { 1731 var i; 1732 for (i = m.map_cache.length - 1; i >= 0; i--) { 1733 if (m.map_cache[i]) { 1734 me.unbind.call($(m.map_cache[i].image)); 1735 } 1736 } 1737 me.graphics = null; 1738 }; 1739 1740 me.snapshot = function () { 1741 return (new m.Method(this, 1742 function () { 1743 $.each(this.data, function (i, e) { 1744 e.selected = false; 1745 }); 1746 1747 this.base_canvas = this.graphics.createVisibleCanvas(this); 1748 $(this.image).before(this.base_canvas); 1749 }, 1750 null, 1751 { name: 'snapshot' } 1752 )).go(); 1753 }; 1754 1755 // do not queue this function 1756 1757 me.state = function () { 1758 var md, result = null; 1759 $(this).each(function (i,e) { 1760 if (e.nodeName === 'IMG') { 1761 md = m.getMapData(e); 1762 if (md) { 1763 result = md.state(); 1764 } 1765 return false; 1766 } 1767 }); 1768 return result; 1769 }; 1770 1771 me.bind = function (options) { 1772 1773 return this.each(function (i,e) { 1774 var img, map, usemap, md; 1775 1776 // save ref to this image even if we can't access it yet. commands will be queued 1777 img = $(e); 1778 1779 md = m.getMapData(e); 1780 1781 // if already bound completely, do a total rebind 1782 1783 if (md) { 1784 me.unbind.apply(img); 1785 if (!md.complete) { 1786 // will be queued 1787 img.bind(); 1788 return true; 1789 } 1790 md = null; 1791 } 1792 1793 // ensure it's a valid image 1794 // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr. 1795 // So use raw getAttribute instead. 1796 1797 usemap = this.getAttribute('usemap'); 1798 map = usemap && $('map[name="' + usemap.substr(1) + '"]'); 1799 if (!(img.is('img') && usemap && map.size() > 0)) { 1800 return true; 1801 } 1802 1803 // sorry - your image must have border:0, things are too unpredictable otherwise. 1804 img.css('border', 0); 1805 1806 if (!md) { 1807 md = new m.MapData(this, options); 1808 1809 md.index = addMap(md); 1810 md.map = map; 1811 md.bindImages().then(function() { 1812 md.initialize(); 1813 }); 1814 } 1815 }); 1816 }; 1817 1818 me.init = function (useCanvas) { 1819 var style, shapes; 1820 1821 // for testing/debugging, use of canvas can be forced by initializing 1822 // manually with "true" or "false". But generally we test for it. 1823 1824 m.hasCanvas = function() { 1825 if (!u.isBool(m.hasCanvas.value)) { 1826 m.hasCanvas.value = u.isBool(useCanvas) ? 1827 useCanvas : 1828 hasCanvas(); 1829 } 1830 return m.hasCanvas.value; 1831 }; 1832 1833 m.hasVml = function() { 1834 if (!u.isBool(m.hasVml.value)) { 1835 // initialize VML the first time we detect its presence. 1836 var d = namespaces(); 1837 1838 if (d && !d.v) { 1839 d.add("v", "urn:schemas-microsoft-com:vml"); 1840 style = document.createStyleSheet(); 1841 shapes = ['shape', 'rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group', 'textbox']; 1842 $.each(shapes, 1843 function (i, el) { 1844 style.addRule('v\\:' + el, "behavior: url(#default#VML); antialias:true"); 1845 }); 1846 } 1847 m.hasVml.value = hasVml(); 1848 } 1849 1850 return m.hasVml.value; 1851 }; 1852 1853 m.isTouch = !!document.documentElement.ontouchstart; 1854 1855 u.indexOf = Array.prototype.indexOf || u.indexOf; 1856 1857 $.extend(m.defaults, m.render_defaults,m.shared_defaults); 1858 $.extend(m.area_defaults, m.render_defaults,m.shared_defaults); 1859 1860 }; 1861 me.test = function (obj) { 1862 return eval(obj); 1863 }; 1864 return me; 1865 } ()); 1866 1867 $.mapster.impl.init(); 1868 1869 1870} (jQuery)); 1871/* graphics.js 1872 Graphics object handles all rendering. 1873*/ 1874(function ($) { 1875 var p, m=$.mapster, 1876 u=m.utils, 1877 canvasMethods, 1878 vmlMethods; 1879 1880 /** 1881 * Implemenation to add each area in an AreaData object to the canvas 1882 * @param {Graphics} graphics The target graphics object 1883 * @param {AreaData} areaData The AreaData object (a collection of area elements and metadata) 1884 * @param {object} options Rendering options to apply when rendering this group of areas 1885 */ 1886 function addShapeGroupImpl(graphics, areaData, options) { 1887 var me = graphics, 1888 md = me.map_data, 1889 isMask = options.isMask; 1890 1891 // first get area options. Then override fade for selecting, and finally merge in the 1892 // "select" effect options. 1893 1894 $.each(areaData.areas(), function (i,e) { 1895 options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); 1896 me.addShape(e, options); 1897 }); 1898 1899 // it's faster just to manipulate the passed options isMask property and restore it, than to 1900 // copy the object each time 1901 1902 options.isMask=isMask; 1903 1904 } 1905 1906 /** 1907 * Convert a hex value to decimal 1908 * @param {string} hex A hexadecimal toString 1909 * @return {int} Integer represenation of the hex string 1910 */ 1911 1912 function hex_to_decimal(hex) { 1913 return Math.max(0, Math.min(parseInt(hex, 16), 255)); 1914 } 1915 function css3color(color, opacity) { 1916 return 'rgba(' + hex_to_decimal(color.substr(0, 2)) + ',' 1917 + hex_to_decimal(color.substr(2, 2)) + ',' 1918 + hex_to_decimal(color.substr(4, 2)) + ',' + opacity + ')'; 1919 } 1920 /** 1921 * An object associated with a particular map_data instance to manage renderin. 1922 * @param {MapData} map_data The MapData object bound to this instance 1923 */ 1924 1925 m.Graphics = function (map_data) { 1926 //$(window).unload($.mapster.unload); 1927 // create graphics functions for canvas and vml browsers. usage: 1928 // 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 1929 // 3) call add_shape_to for each shape or mask, 4) call render() to finish 1930 1931 var me = this; 1932 me.active = false; 1933 me.canvas = null; 1934 me.width = 0; 1935 me.height = 0; 1936 me.shapes = []; 1937 me.masks = []; 1938 me.map_data = map_data; 1939 }; 1940 1941 p = m.Graphics.prototype= { 1942 constructor: m.Graphics, 1943 1944 /** 1945 * Initiate a graphics request for a canvas 1946 * @param {Element} canvas The canvas element that is the target of this operation 1947 * @param {string} [elementName] The name to assign to the element (VML only) 1948 */ 1949 1950 begin: function(canvas, elementName) { 1951 var c = $(canvas); 1952 1953 this.elementName = elementName; 1954 this.canvas = canvas; 1955 1956 this.width = c.width(); 1957 this.height = c.height(); 1958 this.shapes = []; 1959 this.masks = []; 1960 this.active = true; 1961 1962 }, 1963 1964 /** 1965 * Add an area to be rendered to this canvas. 1966 * @param {MapArea} mapArea The MapArea object to render 1967 * @param {object} options An object containing any rendering options that should override the 1968 * defaults for the area 1969 */ 1970 1971 addShape: function(mapArea, options) { 1972 var addto = options.isMask ? this.masks : this.shapes; 1973 addto.push({ mapArea: mapArea, options: options }); 1974 }, 1975 1976 /** 1977 * Create a canvas that is sized and styled for the MapData object 1978 * @param {MapData} mapData The MapData object that will receive this new canvas 1979 * @return {Element} A canvas element 1980 */ 1981 1982 createVisibleCanvas: function (mapData) { 1983 return $(this.createCanvasFor(mapData)) 1984 .addClass('mapster_el') 1985 .css(m.canvas_style)[0]; 1986 }, 1987 1988 /** 1989 * Add a group of shapes from an AreaData object to the canvas 1990 * 1991 * @param {AreaData} areaData An AreaData object (a set of area elements) 1992 * @param {string} mode The rendering mode, "select" or "highlight". This determines the target 1993 * canvas and which default options to use. 1994 * @param {striong} options Rendering options 1995 */ 1996 1997 addShapeGroup: function (areaData, mode,options) { 1998 // render includeKeys first - because they could be masks 1999 var me = this, 2000 list, name, canvas, 2001 map_data = this.map_data, 2002 opts = areaData.effectiveRenderOptions(mode); 2003 2004 if (options) { 2005 $.extend(opts,options); 2006 } 2007 2008 if (mode === 'select') { 2009 name = "static_" + areaData.areaId.toString(); 2010 canvas = map_data.base_canvas; 2011 } else { 2012 canvas = map_data.overlay_canvas; 2013 } 2014 2015 me.begin(canvas, name); 2016 2017 if (opts.includeKeys) { 2018 list = u.split(opts.includeKeys); 2019 $.each(list, function (i,e) { 2020 var areaData = map_data.getDataForKey(e.toString()); 2021 addShapeGroupImpl(me,areaData, areaData.effectiveRenderOptions(mode)); 2022 }); 2023 } 2024 2025 addShapeGroupImpl(me,areaData, opts); 2026 me.render(); 2027 if (opts.fade) { 2028 2029 // fading requires special handling for IE. We must access the fill elements directly. The fader also has to deal with 2030 // the "opacity" attribute (not css) 2031 2032 u.fader(m.hasCanvas() ? 2033 canvas : 2034 $(canvas).find('._fill').not('.mapster_mask'), 2035 0, 2036 m.hasCanvas() ? 2037 1 : 2038 opts.fillOpacity, 2039 opts.fadeDuration); 2040 2041 } 2042 2043 } 2044 2045 // These prototype methods are implementation dependent 2046 }; 2047 2048 function noop() {} 2049 2050 2051 // configure remaining prototype methods for ie or canvas-supporting browser 2052 2053 canvasMethods = { 2054 renderShape: function (context, mapArea, offset) { 2055 var i, 2056 c = mapArea.coords(null,offset); 2057 2058 switch (mapArea.shape) { 2059 case 'rect': 2060 context.rect(c[0], c[1], c[2] - c[0], c[3] - c[1]); 2061 break; 2062 case 'poly': 2063 context.moveTo(c[0], c[1]); 2064 2065 for (i = 2; i < mapArea.length; i += 2) { 2066 context.lineTo(c[i], c[i + 1]); 2067 } 2068 context.lineTo(c[0], c[1]); 2069 break; 2070 case 'circ': 2071 case 'circle': 2072 context.arc(c[0], c[1], c[2], 0, Math.PI * 2, false); 2073 break; 2074 } 2075 }, 2076 addAltImage: function (context, image, mapArea, options) { 2077 context.beginPath(); 2078 2079 this.renderShape(context, mapArea); 2080 context.closePath(); 2081 context.clip(); 2082 2083 context.globalAlpha = options.altImageOpacity || options.fillOpacity; 2084 2085 context.drawImage(image, 0, 0, mapArea.owner.scaleInfo.width, mapArea.owner.scaleInfo.height); 2086 }, 2087 render: function () { 2088 // firefox 6.0 context.save() seems to be broken. to work around, we have to draw the contents on one temp canvas, 2089 // the mask on another, and merge everything. ugh. fixed in 1.2.2. unfortunately this is a lot more code for masks, 2090 // but no other way around it that i can see. 2091 2092 var maskCanvas, maskContext, 2093 me = this, 2094 md = me.map_data, 2095 hasMasks = me.masks.length, 2096 shapeCanvas = me.createCanvasFor(md), 2097 shapeContext = shapeCanvas.getContext('2d'), 2098 context = me.canvas.getContext('2d'); 2099 2100 if (hasMasks) { 2101 maskCanvas = me.createCanvasFor(md); 2102 maskContext = maskCanvas.getContext('2d'); 2103 maskContext.clearRect(0, 0, maskCanvas.width, maskCanvas.height); 2104 2105 $.each(me.masks, function (i,e) { 2106 maskContext.save(); 2107 maskContext.beginPath(); 2108 me.renderShape(maskContext, e.mapArea); 2109 maskContext.closePath(); 2110 maskContext.clip(); 2111 maskContext.lineWidth = 0; 2112 maskContext.fillStyle = '#000'; 2113 maskContext.fill(); 2114 maskContext.restore(); 2115 }); 2116 2117 } 2118 2119 $.each(me.shapes, function (i,s) { 2120 shapeContext.save(); 2121 if (s.options.fill) { 2122 if (s.options.altImageId) { 2123 me.addAltImage(shapeContext, md.images[s.options.altImageId], s.mapArea, s.options); 2124 } else { 2125 shapeContext.beginPath(); 2126 me.renderShape(shapeContext, s.mapArea); 2127 shapeContext.closePath(); 2128 //shapeContext.clip(); 2129 shapeContext.fillStyle = css3color(s.options.fillColor, s.options.fillOpacity); 2130 shapeContext.fill(); 2131 } 2132 } 2133 shapeContext.restore(); 2134 }); 2135 2136 2137 // render strokes at end since masks get stroked too 2138 2139 $.each(me.shapes.concat(me.masks), function (i,s) { 2140 var offset = s.options.strokeWidth === 1 ? 0.5 : 0; 2141 // offset applies only when stroke width is 1 and stroke would render between pixels. 2142 2143 if (s.options.stroke) { 2144 shapeContext.save(); 2145 shapeContext.strokeStyle = css3color(s.options.strokeColor, s.options.strokeOpacity); 2146 shapeContext.lineWidth = s.options.strokeWidth; 2147 2148 shapeContext.beginPath(); 2149 2150 me.renderShape(shapeContext, s.mapArea, offset); 2151 shapeContext.closePath(); 2152 shapeContext.stroke(); 2153 shapeContext.restore(); 2154 } 2155 }); 2156 2157 if (hasMasks) { 2158 // render the new shapes against the mask 2159 2160 maskContext.globalCompositeOperation = "source-out"; 2161 maskContext.drawImage(shapeCanvas, 0, 0); 2162 2163 // flatten into the main canvas 2164 context.drawImage(maskCanvas, 0, 0); 2165 } else { 2166 context.drawImage(shapeCanvas, 0, 0); 2167 } 2168 2169 me.active = false; 2170 return me.canvas; 2171 }, 2172 2173 // create a canvas mimicing dimensions of an existing element 2174 createCanvasFor: function (md) { 2175 return $('<canvas width="' + md.scaleInfo.width + '" height="' +md.scaleInfo.height + '"></canvas>')[0]; 2176 }, 2177 clearHighlight: function () { 2178 var c = this.map_data.overlay_canvas; 2179 c.getContext('2d').clearRect(0, 0, c.width, c.height); 2180 }, 2181 // 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. 2182 refreshSelections: function () { 2183 var canvas_temp, map_data = this.map_data; 2184 // draw new base canvas, then swap with the old one to avoid flickering 2185 canvas_temp = map_data.base_canvas; 2186 2187 map_data.base_canvas = this.createVisibleCanvas(map_data); 2188 $(map_data.base_canvas).hide(); 2189 $(canvas_temp).before(map_data.base_canvas); 2190 2191 map_data.redrawSelections(); 2192 2193 $(map_data.base_canvas).show(); 2194 $(canvas_temp).remove(); 2195 } 2196 }; 2197 2198 vmlMethods = { 2199 2200 renderShape: function (mapArea, options, cssclass) { 2201 var me = this, fill,stroke, e, t_fill, el_name, el_class, template, c = mapArea.coords(); 2202 el_name = me.elementName ? 'name="' + me.elementName + '" ' : ''; 2203 el_class = cssclass ? 'class="' + cssclass + '" ' : ''; 2204 2205 t_fill = '<v:fill color="#' + options.fillColor + '" class="_fill" opacity="' + 2206 (options.fill ? 2207 options.fillOpacity : 2208 0) + 2209 '" /><v:stroke class="_fill" opacity="' + 2210 options.strokeOpacity + '"/>'; 2211 2212 2213 stroke = options.stroke ? 2214 ' strokeweight=' + options.strokeWidth + ' stroked="t" strokecolor="#' + 2215 options.strokeColor + '"' : 2216 ' stroked="f"'; 2217 2218 fill = options.fill ? 2219 ' filled="t"' : 2220 ' filled="f"'; 2221 2222 switch (mapArea.shape) { 2223 case 'rect': 2224 template = '<v:rect ' + el_class + el_name + fill + stroke + 2225 ' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:' + 2226 c[0] + 'px;top:' + c[1] + 'px;width:' + (c[2] - c[0]) + 2227 'px;height:' + (c[3] - c[1]) + 'px;">' + t_fill + '</v:rect>'; 2228 break; 2229 case 'poly': 2230 template = '<v:shape ' + el_class + el_name + fill + stroke + ' coordorigin="0,0" coordsize="' + me.width + ',' + me.height 2231 + '" path="m ' + c[0] + ',' + c[1] + ' l ' + c.slice(2).join(',') 2232 + ' 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>'; 2233 break; 2234 case 'circ': 2235 case 'circle': 2236 template = '<v:oval ' + el_class + el_name + fill + stroke 2237 + ' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:' + (c[0] - c[2]) + 'px;top:' + (c[1] - c[2]) 2238 + 'px;width:' + (c[2] * 2) + 'px;height:' + (c[2] * 2) + 'px;">' + t_fill + '</v:oval>'; 2239 break; 2240 } 2241 e = $(template); 2242 $(me.canvas).append(e); 2243 2244 return e; 2245 }, 2246 render: function () { 2247 var opts, me = this; 2248 2249 $.each(this.shapes, function (i,e) { 2250 me.renderShape(e.mapArea, e.options); 2251 }); 2252 2253 if (this.masks.length) { 2254 $.each(this.masks, function (i,e) { 2255 opts = u.updateProps({}, 2256 e.options, { 2257 fillOpacity: 1, 2258 fillColor: e.options.fillColorMask 2259 }); 2260 me.renderShape(e.mapArea, opts, 'mapster_mask'); 2261 }); 2262 } 2263 2264 this.active = false; 2265 return this.canvas; 2266 }, 2267 2268 createCanvasFor: function (md) { 2269 var w = md.scaleInfo.width, 2270 h = md.scaleInfo.height; 2271 return $('<var width="' + w + '" height="' + h 2272 + '" style="zoom:1;overflow:hidden;display:block;width:' 2273 + w + 'px;height:' + h + 'px;"></var>')[0]; 2274 }, 2275 2276 clearHighlight: function () { 2277 $(this.map_data.overlay_canvas).children().remove(); 2278 }, 2279 // remove single or all selections 2280 removeSelections: function (area_id) { 2281 if (area_id >= 0) { 2282 $(this.map_data.base_canvas).find('[name="static_' + area_id.toString() + '"]').remove(); 2283 } 2284 else { 2285 $(this.map_data.base_canvas).children().remove(); 2286 } 2287 } 2288 2289 }; 2290 2291 // for all methods with two implemenatations, add a function that will automatically replace itself with the correct 2292 // method on first invocation 2293 2294 $.each(['renderShape', 2295 'addAltImage', 2296 'render', 2297 'createCanvasFor', 2298 'clearHighlight', 2299 'removeSelections', 2300 'refreshSelections'], 2301 function(i,e) { 2302 p[e]=(function(method) { 2303 return function() { 2304 p[method] = (m.hasCanvas() ? 2305 canvasMethods[method] : 2306 vmlMethods[method]) || noop; 2307 2308 return p[method].apply(this,arguments); 2309 }; 2310 }(e)); 2311 }); 2312 2313 2314} (jQuery)); 2315/* mapimage.js 2316 the MapImage object, repesents an instance of a single bound imagemap 2317*/ 2318 2319(function ($) { 2320 2321 var m = $.mapster, 2322 u = m.utils, 2323 ap=[]; 2324 /** 2325 * An object encapsulating all the images used by a MapData. 2326 */ 2327 2328 m.MapImages = function(owner) { 2329 this.owner = owner; 2330 this.clear(); 2331 }; 2332 2333 2334 m.MapImages.prototype = { 2335 constructor: m.MapImages, 2336 2337 /* interface to make this array-like */ 2338 2339 slice: function() { 2340 return ap.slice.apply(this,arguments); 2341 }, 2342 splice: function() { 2343 ap.slice.apply(this.status,arguments); 2344 var result= ap.slice.apply(this,arguments); 2345 return result; 2346 }, 2347 2348 /** 2349 * a boolean value indicates whether all images are done loading 2350 * @return {bool} true when all are done 2351 */ 2352 complete: function() { 2353 return $.inArray(false, this.status) < 0; 2354 }, 2355 2356 /** 2357 * Save an image in the images array and return its index 2358 * @param {Image} image An Image object 2359 * @return {int} the index of the image 2360 */ 2361 2362 _add: function(image) { 2363 var index = ap.push.call(this,image)-1; 2364 this.status[index] = false; 2365 return index; 2366 }, 2367 2368 /** 2369 * Return the index of an Image within the images array 2370 * @param {Image} img An Image 2371 * @return {int} the index within the array, or -1 if it was not found 2372 */ 2373 2374 indexOf: function(image) { 2375 return u.indexOf(this,image); 2376 }, 2377 2378 /** 2379 * Clear this object and reset it to its initial state after binding. 2380 */ 2381 2382 clear: function() { 2383 var me=this; 2384 2385 if (me.ids && me.ids.length>0) { 2386 $.each(me.ids,function(i,e) { 2387 delete me[e]; 2388 }); 2389 } 2390 2391 /** 2392 * A list of the cross-reference IDs bound to this object 2393 * @type {string[]} 2394 */ 2395 2396 me.ids=[]; 2397 2398 /** 2399 * Length property for array-like behavior, set to zero when initializing. Array prototype 2400 * methods will update it after that. 2401 * 2402 * @type {int} 2403 */ 2404 2405 me.length=0; 2406 2407 /** 2408 * the loaded status of the corresponding image 2409 * @type {boolean[]} 2410 */ 2411 2412 me.status=[]; 2413 2414 2415 // actually erase the images 2416 2417 me.splice(0); 2418 2419 }, 2420 2421 /** 2422 * Bind an image to the map and add it to the queue to be loaded; return an ID that 2423 * can be used to reference the 2424 * 2425 * @param {Image|string} image An Image object or a URL to an image 2426 * @param {string} [id] An id to refer to this image 2427 * @returns {int} an ID referencing the index of the image object in 2428 * map_data.images 2429 */ 2430 2431 add: function(image,id) { 2432 var index,src,me = this; 2433 2434 if (!image) { return; } 2435 2436 if (typeof image === 'string') { 2437 src = image; 2438 image = me[src]; 2439 if (typeof image==='object') { 2440 return me.indexOf(image); 2441 } 2442 2443 image = $('<img />') 2444 .addClass('mapster_el') 2445 .hide(); 2446 2447 index=me._add(image[0]); 2448 2449 image 2450 .bind('load',function(e) { 2451 me.imageLoaded.call(me,e); 2452 }) 2453 .bind('error',function(e) { 2454 me.imageLoadError.call(me,e); 2455 }); 2456 2457 image.attr('src', src); 2458 } else { 2459 2460 // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src 2461 2462 index=me._add($(image)[0]); 2463 } 2464 if (id) { 2465 if (this[id]) { 2466 throw(id+" is already used or is not available as an altImage alias."); 2467 } 2468 me.ids.push(id); 2469 me[id]=me[index]; 2470 } 2471 return index; 2472 }, 2473 2474 /** 2475 * Bind the images in this object, 2476 * @param {boolean} retry when true, indicates that the function is calling itself after failure 2477 * @return {Promise} a promise that resolves when the images have finished loading 2478 */ 2479 2480 bind: function(retry) { 2481 var me = this, 2482 promise, 2483 triesLeft = me.owner.options.configTimeout / 200, 2484 2485 /* A recursive function to continue checking that the images have been 2486 loaded until a timeout has elapsed */ 2487 2488 check=function() { 2489 var i; 2490 2491 // refresh status of images 2492 2493 i=me.length; 2494 2495 while (i-->0) { 2496 if (!me.isLoaded(i)) { 2497 break; 2498 } 2499 } 2500 2501 // check to see if every image has already been loaded 2502 2503 if (me.complete()) { 2504 me.resolve(); 2505 } else { 2506 // to account for failure of onLoad to fire in rare situations 2507 if (triesLeft-- > 0) { 2508 me.imgTimeout=window.setTimeout(function() { 2509 check.call(me,true); 2510 }, 50); 2511 } else { 2512 me.imageLoadError.call(me); 2513 } 2514 } 2515 2516 }; 2517 2518 promise = me.deferred=u.defer(); 2519 2520 check(); 2521 return promise; 2522 }, 2523 2524 resolve: function() { 2525 var me=this, 2526 resolver=me.deferred; 2527 2528 if (resolver) { 2529 // Make a copy of the resolver before calling & removing it to ensure 2530 // it is not called twice 2531 me.deferred=null; 2532 resolver.resolve(); 2533 } 2534 }, 2535 2536 /** 2537 * Event handler for image onload 2538 * @param {object} e jQuery event data 2539 */ 2540 2541 imageLoaded: function(e) { 2542 var me=this, 2543 index = me.indexOf(e.target); 2544 2545 if (index>=0) { 2546 2547 me.status[index] = true; 2548 if ($.inArray(false, me.status) < 0) { 2549 me.resolve(); 2550 } 2551 } 2552 }, 2553 2554 /** 2555 * Event handler for onload error 2556 * @param {object} e jQuery event data 2557 */ 2558 2559 imageLoadError: function(e) { 2560 clearTimeout(this.imgTimeout); 2561 this.triesLeft=0; 2562 var err = e ? 'The image ' + e.target.src + ' failed to load.' : 2563 'The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.'; 2564 throw err; 2565 }, 2566 /** 2567 * Test if the image at specificed index has finished loading 2568 * @param {int} index The image index 2569 * @return {boolean} true if loaded, false if not 2570 */ 2571 2572 isLoaded: function(index) { 2573 var img, 2574 me=this, 2575 status=me.status; 2576 2577 if (status[index]) { return true; } 2578 img = me[index]; 2579 2580 if (typeof img.complete !== 'undefined') { 2581 status[index]=img.complete; 2582 } else { 2583 status[index]=!!u.imgWidth(img); 2584 } 2585 // if complete passes, the image is loaded, but may STILL not be available because of stuff like adblock. 2586 // make sure it is. 2587 2588 return status[index]; 2589 } 2590 }; 2591 } (jQuery)); 2592/* mapdata.js 2593 the MapData object, repesents an instance of a single bound imagemap 2594*/ 2595 2596 2597(function ($) { 2598 2599 var m = $.mapster, 2600 u = m.utils; 2601 2602 /** 2603 * Set default values for MapData object properties 2604 * @param {MapData} me The MapData object 2605 */ 2606 2607 function initializeDefaults(me) { 2608 $.extend(me,{ 2609 complete: false, // (bool) when configuration is complete 2610 map: null, // ($) the image map 2611 base_canvas: null, // (canvas|var) where selections are rendered 2612 overlay_canvas: null, // (canvas|var) where highlights are rendered 2613 commands: [], // {} commands that were run before configuration was completed (b/c images weren't loaded) 2614 data: [], // MapData[] area groups 2615 mapAreas: [], // MapArea[] list. AreaData entities contain refs to this array, so options are stored with each. 2616 _xref: {}, // (int) xref of mapKeys to data[] 2617 highlightId: -1, // (int) the currently highlighted element. 2618 currentAreaId: -1, 2619 _tooltip_events: [], // {} info on events we bound to a tooltip container, so we can properly unbind them 2620 scaleInfo: null, // {} info about the image size, scaling, defaults 2621 index: -1, // index of this in map_cache - so we have an ID to use for wraper div 2622 activeAreaEvent: null 2623 }); 2624 } 2625 2626 /** 2627 * Return an array of all image-containing options from an options object; 2628 * that is, containers that may have an "altImage" property 2629 * 2630 * @param {object} obj An options object 2631 * @return {object[]} An array of objects 2632 */ 2633 function getOptionImages(obj) { 2634 return [obj, obj.render_highlight, obj.render_select]; 2635 } 2636 2637 /** 2638 * Parse all the altImage references, adding them to the library so they can be preloaded 2639 * and aliased. 2640 * 2641 * @param {MapData} me The MapData object on which to operate 2642 */ 2643 function configureAltImages(me) 2644 { 2645 var opts = me.options, 2646 mi = me.images; 2647 2648 // add alt images 2649 2650 if (m.hasCanvas()) { 2651 // map altImage library first 2652 2653 $.each(opts.altImages || {}, function(i,e) { 2654 mi.add(e,i); 2655 }); 2656 2657 // now find everything else 2658 2659 $.each([opts].concat(opts.areas),function(i,e) { 2660 $.each(getOptionImages(e),function(i2,e2) { 2661 if (e2 && e2.altImage) { 2662 e2.altImageId=mi.add(e2.altImage); 2663 } 2664 }); 2665 }); 2666 } 2667 2668 // set area_options 2669 me.area_options = u.updateProps({}, // default options for any MapArea 2670 m.area_defaults, 2671 opts); 2672 } 2673 2674 /** 2675 * Queue a mouse move action based on current delay settings 2676 * (helper for mouseover/mouseout handlers) 2677 * 2678 * @param {MapData} me The MapData context 2679 * @param {number} delay The number of milliseconds to delay the action 2680 * @param {AreaData} area AreaData affected 2681 * @param {Deferred} deferred A deferred object to return (instead of a new one) 2682 * @return {Promise} A promise that resolves when the action is completed 2683 */ 2684 function queueMouseEvent(me,delay,area, deferred) { 2685 2686 deferred = deferred || u.when.defer(); 2687 2688 function cbFinal(areaId) { 2689 if (me.currentAreaId!==areaId && me.highlightId>=0) { 2690 deferred.resolve(); 2691 } 2692 } 2693 if (me.activeAreaEvent) { 2694 window.clearTimeout(me.activeAreaEvent); 2695 me.activeAreaEvent=0; 2696 } 2697 if (delay<0) { 2698 deferred.reject(); 2699 } else { 2700 if (area.owner.currentAction || delay) { 2701 me.activeAreaEvent = window.setTimeout((function() { 2702 return function() { 2703 queueMouseEvent(me,0,area,deferred); 2704 }; 2705 }(area)), 2706 delay || 100); 2707 } else { 2708 cbFinal(area.areaId); 2709 } 2710 } 2711 return deferred; 2712 } 2713 2714 /** 2715 * Mousedown event. This is captured only to prevent browser from drawing an outline around an 2716 * area when it's clicked. 2717 * 2718 * @param {EventData} e jQuery event data 2719 */ 2720 2721 function mousedown(e) { 2722 if (!m.hasCanvas()) { 2723 this.blur(); 2724 } 2725 e.preventDefault(); 2726 } 2727 2728 /** 2729 * Mouseover event. Handle highlight rendering and client callback on mouseover 2730 * 2731 * @param {MapData} me The MapData context 2732 * @param {EventData} e jQuery event data 2733 * @return {[type]} [description] 2734 */ 2735 2736 function mouseover(me,e) { 2737 var arData = me.getAllDataForArea(this), 2738 ar=arData.length ? arData[0] : null; 2739 2740 // mouseover events are ignored entirely while resizing, though we do care about mouseout events 2741 // and must queue the action to keep things clean. 2742 2743 if (!ar || ar.isNotRendered() || ar.owner.currentAction) { 2744 return; 2745 } 2746 2747 if (me.currentAreaId === ar.areaId) { 2748 return; 2749 } 2750 if (me.highlightId !== ar.areaId) { 2751 me.clearEffects(); 2752 2753 ar.highlight(); 2754 2755 if (me.options.showToolTip) { 2756 $.each(arData,function(i,e) { 2757 if (e.effectiveOptions().toolTip) { 2758 e.showToolTip(); 2759 } 2760 }); 2761 } 2762 } 2763 2764 me.currentAreaId = ar.areaId; 2765 2766 if ($.isFunction(me.options.onMouseover)) { 2767 me.options.onMouseover.call(this, 2768 { 2769 e: e, 2770 options:ar.effectiveOptions(), 2771 key: ar.key, 2772 selected: ar.isSelected() 2773 }); 2774 } 2775 } 2776 2777 /** 2778 * Mouseout event. 2779 * 2780 * @param {MapData} me The MapData context 2781 * @param {EventData} e jQuery event data 2782 * @return {[type]} [description] 2783 */ 2784 2785 function mouseout(me,e) { 2786 var newArea, 2787 ar = me.getDataForArea(this), 2788 opts = me.options; 2789 2790 2791 if (me.currentAreaId<0 || !ar) { 2792 return; 2793 } 2794 2795 newArea=me.getDataForArea(e.relatedTarget); 2796 2797 if (newArea === ar) { 2798 return; 2799 } 2800 2801 me.currentAreaId = -1; 2802 ar.area=null; 2803 2804 queueMouseEvent(me,opts.mouseoutDelay,ar) 2805 .then(me.clearEffects); 2806 2807 if ($.isFunction(opts.onMouseout)) { 2808 opts.onMouseout.call(this, 2809 { 2810 e: e, 2811 options: opts, 2812 key: ar.key, 2813 selected: ar.isSelected() 2814 }); 2815 } 2816 2817 } 2818 2819 /** 2820 * Clear any active tooltip or highlight 2821 * 2822 * @param {MapData} me The MapData context 2823 * @param {EventData} e jQuery event data 2824 * @return {[type]} [description] 2825 */ 2826 2827 function clearEffects(me) { 2828 var opts = me.options; 2829 2830 me.ensureNoHighlight(); 2831 2832 if (opts.toolTipClose 2833 && $.inArray('area-mouseout', opts.toolTipClose) >= 0 2834 && me.activeToolTip) 2835 { 2836 me.clearToolTip(); 2837 } 2838 } 2839 2840 /** 2841 * Mouse click event handler 2842 * 2843 * @param {MapData} me The MapData context 2844 * @param {EventData} e jQuery event data 2845 * @return {[type]} [description] 2846 */ 2847 2848 function click(me,e) { 2849 var selected, list, list_target, newSelectionState, canChangeState, cbResult, 2850 that = this, 2851 ar = me.getDataForArea(this), 2852 opts = me.options; 2853 2854 function clickArea(ar) { 2855 var areaOpts,target; 2856 canChangeState = (ar.isSelectable() && 2857 (ar.isDeselectable() || !ar.isSelected())); 2858 2859 if (canChangeState) { 2860 newSelectionState = !ar.isSelected(); 2861 } else { 2862 newSelectionState = ar.isSelected(); 2863 } 2864 2865 list_target = m.getBoundList(opts, ar.key); 2866 2867 if ($.isFunction(opts.onClick)) 2868 { 2869 cbResult= opts.onClick.call(that, 2870 { 2871 e: e, 2872 listTarget: list_target, 2873 key: ar.key, 2874 selected: newSelectionState 2875 }); 2876 2877 if (u.isBool(cbResult)) { 2878 if (!cbResult) { 2879 return false; 2880 } 2881 target = $(ar.area).attr('href'); 2882 if (target!=='#') { 2883 window.location.href=target; 2884 return false; 2885 } 2886 } 2887 } 2888 2889 if (canChangeState) { 2890 selected = ar.toggle(); 2891 } 2892 2893 if (opts.boundList && opts.boundList.length > 0) { 2894 m.setBoundListProperties(opts, list_target, ar.isSelected()); 2895 } 2896 2897 areaOpts = ar.effectiveOptions(); 2898 if (areaOpts.includeKeys) { 2899 list = u.split(areaOpts.includeKeys); 2900 $.each(list, function (i, e) { 2901 var ar = me.getDataForKey(e.toString()); 2902 if (!ar.options.isMask) { 2903 clickArea(ar); 2904 } 2905 }); 2906 } 2907 } 2908 2909 mousedown.call(this,e); 2910 2911 if (opts.clickNavigate && ar.href) { 2912 window.location.href=ar.href; 2913 return; 2914 } 2915 2916 if (ar && !ar.owner.currentAction) { 2917 opts = me.options; 2918 clickArea(ar); 2919 } 2920 } 2921 2922 /** 2923 * Prototype for a MapData object, representing an ImageMapster bound object 2924 * @param {Element} image an IMG element 2925 * @param {object} options ImageMapster binding options 2926 */ 2927 m.MapData = function (image, options) 2928 { 2929 var me = this; 2930 2931 // (Image) main map image 2932 2933 me.image = image; 2934 2935 me.images = new m.MapImages(me); 2936 me.graphics = new m.Graphics(me); 2937 2938 // save the initial style of the image for unbinding. This is problematic, chrome 2939 // duplicates styles when assigning, and cssText is apparently not universally supported. 2940 // Need to do something more robust to make unbinding work universally. 2941 2942 me.imgCssText = image.style.cssText || null; 2943 2944 initializeDefaults(me); 2945 2946 me.configureOptions(options); 2947 2948 // create context-bound event handlers from our private functions 2949 2950 me.mouseover = function(e) { mouseover.call(this,me,e); }; 2951 me.mouseout = function(e) { mouseout.call(this,me,e); }; 2952 me.click = function(e) { click.call(this,me,e); }; 2953 me.clearEffects = function(e) { clearEffects.call(this,me,e); }; 2954 }; 2955 2956 m.MapData.prototype = { 2957 constructor: m.MapData, 2958 2959 /** 2960 * Set target.options from defaults + options 2961 * @param {[type]} target The target 2962 * @param {[type]} options The options to merge 2963 */ 2964 2965 configureOptions: function(options) { 2966 this.options= u.updateProps({}, m.defaults, options); 2967 }, 2968 2969 /** 2970 * Ensure all images are loaded 2971 * @return {Promise} A promise that resolves when the images have finished loading (or fail) 2972 */ 2973 2974 bindImages: function() { 2975 var me=this, 2976 mi = me.images; 2977 2978 // reset the images if this is a rebind 2979 2980 if (mi.length>2) { 2981 mi.splice(2); 2982 } else if (mi.length===0) { 2983 2984 // add the actual main image 2985 mi.add(me.image); 2986 // will create a duplicate of the main image, we need this to get raw size info 2987 mi.add(me.image.src); 2988 } 2989 2990 configureAltImages(me); 2991 2992 return me.images.bind(); 2993 }, 2994 2995 /** 2996 * Test whether an async action is currently in progress 2997 * @return {Boolean} true or false indicating state 2998 */ 2999 3000 isActive: function() { 3001 return !this.complete || this.currentAction; 3002 }, 3003 3004 /** 3005 * Return an object indicating the various states. This isn't really used by 3006 * production code. 3007 * 3008 * @return {object} An object with properties for various states 3009 */ 3010 3011 state: function () { 3012 return { 3013 complete: this.complete, 3014 resizing: this.currentAction==='resizing', 3015 zoomed: this.zoomed, 3016 zoomedArea: this.zoomedArea, 3017 scaleInfo: this.scaleInfo 3018 }; 3019 }, 3020 3021 /** 3022 * Get a unique ID for the wrapper of this imagemapster 3023 * @return {string} A string that is unique to this image 3024 */ 3025 3026 wrapId: function () { 3027 return 'mapster_wrap_' + this.index; 3028 }, 3029 _idFromKey: function (key) { 3030 return typeof key === "string" && this._xref.hasOwnProperty(key) ? 3031 this._xref[key] : -1; 3032 }, 3033 3034 /** 3035 * Return a comma-separated string of all selected keys 3036 * @return {string} CSV of all keys that are currently selected 3037 */ 3038 3039 getSelected: function () { 3040 var result = ''; 3041 $.each(this.data, function (i,e) { 3042 if (e.isSelected()) { 3043 result += (result ? ',' : '') + this.key; 3044 } 3045 }); 3046 return result; 3047 }, 3048 3049 /** 3050 * Get an array of MapAreas associated with a specific AREA based on the keys for that area 3051 * @param {Element} area An HTML AREA 3052 * @param {number} atMost A number limiting the number of areas to be returned (typically 1 or 0 for no limit) 3053 * @return {MapArea[]} Array of MapArea objects 3054 */ 3055 3056 getAllDataForArea:function (area,atMost) { 3057 var i,ar, result, 3058 me=this, 3059 key = $(area).filter('area').attr(me.options.mapKey); 3060 3061 if (key) { 3062 result=[]; 3063 key = u.split(key); 3064 3065 for (i=0;i<(atMost || key.length);i++) { 3066 ar = me.data[me._idFromKey(key[i])]; 3067 ar.area=area.length ? area[0]:area; 3068 // set the actual area moused over/selected 3069 // TODO: this is a brittle model for capturing which specific area - if this method was not used, 3070 // ar.area could have old data. fix this. 3071 result.push(ar); 3072 } 3073 } 3074 3075 return result; 3076 }, 3077 getDataForArea: function(area) { 3078 var ar=this.getAllDataForArea(area,1); 3079 return ar ? ar[0] || null : null; 3080 }, 3081 getDataForKey: function (key) { 3082 return this.data[this._idFromKey(key)]; 3083 }, 3084 3085 /** 3086 * Get the primary keys associated with an area group. 3087 * If this is a primary key, it will be returned. 3088 * 3089 * @param {string key An area key 3090 * @return {string} A CSV of area keys 3091 */ 3092 3093 getKeysForGroup: function(key) { 3094 var ar=this.getDataForKey(key); 3095 3096 return !ar ? '': 3097 ar.isPrimary ? 3098 ar.key : 3099 this.getPrimaryKeysForMapAreas(ar.areas()).join(','); 3100 }, 3101 3102 /** 3103 * given an array of MapArea object, return an array of its unique primary keys 3104 * @param {MapArea[]} areas The areas to analyze 3105 * @return {string[]} An array of unique primary keys 3106 */ 3107 3108 getPrimaryKeysForMapAreas: function(areas) 3109 { 3110 var keys=[]; 3111 $.each(areas,function(i,e) { 3112 if ($.inArray(e.keys[0],keys)<0) { 3113 keys.push(e.keys[0]); 3114 } 3115 }); 3116 return keys; 3117 }, 3118 getData: function (obj) { 3119 if (typeof obj === 'string') { 3120 return this.getDataForKey(obj); 3121 } else if (obj && obj.mapster || u.isElement(obj)) { 3122 return this.getDataForArea(obj); 3123 } else { 3124 return null; 3125 } 3126 }, 3127 // remove highlight if present, raise event 3128 ensureNoHighlight: function () { 3129 var ar; 3130 if (this.highlightId >= 0) { 3131 this.graphics.clearHighlight(); 3132 ar = this.data[this.highlightId]; 3133 ar.changeState('highlight', false); 3134 this.setHighlightId(-1); 3135 } 3136 }, 3137 setHighlightId: function(id) { 3138 this.highlightId = id; 3139 }, 3140 3141 /** 3142 * Clear all active selections on this map 3143 */ 3144 3145 clearSelections: function () { 3146 $.each(this.data, function (i,e) { 3147 if (e.selected) { 3148 e.deselect(true); 3149 } 3150 }); 3151 this.removeSelectionFinish(); 3152 3153 }, 3154 3155 /** 3156 * Set area options from an array of option data. 3157 * 3158 * @param {object[]} areas An array of objects containing area-specific options 3159 */ 3160 3161 setAreaOptions: function (areas) { 3162 var i, area_options, ar; 3163 areas = areas || []; 3164 3165 // refer by: map_data.options[map_data.data[x].area_option_id] 3166 3167 for (i = areas.length - 1; i >= 0; i--) { 3168 area_options = areas[i]; 3169 if (area_options) { 3170 ar = this.getDataForKey(area_options.key); 3171 if (ar) { 3172 u.updateProps(ar.options, area_options); 3173 3174 // TODO: will not deselect areas that were previously selected, so this only works 3175 // for an initial bind. 3176 3177 if (u.isBool(area_options.selected)) { 3178 ar.selected = area_options.selected; 3179 } 3180 } 3181 } 3182 } 3183 }, 3184 // keys: a comma-separated list 3185 drawSelections: function (keys) { 3186 var i, key_arr = u.asArray(keys); 3187 3188 for (i = key_arr.length - 1; i >= 0; i--) { 3189 this.data[key_arr[i]].drawSelection(); 3190 } 3191 }, 3192 redrawSelections: function () { 3193 $.each(this.data, function (i, e) { 3194 if (e.isSelectedOrStatic()) { 3195 e.drawSelection(); 3196 } 3197 }); 3198 3199 }, 3200 ///called when images are done loading 3201 initialize: function () { 3202 var imgCopy, base_canvas, overlay_canvas, wrap, parentId, css, i,size, 3203 img,sort_func, sorted_list, scale, 3204 me = this, 3205 opts = me.options; 3206 3207 if (me.complete) { 3208 return; 3209 } 3210 3211 img = $(me.image); 3212 3213 parentId = img.parent().attr('id'); 3214 3215 // create a div wrapper only if there's not already a wrapper, otherwise, own it 3216 3217 if (parentId && parentId.length >= 12 && parentId.substring(0, 12) === "mapster_wrap") { 3218 wrap = img.parent(); 3219 wrap.attr('id', me.wrapId()); 3220 } else { 3221 wrap = $('<div id="' + me.wrapId() + '"></div>'); 3222 3223 if (opts.wrapClass) { 3224 if (opts.wrapClass === true) { 3225 wrap.addClass(img[0].className); 3226 } 3227 else { 3228 wrap.addClass(opts.wrapClass); 3229 } 3230 } 3231 } 3232 me.wrapper = wrap; 3233 3234 // 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 3235 // width & height. This is needed to scale the imagemap if not being shown at its native size. It is also needed purely 3236 // to finish binding in case the original image was not visible. It can be impossible in some browsers to obtain the 3237 // native size of a hidden image. 3238 3239 me.scaleInfo = scale = u.scaleMap(me.images[0],me.images[1], opts.scaleMap); 3240 3241 me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); 3242 me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); 3243 3244 // Now we got what we needed from the copy -clone from the original image again to make sure any other attributes are copied 3245 imgCopy = $(me.images[1]) 3246 .addClass('mapster_el '+ me.images[0].className) 3247 .attr({id:null, usemap: null}); 3248 3249 size=u.size(me.images[0]); 3250 3251 if (size.complete) { 3252 imgCopy.css({ 3253 width: size.width, 3254 height: size.height 3255 }); 3256 } 3257 3258 me.buildDataset(); 3259 3260 // now that we have processed all the areas, set css for wrapper, scale map if needed 3261 3262 css = { 3263 display: 'block', 3264 position: 'relative', 3265 padding: 0, 3266 width: scale.width, 3267 height: scale.height 3268 }; 3269 3270 if (opts.wrapCss) { 3271 $.extend(css, opts.wrapCss); 3272 } 3273 // if we were rebinding with an existing wrapper, the image will aready be in it 3274 if (img.parent()[0] !== me.wrapper[0]) { 3275 3276 img.before(me.wrapper); 3277 } 3278 3279 wrap.css(css); 3280 3281 // move all generated images into the wrapper for easy removal later 3282 3283 $(me.images.slice(2)).hide(); 3284 for (i = 1; i < me.images.length; i++) { 3285 wrap.append(me.images[i]); 3286 } 3287 3288 //me.images[1].style.cssText = me.image.style.cssText; 3289 3290 wrap.append(base_canvas) 3291 .append(overlay_canvas) 3292 .append(img.css(m.canvas_style)); 3293 3294 // images[0] is the original image with map, images[1] is the copy/background that is visible 3295 3296 u.setOpacity(me.images[0], 0); 3297 $(me.images[1]).show(); 3298 3299 u.setOpacity(me.images[1],1); 3300 3301 if (opts.isSelectable && opts.onGetList) { 3302 sorted_list = me.data.slice(0); 3303 if (opts.sortList) { 3304 if (opts.sortList === "desc") { 3305 sort_func = function (a, b) { 3306 return a === b ? 0 : (a > b ? -1 : 1); 3307 }; 3308 } 3309 else { 3310 sort_func = function (a, b) { 3311 return a === b ? 0 : (a < b ? -1 : 1); 3312 }; 3313 } 3314 3315 sorted_list.sort(function (a, b) { 3316 a = a.value; 3317 b = b.value; 3318 return sort_func(a, b); 3319 }); 3320 } 3321 3322 me.options.boundList = opts.onGetList.call(me.image, sorted_list); 3323 } 3324 3325 me.complete=true; 3326 me.processCommandQueue(); 3327 3328 if (opts.onConfigured && typeof opts.onConfigured === 'function') { 3329 opts.onConfigured.call(img, true); 3330 } 3331 }, 3332 3333 // when rebind is true, the MapArea data will not be rebuilt. 3334 buildDataset: function(rebind) { 3335 var sel,areas,j,area_id,$area,area,curKey,mapArea,key,keys,mapAreaId,group_value,dataItem,href, 3336 me=this, 3337 opts=me.options, 3338 default_group; 3339 3340 function addAreaData(key, value) { 3341 var dataItem = new m.AreaData(me, key, value); 3342 dataItem.areaId = me._xref[key] = me.data.push(dataItem) - 1; 3343 return dataItem.areaId; 3344 } 3345 3346 me._xref = {}; 3347 me.data = []; 3348 if (!rebind) { 3349 me.mapAreas=[]; 3350 } 3351 3352 default_group = !opts.mapKey; 3353 if (default_group) { 3354 opts.mapKey = 'data-mapster-key'; 3355 } 3356 3357 // the [attribute] selector is broken on old IE with jQuery. hasVml() is a quick and dirty 3358 // way to test for that 3359 3360 sel = m.hasVml() ? 'area' : 3361 (default_group ? 3362 'area[coords]' : 3363 'area[' + opts.mapKey + ']'); 3364 3365 areas = $(me.map).find(sel).unbind('.mapster'); 3366 3367 for (mapAreaId = 0;mapAreaId<areas.length; mapAreaId++) { 3368 area_id = 0; 3369 area = areas[mapAreaId]; 3370 $area = $(area); 3371 3372 // skip areas with no coords - selector broken for older ie 3373 if (!area.coords) { 3374 continue; 3375 } 3376 // Create a key if none was assigned by the user 3377 3378 if (default_group) { 3379 curKey=String(mapAreaId); 3380 $area.attr('data-mapster-key', curKey); 3381 3382 } else { 3383 curKey = area.getAttribute(opts.mapKey); 3384 } 3385 3386 // conditions for which the area will be bound to mouse events 3387 // 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. 3388 3389 if (rebind) { 3390 mapArea = me.mapAreas[$area.data('mapster')-1]; 3391 mapArea.configure(curKey); 3392 } else { 3393 mapArea = new m.MapArea(me, area,curKey); 3394 me.mapAreas.push(mapArea); 3395 } 3396 3397 keys = mapArea.keys; // converted to an array by mapArea 3398 3399 3400 // Iterate through each mapKey assigned to this area 3401 for (j = keys.length - 1; j >= 0; j--) { 3402 key = keys[j]; 3403 3404 if (opts.mapValue) { 3405 group_value = $area.attr(opts.mapValue); 3406 } 3407 if (default_group) { 3408 // set an attribute so we can refer to the area by index from the DOM object if no key 3409 area_id = addAreaData(me.data.length, group_value); 3410 dataItem = me.data[area_id]; 3411 dataItem.key = key = area_id.toString(); 3412 } 3413 else { 3414 area_id = me._xref[key]; 3415 if (area_id >= 0) { 3416 dataItem = me.data[area_id]; 3417 if (group_value && !me.data[area_id].value) { 3418 dataItem.value = group_value; 3419 } 3420 } 3421 else { 3422 area_id = addAreaData(key, group_value); 3423 dataItem = me.data[area_id]; 3424 dataItem.isPrimary=j===0; 3425 } 3426 } 3427 mapArea.areaDataXref.push(area_id); 3428 dataItem.areasXref.push(mapAreaId); 3429 } 3430 3431 href=$area.attr('href'); 3432 if (href && href!=='#' && !dataItem.href) 3433 { 3434 dataItem.href=href; 3435 } 3436 3437 if (!mapArea.nohref) { 3438 $area.bind('click.mapster', me.click) 3439 .bind('mouseover.mapster', me.mouseover) 3440 .bind('mouseout.mapster', me.mouseout) 3441 .bind('mousedown.mapster', me.mousedown); 3442 3443 3444 3445 } 3446 3447 // store an ID with each area. 3448 $area.data("mapster", mapAreaId+1); 3449 } 3450 3451 // TODO listenToList 3452 // if (opts.listenToList && opts.nitG) { 3453 // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); 3454 // } 3455 3456 // populate areas from config options 3457 me.setAreaOptions(opts.areas); 3458 me.redrawSelections(); 3459 3460 }, 3461 processCommandQueue: function() { 3462 3463 var cur,me=this; 3464 while (!me.currentAction && me.commands.length) { 3465 cur = me.commands[0]; 3466 me.commands.splice(0,1); 3467 m.impl[cur.command].apply(cur.that, cur.args); 3468 } 3469 }, 3470 clearEvents: function () { 3471 $(this.map).find('area') 3472 .unbind('.mapster'); 3473 $(this.images) 3474 .unbind('.mapster'); 3475 }, 3476 _clearCanvases: function (preserveState) { 3477 // remove the canvas elements created 3478 if (!preserveState) { 3479 $(this.base_canvas).remove(); 3480 } 3481 $(this.overlay_canvas).remove(); 3482 }, 3483 clearMapData: function (preserveState) { 3484 var me = this; 3485 this._clearCanvases(preserveState); 3486 3487 // release refs to DOM elements 3488 $.each(this.data, function (i, e) { 3489 e.reset(); 3490 }); 3491 this.data = null; 3492 if (!preserveState) { 3493 // get rid of everything except the original image 3494 this.image.style.cssText = this.imgCssText; 3495 $(this.wrapper).before(this.image).remove(); 3496 } 3497 3498 me.images.clear(); 3499 3500 this.image = null; 3501 u.ifFunction(this.clearTooltip, this); 3502 }, 3503 3504 // Compelete cleanup process for deslecting items. Called after a batch operation, or by AreaData for single 3505 // operations not flagged as "partial" 3506 3507 removeSelectionFinish: function () { 3508 var g = this.graphics; 3509 3510 g.refreshSelections(); 3511 // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect 3512 g.clearHighlight(); 3513 } 3514 }; 3515} (jQuery)); 3516/* areadata.js 3517 AreaData and MapArea protoypes 3518*/ 3519 3520(function ($) { 3521 var m = $.mapster, u = m.utils; 3522 3523 /** 3524 * Select this area 3525 * 3526 * @param {AreaData} me AreaData context 3527 * @param {object} options Options for rendering the selection 3528 */ 3529 function select(options) { 3530 // need to add the new one first so that the double-opacity effect leaves the current one highlighted for singleSelect 3531 3532 var me=this, o = me.owner; 3533 if (o.options.singleSelect) { 3534 o.clearSelections(); 3535 } 3536 3537 // because areas can overlap - we can't depend on the selection state to tell us anything about the inner areas. 3538 // don't check if it's already selected 3539 if (!me.isSelected()) { 3540 if (options) { 3541 3542 // cache the current options, and map the altImageId if an altimage 3543 // was passed 3544 3545 me.optsCache = $.extend(me.effectiveRenderOptions('select'), 3546 options, 3547 { 3548 altImageId: o.images.add(options.altImage) 3549 }); 3550 } 3551 3552 me.drawSelection(); 3553 3554 me.selected = true; 3555 me.changeState('select', true); 3556 } 3557 3558 if (o.options.singleSelect) { 3559 o.graphics.refreshSelections(); 3560 } 3561 } 3562 3563 /** 3564 * Deselect this area, optionally deferring finalization so additional areas can be deselected 3565 * in a single operation 3566 * 3567 * @param {boolean} partial when true, the caller must invoke "finishRemoveSelection" to render 3568 */ 3569 3570 function deselect(partial) { 3571 var me=this; 3572 me.selected = false; 3573 me.changeState('select', false); 3574 3575 // release information about last area options when deselecting. 3576 3577 me.optsCache=null; 3578 me.owner.graphics.removeSelections(me.areaId); 3579 3580 // Complete selection removal process. This is separated because it's very inefficient to perform the whole 3581 // process for multiple removals, as the canvas must be totally redrawn at the end of the process.ar.remove 3582 3583 if (!partial) { 3584 me.owner.removeSelectionFinish(); 3585 } 3586 } 3587 3588 /** 3589 * Toggle the selection state of this area 3590 * @param {object} options Rendering options, if toggling on 3591 * @return {bool} The new selection state 3592 */ 3593 function toggle(options) { 3594 var me=this; 3595 if (!me.isSelected()) { 3596 me.select(options); 3597 } 3598 else { 3599 me.deselect(); 3600 } 3601 return me.isSelected(); 3602 } 3603 3604 /** 3605 * An AreaData object; represents a conceptual area that can be composed of 3606 * one or more MapArea objects 3607 * 3608 * @param {MapData} owner The MapData object to which this belongs 3609 * @param {string} key The key for this area 3610 * @param {string} value The mapValue string for this area 3611 */ 3612 3613 m.AreaData = function (owner, key, value) { 3614 $.extend(this,{ 3615 owner: owner, 3616 key: key || '', 3617 // means this represents the first key in a list of keys (it's the area group that gets highlighted on mouseover) 3618 isPrimary: true, 3619 areaId: -1, 3620 href: '', 3621 value: value || '', 3622 options:{}, 3623 // "null" means unchanged. Use "isSelected" method to just test true/false 3624 selected: null, 3625 // xref to MapArea objects 3626 areasXref: [], 3627 // (temporary storage) - the actual area moused over 3628 area: null, 3629 // the last options used to render this. Cache so when re-drawing after a remove, changes in options won't 3630 // break already selected things. 3631 optsCache: null 3632 }); 3633 }; 3634 3635 /** 3636 * The public API for AreaData object 3637 */ 3638 3639 m.AreaData.prototype = { 3640 constuctor: m.AreaData, 3641 select: select, 3642 deselect: deselect, 3643 toggle: toggle, 3644 areas: function() { 3645 var i,result=[]; 3646 for (i=0;i<this.areasXref.length;i++) { 3647 result.push(this.owner.mapAreas[this.areasXref[i]]); 3648 } 3649 return result; 3650 }, 3651 // return all coordinates for all areas 3652 coords: function(offset) { 3653 var coords = []; 3654 $.each(this.areas(), function (i, el) { 3655 coords = coords.concat(el.coords(offset)); 3656 }); 3657 return coords; 3658 }, 3659 reset: function () { 3660 $.each(this.areas(), function (i, e) { 3661 e.reset(); 3662 }); 3663 this.areasXref = []; 3664 this.options = null; 3665 }, 3666 // Return the effective selected state of an area, incorporating staticState 3667 isSelectedOrStatic: function () { 3668 3669 var o = this.effectiveOptions(); 3670 return u.isBool(o.staticState) ? o.staticState : 3671 this.isSelected(); 3672 }, 3673 isSelected: function () { 3674 return u.isBool(this.selected) ? this.selected : 3675 u.isBool(this.owner.area_options.selected) ? this.owner.area_options.selected : false; 3676 }, 3677 isSelectable: function () { 3678 return u.isBool(this.effectiveOptions().staticState) ? false : 3679 (u.isBool(this.owner.options.staticState) ? false : u.boolOrDefault(this.effectiveOptions().isSelectable,true)); 3680 }, 3681 isDeselectable: function () { 3682 return u.isBool(this.effectiveOptions().staticState) ? false : 3683 (u.isBool(this.owner.options.staticState) ? false : u.boolOrDefault(this.effectiveOptions().isDeselectable,true)); 3684 }, 3685 isNotRendered: function() { 3686 var area = $(this.area); 3687 return area.attr('nohref') || 3688 !area.attr('href') || 3689 this.effectiveOptions().isMask; 3690 3691 }, 3692 3693 3694 /** 3695 * Return the overall options effective for this area. 3696 * This should get the default options, and merge in area-specific options, finally 3697 * overlaying options passed by parameter 3698 * 3699 * @param {[type]} options options which will supercede all other options for this area 3700 * @return {[type]} the combined options 3701 */ 3702 3703 effectiveOptions: function (options) { 3704 3705 var opts = u.updateProps({}, 3706 this.owner.area_options, 3707 this.options, 3708 options || {}, 3709 { 3710 id: this.areaId 3711 } 3712 ); 3713 3714 opts.selected = this.isSelected(); 3715 3716 return opts; 3717 }, 3718 3719 /** 3720 * Return the options effective for this area for a "render" or "highlight" mode. 3721 * This should get the default options, merge in the areas-specific options, 3722 * and then the mode-specific options. 3723 * @param {string} mode 'render' or 'highlight' 3724 * @param {[type]} options options which will supercede all other options for this area 3725 * @return {[type]} the combined options 3726 */ 3727 3728 effectiveRenderOptions: function (mode, options) { 3729 var allOpts,opts=this.optsCache; 3730 3731 if (!opts || mode==='highlight') { 3732 allOpts = this.effectiveOptions(options); 3733 opts = u.updateProps({}, 3734 allOpts, 3735 allOpts["render_" + mode] 3736 ); 3737 3738 if (mode!=='highlight') { 3739 this.optsCache=opts; 3740 } 3741 } 3742 return $.extend({},opts); 3743 }, 3744 3745 // Fire callback on area state change 3746 changeState: function (state_type, state) { 3747 if ($.isFunction(this.owner.options.onStateChange)) { 3748 this.owner.options.onStateChange.call(this.owner.image, 3749 { 3750 key: this.key, 3751 state: state_type, 3752 selected: state 3753 } 3754 ); 3755 } 3756 }, 3757 3758 // highlight this area 3759 3760 highlight: function (options) { 3761 var o = this.owner; 3762 if (this.effectiveOptions().highlight) { 3763 o.graphics.addShapeGroup(this, "highlight",options); 3764 } 3765 o.setHighlightId(this.areaId); 3766 this.changeState('highlight', true); 3767 }, 3768 3769 // select this area. if "callEvent" is true then the state change event will be called. (This method can be used 3770 // during config operations, in which case no event is indicated) 3771 3772 drawSelection: function () { 3773 3774 3775 this.owner.graphics.addShapeGroup(this, "select"); 3776 3777 } 3778 3779 3780 }; 3781 // represents an HTML area 3782 m.MapArea = function (owner,areaEl,keys) { 3783 if (!owner) { 3784 return; 3785 } 3786 var me = this; 3787 me.owner = owner; // a MapData object 3788 me.area = areaEl; 3789 me.areaDataXref=[]; // a list of map_data.data[] id's for each areaData object containing this 3790 me.originalCoords = []; 3791 $.each(u.split(areaEl.coords), function (i, el) { 3792 me.originalCoords.push(parseFloat(el)); 3793 }); 3794 me.length = me.originalCoords.length; 3795 me.shape = areaEl.shape.toLowerCase(); 3796 me.nohref = areaEl.nohref || !areaEl.href; 3797 me.configure(keys); 3798 }; 3799 m.MapArea.prototype= { 3800 constructor: m.MapArea, 3801 configure: function(keys) { 3802 this.keys = u.split(keys); 3803 }, 3804 reset: function() { 3805 this.area=null; 3806 }, 3807 coords: function (offset) { 3808 return $.map(this.originalCoords,function(e) { 3809 return offset ? e : e+offset; 3810 }); 3811 } 3812 }; 3813} (jQuery)); 3814/* areacorners.js 3815 determine the best place to put a box of dimensions (width,height) given a circle, rect or poly 3816*/ 3817 3818(function ($) { 3819 var u=$.mapster.utils; 3820 3821 3822 /** 3823 * Compute positions that will place a target with dimensions [width,height] outside 3824 * but near the boundaries of the elements "elements". When an imagemap is passed, the 3825 * 3826 * @param {Element|Element[]} elements An element or an array of elements (such as a jQuery object) 3827 * @param {Element} image The image to which area elements are bound, if this is an image map. 3828 * @param {Element} container The contianer in which the target must be constrained (or document, if missing) 3829 * @param {int} width The width of the target object 3830 * @return {object} a structure with the x and y positions 3831 */ 3832 u.areaCorners = function (elements, image, container, width, height) { 3833 var pos,found, minX, minY, maxX, maxY, bestMinX, bestMaxX, bestMinY, bestMaxY, curX, curY, nest, j, 3834 offsetx=0, 3835 offsety=0, 3836 rootx, 3837 rooty, 3838 iCoords,radius,angle,el, 3839 coords=[]; 3840 3841 // if a single element was passed, map it to an array 3842 3843 elements = elements.length ? 3844 elements: 3845 [elements]; 3846 3847 container = container ? 3848 $(container): 3849 $(document.body); 3850 3851 // get the relative root of calculation 3852 3853 pos = container.offset(); 3854 rootx = pos.left; 3855 rooty = pos.top; 3856 3857 // with areas, all we know about is relative to the top-left corner of the image. We need to add an offset compared to 3858 // the actual container. After this calculation, offsetx/offsety can be added to either the area coords, or the target's 3859 // absolute position to get the correct top/left boundaries of the container. 3860 3861 if (image) { 3862 pos = $(image).offset(); 3863 offsetx = pos.left; 3864 offsety = pos.top; 3865 } 3866 3867 // map the coordinates of any type of shape to a poly and use the logic. simpler than using three different 3868 // calculation methods. Circles use a 20 degree increment for this estimation. 3869 3870 for (j=0;j<elements.length;j++) 3871 { 3872 el=elements[j]; 3873 if (el.nodeName==='AREA') { 3874 iCoords = u.split(el.coords,parseInt); 3875 3876 switch(el.shape) { 3877 case 'circle': 3878 curX=iCoords[0]; 3879 curY=iCoords[1]; 3880 radius=iCoords[2]; 3881 coords=[]; 3882 for (j=0;j<360;j+=20) { 3883 angle=j*Math.PI/180; 3884 coords.push(curX+radius*Math.cos(angle),curY+radius*Math.sin(angle)); 3885 } 3886 break; 3887 case 'rect': 3888 coords.push(iCoords[0],iCoords[1],iCoords[2],iCoords[1],iCoords[2],iCoords[3],iCoords[0],iCoords[3]); 3889 break; 3890 default: 3891 coords=coords.concat(iCoords); 3892 break; 3893 } 3894 3895 // map area positions to it's real position in the container 3896 3897 for (j=0;j<coords.length;j+=2) 3898 { 3899 coords[j]=parseInt(coords[j],10)+offsetx; 3900 coords[j+1]=parseInt(coords[j+1],10)+offsety; 3901 } 3902 } else { 3903 el=$(el); 3904 pos = el.position(); 3905 coords.push(pos.left,pos.top, 3906 pos.left+el.width(),pos.top, 3907 pos.left+el.width(),pos.top+el.height(), 3908 pos.left,pos.top+el.height()); 3909 3910 } 3911 } 3912 3913 minX = minY = bestMinX = bestMinY = 999999; 3914 maxX = maxY = bestMaxX = bestMaxY = -1; 3915 3916 for (j = coords.length - 2; j >= 0; j -= 2) { 3917 curX = coords[j]; 3918 curY = coords[j + 1]; 3919 3920 if (curX < minX) { 3921 minX = curX; 3922 bestMaxY = curY; 3923 } 3924 if (curX > maxX) { 3925 maxX = curX; 3926 bestMinY = curY; 3927 } 3928 if (curY < minY) { 3929 minY = curY; 3930 bestMaxX = curX; 3931 } 3932 if (curY > maxY) { 3933 maxY = curY; 3934 bestMinX = curX; 3935 } 3936 3937 } 3938 3939 // try to figure out the best place for the tooltip 3940 3941 if (width && height) { 3942 found=false; 3943 $.each([[bestMaxX - width, minY - height], [bestMinX, minY - height], 3944 [minX - width, bestMaxY - height], [minX - width, bestMinY], 3945 [maxX,bestMaxY - height], [ maxX,bestMinY], 3946 [bestMaxX - width, maxY], [bestMinX, maxY] 3947 ],function (i, e) { 3948 if (!found && (e[0] > rootx && e[1] > rooty)) { 3949 nest = e; 3950 found=true; 3951 return false; 3952 } 3953 }); 3954 3955 // default to lower-right corner if nothing fit inside the boundaries of the image 3956 3957 if (!found) { 3958 nest=[maxX,maxY]; 3959 } 3960 } 3961 return nest; 3962 }; 3963} (jQuery)); 3964/* scale.js: resize and zoom functionality 3965 requires areacorners.js, when.js 3966*/ 3967 3968 3969(function ($) { 3970 var m = $.mapster, u = m.utils, p = m.MapArea.prototype; 3971 3972 m.utils.getScaleInfo = function (eff, actual) { 3973 var pct; 3974 if (!actual) { 3975 pct = 1; 3976 actual=eff; 3977 } else { 3978 pct = eff.width / actual.width || eff.height / actual.height; 3979 // make sure a float error doesn't muck us up 3980 if (pct > 0.98 && pct < 1.02) { pct = 1; } 3981 } 3982 return { 3983 scale: (pct !== 1), 3984 scalePct: pct, 3985 realWidth: actual.width, 3986 realHeight: actual.height, 3987 width: eff.width, 3988 height: eff.height, 3989 ratio: eff.width / eff.height 3990 }; 3991 }; 3992 // Scale a set of AREAs, return old data as an array of objects 3993 m.utils.scaleMap = function (image, imageRaw, scale) { 3994 3995 // stunningly, jQuery width can return zero even as width does not, seems to happen only 3996 // with adBlock or maybe other plugins. These must interfere with onload events somehow. 3997 3998 3999 var vis=u.size(image), 4000 raw=u.size(imageRaw,true); 4001 4002 if (!raw.complete()) { 4003 throw("Another script, such as an extension, appears to be interfering with image loading. Please let us know about this."); 4004 } 4005 if (!vis.complete()) { 4006 vis=raw; 4007 } 4008 return this.getScaleInfo(vis, scale ? raw : null); 4009 }; 4010 4011 /** 4012 * Resize the image map. Only one of newWidth and newHeight should be passed to preserve scale 4013 * 4014 * @param {int} width The new width OR an object containing named parameters matching this function sig 4015 * @param {int} height The new height 4016 * @param {int} effectDuration Time in ms for the resize animation, or zero for no animation 4017 * @param {function} callback A function to invoke when the operation finishes 4018 * @return {promise} NOT YET IMPLEMENTED 4019 */ 4020 4021 m.MapData.prototype.resize = function (width, height, duration, callback) { 4022 var p,promises,newsize,els, highlightId, ratio, 4023 me = this; 4024 4025 // allow omitting duration 4026 callback = callback || duration; 4027 4028 function sizeCanvas(canvas, w, h) { 4029 if (m.hasCanvas()) { 4030 canvas.width = w; 4031 canvas.height = h; 4032 } else { 4033 $(canvas).width(w); 4034 $(canvas).height(h); 4035 } 4036 } 4037 4038 // Finalize resize action, do callback, pass control to command queue 4039 4040 function cleanupAndNotify() { 4041 4042 me.currentAction = ''; 4043 4044 if ($.isFunction(callback)) { 4045 callback(); 4046 } 4047 4048 me.processCommandQueue(); 4049 } 4050 4051 // handle cleanup after the inner elements are resized 4052 4053 function finishResize() { 4054 sizeCanvas(me.overlay_canvas, width, height); 4055 4056 // restore highlight state if it was highlighted before 4057 if (highlightId >= 0) { 4058 var areaData = me.data[highlightId]; 4059 areaData.tempOptions = { fade: false }; 4060 me.getDataForKey(areaData.key).highlight(); 4061 areaData.tempOptions = null; 4062 } 4063 sizeCanvas(me.base_canvas, width, height); 4064 me.redrawSelections(); 4065 cleanupAndNotify(); 4066 } 4067 4068 function resizeMapData() { 4069 $(me.image).css(newsize); 4070 // start calculation at the same time as effect 4071 me.scaleInfo = u.getScaleInfo({ 4072 width: width, 4073 height: height 4074 }, 4075 { 4076 width: me.scaleInfo.realWidth, 4077 height: me.scaleInfo.realHeight 4078 }); 4079 $.each(me.data, function (i, e) { 4080 $.each(e.areas(), function (i, e) { 4081 e.resize(); 4082 }); 4083 }); 4084 } 4085 4086 if (me.scaleInfo.width === width && me.scaleInfo.height === height) { 4087 return; 4088 } 4089 4090 highlightId = me.highlightId; 4091 4092 4093 if (!width) { 4094 ratio = height / me.scaleInfo.realHeight; 4095 width = Math.round(me.scaleInfo.realWidth * ratio); 4096 } 4097 if (!height) { 4098 ratio = width / me.scaleInfo.realWidth; 4099 height = Math.round(me.scaleInfo.realHeight * ratio); 4100 } 4101 4102 newsize = { 'width': String(width) + 'px', 'height': String(height) + 'px' }; 4103 if (!m.hasCanvas()) { 4104 $(me.base_canvas).children().remove(); 4105 } 4106 4107 // resize all the elements that are part of the map except the image itself (which is not visible) 4108 // but including the div wrapper 4109 els = $(me.wrapper).find('.mapster_el').add(me.wrapper); 4110 4111 if (duration) { 4112 promises = []; 4113 me.currentAction = 'resizing'; 4114 els.each(function (i, e) { 4115 p = u.defer(); 4116 promises.push(p); 4117 4118 $(e).animate(newsize, { 4119 duration: duration, 4120 complete: p.resolve, 4121 easing: "linear" 4122 }); 4123 }); 4124 4125 p = u.defer(); 4126 promises.push(p); 4127 4128 // though resizeMapData is not async, it needs to be finished just the same as the animations, 4129 // so add it to the "to do" list. 4130 4131 u.when.all(promises).then(finishResize); 4132 resizeMapData(); 4133 p.resolve(); 4134 } else { 4135 els.css(newsize); 4136 resizeMapData(); 4137 finishResize(); 4138 4139 } 4140 }; 4141 4142 4143 m.MapArea = u.subclass(m.MapArea, function () { 4144 //change the area tag data if needed 4145 this.base.init(); 4146 if (this.owner.scaleInfo.scale) { 4147 this.resize(); 4148 } 4149 }); 4150 4151 p.coords = function (percent, coordOffset) { 4152 var j, newCoords = [], 4153 pct = percent || this.owner.scaleInfo.scalePct, 4154 offset = coordOffset || 0; 4155 4156 if (pct === 1 && coordOffset === 0) { 4157 return this.originalCoords; 4158 } 4159 4160 for (j = 0; j < this.length; j++) { 4161 //amount = j % 2 === 0 ? xPct : yPct; 4162 newCoords.push(Math.round(this.originalCoords[j] * pct) + offset); 4163 } 4164 return newCoords; 4165 }; 4166 p.resize = function () { 4167 this.area.coords = this.coords().join(','); 4168 }; 4169 4170 p.reset = function () { 4171 this.area.coords = this.coords(1).join(','); 4172 }; 4173 4174 m.impl.resize = function (width, height, duration, callback) { 4175 if (!width && !height) { 4176 return false; 4177 } 4178 var x= (new m.Method(this, 4179 function () { 4180 this.resize(width, height, duration, callback); 4181 }, 4182 null, 4183 { 4184 name: 'resize', 4185 args: arguments 4186 } 4187 )).go(); 4188 return x; 4189 }; 4190 4191/* 4192 m.impl.zoom = function (key, opts) { 4193 var options = opts || {}; 4194 4195 function zoom(areaData) { 4196 // this will be MapData object returned by Method 4197 4198 var scroll, corners, height, width, ratio, 4199 diffX, diffY, ratioX, ratioY, offsetX, offsetY, newWidth, newHeight, scrollLeft, scrollTop, 4200 padding = options.padding || 0, 4201 scrollBarSize = areaData ? 20 : 0, 4202 me = this, 4203 zoomOut = false;
4204 4205 if (areaData) { 4206 // save original state on first zoom operation 4207 if (!me.zoomed) { 4208 me.zoomed = true; 4209 me.preZoomWidth = me.scaleInfo.width; 4210 me.preZoomHeight = me.scaleInfo.height; 4211 me.zoomedArea = areaData; 4212 if (options.scroll) { 4213 me.wrapper.css({ overflow: 'auto' }); 4214 } 4215 } 4216 corners = $.mapster.utils.areaCorners(areaData.coords(1, 0)); 4217 width = me.wrapper.innerWidth() - scrollBarSize - padding * 2; 4218 height = me.wrapper.innerHeight() - scrollBarSize - padding * 2; 4219 diffX = corners.maxX - corners.minX; 4220 diffY = corners.maxY - corners.minY; 4221 ratioX = width / diffX; 4222 ratioY = height / diffY; 4223 ratio = Math.min(ratioX, ratioY); 4224 offsetX = (width - diffX * ratio) / 2; 4225 offsetY = (height - diffY * ratio) / 2; 4226 4227 newWidth = me.scaleInfo.realWidth * ratio; 4228 newHeight = me.scaleInfo.realHeight * ratio; 4229 scrollLeft = (corners.minX) * ratio - padding - offsetX; 4230 scrollTop = (corners.minY) * ratio - padding - offsetY; 4231 } else { 4232 if (!me.zoomed) { 4233 return; 4234 } 4235 zoomOut = true; 4236 newWidth = me.preZoomWidth; 4237 newHeight = me.preZoomHeight; 4238 scrollLeft = null; 4239 scrollTop = null; 4240 } 4241 4242 this.resize({ 4243 width: newWidth, 4244 height: newHeight, 4245 duration: options.duration, 4246 scroll: scroll, 4247 scrollLeft: scrollLeft, 4248 scrollTop: scrollTop, 4249 // closure so we can be sure values are correct 4250 callback: (function () { 4251 var isZoomOut = zoomOut, 4252 scroll = options.scroll, 4253 areaD = areaData; 4254 return function () { 4255 if (isZoomOut) { 4256 me.preZoomWidth = null; 4257 me.preZoomHeight = null; 4258 me.zoomed = false; 4259 me.zoomedArea = false; 4260 if (scroll) { 4261 me.wrapper.css({ overflow: 'inherit' }); 4262 } 4263 } else { 4264 // just to be sure it wasn't canceled & restarted 4265 me.zoomedArea = areaD; 4266 } 4267 }; 4268 } ()) 4269 }); 4270 } 4271 return (new m.Method(this, 4272 function (opts) { 4273 zoom.call(this); 4274 }, 4275 function () { 4276 zoom.call(this.owner, this); 4277 }, 4278 { 4279 name: 'zoom', 4280 args: arguments, 4281 first: true, 4282 key: key 4283 } 4284 )).go(); 4285 4286 4287 }; 4288 */ 4289} (jQuery)); 4290/* tooltip.js - tooltip functionality 4291 requires areacorners.js 4292*/ 4293 4294(function ($) { 4295 4296 var m = $.mapster, u = m.utils; 4297 4298 $.extend(m.defaults, { 4299 toolTipContainer: '<div style="border: 2px solid black; background: #EEEEEE; width:160px; padding:4px; margin: 4px; -moz-box-shadow: 3px 3px 5px #535353; ' + 4300 '-webkit-box-shadow: 3px 3px 5px #535353; box-shadow: 3px 3px 5px #535353; -moz-border-radius: 6px 6px 6px 6px; -webkit-border-radius: 6px; ' + 4301 'border-radius: 6px 6px 6px 6px; opacity: 0.9;"></div>', 4302 showToolTip: false, 4303 toolTipFade: true, 4304 toolTipClose: ['area-mouseout','image-mouseout'], 4305 onShowToolTip: null, 4306 onHideToolTip: null 4307 }); 4308 4309 $.extend(m.area_defaults, { 4310 toolTip: null, 4311 toolTipClose: null 4312 }); 4313 4314 4315 /** 4316 * Show a tooltip positioned near this area. 4317 * 4318 * @param {string|jquery}
vendor: 4,877 bytes, lines 4318-4452
4318 html A string of html or a jQuery object containing the tooltip content. 4319 * @param {string|jquery} [template] The html template in which to wrap the content 4320 * @param {string|object} [css] CSS to apply to the outermost element of the tooltip 4321 * @return {jquery} The tooltip that was created 4322 */ 4323 4324 function createToolTip(html, template, css) { 4325 var tooltip; 4326 4327 // wrap the template in a jQuery object, or clone the template if it's already one. 4328 // This assumes that anything other than a string is a jQuery object; if it's not jQuery will 4329 // probably throw an error. 4330 4331 if (template) { 4332 tooltip = typeof template === 'string' ? 4333 $(template) : 4334 $(template).clone(); 4335 4336 tooltip.append(html); 4337 } else { 4338 tooltip=$(html); 4339 } 4340 4341 // always set display to block, or the positioning css won't work if the end user happened to 4342 // use a non-block type element. 4343 4344 tooltip.css($.extend((css || {}),{ 4345 display:"block", 4346 position:"absolute" 4347 })).hide(); 4348 4349 $('body').append(tooltip); 4350 4351 // we must actually add the tooltip to the DOM and "show" it in order to figure out how much space it 4352 // consumes, and then reposition it with that knowledge. 4353 // We also cache the actual opacity setting to restore finally. 4354 4355 tooltip.attr("data-opacity",tooltip.css("opacity")) 4356 .css("opacity",0); 4357 4358 // doesn't really show it because opacity=0 4359 4360 return tooltip.show(); 4361 } 4362 4363 4364 /** 4365 * Show a tooltip positioned near this area. 4366 * 4367 * @param {jquery} tooltip The tooltip 4368 * @param {object} [options] options for displaying the tooltip. 4369 * @config {int} [left] The 0-based absolute x position for the tooltip 4370 * @config {int} [top] The 0-based absolute y position for the tooltip 4371 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4372 * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. 4373 */ 4374 4375 function showToolTipImpl(tooltip,options) 4376 { 4377 var tooltipCss = { 4378 "left": options.left + "px", 4379 "top": options.top + "px" 4380 }, 4381 actalOpacity=tooltip.attr("data-opacity") || 0, 4382 zindex = tooltip.css("z-index"); 4383 4384 if (parseInt(zindex,10)===0 4385 || zindex === "auto") { 4386 tooltipCss["z-index"] = 9999; 4387 } 4388 4389 tooltip.css(tooltipCss) 4390 .addClass('mapster_tooltip'); 4391 4392 4393 if (options.fadeDuration && options.fadeDuration>0) { 4394 u.fader(tooltip[0], 0, actalOpacity, options.fadeDuration); 4395 } else { 4396 u.setOpacity(tooltip[0], actalOpacity); 4397 } 4398 } 4399 4400 /** 4401 * Hide and remove active tooltips 4402 * 4403 * @param {MapData} this The mapdata object to which the tooltips belong 4404 */ 4405 4406 m.MapData.prototype.clearToolTip = function() { 4407 if (this.activeToolTip) { 4408 this.activeToolTip.stop().remove(); 4409 this.activeToolTip = null; 4410 this.activeToolTipID = null; 4411 u.ifFunction(this.options.onHideToolTip, this); 4412 } 4413 }; 4414 4415 /** 4416 * Configure the binding between a named tooltip closing option, and a mouse event. 4417 * 4418 * If a callback is passed, it will be called when the activating event occurs, and the tooltip will 4419 * only closed if it returns true. 4420 * 4421 * @param {MapData} [this] The MapData object to which this tooltip belongs. 4422 * @param {String} option The name of the tooltip closing option 4423 * @param {String} event UI event to bind to this option 4424 * @param {Element} target The DOM element that is the target of the event 4425 * @param {Function} [beforeClose] Callback when the tooltip is closed 4426 * @param {Function} [onClose] Callback when the tooltip is closed 4427 */ 4428 function bindToolTipClose(options, bindOption, event, target, beforeClose, onClose) { 4429 var event_name = event + '.mapster-tooltip'; 4430 4431 if ($.inArray(bindOption, options) >= 0) { 4432 target.unbind(event_name) 4433 .bind(event_name, function (e) { 4434 if (!beforeClose || beforeClose.call(this,e)) { 4435 target.unbind('.mapster-tooltip'); 4436 if (onClose) { 4437 onClose.call(this); 4438 } 4439 } 4440 }); 4441 4442 return { 4443 object: target, 4444 event: event_name 4445 }; 4446 } 4447 } 4448 4449 /** 4450 * Show a tooltip. 4451 * 4452 * @param {string|jquery}
4452 [tooltip] A string of html or a jQuery object containing the tooltip content. 4453 * 4454 * @param {string|jquery} [target] The target of the tooltip, to be used to determine positioning. If null, 4455 * absolute position values must be passed with left and top. 4456 * 4457 * @param {string|jquery} [image] If target is an [area] the image that owns it 4458 * 4459 * @param {string|jquery} [container] An element within which the tooltip must be bounded 4460 * 4461 * 4462 * 4463 * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - 4464 * The markup, or a jquery object, containing the data for the tooltip 4465 * 4466 * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip 4467 * closes: 'area-click','tooltip-click','image-mouseout' are valid values 4468 * then no template will be used. 4469 * @config {int} [offsetx] the horizontal amount to offset the tooltip 4470 * @config {int} [offsety] the vertical amount to offset the tooltip 4471 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4472 */ 4473 4474 function showToolTip(tooltip,target,image,container,options) { 4475 var corners, 4476 ttopts = {}; 4477 4478 options = options || {}; 4479 4480 4481 if (target) { 4482 4483 corners = u.areaCorners(target,image,container, 4484 tooltip.outerWidth(true), 4485 tooltip.outerHeight(true)); 4486 4487 // Try to upper-left align it first, if that doesn't work, change the parameters 4488 4489 ttopts.left = corners[0]; 4490 ttopts.top = corners[1]; 4491 4492 } else { 4493 4494 ttopts.left = options.left; 4495 ttopts.top = options.top; 4496 } 4497 4498 ttopts.left += (options.offsetx || 0); 4499 ttopts.top +=(options.offsety || 0); 4500 4501 ttopts.css= options.css; 4502 ttopts.fadeDuration = options.fadeDuration; 4503 4504 showToolTipImpl(tooltip,ttopts); 4505 4506 return tooltip; 4507 } 4508 4509 /** 4510 * Show a tooltip positioned near this area. 4511 * 4512 * @param {string|jquery} [content] A string of html or a jQuery object containing the tooltip content. 4513 4514 * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - 4515 * The markup, or a jquery object, containing the data for the tooltip 4516 * @config {string|jquery} [container] An element within which the tooltip must be bounded 4517 * @config {bool} [template] a template to use instead of the default. If this property exists and is null, 4518 * then no template will be used. 4519 * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip 4520 * closes: 'area-click','tooltip-click','image-mouseout' are valid values 4521 * then no template will be used. 4522 * @config {int} [offsetx] the horizontal amount to offset the tooltip 4523 * @config {int} [offsety] the vertical amount to offset the tooltip 4524 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4525 */ 4526 m.AreaData.prototype.showToolTip= function(content,options) { 4527 var tooltip, closeOpts, target, tipClosed, template, 4528 ttopts = {}, 4529 ad=this, 4530 md=ad.owner, 4531 areaOpts = ad.effectiveOptions(); 4532 4533 // copy the options object so we can update it 4534 options = options ? $.extend({},options) : {}; 4535 4536 content = content || areaOpts.toolTip; 4537 closeOpts = options.closeEvents || areaOpts.toolTipClose || md.options.toolTipClose || 'tooltip-click'; 4538 4539 template = typeof options.template !== 'undefined' ? 4540 options.template : 4541 md.options.toolTipContainer; 4542 4543 options.closeEvents = typeof closeOpts === 'string' ? 4544 closeOpts = u.split(closeOpts) : 4545 closeOpts; 4546 4547 options.fadeDuration = options.fadeDuration || 4548 (md.options.toolTipFade ? 4549 (md.options.fadeDuration || areaOpts.fadeDuration) : 0); 4550 4551 target = ad.area ? 4552 ad.area : 4553 $.map(ad.areas(), 4554 function(e) { 4555 return e.area; 4556 }); 4557 4558 if (md.activeToolTipID===ad.areaId) { 4559 return; 4560 } 4561 4562 md.clearToolTip(); 4563 4564 md.activeToolTip = tooltip = createToolTip(content, 4565 template, 4566 options.css); 4567 4568 md.activeToolTipID = ad.areaId; 4569 4570 tipClosed = function() { 4571 md.clearToolTip(); 4572 }; 4573 4574 bindToolTipClose(closeOpts,'area-click', 'click', $(md.map), null, tipClosed); 4575 bindToolTipClose(closeOpts,'tooltip-click', 'click', tooltip,null, tipClosed); 4576 bindToolTipClose(closeOpts,'image-mouseout', 'mouseout', $(md.image), function(e) { 4577 return (e.relatedTarget && e.relatedTarget.nodeName!=='AREA' && e.relatedTarget!==ad.area); 4578 }, tipClosed); 4579 4580 4581 showToolTip(tooltip, 4582 target, 4583 md.image, 4584 options.container, 4585 template, 4586 options); 4587 4588 u.ifFunction(md.options.onShowToolTip, ad.area, 4589 { 4590 toolTip: tooltip, 4591 options: ttopts, 4592 areaOptions: areaOpts, 4593 key: ad.key, 4594 selected: ad.isSelected() 4595 }); 4596 4597 return tooltip; 4598 }; 4599 4600 4601 /** 4602 * Parse an object that could be a string, a jquery object, or an object with a "contents" property 4603 * containing html or a jQuery object. 4604 * 4605 * @param {object|string|jQuery} options The parameter to parse 4606 * @return {string|jquery} A string or jquery object 4607 */ 4608 function getHtmlFromOptions(options) { 4609 4610 // see if any html was passed as either the options object itself, or the content property 4611 4612 return (options ? 4613 ((typeof options === 'string' || options.jquery) ? 4614 options : 4615 options.content) : 4616 null); 4617 } 4618 4619 /** 4620 * Activate or remove a tooltip for an area. When this method is called on an area, the 4621 * key parameter doesn't apply and "options" is the first parameter. 4622 *
4623 * When called with no parameters, or "key" is a falsy value, any active tooltip is cleared. 4624 * 4625 * When only a key is provided, the default tooltip for the area is used. 4626 * 4627 * When html is provided, this is used instead of the default tooltip. 4628 * 4629 * When "noTemplate" is true, the default tooltip template will not be used either, meaning only 4630 * the actual html passed will be used. 4631 * 4632 * @param {string|AreaElement} key The area for which to activate a tooltip, or a DOM element. 4633 * 4634 * @param {object|string|jquery} [options] options to apply when creating this tooltip - OR - 4635 * The markup, or a jquery object, containing the data for the tooltip 4636 * @config {string|jQuery} [content] the inner content of the tooltip; the tooltip text or HTML 4637 * @config {Element|jQuery} [container] the inner content of the tooltip; the tooltip text or HTML 4638 * @config {bool} [template] a template to use instead of the default. If this property exists and is null, 4639 * then no template will be used. 4640 * @config {int} [offsetx] the horizontal amount to offset the tooltip. 4641 * @config {int} [offsety] the vertical amount to offset the tooltip. 4642 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4643 * @config {string|object} [css] CSS to apply to the outermost element of the tooltip 4644 * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. 4645 * @return {jQuery} The jQuery object 4646 */ 4647 4648 m.impl.tooltip = function (key,options) { 4649 return (new m.Method(this, 4650 function mapData() { 4651 var tooltip, target, md=this; 4652 if (!key) { 4653 md.clearToolTip(); 4654 } else { 4655 target=$(key); 4656 if (md.activeToolTipID ===target[0]) { 4657 return; 4658 } 4659 md.clearToolTip(); 4660 4661 md.activeToolTip = tooltip = createToolTip(getHtmlFromOptions(options), 4662 options.template || md.options.toolTipContainer, 4663 options.css); 4664 md.activeToolTipID = target[0]; 4665 4666 bindToolTipClose(['tooltip-click'],'tooltip-click', 'click', tooltip, null, function() { 4667 md.clearToolTip(); 4668 }); 4669 4670 md.activeToolTip = tooltip = showToolTip(tooltip, 4671 target, 4672 md.image, 4673 options.container, 4674 options); 4675 } 4676 }, 4677 function areaData() { 4678 if ($.isPlainObject(key) && !options) { 4679 options = key; 4680 } 4681 4682 this.showToolTip(getHtmlFromOptions(options),options); 4683 }, 4684 { 4685 name: 'tooltip', 4686 args: arguments, 4687 key: key 4688 } 4689 )).go(); 4690 }; 4691} (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.