# HG changeset patch # User Thierry Florac # Date 1485339960 -3600 # Node ID ba26f4023bc29b9a5f1a9589e6faa7a487594e7f # Parent 8c5bbc396670bfe80e4ce7132d5daa32a66f1168 Added images maps preview diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo Binary file src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo has changed diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po --- a/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po Wed Jan 25 11:26:00 2017 +0100 @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2017-01-20 15:26+0100\n" +"POT-Creation-Date: 2017-01-25 11:12+0100\n" "PO-Revision-Date: 2015-09-10 10:42+0200\n" "Last-Translator: Thierry Florac \n" "Language-Team: French\n" @@ -112,7 +112,7 @@ #: src/pyams_content/component/links/interfaces/__init__.py:43 #: src/pyams_content/shared/common/zmi/dashboard.py:104 #: src/pyams_content/shared/common/zmi/templates/advanced-search.pt:188 -#: src/pyams_content/shared/imagemap/zmi/container.py:118 +#: src/pyams_content/shared/imagemap/zmi/container.py:121 #: src/pyams_content/interfaces/__init__.py:54 msgid "Title" msgstr "Titre" @@ -150,7 +150,7 @@ #: src/pyams_content/component/gallery/zmi/gallery.py:239 #: src/pyams_content/shared/zmi/sites.py:124 -#: src/pyams_content/shared/imagemap/zmi/container.py:149 +#: src/pyams_content/shared/imagemap/zmi/container.py:171 msgid "No provided object_name argument!" msgstr "Argument 'object_name' non fourni !" @@ -555,6 +555,7 @@ msgstr "Liste des liens utiles" #: src/pyams_content/component/links/zmi/container.py:173 +#: src/pyams_content/shared/imagemap/zmi/container.py:133 #: src/pyams_content/shared/imagemap/interfaces/__init__.py:46 msgid "Link target" msgstr "Cible du lien" @@ -1878,28 +1879,28 @@ msgid "Image map « {title} »" msgstr "Article de blog « {title} »" -#: src/pyams_content/shared/imagemap/zmi/container.py:53 +#: src/pyams_content/shared/imagemap/zmi/container.py:56 msgid "Image areas" msgstr "Zones de l'image" -#: src/pyams_content/shared/imagemap/zmi/container.py:64 +#: src/pyams_content/shared/imagemap/zmi/container.py:67 #: src/pyams_content/shared/imagemap/interfaces/__init__.py:63 msgid "Image map areas" msgstr "Zones cliquables de l'images" -#: src/pyams_content/shared/imagemap/zmi/container.py:108 +#: src/pyams_content/shared/imagemap/zmi/container.py:111 msgid "No currently defined image." msgstr "Aucun image de fond n'est actuellement définie." -#: src/pyams_content/shared/imagemap/zmi/container.py:110 +#: src/pyams_content/shared/imagemap/zmi/container.py:113 msgid "No currently defined area." msgstr "Aucune zone cliquable n'est actuellement définie." -#: src/pyams_content/shared/imagemap/zmi/container.py:160 +#: src/pyams_content/shared/imagemap/zmi/container.py:182 msgid "Given area name doesn't exist!" msgstr "La zone indiquée n'existe pas !" -#: src/pyams_content/shared/imagemap/zmi/container.py:156 +#: src/pyams_content/shared/imagemap/zmi/container.py:178 msgid "Bad query object_name parameter value!" msgstr "Valeur incorrecte du paramètre object_name !" @@ -1915,6 +1916,10 @@ msgid "Edit image map properties" msgstr "Modifier les propriétés d'une image" +#: src/pyams_content/shared/imagemap/zmi/templates/summary.pt:3 +msgid "Image areas preview" +msgstr "Aperçu des zones de l'image" + #: src/pyams_content/shared/imagemap/interfaces/__init__.py:32 msgid "Image map" msgstr "Image cliquable" diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/locales/pyams_content.pot --- a/src/pyams_content/locales/pyams_content.pot Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/locales/pyams_content.pot Wed Jan 25 11:26:00 2017 +0100 @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2017-01-20 15:26+0100\n" +"POT-Creation-Date: 2017-01-25 11:12+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" @@ -113,7 +113,7 @@ #: ./src/pyams_content/component/links/interfaces/__init__.py:43 #: ./src/pyams_content/shared/common/zmi/dashboard.py:104 #: ./src/pyams_content/shared/common/zmi/templates/advanced-search.pt:188 -#: ./src/pyams_content/shared/imagemap/zmi/container.py:118 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:121 #: ./src/pyams_content/interfaces/__init__.py:54 msgid "Title" msgstr "" @@ -151,7 +151,7 @@ #: ./src/pyams_content/component/gallery/zmi/gallery.py:239 #: ./src/pyams_content/shared/zmi/sites.py:124 -#: ./src/pyams_content/shared/imagemap/zmi/container.py:149 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:171 msgid "No provided object_name argument!" msgstr "" @@ -551,6 +551,7 @@ msgstr "" #: ./src/pyams_content/component/links/zmi/container.py:173 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:133 #: ./src/pyams_content/shared/imagemap/interfaces/__init__.py:46 msgid "Link target" msgstr "" @@ -1773,28 +1774,28 @@ msgid "Image map « {title} »" msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:53 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:56 msgid "Image areas" msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:64 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:67 #: ./src/pyams_content/shared/imagemap/interfaces/__init__.py:63 msgid "Image map areas" msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:108 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:111 msgid "No currently defined image." msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:110 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:113 msgid "No currently defined area." msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:160 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:182 msgid "Given area name doesn't exist!" msgstr "" -#: ./src/pyams_content/shared/imagemap/zmi/container.py:156 +#: ./src/pyams_content/shared/imagemap/zmi/container.py:178 msgid "Bad query object_name parameter value!" msgstr "" @@ -1810,6 +1811,10 @@ msgid "Edit image map properties" msgstr "" +#: ./src/pyams_content/shared/imagemap/zmi/templates/summary.pt:3 +msgid "Image areas preview" +msgstr "" + #: ./src/pyams_content/shared/imagemap/interfaces/__init__.py:32 msgid "Image map" msgstr "" diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/shared/imagemap/zmi/container.py --- a/src/pyams_content/shared/imagemap/zmi/container.py Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/shared/imagemap/zmi/container.py Wed Jan 25 11:26:00 2017 +0100 @@ -14,8 +14,10 @@ # import standard library +import html # import interfaces +from pyams_content.component.links.interfaces import ILinkContainer, IInternalLink from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION from pyams_content.shared.imagemap.interfaces import IWfImageMap from pyams_i18n.interfaces import II18n @@ -31,6 +33,7 @@ from pyams_content.shared.common.zmi import WfModifiedContentColumnMixin from pyams_form.security import ProtectedFormObjectMixin from pyams_pagelet.pagelet import pagelet_config +from pyams_sequence.utility import get_sequence_dict from pyams_skin.table import BaseTable, I18nColumn, TrashColumn from pyams_skin.viewlet.menu import MenuItem from pyams_template.template import template_config @@ -123,6 +126,25 @@ return II18n(obj).query_attribute('title', request=self.request) +@adapter_config(name='target', context=(IWfImageMap, IPyAMSLayer, ImagemapAreasTable), provides=IColumn) +class ImagemapAreasContainerTargetColumn(I18nColumn, GetAttrColumn): + """Image map areas container target URL column""" + + _header = _("Link target") + + weight = 20 + + def getValue(self, obj): + link = ILinkContainer(self.context).get(obj.link) + if link is None: + return '--' + if IInternalLink.providedBy(link): + mapping = get_sequence_dict(link.get_target()) + return mapping['text'] + else: + return html.escape(link.get_url(self.request)) + + @adapter_config(name='trash', context=(IWfImageMap, IPyAMSLayer, ImagemapAreasTable), provides=IColumn) class ImagemapAreasContainerTrashColumn(ProtectedFormObjectMixin, TrashColumn): """Image map areas container trash column""" diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/shared/imagemap/zmi/templates/summary.pt --- a/src/pyams_content/shared/imagemap/zmi/templates/summary.pt Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/shared/imagemap/zmi/templates/summary.pt Wed Jan 25 11:26:00 2017 +0100 @@ -1,12 +1,20 @@ -
- - - - +
+
+ Image areas preview +
+ + + + +
+
diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/skin/resources/js/jquery-canvasAreaDraw.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/skin/resources/js/jquery-canvasAreaDraw.min.js Wed Jan 25 11:26:00 2017 +0100 @@ -0,0 +1,1 @@ +(function(b){b.fn.canvasAreaDraw=function(d){this.each(function(e,f){c.apply(f,[e,f,d])})};var c=function(i,o,h){var u,g,x;var p,j,s,q;var l,t,m,r,d,z,y,w,f;var e;var k=false;x=b.extend({imageUrl:b(this).attr("data-image-url")},h);var n=b(o).val().replace(/[^0-9\,]/ig,"");if(n.length){u=n.split(",").map(function(v){return parseInt(v,10)})}else{u=[]}p=b('');j=b("");s=j[0].getContext("2d");q=new Image();z=function(){j.attr("height",q.height).attr("width",q.width);l()};b(q).load(z);q.src=x.imageUrl;if(q.loaded){z()}j.css({background:"url("+q.src+")"});if(o.type!=="hidden"){b(o).after("
")}b(o).after(j,"
",p);y=function(){u=[];l()};r=function(v){if(!v.offsetX){v.offsetX=(v.pageX-b(v.target).offset().left);v.offsetY=(v.pageY-b(v.target).offset().top)}u[g]=Math.round(v.offsetX);u[g+1]=Math.round(v.offsetY);l()};d=function(B){if(!B.offsetX){B.offsetX=(B.pageX-b(B.target).offset().left);B.offsetY=(B.pageY-b(B.target).offset().top)}if(!k){k={x:Math.round(B.offsetX),y:Math.round(B.offsetY)}}var v={x:Math.round(B.offsetX),y:Math.round(B.offsetY)};for(var A=0;A=6){var F=getCenter();s.fillRect(F.x-4,F.y-4,8,8);A=Math.sqrt(Math.pow(v-F.x,2)+Math.pow(G-F.y,2));if(A<6){k=false;b(this).on("mousemove",d);return false}}for(var B=0;B1){D=a(v,G,u[B],u[B+1],u[B-2],u[B-1],true);if(D<6){C=B}}}u.splice(C,0,Math.round(v),Math.round(G));g=C;b(this).on("mousemove",r);l();f();return false};l=function(){s.canvas.width=s.canvas.width;f();if(u.length<2){return}s.globalCompositeOperation="destination-over";s.fillStyle="rgb(255,255,255)";s.strokeStyle="rgb(255,20,20)";s.lineWidth=1;if(u.length>=6){var A=getCenter();s.fillRect(A.x-4,A.y-4,8,8)}s.beginPath();s.moveTo(u[0],u[1]);for(var v=0;v2&&v>1){s.lineTo(u[v],u[v+1])}}s.closePath();s.fillStyle="rgba(255,0,0,0.3)";s.fill();s.stroke()};f=function(){b(o).val(u.join(","))};getCenter=function(){var E=[];for(A=0;A=Math.min(g,e)&&f.x<=Math.max(g,e)&&f.y>=Math.min(q,n)&&f.y<=Math.max(q,n))){var i=d(p,l,g,q),h=d(p,l,e,n);return i>h?h:i}else{var m=q-n,k=e-g,j=g*n-q*e;return Math.abs(m*p+k*l+j)/Math.sqrt(m*m+k*k)}}})(jQuery); \ No newline at end of file diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/skin/resources/js/jquery-imagemapster-1.2.10.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/skin/resources/js/jquery-imagemapster-1.2.10.js Wed Jan 25 11:26:00 2017 +0100 @@ -0,0 +1,4668 @@ +/* ImageMapster + Version: 1.2.10 (2/25/2013) + +Copyright 2011-2012 James Treworgy + +http://www.outsharked.com/imagemapster +https://github.com/jamietre/ImageMapster + +A jQuery plugin to enhance image maps. + +*/ + +; + +/// LICENSE (MIT License) +/// +/// Permission is hereby granted, free of charge, to any person obtaining +/// a copy of this software and associated documentation files (the +/// "Software"), to deal in the Software without restriction, including +/// without limitation the rights to use, copy, modify, merge, publish, +/// distribute, sublicense, and/or sell copies of the Software, and to +/// permit persons to whom the Software is furnished to do so, subject to +/// the following conditions: +/// +/// The above copyright notice and this permission notice shall be +/// included in all copies or substantial portions of the Software. +/// +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +/// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +/// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +/// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/// +/// January 19, 2011 + +/** @license MIT License (c) copyright B Cavalier & J Hann */ + +/** +* when +* A lightweight CommonJS Promises/A and when() implementation +* +* when is part of the cujo.js family of libraries (http://cujojs.com/) +* +* Licensed under the MIT License at: +* http://www.opensource.org/licenses/mit-license.php +* +* @version 1.2.0 +*/ + +/*lint-ignore-start*/ + +(function (define) { + define(function () { + var freeze, reduceArray, slice, undef; + + // + // Public API + // + + when.defer = defer; + when.reject = reject; + when.isPromise = isPromise; + + when.all = all; + when.some = some; + when.any = any; + + when.map = map; + when.reduce = reduce; + + when.chain = chain; + + /** Object.freeze */ + freeze = Object.freeze || function (o) { return o; }; + + /** + * Trusted Promise constructor. A Promise created from this constructor is + * a trusted when.js promise. Any other duck-typed promise is considered + * untrusted. + * + * @constructor + */ + function Promise() { } + + Promise.prototype = freeze({ + always: function (alwaysback, progback) { + return this.then(alwaysback, alwaysback, progback); + }, + + otherwise: function (errback) { + return this.then(undef, errback); + } + }); + + /** + * Create an already-resolved promise for the supplied value + * @private + * + * @param value anything + * @return {Promise} + */ + function resolved(value) { + + var p = new Promise(); + + p.then = function (callback) { + var nextValue; + try { + if (callback) nextValue = callback(value); + return promise(nextValue === undef ? value : nextValue); + } catch (e) { + return rejected(e); + } + }; + + return freeze(p); + } + + /** + * Create an already-rejected {@link Promise} with the supplied + * rejection reason. + * @private + * + * @param reason rejection reason + * @return {Promise} + */ + function rejected(reason) { + + var p = new Promise(); + + p.then = function (callback, errback) { + var nextValue; + try { + if (errback) { + nextValue = errback(reason); + return promise(nextValue === undef ? reason : nextValue) + } + + return rejected(reason); + + } catch (e) { + return rejected(e); + } + }; + + return freeze(p); + } + + /** + * Returns a rejected promise for the supplied promiseOrValue. If + * promiseOrValue is a value, it will be the rejection value of the + * returned promise. If promiseOrValue is a promise, its + * completion value will be the rejected value of the returned promise + * + * @param promiseOrValue {*} the rejected value of the returned {@link Promise} + * + * @return {Promise} rejected {@link Promise} + */ + function reject(promiseOrValue) { + return when(promiseOrValue, function (value) { + return rejected(value); + }); + } + + /** + * Creates a new, CommonJS compliant, Deferred with fully isolated + * resolver and promise parts, either or both of which may be given out + * safely to consumers. + * The Deferred itself has the full API: resolve, reject, progress, and + * then. The resolver has resolve, reject, and progress. The promise + * only has then. + * + * @memberOf when + * @function + * + * @returns {Deferred} + */ + function defer() { + var deferred, promise, listeners, progressHandlers, _then, _progress, complete; + + listeners = []; + progressHandlers = []; + + /** + * Pre-resolution then() that adds the supplied callback, errback, and progback + * functions to the registered listeners + * + * @private + * + * @param [callback] {Function} resolution handler + * @param [errback] {Function} rejection handler + * @param [progback] {Function} progress handler + * + * @throws {Error} if any argument is not null, undefined, or a Function + */ + _then = function unresolvedThen(callback, errback, progback) { + var deferred = defer(); + + listeners.push(function (promise) { + promise.then(callback, errback) + .then(deferred.resolve, deferred.reject, deferred.progress); + }); + + progback && progressHandlers.push(progback); + + return deferred.promise; + }; + + /** + * Registers a handler for this {@link Deferred}'s {@link Promise}. Even though all arguments + * are optional, each argument that *is* supplied must be null, undefined, or a Function. + * Any other value will cause an Error to be thrown. + * + * @memberOf Promise + * + * @param [callback] {Function} resolution handler + * @param [errback] {Function} rejection handler + * @param [progback] {Function} progress handler + * + * @throws {Error} if any argument is not null, undefined, or a Function + */ + function then(callback, errback, progback) { + return _then(callback, errback, progback); + } + + /** + * Resolves this {@link Deferred}'s {@link Promise} with val as the + * resolution value. + * + * @memberOf Resolver + * + * @param val anything + */ + function resolve(val) { + complete(resolved(val)); + } + + /** + * Rejects this {@link Deferred}'s {@link Promise} with err as the + * reason. + * + * @memberOf Resolver + * + * @param err anything + */ + function reject(err) { + complete(rejected(err)); + } + + /** + * @private + * @param update + */ + _progress = function (update) { + var progress, i = 0; + while (progress = progressHandlers[i++]) progress(update); + }; + + /** + * Emits a progress update to all progress observers registered with + * this {@link Deferred}'s {@link Promise} + * + * @memberOf Resolver + * + * @param update anything + */ + function progress(update) { + _progress(update); + } + + /** + * Transition from pre-resolution state to post-resolution state, notifying + * all listeners of the resolution or rejection + * + * @private + * + * @param completed {Promise} the completed value of this deferred + */ + complete = function (completed) { + var listener, i = 0; + + // Replace _then with one that directly notifies with the result. + _then = completed.then; + + // Replace complete so that this Deferred can only be completed + // once. Also Replace _progress, so that subsequent attempts to issue + // progress throw. + complete = _progress = function alreadyCompleted() { + // TODO: Consider silently returning here so that parties who + // have a reference to the resolver cannot tell that the promise + // has been resolved using try/catch + throw new Error("already completed"); + }; + + // Free progressHandlers array since we'll never issue progress events + // for this promise again now that it's completed + progressHandlers = undef; + + // Notify listeners + // Traverse all listeners registered directly with this Deferred + + while (listener = listeners[i++]) { + listener(completed); + } + + listeners = []; + }; + + /** + * The full Deferred object, with both {@link Promise} and {@link Resolver} + * parts + * @class Deferred + * @name Deferred + */ + deferred = {}; + + // Promise and Resolver parts + // Freeze Promise and Resolver APIs + + promise = new Promise(); + promise.then = deferred.then = then; + + /** + * The {@link Promise} for this {@link Deferred} + * @memberOf Deferred + * @name promise + * @type {Promise} + */ + deferred.promise = freeze(promise); + + /** + * The {@link Resolver} for this {@link Deferred} + * @memberOf Deferred + * @name resolver + * @class Resolver + */ + deferred.resolver = freeze({ + resolve: (deferred.resolve = resolve), + reject: (deferred.reject = reject), + progress: (deferred.progress = progress) + }); + + return deferred; + } + + /** + * Determines if promiseOrValue is a promise or not. Uses the feature + * test from http://wiki.commonjs.org/wiki/Promises/A to determine if + * promiseOrValue is a promise. + * + * @param promiseOrValue anything + * + * @returns {Boolean} true if promiseOrValue is a {@link Promise} + */ + function isPromise(promiseOrValue) { + return promiseOrValue && typeof promiseOrValue.then === 'function'; + } + + /** + * Register an observer for a promise or immediate value. + * + * @function + * @name when + * @namespace + * + * @param promiseOrValue anything + * @param {Function} [callback] callback to be called when promiseOrValue is + * successfully resolved. If promiseOrValue is an immediate value, callback + * will be invoked immediately. + * @param {Function} [errback] callback to be called when promiseOrValue is + * rejected. + * @param {Function} [progressHandler] callback to be called when progress updates + * are issued for promiseOrValue. + * + * @returns {Promise} a new {@link Promise} that will complete with the return + * value of callback or errback or the completion value of promiseOrValue if + * callback and/or errback is not supplied. + */ + function when(promiseOrValue, callback, errback, progressHandler) { + // Get a promise for the input promiseOrValue + // See promise() + var trustedPromise = promise(promiseOrValue); + + // Register promise handlers + return trustedPromise.then(callback, errback, progressHandler); + } + + /** + * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if + * promiseOrValue is a foreign promise, or a new, already-resolved {@link Promise} + * whose resolution value is promiseOrValue if promiseOrValue is an immediate value. + * + * Note that this function is not safe to export since it will return its + * input when promiseOrValue is a {@link Promise} + * + * @private + * + * @param promiseOrValue anything + * + * @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} + * returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} + * whose resolution value is: + * * the resolution value of promiseOrValue if it's a foreign promise, or + * * promiseOrValue if it's a value + */ + function promise(promiseOrValue) { + var promise, deferred; + + if (promiseOrValue instanceof Promise) { + // It's a when.js promise, so we trust it + promise = promiseOrValue; + + } else { + // It's not a when.js promise. Check to see if it's a foreign promise + // or a value. + + deferred = defer(); + if (isPromise(promiseOrValue)) { + // It's a compliant promise, but we don't know where it came from, + // so we don't trust its implementation entirely. Introduce a trusted + // middleman when.js promise + + // IMPORTANT: This is the only place when.js should ever call .then() on + // an untrusted promise. + promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress); + promise = deferred.promise; + + } else { + // It's a value, not a promise. Create an already-resolved promise + // for it. + deferred.resolve(promiseOrValue); + promise = deferred.promise; + } + } + + return promise; + } + + /** + * Return a promise that will resolve when howMany of the supplied promisesOrValues + * have resolved. The resolution value of the returned promise will be an array of + * length howMany containing the resolutions values of the triggering promisesOrValues. + * + * @memberOf when + * + * @param promisesOrValues {Array} array of anything, may contain a mix + * of {@link Promise}s and values + * @param howMany + * @param [callback] + * @param [errback] + * @param [progressHandler] + * + * @returns {Promise} + */ + function some(promisesOrValues, howMany, callback, errback, progressHandler) { + + checkCallbacks(2, arguments); + + return when(promisesOrValues, function (promisesOrValues) { + + var toResolve, results, ret, deferred, resolver, rejecter, handleProgress, len, i; + + len = promisesOrValues.length >>> 0; + + toResolve = Math.max(0, Math.min(howMany, len)); + results = []; + deferred = defer(); + ret = when(deferred, callback, errback, progressHandler); + + // Wrapper so that resolver can be replaced + function resolve(val) { + resolver(val); + } + + // Wrapper so that rejecter can be replaced + function reject(err) { + rejecter(err); + } + + // Wrapper so that progress can be replaced + function progress(update) { + handleProgress(update); + } + + function complete() { + resolver = rejecter = handleProgress = noop; + } + + // No items in the input, resolve immediately + if (!toResolve) { + deferred.resolve(results); + + } else { + // Resolver for promises. Captures the value and resolves + // the returned promise when toResolve reaches zero. + // Overwrites resolver var with a noop once promise has + // be resolved to cover case where n < promises.length + resolver = function (val) { + // This orders the values based on promise resolution order + // Another strategy would be to use the original position of + // the corresponding promise. + results.push(val); + + if (! --toResolve) { + complete(); + deferred.resolve(results); + } + }; + + // Rejecter for promises. Rejects returned promise + // immediately, and overwrites rejecter var with a noop + // once promise to cover case where n < promises.length. + // TODO: Consider rejecting only when N (or promises.length - N?) + // promises have been rejected instead of only one? + rejecter = function (err) { + complete(); + deferred.reject(err); + }; + + handleProgress = deferred.progress; + + // TODO: Replace while with forEach + for (i = 0; i < len; ++i) { + if (i in promisesOrValues) { + when(promisesOrValues[i], resolve, reject, progress); + } + } + } + + return ret; + }); + } + + /** + * Return a promise that will resolve only once all the supplied promisesOrValues + * have resolved. The resolution value of the returned promise will be an array + * containing the resolution values of each of the promisesOrValues. + * + * @memberOf when + * + * @param promisesOrValues {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param [callback] {Function} + * @param [errback] {Function} + * @param [progressHandler] {Function} + * + * @returns {Promise} + */ + function all(promisesOrValues, callback, errback, progressHandler) { + + checkCallbacks(1, arguments); + + return when(promisesOrValues, function (promisesOrValues) { + return _reduce(promisesOrValues, reduceIntoArray, []); + }).then(callback, errback, progressHandler); + } + + function reduceIntoArray(current, val, i) { + current[i] = val; + return current; + } + + /** + * Return a promise that will resolve when any one of the supplied promisesOrValues + * has resolved. The resolution value of the returned promise will be the resolution + * value of the triggering promiseOrValue. + * + * @memberOf when + * + * @param promisesOrValues {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param [callback] {Function} + * @param [errback] {Function} + * @param [progressHandler] {Function} + * + * @returns {Promise} + */ + function any(promisesOrValues, callback, errback, progressHandler) { + + function unwrapSingleResult(val) { + return callback ? callback(val[0]) : val[0]; + } + + return some(promisesOrValues, 1, unwrapSingleResult, errback, progressHandler); + } + + /** + * Traditional map function, similar to `Array.prototype.map()`, but allows + * input to contain {@link Promise}s and/or values, and mapFunc may return + * either a value or a {@link Promise} + * + * @memberOf when + * + * @param promise {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param mapFunc {Function} mapping function mapFunc(value) which may return + * either a {@link Promise} or value + * + * @returns {Promise} a {@link Promise} that will resolve to an array containing + * the mapped output values. + */ + function map(promise, mapFunc) { + return when(promise, function (array) { + return _map(array, mapFunc); + }); + } + + /** + * Private map helper to map an array of promises + * @private + * + * @param promisesOrValues {Array} + * @param mapFunc {Function} + * @return {Promise} + */ + function _map(promisesOrValues, mapFunc) { + + var results, len, i; + + // Since we know the resulting length, we can preallocate the results + // array to avoid array expansions. + len = promisesOrValues.length >>> 0; + results = new Array(len); + + // Since mapFunc may be async, get all invocations of it into flight + // asap, and then use reduce() to collect all the results + for (i = 0; i < len; i++) { + if (i in promisesOrValues) + results[i] = when(promisesOrValues[i], mapFunc); + } + + // Could use all() here, but that would result in another array + // being allocated, i.e. map() would end up allocating 2 arrays + // of size len instead of just 1. Since all() uses reduce() + // anyway, avoid the additional allocation by calling reduce + // directly. + return _reduce(results, reduceIntoArray, results); + } + + /** + * Traditional reduce function, similar to `Array.prototype.reduce()`, but + * input may contain {@link Promise}s and/or values, and reduceFunc + * may return either a value or a {@link Promise}, *and* initialValue may + * be a {@link Promise} for the starting value. + * + * @memberOf when + * + * @param promise {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values. May also be a {@link Promise} for + * an array. + * @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total), + * where total is the total number of items being reduced, and will be the same + * in each call to reduceFunc. + * @param initialValue starting value, or a {@link Promise} for the starting value + * + * @returns {Promise} that will resolve to the final reduced value + */ + function reduce(promise, reduceFunc, initialValue) { + var args = slice.call(arguments, 1); + return when(promise, function (array) { + return _reduce.apply(undef, [array].concat(args)); + }); + } + + /** + * Private reduce to reduce an array of promises + * @private + * + * @param promisesOrValues {Array} + * @param reduceFunc {Function} + * @param initialValue {*} + * @return {Promise} + */ + function _reduce(promisesOrValues, reduceFunc, initialValue) { + + var total, args; + + total = promisesOrValues.length; + + // Skip promisesOrValues, since it will be used as 'this' in the call + // to the actual reduce engine below. + + // Wrap the supplied reduceFunc with one that handles promises and then + // delegates to the supplied. + + args = [ + function (current, val, i) { + return when(current, function (c) { + return when(val, function (value) { + return reduceFunc(c, value, i, total); + }); + }); + } + ]; + + if (arguments.length > 2) args.push(initialValue); + + return reduceArray.apply(promisesOrValues, args); + } + + /** + * Ensure that resolution of promiseOrValue will complete resolver with the completion + * value of promiseOrValue, or instead with resolveValue if it is provided. + * + * @memberOf when + * + * @param promiseOrValue + * @param resolver {Resolver} + * @param [resolveValue] anything + * + * @returns {Promise} + */ + function chain(promiseOrValue, resolver, resolveValue) { + var useResolveValue = arguments.length > 2; + + return when(promiseOrValue, + function (val) { + if (useResolveValue) val = resolveValue; + resolver.resolve(val); + return val; + }, + function (e) { + resolver.reject(e); + return rejected(e); + }, + resolver.progress + ); + } + + // + // Utility functions + // + + /** + * Helper that checks arrayOfCallbacks to ensure that each element is either + * a function, or null or undefined. + * + * @private + * + * @param arrayOfCallbacks {Array} array to check + * @throws {Error} if any element of arrayOfCallbacks is something other than + * a Functions, null, or undefined. + */ + function checkCallbacks(start, arrayOfCallbacks) { + var arg, i = arrayOfCallbacks.length; + while (i > start) { + arg = arrayOfCallbacks[--i]; + if (arg != null && typeof arg != 'function') throw new Error('callback is not a function'); + } + } + + /** + * No-Op function used in method replacement + * @private + */ + function noop() { } + + slice = [].slice; + + // ES5 reduce implementation if native not available + // See: http://es5.github.com/#x15.4.4.21 as there are many + // specifics and edge cases. + reduceArray = [].reduce || + function (reduceFunc /*, initialValue */) { + // ES5 dictates that reduce.length === 1 + + // This implementation deviates from ES5 spec in the following ways: + // 1. It does not check if reduceFunc is a Callable + + var arr, args, reduced, len, i; + + i = 0; + arr = Object(this); + len = arr.length >>> 0; + args = arguments; + + // If no initialValue, use first item of array (we know length !== 0 here) + // and adjust i to start at second item + if (args.length <= 1) { + // Skip to the first real element in the array + for (; ; ) { + if (i in arr) { + reduced = arr[i++]; + break; + } + + // If we reached the end of the array without finding any real + // elements, it's a TypeError + if (++i >= len) { + throw new TypeError(); + } + } + } else { + // If initialValue provided, use it + reduced = args[1]; + } + + // Do the actual reduce + for (; i < len; ++i) { + // Skip holes + if (i in arr) + reduced = reduceFunc(reduced, arr[i], i, arr); + } + + return reduced; + }; + + return when; + }); +})(typeof define == 'function' + ? define + : function (factory) { + typeof module != 'undefined' + ? (module.exports = factory()) + : (jQuery.mapster_when = factory()); + } +// Boilerplate for AMD, Node, and browser global +); +/*lint-ignore-end*/ +/* ImageMapster core */ + +/*jslint laxbreak: true, evil: true, unparam: true */ + +/*global jQuery: true, Zepto: true */ + + +(function ($) { + // all public functions in $.mapster.impl are methods + $.fn.mapster = function (method) { + var m = $.mapster.impl; + if ($.isFunction(m[method])) { + return m[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return m.bind.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.mapster'); + } + }; + + $.mapster = { + version: "1.2.10", + render_defaults: { + isSelectable: true, + isDeselectable: true, + fade: false, + fadeDuration: 150, + fill: true, + fillColor: '000000', + fillColorMask: 'FFFFFF', + fillOpacity: 0.7, + highlight: true, + stroke: false, + strokeColor: 'ff0000', + strokeOpacity: 1, + strokeWidth: 1, + includeKeys: '', + altImage: null, + altImageId: null, // used internally + altImages: {} + }, + defaults: { + clickNavigate: false, + wrapClass: null, + wrapCss: null, + onGetList: null, + sortList: false, + listenToList: false, + mapKey: '', + mapValue: '', + singleSelect: false, + listKey: 'value', + listSelectedAttribute: 'selected', + listSelectedClass: null, + onClick: null, + onMouseover: null, + onMouseout: null, + mouseoutDelay: 0, + onStateChange: null, + boundList: null, + onConfigured: null, + configTimeout: 30000, + noHrefIsMask: true, + scaleMap: true, + safeLoad: false, + areas: [] + }, + shared_defaults: { + render_highlight: { fade: true }, + render_select: { fade: false }, + staticState: null, + selected: null + }, + area_defaults: + { + includeKeys: '', + isMask: false + }, + canvas_style: { + position: 'absolute', + left: 0, + top: 0, + padding: 0, + border: 0 + }, + hasCanvas: null, + isTouch: null, + map_cache: [], + hooks: {}, + addHook: function(name,callback) { + this.hooks[name]=(this.hooks[name]||[]).push(callback); + }, + callHooks: function(name,context) { + $.each(this.hooks[name]||[],function(i,e) { + e.apply(context); + }); + }, + utils: { + when: $.mapster_when, + defer: $.mapster_when.defer, + + // extends the constructor, returns a new object prototype. Does not refer to the + // original constructor so is protected if the original object is altered. This way you + // can "extend" an object by replacing it with its subclass. + subclass: function(BaseClass, constr) { + var Subclass=function() { + var me=this, + args=Array.prototype.slice.call(arguments,0); + me.base = BaseClass.prototype; + me.base.init = function() { + BaseClass.prototype.constructor.apply(me,args); + }; + constr.apply(me,args); + }; + Subclass.prototype = new BaseClass(); + Subclass.prototype.constructor=Subclass; + return Subclass; + }, + asArray: function (obj) { + return obj.constructor === Array ? + obj : this.split(obj); + }, + // clean split: no padding or empty elements + split: function (text,cb) { + var i,el, arr = text.split(','); + for (i = 0; i < arr.length; i++) { + el = $.trim(arr[i]); + if (el==='') { + arr.splice(i,1); + } else { + arr[i] = cb ? cb(el):el; + } + } + return arr; + }, + // similar to $.extend but does not add properties (only updates), unless the + // first argument is an empty object, then all properties will be copied + updateProps: function (_target, _template) { + var onlyProps, + target = _target || {}, + template = $.isEmptyObject(target) ? _template : _target; + + //if (template) { + onlyProps = []; + $.each(template, function (prop) { + onlyProps.push(prop); + }); + //} + + $.each(Array.prototype.slice.call(arguments, 1), function (i, src) { + $.each(src || {}, function (prop) { + if (!onlyProps || $.inArray(prop, onlyProps) >= 0) { + var p = src[prop]; + + if ($.isPlainObject(p)) { + // not recursive - only copies 1 level of subobjects, and always merges + target[prop] = $.extend(target[prop] || {}, p); + } else if (p && p.constructor === Array) { + target[prop] = p.slice(0); + } else if (typeof p !== 'undefined') { + target[prop] = src[prop]; + } + + } + }); + }); + return target; + }, + isElement: function (o) { + return (typeof HTMLElement === "object" ? o instanceof HTMLElement : + o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === "string"); + }, + // finds element of array or object with a property "prop" having value "val" + // if prop is not defined, then just looks for property with value "val" + indexOfProp: function (obj, prop, val) { + var result = obj.constructor === Array ? -1 : null; + $.each(obj, function (i, e) { + if (e && (prop ? e[prop] : e) === val) { + result = i; + return false; + } + }); + return result; + }, + // returns "obj" if true or false, or "def" if not true/false + boolOrDefault: function (obj, def) { + return this.isBool(obj) ? + obj : def || false; + }, + isBool: function (obj) { + return typeof obj === "boolean"; + }, + isUndef: function(obj) { + return typeof obj === "undefined"; + }, + // evaluates "obj", if function, calls it with args + // (todo - update this to handle variable lenght/more than one arg) + ifFunction: function (obj, that, args) { + if ($.isFunction(obj)) { + obj.call(that, args); + } + }, + size: function(image, raw) { + var u=$.mapster.utils; + return { + width: raw ? (image.width || image.naturalWidth) : u.imgWidth(image,true) , + height: raw ? (image.height || image.naturalHeight) : u.imgHeight(image,true), + complete: function() { return !!this.height && !!this.width;} + }; + }, + + + /** + * Set the opacity of the element. This is an IE<8 specific function for handling VML. + * When using VML we must override the "setOpacity" utility function (monkey patch ourselves). + * jQuery does not deal with opacity correctly for VML elements. This deals with that. + * + * @param {Element} el The DOM element + * @param {double} opacity A value between 0 and 1 inclusive. + */ + + setOpacity: function (el, opacity) { + if ($.mapster.hasCanvas()) { + el.style.opacity = opacity; + } else { + $(el).each(function(i,e) { + if (typeof e.opacity !=='undefined') { + e.opacity=opacity; + } else { + $(e).css("opacity",opacity); + } + }); + } + }, + + + // fade "el" from opacity "op" to "endOp" over a period of time "duration" + + fader: (function () { + var elements = {}, + lastKey = 0, + fade_func = function (el, op, endOp, duration) { + var index, + cbIntervals = duration/15, + obj, u = $.mapster.utils; + + if (typeof el === 'number') { + obj = elements[el]; + if (!obj) { + return; + } + } else { + index = u.indexOfProp(elements, null, el); + if (index) { + delete elements[index]; + } + elements[++lastKey] = obj = el; + el = lastKey; + } + + endOp = endOp || 1; + + op = (op + (endOp / cbIntervals) > endOp - 0.01) ? endOp : op + (endOp / cbIntervals); + + u.setOpacity(obj, op); + if (op < endOp) { + setTimeout(function () { + fade_func(el, op, endOp, duration); + }, 15); + } + }; + return fade_func; + } ()) + }, + getBoundList: function (opts, key_list) { + if (!opts.boundList) { + return null; + } + var index, key, result = $(), list = $.mapster.utils.split(key_list); + opts.boundList.each(function (i,e) { + for (index = 0; index < list.length; index++) { + key = list[index]; + if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { + result = result.add(e); + } + } + }); + return result; + }, + // Causes changes to the bound list based on the user action (select or deselect) + // area: the jQuery area object + // returns the matching elements from the bound list for the first area passed (normally only one should be passed, but + // a list can be passed + setBoundListProperties: function (opts, target, selected) { + target.each(function (i,e) { + if (opts.listSelectedClass) { + if (selected) { + $(e).addClass(opts.listSelectedClass); + } else { + $(e).removeClass(opts.listSelectedClass); + } + } + if (opts.listSelectedAttribute) { + $(e).attr(opts.listSelectedAttribute, selected); + } + }); + }, + getMapDataIndex: function (obj) { + var img, id; + switch (obj.tagName && obj.tagName.toLowerCase()) { + case 'area': + id = $(obj).parent().attr('name'); + img = $("img[usemap='#" + id + "']")[0]; + break; + case 'img': + img = obj; + break; + } + return img ? + this.utils.indexOfProp(this.map_cache, 'image', img) : -1; + }, + getMapData: function (obj) { + var index = this.getMapDataIndex(obj.length ? obj[0]:obj); + if (index >= 0) { + return index >= 0 ? this.map_cache[index] : null; + } + }, + /** + * Queue a command to be run after the active async operation has finished + * @param {MapData} map_data The target MapData object + * @param {jQuery} that jQuery object on which the command was invoked + * @param {string} command the ImageMapster method name + * @param {object[]} args arguments passed to the method + * @return {bool} true if the command was queued, false if not (e.g. there was no need to) + */ + queueCommand: function (map_data, that, command, args) { + if (!map_data) { + return false; + } + if (!map_data.complete || map_data.currentAction) { + map_data.commands.push( + { + that: that, + command: command, + args: args + }); + return true; + } + return false; + }, + unload: function () { + this.impl.unload(); + this.utils = null; + this.impl = null; + $.fn.mapster = null; + $.mapster = null; + $('*').unbind(); + } + }; + + // Config for object prototypes + // first: use only first object (for things that should not apply to lists) + /// calls back one of two fuinctions, depending on whether an area was obtained. + // opts: { + // name: 'method name', + // key: 'key, + // args: 'args' + // + //} + // name: name of method (required) + // args: arguments to re-call with + // Iterates through all the objects passed, and determines whether it's an area or an image, and calls the appropriate + // callback for each. If anything is returned from that callback, the process is stopped and that data return. Otherwise, + // the object itself is returned. + + var m = $.mapster, + u = m.utils, + ap = Array.prototype; + + + // jQuery's width() and height() are broken on IE9 in some situations. This tries everything. + $.each(["width","height"],function(i,e) { + var capProp = e.substr(0,1).toUpperCase() + e.substr(1); + // when jqwidth parm is passed, it also checks the jQuery width()/height() property + // the issue is that jQUery width() can report a valid size before the image is loaded in some browsers + // without it, we can read zero even when image is loaded in other browsers if its not visible + // we must still check because stuff like adblock can temporarily block it + // what a goddamn headache + u["img"+capProp]=function(img,jqwidth) { + return (jqwidth ? $(img)[e]() : 0) || + img[e] || img["natural"+capProp] || img["client"+capProp] || img["offset"+capProp]; + }; + + }); + + /** + * The Method object encapsulates the process of testing an ImageMapster method to see if it's being + * invoked on an image, or an area; then queues the command if the MapData is in an active state. + * + * @param {[jQuery]} that The target of the invocation + * @param {[function]} func_map The callback if the target is an imagemap + * @param {[function]} func_area The callback if the target is an area + * @param {[object]} opt Options: { key: a map key if passed explicitly + * name: the command name, if it can be queued, + * args: arguments to the method + * } + */ + + m.Method = function (that, func_map, func_area, opts) { + var me = this; + me.name = opts.name; + me.output = that; + me.input = that; + me.first = opts.first || false; + me.args = opts.args ? ap.slice.call(opts.args, 0) : []; + me.key = opts.key; + me.func_map = func_map; + me.func_area = func_area; + //$.extend(me, opts); + me.name = opts.name; + me.allowAsync = opts.allowAsync || false; + }; + m.Method.prototype = { + constructor: m.Method, + go: function () { + var i, data, ar, len, result, src = this.input, + area_list = [], + me = this; + + len = src.length; + for (i = 0; i < len; i++) { + data = $.mapster.getMapData(src[i]); + if (data) { + if (!me.allowAsync && m.queueCommand(data, me.input, me.name, me.args)) { + if (this.first) { + result = ''; + } + continue; + } + + ar = data.getData(src[i].nodeName === 'AREA' ? src[i] : this.key); + if (ar) { + if ($.inArray(ar, area_list) < 0) { + area_list.push(ar); + } + } else { + result = this.func_map.apply(data, me.args); + } + if (this.first || typeof result !== 'undefined') { + break; + } + } + } + // if there were areas, call the area function for each unique group + $(area_list).each(function (i,e) { + result = me.func_area.apply(e, me.args); + }); + + if (typeof result !== 'undefined') { + return result; + } else { + return this.output; + } + } + }; + + $.mapster.impl = (function () { + var me = {}, + addMap= function (map_data) { + return m.map_cache.push(map_data) - 1; + }, + removeMap = function (map_data) { + m.map_cache.splice(map_data.index, 1); + for (var i = m.map_cache.length - 1; i >= this.index; i--) { + m.map_cache[i].index--; + } + }; + + + /** + * Test whether the browser supports VML. Credit: google. + * http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + * + * @return {bool} true if vml is supported, false if not + */ + + function hasVml() { + var a = $('
').appendTo('body'); + a.html(''); + + var b = a[0].firstChild; + b.style.behavior = "url(#default#VML)"; + var has = b ? typeof b.adj === "object" : true; + a.remove(); + return has; + } + + /** + * Return a reference to the IE namespaces object, if available, or an empty object otherwise + * @return {obkect} The document.namespaces object. + */ + function namespaces() { + return typeof(document.namespaces)==='object' ? + document.namespaces : + null; + } + + /** + * Test for the presence of HTML5 Canvas support. This also checks to see if excanvas.js has been + * loaded and is faking it; if so, we assume that canvas is not supported. + * + * @return {bool} true if HTML5 canvas support, false if not + */ + + function hasCanvas() { + var d = namespaces(); + // when g_vml_ is present, then we can be sure excanvas is active, meaning there's not a real canvas. + + return d && d.g_vml_ ? + false : + $('')[0].getContext ? + true : + false; + } + + /** + * Merge new area data into existing area options on a MapData object. Used for rebinding. + * + * @param {[MapData]} map_data The MapData object + * @param {[object[]]} areas areas array to merge + */ + + function merge_areas(map_data, areas) { + var ar, index, + map_areas = map_data.options.areas; + + if (areas) { + $.each(areas, function (i, e) { + + // Issue #68 - ignore invalid data in areas array + + if (!e || !e.key) { + return; + } + + index = u.indexOfProp(map_areas, "key", e.key); + + if (index >= 0) { + $.extend(map_areas[index], e); + } + else { + map_areas.push(e); + } + ar = map_data.getDataForKey(e.key); + if (ar) { + $.extend(ar.options, e); + } + }); + } + } + function merge_options(map_data, options) { + var temp_opts = u.updateProps({}, options); + delete temp_opts.areas; + + u.updateProps(map_data.options, temp_opts); + + merge_areas(map_data, options.areas); + // refresh the area_option template + u.updateProps(map_data.area_options, map_data.options); + } + + // Most methods use the "Method" object which handles figuring out whether it's an image or area called and + // parsing key parameters. The constructor wants: + // this, the jQuery object + // a function that is called when an image was passed (with a this context of the MapData) + // a function that is called when an area was passed (with a this context of the AreaData) + // options: first = true means only the first member of a jQuery object is handled + // key = the key parameters passed + // defaultReturn: a value to return other than the jQuery object (if its not chainable) + // args: the arguments + // Returns a comma-separated list of user-selected areas. "staticState" areas are not considered selected for the purposes of this method. + + me.get = function (key) { + var md = m.getMapData(this); + if (!(md && md.complete)) { + throw("Can't access data until binding complete."); + } + + return (new m.Method(this, + function () { + // map_data return + return this.getSelected(); + }, + function () { + return this.isSelected(); + }, + { name: 'get', + args: arguments, + key: key, + first: true, + allowAsync: true, + defaultReturn: '' + } + )).go(); + }; + me.data = function (key) { + return (new m.Method(this, + null, + function () { + return this; + }, + { name: 'data', + args: arguments, + key: key + } + )).go(); + }; + + + // Set or return highlight state. + // $(img).mapster('highlight') -- return highlighted area key, or null if none + // $(area).mapster('highlight') -- highlight an area + // $(img).mapster('highlight','area_key') -- highlight an area + // $(img).mapster('highlight',false) -- remove highlight + me.highlight = function (key) { + return (new m.Method(this, + function () { + if (key === false) { + this.ensureNoHighlight(); + } else { + var id = this.highlightId; + return id >= 0 ? this.data[id].key : null; + } + }, + function () { + this.highlight(); + }, + { name: 'highlight', + args: arguments, + key: key, + first: true + } + )).go(); + }; + // Return the primary keys for an area or group key. + // $(area).mapster('key') + // includes all keys (not just primary keys) + // $(area).mapster('key',true) + // $(img).mapster('key','group-key') + + // $(img).mapster('key','group-key', true) + me.keys = function(key,all) { + var keyList=[], + md = m.getMapData(this); + + if (!(md && md.complete)) { + throw("Can't access data until binding complete."); + } + + + function addUniqueKeys(ad) { + var areas,keys=[]; + if (!all) { + keys.push(ad.key); + } else { + areas=ad.areas(); + $.each(areas,function(i,e) { + keys=keys.concat(e.keys); + }); + } + $.each(keys,function(i,e) { + if ($.inArray(e,keyList)<0) { + keyList.push(e); + } + }); + } + + if (!(md && md.complete)) { + return ''; + } + if (typeof key === 'string') { + if (all) { + addUniqueKeys(md.getDataForKey(key)); + } else { + keyList=[md.getKeysForGroup(key)]; + } + } else { + all = key; + this.each(function(i,e) { + if (e.nodeName==='AREA') { + addUniqueKeys(md.getDataForArea(e)); + } + }); + } + return keyList.join(','); + + + }; + me.select = function () { + me.set.call(this, true); + }; + me.deselect = function () { + me.set.call(this, false); + }; + + /** + * Select or unselect areas. Areas can be identified by a single string key, a comma-separated list of keys, + * or an array of strings. + * + * + * @param {boolean} selected Determines whether areas are selected or deselected + * @param {string|string[]} key A string, comma-separated string, or array of strings indicating + * the areas to select or deselect + * @param {object} options Rendering options to apply when selecting an area + */ + + me.set = function (selected, key, options) { + var lastMap, map_data, opts=options, + key_list, area_list; // array of unique areas passed + + function setSelection(ar) { + if (ar) { + switch (selected) { + case true: + ar.select(opts); break; + case false: + ar.deselect(true); break; + default: + ar.toggle(opts); break; + } + } + } + function addArea(ar) { + if (ar && $.inArray(ar, area_list) < 0) { + area_list.push(ar); + key_list+=(key_list===''?'':',')+ar.key; + } + } + // Clean up after a group that applied to the same map + function finishSetForMap(map_data) { + $.each(area_list, function (i, el) { + setSelection(el); + }); + if (!selected) { + map_data.removeSelectionFinish(); + } + if (map_data.options.boundList) { + m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), selected); + } + } + + this.filter('img,area').each(function (i,e) { + var keys; + map_data = m.getMapData(e); + + if (map_data !== lastMap) { + if (lastMap) { + finishSetForMap(lastMap); + } + + area_list = []; + key_list=''; + } + + if (map_data) { + + keys = ''; + if (e.nodeName.toUpperCase()==='IMG') { + if (!m.queueCommand(map_data, $(e), 'set', [selected, key, opts])) { + if (key instanceof Array) { + if (key.length) { + keys = key.join(","); + } + } + else { + keys = key; + } + + if (keys) { + $.each(u.split(keys), function (i,key) { + addArea(map_data.getDataForKey(key.toString())); + lastMap = map_data; + }); + } + } + } else { + opts=key; + if (!m.queueCommand(map_data, $(e), 'set', [selected, opts])) { + addArea(map_data.getDataForArea(e)); + lastMap = map_data; + } + + } + } + }); + + if (map_data) { + finishSetForMap(map_data); + } + + + return this; + }; + me.unbind = function (preserveState) { + return (new m.Method(this, + function () { + this.clearEvents(); + this.clearMapData(preserveState); + removeMap(this); + }, + null, + { name: 'unbind', + args: arguments + } + )).go(); + }; + + + // refresh options and update selection information. + me.rebind = function (options) { + return (new m.Method(this, + function () { + var me=this; + + me.complete=false; + me.configureOptions(options); + me.bindImages().then(function() { + me.buildDataset(true); + me.complete=true; + }); + //this.redrawSelections(); + }, + null, + { + name: 'rebind', + args: arguments + } + )).go(); + }; + // get options. nothing or false to get, or "true" to get effective options (versus passed options) + me.get_options = function (key, effective) { + var eff = u.isBool(key) ? key : effective; // allow 2nd parm as "effective" when no key + return (new m.Method(this, + function () { + var opts = $.extend({}, this.options); + if (eff) { + opts.render_select = u.updateProps( + {}, + m.render_defaults, + opts, + opts.render_select); + + opts.render_highlight = u.updateProps( + {}, + m.render_defaults, + opts, + opts.render_highlight); + } + return opts; + }, + function () { + return eff ? this.effectiveOptions() : this.options; + }, + { + name: 'get_options', + args: arguments, + first: true, + allowAsync: true, + key: key + } + )).go(); + }; + + // set options - pass an object with options to set, + me.set_options = function (options) { + return (new m.Method(this, + function () { + merge_options(this, options); + }, + null, + { + name: 'set_options', + args: arguments + } + )).go(); + }; + me.unload = function () { + var i; + for (i = m.map_cache.length - 1; i >= 0; i--) { + if (m.map_cache[i]) { + me.unbind.call($(m.map_cache[i].image)); + } + } + me.graphics = null; + }; + + me.snapshot = function () { + return (new m.Method(this, + function () { + $.each(this.data, function (i, e) { + e.selected = false; + }); + + this.base_canvas = this.graphics.createVisibleCanvas(this); + $(this.image).before(this.base_canvas); + }, + null, + { name: 'snapshot' } + )).go(); + }; + + // do not queue this function + + me.state = function () { + var md, result = null; + $(this).each(function (i,e) { + if (e.nodeName === 'IMG') { + md = m.getMapData(e); + if (md) { + result = md.state(); + } + return false; + } + }); + return result; + }; + + me.bind = function (options) { + + return this.each(function (i,e) { + var img, map, usemap, md; + + // save ref to this image even if we can't access it yet. commands will be queued + img = $(e); + + md = m.getMapData(e); + + // if already bound completely, do a total rebind + + if (md) { + me.unbind.apply(img); + if (!md.complete) { + // will be queued + img.bind(); + return true; + } + md = null; + } + + // ensure it's a valid image + // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr. + // So use raw getAttribute instead. + + usemap = this.getAttribute('usemap'); + map = usemap && $('map[name="' + usemap.substr(1) + '"]'); + if (!(img.is('img') && usemap && map.size() > 0)) { + return true; + } + + // sorry - your image must have border:0, things are too unpredictable otherwise. + img.css('border', 0); + + if (!md) { + md = new m.MapData(this, options); + + md.index = addMap(md); + md.map = map; + md.bindImages().then(function() { + md.initialize(); + }); + } + }); + }; + + me.init = function (useCanvas) { + var style, shapes; + + // for testing/debugging, use of canvas can be forced by initializing + // manually with "true" or "false". But generally we test for it. + + m.hasCanvas = function() { + if (!u.isBool(m.hasCanvas.value)) { + m.hasCanvas.value = u.isBool(useCanvas) ? + useCanvas : + hasCanvas(); + } + return m.hasCanvas.value; + }; + m.hasVml = function() { + if (!u.isBool(m.hasVml.value)) { + // initialize VML the first time we detect its presence. + var d = namespaces(); + + if (d && !d.v) { + d.add("v", "urn:schemas-microsoft-com:vml"); + style = document.createStyleSheet(); + shapes = ['shape', 'rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group', 'textbox']; + $.each(shapes, + function (i, el) { + style.addRule('v\\:' + el, "behavior: url(#default#VML); antialias:true"); + }); + } + m.hasVml.value = hasVml(); + } + + return m.hasVml.value; + }; + + m.isTouch = !!document.documentElement.ontouchstart; + + $.extend(m.defaults, m.render_defaults,m.shared_defaults); + $.extend(m.area_defaults, m.render_defaults,m.shared_defaults); + + }; + me.test = function (obj) { + return eval(obj); + }; + return me; + } ()); + + $.mapster.impl.init(); + + +} (jQuery)); +/* graphics.js + Graphics object handles all rendering. +*/ +(function ($) { + var p, m=$.mapster, + u=m.utils, + canvasMethods, + vmlMethods; + + /** + * Implemenation to add each area in an AreaData object to the canvas + * @param {Graphics} graphics The target graphics object + * @param {AreaData} areaData The AreaData object (a collection of area elements and metadata) + * @param {object} options Rendering options to apply when rendering this group of areas + */ + function addShapeGroupImpl(graphics, areaData, options) { + var me = graphics, + md = me.map_data, + isMask = options.isMask; + + // first get area options. Then override fade for selecting, and finally merge in the + // "select" effect options. + + $.each(areaData.areas(), function (i,e) { + options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); + me.addShape(e, options); + }); + + // it's faster just to manipulate the passed options isMask property and restore it, than to + // copy the object each time + + options.isMask=isMask; + + } + + /** + * Convert a hex value to decimal + * @param {string} hex A hexadecimal toString + * @return {int} Integer represenation of the hex string + */ + + function hex_to_decimal(hex) { + return Math.max(0, Math.min(parseInt(hex, 16), 255)); + } + function css3color(color, opacity) { + return 'rgba(' + hex_to_decimal(color.substr(0, 2)) + ',' + + hex_to_decimal(color.substr(2, 2)) + ',' + + hex_to_decimal(color.substr(4, 2)) + ',' + opacity + ')'; + } + /** + * An object associated with a particular map_data instance to manage renderin. + * @param {MapData} map_data The MapData object bound to this instance + */ + + m.Graphics = function (map_data) { + //$(window).unload($.mapster.unload); + // create graphics functions for canvas and vml browsers. usage: + // 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 + // 3) call add_shape_to for each shape or mask, 4) call render() to finish + + var me = this; + me.active = false; + me.canvas = null; + me.width = 0; + me.height = 0; + me.shapes = []; + me.masks = []; + me.map_data = map_data; + }; + + p = m.Graphics.prototype= { + constructor: m.Graphics, + + /** + * Initiate a graphics request for a canvas + * @param {Element} canvas The canvas element that is the target of this operation + * @param {string} [elementName] The name to assign to the element (VML only) + */ + + begin: function(canvas, elementName) { + var c = $(canvas); + + this.elementName = elementName; + this.canvas = canvas; + + this.width = c.width(); + this.height = c.height(); + this.shapes = []; + this.masks = []; + this.active = true; + + }, + + /** + * Add an area to be rendered to this canvas. + * @param {MapArea} mapArea The MapArea object to render + * @param {object} options An object containing any rendering options that should override the + * defaults for the area + */ + + addShape: function(mapArea, options) { + var addto = options.isMask ? this.masks : this.shapes; + addto.push({ mapArea: mapArea, options: options }); + }, + + /** + * Create a canvas that is sized and styled for the MapData object + * @param {MapData} mapData The MapData object that will receive this new canvas + * @return {Element} A canvas element + */ + + createVisibleCanvas: function (mapData) { + return $(this.createCanvasFor(mapData)) + .addClass('mapster_el') + .css(m.canvas_style)[0]; + }, + + /** + * Add a group of shapes from an AreaData object to the canvas + * + * @param {AreaData} areaData An AreaData object (a set of area elements) + * @param {string} mode The rendering mode, "select" or "highlight". This determines the target + * canvas and which default options to use. + * @param {striong} options Rendering options + */ + + addShapeGroup: function (areaData, mode,options) { + // render includeKeys first - because they could be masks + var me = this, + list, name, canvas, + map_data = this.map_data, + opts = areaData.effectiveRenderOptions(mode); + + if (options) { + $.extend(opts,options); + } + + if (mode === 'select') { + name = "static_" + areaData.areaId.toString(); + canvas = map_data.base_canvas; + } else { + canvas = map_data.overlay_canvas; + } + + me.begin(canvas, name); + + if (opts.includeKeys) { + list = u.split(opts.includeKeys); + $.each(list, function (i,e) { + var areaData = map_data.getDataForKey(e.toString()); + addShapeGroupImpl(me,areaData, areaData.effectiveRenderOptions(mode)); + }); + } + + addShapeGroupImpl(me,areaData, opts); + me.render(); + if (opts.fade) { + + // fading requires special handling for IE. We must access the fill elements directly. The fader also has to deal with + // the "opacity" attribute (not css) + + u.fader(m.hasCanvas() ? + canvas : + $(canvas).find('._fill').not('.mapster_mask'), + 0, + m.hasCanvas() ? + 1 : + opts.fillOpacity, + opts.fadeDuration); + + } + + } + + // These prototype methods are implementation dependent + }; + + function noop() {} + + + // configure remaining prototype methods for ie or canvas-supporting browser + + canvasMethods = { + renderShape: function (context, mapArea, offset) { + var i, + c = mapArea.coords(null,offset); + + switch (mapArea.shape) { + case 'rect': + context.rect(c[0], c[1], c[2] - c[0], c[3] - c[1]); + break; + case 'poly': + context.moveTo(c[0], c[1]); + + for (i = 2; i < mapArea.length; i += 2) { + context.lineTo(c[i], c[i + 1]); + } + context.lineTo(c[0], c[1]); + break; + case 'circ': + case 'circle': + context.arc(c[0], c[1], c[2], 0, Math.PI * 2, false); + break; + } + }, + addAltImage: function (context, image, mapArea, options) { + context.beginPath(); + + this.renderShape(context, mapArea); + context.closePath(); + context.clip(); + + context.globalAlpha = options.altImageOpacity || options.fillOpacity; + + context.drawImage(image, 0, 0, mapArea.owner.scaleInfo.width, mapArea.owner.scaleInfo.height); + }, + render: function () { + // firefox 6.0 context.save() seems to be broken. to work around, we have to draw the contents on one temp canvas, + // the mask on another, and merge everything. ugh. fixed in 1.2.2. unfortunately this is a lot more code for masks, + // but no other way around it that i can see. + + var maskCanvas, maskContext, + me = this, + md = me.map_data, + hasMasks = me.masks.length, + shapeCanvas = me.createCanvasFor(md), + shapeContext = shapeCanvas.getContext('2d'), + context = me.canvas.getContext('2d'); + + if (hasMasks) { + maskCanvas = me.createCanvasFor(md); + maskContext = maskCanvas.getContext('2d'); + maskContext.clearRect(0, 0, maskCanvas.width, maskCanvas.height); + + $.each(me.masks, function (i,e) { + maskContext.save(); + maskContext.beginPath(); + me.renderShape(maskContext, e.mapArea); + maskContext.closePath(); + maskContext.clip(); + maskContext.lineWidth = 0; + maskContext.fillStyle = '#000'; + maskContext.fill(); + maskContext.restore(); + }); + + } + + $.each(me.shapes, function (i,s) { + shapeContext.save(); + if (s.options.fill) { + if (s.options.altImageId) { + me.addAltImage(shapeContext, md.images[s.options.altImageId], s.mapArea, s.options); + } else { + shapeContext.beginPath(); + me.renderShape(shapeContext, s.mapArea); + shapeContext.closePath(); + //shapeContext.clip(); + shapeContext.fillStyle = css3color(s.options.fillColor, s.options.fillOpacity); + shapeContext.fill(); + } + } + shapeContext.restore(); + }); + + + // render strokes at end since masks get stroked too + + $.each(me.shapes.concat(me.masks), function (i,s) { + var offset = s.options.strokeWidth === 1 ? 0.5 : 0; + // offset applies only when stroke width is 1 and stroke would render between pixels. + + if (s.options.stroke) { + shapeContext.save(); + shapeContext.strokeStyle = css3color(s.options.strokeColor, s.options.strokeOpacity); + shapeContext.lineWidth = s.options.strokeWidth; + + shapeContext.beginPath(); + + me.renderShape(shapeContext, s.mapArea, offset); + shapeContext.closePath(); + shapeContext.stroke(); + shapeContext.restore(); + } + }); + + if (hasMasks) { + // render the new shapes against the mask + + maskContext.globalCompositeOperation = "source-out"; + maskContext.drawImage(shapeCanvas, 0, 0); + + // flatten into the main canvas + context.drawImage(maskCanvas, 0, 0); + } else { + context.drawImage(shapeCanvas, 0, 0); + } + + me.active = false; + return me.canvas; + }, + + // create a canvas mimicing dimensions of an existing element + createCanvasFor: function (md) { + return $('')[0]; + }, + clearHighlight: function () { + var c = this.map_data.overlay_canvas; + c.getContext('2d').clearRect(0, 0, c.width, c.height); + }, + // 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. + refreshSelections: function () { + var canvas_temp, map_data = this.map_data; + // draw new base canvas, then swap with the old one to avoid flickering + canvas_temp = map_data.base_canvas; + + map_data.base_canvas = this.createVisibleCanvas(map_data); + $(map_data.base_canvas).hide(); + $(canvas_temp).before(map_data.base_canvas); + + map_data.redrawSelections(); + + $(map_data.base_canvas).show(); + $(canvas_temp).remove(); + } + }; + + vmlMethods = { + + renderShape: function (mapArea, options, cssclass) { + var me = this, fill,stroke, e, t_fill, el_name, el_class, template, c = mapArea.coords(); + el_name = me.elementName ? 'name="' + me.elementName + '" ' : ''; + el_class = cssclass ? 'class="' + cssclass + '" ' : ''; + + t_fill = ''; + + + stroke = options.stroke ? + ' strokeweight=' + options.strokeWidth + ' stroked="t" strokecolor="#' + + options.strokeColor + '"' : + ' stroked="f"'; + + fill = options.fill ? + ' filled="t"' : + ' filled="f"'; + + switch (mapArea.shape) { + case 'rect': + template = '' + t_fill + ''; + break; + case 'poly': + template = '' + t_fill + ''; + break; + case 'circ': + case 'circle': + template = '' + t_fill + ''; + break; + } + e = $(template); + $(me.canvas).append(e); + + return e; + }, + render: function () { + var opts, me = this; + + $.each(this.shapes, function (i,e) { + me.renderShape(e.mapArea, e.options); + }); + + if (this.masks.length) { + $.each(this.masks, function (i,e) { + opts = u.updateProps({}, + e.options, { + fillOpacity: 1, + fillColor: e.options.fillColorMask + }); + me.renderShape(e.mapArea, opts, 'mapster_mask'); + }); + } + + this.active = false; + return this.canvas; + }, + + createCanvasFor: function (md) { + var w = md.scaleInfo.width, + h = md.scaleInfo.height; + return $('')[0]; + }, + + clearHighlight: function () { + $(this.map_data.overlay_canvas).children().remove(); + }, + // remove single or all selections + removeSelections: function (area_id) { + if (area_id >= 0) { + $(this.map_data.base_canvas).find('[name="static_' + area_id.toString() + '"]').remove(); + } + else { + $(this.map_data.base_canvas).children().remove(); + } + } + + }; + + // for all methods with two implemenatations, add a function that will automatically replace itself with the correct + // method on first invocation + + $.each(['renderShape', + 'addAltImage', + 'render', + 'createCanvasFor', + 'clearHighlight', + 'removeSelections', + 'refreshSelections'], + function(i,e) { + p[e]=(function(method) { + return function() { + p[method] = (m.hasCanvas() ? + canvasMethods[method] : + vmlMethods[method]) || noop; + + return p[method].apply(this,arguments); + }; + }(e)); + }); + + +} (jQuery)); +/* mapimage.js + the MapImage object, repesents an instance of a single bound imagemap +*/ + +(function ($) { + + var m = $.mapster, + u = m.utils, + ap=[]; + /** + * An object encapsulating all the images used by a MapData. + */ + + m.MapImages = function(owner) { + this.owner = owner; + this.clear(); + }; + + + m.MapImages.prototype = { + constructor: m.MapImages, + + /* interface to make this array-like */ + + slice: function() { + return ap.slice.apply(this,arguments); + }, + splice: function() { + ap.slice.apply(this.status,arguments); + var result= ap.slice.apply(this,arguments); + return result; + }, + + /** + * a boolean value indicates whether all images are done loading + * @return {bool} true when all are done + */ + complete: function() { + return $.inArray(false, this.status) < 0; + }, + + /** + * Save an image in the images array and return its index + * @param {Image} image An Image object + * @return {int} the index of the image + */ + + _add: function(image) { + var index = ap.push.call(this,image)-1; + this.status[index] = false; + return index; + }, + + /** + * Return the index of an Image within the images array + * @param {Image} img An Image + * @return {int} the index within the array, or -1 if it was not found + */ + + indexOf: function(image) { + return $.inArray(image, this); + }, + + /** + * Clear this object and reset it to its initial state after binding. + */ + + clear: function() { + var me=this; + + if (me.ids && me.ids.length>0) { + $.each(me.ids,function(i,e) { + delete me[e]; + }); + } + + /** + * A list of the cross-reference IDs bound to this object + * @type {string[]} + */ + + me.ids=[]; + + /** + * Length property for array-like behavior, set to zero when initializing. Array prototype + * methods will update it after that. + * + * @type {int} + */ + + me.length=0; + + /** + * the loaded status of the corresponding image + * @type {boolean[]} + */ + + me.status=[]; + + + // actually erase the images + + me.splice(0); + + }, + + /** + * Bind an image to the map and add it to the queue to be loaded; return an ID that + * can be used to reference the + * + * @param {Image|string} image An Image object or a URL to an image + * @param {string} [id] An id to refer to this image + * @returns {int} an ID referencing the index of the image object in + * map_data.images + */ + + add: function(image,id) { + var index,src,me = this; + + if (!image) { return; } + + if (typeof image === 'string') { + src = image; + image = me[src]; + if (typeof image==='object') { + return me.indexOf(image); + } + + image = $('') + .addClass('mapster_el') + .hide(); + + index=me._add(image[0]); + + image + .bind('load',function(e) { + me.imageLoaded.call(me,e); + }) + .bind('error',function(e) { + me.imageLoadError.call(me,e); + }); + + image.attr('src', src); + } else { + + // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src + + index=me._add($(image)[0]); + } + if (id) { + if (this[id]) { + throw(id+" is already used or is not available as an altImage alias."); + } + me.ids.push(id); + me[id]=me[index]; + } + return index; + }, + + /** + * Bind the images in this object, + * @param {boolean} retry when true, indicates that the function is calling itself after failure + * @return {Promise} a promise that resolves when the images have finished loading + */ + + bind: function(retry) { + var me = this, + promise, + triesLeft = me.owner.options.configTimeout / 200, + + /* A recursive function to continue checking that the images have been + loaded until a timeout has elapsed */ + + check=function() { + var i; + + // refresh status of images + + i=me.length; + + while (i-->0) { + if (!me.isLoaded(i)) { + break; + } + } + + // check to see if every image has already been loaded + + if (me.complete()) { + me.resolve(); + } else { + // to account for failure of onLoad to fire in rare situations + if (triesLeft-- > 0) { + me.imgTimeout=window.setTimeout(function() { + check.call(me,true); + }, 50); + } else { + me.imageLoadError.call(me); + } + } + + }; + + promise = me.deferred=u.defer(); + + check(); + return promise; + }, + + resolve: function() { + var me=this, + resolver=me.deferred; + + if (resolver) { + // Make a copy of the resolver before calling & removing it to ensure + // it is not called twice + me.deferred=null; + resolver.resolve(); + } + }, + + /** + * Event handler for image onload + * @param {object} e jQuery event data + */ + + imageLoaded: function(e) { + var me=this, + index = me.indexOf(e.target); + + if (index>=0) { + + me.status[index] = true; + if ($.inArray(false, me.status) < 0) { + me.resolve(); + } + } + }, + + /** + * Event handler for onload error + * @param {object} e jQuery event data + */ + + imageLoadError: function(e) { + clearTimeout(this.imgTimeout); + this.triesLeft=0; + var err = e ? 'The image ' + e.target.src + ' failed to load.' : + 'The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.'; + throw err; + }, + /** + * Test if the image at specificed index has finished loading + * @param {int} index The image index + * @return {boolean} true if loaded, false if not + */ + + isLoaded: function(index) { + var img, + me=this, + status=me.status; + + if (status[index]) { return true; } + img = me[index]; + + if (typeof img.complete !== 'undefined') { + status[index]=img.complete; + } else { + status[index]=!!u.imgWidth(img); + } + // if complete passes, the image is loaded, but may STILL not be available because of stuff like adblock. + // make sure it is. + + return status[index]; + } + }; + } (jQuery)); +/* mapdata.js + the MapData object, repesents an instance of a single bound imagemap +*/ + + +(function ($) { + + var m = $.mapster, + u = m.utils; + + /** + * Set default values for MapData object properties + * @param {MapData} me The MapData object + */ + + function initializeDefaults(me) { + $.extend(me,{ + complete: false, // (bool) when configuration is complete + map: null, // ($) the image map + base_canvas: null, // (canvas|var) where selections are rendered + overlay_canvas: null, // (canvas|var) where highlights are rendered + commands: [], // {} commands that were run before configuration was completed (b/c images weren't loaded) + data: [], // MapData[] area groups + mapAreas: [], // MapArea[] list. AreaData entities contain refs to this array, so options are stored with each. + _xref: {}, // (int) xref of mapKeys to data[] + highlightId: -1, // (int) the currently highlighted element. + currentAreaId: -1, + _tooltip_events: [], // {} info on events we bound to a tooltip container, so we can properly unbind them + scaleInfo: null, // {} info about the image size, scaling, defaults + index: -1, // index of this in map_cache - so we have an ID to use for wraper div + activeAreaEvent: null + }); + } + + /** + * Return an array of all image-containing options from an options object; + * that is, containers that may have an "altImage" property + * + * @param {object} obj An options object + * @return {object[]} An array of objects + */ + function getOptionImages(obj) { + return [obj, obj.render_highlight, obj.render_select]; + } + + /** + * Parse all the altImage references, adding them to the library so they can be preloaded + * and aliased. + * + * @param {MapData} me The MapData object on which to operate + */ + function configureAltImages(me) + { + var opts = me.options, + mi = me.images; + + // add alt images + + if (m.hasCanvas()) { + // map altImage library first + + $.each(opts.altImages || {}, function(i,e) { + mi.add(e,i); + }); + + // now find everything else + + $.each([opts].concat(opts.areas),function(i,e) { + $.each(getOptionImages(e),function(i2,e2) { + if (e2 && e2.altImage) { + e2.altImageId=mi.add(e2.altImage); + } + }); + }); + } + + // set area_options + me.area_options = u.updateProps({}, // default options for any MapArea + m.area_defaults, + opts); + } + + /** + * Queue a mouse move action based on current delay settings + * (helper for mouseover/mouseout handlers) + * + * @param {MapData} me The MapData context + * @param {number} delay The number of milliseconds to delay the action + * @param {AreaData} area AreaData affected + * @param {Deferred} deferred A deferred object to return (instead of a new one) + * @return {Promise} A promise that resolves when the action is completed + */ + function queueMouseEvent(me,delay,area, deferred) { + + deferred = deferred || u.when.defer(); + + function cbFinal(areaId) { + if (me.currentAreaId!==areaId && me.highlightId>=0) { + deferred.resolve(); + } + } + if (me.activeAreaEvent) { + window.clearTimeout(me.activeAreaEvent); + me.activeAreaEvent=0; + } + if (delay<0) { + return; + } + + if (area.owner.currentAction || delay) { + me.activeAreaEvent = window.setTimeout((function() { + return function() { + queueMouseEvent(me,0,area,deferred); + }; + }(area)), + delay || 100); + } else { + cbFinal(area.areaId); + } + return deferred; + } + + /** + * Mousedown event. This is captured only to prevent browser from drawing an outline around an + * area when it's clicked. + * + * @param {EventData} e jQuery event data + */ + + function mousedown(e) { + if (!m.hasCanvas()) { + this.blur(); + } + e.preventDefault(); + } + + /** + * Mouseover event. Handle highlight rendering and client callback on mouseover + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function mouseover(me,e) { + var arData = me.getAllDataForArea(this), + ar=arData.length ? arData[0] : null; + + // mouseover events are ignored entirely while resizing, though we do care about mouseout events + // and must queue the action to keep things clean. + + if (!ar || ar.isNotRendered() || ar.owner.currentAction) { + return; + } + + if (me.currentAreaId === ar.areaId) { + return; + } + if (me.highlightId !== ar.areaId) { + me.clearEffects(); + + ar.highlight(); + + if (me.options.showToolTip) { + $.each(arData,function(i,e) { + if (e.effectiveOptions().toolTip) { + e.showToolTip(); + } + }); + } + } + + me.currentAreaId = ar.areaId; + + if ($.isFunction(me.options.onMouseover)) { + me.options.onMouseover.call(this, + { + e: e, + options:ar.effectiveOptions(), + key: ar.key, + selected: ar.isSelected() + }); + } + } + + /** + * Mouseout event. + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function mouseout(me,e) { + var newArea, + ar = me.getDataForArea(this), + opts = me.options; + + + if (me.currentAreaId<0 || !ar) { + return; + } + + newArea=me.getDataForArea(e.relatedTarget); + + if (newArea === ar) { + return; + } + + me.currentAreaId = -1; + ar.area=null; + + queueMouseEvent(me,opts.mouseoutDelay,ar) + .then(me.clearEffects); + + if ($.isFunction(opts.onMouseout)) { + opts.onMouseout.call(this, + { + e: e, + options: opts, + key: ar.key, + selected: ar.isSelected() + }); + } + + } + + /** + * Clear any active tooltip or highlight + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function clearEffects(me) { + var opts = me.options; + + me.ensureNoHighlight(); + + if (opts.toolTipClose + && $.inArray('area-mouseout', opts.toolTipClose) >= 0 + && me.activeToolTip) + { + me.clearToolTip(); + } + } + + /** + * Mouse click event handler + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function click(me,e) { + var selected, list, list_target, newSelectionState, canChangeState, cbResult, + that = this, + ar = me.getDataForArea(this), + opts = me.options; + + function clickArea(ar) { + var areaOpts,target; + canChangeState = (ar.isSelectable() && + (ar.isDeselectable() || !ar.isSelected())); + + if (canChangeState) { + newSelectionState = !ar.isSelected(); + } else { + newSelectionState = ar.isSelected(); + } + + list_target = m.getBoundList(opts, ar.key); + + if ($.isFunction(opts.onClick)) + { + cbResult= opts.onClick.call(that, + { + e: e, + listTarget: list_target, + key: ar.key, + selected: newSelectionState + }); + + if (u.isBool(cbResult)) { + if (!cbResult) { + return false; + } + target = $(ar.area).attr('href'); + if (target!=='#') { + window.location.href=target; + return false; + } + } + } + + if (canChangeState) { + selected = ar.toggle(); + } + + if (opts.boundList && opts.boundList.length > 0) { + m.setBoundListProperties(opts, list_target, ar.isSelected()); + } + + areaOpts = ar.effectiveOptions(); + if (areaOpts.includeKeys) { + list = u.split(areaOpts.includeKeys); + $.each(list, function (i, e) { + var ar = me.getDataForKey(e.toString()); + if (!ar.options.isMask) { + clickArea(ar); + } + }); + } + } + + mousedown.call(this,e); + + if (opts.clickNavigate && ar.href) { + window.location.href=ar.href; + return; + } + + if (ar && !ar.owner.currentAction) { + opts = me.options; + clickArea(ar); + } + } + + /** + * Prototype for a MapData object, representing an ImageMapster bound object + * @param {Element} image an IMG element + * @param {object} options ImageMapster binding options + */ + m.MapData = function (image, options) + { + var me = this; + + // (Image) main map image + + me.image = image; + + me.images = new m.MapImages(me); + me.graphics = new m.Graphics(me); + + // save the initial style of the image for unbinding. This is problematic, chrome + // duplicates styles when assigning, and cssText is apparently not universally supported. + // Need to do something more robust to make unbinding work universally. + + me.imgCssText = image.style.cssText || null; + + initializeDefaults(me); + + me.configureOptions(options); + + // create context-bound event handlers from our private functions + + me.mouseover = function(e) { mouseover.call(this,me,e); }; + me.mouseout = function(e) { mouseout.call(this,me,e); }; + me.click = function(e) { click.call(this,me,e); }; + me.clearEffects = function(e) { clearEffects.call(this,me,e); }; + }; + + m.MapData.prototype = { + constructor: m.MapData, + + /** + * Set target.options from defaults + options + * @param {[type]} target The target + * @param {[type]} options The options to merge + */ + + configureOptions: function(options) { + this.options= u.updateProps({}, m.defaults, options); + }, + + /** + * Ensure all images are loaded + * @return {Promise} A promise that resolves when the images have finished loading (or fail) + */ + + bindImages: function() { + var me=this, + mi = me.images; + + // reset the images if this is a rebind + + if (mi.length>2) { + mi.splice(2); + } else if (mi.length===0) { + + // add the actual main image + mi.add(me.image); + // will create a duplicate of the main image, we need this to get raw size info + mi.add(me.image.src); + } + + configureAltImages(me); + + return me.images.bind(); + }, + + /** + * Test whether an async action is currently in progress + * @return {Boolean} true or false indicating state + */ + + isActive: function() { + return !this.complete || this.currentAction; + }, + + /** + * Return an object indicating the various states. This isn't really used by + * production code. + * + * @return {object} An object with properties for various states + */ + + state: function () { + return { + complete: this.complete, + resizing: this.currentAction==='resizing', + zoomed: this.zoomed, + zoomedArea: this.zoomedArea, + scaleInfo: this.scaleInfo + }; + }, + + /** + * Get a unique ID for the wrapper of this imagemapster + * @return {string} A string that is unique to this image + */ + + wrapId: function () { + return 'mapster_wrap_' + this.index; + }, + _idFromKey: function (key) { + return typeof key === "string" && this._xref.hasOwnProperty(key) ? + this._xref[key] : -1; + }, + + /** + * Return a comma-separated string of all selected keys + * @return {string} CSV of all keys that are currently selected + */ + + getSelected: function () { + var result = ''; + $.each(this.data, function (i,e) { + if (e.isSelected()) { + result += (result ? ',' : '') + this.key; + } + }); + return result; + }, + + /** + * Get an array of MapAreas associated with a specific AREA based on the keys for that area + * @param {Element} area An HTML AREA + * @param {number} atMost A number limiting the number of areas to be returned (typically 1 or 0 for no limit) + * @return {MapArea[]} Array of MapArea objects + */ + + getAllDataForArea:function (area,atMost) { + var i,ar, result, + me=this, + key = $(area).filter('area').attr(me.options.mapKey); + + if (key) { + result=[]; + key = u.split(key); + + for (i=0;i<(atMost || key.length);i++) { + ar = me.data[me._idFromKey(key[i])]; + ar.area=area.length ? area[0]:area; + // set the actual area moused over/selected + // TODO: this is a brittle model for capturing which specific area - if this method was not used, + // ar.area could have old data. fix this. + result.push(ar); + } + } + + return result; + }, + getDataForArea: function(area) { + var ar=this.getAllDataForArea(area,1); + return ar ? ar[0] || null : null; + }, + getDataForKey: function (key) { + return this.data[this._idFromKey(key)]; + }, + + /** + * Get the primary keys associated with an area group. + * If this is a primary key, it will be returned. + * + * @param {string key An area key + * @return {string} A CSV of area keys + */ + + getKeysForGroup: function(key) { + var ar=this.getDataForKey(key); + + return !ar ? '': + ar.isPrimary ? + ar.key : + this.getPrimaryKeysForMapAreas(ar.areas()).join(','); + }, + + /** + * given an array of MapArea object, return an array of its unique primary keys + * @param {MapArea[]} areas The areas to analyze + * @return {string[]} An array of unique primary keys + */ + + getPrimaryKeysForMapAreas: function(areas) + { + var keys=[]; + $.each(areas,function(i,e) { + if ($.inArray(e.keys[0],keys)<0) { + keys.push(e.keys[0]); + } + }); + return keys; + }, + getData: function (obj) { + if (typeof obj === 'string') { + return this.getDataForKey(obj); + } else if (obj && obj.mapster || u.isElement(obj)) { + return this.getDataForArea(obj); + } else { + return null; + } + }, + // remove highlight if present, raise event + ensureNoHighlight: function () { + var ar; + if (this.highlightId >= 0) { + this.graphics.clearHighlight(); + ar = this.data[this.highlightId]; + ar.changeState('highlight', false); + this.setHighlightId(-1); + } + }, + setHighlightId: function(id) { + this.highlightId = id; + }, + + /** + * Clear all active selections on this map + */ + + clearSelections: function () { + $.each(this.data, function (i,e) { + if (e.selected) { + e.deselect(true); + } + }); + this.removeSelectionFinish(); + + }, + + /** + * Set area options from an array of option data. + * + * @param {object[]} areas An array of objects containing area-specific options + */ + + setAreaOptions: function (areas) { + var i, area_options, ar; + areas = areas || []; + + // refer by: map_data.options[map_data.data[x].area_option_id] + + for (i = areas.length - 1; i >= 0; i--) { + area_options = areas[i]; + if (area_options) { + ar = this.getDataForKey(area_options.key); + if (ar) { + u.updateProps(ar.options, area_options); + + // TODO: will not deselect areas that were previously selected, so this only works + // for an initial bind. + + if (u.isBool(area_options.selected)) { + ar.selected = area_options.selected; + } + } + } + } + }, + // keys: a comma-separated list + drawSelections: function (keys) { + var i, key_arr = u.asArray(keys); + + for (i = key_arr.length - 1; i >= 0; i--) { + this.data[key_arr[i]].drawSelection(); + } + }, + redrawSelections: function () { + $.each(this.data, function (i, e) { + if (e.isSelectedOrStatic()) { + e.drawSelection(); + } + }); + + }, + ///called when images are done loading + initialize: function () { + var imgCopy, base_canvas, overlay_canvas, wrap, parentId, css, i,size, + img,sort_func, sorted_list, scale, + me = this, + opts = me.options; + + if (me.complete) { + return; + } + + img = $(me.image); + + parentId = img.parent().attr('id'); + + // create a div wrapper only if there's not already a wrapper, otherwise, own it + + if (parentId && parentId.length >= 12 && parentId.substring(0, 12) === "mapster_wrap") { + wrap = img.parent(); + wrap.attr('id', me.wrapId()); + } else { + wrap = $('
'); + + if (opts.wrapClass) { + if (opts.wrapClass === true) { + wrap.addClass(img[0].className); + } + else { + wrap.addClass(opts.wrapClass); + } + } + } + me.wrapper = wrap; + + // 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 + // width & height. This is needed to scale the imagemap if not being shown at its native size. It is also needed purely + // to finish binding in case the original image was not visible. It can be impossible in some browsers to obtain the + // native size of a hidden image. + + me.scaleInfo = scale = u.scaleMap(me.images[0],me.images[1], opts.scaleMap); + + me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); + me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); + + // Now we got what we needed from the copy -clone from the original image again to make sure any other attributes are copied + imgCopy = $(me.images[1]) + .addClass('mapster_el '+ me.images[0].className) + .attr({id:null, usemap: null}); + + size=u.size(me.images[0]); + + if (size.complete) { + imgCopy.css({ + width: size.width, + height: size.height + }); + } + + me.buildDataset(); + + // now that we have processed all the areas, set css for wrapper, scale map if needed + + css = { + display: 'block', + position: 'relative', + padding: 0, + width: scale.width, + height: scale.height + }; + + if (opts.wrapCss) { + $.extend(css, opts.wrapCss); + } + // if we were rebinding with an existing wrapper, the image will aready be in it + if (img.parent()[0] !== me.wrapper[0]) { + + img.before(me.wrapper); + } + + wrap.css(css); + + // move all generated images into the wrapper for easy removal later + + $(me.images.slice(2)).hide(); + for (i = 1; i < me.images.length; i++) { + wrap.append(me.images[i]); + } + + //me.images[1].style.cssText = me.image.style.cssText; + + wrap.append(base_canvas) + .append(overlay_canvas) + .append(img.css(m.canvas_style)); + + // images[0] is the original image with map, images[1] is the copy/background that is visible + + u.setOpacity(me.images[0], 0); + $(me.images[1]).show(); + + u.setOpacity(me.images[1],1); + + if (opts.isSelectable && opts.onGetList) { + sorted_list = me.data.slice(0); + if (opts.sortList) { + if (opts.sortList === "desc") { + sort_func = function (a, b) { + return a === b ? 0 : (a > b ? -1 : 1); + }; + } + else { + sort_func = function (a, b) { + return a === b ? 0 : (a < b ? -1 : 1); + }; + } + + sorted_list.sort(function (a, b) { + a = a.value; + b = b.value; + return sort_func(a, b); + }); + } + + me.options.boundList = opts.onGetList.call(me.image, sorted_list); + } + + me.complete=true; + me.processCommandQueue(); + + if (opts.onConfigured && typeof opts.onConfigured === 'function') { + opts.onConfigured.call(img, true); + } + }, + + // when rebind is true, the MapArea data will not be rebuilt. + buildDataset: function(rebind) { + var sel,areas,j,area_id,$area,area,curKey,mapArea,key,keys,mapAreaId,group_value,dataItem,href, + me=this, + opts=me.options, + default_group; + + function addAreaData(key, value) { + var dataItem = new m.AreaData(me, key, value); + dataItem.areaId = me._xref[key] = me.data.push(dataItem) - 1; + return dataItem.areaId; + } + + me._xref = {}; + me.data = []; + if (!rebind) { + me.mapAreas=[]; + } + + default_group = !opts.mapKey; + if (default_group) { + opts.mapKey = 'data-mapster-key'; + } + + // the [attribute] selector is broken on old IE with jQuery. hasVml() is a quick and dirty + // way to test for that + + sel = m.hasVml() ? 'area' : + (default_group ? + 'area[coords]' : + 'area[' + opts.mapKey + ']'); + + areas = $(me.map).find(sel).unbind('.mapster'); + + for (mapAreaId = 0;mapAreaId= 0; j--) { + key = keys[j]; + + if (opts.mapValue) { + group_value = $area.attr(opts.mapValue); + } + if (default_group) { + // set an attribute so we can refer to the area by index from the DOM object if no key + area_id = addAreaData(me.data.length, group_value); + dataItem = me.data[area_id]; + dataItem.key = key = area_id.toString(); + } + else { + area_id = me._xref[key]; + if (area_id >= 0) { + dataItem = me.data[area_id]; + if (group_value && !me.data[area_id].value) { + dataItem.value = group_value; + } + } + else { + area_id = addAreaData(key, group_value); + dataItem = me.data[area_id]; + dataItem.isPrimary=j===0; + } + } + mapArea.areaDataXref.push(area_id); + dataItem.areasXref.push(mapAreaId); + } + + href=$area.attr('href'); + if (href && href!=='#' && !dataItem.href) + { + dataItem.href=href; + } + + if (!mapArea.nohref) { + $area.bind('click.mapster', me.click); + + if (!m.isTouch) { + $area.bind('mouseover.mapster', me.mouseover) + .bind('mouseout.mapster', me.mouseout) + .bind('mousedown.mapster', me.mousedown); + + } + + } + + // store an ID with each area. + $area.data("mapster", mapAreaId+1); + } + + // TODO listenToList + // if (opts.listenToList && opts.nitG) { + // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); + // } + + // populate areas from config options + me.setAreaOptions(opts.areas); + me.redrawSelections(); + + }, + processCommandQueue: function() { + + var cur,me=this; + while (!me.currentAction && me.commands.length) { + cur = me.commands[0]; + me.commands.splice(0,1); + m.impl[cur.command].apply(cur.that, cur.args); + } + }, + clearEvents: function () { + $(this.map).find('area') + .unbind('.mapster'); + $(this.images) + .unbind('.mapster'); + }, + _clearCanvases: function (preserveState) { + // remove the canvas elements created + if (!preserveState) { + $(this.base_canvas).remove(); + } + $(this.overlay_canvas).remove(); + }, + clearMapData: function (preserveState) { + var me = this; + this._clearCanvases(preserveState); + + // release refs to DOM elements + $.each(this.data, function (i, e) { + e.reset(); + }); + this.data = null; + if (!preserveState) { + // get rid of everything except the original image + this.image.style.cssText = this.imgCssText; + $(this.wrapper).before(this.image).remove(); + } + + me.images.clear(); + + this.image = null; + u.ifFunction(this.clearTooltip, this); + }, + + // Compelete cleanup process for deslecting items. Called after a batch operation, or by AreaData for single + // operations not flagged as "partial" + + removeSelectionFinish: function () { + var g = this.graphics; + + g.refreshSelections(); + // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect + g.clearHighlight(); + } + }; +} (jQuery)); +/* areadata.js + AreaData and MapArea protoypes +*/ + +(function ($) { + var m = $.mapster, u = m.utils; + + /** + * Select this area + * + * @param {AreaData} me AreaData context + * @param {object} options Options for rendering the selection + */ + function select(options) { + // need to add the new one first so that the double-opacity effect leaves the current one highlighted for singleSelect + + var me=this, o = me.owner; + if (o.options.singleSelect) { + o.clearSelections(); + } + + // because areas can overlap - we can't depend on the selection state to tell us anything about the inner areas. + // don't check if it's already selected + if (!me.isSelected()) { + if (options) { + + // cache the current options, and map the altImageId if an altimage + // was passed + + me.optsCache = $.extend(me.effectiveRenderOptions('select'), + options, + { + altImageId: o.images.add(options.altImage) + }); + } + + me.drawSelection(); + + me.selected = true; + me.changeState('select', true); + } + + if (o.options.singleSelect) { + o.graphics.refreshSelections(); + } + } + + /** + * Deselect this area, optionally deferring finalization so additional areas can be deselected + * in a single operation + * + * @param {boolean} partial when true, the caller must invoke "finishRemoveSelection" to render + */ + + function deselect(partial) { + var me=this; + me.selected = false; + me.changeState('select', false); + + // release information about last area options when deselecting. + + me.optsCache=null; + me.owner.graphics.removeSelections(me.areaId); + + // Complete selection removal process. This is separated because it's very inefficient to perform the whole + // process for multiple removals, as the canvas must be totally redrawn at the end of the process.ar.remove + + if (!partial) { + me.owner.removeSelectionFinish(); + } + } + + /** + * Toggle the selection state of this area + * @param {object} options Rendering options, if toggling on + * @return {bool} The new selection state + */ + function toggle(options) { + var me=this; + if (!me.isSelected()) { + me.select(options); + } + else { + me.deselect(); + } + return me.isSelected(); + } + + /** + * An AreaData object; represents a conceptual area that can be composed of + * one or more MapArea objects + * + * @param {MapData} owner The MapData object to which this belongs + * @param {string} key The key for this area + * @param {string} value The mapValue string for this area + */ + + m.AreaData = function (owner, key, value) { + $.extend(this,{ + owner: owner, + key: key || '', + // means this represents the first key in a list of keys (it's the area group that gets highlighted on mouseover) + isPrimary: true, + areaId: -1, + href: '', + value: value || '', + options:{}, + // "null" means unchanged. Use "isSelected" method to just test true/false + selected: null, + // xref to MapArea objects + areasXref: [], + // (temporary storage) - the actual area moused over + area: null, + // the last options used to render this. Cache so when re-drawing after a remove, changes in options won't + // break already selected things. + optsCache: null + }); + }; + + /** + * The public API for AreaData object + */ + + m.AreaData.prototype = { + constuctor: m.AreaData, + select: select, + deselect: deselect, + toggle: toggle, + areas: function() { + var i,result=[]; + for (i=0;i= 0; j -= 2) { + curX = coords[j]; + curY = coords[j + 1]; + + if (curX < minX) { + minX = curX; + bestMaxY = curY; + } + if (curX > maxX) { + maxX = curX; + bestMinY = curY; + } + if (curY < minY) { + minY = curY; + bestMaxX = curX; + } + if (curY > maxY) { + maxY = curY; + bestMinX = curX; + } + + } + + // try to figure out the best place for the tooltip + + if (width && height) { + found=false; + $.each([[bestMaxX - width, minY - height], [bestMinX, minY - height], + [minX - width, bestMaxY - height], [minX - width, bestMinY], + [maxX,bestMaxY - height], [ maxX,bestMinY], + [bestMaxX - width, maxY], [bestMinX, maxY] + ],function (i, e) { + if (!found && (e[0] > rootx && e[1] > rooty)) { + nest = e; + found=true; + return false; + } + }); + + // default to lower-right corner if nothing fit inside the boundaries of the image + + if (!found) { + nest=[maxX,maxY]; + } + } + return nest; + }; +} (jQuery)); +/* scale.js: resize and zoom functionality + requires areacorners.js, when.js +*/ + + +(function ($) { + var m = $.mapster, u = m.utils, p = m.MapArea.prototype; + + m.utils.getScaleInfo = function (eff, actual) { + var pct; + if (!actual) { + pct = 1; + actual=eff; + } else { + pct = eff.width / actual.width || eff.height / actual.height; + // make sure a float error doesn't muck us up + if (pct > 0.98 && pct < 1.02) { pct = 1; } + } + return { + scale: (pct !== 1), + scalePct: pct, + realWidth: actual.width, + realHeight: actual.height, + width: eff.width, + height: eff.height, + ratio: eff.width / eff.height + }; + }; + // Scale a set of AREAs, return old data as an array of objects + m.utils.scaleMap = function (image, imageRaw, scale) { + + // stunningly, jQuery width can return zero even as width does not, seems to happen only + // with adBlock or maybe other plugins. These must interfere with onload events somehow. + + + var vis=u.size(image), + raw=u.size(imageRaw,true); + + if (!raw.complete()) { + throw("Another script, such as an extension, appears to be interfering with image loading. Please let us know about this."); + } + if (!vis.complete()) { + vis=raw; + } + return this.getScaleInfo(vis, scale ? raw : null); + }; + + /** + * Resize the image map. Only one of newWidth and newHeight should be passed to preserve scale + * + * @param {int} width The new width OR an object containing named parameters matching this function sig + * @param {int} height The new height + * @param {int} effectDuration Time in ms for the resize animation, or zero for no animation + * @param {function} callback A function to invoke when the operation finishes + * @return {promise} NOT YET IMPLEMENTED + */ + + m.MapData.prototype.resize = function (width, height, duration, callback) { + var p,promises,newsize,els, highlightId, ratio, + me = this; + + // allow omitting duration + callback = callback || duration; + + function sizeCanvas(canvas, w, h) { + if (m.hasCanvas()) { + canvas.width = w; + canvas.height = h; + } else { + $(canvas).width(w); + $(canvas).height(h); + } + } + + // Finalize resize action, do callback, pass control to command queue + + function cleanupAndNotify() { + + me.currentAction = ''; + + if ($.isFunction(callback)) { + callback(); + } + + me.processCommandQueue(); + } + + // handle cleanup after the inner elements are resized + + function finishResize() { + sizeCanvas(me.overlay_canvas, width, height); + + // restore highlight state if it was highlighted before + if (highlightId >= 0) { + var areaData = me.data[highlightId]; + areaData.tempOptions = { fade: false }; + me.getDataForKey(areaData.key).highlight(); + areaData.tempOptions = null; + } + sizeCanvas(me.base_canvas, width, height); + me.redrawSelections(); + cleanupAndNotify(); + } + + function resizeMapData() { + $(me.image).css(newsize); + // start calculation at the same time as effect + me.scaleInfo = u.getScaleInfo({ + width: width, + height: height + }, + { + width: me.scaleInfo.realWidth, + height: me.scaleInfo.realHeight + }); + $.each(me.data, function (i, e) { + $.each(e.areas(), function (i, e) { + e.resize(); + }); + }); + } + + if (me.scaleInfo.width === width && me.scaleInfo.height === height) { + return; + } + + highlightId = me.highlightId; + + + if (!width) { + ratio = height / me.scaleInfo.realHeight; + width = Math.round(me.scaleInfo.realWidth * ratio); + } + if (!height) { + ratio = width / me.scaleInfo.realWidth; + height = Math.round(me.scaleInfo.realHeight * ratio); + } + + newsize = { 'width': String(width) + 'px', 'height': String(height) + 'px' }; + if (!m.hasCanvas()) { + $(me.base_canvas).children().remove(); + } + + // resize all the elements that are part of the map except the image itself (which is not visible) + // but including the div wrapper + els = $(me.wrapper).find('.mapster_el').add(me.wrapper); + + if (duration) { + promises = []; + me.currentAction = 'resizing'; + els.each(function (i, e) { + p = u.defer(); + promises.push(p); + + $(e).animate(newsize, { + duration: duration, + complete: p.resolve, + easing: "linear" + }); + }); + + p = u.defer(); + promises.push(p); + + // though resizeMapData is not async, it needs to be finished just the same as the animations, + // so add it to the "to do" list. + + u.when.all(promises).then(finishResize); + resizeMapData(); + p.resolve(); + } else { + els.css(newsize); + resizeMapData(); + finishResize(); + + } + }; + + + m.MapArea = u.subclass(m.MapArea, function () { + //change the area tag data if needed + this.base.init(); + if (this.owner.scaleInfo.scale) { + this.resize(); + } + }); + + p.coords = function (percent, coordOffset) { + var j, newCoords = [], + pct = percent || this.owner.scaleInfo.scalePct, + offset = coordOffset || 0; + + if (pct === 1 && coordOffset === 0) { + return this.originalCoords; + } + + for (j = 0; j < this.length; j++) { + //amount = j % 2 === 0 ? xPct : yPct; + newCoords.push(Math.round(this.originalCoords[j] * pct) + offset); + } + return newCoords; + }; + p.resize = function () { + this.area.coords = this.coords().join(','); + }; + + p.reset = function () { + this.area.coords = this.coords(1).join(','); + }; + + m.impl.resize = function (width, height, duration, callback) { + if (!width && !height) { + return false; + } + var x= (new m.Method(this, + function () { + this.resize(width, height, duration, callback); + }, + null, + { + name: 'resize', + args: arguments + } + )).go(); + return x; + }; + +/* + m.impl.zoom = function (key, opts) { + var options = opts || {}; + + function zoom(areaData) { + // this will be MapData object returned by Method + + var scroll, corners, height, width, ratio, + diffX, diffY, ratioX, ratioY, offsetX, offsetY, newWidth, newHeight, scrollLeft, scrollTop, + padding = options.padding || 0, + scrollBarSize = areaData ? 20 : 0, + me = this, + zoomOut = false; + + if (areaData) { + // save original state on first zoom operation + if (!me.zoomed) { + me.zoomed = true; + me.preZoomWidth = me.scaleInfo.width; + me.preZoomHeight = me.scaleInfo.height; + me.zoomedArea = areaData; + if (options.scroll) { + me.wrapper.css({ overflow: 'auto' }); + } + } + corners = $.mapster.utils.areaCorners(areaData.coords(1, 0)); + width = me.wrapper.innerWidth() - scrollBarSize - padding * 2; + height = me.wrapper.innerHeight() - scrollBarSize - padding * 2; + diffX = corners.maxX - corners.minX; + diffY = corners.maxY - corners.minY; + ratioX = width / diffX; + ratioY = height / diffY; + ratio = Math.min(ratioX, ratioY); + offsetX = (width - diffX * ratio) / 2; + offsetY = (height - diffY * ratio) / 2; + + newWidth = me.scaleInfo.realWidth * ratio; + newHeight = me.scaleInfo.realHeight * ratio; + scrollLeft = (corners.minX) * ratio - padding - offsetX; + scrollTop = (corners.minY) * ratio - padding - offsetY; + } else { + if (!me.zoomed) { + return; + } + zoomOut = true; + newWidth = me.preZoomWidth; + newHeight = me.preZoomHeight; + scrollLeft = null; + scrollTop = null; + } + + this.resize({ + width: newWidth, + height: newHeight, + duration: options.duration, + scroll: scroll, + scrollLeft: scrollLeft, + scrollTop: scrollTop, + // closure so we can be sure values are correct + callback: (function () { + var isZoomOut = zoomOut, + scroll = options.scroll, + areaD = areaData; + return function () { + if (isZoomOut) { + me.preZoomWidth = null; + me.preZoomHeight = null; + me.zoomed = false; + me.zoomedArea = false; + if (scroll) { + me.wrapper.css({ overflow: 'inherit' }); + } + } else { + // just to be sure it wasn't canceled & restarted + me.zoomedArea = areaD; + } + }; + } ()) + }); + } + return (new m.Method(this, + function (opts) { + zoom.call(this); + }, + function () { + zoom.call(this.owner, this); + }, + { + name: 'zoom', + args: arguments, + first: true, + key: key + } + )).go(); + + + }; + */ +} (jQuery)); +/* tooltip.js - tooltip functionality + requires areacorners.js +*/ + +(function ($) { + + var m = $.mapster, u = m.utils; + + $.extend(m.defaults, { + toolTipContainer: '
', + showToolTip: false, + toolTipFade: true, + toolTipClose: ['area-mouseout','image-mouseout'], + onShowToolTip: null, + onHideToolTip: null + }); + + $.extend(m.area_defaults, { + toolTip: null, + toolTipClose: null + }); + + + /** + * Show a tooltip positioned near this area. + * + * @param {string|jquery} html A string of html or a jQuery object containing the tooltip content. + * @param {string|jquery} [template] The html template in which to wrap the content + * @param {string|object} [css] CSS to apply to the outermost element of the tooltip + * @return {jquery} The tooltip that was created + */ + + function createToolTip(html, template, css) { + var tooltip; + + // wrap the template in a jQuery object, or clone the template if it's already one. + // This assumes that anything other than a string is a jQuery object; if it's not jQuery will + // probably throw an error. + + if (template) { + tooltip = typeof template === 'string' ? + $(template) : + $(template).clone(); + + tooltip.append(html); + } else { + tooltip=$(html); + } + + // always set display to block, or the positioning css won't work if the end user happened to + // use a non-block type element. + + tooltip.css($.extend((css || {}),{ + display:"block", + position:"absolute" + })).hide(); + + $('body').append(tooltip); + + // we must actually add the tooltip to the DOM and "show" it in order to figure out how much space it + // consumes, and then reposition it with that knowledge. + // We also cache the actual opacity setting to restore finally. + + tooltip.attr("data-opacity",tooltip.css("opacity")) + .css("opacity",0); + + // doesn't really show it because opacity=0 + + return tooltip.show(); + } + + + /** + * Show a tooltip positioned near this area. + * + * @param {jquery} tooltip The tooltip + * @param {object} [options] options for displaying the tooltip. + * @config {int} [left] The 0-based absolute x position for the tooltip + * @config {int} [top] The 0-based absolute y position for the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. + */ + + function showToolTipImpl(tooltip,options) + { + var tooltipCss = { + "left": options.left + "px", + "top": options.top + "px" + }, + actalOpacity=tooltip.attr("data-opacity") || 0, + zindex = tooltip.css("z-index"); + + if (parseInt(zindex,10)===0 + || zindex === "auto") { + tooltipCss["z-index"] = 9999; + } + + tooltip.css(tooltipCss) + .addClass('mapster_tooltip'); + + + if (options.fadeDuration && options.fadeDuration>0) { + u.fader(tooltip[0], 0, actalOpacity, options.fadeDuration); + } else { + u.setOpacity(tooltip[0], actalOpacity); + } + } + + /** + * Hide and remove active tooltips + * + * @param {MapData} this The mapdata object to which the tooltips belong + */ + + m.MapData.prototype.clearToolTip = function() { + if (this.activeToolTip) { + this.activeToolTip.stop().remove(); + this.activeToolTip = null; + this.activeToolTipID = null; + u.ifFunction(this.options.onHideToolTip, this); + } + }; + + /** + * Configure the binding between a named tooltip closing option, and a mouse event. + * + * If a callback is passed, it will be called when the activating event occurs, and the tooltip will + * only closed if it returns true. + * + * @param {MapData} [this] The MapData object to which this tooltip belongs. + * @param {String} option The name of the tooltip closing option + * @param {String} event UI event to bind to this option + * @param {Element} target The DOM element that is the target of the event + * @param {Function} [beforeClose] Callback when the tooltip is closed + * @param {Function} [onClose] Callback when the tooltip is closed + */ + function bindToolTipClose(options, bindOption, event, target, beforeClose, onClose) { + var event_name = event + '.mapster-tooltip'; + + if ($.inArray(bindOption, options) >= 0) { + target.unbind(event_name) + .bind(event_name, function (e) { + if (!beforeClose || beforeClose.call(this,e)) { + target.unbind('.mapster-tooltip'); + if (onClose) { + onClose.call(this); + } + } + }); + + return { + object: target, + event: event_name + }; + } + } + + /** + * Show a tooltip. + * + * @param {string|jquery} [tooltip] A string of html or a jQuery object containing the tooltip content. + * + * @param {string|jquery} [target] The target of the tooltip, to be used to determine positioning. If null, + * absolute position values must be passed with left and top. + * + * @param {string|jquery} [image] If target is an [area] the image that owns it + * + * @param {string|jquery} [container] An element within which the tooltip must be bounded + * + * + * + * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * + * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip + * closes: 'area-click','tooltip-click','image-mouseout' are valid values + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip + * @config {int} [offsety] the vertical amount to offset the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + */ + + function showToolTip(tooltip,target,image,container,options) { + var corners, + ttopts = {}; + + options = options || {}; + + + if (target) { + + corners = u.areaCorners(target,image,container, + tooltip.outerWidth(true), + tooltip.outerHeight(true)); + + // Try to upper-left align it first, if that doesn't work, change the parameters + + ttopts.left = corners[0]; + ttopts.top = corners[1]; + + } else { + + ttopts.left = options.left; + ttopts.top = options.top; + } + + ttopts.left += (options.offsetx || 0); + ttopts.top +=(options.offsety || 0); + + ttopts.css= options.css; + ttopts.fadeDuration = options.fadeDuration; + + showToolTipImpl(tooltip,ttopts); + + return tooltip; + } + + /** + * Show a tooltip positioned near this area. + * + * @param {string|jquery} [content] A string of html or a jQuery object containing the tooltip content. + + * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * @config {string|jquery} [container] An element within which the tooltip must be bounded + * @config {bool} [template] a template to use instead of the default. If this property exists and is null, + * then no template will be used. + * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip + * closes: 'area-click','tooltip-click','image-mouseout' are valid values + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip + * @config {int} [offsety] the vertical amount to offset the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + */ + m.AreaData.prototype.showToolTip= function(content,options) { + var tooltip, closeOpts, target, tipClosed, template, + ttopts = {}, + ad=this, + md=ad.owner, + areaOpts = ad.effectiveOptions(); + + // copy the options object so we can update it + options = options ? $.extend({},options) : {}; + + content = content || areaOpts.toolTip; + closeOpts = options.closeEvents || areaOpts.toolTipClose || md.options.toolTipClose || 'tooltip-click'; + + template = typeof options.template !== 'undefined' ? + options.template : + md.options.toolTipContainer; + + options.closeEvents = typeof closeOpts === 'string' ? + closeOpts = u.split(closeOpts) : + closeOpts; + + options.fadeDuration = options.fadeDuration || + (md.options.toolTipFade ? + (md.options.fadeDuration || areaOpts.fadeDuration) : 0); + + target = ad.area ? + ad.area : + $.map(ad.areas(), + function(e) { + return e.area; + }); + + if (md.activeToolTipID===ad.areaId) { + return; + } + + md.clearToolTip(); + + md.activeToolTip = tooltip = createToolTip(content, + template, + options.css); + + md.activeToolTipID = ad.areaId; + + tipClosed = function() { + md.clearToolTip(); + }; + + bindToolTipClose(closeOpts,'area-click', 'click', $(md.map), null, tipClosed); + bindToolTipClose(closeOpts,'tooltip-click', 'click', tooltip,null, tipClosed); + bindToolTipClose(closeOpts,'image-mouseout', 'mouseout', $(md.image), function(e) { + return (e.relatedTarget && e.relatedTarget.nodeName!=='AREA' && e.relatedTarget!==ad.area); + }, tipClosed); + + + showToolTip(tooltip, + target, + md.image, + options.container, + template, + options); + + u.ifFunction(md.options.onShowToolTip, ad.area, + { + toolTip: tooltip, + options: ttopts, + areaOptions: areaOpts, + key: ad.key, + selected: ad.isSelected() + }); + + return tooltip; + }; + + + /** + * Parse an object that could be a string, a jquery object, or an object with a "contents" property + * containing html or a jQuery object. + * + * @param {object|string|jQuery} options The parameter to parse + * @return {string|jquery} A string or jquery object + */ + function getHtmlFromOptions(options) { + + // see if any html was passed as either the options object itself, or the content property + + return (options ? + ((typeof options === 'string' || options.jquery) ? + options : + options.content) : + null); + } + + /** + * Activate or remove a tooltip for an area. When this method is called on an area, the + * key parameter doesn't apply and "options" is the first parameter. + * + * When called with no parameters, or "key" is a falsy value, any active tooltip is cleared. + * + * When only a key is provided, the default tooltip for the area is used. + * + * When html is provided, this is used instead of the default tooltip. + * + * When "noTemplate" is true, the default tooltip template will not be used either, meaning only + * the actual html passed will be used. + * + * @param {string|AreaElement} key The area for which to activate a tooltip, or a DOM element. + * + * @param {object|string|jquery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * @config {string|jQuery} [content] the inner content of the tooltip; the tooltip text or HTML + * @config {Element|jQuery} [container] the inner content of the tooltip; the tooltip text or HTML + * @config {bool} [template] a template to use instead of the default. If this property exists and is null, + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip. + * @config {int} [offsety] the vertical amount to offset the tooltip. + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. + * @return {jQuery} The jQuery object + */ + + m.impl.tooltip = function (key,options) { + return (new m.Method(this, + function mapData() { + var tooltip, target, md=this; + if (!key) { + md.clearToolTip(); + } else { + target=$(key); + if (md.activeToolTipID ===target[0]) { + return; + } + md.clearToolTip(); + + md.activeToolTip = tooltip = createToolTip(getHtmlFromOptions(options), + options.template || md.options.toolTipContainer, + options.css); + md.activeToolTipID = target[0]; + + bindToolTipClose(['tooltip-click'],'tooltip-click', 'click', tooltip, null, function() { + md.clearToolTip(); + }); + + md.activeToolTip = tooltip = showToolTip(tooltip, + target, + md.image, + options.container, + options); + } + }, + function areaData() { + if ($.isPlainObject(key) && !options) { + options = key; + } + + this.showToolTip(getHtmlFromOptions(options),options); + }, + { + name: 'tooltip', + args: arguments, + key: key + } + )).go(); + }; +} (jQuery)); diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/skin/resources/js/jquery-imagemapster-1.2.10.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/skin/resources/js/jquery-imagemapster-1.2.10.min.js Wed Jan 25 11:26:00 2017 +0100 @@ -0,0 +1,1 @@ +(function(a){a(function(){var d,j,u,c;k.defer=f;k.reject=i;k.isPromise=q;k.all=e;k.some=o;k.any=p;k.map=x;k.reduce=n;k.chain=s;d=Object.freeze||function(y){return y};function g(){}g.prototype=d({always:function(y,z){return this.then(y,y,z)},otherwise:function(y){return this.then(c,y)}});function t(y){var z=new g();z.then=function(C){var A;try{if(C){A=C(y)}return r(A===c?y:A)}catch(B){return w(B)}};return d(z)}function w(z){var y=new g();y.then=function(D,A){var B;try{if(A){B=A(z);return r(B===c?z:B)}return w(z)}catch(C){return w(C)}};return d(y)}function i(y){return k(y,function(z){return w(z)})}function f(){var H,J,F,D,B,z,A;F=[];D=[];B=function E(N,L,M){var K=f();F.push(function(O){O.then(N,L).then(K.resolve,K.reject,K.progress)});M&&D.push(M);return K.promise};function C(M,K,L){return B(M,K,L)}function G(K){A(t(K))}function I(K){A(w(K))}z=function(M){var K,L=0;while(K=D[L++]){K(M)}};function y(K){z(K)}A=function(L){var N,K=0;B=L.then;A=z=function M(){throw new Error("already completed")};D=c;while(N=F[K++]){N(L)}F=[]};H={};J=new g();J.then=H.then=C;H.promise=d(J);H.resolver=d({resolve:(H.resolve=G),reject:(H.reject=I),progress:(H.progress=y)});return H}function q(y){return y&&typeof y.then==="function"}function k(A,C,B,z){var y=r(A);return y.then(C,B,z)}function r(y){var A,z;if(y instanceof g){A=y}else{z=f();if(q(y)){y.then(z.resolve,z.reject,z.progress);A=z.promise}else{z.resolve(y);A=z.promise}}return A}function o(B,A,C,z,y){m(2,arguments);return k(B,function(H){var M,J,L,P,F,G,O,K,I;K=H.length>>>0;M=Math.max(0,Math.min(A,K));J=[];P=f();L=k(P,C,z,y);function N(R){F(R)}function Q(R){G(R)}function D(R){O(R)}function E(){F=G=O=h}if(!M){P.resolve(J)}else{F=function(R){J.push(R);if(!--M){E();P.resolve(J)}};G=function(R){E();P.reject(R)};O=P.progress;for(I=0;I>>0;A=new Array(y);for(z=0;z2){z.push(y)}return j.apply(C,z)}function s(y,B,A){var z=arguments.length>2;return k(y,function(C){if(z){C=A}B.resolve(C);return C},function(C){B.reject(C);return w(C)},B.progress)}function m(B,A){var y,z=A.length;while(z>B){y=A[--z];if(y!=null&&typeof y!="function"){throw new Error("callback is not a function")}}}function h(){}u=[].slice;j=[].reduce||function(D){var z,B,A,y,C;C=0;z=Object(this);y=z.length>>>0;B=arguments;if(B.length<=1){for(;;){if(C in z){A=z[C++];break}if(++C>=y){throw new TypeError()}}}else{A=B[1]}for(;C=0){var p=src[prop];if($.isPlainObject(p)){target[prop]=$.extend(target[prop]||{},p)}else{if(p&&p.constructor===Array){target[prop]=p.slice(0)}else{if(typeof p!=="undefined"){target[prop]=src[prop]}}}}})});return target},isElement:function(o){return(typeof HTMLElement==="object"?o instanceof HTMLElement:o&&typeof o==="object"&&o.nodeType===1&&typeof o.nodeName==="string")},indexOfProp:function(obj,prop,val){var result=obj.constructor===Array?-1:null;$.each(obj,function(i,e){if(e&&(prop?e[prop]:e)===val){result=i;return false}});return result},boolOrDefault:function(obj,def){return this.isBool(obj)?obj:def||false},isBool:function(obj){return typeof obj==="boolean"},isUndef:function(obj){return typeof obj==="undefined"},ifFunction:function(obj,that,args){if($.isFunction(obj)){obj.call(that,args)}},size:function(image,raw){var u=$.mapster.utils;return{width:raw?(image.width||image.naturalWidth):u.imgWidth(image,true),height:raw?(image.height||image.naturalHeight):u.imgHeight(image,true),complete:function(){return !!this.height&&!!this.width}}},setOpacity:function(el,opacity){if($.mapster.hasCanvas()){el.style.opacity=opacity}else{$(el).each(function(i,e){if(typeof e.opacity!=="undefined"){e.opacity=opacity}else{$(e).css("opacity",opacity)}})}},fader:(function(){var elements={},lastKey=0,fade_func=function(el,op,endOp,duration){var index,cbIntervals=duration/15,obj,u=$.mapster.utils;if(typeof el==="number"){obj=elements[el];if(!obj){return}}else{index=u.indexOfProp(elements,null,el);if(index){delete elements[index]}elements[++lastKey]=obj=el;el=lastKey}endOp=endOp||1;op=(op+(endOp/cbIntervals)>endOp-0.01)?endOp:op+(endOp/cbIntervals);u.setOpacity(obj,op);if(op=0){return index>=0?this.map_cache[index]:null}},queueCommand:function(map_data,that,command,args){if(!map_data){return false}if(!map_data.complete||map_data.currentAction){map_data.commands.push({that:that,command:command,args:args});return true}return false},unload:function(){this.impl.unload();this.utils=null;this.impl=null;$.fn.mapster=null;$.mapster=null;$("*").unbind()}};var m=$.mapster,u=m.utils,ap=Array.prototype;$.each(["width","height"],function(i,e){var capProp=e.substr(0,1).toUpperCase()+e.substr(1);u["img"+capProp]=function(img,jqwidth){return(jqwidth?$(img)[e]():0)||img[e]||img["natural"+capProp]||img["client"+capProp]||img["offset"+capProp]}});m.Method=function(that,func_map,func_area,opts){var me=this;me.name=opts.name;me.output=that;me.input=that;me.first=opts.first||false;me.args=opts.args?ap.slice.call(opts.args,0):[];me.key=opts.key;me.func_map=func_map;me.func_area=func_area;me.name=opts.name;me.allowAsync=opts.allowAsync||false};m.Method.prototype={constructor:m.Method,go:function(){var i,data,ar,len,result,src=this.input,area_list=[],me=this;len=src.length;for(i=0;i=this.index;i--){m.map_cache[i].index--}};function hasVml(){var a=$("
").appendTo("body");a.html('');var b=a[0].firstChild;b.style.behavior="url(#default#VML)";var has=b?typeof b.adj==="object":true;a.remove();return has}function namespaces(){return typeof(document.namespaces)==="object"?document.namespaces:null}function hasCanvas(){var d=namespaces();return d&&d.g_vml_?false:$("")[0].getContext?true:false}function merge_areas(map_data,areas){var ar,index,map_areas=map_data.options.areas;if(areas){$.each(areas,function(i,e){if(!e||!e.key){return}index=u.indexOfProp(map_areas,"key",e.key);if(index>=0){$.extend(map_areas[index],e)}else{map_areas.push(e)}ar=map_data.getDataForKey(e.key);if(ar){$.extend(ar.options,e)}})}}function merge_options(map_data,options){var temp_opts=u.updateProps({},options);delete temp_opts.areas;u.updateProps(map_data.options,temp_opts);merge_areas(map_data,options.areas);u.updateProps(map_data.area_options,map_data.options)}me.get=function(key){var md=m.getMapData(this);if(!(md&&md.complete)){throw ("Can't access data until binding complete.")}return(new m.Method(this,function(){return this.getSelected()},function(){return this.isSelected()},{name:"get",args:arguments,key:key,first:true,allowAsync:true,defaultReturn:""})).go()};me.data=function(key){return(new m.Method(this,null,function(){return this},{name:"data",args:arguments,key:key})).go()};me.highlight=function(key){return(new m.Method(this,function(){if(key===false){this.ensureNoHighlight()}else{var id=this.highlightId;return id>=0?this.data[id].key:null}},function(){this.highlight()},{name:"highlight",args:arguments,key:key,first:true})).go()};me.keys=function(key,all){var keyList=[],md=m.getMapData(this);if(!(md&&md.complete)){throw ("Can't access data until binding complete.")}function addUniqueKeys(ad){var areas,keys=[];if(!all){keys.push(ad.key)}else{areas=ad.areas();$.each(areas,function(i,e){keys=keys.concat(e.keys)})}$.each(keys,function(i,e){if($.inArray(e,keyList)<0){keyList.push(e)}})}if(!(md&&md.complete)){return""}if(typeof key==="string"){if(all){addUniqueKeys(md.getDataForKey(key))}else{keyList=[md.getKeysForGroup(key)]}}else{all=key;this.each(function(i,e){if(e.nodeName==="AREA"){addUniqueKeys(md.getDataForArea(e))}})}return keyList.join(",")};me.select=function(){me.set.call(this,true)};me.deselect=function(){me.set.call(this,false)};me.set=function(selected,key,options){var lastMap,map_data,opts=options,key_list,area_list;function setSelection(ar){if(ar){switch(selected){case true:ar.select(opts);break;case false:ar.deselect(true);break;default:ar.toggle(opts);break}}}function addArea(ar){if(ar&&$.inArray(ar,area_list)<0){area_list.push(ar);key_list+=(key_list===""?"":",")+ar.key}}function finishSetForMap(map_data){$.each(area_list,function(i,el){setSelection(el)});if(!selected){map_data.removeSelectionFinish()}if(map_data.options.boundList){m.setBoundListProperties(map_data.options,m.getBoundList(map_data.options,key_list),selected)}}this.filter("img,area").each(function(i,e){var keys;map_data=m.getMapData(e);if(map_data!==lastMap){if(lastMap){finishSetForMap(lastMap)}area_list=[];key_list=""}if(map_data){keys="";if(e.nodeName.toUpperCase()==="IMG"){if(!m.queueCommand(map_data,$(e),"set",[selected,key,opts])){if(key instanceof Array){if(key.length){keys=key.join(",")}}else{keys=key}if(keys){$.each(u.split(keys),function(i,key){addArea(map_data.getDataForKey(key.toString()));lastMap=map_data})}}}else{opts=key;if(!m.queueCommand(map_data,$(e),"set",[selected,opts])){addArea(map_data.getDataForArea(e));lastMap=map_data}}}});if(map_data){finishSetForMap(map_data)}return this};me.unbind=function(preserveState){return(new m.Method(this,function(){this.clearEvents();this.clearMapData(preserveState);removeMap(this)},null,{name:"unbind",args:arguments})).go()};me.rebind=function(options){return(new m.Method(this,function(){var me=this;me.complete=false;me.configureOptions(options);me.bindImages().then(function(){me.buildDataset(true);me.complete=true})},null,{name:"rebind",args:arguments})).go()};me.get_options=function(key,effective){var eff=u.isBool(key)?key:effective;return(new m.Method(this,function(){var opts=$.extend({},this.options);if(eff){opts.render_select=u.updateProps({},m.render_defaults,opts,opts.render_select);opts.render_highlight=u.updateProps({},m.render_defaults,opts,opts.render_highlight)}return opts},function(){return eff?this.effectiveOptions():this.options},{name:"get_options",args:arguments,first:true,allowAsync:true,key:key})).go()};me.set_options=function(options){return(new m.Method(this,function(){merge_options(this,options)},null,{name:"set_options",args:arguments})).go()};me.unload=function(){var i;for(i=m.map_cache.length-1;i>=0;i--){if(m.map_cache[i]){me.unbind.call($(m.map_cache[i].image))}}me.graphics=null};me.snapshot=function(){return(new m.Method(this,function(){$.each(this.data,function(i,e){e.selected=false});this.base_canvas=this.graphics.createVisibleCanvas(this);$(this.image).before(this.base_canvas)},null,{name:"snapshot"})).go()};me.state=function(){var md,result=null;$(this).each(function(i,e){if(e.nodeName==="IMG"){md=m.getMapData(e);if(md){result=md.state()}return false}});return result};me.bind=function(options){return this.each(function(i,e){var img,map,usemap,md;img=$(e);md=m.getMapData(e);if(md){me.unbind.apply(img);if(!md.complete){img.bind();return true}md=null}usemap=this.getAttribute("usemap");map=usemap&&$('map[name="'+usemap.substr(1)+'"]');if(!(img.is("img")&&usemap&&map.size()>0)){return true}img.css("border",0);if(!md){md=new m.MapData(this,options);md.index=addMap(md);md.map=map;md.bindImages().then(function(){md.initialize()})}})};me.init=function(useCanvas){var style,shapes;m.hasCanvas=function(){if(!u.isBool(m.hasCanvas.value)){m.hasCanvas.value=u.isBool(useCanvas)?useCanvas:hasCanvas()}return m.hasCanvas.value};m.hasVml=function(){if(!u.isBool(m.hasVml.value)){var d=namespaces();if(d&&!d.v){d.add("v","urn:schemas-microsoft-com:vml");style=document.createStyleSheet();shapes=["shape","rect","oval","circ","fill","stroke","imagedata","group","textbox"];$.each(shapes,function(i,el){style.addRule("v\\:"+el,"behavior: url(#default#VML); antialias:true")})}m.hasVml.value=hasVml()}return m.hasVml.value};m.isTouch=!!document.documentElement.ontouchstart;$.extend(m.defaults,m.render_defaults,m.shared_defaults);$.extend(m.area_defaults,m.render_defaults,m.shared_defaults)};me.test=function(obj){return eval(obj)};return me}());$.mapster.impl.init()}(jQuery));(function(e){var a,d=e.mapster,i=d.utils,g,b;function h(l,n,m){var o=l,p=o.map_data,k=m.isMask;e.each(n.areas(),function(q,r){m.isMask=k||(r.nohref&&p.options.noHrefIsMask);o.addShape(r,m)});m.isMask=k}function c(k){return Math.max(0,Math.min(parseInt(k,16),255))}function f(k,l){return"rgba("+c(k.substr(0,2))+","+c(k.substr(2,2))+","+c(k.substr(4,2))+","+l+")"}d.Graphics=function(l){var k=this;k.active=false;k.canvas=null;k.width=0;k.height=0;k.shapes=[];k.masks=[];k.map_data=l};a=d.Graphics.prototype={constructor:d.Graphics,begin:function(l,k){var m=e(l);this.elementName=k;this.canvas=l;this.width=m.width();this.height=m.height();this.shapes=[];this.masks=[];this.active=true},addShape:function(m,l){var k=l.isMask?this.masks:this.shapes;k.push({mapArea:m,options:l})},createVisibleCanvas:function(k){return e(this.createCanvasFor(k)).addClass("mapster_el").css(d.canvas_style)[0]},addShapeGroup:function(r,n,s){var q=this,o,l,m,p=this.map_data,k=r.effectiveRenderOptions(n);if(s){e.extend(k,s)}if(n==="select"){l="static_"+r.areaId.toString();m=p.base_canvas}else{m=p.overlay_canvas}q.begin(m,l);if(k.includeKeys){o=i.split(k.includeKeys);e.each(o,function(u,v){var t=p.getDataForKey(v.toString());h(q,t,t.effectiveRenderOptions(n))})}h(q,r,k);q.render();if(k.fade){i.fader(d.hasCanvas()?m:e(m).find("._fill").not(".mapster_mask"),0,d.hasCanvas()?1:k.fillOpacity,k.fadeDuration)}}};function j(){}g={renderShape:function(l,m,n){var k,o=m.coords(null,n);switch(m.shape){case"rect":l.rect(o[0],o[1],o[2]-o[0],o[3]-o[1]);break;case"poly":l.moveTo(o[0],o[1]);for(k=2;k')[0]},clearHighlight:function(){var k=this.map_data.overlay_canvas;k.getContext("2d").clearRect(0,0,k.width,k.height)},refreshSelections:function(){var k,l=this.map_data;k=l.base_canvas;l.base_canvas=this.createVisibleCanvas(l);e(l.base_canvas).hide();e(k).before(l.base_canvas);l.redrawSelections();e(l.base_canvas).show();e(k).remove()}};b={renderShape:function(n,v,k){var p=this,t,u,m,l,r,q,s,o=n.coords();r=p.elementName?'name="'+p.elementName+'" ':"";q=k?'class="'+k+'" ':"";l='';u=v.stroke?" strokeweight="+v.strokeWidth+' stroked="t" strokecolor="#'+v.strokeColor+'"':' stroked="f"';t=v.fill?' filled="t"':' filled="f"';switch(n.shape){case"rect":s="'+l+"";break;case"poly":s="'+l+"";break;case"circ":case"circle":s="'+l+"";break}m=e(s);e(p.canvas).append(m);return m},render:function(){var l,k=this;e.each(this.shapes,function(m,n){k.renderShape(n.mapArea,n.options)});if(this.masks.length){e.each(this.masks,function(m,n){l=i.updateProps({},n.options,{fillOpacity:1,fillColor:n.options.fillColorMask});k.renderShape(n.mapArea,l,"mapster_mask")})}this.active=false;return this.canvas},createCanvasFor:function(m){var k=m.scaleInfo.width,l=m.scaleInfo.height;return e('')[0]},clearHighlight:function(){e(this.map_data.overlay_canvas).children().remove()},removeSelections:function(k){if(k>=0){e(this.map_data.base_canvas).find('[name="static_'+k.toString()+'"]').remove()}else{e(this.map_data.base_canvas).children().remove()}}};e.each(["renderShape","addAltImage","render","createCanvasFor","clearHighlight","removeSelections","refreshSelections"],function(k,l){a[l]=(function(m){return function(){a[m]=(d.hasCanvas()?g[m]:b[m])||j;return a[m].apply(this,arguments)}}(l))})}(jQuery));(function(d){var a=d.mapster,b=a.utils,c=[];a.MapImages=function(e){this.owner=e;this.clear()};a.MapImages.prototype={constructor:a.MapImages,slice:function(){return c.slice.apply(this,arguments)},splice:function(){c.slice.apply(this.status,arguments);var e=c.slice.apply(this,arguments);return e},complete:function(){return d.inArray(false,this.status)<0},_add:function(f){var e=c.push.call(this,f)-1;this.status[e]=false;return e},indexOf:function(e){return d.inArray(e,this)},clear:function(){var e=this;if(e.ids&&e.ids.length>0){d.each(e.ids,function(f,g){delete e[g]})}e.ids=[];e.length=0;e.status=[];e.splice(0)},add:function(g,i){var e,h,f=this;if(!g){return}if(typeof g==="string"){h=g;g=f[h];if(typeof g==="object"){return f.indexOf(g)}g=d("").addClass("mapster_el").hide();e=f._add(g[0]);g.bind("load",function(j){f.imageLoaded.call(f,j)}).bind("error",function(j){f.imageLoadError.call(f,j)});g.attr("src",h)}else{e=f._add(d(g)[0])}if(i){if(this[i]){throw (i+" is already used or is not available as an altImage alias.")}f.ids.push(i);f[i]=f[e]}return e},bind:function(g){var h=this,i,e=h.owner.options.configTimeout/200,f=function(){var j;j=h.length;while(j-->0){if(!h.isLoaded(j)){break}}if(h.complete()){h.resolve()}else{if(e-->0){h.imgTimeout=window.setTimeout(function(){f.call(h,true)},50)}else{h.imageLoadError.call(h)}}};i=h.deferred=b.defer();f();return i},resolve:function(){var e=this,f=e.deferred;if(f){e.deferred=null;f.resolve()}},imageLoaded:function(h){var g=this,f=g.indexOf(h.target);if(f>=0){g.status[f]=true;if(d.inArray(false,g.status)<0){g.resolve()}}},imageLoadError:function(g){clearTimeout(this.imgTimeout);this.triesLeft=0;var f=g?"The image "+g.target.src+" failed to load.":"The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.";throw f},isLoaded:function(g){var f,h=this,e=h.status;if(e[g]){return true}f=h[g];if(typeof f.complete!=="undefined"){e[g]=f.complete}else{e[g]=!!b.imgWidth(f)}return e[g]}}}(jQuery));(function(h){var d=h.mapster,k=d.utils;function a(m){h.extend(m,{complete:false,map:null,base_canvas:null,overlay_canvas:null,commands:[],data:[],mapAreas:[],_xref:{},highlightId:-1,currentAreaId:-1,_tooltip_events:[],scaleInfo:null,index:-1,activeAreaEvent:null})}function e(m){return[m,m.render_highlight,m.render_select]}function g(o){var n=o.options,m=o.images;if(d.hasCanvas()){h.each(n.altImages||{},function(p,q){m.add(q,p)});h.each([n].concat(n.areas),function(p,q){h.each(e(q),function(r,s){if(s&&s.altImage){s.altImageId=m.add(s.altImage)}})})}o.area_options=k.updateProps({},d.area_defaults,n)}function f(q,o,p,n){n=n||k.when.defer();function m(r){if(q.currentAreaId!==r&&q.highlightId>=0){n.resolve()}}if(q.activeAreaEvent){window.clearTimeout(q.activeAreaEvent);q.activeAreaEvent=0}if(o<0){return}if(p.owner.currentAction||o){q.activeAreaEvent=window.setTimeout((function(){return function(){f(q,0,p,n)}}(p)),o||100)}else{m(p.areaId)}return n}function c(m){if(!d.hasCanvas()){this.blur()}m.preventDefault()}function j(n,o){var p=n.getAllDataForArea(this),m=p.length?p[0]:null;if(!m||m.isNotRendered()||m.owner.currentAction){return}if(n.currentAreaId===m.areaId){return}if(n.highlightId!==m.areaId){n.clearEffects();m.highlight();if(n.options.showToolTip){h.each(p,function(q,r){if(r.effectiveOptions().toolTip){r.showToolTip()}})}}n.currentAreaId=m.areaId;if(h.isFunction(n.options.onMouseover)){n.options.onMouseover.call(this,{e:o,options:m.effectiveOptions(),key:m.key,selected:m.isSelected()})}}function i(p,q){var n,m=p.getDataForArea(this),o=p.options;if(p.currentAreaId<0||!m){return}n=p.getDataForArea(q.relatedTarget);if(n===m){return}p.currentAreaId=-1;m.area=null;f(p,o.mouseoutDelay,m).then(p.clearEffects);if(h.isFunction(o.onMouseout)){o.onMouseout.call(this,{e:q,options:o,key:m.key,selected:m.isSelected()})}}function b(n){var m=n.options;n.ensureNoHighlight();if(m.toolTipClose&&h.inArray("area-mouseout",m.toolTipClose)>=0&&n.activeToolTip){n.clearToolTip()}}function l(w,u){var r,v,o,q,x,s,t=this,p=w.getDataForArea(this),n=w.options;function m(z){var y,A;x=(z.isSelectable()&&(z.isDeselectable()||!z.isSelected()));if(x){q=!z.isSelected()}else{q=z.isSelected()}o=d.getBoundList(n,z.key);if(h.isFunction(n.onClick)){s=n.onClick.call(t,{e:u,listTarget:o,key:z.key,selected:q});if(k.isBool(s)){if(!s){return false}A=h(z.area).attr("href");if(A!=="#"){window.location.href=A;return false}}}if(x){r=z.toggle()}if(n.boundList&&n.boundList.length>0){d.setBoundListProperties(n,o,z.isSelected())}y=z.effectiveOptions();if(y.includeKeys){v=k.split(y.includeKeys);h.each(v,function(C,D){var B=w.getDataForKey(D.toString());if(!B.options.isMask){m(B)}})}}c.call(this,u);if(n.clickNavigate&&p.href){window.location.href=p.href;return}if(p&&!p.owner.currentAction){n=w.options;m(p)}}d.MapData=function(o,m){var n=this;n.image=o;n.images=new d.MapImages(n);n.graphics=new d.Graphics(n);n.imgCssText=o.style.cssText||null;a(n);n.configureOptions(m);n.mouseover=function(p){j.call(this,n,p)};n.mouseout=function(p){i.call(this,n,p)};n.click=function(p){l.call(this,n,p)};n.clearEffects=function(p){b.call(this,n,p)}};d.MapData.prototype={constructor:d.MapData,configureOptions:function(m){this.options=k.updateProps({},d.defaults,m)},bindImages:function(){var n=this,m=n.images;if(m.length>2){m.splice(2)}else{if(m.length===0){m.add(n.image);m.add(n.image.src)}}g(n);return n.images.bind()},isActive:function(){return !this.complete||this.currentAction},state:function(){return{complete:this.complete,resizing:this.currentAction==="resizing",zoomed:this.zoomed,zoomedArea:this.zoomedArea,scaleInfo:this.scaleInfo}},wrapId:function(){return"mapster_wrap_"+this.index},_idFromKey:function(m){return typeof m==="string"&&this._xref.hasOwnProperty(m)?this._xref[m]:-1},getSelected:function(){var m="";h.each(this.data,function(n,o){if(o.isSelected()){m+=(m?",":"")+this.key}});return m},getAllDataForArea:function(s,r){var p,n,m,q=this,o=h(s).filter("area").attr(q.options.mapKey);if(o){m=[];o=k.split(o);for(p=0;p<(r||o.length);p++){n=q.data[q._idFromKey(o[p])];n.area=s.length?s[0]:s;m.push(n)}}return m},getDataForArea:function(n){var m=this.getAllDataForArea(n,1);return m?m[0]||null:null},getDataForKey:function(m){return this.data[this._idFromKey(m)]},getKeysForGroup:function(n){var m=this.getDataForKey(n);return !m?"":m.isPrimary?m.key:this.getPrimaryKeysForMapAreas(m.areas()).join(",")},getPrimaryKeysForMapAreas:function(m){var n=[];h.each(m,function(o,p){if(h.inArray(p.keys[0],n)<0){n.push(p.keys[0])}});return n},getData:function(m){if(typeof m==="string"){return this.getDataForKey(m)}else{if(m&&m.mapster||k.isElement(m)){return this.getDataForArea(m)}else{return null}}},ensureNoHighlight:function(){var m;if(this.highlightId>=0){this.graphics.clearHighlight();m=this.data[this.highlightId];m.changeState("highlight",false);this.setHighlightId(-1)}},setHighlightId:function(m){this.highlightId=m},clearSelections:function(){h.each(this.data,function(m,n){if(n.selected){n.deselect(true)}});this.removeSelectionFinish()},setAreaOptions:function(n){var p,o,m;n=n||[];for(p=n.length-1;p>=0;p--){o=n[p];if(o){m=this.getDataForKey(o.key);if(m){k.updateProps(m.options,o);if(k.isBool(o.selected)){m.selected=o.selected}}}}},drawSelections:function(o){var m,n=k.asArray(o);for(m=n.length-1;m>=0;m--){this.data[n[m]].drawSelection()}},redrawSelections:function(){h.each(this.data,function(m,n){if(n.isSelectedOrStatic()){n.drawSelection()}})},initialize:function(){var y,r,n,p,u,v,t,z,s,o,x,q,w=this,m=w.options;if(w.complete){return}s=h(w.image);u=s.parent().attr("id");if(u&&u.length>=12&&u.substring(0,12)==="mapster_wrap"){p=s.parent();p.attr("id",w.wrapId())}else{p=h('
');if(m.wrapClass){if(m.wrapClass===true){p.addClass(s[0].className)}else{p.addClass(m.wrapClass)}}}w.wrapper=p;w.scaleInfo=q=k.scaleMap(w.images[0],w.images[1],m.scaleMap);w.base_canvas=r=w.graphics.createVisibleCanvas(w);w.overlay_canvas=n=w.graphics.createVisibleCanvas(w);y=h(w.images[1]).addClass("mapster_el "+w.images[0].className).attr({id:null,usemap:null});z=k.size(w.images[0]);if(z.complete){y.css({width:z.width,height:z.height})}w.buildDataset();v={display:"block",position:"relative",padding:0,width:q.width,height:q.height};if(m.wrapCss){h.extend(v,m.wrapCss)}if(s.parent()[0]!==w.wrapper[0]){s.before(w.wrapper)}p.css(v);h(w.images.slice(2)).hide();for(t=1;tA?-1:1)}}else{o=function(B,A){return B===A?0:(B=0;z--){E=t[z];if(s.mapValue){p=m.attr(s.mapValue)}if(v){x=A(D.data.length,p);q=D.data[x];q.key=E=x.toString()}else{x=D._xref[E];if(x>=0){q=D.data[x];if(p&&!D.data[x].value){q.value=p}}else{x=A(E,p);q=D.data[x];q.isPrimary=z===0}}r.areaDataXref.push(x);q.areasXref.push(w)}B=m.attr("href");if(B&&B!=="#"&&!q.href){q.href=B}if(!r.nohref){m.bind("click.mapster",D.click);if(!d.isTouch){m.bind("mouseover.mapster",D.mouseover).bind("mouseout.mapster",D.mouseout).bind("mousedown.mapster",D.mousedown)}}m.data("mapster",w+1)}D.setAreaOptions(s.areas);D.redrawSelections()},processCommandQueue:function(){var n,m=this;while(!m.currentAction&&m.commands.length){n=m.commands[0];m.commands.splice(0,1);d.impl[n.command].apply(n.that,n.args)}},clearEvents:function(){h(this.map).find("area").unbind(".mapster");h(this.images).unbind(".mapster")},_clearCanvases:function(m){if(!m){h(this.base_canvas).remove()}h(this.overlay_canvas).remove()},clearMapData:function(n){var m=this;this._clearCanvases(n);h.each(this.data,function(o,p){p.reset()});this.data=null;if(!n){this.image.style.cssText=this.imgCssText;h(this.wrapper).before(this.image).remove()}m.images.clear();this.image=null;k.ifFunction(this.clearTooltip,this)},removeSelectionFinish:function(){var m=this.graphics;m.refreshSelections();m.clearHighlight()}}}(jQuery));(function(f){var c=f.mapster,d=c.utils;function b(g){var h=this,i=h.owner;if(i.options.singleSelect){i.clearSelections()}if(!h.isSelected()){if(g){h.optsCache=f.extend(h.effectiveRenderOptions("select"),g,{altImageId:i.images.add(g.altImage)})}h.drawSelection();h.selected=true;h.changeState("select",true)}if(i.options.singleSelect){i.graphics.refreshSelections()}}function e(g){var h=this;h.selected=false;h.changeState("select",false);h.optsCache=null;h.owner.graphics.removeSelections(h.areaId);if(!g){h.owner.removeSelectionFinish()}}function a(g){var h=this;if(!h.isSelected()){h.select(g)}else{h.deselect()}return h.isSelected()}c.AreaData=function(g,h,i){f.extend(this,{owner:g,key:h||"",isPrimary:true,areaId:-1,href:"",value:i||"",options:{},selected:null,areasXref:[],area:null,optsCache:null})};c.AreaData.prototype={constuctor:c.AreaData,select:b,deselect:e,toggle:a,areas:function(){var h,g=[];for(h=0;h=0;u-=2){l=x[u];k=x[u+1];if(lC){C=l;v=k}if(kA){A=k;y=l}}if(s&&q){n=false;b.each([[w-s,B-q],[y,B-q],[D-s,t-q],[D-s,v],[C,t-q],[C,v],[w-s,A],[y,A]],function(j,F){if(!n&&(F[0]>h&&F[1]>f)){E=F;n=true;return false}});if(!n){E=[C,A]}}return E}}(jQuery));(function(c){var a=c.mapster,b=a.utils,d=a.MapArea.prototype;a.utils.getScaleInfo=function(e,g){var f;if(!g){f=1;g=e}else{f=e.width/g.width||e.height/g.height;if(f>0.98&&f<1.02){f=1}}return{scale:(f!==1),scalePct:f,realWidth:g.width,realHeight:g.height,width:e.width,height:e.height,ratio:e.width/e.height}};a.utils.scaleMap=function(h,f,i){var g=b.size(h),e=b.size(f,true);if(!e.complete()){throw ("Another script, such as an extension, appears to be interfering with image loading. Please let us know about this.")}if(!g.complete()){g=e}return this.getScaleInfo(g,i?e:null)};a.MapData.prototype.resize=function(g,t,j,s){var f,r,i,k,e,n,o=this;s=s||j;function q(u,p,v){if(a.hasCanvas()){u.width=p;u.height=v}else{c(u).width(p);c(u).height(v)}}function l(){o.currentAction="";if(c.isFunction(s)){s()}o.processCommandQueue()}function m(){q(o.overlay_canvas,g,t);if(e>=0){var p=o.data[e];p.tempOptions={fade:false};o.getDataForKey(p.key).highlight();p.tempOptions=null}q(o.base_canvas,g,t);o.redrawSelections();l()}function h(){c(o.image).css(i);o.scaleInfo=b.getScaleInfo({width:g,height:t},{width:o.scaleInfo.realWidth,height:o.scaleInfo.realHeight});c.each(o.data,function(p,u){c.each(u.areas(),function(v,w){w.resize()})})}if(o.scaleInfo.width===g&&o.scaleInfo.height===t){return}e=o.highlightId;if(!g){n=t/o.scaleInfo.realHeight;g=Math.round(o.scaleInfo.realWidth*n)}if(!t){n=g/o.scaleInfo.realWidth;t=Math.round(o.scaleInfo.realHeight*n)}i={width:String(g)+"px",height:String(t)+"px"};if(!a.hasCanvas()){c(o.base_canvas).children().remove()}k=c(o.wrapper).find(".mapster_el").add(o.wrapper);if(j){r=[];o.currentAction="resizing";k.each(function(p,u){f=b.defer();r.push(f);c(u).animate(i,{duration:j,complete:f.resolve,easing:"linear"})});f=b.defer();r.push(f);b.when.all(r).then(m);h();f.resolve()}else{k.css(i);h();m()}};a.MapArea=b.subclass(a.MapArea,function(){this.base.init();if(this.owner.scaleInfo.scale){this.resize()}});d.coords=function(f,i){var e,h=[],g=f||this.owner.scaleInfo.scalePct,k=i||0;if(g===1&&i===0){return this.originalCoords}for(e=0;e',showToolTip:false,toolTipFade:true,toolTipClose:["area-mouseout","image-mouseout"],onShowToolTip:null,onHideToolTip:null});g.extend(a.area_defaults,{toolTip:null,toolTipClose:null});function e(j,k,i){var l;if(k){l=typeof k==="string"?g(k):g(k).clone();l.append(j)}else{l=g(j)}l.css(g.extend((i||{}),{display:"block",position:"absolute"})).hide();g("body").append(l);l.attr("data-opacity",l.css("opacity")).css("opacity",0);return l.show()}function h(m,k){var i={left:k.left+"px",top:k.top+"px"},j=m.attr("data-opacity")||0,l=m.css("z-index");if(parseInt(l,10)===0||l==="auto"){i["z-index"]=9999}m.css(i).addClass("mapster_tooltip");if(k.fadeDuration&&k.fadeDuration>0){d.fader(m[0],0,j,k.fadeDuration)}else{d.setOpacity(m[0],j)}}a.MapData.prototype.clearToolTip=function(){if(this.activeToolTip){this.activeToolTip.stop().remove();this.activeToolTip=null;this.activeToolTipID=null;d.ifFunction(this.options.onHideToolTip,this)}};function b(k,i,l,o,m,j){var n=l+".mapster-tooltip";if(g.inArray(i,k)>=0){o.unbind(n).bind(n,function(p){if(!m||m.call(this,p)){o.unbind(".mapster-tooltip");if(j){j.call(this)}}});return{object:o,event:n}}}function f(m,o,n,i,k){var j,l={};k=k||{};if(o){j=d.areaCorners(o,n,i,m.outerWidth(true),m.outerHeight(true));l.left=j[0];l.top=j[1]}else{l.left=k.left;l.top=k.top}l.left+=(k.offsetx||0);l.top+=(k.offsety||0);l.css=k.css;l.fadeDuration=k.fadeDuration;h(m,l);return m}a.AreaData.prototype.showToolTip=function(m,s){var r,j,l,n,p,k={},q=this,o=q.owner,i=q.effectiveOptions();s=s?g.extend({},s):{};m=m||i.toolTip;j=s.closeEvents||i.toolTipClose||o.options.toolTipClose||"tooltip-click";p=typeof s.template!=="undefined"?s.template:o.options.toolTipContainer;s.closeEvents=typeof j==="string"?j=d.split(j):j;s.fadeDuration=s.fadeDuration||(o.options.toolTipFade?(o.options.fadeDuration||i.fadeDuration):0);l=q.area?q.area:g.map(q.areas(),function(t){return t.area});if(o.activeToolTipID===q.areaId){return}o.clearToolTip();o.activeToolTip=r=e(m,p,s.css);o.activeToolTipID=q.areaId;n=function(){o.clearToolTip()};b(j,"area-click","click",g(o.map),null,n);b(j,"tooltip-click","click",r,null,n);b(j,"image-mouseout","mouseout",g(o.image),function(t){return(t.relatedTarget&&t.relatedTarget.nodeName!=="AREA"&&t.relatedTarget!==q.area)},n);f(r,l,o.image,s.container,p,s);d.ifFunction(o.options.onShowToolTip,q.area,{toolTip:r,options:k,areaOptions:i,key:q.key,selected:q.isSelected()});return r};function c(i){return(i?((typeof i==="string"||i.jquery)?i:i.content):null)}a.impl.tooltip=function(k,j){return(new a.Method(this,function l(){var n,o,m=this;if(!k){m.clearToolTip()}else{o=g(k);if(m.activeToolTipID===o[0]){return}m.clearToolTip();m.activeToolTip=n=e(c(j),j.template||m.options.toolTipContainer,j.css);m.activeToolTipID=o[0];b(["tooltip-click"],"tooltip-click","click",n,null,function(){m.clearToolTip()});m.activeToolTip=n=f(n,o,m.image,j.container,j)}},function i(){if(g.isPlainObject(k)&&!j){j=k}this.showToolTip(c(j),j)},{name:"tooltip",args:arguments,key:k})).go()}}(jQuery)); \ No newline at end of file diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/skin/resources/js/pyams_content.js --- a/src/pyams_content/skin/resources/js/pyams_content.js Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/skin/resources/js/pyams_content.js Wed Jan 25 11:26:00 2017 +0100 @@ -214,11 +214,26 @@ imgmap: { init: function() { - var element = $(this); + var image = $(this); MyAMS.ajax.check($.fn.canvasAreaDraw, '/--static--/pyams_content/js/jquery-canvasAreaDraw' + MyAMS.devext + '.js', function() { - element.canvasAreaDraw({imageUrl: element.data('ams-image-url')}); + image.canvasAreaDraw({imageUrl: image.data('ams-image-url')}); + }); + }, + + initSummary: function() { + var image = $(this); + MyAMS.ajax.check($.fn.mapster, + '/--static--/pyams_content/js/jquery-imagemapster-1.2.10' + MyAMS.devext + '.js', + function() { + image.mapster({ + fillColor: 'ff0000', + fillOpacity: 0.35, + selected: true, + highlight: true, + staticState: true + }); }); } }, diff -r 8c5bbc396670 -r ba26f4023bc2 src/pyams_content/skin/resources/js/pyams_content.min.js --- a/src/pyams_content/skin/resources/js/pyams_content.min.js Fri Jan 20 15:42:51 2017 +0100 +++ b/src/pyams_content/skin/resources/js/pyams_content.min.js Wed Jan 25 11:26:00 2017 +0100 @@ -1,1 +1,1 @@ -(function(c,b){var e=b.MyAMS;var d={TinyMCE:{initEditor:function(f){f.image_list=d.TinyMCE.getImagesList;f.link_list=d.TinyMCE.getLinksList;return f},getImagesList:function(f){return e.ajax.post("get-images-list.json",{},f)},getLinksList:function(f){return e.ajax.post("get-links-list.json",{},f)}},profile:{switchFavorite:function(){var g=c(this);var f=g.data("sequence-oid");e.ajax.post("switch-user-favorite.json",{oid:f},function(h,i){if(h.favorite){g.removeClass("fa-star-o").addClass("fa-star")}else{g.removeClass("fa-star").addClass("fa-star-o")}})}},extfiles:{refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('select[name="form.widgets.files:list"]');var h=f.data("select2");c("").attr("value",g.new_file.id).attr("selected","selected").text(g.new_file.text).appendTo(f);var i=f.select2("data");i.push(g.new_file);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.files,{term:""})},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.extfiles",f));if(h.nb_files>0){g.text("("+h.nb_files+")")}else{g.text("")}}},links:{init:function(){function g(j,h){var i=f.data("select2");i.results.empty();i.opts.populateResults.call(i,i.results,j,{term:""})}var f=c(this);e.ajax.post("get-links.json",{keyFieldName:"id",valueFieldName:"text"},g)},refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('input[name="form.widgets.link"], select[name="form.widgets.links:list"]');var h=f.data("select2");if(f.attr("type")==="select"){c("").attr("value",g.new_link.id).attr("selected","selected").text(g.new_link.text).appendTo(f)}var i;if(f.prop("multiple")){i=f.select2("data");i.push(g.new_link);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.links,{term:""})}else{f.select2("data",[g.new_link]);h.results.empty();h.opts.populateResults.call(h,h.results,g.links,{term:""});f.val(g.new_link.id)}},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.links",f));if(h.nb_links>0){g.text("("+h.nb_links+")")}else{g.text("")}}},galleries:{refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('select[name="form.widgets.galleries:list"]');var h=f.data("select2");c("").attr("value",g.new_gallery.id).attr("selected","selected").text(g.new_gallery.text).appendTo(f);var i=f.select2("data");i.push(g.new_gallery);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.galleries,{term:""})},setOrder:function(h,i){if(i&&i.item.hasClass("already-dropped")){return}var f=i.item.parents(".gallery");var g=c(".image",f).listattr("data-ams-element-name");e.ajax.post(f.data("ams-location")+"/set-images-order.json",{images:JSON.stringify(g)})},removeFile:function(f){return function(){var g=c(this);e.skin.bigBox({title:e.i18n.WARNING,content:'  '+e.i18n.DELETE_WARNING,buttons:e.i18n.BTN_OK_CANCEL},function(k){if(k===e.i18n.BTN_OK){var j=g.parents(".gallery");var i=j.data("ams-location");var l=g.parents(".image");var h=l.data("ams-element-name");e.ajax.post(i+"/delete-element.json",{object_name:h},function(m,n){l.remove()})}})}},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.galleries",f));if(h.nb_galleries>0){g.text("("+h.nb_galleries+")")}else{g.text("")}}},imgmap:{init:function(){var f=c(this);e.ajax.check(c.fn.canvasAreaDraw,"/--static--/pyams_content/js/jquery-canvasAreaDraw"+e.devext+".js",function(){f.canvasAreaDraw({imageUrl:f.data("ams-image-url")})})}},paragraphs:{switchVisibility:function(f){return function(){var i=c(this);var g=i.parents("tr");var h=g.parents("table");e.ajax.post(h.data("ams-location")+"/set-paragraph-visibility.json",{object_name:g.data("ams-element-name")},function(j,k){if(j.visible){c("i",i).attr("class","fa fa-fw fa-eye")}else{c("i",i).attr("class","fa fa-fw fa-eye-slash text-danger")}})}},refreshParagraph:function(h){var g=c('table[id="paragraphs_list"]');var f=c('tr[data-ams-element-name="'+h.object_name+'"]',g);if(h.visible){c("i",c("td.switcher",f)).removeClass("fa-eye-slash text-danger").addClass("fa-eye")}else{c("i",c("td.switcher",f)).removeClass("fa-eye").addClass("fa-eye-slash text-danger")}c("span.title",f).text(h.title||"--")},switchEditor:function(h){var k=c(this);var j=c("i",k);var l=k.parents("td");var i=c(".editor",l);var f=k.parents("tr");if(j.hasClass("fa-plus-square-o")){var g=f.parents("table");i.html('

');e.ajax.post(g.data("ams-location")+"/get-paragraph-editor.json",{object_name:f.data("ams-element-name")},function(m){i.html(m);if(m){e.initContent(i);j.removeClass("fa-plus-square-o").addClass("fa-minus-square-o");f.data("ams-disabled-handlers",true)}})}else{e.skin.cleanContainer(i);i.empty();j.removeClass("fa-minus-square-o").addClass("fa-plus-square-o");f.removeData("ams-disabled-handlers")}},switchAllEditors:function(g){var i=c(this);var h=c("i",i);var f=i.parents("table");if(h.hasClass("fa-plus-square-o")){h.removeClass("fa-plus-square-o").addClass("fa-cog fa-spin");e.ajax.post(f.data("ams-location")+"/get-paragraphs-editors.json",{},function(k){for(var l in k){if(!k.hasOwnProperty(l)){continue}var j=c('tr[data-ams-element-name="'+l+'"]',f);var m=c(".editor",j);if(m.is(":empty")){m.html(k[l]);e.initContent(m)}c(".fa-plus-square-o",j).removeClass("fa-plus-square-o").addClass("fa-minus-square-o");j.data("ams-disabled-handlers",true)}if(!c("i.fa-plus-square-o",c("tbody",f)).exists()){h.removeClass("fa-cog fa-spin").addClass("fa-minus-square-o")}})}else{c(".editor",f).each(function(){e.skin.cleanContainer(c(this));c(this).empty()});c(".fa-minus-square-o",f).removeClass("fa-minus-square-o").addClass("fa-plus-square-o");c("tr",f).removeData("ams-disabled-handlers")}}},themes:{initExtracts:function(h){var g=c('select[name="form.widgets.thesaurus_name:list"]',h);var f=g.val();var j=c('select[name="form.widgets.extract_name:list"]',h);var i=j.val();if(f){e.jsonrpc.post("getExtracts",{thesaurus_name:f},{url:"/api/thesaurus/json"},function(k){j.empty();c(k.result).each(function(){c("").attr("value",this.id).attr("selected",this.id===i).text(this.text).appendTo(j)})})}j.attr("data-ams-events-handlers",'{"select2-open": "PyAMS_content.themes.getExtracts"}')},getExtracts:function(i){var f=c(i.currentTarget);var h=f.parents("form");var g=c('select[name="form.widgets.thesaurus_name:list"]',h).val();if(g){e.jsonrpc.post("getExtracts",{thesaurus_name:g},{url:"/api/thesaurus/json"},function(l){var k=c('select[name="form.widgets.extract_name:list"]',h);var j=k.data("select2");j.results.empty();j.opts.populateResults.call(j,j.results,l.result,{term:""})})}}},review:{timer:null,timer_duration:{general:30000,chat:5000},initComments:function(g){var f=c(".chat-body",g);f.animate({scrollTop:f[0].scrollHeight},1000);clearInterval(d.review.timer);d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.chat);e.skin.registerCleanCallback(d.review.cleanCommentsCallback)},cleanCommentsCallback:function(){clearInterval(d.review.timer);d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.general)},updateComments:function(){var f=c(".badge",'nav a[href="#review-comments.html"]'),h;var g=c(".chat-body",".widget-body");if(g.exists()){h=c(".message",g).length}else{h=parseInt(f.text())}e.ajax.post("get-last-review-comments.json",{count:h},function(i){if(g.exists()){f.removeClass("bg-color-danger").addClass("bg-color-info")}if(h!==i.count){f.text(i.count).removeClass("hidden");if(g.exists()){c(".messages",g).append(i.content);g.animate({scrollTop:g[0].scrollHeight},1000)}if(!g.exists()){f.removeClass("bg-color-info").addClass("bg-color-danger").animate({padding:"3px 12px 2px","margin-right":"9px"},"slow",function(){c(this).animate({padding:"3px 6px 2px","margin-right":"15px"},"slow")})}}})},initCommentData:function(f){var g=c(".chat-body",".widget-body");return{count:c(".message",g).length}},addCommentCallback:function(g){var h=c(this);var i=h.parents(".widget-body");c(".messages",i).append(g.content);c('textarea[name="comment"]',h).val("");var f=c(".chat-body",i);f.animate({scrollTop:f[0].scrollHeight},1000);c(".badge",'nav a[href="#review-comments.html"]').text(g.count).removeClass("hidden")}}};b.PyAMS_content=d;c(b.document).on("PyAMS_content.changed_item",function(g,f){switch(f.object_type){case"paragraph":d.paragraphs.refreshParagraph(f);break;case"extfiles_container":d.extfiles.refreshContainer(f);break;case"links_container":d.links.refreshContainer(f);break;case"galleries_container":d.galleries.refreshContainer(f);break;case"review_comments":d.review.updateComments();break}});var a=c(".badge",'nav a[href="#review-comments.html"]');if(a.exists()){d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.general)}})(jQuery,this); \ No newline at end of file +(function(c,b){var e=b.MyAMS;var d={TinyMCE:{initEditor:function(f){f.image_list=d.TinyMCE.getImagesList;f.link_list=d.TinyMCE.getLinksList;return f},getImagesList:function(f){return e.ajax.post("get-images-list.json",{},f)},getLinksList:function(f){return e.ajax.post("get-links-list.json",{},f)}},profile:{switchFavorite:function(){var g=c(this);var f=g.data("sequence-oid");e.ajax.post("switch-user-favorite.json",{oid:f},function(h,i){if(h.favorite){g.removeClass("fa-star-o").addClass("fa-star")}else{g.removeClass("fa-star").addClass("fa-star-o")}})}},extfiles:{refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('select[name="form.widgets.files:list"]');var h=f.data("select2");c("").attr("value",g.new_file.id).attr("selected","selected").text(g.new_file.text).appendTo(f);var i=f.select2("data");i.push(g.new_file);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.files,{term:""})},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.extfiles",f));if(h.nb_files>0){g.text("("+h.nb_files+")")}else{g.text("")}}},links:{init:function(){function g(j,h){var i=f.data("select2");i.results.empty();i.opts.populateResults.call(i,i.results,j,{term:""})}var f=c(this);e.ajax.post("get-links.json",{keyFieldName:"id",valueFieldName:"text"},g)},refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('input[name="form.widgets.link"], select[name="form.widgets.links:list"]');var h=f.data("select2");if(f.attr("type")==="select"){c("").attr("value",g.new_link.id).attr("selected","selected").text(g.new_link.text).appendTo(f)}var i;if(f.prop("multiple")){i=f.select2("data");i.push(g.new_link);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.links,{term:""})}else{f.select2("data",[g.new_link]);h.results.empty();h.opts.populateResults.call(h,h.results,g.links,{term:""});f.val(g.new_link.id)}},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.links",f));if(h.nb_links>0){g.text("("+h.nb_links+")")}else{g.text("")}}},galleries:{refresh:function(g){if(typeof(g)==="string"){g=JSON.parse(g)}var f=c('select[name="form.widgets.galleries:list"]');var h=f.data("select2");c("").attr("value",g.new_gallery.id).attr("selected","selected").text(g.new_gallery.text).appendTo(f);var i=f.select2("data");i.push(g.new_gallery);f.select2("data",i);h.results.empty();h.opts.populateResults.call(h,h.results,g.galleries,{term:""})},setOrder:function(h,i){if(i&&i.item.hasClass("already-dropped")){return}var f=i.item.parents(".gallery");var g=c(".image",f).listattr("data-ams-element-name");e.ajax.post(f.data("ams-location")+"/set-images-order.json",{images:JSON.stringify(g)})},removeFile:function(f){return function(){var g=c(this);e.skin.bigBox({title:e.i18n.WARNING,content:'  '+e.i18n.DELETE_WARNING,buttons:e.i18n.BTN_OK_CANCEL},function(k){if(k===e.i18n.BTN_OK){var j=g.parents(".gallery");var i=j.data("ams-location");var l=g.parents(".image");var h=l.data("ams-element-name");e.ajax.post(i+"/delete-element.json",{object_name:h},function(m,n){l.remove()})}})}},refreshContainer:function(h){var f=c('tr[data-ams-element-name="'+h.object_name+'"]');var g=c("span.count",c("div.action.galleries",f));if(h.nb_galleries>0){g.text("("+h.nb_galleries+")")}else{g.text("")}}},imgmap:{init:function(){var f=c(this);e.ajax.check(c.fn.canvasAreaDraw,"/--static--/pyams_content/js/jquery-canvasAreaDraw"+e.devext+".js",function(){f.canvasAreaDraw({imageUrl:f.data("ams-image-url")})})},initSummary:function(){var f=c(this);e.ajax.check(c.fn.mapster,"/--static--/pyams_content/js/jquery-imagemapster-1.2.10"+e.devext+".js",function(){f.mapster({fillColor:"ff0000",fillOpacity:0.35,selected:true,highlight:true,staticState:true})})}},paragraphs:{switchVisibility:function(f){return function(){var i=c(this);var g=i.parents("tr");var h=g.parents("table");e.ajax.post(h.data("ams-location")+"/set-paragraph-visibility.json",{object_name:g.data("ams-element-name")},function(j,k){if(j.visible){c("i",i).attr("class","fa fa-fw fa-eye")}else{c("i",i).attr("class","fa fa-fw fa-eye-slash text-danger")}})}},refreshParagraph:function(h){var g=c('table[id="paragraphs_list"]');var f=c('tr[data-ams-element-name="'+h.object_name+'"]',g);if(h.visible){c("i",c("td.switcher",f)).removeClass("fa-eye-slash text-danger").addClass("fa-eye")}else{c("i",c("td.switcher",f)).removeClass("fa-eye").addClass("fa-eye-slash text-danger")}c("span.title",f).text(h.title||"--")},switchEditor:function(h){var k=c(this);var j=c("i",k);var l=k.parents("td");var i=c(".editor",l);var f=k.parents("tr");if(j.hasClass("fa-plus-square-o")){var g=f.parents("table");i.html('

');e.ajax.post(g.data("ams-location")+"/get-paragraph-editor.json",{object_name:f.data("ams-element-name")},function(m){i.html(m);if(m){e.initContent(i);j.removeClass("fa-plus-square-o").addClass("fa-minus-square-o");f.data("ams-disabled-handlers",true)}})}else{e.skin.cleanContainer(i);i.empty();j.removeClass("fa-minus-square-o").addClass("fa-plus-square-o");f.removeData("ams-disabled-handlers")}},switchAllEditors:function(g){var i=c(this);var h=c("i",i);var f=i.parents("table");if(h.hasClass("fa-plus-square-o")){h.removeClass("fa-plus-square-o").addClass("fa-cog fa-spin");e.ajax.post(f.data("ams-location")+"/get-paragraphs-editors.json",{},function(k){for(var l in k){if(!k.hasOwnProperty(l)){continue}var j=c('tr[data-ams-element-name="'+l+'"]',f);var m=c(".editor",j);if(m.is(":empty")){m.html(k[l]);e.initContent(m)}c(".fa-plus-square-o",j).removeClass("fa-plus-square-o").addClass("fa-minus-square-o");j.data("ams-disabled-handlers",true)}if(!c("i.fa-plus-square-o",c("tbody",f)).exists()){h.removeClass("fa-cog fa-spin").addClass("fa-minus-square-o")}})}else{c(".editor",f).each(function(){e.skin.cleanContainer(c(this));c(this).empty()});c(".fa-minus-square-o",f).removeClass("fa-minus-square-o").addClass("fa-plus-square-o");c("tr",f).removeData("ams-disabled-handlers")}}},themes:{initExtracts:function(h){var g=c('select[name="form.widgets.thesaurus_name:list"]',h);var f=g.val();var j=c('select[name="form.widgets.extract_name:list"]',h);var i=j.val();if(f){e.jsonrpc.post("getExtracts",{thesaurus_name:f},{url:"/api/thesaurus/json"},function(k){j.empty();c(k.result).each(function(){c("").attr("value",this.id).attr("selected",this.id===i).text(this.text).appendTo(j)})})}j.attr("data-ams-events-handlers",'{"select2-open": "PyAMS_content.themes.getExtracts"}')},getExtracts:function(i){var f=c(i.currentTarget);var h=f.parents("form");var g=c('select[name="form.widgets.thesaurus_name:list"]',h).val();if(g){e.jsonrpc.post("getExtracts",{thesaurus_name:g},{url:"/api/thesaurus/json"},function(l){var k=c('select[name="form.widgets.extract_name:list"]',h);var j=k.data("select2");j.results.empty();j.opts.populateResults.call(j,j.results,l.result,{term:""})})}}},review:{timer:null,timer_duration:{general:30000,chat:5000},initComments:function(g){var f=c(".chat-body",g);f.animate({scrollTop:f[0].scrollHeight},1000);clearInterval(d.review.timer);d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.chat);e.skin.registerCleanCallback(d.review.cleanCommentsCallback)},cleanCommentsCallback:function(){clearInterval(d.review.timer);d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.general)},updateComments:function(){var f=c(".badge",'nav a[href="#review-comments.html"]'),h;var g=c(".chat-body",".widget-body");if(g.exists()){h=c(".message",g).length}else{h=parseInt(f.text())}e.ajax.post("get-last-review-comments.json",{count:h},function(i){if(g.exists()){f.removeClass("bg-color-danger").addClass("bg-color-info")}if(h!==i.count){f.text(i.count).removeClass("hidden");if(g.exists()){c(".messages",g).append(i.content);g.animate({scrollTop:g[0].scrollHeight},1000)}if(!g.exists()){f.removeClass("bg-color-info").addClass("bg-color-danger").animate({padding:"3px 12px 2px","margin-right":"9px"},"slow",function(){c(this).animate({padding:"3px 6px 2px","margin-right":"15px"},"slow")})}}})},initCommentData:function(f){var g=c(".chat-body",".widget-body");return{count:c(".message",g).length}},addCommentCallback:function(g){var h=c(this);var i=h.parents(".widget-body");c(".messages",i).append(g.content);c('textarea[name="comment"]',h).val("");var f=c(".chat-body",i);f.animate({scrollTop:f[0].scrollHeight},1000);c(".badge",'nav a[href="#review-comments.html"]').text(g.count).removeClass("hidden")}}};b.PyAMS_content=d;c(b.document).on("PyAMS_content.changed_item",function(g,f){switch(f.object_type){case"paragraph":d.paragraphs.refreshParagraph(f);break;case"extfiles_container":d.extfiles.refreshContainer(f);break;case"links_container":d.links.refreshContainer(f);break;case"galleries_container":d.galleries.refreshContainer(f);break;case"review_comments":d.review.updateComments();break}});var a=c(".badge",'nav a[href="#review-comments.html"]');if(a.exists()){d.review.timer=setInterval(d.review.updateComments,d.review.timer_duration.general)}})(jQuery,this); \ No newline at end of file