From 58178a6fdbe9d0816fa069344527e441dbc2f215 Mon Sep 17 00:00:00 2001 From: techfg Date: Mon, 18 Jan 2021 21:52:16 -0800 Subject: [PATCH] Resolves #327 - Add linter, format code, etc. --- .editorconfig | 12 + .eslintignore | 5 + .eslintrc.json | 21 + .gitattributes | 98 +- .github/ISSUE_TEMPLATE/bug_report.md | 18 +- .github/ISSUE_TEMPLATE/feature_request.md | 1 - .github/PULL_REQUEST_TEMPLATE.md | 6 +- .prettierrc.json | 4 + CHANGELOG.md | 582 +- CODE_OF_CONDUCT.md | 28 +- CONTRIBUTING.md | 8 +- README.md | 384 +- SUPPORT.md | 10 +- bower.json | 4 +- dist/jquery.imagemapster.js | 7364 +++--- dist/jquery.imagemapster.min.js | 2 +- dist/jquery.imagemapster.min.js.map | 2 +- examples/altimages.html | 850 +- examples/multiple-maps.html | 42 +- .../jquery.3.5.1.min.js | 0 examples/shapes-ie.html | 255 +- examples/shapes.html | 258 +- examples/stylesheets/base.css | 8 +- examples/tooltips.html | 338 +- examples/usa.html | 1791 +- examples/vegetables.html | 321 +- gruntfile.js | 80 +- jqueryplugin.hbs | 50 +- package-lock.json | 1191 +- package.json | 5 + src/areacorners.js | 313 +- src/areadata.js | 533 +- src/core.js | 2064 +- src/graphics.js | 946 +- src/jqueryextensions.js | 94 +- src/mapdata.js | 1688 +- src/mapimage.js | 469 +- src/scale.js | 460 +- src/tooltip.js | 773 +- src/zepto.js | 182 +- tests/README.md | 3 +- tests/core.tests.js | 278 +- tests/data.tests.js | 99 +- tests/events.tests.js | 182 +- tests/global.tests.js | 147 +- tests/imagemapster-test-runner.html | 1018 +- tests/migrated.tests.js | 592 +- tests/redist/common.utils.1.0.js | 639 +- tests/redist/excanvas.js | 70 +- tests/redist/iqtest-browser-default.js | 7385 +++--- tests/redist/jquery.1.7.1.js | 18530 +++++++-------- tests/redist/jquery.1.9.1.js | 19192 ++++++++-------- tests/redist/when.js | 184 +- tests/redist/zepto.js | 2708 +-- tests/resize.tests.js | 76 +- tests/tooltip.tests.js | 173 +- 56 files changed, 38088 insertions(+), 34448 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .prettierrc.json rename examples/{javascripts => redist}/jquery.3.5.1.min.js (100%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f52a3b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +indent_size = 2 +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e9e08c4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +tests/redist/ +examples/redist/ +build/ +dist/ +node_modules/ \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6fd9c6d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "root": true, + "extends": ["eslint:recommended", "jquery", "prettier"], + "plugins": ["html"], + "reportUnusedDisableDirectives": true, + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + }, + "env": { + "browser": true, + "jquery": true + }, + "globals": {}, + "rules": { + "one-var": ["error", { "var": "consecutive" }], + "strict": ["error", "function"], + "no-nested-ternary": 0, + "camelcase": 0 + } +} diff --git a/.gitattributes b/.gitattributes index f5cdc18..386f838 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,49 +1,49 @@ -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain - -*.jpg binary -*.png binary -*.gif binary - -*.cs -text diff=csharp -*.vb -text -*.c -text -*.cpp -text -*.cxx -text -*.h -text -*.hxx -text -*.py -text -*.rb -text -*.java -text -*.html -text -*.htm -text -*.css -text -*.scss -text -*.sass -text -*.less -text -*.js -text -*.lisp -text -*.clj -text -*.sql -text -*.php -text -*.lua -text -*.m -text -*.asm -text -*.erl -text -*.fs -text -*.fsx -text -*.hs -text - -*.csproj -text merge=union -*.vbproj -text merge=union -*.fsproj -text merge=union -*.dbproj -text merge=union -*.sln -text merge=union +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs -text diff=csharp +*.vb -text +*.c -text +*.cpp -text +*.cxx -text +*.h -text +*.hxx -text +*.py -text +*.rb -text +*.java -text +*.html -text +*.htm -text +*.css -text +*.scss -text +*.sass -text +*.less -text +*.js -text +*.lisp -text +*.clj -text +*.sql -text +*.php -text +*.lua -text +*.m -text +*.asm -text +*.erl -text +*.fs -text +*.fsx -text +*.hs -text + +*.csproj -text merge=union +*.vbproj -text merge=union +*.fsproj -text merge=union +*.dbproj -text merge=union +*.sln -text merge=union diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0df677a..1ad4534 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- @@ -26,4 +26,4 @@ Demonstrate the code is solid. Example: The exact commands you ran and their out **Closing issues** -Put `Closes #XXXX` in your comment to auto-close the issue that your PR fixes/addresses. \ No newline at end of file +Put `Closes #XXXX` in your comment to auto-close the issue that your PR fixes/addresses. diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..32ebab4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "none" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index c77d2de..fd912da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,290 +1,292 @@ -# Changelog - -## Roadmap - -* Return promises from asynchronous events rather than requiring callbacks like "onConfigured" -* Return a non-jQuery object exposing the imagemapster API to simplify coding against it -* Detect input device actively to determine when highlight effect should be enabled -* Rewrite in ES6/Typescript -* Update to modern build system (e.g. rollup/weback/etc.) -* Create React component -* Migrate to modern testframework -* Update website and host on Github - -## Version 2.0.0 (planned) -* **Breaking Change** - [Issue 70](https://github.com/jamietre/ImageMapster/issues/70) Respect target attribute for AREA elements with href. - -## Version 1.3.2 (planned) -* [Issue 328](https://github.com/jamietre/ImageMapster/issues/328) Fix tracking internal map_cache on unbind - -## Version 1.3.1 - 2021.01.10 -* Publish to NPM - -## Version 1.3.0 - 2021.01.10 - -* [Issue 273](https://github.com/jamietre/ImageMapster/issues/273) Add NPM support -* [Issue 318](https://github.com/jamietre/ImageMapster/issues/318) Update to conform with jQuery 3.5.1 (latest release) -* [Issue 319](https://github.com/jamietre/ImageMapster/issues/319) Fix Passive Event Listeners for touchstart/touchend -* [Issue 320](https://github.com/jamietre/ImageMapster/issues/320) Support UMD and improve build system - -## Version 1.2.14 - 2021.01.06 - -* [Issue 148](https://github.com/jamietre/ImageMapster/issues/148) Enable mouseover events when touchscreen found -* [PR 248](https://github.com/jamietre/ImageMapster/pull/248) Add touchstart/touchend support -* [Issue 284](https://github.com/jamietre/ImageMapster/issues/284) Fix size not a function as of jQuery 3.0 -* [PR 263](https://github.com/jamietre/ImageMapster/pull/263) Fix EDGE issue when no map data found -* [Issue 311](https://github.com/jamietre/ImageMapster/issues/311) Update /dist with latest code -* [Issue 312](https://github.com/jamietre/ImageMapster/issues/312) Fix tests -* [Issue 313](https://github.com/jamietre/ImageMapster/issues/313) Fix events not be cleared -* [Issue 314](https://github.com/jamietre/ImageMapster/issues/314) Eliminate when.js dependency from distribution -* [Issue 316](https://github.com/jamietre/ImageMapster/issues/316) Fix AltImage -* [Issue 317](https://github.com/jamietre/ImageMapster/issues/317) Fix USA Example - -## Version 1.2.13 - -* Fix problem with mouseoutdelay=01 - -## Version 1.2.12 - -* Fix issue with $.inArray on IE8 -* Fix problem with boundList when using "set" to toggle a multiple areas at once (from 1.2.11 issue - not quite fixed) - -## Version 1.2.11 - -* Fix problem de-selecting boundlist when using "set" to toggle - -## Version 1.2.10 - -* [Issue 120](https://github.com/jamietre/ImageMapster/issues/114) 1.2.9 broke IE9 - -## Version 1.2.9 - -* [Issue 114](https://github.com/jamietre/ImageMapster/issues/114) Fix jQuery 1.9 compatibility problem - -## Version 1.2.8 - -* [Issue 108](https://github.com/jamietre/ImageMapster/issues/108) Opacity of tooltip container not preserved -* [Issue 107](https://github.com/jamietre/ImageMapster/issues/107) mouseoutDelay broken - -## Version 1.2.7 - -* [Issue 95](https://github.com/jamietre/ImageMapster/issues/95) SingleSelect broken in 1.2.6.099 -* [Issue 87](https://github.com/jamietre/ImageMapster/issues/87) Resize callback not working -* [Issue 84](https://github.com/jamietre/ImageMapster/issues/84) Mouseover events not completely suppressed on mobile -* Tooltip enhancements: tooltips can be called against arbitrary elements. -* AltImage enhancements: see below - -*tooltip-enhancements branch* - -* [Issue 72](https://github.com/jamietre/ImageMapster/issues/72): `scaleMap` not working propery when using bootstrap (css on `body` causing incorrect evaluation of native image size) -* Enhanced tooltip API to allow creating arbitrary tool tips bound to any area, or at an arbitrary position. - -*altimage-enhancements branch* - -* Add `altImages` option that accepts an option defining aliases to alternate images. The name of each property is an alias that can be specified as a valid `altImage` option value elsewhere - -Example use of this option: - - - - altImages: { - roadmap: 'images/usamap-roads.png', - elevation: 'images/uasmap-elevation.png' - } - -then: - - $('img').mapster('set',true,'AZ', { - altImage: 'roadmap' - }); - - -The aliases can also be used in the initial configuration options, both globally and for specific areas. - - -## Version 1.2.6 - 2012.07.13 - -Bug Fixes: - -* [Issue #69](https://github.com/jamietre/ImageMapster/issues/69) `fill` setting not being honored sometimes in IE6-8 -* [Issue #68](https://github.com/jamietre/ImageMapster/issues/68) Accept `areas` array with dangling commas - -## Version 1.2.5 - 2012.06.19 - - - -Bug fixes: - -* [Issue #59](https://github.com/jamietre/ImageMapster/issues/59), [Issue #55](https://github.com/jamietre/ImageMapster/issues/55) Opacity/fade effects not working right in IE8 -* [Issue #58](https://github.com/jamietre/ImageMapster/issues/58) Resize not changing CSS for the `div` contiainer around the image elements -* [Issue #53](https://github.com/jamietre/ImageMapster/issues/53) Not working in Google Chrome with Adblock plugin -* [Issue #44](https://github.com/jamietre/ImageMapster/issues/44) Incorrect opacity with altImage -* [Issue #36](https://github.com/jamietre/ImageMapster/issues/36) Resize firing callback before resize is finished -* Rebind not cleaning up resources properly -* Offset 1 pixel strokes by 0.5 px to prevent the fuzzies -* Ignore UI events during resize - can cause problems if highlights are activated during an effect - - -Features: - -* [Issue #52](https://github.com/jamietre/ImageMapster/issues/52) Add "clickNavigate" feature to allow basic imagemap functionality -* Add "highlight" option to programatically set/unset the highlight effect (as if a user just moused over an area vs. clicked) -* Detect touchscreen devices and disable "mouseover" -* [Issue #11](https://github.com/jamietre/ImageMapster/issues/11) Detect excanvas automatically and force into IE mode if present - -Notes - -* refactor into modular architecture -* tighten up tooltip code a little -* Removed "attrmatches" jQuery selector exetnsion, recoded as a function, removed from tests -* Queue all methods (highlight, data, tooltip) so configuration delays don't cause problems -* Unbind "load" event explicitly from images added. -* Add dynamic images to DOM instead of loading through Javascript -* Ignore missing keys on some operations to increase stability with bad data -* Trim results of string splits so spaces don't cause problems -* Yet more tweaking of image loading detection -* Refactor "graphics" into an object and instantiate for each instance. "load" callbacks were changing event order, resulting in the single instance getting wires crossed. Isolated -each map instance completely, problem solved. -* Fix canvases re-ordered after first selection making effect sometimes inconsistent -* Fix resize bug when area groups are used - -## Version 1.2.4 - 2011.09.27 - -* [Issue #14](https://github.com/jamietre/ImageMapster/issues/14) Resize bug in IE <9 fixed - -## Version 1.2.3 - -* Resize with multiple images affecting other images - fixed - -## Version 1.2.2 - 2011.09.22 - -* masks not working in Firefox 6.0 only. behavior of context.globalCompositeOperation='source-out' and - save/restore somehow changed in ff6. updated code to not depend (possibly) on idiosyncracies of chrome - and ie9. honestly not sure why it worked before as it appears I may have been doing something wrong, - but the code is more explicit now and it works across all browsers. - -## Version 1.2.1 - -* Click callback "this" is not set - fixed -* Replace u.isFunction with $.isFunction to save a few bytes - -## Version 1.2 - -* fixed fader problem for old IE (again, really this time) -* allow selecting includeKeys areas from staticState areas -* test browser features for filter vs. opacity -* "resize" option -* improve startup speed by eliminating need for setTimeout callback -* address startup bug when images aren't loaded and there are lots of images -* fixed exception when "set" with no data for key -* bug when multiple images bound on same page * another IE tweak: blur() on mouseover/click to remove browser-rendered border around area -* force "border=0" on image to ensure consistent display across bind/unbind in IE -* Fixed broken "onMouseover" option, added tests for onMouseover/onMouseout. -* many performance improvements, tests, refactoring some old inefficient code. -* fix css flickering when debinding/rebinding in HTML5 browsers -* add "scaleMap" option to automatically resize image maps when a bound image is resized dynamically. Enabled by default if an image is displayed at a size other than its native size. - -## Version 1.1.3 - -* revised "highlight" method API (previously undocumented). Added tests & documented. -* added a generic prototype for parsing method data to improve consistency & stability -* added tests for tooltip external & event bound invocation -* added invoking tooltip from area, e.g. $('some-area').mapster('tooltip') -* added invoking tooltip from key, e.g. .mapster('tooltip',key); -* Bug fix for get_options, showToolTip (related) -* Bug fix - area id 0 on VML rendereding deselection causes all selections to disappear (introduced in beta 2) -* Changed "get" to return true "selected" state and not "isSelected()" which includes staticState items in selected. -* Bug fix - stroke sometimes rendered improperly when using render-specific options -* change onClick handler to BEFORE action, permit canceling of action by returning false -* refactor into mostly OO design - functional design was getting unwieldy. -* fix bugs related to cascading of "staticState" options -* add "snapshot" option -* check for existing wrapper, skip if it already exists -* remove map data when unbinding+preserveState -- it should act as if not there -* IE performance improvements (optimizing rendering code a little bit) - -## Version 1.1.2 - 2011.06-15 - -* minor bugfix release - -## Version 1.1.1 - 2011.06.03 - -* Performance improvement: cache area groups on map binding to eliminate need for attribute selector -* Significant enhancement to permit complex area grouping and area exclusions (masks): - * added: mapKey can contain multiple keys, allowing an area to be a member of multiple groups - * added: "noHrefIsMask" option to determine "nohref" attribute treatment - * added: "isMask" option (area-specific) - * added: "includeKeys" option (area-specific) -* added: 'highlight' method to enable highlighting of areas from code -* bufgix: fading didn't work in IE6-7, some Operas. Should work in all browsers except IE8 now. -* bugfix: ignore areas with no mapkey when it is provided -* bugfix: not binding properly when no mapkey provided - -## Version 1.1 - -* added: per-action options (highlight, select) -* fixed some memory leaks -* minor performance improvements -* cleanup in VML mode -* fix IE9 canvas support (fader problem) -* fix flickering on fades when moving quickly -* add altImage options -* added onConfigured callback -* fixed problems with cleanup (not removing wrap) -* added failure timeout for configure - -## Version 1.0.10 - 2011.05.12 - -* ignore errors when binding mapster to invalid elements -* minor performance improvements -* fixed command queue problem (broke in 1.0.9) for commands issued after bind, but before image is ready -* enhanced tests - -## Version 1.0.9 - 2011.05.10 - -* added "unbind" option to remove mapster from an image -* add 'options' option -* add 'rebind' option -* add isDeselectable option -* handle exceptions better (when acting on unbound images) -* add 'get' method to retrieve selections -* add unbind options -* clear command queue after processing - -## Version 1.0.8 - 2011.05.05 - -* Handle problem when "img.complete" is not true at config time and "set" commands are issued after initial config call but before config is complete. Queue any "set" commands and process them after the timer callback. -* Pass listTarget to onClick callback even when !isSelectable (previously passed null) -* Pass ref to toolTip element on callback -* Don't show tooltip again if the one for an area is already displayed -* Add singleSelect option - clears any other selected item when a new item is selected - -## Version 1.0.6 - 2011.04.27 - -* Problem when not using mapKey -* staticState=false not working - -## Version 1.0.5 - 2011.04.26 - -* Corrected jquery attribute selector (not using quote marks) -* added area persistence behavior options -* tooltips not working in Firefox - bug in area data management. Deprecated use of jquery.data for passing area-specific options, added "areas" option to replace -* fixed "showToolTip" default property name (was showToolTips) - should have had no effect - -## Version 1.0.4 - 2011.04.20 - -* allow using jQuery object for toolTip text -* happy earth day - -## Version 1.0.3 - -* missing preventDefault on click (post refactor issue) - -## Version 1.0.2 - -* fixed tooltip in IE6 - -## Version 1.0.0 - 2011.04.19 - **first official release** - -* refactored from remaining old to use a clean namespace -* added simple mouseover dialog +# Changelog + +## Roadmap + +- Return promises from asynchronous events rather than requiring callbacks like "onConfigured" +- Return a non-jQuery object exposing the imagemapster API to simplify coding against it +- Detect input device actively to determine when highlight effect should be enabled +- Rewrite in ES6/Typescript +- Update to modern build system (e.g. rollup/weback/etc.) +- Create React component +- Migrate to modern testframework +- Update website and host on Github + +## Version 2.0.0 (planned) + +- **Breaking Change** - [Issue 70](https://github.com/jamietre/ImageMapster/issues/70) Respect target attribute for AREA elements with href. + +## Version 1.3.2 (planned) + +- [Issue 327](https://github.com/jamietre/ImageMapster/issues/327) Add linter & format files +- [Issue 328](https://github.com/jamietre/ImageMapster/issues/328) Fix tracking internal map_cache on unbind +- [Issue 330](https://github.com/jamietre/ImageMapster/issues/330) Fix shapes example rectangle responds to mouseevents + +## Version 1.3.1 - 2021.01.10 + +- Publish to NPM + +## Version 1.3.0 - 2021.01.10 + +- [Issue 273](https://github.com/jamietre/ImageMapster/issues/273) Add NPM support +- [Issue 318](https://github.com/jamietre/ImageMapster/issues/318) Update to conform with jQuery 3.5.1 (latest release) +- [Issue 319](https://github.com/jamietre/ImageMapster/issues/319) Fix Passive Event Listeners for touchstart/touchend +- [Issue 320](https://github.com/jamietre/ImageMapster/issues/320) Support UMD and improve build system + +## Version 1.2.14 - 2021.01.06 + +- [Issue 148](https://github.com/jamietre/ImageMapster/issues/148) Enable mouseover events when touchscreen found +- [PR 248](https://github.com/jamietre/ImageMapster/pull/248) Add touchstart/touchend support +- [Issue 284](https://github.com/jamietre/ImageMapster/issues/284) Fix size not a function as of jQuery 3.0 +- [PR 263](https://github.com/jamietre/ImageMapster/pull/263) Fix EDGE issue when no map data found +- [Issue 311](https://github.com/jamietre/ImageMapster/issues/311) Update /dist with latest code +- [Issue 312](https://github.com/jamietre/ImageMapster/issues/312) Fix tests +- [Issue 313](https://github.com/jamietre/ImageMapster/issues/313) Fix events not be cleared +- [Issue 314](https://github.com/jamietre/ImageMapster/issues/314) Eliminate when.js dependency from distribution +- [Issue 316](https://github.com/jamietre/ImageMapster/issues/316) Fix AltImage +- [Issue 317](https://github.com/jamietre/ImageMapster/issues/317) Fix USA Example + +## Version 1.2.13 + +- Fix problem with mouseoutdelay=01 + +## Version 1.2.12 + +- Fix issue with $.inArray on IE8 +- Fix problem with boundList when using "set" to toggle a multiple areas at once (from 1.2.11 issue - not quite fixed) + +## Version 1.2.11 + +- Fix problem de-selecting boundlist when using "set" to toggle + +## Version 1.2.10 + +- [Issue 120](https://github.com/jamietre/ImageMapster/issues/114) 1.2.9 broke IE9 + +## Version 1.2.9 + +- [Issue 114](https://github.com/jamietre/ImageMapster/issues/114) Fix jQuery 1.9 compatibility problem + +## Version 1.2.8 + +- [Issue 108](https://github.com/jamietre/ImageMapster/issues/108) Opacity of tooltip container not preserved +- [Issue 107](https://github.com/jamietre/ImageMapster/issues/107) mouseoutDelay broken + +## Version 1.2.7 + +- [Issue 95](https://github.com/jamietre/ImageMapster/issues/95) SingleSelect broken in 1.2.6.099 +- [Issue 87](https://github.com/jamietre/ImageMapster/issues/87) Resize callback not working +- [Issue 84](https://github.com/jamietre/ImageMapster/issues/84) Mouseover events not completely suppressed on mobile +- Tooltip enhancements: tooltips can be called against arbitrary elements. +- AltImage enhancements: see below + +_tooltip-enhancements branch_ + +- [Issue 72](https://github.com/jamietre/ImageMapster/issues/72): `scaleMap` not working propery when using bootstrap (css on `body` causing incorrect evaluation of native image size) +- Enhanced tooltip API to allow creating arbitrary tool tips bound to any area, or at an arbitrary position. + +_altimage-enhancements branch_ + +- Add `altImages` option that accepts an option defining aliases to alternate images. The name of each property is an alias that can be specified as a valid `altImage` option value elsewhere + +Example use of this option: + +```js +altImages: { + roadmap: 'images/usamap-roads.png', + elevation: 'images/uasmap-elevation.png' +} +``` + +then: + +```js +$('img').mapster('set', true, 'AZ', { + altImage: 'roadmap' +}); +``` + +The aliases can also be used in the initial configuration options, both globally and for specific areas. + +## Version 1.2.6 - 2012.07.13 + +Bug Fixes: + +- [Issue #69](https://github.com/jamietre/ImageMapster/issues/69) `fill` setting not being honored sometimes in IE6-8 +- [Issue #68](https://github.com/jamietre/ImageMapster/issues/68) Accept `areas` array with dangling commas + +## Version 1.2.5 - 2012.06.19 + +Bug fixes: + +- [Issue #59](https://github.com/jamietre/ImageMapster/issues/59), [Issue #55](https://github.com/jamietre/ImageMapster/issues/55) Opacity/fade effects not working right in IE8 +- [Issue #58](https://github.com/jamietre/ImageMapster/issues/58) Resize not changing CSS for the `div` contiainer around the image elements +- [Issue #53](https://github.com/jamietre/ImageMapster/issues/53) Not working in Google Chrome with Adblock plugin +- [Issue #44](https://github.com/jamietre/ImageMapster/issues/44) Incorrect opacity with altImage +- [Issue #36](https://github.com/jamietre/ImageMapster/issues/36) Resize firing callback before resize is finished +- Rebind not cleaning up resources properly +- Offset 1 pixel strokes by 0.5 px to prevent the fuzzies +- Ignore UI events during resize - can cause problems if highlights are activated during an effect + +Features: + +- [Issue #52](https://github.com/jamietre/ImageMapster/issues/52) Add "clickNavigate" feature to allow basic imagemap functionality +- Add "highlight" option to programatically set/unset the highlight effect (as if a user just moused over an area vs. clicked) +- Detect touchscreen devices and disable "mouseover" +- [Issue #11](https://github.com/jamietre/ImageMapster/issues/11) Detect excanvas automatically and force into IE mode if present + +Notes + +- refactor into modular architecture +- tighten up tooltip code a little +- Removed "attrmatches" jQuery selector exetnsion, recoded as a function, removed from tests +- Queue all methods (highlight, data, tooltip) so configuration delays don't cause problems +- Unbind "load" event explicitly from images added. +- Add dynamic images to DOM instead of loading through Javascript +- Ignore missing keys on some operations to increase stability with bad data +- Trim results of string splits so spaces don't cause problems +- Yet more tweaking of image loading detection +- Refactor "graphics" into an object and instantiate for each instance. "load" callbacks were changing event order, resulting in the single instance getting wires crossed. Isolated + each map instance completely, problem solved. +- Fix canvases re-ordered after first selection making effect sometimes inconsistent +- Fix resize bug when area groups are used + +## Version 1.2.4 - 2011.09.27 + +- [Issue #14](https://github.com/jamietre/ImageMapster/issues/14) Resize bug in IE <9 fixed + +## Version 1.2.3 + +- Resize with multiple images affecting other images - fixed + +## Version 1.2.2 - 2011.09.22 + +- masks not working in Firefox 6.0 only. behavior of context.globalCompositeOperation='source-out' and + save/restore somehow changed in ff6. updated code to not depend (possibly) on idiosyncracies of chrome + and ie9. honestly not sure why it worked before as it appears I may have been doing something wrong, + but the code is more explicit now and it works across all browsers. + +## Version 1.2.1 + +- Click callback "this" is not set - fixed +- Replace u.isFunction with $.isFunction to save a few bytes + +## Version 1.2 + +- fixed fader problem for old IE (again, really this time) +- allow selecting includeKeys areas from staticState areas +- test browser features for filter vs. opacity +- "resize" option +- improve startup speed by eliminating need for setTimeout callback +- address startup bug when images aren't loaded and there are lots of images +- fixed exception when "set" with no data for key +- bug when multiple images bound on same page \* another IE tweak: blur() on mouseover/click to remove browser-rendered border around area +- force "border=0" on image to ensure consistent display across bind/unbind in IE +- Fixed broken "onMouseover" option, added tests for onMouseover/onMouseout. +- many performance improvements, tests, refactoring some old inefficient code. +- fix css flickering when debinding/rebinding in HTML5 browsers +- add "scaleMap" option to automatically resize image maps when a bound image is resized dynamically. Enabled by default if an image is displayed at a size other than its native size. + +## Version 1.1.3 + +- revised "highlight" method API (previously undocumented). Added tests & documented. +- added a generic prototype for parsing method data to improve consistency & stability +- added tests for tooltip external & event bound invocation +- added invoking tooltip from area, e.g. $('some-area').mapster('tooltip') +- added invoking tooltip from key, e.g. .mapster('tooltip',key); +- Bug fix for get_options, showToolTip (related) +- Bug fix - area id 0 on VML rendereding deselection causes all selections to disappear (introduced in beta 2) +- Changed "get" to return true "selected" state and not "isSelected()" which includes staticState items in selected. +- Bug fix - stroke sometimes rendered improperly when using render-specific options +- change onClick handler to BEFORE action, permit canceling of action by returning false +- refactor into mostly OO design - functional design was getting unwieldy. +- fix bugs related to cascading of "staticState" options +- add "snapshot" option +- check for existing wrapper, skip if it already exists +- remove map data when unbinding+preserveState -- it should act as if not there +- IE performance improvements (optimizing rendering code a little bit) + +## Version 1.1.2 - 2011.06-15 + +- minor bugfix release + +## Version 1.1.1 - 2011.06.03 + +- Performance improvement: cache area groups on map binding to eliminate need for attribute selector +- Significant enhancement to permit complex area grouping and area exclusions (masks): + - added: mapKey can contain multiple keys, allowing an area to be a member of multiple groups + - added: "noHrefIsMask" option to determine "nohref" attribute treatment + - added: "isMask" option (area-specific) + - added: "includeKeys" option (area-specific) +- added: 'highlight' method to enable highlighting of areas from code +- bufgix: fading didn't work in IE6-7, some Operas. Should work in all browsers except IE8 now. +- bugfix: ignore areas with no mapkey when it is provided +- bugfix: not binding properly when no mapkey provided + +## Version 1.1 + +- added: per-action options (highlight, select) +- fixed some memory leaks +- minor performance improvements +- cleanup in VML mode +- fix IE9 canvas support (fader problem) +- fix flickering on fades when moving quickly +- add altImage options +- added onConfigured callback +- fixed problems with cleanup (not removing wrap) +- added failure timeout for configure + +## Version 1.0.10 - 2011.05.12 + +- ignore errors when binding mapster to invalid elements +- minor performance improvements +- fixed command queue problem (broke in 1.0.9) for commands issued after bind, but before image is ready +- enhanced tests + +## Version 1.0.9 - 2011.05.10 + +- added "unbind" option to remove mapster from an image +- add 'options' option +- add 'rebind' option +- add isDeselectable option +- handle exceptions better (when acting on unbound images) +- add 'get' method to retrieve selections +- add unbind options +- clear command queue after processing + +## Version 1.0.8 - 2011.05.05 + +- Handle problem when "img.complete" is not true at config time and "set" commands are issued after initial config call but before config is complete. Queue any "set" commands and process them after the timer callback. +- Pass listTarget to onClick callback even when !isSelectable (previously passed null) +- Pass ref to toolTip element on callback +- Don't show tooltip again if the one for an area is already displayed +- Add singleSelect option - clears any other selected item when a new item is selected + +## Version 1.0.6 - 2011.04.27 + +- Problem when not using mapKey +- staticState=false not working + +## Version 1.0.5 - 2011.04.26 + +- Corrected jquery attribute selector (not using quote marks) +- added area persistence behavior options +- tooltips not working in Firefox - bug in area data management. Deprecated use of jquery.data for passing area-specific options, added "areas" option to replace +- fixed "showToolTip" default property name (was showToolTips) - should have had no effect + +## Version 1.0.4 - 2011.04.20 + +- allow using jQuery object for toolTip text +- happy earth day + +## Version 1.0.3 + +- missing preventDefault on click (post refactor issue) + +## Version 1.0.2 + +- fixed tooltip in IE6 + +## Version 1.0.0 - 2011.04.19 - **first official release** + +- refactored from remaining old to use a clean namespace +- added simple mouseover dialog diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index edf4ed8..58c165f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities @@ -73,4 +73,4 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq. \ No newline at end of file +https://www.contributor-covenant.org/faq. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cda21e4..4e7e9f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -The following is a set of guidelines for contributing to ImageMapster. +The following is a set of guidelines for contributing to ImageMapster. Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. @@ -28,13 +28,13 @@ Guidelines for bug reports: A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS experience the problem? Do other browsers show the bug differently? What would you expect to be the outcome? All these details will help people to fix any potential bugs. -Once you have established that there is a potential issue, please use the [bug report](https://github.com/jamietre/ImageMapster/issues/new?template=bug_report.md) issue template and supply all requested information. If the required information is not provided, your issue will be marked with the 'needs author feedback' label and closed until there is enough information for the team to evaluate/reproduce. +Once you have established that there is a potential issue, please use the [bug report](https://github.com/jamietre/ImageMapster/issues/new?template=bug_report.md) issue template and supply all requested information. If the required information is not provided, your issue will be marked with the 'needs author feedback' label and closed until there is enough information for the team to evaluate/reproduce. ## Feature requests Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. -Please use the [feature request](https://github.com/jamietre/ImageMapster/issues/new?template=feature_request.md) issue template and supply all requested information. If the required information is not provided, your issue will be marked with the 'needs author feedback' label and closed until there is enough information to evaluate your proposal. +Please use the [feature request](https://github.com/jamietre/ImageMapster/issues/new?template=feature_request.md) issue template and supply all requested information. If the required information is not provided, your issue will be marked with the 'needs author feedback' label and closed until there is enough information to evaluate your proposal. ## Pull requests @@ -44,7 +44,7 @@ Please use the [pull request](https://github.com/jamietre/ImageMapster/pulls/new **Please ask first** before embarking on any significant pull request (e.g. implementing features or refactoring code), otherwise you risk spending a lot of time working on something that the project's maintainers might not want to merge into the project. -In lieu of a formal style-guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. If the type of unit test required to support your pull request is not supported by the current test framework (iqTest), please add an example covering the functionality. Please make sure your changes build by running `grunt build` as described in the [development section](README.md#development) of the [Readme](README.md). +In lieu of a formal style-guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. If the type of unit test required to support your pull request is not supported by the current test framework (iqTest), please add an example covering the functionality. Please make sure your changes build by running `grunt build` as described in the [development section](README.md#development) of the [Readme](README.md). ## License diff --git a/README.md b/README.md index 36a8946..f49de0c 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,196 @@ -# ImageMapster: A jQuery Plugin to make image maps useful - -[![license](https://img.shields.io/github/license/jamietre/ImageMapster)](https://github.com/jamietre/ImageMapster/blob/master/LICENSE) -[![gh stable](https://img.shields.io/github/v/release/jamietre/imagemapster?sort=semver&label=stable)](https://GitHub.com/jamietre/ImageMapster/releases/) -[![gh latest](https://img.shields.io/github/v/release/jamietre/imagemapster?include_prereleases&sort=semver&label=latest)](https://GitHub.com/jamietre/ImageMapster/releases/) -[![npm downloads](https://img.shields.io/npm/dm/imagemapster?label=npm)](https://www.npmjs.com/package/imagemapster) -[![jsDelivr downloads](https://data.jsdelivr.com/v1/package/npm/imagemapster/badge?style=rounded)](https://www.jsdelivr.com/package/npm/imagemapster) -[![cdnjs version](https://img.shields.io/cdnjs/v/imagemapster.svg?color=orange)](https://cdnjs.com/libraries/imagemapster) - -ImageMapster activates the areas in HTML imagemaps so you can highlight and select them. It has lots of other features for manual control, tooltips, resizing, and more. It is designed to be compatible with every common platform, and is tested with Internet Explorer 6-10, Firefox 3.0+, Safari, Opera, and Chrome. It works on mobile devices and doesn't use Flash. - -## Release Information - -See the [change log](https://github.com/jamietre/ImageMapster/blob/master/CHANGELOG.md) for details on the release history and roadmap. - -Read the [release notes](http://blog.outsharked.com/2012/06/imagemapster-125-released.html) for 1.2.5, the last significant feature update. - -## Getting Started - -### Installation - -#### NPM -This package can be installed via NPM: - -```sh -npm install jquery imagemapster --save -``` - -#### Browser - -Download the latest version of ImageMapster from the [Releases](https://github.com/jamietre/ImageMapster/releases) page and include in your webpage: - -```html - - -``` - -Alternatively, you can include ImageMapster from one of the following CDNs: - -1. [jsDelivr](https://www.jsdelivr.com/package/npm/imagemapster) - https://www.jsdelivr.com/package/npm/imagemapster -2. [cdnjs](https://cdnjs.com/libraries/imagemapster) - https://cdnjs.com/libraries/imagemapster - -### Usage - -Activate all image maps on the page with default options: on mouseover areas are highlighted with a gray fill with no border, and clicking an area causes it to become selected. - -```js -$('img').mapster(); -``` - -Activate image maps with some specific options. - -```js -$('img').mapster( { - fillColor: 'ff0000', - stroke: true, - singleSelect: true -}); -``` - -#### Methods - -There are lots of ways to manipulate the imagemap from Javascript. Here area a few, see the [ImageMapster web site](http://www.outsharked.com/imagemapster) for complete documentation. - -**select**: Cause an area to become "selected" - -```js -$('area').mapster('select'); -``` - -Programatically select elements from the image map. The programmatic selection/deselection methods will not honor the staticState property. - -**deselect**: Cause an area to become "selected" - -```js -$('area').mapster('deselect'); -``` - -**set**: select or deselect an element. If `selected` is true, the area is selected, if false, it is deselected. - -```js -$('area').mapster('set',selected); -``` - -You can also select or deselect areas using a their `mapKey`. This is an attribute on each area in your HTML that identifies it. You define a mapKey using a configuration option: `mapKey: 'data-key'`. - -```js -$('img').mapster('set',true,'key1,key2'); -``` - -If two areas share the same value for the `mapKey` they will be automatically grouped together when activated. You can also use the values of the mapKey to select areas from code. - -You can pass options to change the rendering effects when using set as the last parameter: - -```js -$('img').mapster('set',true,'key', {fillColor: 'ff0000'} ); -``` - -MapKeys can contain more than one value. The first value always defines groups when you mouse over. Other values can be used to create logical groups. For example: - -```html - - - - - - - - - - -``` - -```js -$('#usamap').mapster( { mapKey: 'data-key' } ); -``` - -Mousing over each state would cause just that state to be higlighted. You can also select other logical groups from code code: - -```js -// select all New England states -$('img').mapster('set',true,'new-england'); - -// select just Maine, New Hampshire & Vermont -$('img').mapster('set',true,'really-cold'); -``` - -Groups created this way are *independent* of the primary group. If you select "new-england" from code, you can't unselect just "MA" by clicking on it. You would have to unselect "new-england" from code. - -To simply indentify a set of areas to turn on or off, but not treat them as a logical group, you can use CSS classes and select areas directly, or use the keys option to identify the primary keys associated with a group (see documentation). - -#### Options - -Please see the [ImageMapster web site](http://www.outsharked.com/imagemapster/default.aspx?docs.html) for complete documentation. - -## Examples - -ImageMapster includes several examples. To view the examples: - -1. Clone the repo -2. Install NPM dependencies - `npm install` -3. Open [index.html](examples/index.html) directly from your file system in a browser - -## Zepto Compatibility - -Newer versions of Zepto don't seem to work any more (as of 1.2.5). I didn't want this to hold up the ever-delayed release even further so I didn't figure out why. - -In theory it should work; you need to use the "jquery.imagemapster.zepto.js" build. This patches a few holes in Zepto that ImageMapster needs. It is safe to use the zepto version with jQuery. - -To generate a Zepto build of ImageMapster: - -1. Clone the repository -2. Install NPM dependencies - `npm install` -3. Generate a full build - `npm run fullbuild` - -## Find out More - -Please see how to obtain [ImageMapster Support](SUPPORT.md). - -## Contributing - -Please see our [Contributing Guidelines](CONTRIBUTING.md). - -## Development - -### Build - -The source code is broken into several modules to make management easier and to make it possible to create feature-targeted builds. ImageMapster is built using grunt and can be invoked as follows: - -1. Clone the repo -2. Install NPM dependencies - `npm install` -3. Install [Grunt Cli](https://gruntjs.com/getting-started) - `npm install -g grunt-cli` -4. Generate a Build: - - Release Build (compressed/uncompressed/sourcemap for jQuery) - `grunt build` - - Full Release Build (compressed/uncompressed/sourcemap for jQuery & Zepto) - `grunt fullbuild` - - jQuery Dev Build (uncompressed only) - `grunt jquery` - - Zepto Dev Build (uncompressed only) - `grunt zepto` - -### Debug - -1. Clone the repo -2. Install NPM dependencies - `npm install` -3. Install [Grunt Cli](https://gruntjs.com/getting-started) - `npm install -g grunt-cli` -4. Run the debug task - `grunt debug` - -## License - -Copyright © 2011-21 [James Treworgy](https://github.com/jamietre). Licensed under the [MIT License](LICENSE). \ No newline at end of file +# ImageMapster: A jQuery Plugin to make image maps useful + +[![license](https://img.shields.io/github/license/jamietre/ImageMapster)](https://github.com/jamietre/ImageMapster/blob/master/LICENSE) +[![gh stable](https://img.shields.io/github/v/release/jamietre/imagemapster?sort=semver&label=stable)](https://GitHub.com/jamietre/ImageMapster/releases/) +[![gh latest](https://img.shields.io/github/v/release/jamietre/imagemapster?include_prereleases&sort=semver&label=latest)](https://GitHub.com/jamietre/ImageMapster/releases/) +[![npm downloads](https://img.shields.io/npm/dm/imagemapster?label=npm)](https://www.npmjs.com/package/imagemapster) +[![jsDelivr downloads](https://data.jsdelivr.com/v1/package/npm/imagemapster/badge?style=rounded)](https://www.jsdelivr.com/package/npm/imagemapster) +[![cdnjs version](https://img.shields.io/cdnjs/v/imagemapster.svg?color=orange)](https://cdnjs.com/libraries/imagemapster) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) + +ImageMapster activates the areas in HTML imagemaps so you can highlight and select them. It has lots of other features for manual control, tooltips, resizing, and more. It is designed to be compatible with every common platform, and is tested with Internet Explorer 6-10, Firefox 3.0+, Safari, Opera, and Chrome. It works on mobile devices and doesn't use Flash. + +## Release Information + +See the [change log](https://github.com/jamietre/ImageMapster/blob/master/CHANGELOG.md) for details on the release history and roadmap. + +Read the [release notes](http://blog.outsharked.com/2012/06/imagemapster-125-released.html) for 1.2.5, the last significant feature update. + +## Getting Started + +### Installation + +#### NPM + +This package can be installed via NPM: + +```sh +npm install jquery imagemapster --save +``` + +#### Browser + +Download the latest version of ImageMapster from the [Releases](https://github.com/jamietre/ImageMapster/releases) page and include in your webpage: + +```html + + +``` + +Alternatively, you can include ImageMapster from one of the following CDNs: + +1. [jsDelivr](https://www.jsdelivr.com/package/npm/imagemapster) - https://www.jsdelivr.com/package/npm/imagemapster +2. [cdnjs](https://cdnjs.com/libraries/imagemapster) - https://cdnjs.com/libraries/imagemapster + +### Usage + +Activate all image maps on the page with default options: on mouseover areas are highlighted with a gray fill with no border, and clicking an area causes it to become selected. + +```js +$('img').mapster(); +``` + +Activate image maps with some specific options. + +```js +$('img').mapster({ + fillColor: 'ff0000', + stroke: true, + singleSelect: true +}); +``` + +#### Methods + +There are lots of ways to manipulate the imagemap from Javascript. Here area a few, see the [ImageMapster web site](http://www.outsharked.com/imagemapster) for complete documentation. + +**select**: Cause an area to become "selected" + +```js +$('area').mapster('select'); +``` + +Programatically select elements from the image map. The programmatic selection/deselection methods will not honor the staticState property. + +**deselect**: Cause an area to become "selected" + +```js +$('area').mapster('deselect'); +``` + +**set**: select or deselect an element. If `selected` is true, the area is selected, if false, it is deselected. + +```js +$('area').mapster('set', selected); +``` + +You can also select or deselect areas using a their `mapKey`. This is an attribute on each area in your HTML that identifies it. You define a mapKey using a configuration option: `mapKey: 'data-key'`. + +```js +$('img').mapster('set', true, 'key1,key2'); +``` + +If two areas share the same value for the `mapKey` they will be automatically grouped together when activated. You can also use the values of the mapKey to select areas from code. + +You can pass options to change the rendering effects when using set as the last parameter: + +```js +$('img').mapster('set', true, 'key', { fillColor: 'ff0000' }); +``` + +MapKeys can contain more than one value. The first value always defines groups when you mouse over. Other values can be used to create logical groups. For example: + +```html + + + + + + + + + + +``` + +```js +$('#usamap').mapster({ mapKey: 'data-key' }); +``` + +Mousing over each state would cause just that state to be higlighted. You can also select other logical groups from code code: + +```js +// select all New England states +$('img').mapster('set', true, 'new-england'); + +// select just Maine, New Hampshire & Vermont +$('img').mapster('set', true, 'really-cold'); +``` + +Groups created this way are _independent_ of the primary group. If you select "new-england" from code, you can't unselect just "MA" by clicking on it. You would have to unselect "new-england" from code. + +To simply indentify a set of areas to turn on or off, but not treat them as a logical group, you can use CSS classes and select areas directly, or use the keys option to identify the primary keys associated with a group (see documentation). + +#### Options + +Please see the [ImageMapster web site](http://www.outsharked.com/imagemapster/default.aspx?docs.html) for complete documentation. + +## Examples + +ImageMapster includes several examples. To view the examples: + +1. Clone the repo +2. Open [index.html](examples/index.html) directly from your file system in a browser + +## Zepto Compatibility + +Newer versions of Zepto don't seem to work any more (as of 1.2.5). I didn't want this to hold up the ever-delayed release even further so I didn't figure out why. + +In theory it should work; you need to use the "jquery.imagemapster.zepto.js" build. This patches a few holes in Zepto that ImageMapster needs. It is safe to use the zepto version with jQuery. + +To generate a Zepto build of ImageMapster: + +1. Clone the repository +2. Install NPM dependencies - `npm install` +3. Generate a full build - `npm run fullbuild` + +## Find out More + +Please see how to obtain [ImageMapster Support](SUPPORT.md). + +## Contributing + +Please see our [Contributing Guidelines](CONTRIBUTING.md). + +## Development + +### Build + +The source code is broken into several modules to make management easier and to make it possible to create feature-targeted builds. ImageMapster is built using grunt and can be invoked as follows: + +1. Clone the repo +2. Install NPM dependencies - `npm install` +3. Install [Grunt Cli](https://gruntjs.com/getting-started) - `npm install -g grunt-cli` +4. Generate a Build: + - Release Build (compressed/uncompressed/sourcemap for jQuery) - `grunt build` + - Full Release Build (compressed/uncompressed/sourcemap for jQuery & Zepto) - `grunt fullbuild` + - jQuery Dev Build (uncompressed only) - `grunt jquery` + - Zepto Dev Build (uncompressed only) - `grunt zepto` + +### Debug + +1. Clone the repo +2. Install NPM dependencies - `npm install` +3. Install [Grunt Cli](https://gruntjs.com/getting-started) - `npm install -g grunt-cli` +4. Run the debug task - `grunt debug` + +## License + +Copyright © 2011-21 [James Treworgy](https://github.com/jamietre). Licensed under the [MIT License](LICENSE). diff --git a/SUPPORT.md b/SUPPORT.md index 93a2095..6bbfc0f 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -2,7 +2,7 @@ **Please do not use the issue tracker for personal support requests.** -Have questions? Looking for support for ImageMapster? +Have questions? Looking for support for ImageMapster? - There are lots of examples and documentation on the [ImageMapster web site](http://www.outsharked.com/imagemapster). @@ -10,10 +10,10 @@ Have questions? Looking for support for ImageMapster? - Take a look at [ImageMapster questions on StackOverflow](http://stackoverflow.com/search?q=imagemapster); there are quite a few. Maybe someone's asked the same question already. -- There are also some very detailed discussions in the [GitHub issues](https://github.com/jamietre/imagemapster/issues?direction=desc&labels=support&page=1&sort=created&state=closed) section that I've flagged as "support". The issues section is no longer the way to request support but these issues can provide valuable insight. +- There are also some very detailed discussions in the [GitHub issues](https://github.com/jamietre/imagemapster/issues?direction=desc&labels=support&page=1&sort=created&state=closed) section that I've flagged as "support". The issues section is no longer the way to request support but these issues can provide valuable insight. -- You can also check the [feedback](http://www.outsharked.com/imagemapster/default.aspx?feedback.html) page on the project web site. +- You can also check the [feedback](http://www.outsharked.com/imagemapster/default.aspx?feedback.html) page on the project web site. -- You can review the [source code on GitHub](https://github.com/jamietre/ImageMapster). +- You can review the [source code on GitHub](https://github.com/jamietre/ImageMapster). -If you identify a bug, please file a [bug report](https://github.com/jamietre/ImageMapster/issues/new?template=bug_report.md) and if you'd like to see a new feature, please file a [feature request](https://github.com/jamietre/ImageMapster/issues/new?template=feature_request.md)! \ No newline at end of file +If you identify a bug, please file a [bug report](https://github.com/jamietre/ImageMapster/issues/new?template=bug_report.md) and if you'd like to see a new feature, please file a [feature request](https://github.com/jamietre/ImageMapster/issues/new?template=feature_request.md)! diff --git a/bower.json b/bower.json index 31b5487..dcdc6f7 100644 --- a/bower.json +++ b/bower.json @@ -20,9 +20,7 @@ "resize", "tooltip" ], - "authors": [ - "James Treworgy " - ], + "authors": ["James Treworgy "], "license": "MIT", "ignore": [ "**/.*", diff --git a/dist/jquery.imagemapster.js b/dist/jquery.imagemapster.js index 9be343e..3231b28 100644 --- a/dist/jquery.imagemapster.js +++ b/dist/jquery.imagemapster.js @@ -5,3484 +5,3713 @@ * License: MIT */ (function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof module === 'object' && module.exports) { - // Node/CommonJS - module.exports = function( root, jQuery ) { - if ( jQuery === undefined ) { - // require('jQuery') returns a factory that requires window to - // build a jQuery instance, we normalize how we use modules - // that require this pattern but the window provided is a noop - // if it's defined (how jquery works) - if ( typeof window !== 'undefined' ) { - jQuery = require('jquery'); - } - else { - jQuery = require('jquery')(root); - } - } - factory(jQuery); - return jQuery; - }; - } else { - // Browser globals - factory(jQuery); - } -}(function (jQuery) { - (function ($) { - // Test via a getter in the options object to see if the passive property is accessed - var supportsPassive = false; - try { - var opts = Object.defineProperty({}, 'passive', { - get: function() { - supportsPassive = true; - } - }); - window.addEventListener("testPassive.mapster", function() {}, opts); - window.removeEventListener("testPassive.mapster", function() {}, opts); - } catch (e) {} - - if (supportsPassive) { - // In order to not interrupt scrolling on touch devices - // we commit to not calling preventDefault from within listeners - // There is a plan to handle this natively in jQuery 4.0 but for - // now we are on our own. - // TODO: Migrate to jQuery 4.0 approach if/when released - // https://www.chromestatus.com/feature/5745543795965952 - // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md - // https://github.com/jquery/jquery/issues/2871#issuecomment-175175180 - // https://jsbin.com/bupesajoza/edit?html,js,output - var setupListener = function(ns, type, listener) { - if (ns.includes("noPreventDefault")) { - this.addEventListener(type, listener, { passive: true }); - } else { - console.warn("non-passive events - listener not added"); - return false; - } - }; - - // special events for noPreventDefault - $.event.special.touchstart = { - setup: function (_, ns, listener) { - return setupListener(ns, "touchstart", listener); - } - }; - $.event.special.touchend = { - setup: function (_, ns, listener) { - return setupListener(ns, "touchend", listener); - } - }; - } -}(jQuery)); - -/* ImageMapster core */ - -/*jslint laxbreak: true, evil: true, unparam: true */ - -/*global jQuery: true, Zepto: true */ - - -(function ($) { - var mapster_version = '1.3.2-beta.0'; - - // all public functions in $.mapster.impl are methods - $.fn.mapster = function (method) { - var m = $.mapster.impl; - if ($.mapster.utils.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: mapster_version, - 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, - 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: { - all: function(deferredArray) { - return Promise.all(deferredArray); - }, - defer: function() { - // Deferred is frequently referred to as an anti-pattern largely - // due to error handling, however to avoid reworking existing - // APIs and support backwards compat, creating a "deferred" - // polyfill via native promise - var Deferred = function() { - this.promise = new Promise((function(resolve, reject) { - this.resolve = resolve; - this.reject = reject; - }).bind(this)); - - this.then = this.promise.then.bind(this.promise); - this.catch = this.promise.catch.bind(this.promise); - }; - return new Deferred(); - } - }, - defer: function() { - return this.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++) { - // backwards compat for $.trim which would return empty string on null - // which theoertically should not happen here - el = arr[i] ? arr[i].trim() : ''; - 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"); - }, - /** - * Basic indexOf implementation for IE7-8. Though we use $.inArray, some jQuery versions will try to - * use a prototpye on the calling object, defeating the purpose of using $.inArray in the first place. - * - * This will be replaced with the array prototype if it's available. - * - * @param {Array} arr The array to search - * @param {Object} target The item to search for - * @return {Number} The index of the item, or -1 if not found - */ - indexOf: function(arr,target){ - if (Array.prototype.indexOf) { - return Array.prototype.indexOf.call(arr, target); - } else { - for(var i=0; i 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).prop(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; - $('*').off(); - } - }; - - // 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 >= map_data.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); - } - }); - } + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = function( root, jQuery ) { + if ( jQuery === undefined ) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if ( typeof window !== 'undefined' ) { + jQuery = require('jquery'); } - 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) { - var newState = selected; - if (ar) { - switch (selected) { - case true: - ar.select(opts); break; - case false: - ar.deselect(true); break; - default: - newState = ar.toggle(opts); break; - } - return newState; - } - } - 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) { - var newState = setSelection(el); - if (map_data.options.boundList) { - m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), newState); - } - }); - if (!selected) { - map_data.removeSelectionFinish(); - } - - } - - 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.on(); - 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.length > 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; - }; - - $.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 + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; + } else { + // Browser globals + factory(jQuery); + } +}(function (jQuery) { + /* + jqueryextensions.js + Extend/intercept jquery behavior */ (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(); + 'use strict'; + + // Test via a getter in the options object to see if the passive property is accessed + var supportsPassive = false; + try { + var opts = Object.defineProperty({}, 'passive', { + get: function () { + supportsPassive = true; + return true; + } + }); + window.addEventListener('testPassive.mapster', function () {}, opts); + window.removeEventListener('testPassive.mapster', function () {}, opts); + } catch (e) { + // intentionally ignored + } + + if (supportsPassive) { + // In order to not interrupt scrolling on touch devices + // we commit to not calling preventDefault from within listeners + // There is a plan to handle this natively in jQuery 4.0 but for + // now we are on our own. + // TODO: Migrate to jQuery 4.0 approach if/when released + // https://www.chromestatus.com/feature/5745543795965952 + // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md + // https://github.com/jquery/jquery/issues/2871#issuecomment-175175180 + // https://jsbin.com/bupesajoza/edit?html,js,output + var setupListener = function (ns, type, listener) { + if (ns.includes('noPreventDefault')) { + window.addEventListener(type, listener, { passive: true }); + } else { + console.warn('non-passive events - listener not added'); + return false; + } }; + // special events for noPreventDefault + $.event.special.touchstart = { + setup: function (_, ns, listener) { + return setupListener(ns, 'touchstart', listener); + } + }; + $.event.special.touchend = { + setup: function (_, ns, listener) { + return setupListener(ns, 'touchend', listener); + } + }; + } +})(jQuery); - 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 - */ +/* + core.js + ImageMapster core +*/ - _add: function(image) { - var index = ap.push.call(this,image)-1; - this.status[index] = false; - return index; - }, +(function ($) { + 'use strict'; - /** - * 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 - */ + var mapster_version = '1.3.2-beta.0'; - indexOf: function(image) { - return u.indexOf(this,image); + // all public functions in $.mapster.impl are methods + $.fn.mapster = function (method) { + var m = $.mapster.impl; + if ($.mapster.utils.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: mapster_version, + 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, + map_cache: [], + hooks: {}, + addHook: function (name, callback) { + this.hooks[name] = (this.hooks[name] || []).push(callback); + }, + callHooks: function (name, context) { + $.each(this.hooks[name] || [], function (_, e) { + e.apply(context); + }); + }, + utils: { + when: { + all: function (deferredArray) { + // TODO: Promise breaks ES5 support + // eslint-disable-next-line no-undef + return Promise.all(deferredArray); }, - - /** - * 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]; - }); + defer: function () { + // Deferred is frequently referred to as an anti-pattern largely + // due to error handling, however to avoid reworking existing + // APIs and support backwards compat, creating a "deferred" + // polyfill via native promise + var Deferred = function () { + // TODO: Promise breaks ES5 support + // eslint-disable-next-line no-undef + this.promise = new Promise( + function (resolve, reject) { + this.resolve = resolve; + this.reject = reject; + }.bind(this) + ); + + this.then = this.promise.then.bind(this.promise); + this.catch = this.promise.catch.bind(this.promise); + }; + return new Deferred(); + } + }, + defer: function () { + return this.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++) { + // backwards compat for $.trim which would return empty string on null + // which theoertically should not happen here + el = arr[i] ? arr[i].trim() : ''; + 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 (_, 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]; + } } - - /** - * 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 - .on('load',function(e) { - me.imageLoaded.call(me,e); - }) - .on('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]); + }); + }); + return target; + }, + isElement: function (o) { + return typeof HTMLElement === 'object' + ? o instanceof HTMLElement + : o && + typeof o === 'object' && + o.nodeType === 1 && + typeof o.nodeName === 'string'; + }, + /** + * Basic indexOf implementation for IE7-8. Though we use $.inArray, some jQuery versions will try to + * use a prototpye on the calling object, defeating the purpose of using $.inArray in the first place. + * + * This will be replaced with the array prototype if it's available. + * + * @param {Array} arr The array to search + * @param {Object} target The item to search for + * @return {Number} The index of the item, or -1 if not found + */ + indexOf: function (arr, target) { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(arr, target); + } else { + for (var i = 0; i < arr.length; i++) { + if (arr[i] === target) { + return i; } - 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 -1; + } + }, + + // 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'; + }, + isFunction: function (obj) { + return typeof obj === 'function'; + }, + // 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 (this.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 (_, e) { + if (typeof e.opacity !== 'undefined') { + e.opacity = opacity; + } else { + $(e).css('opacity', opacity); } - 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(); + }); + } + }, + + // 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; } - }, - - /** - * Event handler for image onload - * @param {object} e jQuery event data - */ - imageLoaded: function(e) { - var me=this, - index = me.indexOf(e.target); + endOp = endOp || 1; - if (index>=0) { + op = + op + endOp / cbIntervals > endOp - 0.01 + ? endOp + : op + endOp / cbIntervals; - 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); + u.setOpacity(obj, op); + if (op < endOp) { + setTimeout(function () { + fade_func(el, op, endOp, duration); + }, 15); } - // 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]; + }; + 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 (_, e) { + for (index = 0; index < list.length; index++) { + key = list[index]; + if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { + result = result.add(e); + } } - }; - } (jQuery)); - -/* mapdata.js - the MapData object, repesents an instance of a single bound imagemap -*/ - - -(function ($) { - - var m = $.mapster, - u = m.utils; - + }); + 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 (_, e) { + if (opts.listSelectedClass) { + if (selected) { + $(e).addClass(opts.listSelectedClass); + } else { + $(e).removeClass(opts.listSelectedClass); + } + } + if (opts.listSelectedAttribute) { + $(e).prop(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; + } + }, /** - * Set default values for MapData object properties - * @param {MapData} me The MapData object + * 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) */ - - 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 + 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; + $('*').off(); } + }; + + // 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 (_, 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; + } - /** - * 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]; + 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 (_, 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 >= map_data.index; i--) { + m.map_cache[i].index--; + } + }; /** - * Parse all the altImage references, adding them to the library so they can be preloaded - * and aliased. + * 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 * - * @param {MapData} me The MapData object on which to operate + * @return {bool} true if vml is supported, false if not */ - 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); - } - }); - }); - } + function hasVml() { + var a = $('
').appendTo('body'); + a.html(''); - // set area_options - me.area_options = u.updateProps({}, // default options for any MapArea - m.area_defaults, - opts); + var b = a[0].firstChild; + b.style.behavior = 'url(#default#VML)'; + var has = b ? typeof b.adj === 'object' : true; + a.remove(); + return has; } /** - * 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 + * Return a reference to the IE namespaces object, if available, or an empty object otherwise + * @return {obkect} The document.namespaces object. */ - 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) { - deferred.reject(); - } else { - 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; + function namespaces() { + return typeof document.namespaces === 'object' + ? document.namespaces + : null; } - /** - * Mousedown event. This is captured only to prevent browser from drawing an outline around an - * area when it's clicked. + /** + * 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. * - * @param {EventData} e jQuery event data + * @return {bool} true if HTML5 canvas support, false if not */ - function mousedown(e) { - if (!m.hasCanvas()) { - this.blur(); - } - e.preventDefault(); + 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; } /** - * Mouseover event. Handle highlight rendering and client callback on mouseover + * Merge new area data into existing area options on a MapData object. Used for rebinding. * - * @param {MapData} me The MapData context - * @param {EventData} e jQuery event data - * @return {[type]} [description] + * @param {[MapData]} map_data The MapData object + * @param {[object[]]} areas areas array to merge */ - function mouseover(me,e) { - var arData = me.getAllDataForArea(this), - ar=arData.length ? arData[0] : null; + function merge_areas(map_data, areas) { + var ar, + index, + map_areas = map_data.options.areas; - // mouseover events are ignored entirely while resizing, though we do care about mouseout events - // and must queue the action to keep things clean. + if (areas) { + $.each(areas, function (_, e) { + // Issue #68 - ignore invalid data in areas array - if (!ar || ar.isNotRendered() || ar.owner.currentAction) { + 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; - if (me.currentAreaId === ar.areaId) { - return; - } - if (me.highlightId !== ar.areaId) { - me.clearEffects(); + u.updateProps(map_data.options, temp_opts); - ar.highlight(); + merge_areas(map_data, options.areas); + // refresh the area_option template + u.updateProps(map_data.area_options, map_data.options); + } - if (me.options.showToolTip) { - $.each(arData,function(i,e) { - if (e.effectiveOptions().toolTip) { - e.showToolTip(); - } - }); - } + // 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(); + }; - me.currentAreaId = ar.areaId; - - if (u.isFunction(me.options.onMouseover)) { - me.options.onMouseover.call(this, - { - e: e, - options:ar.effectiveOptions(), - key: ar.key, - selected: ar.isSelected() - }); + // 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 (_, e) { + keys = keys.concat(e.keys); + }); } - } + $.each(keys, function (_, 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 (_, 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); + }; /** - * Mouseout event. + * 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 {MapData} me The MapData context - * @param {EventData} e jQuery event data - * @return {[type]} [description] + * @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 */ - function mouseout(me,e) { - var newArea, - ar = me.getDataForArea(this), - opts = me.options; - - - if (me.currentAreaId<0 || !ar) { - return; + me.set = function (selected, key, options) { + var lastMap, + map_data, + opts = options, + key_list, + area_list; // array of unique areas passed + + function setSelection(ar) { + var newState = selected; + if (ar) { + switch (selected) { + case true: + ar.select(opts); + break; + case false: + ar.deselect(true); + break; + default: + newState = ar.toggle(opts); + break; + } + return newState; } - - newArea=me.getDataForArea(e.relatedTarget); - - if (newArea === ar) { - return; + } + function addArea(ar) { + if (ar && $.inArray(ar, area_list) < 0) { + area_list.push(ar); + key_list += (key_list === '' ? '' : ',') + ar.key; } - - me.currentAreaId = -1; - ar.area=null; - - queueMouseEvent(me,opts.mouseoutDelay,ar) - .then(me.clearEffects); - - if (u.isFunction(opts.onMouseout)) { - opts.onMouseout.call(this, - { - e: e, - options: opts, - key: ar.key, - selected: ar.isSelected() - }); + } + // Clean up after a group that applied to the same map + function finishSetForMap(map_data) { + $.each(area_list, function (_, el) { + var newState = setSelection(el); + if (map_data.options.boundList) { + m.setBoundListProperties( + map_data.options, + m.getBoundList(map_data.options, key_list), + newState + ); + } + }); + if (!selected) { + map_data.removeSelectionFinish(); } + } - } - - /** - * 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; + this.filter('img,area').each(function (_, e) { + var keys; + map_data = m.getMapData(e); - me.ensureNoHighlight(); + if (map_data !== lastMap) { + if (lastMap) { + finishSetForMap(lastMap); + } - if (opts.toolTipClose - && $.inArray('area-mouseout', opts.toolTipClose) >= 0 - && me.activeToolTip) - { - me.clearToolTip(); + area_list = []; + key_list = ''; } - } - - /** - * 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 (u.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 (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(','); } - } - - 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); - } + } else { + keys = key; + } + + if (keys) { + $.each(u.split(keys), function (_, 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; } + } } + }); - mousedown.call(this,e); + if (map_data) { + finishSetForMap(map_data); + } - if (opts.clickNavigate && ar.href) { - window.location.href=ar.href; - return; - } + 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(); + }; - if (ar && !ar.owner.currentAction) { - opts = me.options; - clickArea(ar); + // 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 } - } - - /** - * 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 + ).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(); + }; - me.image = image; + // 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.images = new m.MapImages(me); - me.graphics = new m.Graphics(me); + me.snapshot = function () { + return new m.Method( + this, + function () { + $.each(this.data, function (_, e) { + e.selected = false; + }); - // 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. + this.base_canvas = this.graphics.createVisibleCanvas(this); + $(this.image).before(this.base_canvas); + }, + null, + { name: 'snapshot' } + ).go(); + }; - me.imgCssText = image.style.cssText || null; + // do not queue this function + + me.state = function () { + var md, + result = null; + $(this).each(function (_, e) { + if (e.nodeName === 'IMG') { + md = m.getMapData(e); + if (md) { + result = md.state(); + } + return false; + } + }); + return result; + }; - initializeDefaults(me); + me.bind = function (options) { + return this.each(function (_, e) { + var img, map, usemap, md; - me.configureOptions(options); + // save ref to this image even if we can't access it yet. commands will be queued + img = $(e); - // create context-bound event handlers from our private functions + md = m.getMapData(e); - 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); }; - }; + // if already bound completely, do a total rebind - m.MapData.prototype = { - constructor: m.MapData, + if (md) { + me.unbind.apply(img); + if (!md.complete) { + // will be queued + img.on(); + return true; + } + md = null; + } - /** - * Set target.options from defaults + options - * @param {[type]} target The target - * @param {[type]} options The options to merge - */ + // 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. - configureOptions: function(options) { - this.options= u.updateProps({}, m.defaults, options); - }, + usemap = this.getAttribute('usemap'); + map = usemap && $('map[name="' + usemap.substr(1) + '"]'); + if (!(img.is('img') && usemap && map.length > 0)) { + return true; + } - /** - * Ensure all images are loaded - * @return {Promise} A promise that resolves when the images have finished loading (or fail) - */ + // sorry - your image must have border:0, things are too unpredictable otherwise. + img.css('border', 0); - bindImages: function() { - var me=this, - mi = me.images; + if (!md) { + md = new m.MapData(this, options); - // reset the images if this is a rebind + md.index = addMap(md); + md.map = map; + md.bindImages().then(function () { + md.initialize(); + }); + } + }); + }; - if (mi.length>2) { - mi.splice(2); - } else if (mi.length===0) { + me.init = function (useCanvas) { + var style, shapes; - // 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); - } + // for testing/debugging, use of canvas can be forced by initializing + // manually with "true" or "false". But generally we test for it. - configureAltImages(me); + 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 (_, el) { + style.addRule( + 'v\\:' + el, + 'behavior: url(#default#VML); antialias:true' + ); + }); + } + m.hasVml.value = hasVml(); + } - return me.images.bind(); - }, + return m.hasVml.value; + }; - /** - * Test whether an async action is currently in progress - * @return {Boolean} true or false indicating state - */ + $.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; + })(); - isActive: function() { - return !this.complete || this.currentAction; - }, + $.mapster.impl.init(); +})(jQuery); - /** - * 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 - }; - }, +/* + graphics.js + Graphics object handles all rendering. +*/ - /** - * Get a unique ID for the wrapper of this imagemapster - * @return {string} A string that is unique to this image - */ +(function ($) { + 'use strict'; + + 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 (_, e) { + options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); + me.addShape(e, options); + }); - wrapId: function () { - return 'mapster_wrap_' + this.index; - }, - _idFromKey: function (key) { - return typeof key === "string" && this._xref.hasOwnProperty(key) ? - this._xref[key] : -1; - }, + // 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, - /** - * Return a comma-separated string of all selected keys - * @return {string} CSV of all keys that are currently selected - */ + /** + * 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) + */ - getSelected: function () { - var result = ''; - $.each(this.data, function (i,e) { - if (e.isSelected()) { - result += (result ? ',' : '') + this.key; - } - }); - return result; - }, + begin: function (canvas, elementName) { + var c = $(canvas); - /** - * 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])]; - if (ar){ - 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); - } - } - } + this.elementName = elementName; + this.canvas = canvas; - 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)]; - }, + this.width = c.width(); + this.height = c.height(); + this.shapes = []; + this.masks = []; + this.active = true; + }, - /** - * 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(','); - }, + /** + * 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 + */ - /** - * 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 - */ + addShape: function (mapArea, options) { + var addto = options.isMask ? this.masks : this.shapes; + addto.push({ mapArea: mapArea, options: options }); + }, - 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; - }, + /** + * 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 + */ - /** - * Clear all active selections on this map - */ + createVisibleCanvas: function (mapData) { + return $(this.createCanvasFor(mapData)) + .addClass('mapster_el') + .css(m.canvas_style)[0]; + }, - clearSelections: function () { - $.each(this.data, function (i,e) { - if (e.selected) { - e.deselect(true); - } - }); - this.removeSelectionFinish(); + /** + * 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 (_, 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 + ); + } + } - /** - * Set area options from an array of option data. - * - * @param {object[]} areas An array of objects containing area-specific options - */ + // 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 (_, 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 (_, 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(); + }); - setAreaOptions: function (areas) { - var i, area_options, ar; - areas = areas || []; + // render strokes at end since masks get stroked too - // refer by: map_data.options[map_data.data[x].area_option_id] + $.each(me.shapes.concat(me.masks), function (_, s) { + var offset = s.options.strokeWidth === 1 ? 0.5 : 0; + // offset applies only when stroke width is 1 and stroke would render between pixels. - 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); + if (s.options.stroke) { + shapeContext.save(); + shapeContext.strokeStyle = css3color( + s.options.strokeColor, + s.options.strokeOpacity + ); + shapeContext.lineWidth = s.options.strokeWidth; - // TODO: will not deselect areas that were previously selected, so this only works - // for an initial bind. + shapeContext.beginPath(); - 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); + 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 (_, e) { + me.renderShape(e.mapArea, e.options); + }); + + if (this.masks.length) { + $.each(this.masks, function (_, 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 (_, e) { + p[e] = (function (method) { + return function () { + p[method] = + (m.hasCanvas() ? canvasMethods[method] : vmlMethods[method]) || + noop; + + return p[method].apply(this, arguments); + }; + })(e); + } + ); +})(jQuery); - 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(); - } - }); +/* + mapimage.js + The MapImage object, repesents an instance of a single bound imagemap +*/ - }, - ///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; - } +(function ($) { + 'use strict'; + + 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; + }, - img = $(me.image); + /** + * 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; + }, - parentId = img.parent().attr('id'); + /** + * Save an image in the images array and return its index + * @param {Image} image An Image object + * @return {int} the index of the image + */ - // create a div wrapper only if there's not already a wrapper, otherwise, own it + _add: function (image) { + var index = ap.push.call(this, image) - 1; + this.status[index] = false; + return index; + }, - if (parentId && parentId.length >= 12 && parentId.substring(0, 12) === "mapster_wrap") { - wrap = img.parent(); - wrap.attr('id', me.wrapId()); - } else { - wrap = $('
'); + /** + * 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 + */ - if (opts.wrapClass) { - if (opts.wrapClass === true) { - wrap.addClass(img[0].className); - } - else { - wrap.addClass(opts.wrapClass); - } - } - } - me.wrapper = wrap; + indexOf: function (image) { + return u.indexOf(this, image); + }, - // 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. + /** + * Clear this object and reset it to its initial state after binding. + */ - me.scaleInfo = scale = u.scaleMap(me.images[0],me.images[1], opts.scaleMap); + clear: function () { + var me = this; - me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); - me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); + if (me.ids && me.ids.length > 0) { + $.each(me.ids, function (_, e) { + delete me[e]; + }); + } - // 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}); + /** + * A list of the cross-reference IDs bound to this object + * @type {string[]} + */ - size=u.size(me.images[0]); + me.ids = []; - if (size.complete) { - imgCopy.css({ - width: size.width, - height: size.height - }); - } + /** + * Length property for array-like behavior, set to zero when initializing. Array prototype + * methods will update it after that. + * + * @type {int} + */ - me.buildDataset(); + me.length = 0; - // now that we have processed all the areas, set css for wrapper, scale map if needed + /** + * the loaded status of the corresponding image + * @type {boolean[]} + */ - css = { - display: 'block', - position: 'relative', - padding: 0, - width: scale.width, - height: scale.height - }; + me.status = []; - 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]) { + // actually erase the images - img.before(me.wrapper); - } + me.splice(0); + }, - wrap.css(css); + /** + * 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 + */ - // move all generated images into the wrapper for easy removal later + add: function (image, id) { + var index, + src, + me = this; - $(me.images.slice(2)).hide(); - for (i = 1; i < me.images.length; i++) { - wrap.append(me.images[i]); - } + if (!image) { + return; + } - //me.images[1].style.cssText = me.image.style.cssText; + if (typeof image === 'string') { + src = image; + image = me[src]; + if (typeof image === 'object') { + return me.indexOf(image); + } - wrap.append(base_canvas) - .append(overlay_canvas) - .append(img.css(m.canvas_style)); + image = $('').addClass('mapster_el').hide(); + + index = me._add(image[0]); + + image + .on('load', function (e) { + me.imageLoaded.call(me, e); + }) + .on('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; + }, - // images[0] is the original image with map, images[1] is the copy/background that is visible + /** + * Bind the images in this object, + * @return {Promise} a promise that resolves when the images have finished loading + */ - u.setOpacity(me.images[0], 0); - $(me.images[1]).show(); + bind: function () { + 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 */ - u.setOpacity(me.images[1],1); + check = function () { + var i; - 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); - }; - } + // refresh status of images - sorted_list.sort(function (a, b) { - a = a.value; - b = b.value; - return sort_func(a, b); - }); - } + i = me.length; - me.options.boundList = opts.onGetList.call(me.image, sorted_list); + 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); + } + } + }; - me.complete=true; - me.processCommandQueue(); + promise = me.deferred = u.defer(); - if (opts.onConfigured && typeof opts.onConfigured === 'function') { - opts.onConfigured.call(img, true); - } - }, + check(); + return promise; + }, - // 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; - } + resolve: function () { + var me = this, + resolver = me.deferred; - me._xref = {}; - me.data = []; - if (!rebind) { - me.mapAreas=[]; - } + if (resolver) { + // Make a copy of the resolver before calling & removing it to ensure + // it is not called twice + me.deferred = null; + resolver.resolve(); + } + }, - default_group = !opts.mapKey; - if (default_group) { - opts.mapKey = 'data-mapster-key'; - } + /** + * Event handler for image onload + * @param {object} e jQuery event data + */ - // the [attribute] selector is broken on old IE with jQuery. hasVml() is a quick and dirty - // way to test for that + imageLoaded: function (e) { + var me = this, + index = me.indexOf(e.target); - sel = m.hasVml() ? 'area' : - (default_group ? - 'area[coords]' : - 'area[' + opts.mapKey + ']'); + if (index >= 0) { + me.status[index] = true; + if ($.inArray(false, me.status) < 0) { + me.resolve(); + } + } + }, - areas = $(me.map).find(sel).off('.mapster'); + /** + * Event handler for onload error + * @param {object} e jQuery event data + */ - for (mapAreaId = 0;mapAreaId= 0) { + deferred.resolve(); + } + } + if (me.activeAreaEvent) { + window.clearTimeout(me.activeAreaEvent); + me.activeAreaEvent = 0; + } + if (delay < 0) { + deferred.reject(); + } else { + 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 (rebind) { - mapArea = me.mapAreas[$area.data('mapster')-1]; - mapArea.configure(curKey); - } else { - mapArea = new m.MapArea(me, area,curKey); - me.mapAreas.push(mapArea); - } + if (me.currentAreaId === ar.areaId) { + return; + } + if (me.highlightId !== ar.areaId) { + me.clearEffects(); - keys = mapArea.keys; // converted to an array by mapArea + ar.highlight(); + if (me.options.showToolTip) { + $.each(arData, function (_, e) { + if (e.effectiveOptions().toolTip) { + e.showToolTip(); + } + }); + } + } - // Iterate through each mapKey assigned to this area - for (j = keys.length - 1; j >= 0; j--) { - key = keys[j]; + me.currentAreaId = ar.areaId; - 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); - } + if (u.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; + } - href=$area.attr('href'); - if (href && href!=='#' && !dataItem.href) - { - dataItem.href=href; - } + newArea = me.getDataForArea(e.relatedTarget); - if (!mapArea.nohref) { - $area.on('click.mapster', me.click) - .on('mouseover.mapster touchstart.mapster.noPreventDefault', me.mouseover) - .on('mouseout.mapster touchend.mapster.noPreventDefault', me.mouseout) - .on('mousedown.mapster', me.mousedown); + if (newArea === ar) { + return; + } + me.currentAreaId = -1; + ar.area = null; + queueMouseEvent(me, opts.mouseoutDelay, ar).then(me.clearEffects); - } + if (u.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 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 (u.isFunction(opts.onClick)) { + cbResult = opts.onClick.call(that, { + e: e, + listTarget: list_target, + key: ar.key, + selected: newSelectionState + }); - // store an ID with each area. - $area.data("mapster", mapAreaId+1); - } + if (u.isBool(cbResult)) { + if (!cbResult) { + return false; + } + target = $(ar.area).attr('href'); + if (target !== '#') { + window.location.href = target; + return false; + } + } + } + + if (canChangeState) { + 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 (_, e) { + var ar = me.getDataForKey(e.toString()); + if (!ar.options.isMask) { + clickArea(ar); + } + }); + } + } - // TODO listenToList - // if (opts.listenToList && opts.nitG) { - // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); - // } + mousedown.call(this, e); - // populate areas from config options - me.setAreaOptions(opts.areas); - me.redrawSelections(); + if (opts.clickNavigate && ar.href) { + window.location.href = ar.href; + return; + } - }, - processCommandQueue: function() { + if (ar && !ar.owner.currentAction) { + opts = me.options; + clickArea(ar); + } + } - 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') - .off('.mapster'); - $(this.images) - .off('.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); + /** + * 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; - // 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(); - } + // (Image) main map image - me.images.clear(); + me.image = image; - this.image = null; - u.ifFunction(this.clearTooltip, this); - }, + me.images = new m.MapImages(me); + me.graphics = new m.Graphics(me); - // Compelete cleanup process for deslecting items. Called after a batch operation, or by AreaData for single - // operations not flagged as "partial" + // 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. - removeSelectionFinish: function () { - var g = this.graphics; + me.imgCssText = image.style.cssText || null; - g.refreshSelections(); - // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect - g.clearHighlight(); - } + initializeDefaults(me); + + me.configureOptions(options); + + // create context-bound event handlers from our private functions + + me.mouseover = function (e) { + mouseover.call(this, me, e); }; -} (jQuery)); + 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); + }; + }; -/* areadata.js - AreaData and MapArea protoypes -*/ + m.MapData.prototype = { + constructor: m.MapData, -(function ($) { - var m = $.mapster, u = m.utils; + /** + * 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); + }, /** - * Select this area - * - * @param {AreaData} me AreaData context - * @param {object} options Options for rendering the selection + * Ensure all images are loaded + * @return {Promise} A promise that resolves when the images have finished loading (or fail) */ - 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(); - } + bindImages: function () { + var me = this, + mi = me.images; - // 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) { + // reset the images if this is a rebind - // cache the current options, and map the altImageId if an altimage - // was passed + 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); + } - me.optsCache = $.extend(me.effectiveRenderOptions('select'), - options, - { - altImageId: o.images.add(options.altImage) - }); - } + configureAltImages(me); - me.drawSelection(); + return me.images.bind(); + }, - me.selected = true; - me.changeState('select', true); - } + /** + * Test whether an async action is currently in progress + * @return {Boolean} true or false indicating state + */ - if (o.options.singleSelect) { - o.graphics.refreshSelections(); - } - } + isActive: function () { + return !this.complete || this.currentAction; + }, /** - * Deselect this area, optionally deferring finalization so additional areas can be deselected - * in a single operation + * Return an object indicating the various states. This isn't really used by + * production code. * - * @param {boolean} partial when true, the caller must invoke "finishRemoveSelection" to render + * @return {object} An object with properties for various states */ - function deselect(partial) { - var me=this; - me.selected = false; - me.changeState('select', false); + state: function () { + return { + complete: this.complete, + resizing: this.currentAction === 'resizing', + zoomed: this.zoomed, + zoomedArea: this.zoomedArea, + scaleInfo: this.scaleInfo + }; + }, - // release information about last area options when deselecting. + /** + * Get a unique ID for the wrapper of this imagemapster + * @return {string} A string that is unique to this image + */ - me.optsCache=null; - me.owner.graphics.removeSelections(me.areaId); + wrapId: function () { + return 'mapster_wrap_' + this.index; + }, + _idFromKey: function (key) { + return typeof key === 'string' && + Object.prototype.hasOwnProperty.call(this._xref, key) + ? this._xref[key] + : -1; + }, - // 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 + /** + * Return a comma-separated string of all selected keys + * @return {string} CSV of all keys that are currently selected + */ - if (!partial) { - me.owner.removeSelectionFinish(); + getSelected: function () { + var result = ''; + $.each(this.data, function (_, e) { + if (e.isSelected()) { + result += (result ? ',' : '') + this.key; } - } + }); + return result; + }, /** - * Toggle the selection state of this area - * @param {object} options Rendering options, if toggling on - * @return {bool} The new selection state + * 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 */ - function toggle(options) { - var me=this; - if (!me.isSelected()) { - me.select(options); - } - else { - me.deselect(); + + 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])]; + if (ar) { + 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 me.isSelected(); - } + } + + 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)]; + }, /** - * An AreaData object; represents a conceptual area that can be composed of - * one or more MapArea objects + * Get the primary keys associated with an area group. + * If this is a primary key, it will be returned. * - * @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 + * @param {string key An area key + * @return {string} A CSV of area keys */ - 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 - }); - }; + getKeysForGroup: function (key) { + var ar = this.getDataForKey(key); + + return !ar + ? '' + : ar.isPrimary + ? ar.key + : this.getPrimaryKeysForMapAreas(ar.areas()).join(','); + }, /** - * The public API for AreaData object + * 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 */ - m.AreaData.prototype = { - constuctor: m.AreaData, - select: select, - deselect: deselect, - toggle: toggle, - areas: function() { - var i,result=[]; - for (i=0;i= 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 (_, e) { + if (e.selected) { + e.deselect(true); + } + }); + this.removeSelectionFinish(); + }, - /** - * Return the overall options effective for this area. - * This should get the default options, and merge in area-specific options, finally - * overlaying options passed by parameter - * - * @param {[type]} options options which will supercede all other options for this area - * @return {[type]} the combined options - */ + /** + * Set area options from an array of option data. + * + * @param {object[]} areas An array of objects containing area-specific options + */ - effectiveOptions: function (options) { + setAreaOptions: function (areas) { + var i, area_options, ar; + areas = areas || []; - var opts = u.updateProps({}, - this.owner.area_options, - this.options, - options || {}, - { - id: this.areaId - } - ); + // refer by: map_data.options[map_data.data[x].area_option_id] - opts.selected = this.isSelected(); + 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); - return opts; - }, + // TODO: will not deselect areas that were previously selected, so this only works + // for an initial bind. - /** - * Return the options effective for this area for a "render" or "highlight" mode. - * This should get the default options, merge in the areas-specific options, - * and then the mode-specific options. - * @param {string} mode 'render' or 'highlight' - * @param {[type]} options options which will supercede all other options for this area - * @return {[type]} the combined options - */ - - effectiveRenderOptions: function (mode, options) { - var allOpts,opts=this.optsCache; - - if (!opts || mode==='highlight') { - allOpts = this.effectiveOptions(options); - opts = u.updateProps({}, - allOpts, - allOpts["render_" + mode] - ); - - if (mode!=='highlight') { - this.optsCache=opts; - } + if (u.isBool(area_options.selected)) { + ar.selected = area_options.selected; } - return $.extend({},opts); - }, + } + } + } + }, + // 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 (_, 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 + }); + } - // Fire callback on area state change - changeState: function (state_type, state) { - if (u.isFunction(this.owner.options.onStateChange)) { - this.owner.options.onStateChange.call(this.owner.image, - { - key: this.key, - state: state_type, - selected: state - } - ); - } - }, + me.buildDataset(); - // highlight this area + // now that we have processed all the areas, set css for wrapper, scale map if needed - highlight: function (options) { - var o = this.owner; - o.ensureNoHighlight(); - if (this.effectiveOptions().highlight) { - o.graphics.addShapeGroup(this, "highlight",options); - } - o.setHighlightId(this.areaId); - this.changeState('highlight', true); - }, + css = { + display: 'block', + position: 'relative', + padding: 0, + width: scale.width, + height: scale.height + }; - // select this area. if "callEvent" is true then the state change event will be called. (This method can be used - // during config operations, in which case no event is indicated) + 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); + } - drawSelection: function () { + wrap.css(css); + // move all generated images into the wrapper for easy removal later - this.owner.graphics.addShapeGroup(this, "select"); + $(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)); - }; - // represents an HTML area - m.MapArea = function (owner,areaEl,keys) { - if (!owner) { - return; - } - var me = this; - me.owner = owner; // a MapData object - me.area = areaEl; - me.areaDataXref=[]; // a list of map_data.data[] id's for each areaData object containing this - me.originalCoords = []; - $.each(u.split(areaEl.coords), function (i, el) { - me.originalCoords.push(parseFloat(el)); - }); - me.length = me.originalCoords.length; - me.shape = areaEl.shape.toLowerCase(); - me.nohref = areaEl.nohref || !areaEl.href; - me.configure(keys); - }; - m.MapArea.prototype= { - constructor: m.MapArea, - configure: function(keys) { - this.keys = u.split(keys); - }, - reset: function() { - this.area=null; - }, - coords: function (offset) { - return $.map(this.originalCoords,function(e) { - return offset ? e : e+offset; - }); - } - }; -} (jQuery)); + // images[0] is the original image with map, images[1] is the copy/background that is visible -/* areacorners.js - determine the best place to put a box of dimensions (width,height) given a circle, rect or poly -*/ + u.setOpacity(me.images[0], 0); + $(me.images[1]).show(); -(function ($) { - var u=$.mapster.utils; + 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; + }; + } - /** - * Compute positions that will place a target with dimensions [width,height] outside - * but near the boundaries of the elements "elements". When an imagemap is passed, the - * - * @param {Element|Element[]} elements An element or an array of elements (such as a jQuery object) - * @param {Element} image The image to which area elements are bound, if this is an image map. - * @param {Element} container The contianer in which the target must be constrained (or document, if missing) - * @param {int} width The width of the target object - * @return {object} a structure with the x and y positions - */ - u.areaCorners = function (elements, image, container, width, height) { - var pos,found, minX, minY, maxX, maxY, bestMinX, bestMaxX, bestMinY, bestMaxY, curX, curY, nest, j, - offsetx=0, - offsety=0, - rootx, - rooty, - iCoords,radius,angle,el, - coords=[]; - - // if a single element was passed, map it to an array - - elements = elements.length ? - elements: - [elements]; - - container = container ? - $(container): - $(document.body); - - // get the relative root of calculation - - pos = container.offset(); - rootx = pos.left; - rooty = pos.top; - - // with areas, all we know about is relative to the top-left corner of the image. We need to add an offset compared to - // the actual container. After this calculation, offsetx/offsety can be added to either the area coords, or the target's - // absolute position to get the correct top/left boundaries of the container. - - if (image) { - pos = $(image).offset(); - offsetx = pos.left; - offsety = pos.top; + sorted_list.sort(function (a, b) { + a = a.value; + b = b.value; + return sort_func(a, b); + }); } - // map the coordinates of any type of shape to a poly and use the logic. simpler than using three different - // calculation methods. Circles use a 20 degree increment for this estimation. + 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).off('.mapster'); + + for (mapAreaId = 0; mapAreaId < areas.length; mapAreaId++) { + area_id = 0; + area = areas[mapAreaId]; + $area = $(area); + + // skip areas with no coords - selector broken for older ie + if (!area.coords) { + continue; + } + // Create a key if none was assigned by the user - for (j=0;j= 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 { - el=$(el); - pos = el.position(); - coords.push(pos.left,pos.top, - pos.left+el.width(),pos.top, - pos.left+el.width(),pos.top+el.height(), - pos.left,pos.top+el.height()); + 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 + .on('click.mapster', me.click) + .on( + 'mouseover.mapster touchstart.mapster.noPreventDefault', + me.mouseover + ) + .on( + 'mouseout.mapster touchend.mapster.noPreventDefault', + me.mouseout + ) + .on('mousedown.mapster', me.mousedown); } - minX = minY = bestMinX = bestMinY = 999999; - maxX = maxY = bestMaxX = bestMaxY = -1; + // 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').off('.mapster'); + $(this.images).off('.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 (_, 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); - for (j = coords.length - 2; j >= 0; j -= 2) { - curX = coords[j]; - curY = coords[j + 1]; +/* areadata.js + AreaData and MapArea protoypes +*/ - 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; - } +(function ($) { + 'use strict'; + + 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 - // 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)); + me.optsCache = $.extend(me.effectiveRenderOptions('select'), options, { + altImageId: o.images.add(options.altImage) + }); + } -/* scale.js: resize and zoom functionality - requires areacorners.js -*/ + me.drawSelection(); + me.selected = true; + me.changeState('select', true); + } -(function ($) { - var m = $.mapster, u = m.utils, p = m.MapArea.prototype; + if (o.options.singleSelect) { + o.graphics.refreshSelections(); + } + } - 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) { + /** + * 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 + */ - // 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. + function deselect(partial) { + var me = this; + me.selected = false; + me.changeState('select', false); + // release information about last area options when deselecting. - var vis=u.size(image), - raw=u.size(imageRaw,true); + me.optsCache = null; + me.owner.graphics.removeSelections(me.areaId); - 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); - }; + // 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 < this.areasXref.length; i++) { + result.push(this.owner.mapAreas[this.areasXref[i]]); + } + return result; + }, + // return all coordinates for all areas + coords: function (offset) { + var coords = []; + $.each(this.areas(), function (_, el) { + coords = coords.concat(el.coords(offset)); + }); + return coords; + }, + reset: function () { + $.each(this.areas(), function (_, e) { + e.reset(); + }); + this.areasXref = []; + this.options = null; + }, + // Return the effective selected state of an area, incorporating staticState + isSelectedOrStatic: function () { + var o = this.effectiveOptions(); + return u.isBool(o.staticState) ? o.staticState : this.isSelected(); + }, + isSelected: function () { + return u.isBool(this.selected) + ? this.selected + : u.isBool(this.owner.area_options.selected) + ? this.owner.area_options.selected + : false; + }, + isSelectable: function () { + return u.isBool(this.effectiveOptions().staticState) + ? false + : u.isBool(this.owner.options.staticState) + ? false + : u.boolOrDefault(this.effectiveOptions().isSelectable, true); + }, + isDeselectable: function () { + return u.isBool(this.effectiveOptions().staticState) + ? false + : u.isBool(this.owner.options.staticState) + ? false + : u.boolOrDefault(this.effectiveOptions().isDeselectable, true); + }, + isNotRendered: function () { + var area = $(this.area); + return ( + area.attr('nohref') || + !area.attr('href') || + this.effectiveOptions().isMask + ); + }, /** - * Resize the image map. Only one of newWidth and newHeight should be passed to preserve scale + * Return the overall options effective for this area. + * This should get the default options, and merge in area-specific options, finally + * overlaying options passed by parameter * - * @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 + * @param {[type]} options options which will supercede all other options for this area + * @return {[type]} the combined options */ - 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); - } + effectiveOptions: function (options) { + var opts = u.updateProps( + {}, + this.owner.area_options, + this.options, + options || {}, + { + id: this.areaId } + ); - // Finalize resize action, do callback, pass control to command queue + opts.selected = this.isSelected(); - function cleanupAndNotify() { + return opts; + }, + + /** + * Return the options effective for this area for a "render" or "highlight" mode. + * This should get the default options, merge in the areas-specific options, + * and then the mode-specific options. + * @param {string} mode 'render' or 'highlight' + * @param {[type]} options options which will supercede all other options for this area + * @return {[type]} the combined options + */ - me.currentAction = ''; + effectiveRenderOptions: function (mode, options) { + var allOpts, + opts = this.optsCache; - if (u.isFunction(callback)) { - callback(); - } + if (!opts || mode === 'highlight') { + allOpts = this.effectiveOptions(options); + opts = u.updateProps({}, allOpts, allOpts['render_' + mode]); - me.processCommandQueue(); + if (mode !== 'highlight') { + this.optsCache = opts; } + } + return $.extend({}, opts); + }, + + // Fire callback on area state change + changeState: function (state_type, state) { + if (u.isFunction(this.owner.options.onStateChange)) { + this.owner.options.onStateChange.call(this.owner.image, { + key: this.key, + state: state_type, + selected: state + }); + } + }, + + // highlight this area + + highlight: function (options) { + var o = this.owner; + o.ensureNoHighlight(); + if (this.effectiveOptions().highlight) { + o.graphics.addShapeGroup(this, 'highlight', options); + } + o.setHighlightId(this.areaId); + this.changeState('highlight', true); + }, + + // select this area. if "callEvent" is true then the state change event will be called. (This method can be used + // during config operations, in which case no event is indicated) + + drawSelection: function () { + this.owner.graphics.addShapeGroup(this, 'select'); + } + }; + // represents an HTML area + m.MapArea = function (owner, areaEl, keys) { + if (!owner) { + return; + } + var me = this; + me.owner = owner; // a MapData object + me.area = areaEl; + me.areaDataXref = []; // a list of map_data.data[] id's for each areaData object containing this + me.originalCoords = []; + $.each(u.split(areaEl.coords), function (_, el) { + me.originalCoords.push(parseFloat(el)); + }); + me.length = me.originalCoords.length; + me.shape = areaEl.shape.toLowerCase(); + me.nohref = areaEl.nohref || !areaEl.href; + me.configure(keys); + }; + m.MapArea.prototype = { + constructor: m.MapArea, + configure: function (keys) { + this.keys = u.split(keys); + }, + reset: function () { + this.area = null; + }, + coords: function (offset) { + return $.map(this.originalCoords, function (e) { + return offset ? e : e + offset; + }); + } + }; +})(jQuery); - // handle cleanup after the inner elements are resized +/* areacorners.js + determine the best place to put a box of dimensions (width,height) given a circle, rect or poly +*/ - function finishResize() { - sizeCanvas(me.overlay_canvas, width, height); +(function ($) { + 'use strict'; + + var u = $.mapster.utils; + + /** + * Compute positions that will place a target with dimensions [width,height] outside + * but near the boundaries of the elements "elements". When an imagemap is passed, the + * + * @param {Element|Element[]} elements An element or an array of elements (such as a jQuery object) + * @param {Element} image The image to which area elements are bound, if this is an image map. + * @param {Element} container The contianer in which the target must be constrained (or document, if missing) + * @param {int} width The width of the target object + * @return {object} a structure with the x and y positions + */ + u.areaCorners = function (elements, image, container, width, height) { + var pos, + found, + minX, + minY, + maxX, + maxY, + bestMinX, + bestMaxX, + bestMinY, + bestMaxY, + curX, + curY, + nest, + j, + offsetx = 0, + offsety = 0, + rootx, + rooty, + iCoords, + radius, + angle, + el, + coords = []; + + // if a single element was passed, map it to an array + + elements = elements.length ? elements : [elements]; + + container = container ? $(container) : $(document.body); + + // get the relative root of calculation + + pos = container.offset(); + rootx = pos.left; + rooty = pos.top; + + // with areas, all we know about is relative to the top-left corner of the image. We need to add an offset compared to + // the actual container. After this calculation, offsetx/offsety can be added to either the area coords, or the target's + // absolute position to get the correct top/left boundaries of the container. + + if (image) { + pos = $(image).offset(); + offsetx = pos.left; + offsety = pos.top; + } - // 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; + // map the coordinates of any type of shape to a poly and use the logic. simpler than using three different + // calculation methods. Circles use a 20 degree increment for this estimation. + + for (j = 0; j < elements.length; j++) { + el = elements[j]; + if (el.nodeName === 'AREA') { + iCoords = u.split(el.coords, parseInt); + + switch (el.shape) { + case 'circle': + curX = iCoords[0]; + curY = iCoords[1]; + radius = iCoords[2]; + coords = []; + for (j = 0; j < 360; j += 20) { + angle = (j * Math.PI) / 180; + coords.push( + curX + radius * Math.cos(angle), + curY + radius * Math.sin(angle) + ); } - sizeCanvas(me.base_canvas, width, height); - me.redrawSelections(); - cleanupAndNotify(); + break; + case 'rect': + coords.push( + iCoords[0], + iCoords[1], + iCoords[2], + iCoords[1], + iCoords[2], + iCoords[3], + iCoords[0], + iCoords[3] + ); + break; + default: + coords = coords.concat(iCoords); + break; } - 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(); - }); - }); + // map area positions to it's real position in the container + + for (j = 0; j < coords.length; j += 2) { + coords[j] = parseInt(coords[j], 10) + offsetx; + coords[j + 1] = parseInt(coords[j + 1], 10) + offsety; } + } else { + el = $(el); + pos = el.position(); + coords.push( + pos.left, + pos.top, + pos.left + el.width(), + pos.top, + pos.left + el.width(), + pos.top + el.height(), + pos.left, + pos.top + el.height() + ); + } + } - if (me.scaleInfo.width === width && me.scaleInfo.height === height) { - return; + minX = minY = bestMinX = bestMinY = 999999; + maxX = maxY = bestMaxX = bestMaxY = -1; + + for (j = coords.length - 2; j >= 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 (_, 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 - highlightId = me.highlightId; + if (!found) { + nest = [maxX, maxY]; + } + } + return nest; + }; +})(jQuery); + +/* + scale.js + Resize and zoom functionality + Requires areacorners.js +*/ +(function ($) { + 'use strict'; - 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); - } + var m = $.mapster, + u = m.utils, + p = m.MapArea.prototype; - newsize = { 'width': String(width) + 'px', 'height': String(height) + 'px' }; - if (!m.hasCanvas()) { - $(me.base_canvas).children().remove(); - } + 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. - // 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" - }); - }); + var vis = u.size(image), + raw = u.size(imageRaw, true); - p = u.defer(); - promises.push(p); + 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); + } + } - // though resizeMapData is not async, it needs to be finished just the same as the animations, - // so add it to the "to do" list. + // Finalize resize action, do callback, pass control to command queue - u.when.all(promises).then(finishResize); - resizeMapData(); - p.resolve(); - } else { - els.css(newsize); - resizeMapData(); - finishResize(); + function cleanupAndNotify() { + me.currentAction = ''; - } - }; + if (u.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(); + } - m.MapArea = u.subclass(m.MapArea, function () { - //change the area tag data if needed - this.base.init(); - if (this.owner.scaleInfo.scale) { - this.resize(); + 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 (_, e) { + $.each(e.areas(), function (_, e) { + e.resize(); + }); + }); + } - p.coords = function (percent, coordOffset) { - var j, newCoords = [], - pct = percent || this.owner.scaleInfo.scalePct, - offset = coordOffset || 0; + if (me.scaleInfo.width === width && me.scaleInfo.height === height) { + return; + } - if (pct === 1 && coordOffset === 0) { - return this.originalCoords; - } + highlightId = me.highlightId; - 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(','); - }; + 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); + } - p.reset = function () { - this.area.coords = this.coords(1).join(','); - }; + newsize = { width: String(width) + 'px', height: String(height) + 'px' }; + if (!m.hasCanvas()) { + $(me.base_canvas).children().remove(); + } - 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; - }; + // 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 (_, 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 || {}; @@ -3576,232 +3805,237 @@ key: key } )).go(); - - }; */ -} (jQuery)); +})(jQuery); -/* tooltip.js - tooltip functionality - requires areacorners.js +/* + 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(); + 'use strict'; + + 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); } - - /** - * 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); - } + // 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; } - /** - * Hide and remove active tooltips - * - * @param {MapData} this The mapdata object to which the tooltips belong - */ + tooltip.css(tooltipCss).addClass('mapster_tooltip'); - m.MapData.prototype.clearToolTip = function() { - if (this.activeToolTip) { - this.activeToolTip.stop().remove(); - this.activeToolTip = null; - this.activeToolTipID = null; - u.ifFunction(this.options.onHideToolTip, this); + 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.off(event_name).on(event_name, function (e) { + if (!beforeClose || beforeClose.call(this, e)) { + target.off('.mapster-tooltip'); + if (onClose) { + onClose.call(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.off(event_name) - .on(event_name, function (e) { - if (!beforeClose || beforeClose.call(this,e)) { - target.off('.mapster-tooltip'); - if (onClose) { - onClose.call(this); - } - } - }); + }); - return { - object: target, - event: event_name - }; - } + 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; } - /** - * 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.left += options.offsetx || 0; + ttopts.top += options.offsety || 0; - ttopts.css= options.css; - ttopts.fadeDuration = options.fadeDuration; + ttopts.css = options.css; + ttopts.fadeDuration = options.fadeDuration; - showToolTipImpl(tooltip,ttopts); + showToolTipImpl(tooltip, ttopts); - return tooltip; - } + 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. @@ -3818,171 +4052,217 @@ * @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() + 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; }); - 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); + if (md.activeToolTipID === ad.areaId) { + return; } - /** - * 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 - */ + md.clearToolTip(); - 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(content, template, options.css); - md.activeToolTip = tooltip = createToolTip(getHtmlFromOptions(options), - options.template || md.options.toolTipContainer, - options.css); - md.activeToolTipID = target[0]; + md.activeToolTipID = ad.areaId; - bindToolTipClose(['tooltip-click'],'tooltip-click', 'click', tooltip, null, function() { - md.clearToolTip(); - }); + tipClosed = function () { + md.clearToolTip(); + }; - md.activeToolTip = tooltip = showToolTip(tooltip, - target, - md.image, - options.container, - options); - } - }, - function areaData() { - if ($.isPlainObject(key) && !options) { - options = key; - } + 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() + }); - this.showToolTip(getHtmlFromOptions(options),options); - }, - { - name: 'tooltip', - args: arguments, - key: key + 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 + ); } - )).go(); - }; -} (jQuery)); + }, + function areaData() { + if ($.isPlainObject(key) && !options) { + options = key; + } + + this.showToolTip(getHtmlFromOptions(options), options); + }, + { + name: 'tooltip', + args: arguments, + key: key + } + ).go(); + }; +})(jQuery); })); \ No newline at end of file diff --git a/dist/jquery.imagemapster.min.js b/dist/jquery.imagemapster.min.js index 17e13f7..85e5d8d 100644 --- a/dist/jquery.imagemapster.min.js +++ b/dist/jquery.imagemapster.min.js @@ -5,5 +5,5 @@ * License: MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),a(t),t}:a(jQuery)}(function(jQuery){function id(e,t,a){var i=e,n=i.map_data,s=a.isMask;cd.each(t.areas(),function(e,t){a.isMask=s||t.nohref&&n.options.noHrefIsMask,i.addShape(t,a)}),a.isMask=s}function jd(e){return Math.max(0,Math.min(parseInt(e,16),255))}function kd(e,t){return"rgba("+jd(e.substr(0,2))+","+jd(e.substr(2,2))+","+jd(e.substr(4,2))+","+t+")"}function ld(){}var cd,dd,gd,hd,ed,fd,Se,Te,Ue,Ve,Bf,Cf,Df,ui,vi,wi,lj,mj,Rj,Sj,Tj,Uj,Lk,Mk,Nk;function Gf(e){var t=e.options,a=e.images;Cf.hasCanvas()&&(Bf.each(t.altImages||{},function(e,t){a.add(t,e)}),Bf.each([t].concat(t.areas),function(e,t){Bf.each([t=t,t.render_highlight,t.render_select],function(e,t){t&&t.altImage&&(t.altImageId=a.add(t.altImage))})})),e.area_options=Df.updateProps({},Cf.area_defaults,t)}function Kf(e,t){var a=e.getDataForArea(this),i=e.options;e.currentAreaId<0||!a||e.getDataForArea(t.relatedTarget)!==a&&(e.currentAreaId=-1,a.area=null,function e(t,a,i,n){return n=n||Df.when.defer(),t.activeAreaEvent&&(window.clearTimeout(t.activeAreaEvent),t.activeAreaEvent=0),a<0?n.reject():i.owner.currentAction||a?t.activeAreaEvent=window.setTimeout(function(){e(t,0,i,n)},a||100):(a=i.areaId,t.currentAreaId!==a&&0<=t.highlightId&&n.resolve()),n}(e,i.mouseoutDelay,a).then(e.clearEffects),Df.isFunction(i.onMouseout)&&i.onMouseout.call(this,{e:t,options:i,key:a.key,selected:a.isSelected()}))}function Mf(i,n){var s,o,r,c,l,u=this,e=i.getDataForArea(this),h=i.options;(function(e){Cf.hasCanvas()||this.blur(),e.preventDefault()}).call(this,n),h.clickNavigate&&e.href?window.location.href=e.href:e&&!e.owner.currentAction&&(h=i.options,function a(e){var t;if(c=e.isSelectable()&&(e.isDeselectable()||!e.isSelected()),r=c?!e.isSelected():e.isSelected(),o=Cf.getBoundList(h,e.key),Df.isFunction(h.onClick)&&(l=h.onClick.call(u,{e:n,listTarget:o,key:e.key,selected:r}),Df.isBool(l))){if(!l)return;if("#"!==(t=Bf(e.area).attr("href")))return void(window.location.href=t)}c&&e.toggle(),h.boundList&&0=e.index;t--)m.map_cache[t].index--};function hasVml(){var e=$("
").appendTo("body");e.html('');var t=e[0].firstChild;t.style.behavior="url(#default#VML)";t=!t||"object"==typeof t.adj;return e.remove(),t}function namespaces(){return"object"==typeof document.namespaces?document.namespaces:null}function hasCanvas(){var e=namespaces();return(!e||!e.g_vml_)&&!!$("")[0].getContext}function merge_areas(a,e){var i,n=a.options.areas;e&&$.each(e,function(e,t){t&&t.key&&(0<=(i=u.indexOfProp(n,"key",t.key))?$.extend(n[i],t):n.push(t),(i=a.getDataForKey(t.key))&&$.extend(i.options,t))})}function merge_options(e,t){var a=u.updateProps({},t);delete a.areas,u.updateProps(e.options,a),merge_areas(e,t.areas),u.updateProps(e.area_options,e.options)}return me.get=function(e){var t=m.getMapData(this);if(!t||!t.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:e,first:!0,allowAsync:!0,defaultReturn:""}).go()},me.data=function(e){return new m.Method(this,null,function(){return this},{name:"data",args:arguments,key:e}).go()},me.highlight=function(t){return new m.Method(this,function(){if(!1!==t){var e=this.highlightId;return 0<=e?this.data[e].key:null}this.ensureNoHighlight()},function(){this.highlight()},{name:"highlight",args:arguments,key:t,first:!0}).go()},me.keys=function(e,i){var n=[],a=m.getMapData(this);if(!a||!a.complete)throw"Can't access data until binding complete.";function s(e){var t,a=[];i?(t=e.areas(),$.each(t,function(e,t){a=a.concat(t.keys)})):a.push(e.key),$.each(a,function(e,t){$.inArray(t,n)<0&&n.push(t)})}return a&&a.complete?("string"==typeof e?i?s(a.getDataForKey(e)):n=[a.getKeysForGroup(e)]:(i=e,this.each(function(e,t){"AREA"===t.nodeName&&s(a.getDataForArea(t))})),n.join(",")):""},me.select=function(){me.set.call(this,!0)},me.deselect=function(){me.set.call(this,!1)},me.set=function(i,n,e){var s,o,r,c,l=e;function h(e){e&&$.inArray(e,c)<0&&(c.push(e),r+=(""===r?"":",")+e.key)}function d(a){$.each(c,function(e,t){t=function(e){var t=i;if(e){switch(i){case!0:e.select(l);break;case!1:e.deselect(!0);break;default:t=e.toggle(l)}return t}}(t);a.options.boundList&&m.setBoundListProperties(a.options,m.getBoundList(a.options,r),t)}),i||a.removeSelectionFinish()}return this.filter("img,area").each(function(e,t){var a;(o=m.getMapData(t))!==s&&(s&&d(s),c=[],r=""),o&&(a="","IMG"===t.nodeName.toUpperCase()?m.queueCommand(o,$(t),"set",[i,n,l])||(n instanceof Array?n.length&&(a=n.join(",")):a=n,a&&$.each(u.split(a),function(e,t){h(o.getDataForKey(t.toString())),s=o})):(l=n,m.queueCommand(o,$(t),"set",[i,l])||(h(o.getDataForArea(t)),s=o)))}),o&&d(o),this},me.unbind=function(e){return new m.Method(this,function(){this.clearEvents(),this.clearMapData(e),removeMap(this)},null,{name:"unbind",args:arguments}).go()},me.rebind=function(t){return new m.Method(this,function(){var e=this;e.complete=!1,e.configureOptions(t),e.bindImages().then(function(){e.buildDataset(!0),e.complete=!0})},null,{name:"rebind",args:arguments}).go()},me.get_options=function(e,t){var a=u.isBool(e)?e:t;return new m.Method(this,function(){var e=$.extend({},this.options);return a&&(e.render_select=u.updateProps({},m.render_defaults,e,e.render_select),e.render_highlight=u.updateProps({},m.render_defaults,e,e.render_highlight)),e},function(){return a?this.effectiveOptions():this.options},{name:"get_options",args:arguments,first:!0,allowAsync:!0,key:e}).go()},me.set_options=function(e){return new m.Method(this,function(){merge_options(this,e)},null,{name:"set_options",args:arguments}).go()},me.unload=function(){for(var e=m.map_cache.length-1;0<=e;e--)m.map_cache[e]&&me.unbind.call($(m.map_cache[e].image));me.graphics=null},me.snapshot=function(){return new m.Method(this,function(){$.each(this.data,function(e,t){t.selected=!1}),this.base_canvas=this.graphics.createVisibleCanvas(this),$(this.image).before(this.base_canvas)},null,{name:"snapshot"}).go()},me.state=function(){var a,i=null;return $(this).each(function(e,t){if("IMG"===t.nodeName)return(a=m.getMapData(t))&&(i=a.state()),!1}),i},me.bind=function(s){return this.each(function(e,t){var a,i=$(t),n=m.getMapData(t);if(n){if(me.unbind.apply(i),!n.complete)return i.on(),!0;n=null}if(t=(a=this.getAttribute("usemap"))&&$('map[name="'+a.substr(1)+'"]'),!(i.is("img")&&a&&0')[0]},clearHighlight:function(){var e=this.map_data.overlay_canvas;e.getContext("2d").clearRect(0,0,e.width,e.height)},refreshSelections:function(){var e=this.map_data,t=e.base_canvas;e.base_canvas=this.createVisibleCanvas(e),cd(e.base_canvas).hide(),cd(t).before(e.base_canvas),e.redrawSelections(),cd(e.base_canvas).show(),cd(t).remove()}},hd={renderShape:function(e,t,a){var i,n=this,s=e.coords(),o=n.elementName?'name="'+n.elementName+'" ':"",r=a?'class="'+a+'" ':"",c='',l=t.stroke?" strokeweight="+t.strokeWidth+' stroked="t" strokecolor="#'+t.strokeColor+'"':' stroked="f"',u=t.fill?' filled="t"':' filled="f"';switch(e.shape){case"rect":i="'+c+"";break;case"poly":i="'+c+"";break;case"circ":case"circle":i="'+c+""}return e=cd(i),cd(n.canvas).append(e),e},render:function(){var a,i=this;return cd.each(this.shapes,function(e,t){i.renderShape(t.mapArea,t.options)}),this.masks.length&&cd.each(this.masks,function(e,t){a=fd.updateProps({},t.options,{fillOpacity:1,fillColor:t.options.fillColorMask}),i.renderShape(t.mapArea,a,"mapster_mask")}),this.active=!1,this.canvas},createCanvasFor:function(e){var t=e.scaleInfo.width,e=e.scaleInfo.height;return cd('')[0]},clearHighlight:function(){cd(this.map_data.overlay_canvas).children().remove()},removeSelections:function(e){(0<=e?cd(this.map_data.base_canvas).find('[name="static_'+e.toString()+'"]'):cd(this.map_data.base_canvas).children()).remove()}},cd.each(["renderShape","addAltImage","render","createCanvasFor","clearHighlight","removeSelections","refreshSelections"],function(e,t){var a;dd[t]=(a=t,function(){return dd[a]=(ed.hasCanvas()?gd:hd)[a]||ld,dd[a].apply(this,arguments)})}),Se=jQuery,Te=Se.mapster,Ue=Te.utils,Ve=[],Te.MapImages=function(e){this.owner=e,this.clear()},Te.MapImages.prototype={constructor:Te.MapImages,slice:function(){return Ve.slice.apply(this,arguments)},splice:function(){return Ve.slice.apply(this.status,arguments),Ve.slice.apply(this,arguments)},complete:function(){return Se.inArray(!1,this.status)<0},_add:function(e){e=Ve.push.call(this,e)-1;return this.status[e]=!1,e},indexOf:function(e){return Ue.indexOf(this,e)},clear:function(){var a=this;a.ids&&0").addClass("mapster_el").hide(),a=n._add(e[0]),e.on("load",function(e){n.imageLoaded.call(n,e)}).on("error",function(e){n.imageLoadError.call(n,e)}),e.attr("src",i)}else a=n._add(Se(e)[0]);if(t){if(this[t])throw t+" is already used or is not available as an altImage alias.";n.ids.push(t),n[t]=n[a]}return a}},bind:function(e){var t=this,a=t.owner.options.configTimeout/200,i=function(){for(var e=t.length;0
'),u.wrapClass&&(!0===u.wrapClass?a.addClass(s[0].className):a.addClass(u.wrapClass))),l.wrapper=a,l.scaleInfo=c=Df.scaleMap(l.images[0],l.images[1],u.scaleMap),l.base_canvas=t=l.graphics.createVisibleCanvas(l),l.overlay_canvas=r=l.graphics.createVisibleCanvas(l),e=Bf(l.images[1]).addClass("mapster_el "+l.images[0].className).attr({id:null,usemap:null}),(n=Df.size(l.images[0])).complete&&e.css({width:n.width,height:n.height}),l.buildDataset(),c={display:"block",position:"relative",padding:0,width:c.width,height:c.height},u.wrapCss&&Bf.extend(c,u.wrapCss),s.parent()[0]!==l.wrapper[0]&&s.before(l.wrapper),a.css(c),Bf(l.images.slice(2)).hide(),i=1;iw&&t[1]>k)return v=t,!(o=!0)}),o||(v=[l,u])),v},Rj=jQuery,Sj=Rj.mapster,Tj=Sj.utils,Uj=Sj.MapArea.prototype,Sj.utils.getScaleInfo=function(e,t){var a;return t?.98<(a=e.width/t.width||e.height/t.height)&&a<1.02&&(a=1):(a=1,t=e),{scale:1!==a,scalePct:a,realWidth:t.width,realHeight:t.height,width:e.width,height:e.height,ratio:e.width/e.height}},Sj.utils.scaleMap=function(e,t,a){e=Tj.size(e),t=Tj.size(t,!0);if(!t.complete())throw"Another script, such as an extension, appears to be interfering with image loading. Please let us know about this.";return e.complete()||(e=t),this.getScaleInfo(e,a?t:null)},Sj.MapData.prototype.resize=function(t,a,i,n){var s,o,r,c,e,l=this;function u(e,t,a){Sj.hasCanvas()?(e.width=t,e.height=a):(Rj(e).width(t),Rj(e).height(a))}function h(){var e;u(l.overlay_canvas,t,a),0<=c&&((e=l.data[c]).tempOptions={fade:!1},l.getDataForKey(e.key).highlight(),e.tempOptions=null),u(l.base_canvas,t,a),l.redrawSelections(),l.currentAction="",Tj.isFunction(n)&&n(),l.processCommandQueue()}function d(){Rj(l.image).css(r),l.scaleInfo=Tj.getScaleInfo({width:t,height:a},{width:l.scaleInfo.realWidth,height:l.scaleInfo.realHeight}),Rj.each(l.data,function(e,t){Rj.each(t.areas(),function(e,t){t.resize()})})}n=n||i,l.scaleInfo.width===t&&l.scaleInfo.height===a||(c=l.highlightId,t||(e=a/l.scaleInfo.realHeight,t=Math.round(l.scaleInfo.realWidth*e)),a||(e=t/l.scaleInfo.realWidth,a=Math.round(l.scaleInfo.realHeight*e)),r={width:String(t)+"px",height:String(a)+"px"},Sj.hasCanvas()||Rj(l.base_canvas).children().remove(),e=Rj(l.wrapper).find(".mapster_el").add(l.wrapper),i?(o=[],l.currentAction="resizing",e.each(function(e,t){s=Tj.defer(),o.push(s),Rj(t).animate(r,{duration:i,complete:s.resolve,easing:"linear"})}),s=Tj.defer(),o.push(s),Tj.when.all(o).then(h),d(),s.resolve()):(e.css(r),d(),h()))},Sj.MapArea=Tj.subclass(Sj.MapArea,function(){this.base.init(),this.owner.scaleInfo.scale&&this.resize()}),Uj.coords=function(e,t){var a,i=[],n=e||this.owner.scaleInfo.scalePct,s=t||0;if(1===n&&0===t)return this.originalCoords;for(a=0;a
',showToolTip:!1,toolTipFade:!0,toolTipClose:["area-mouseout","image-mouseout"],onShowToolTip:null,onHideToolTip:null}),Lk.extend(Mk.area_defaults,{toolTip:null,toolTipClose:null}),Mk.MapData.prototype.clearToolTip=function(){this.activeToolTip&&(this.activeToolTip.stop().remove(),this.activeToolTip=null,this.activeToolTipID=null,Nk.ifFunction(this.options.onHideToolTip,this))},Mk.AreaData.prototype.showToolTip=function(e,t){var a,i,n,s,o=this,r=o.owner,c=o.effectiveOptions();if(t=t?Lk.extend({},t):{},e=e||c.toolTip,i=t.closeEvents||c.toolTipClose||r.options.toolTipClose||"tooltip-click",s=void 0!==t.template?t.template:r.options.toolTipContainer,t.closeEvents="string"==typeof i?i=Nk.split(i):i,t.fadeDuration=t.fadeDuration||(r.options.toolTipFade?r.options.fadeDuration||c.fadeDuration:0),n=o.area||Lk.map(o.areas(),function(e){return e.area}),r.activeToolTipID!==o.areaId)return r.clearToolTip(),r.activeToolTip=a=Ok(e,s,t.css),r.activeToolTipID=o.areaId,e=function(){r.clearToolTip()},Qk(i,"area-click","click",Lk(r.map),null,e),Qk(i,"tooltip-click","click",a,null,e),Qk(i,"image-mouseout","mouseout",Lk(r.image),function(e){return e.relatedTarget&&"AREA"!==e.relatedTarget.nodeName&&e.relatedTarget!==o.area},e),Rk(a,n,r.image,t.container,s),Nk.ifFunction(r.options.onShowToolTip,o.area,{toolTip:a,options:{},areaOptions:c,key:o.key,selected:o.isSelected()}),a},Mk.impl.tooltip=function(i,n){return new Mk.Method(this,function(){var e,t,a=this;i?(t=Lk(i),a.activeToolTipID!==t[0]&&(a.clearToolTip(),a.activeToolTip=e=Ok(Sk(n),n.template||a.options.toolTipContainer,n.css),a.activeToolTipID=t[0],Qk(["tooltip-click"],"tooltip-click","click",e,null,function(){a.clearToolTip()}),a.activeToolTip=Rk(e,t,a.image,n.container,n))):a.clearToolTip()},function(){Lk.isPlainObject(i)&&!n&&(n=i),this.showToolTip(Sk(n),n)},{name:"tooltip",args:arguments,key:i}).go()}}); +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),a(t),t}:a(jQuery)}(function(jQuery){!function(e){"use strict";var i,t=!1;try{var a=Object.defineProperty({},"passive",{get:function(){return t=!0}});window.addEventListener("testPassive.mapster",function(){},a),window.removeEventListener("testPassive.mapster",function(){},a)}catch(e){}t&&(i=function(e,t,a){if(!e.includes("noPreventDefault"))return console.warn("non-passive events - listener not added"),!1;window.addEventListener(t,a,{passive:!0})},e.event.special.touchstart={setup:function(e,t,a){return i(t,"touchstart",a)}},e.event.special.touchend={setup:function(e,t,a){return i(t,"touchend",a)}})}(jQuery),function($){"use strict";var mapster_version="1.3.2-beta.0",ya,za,Aa;$.fn.mapster=function(e){var t=$.mapster.impl;return $.mapster.utils.isFunction(t[e])?t[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?void $.error("Method "+e+" does not exist on jQuery.mapster"):t.bind.apply(this,arguments)},$.mapster={version:mapster_version,render_defaults:{isSelectable:!0,isDeselectable:!0,fade:!1,fadeDuration:150,fill:!0,fillColor:"000000",fillColorMask:"FFFFFF",fillOpacity:.7,highlight:!0,stroke:!1,strokeColor:"ff0000",strokeOpacity:1,strokeWidth:1,includeKeys:"",altImage:null,altImageId:null,altImages:{}},defaults:{clickNavigate:!1,wrapClass:null,wrapCss:null,onGetList:null,sortList:!1,listenToList:!1,mapKey:"",mapValue:"",singleSelect:!1,listKey:"value",listSelectedAttribute:"selected",listSelectedClass:null,onClick:null,onMouseover:null,onMouseout:null,mouseoutDelay:0,onStateChange:null,boundList:null,onConfigured:null,configTimeout:3e4,noHrefIsMask:!0,scaleMap:!0,safeLoad:!1,areas:[]},shared_defaults:{render_highlight:{fade:!0},render_select:{fade:!1},staticState:null,selected:null},area_defaults:{includeKeys:"",isMask:!1},canvas_style:{position:"absolute",left:0,top:0,padding:0,border:0},hasCanvas:null,map_cache:[],hooks:{},addHook:function(e,t){this.hooks[e]=(this.hooks[e]||[]).push(t)},callHooks:function(e,a){$.each(this.hooks[e]||[],function(e,t){t.apply(a)})},utils:{when:{all:function(e){return Promise.all(e)},defer:function(){return new function(){this.promise=new Promise(function(e,t){this.resolve=e,this.reject=t}.bind(this)),this.then=this.promise.then.bind(this.promise),this.catch=this.promise.catch.bind(this.promise)}}},defer:function(){return this.when.defer()},subclass:function(a,i){function e(){var e=this,t=Array.prototype.slice.call(arguments,0);e.base=a.prototype,e.base.init=function(){a.prototype.constructor.apply(e,t)},i.apply(e,t)}return(e.prototype=new a).constructor=e},asArray:function(e){return e.constructor===Array?e:this.split(e)},split:function(e,t){for(var a,i=e.split(","),n=0;n=e.index;t--)m.map_cache[t].index--};function hasVml(){var e=$("
").appendTo("body");e.html('');var t=e[0].firstChild;t.style.behavior="url(#default#VML)";t=!t||"object"==typeof t.adj;return e.remove(),t}function namespaces(){return"object"==typeof document.namespaces?document.namespaces:null}function hasCanvas(){var e=namespaces();return(!e||!e.g_vml_)&&!!$("")[0].getContext}function merge_areas(a,e){var i,n=a.options.areas;e&&$.each(e,function(e,t){t&&t.key&&(0<=(i=u.indexOfProp(n,"key",t.key))?$.extend(n[i],t):n.push(t),(i=a.getDataForKey(t.key))&&$.extend(i.options,t))})}function merge_options(e,t){var a=u.updateProps({},t);delete a.areas,u.updateProps(e.options,a),merge_areas(e,t.areas),u.updateProps(e.area_options,e.options)}return me.get=function(e){var t=m.getMapData(this);if(!t||!t.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:e,first:!0,allowAsync:!0,defaultReturn:""}).go()},me.data=function(e){return new m.Method(this,null,function(){return this},{name:"data",args:arguments,key:e}).go()},me.highlight=function(t){return new m.Method(this,function(){if(!1!==t){var e=this.highlightId;return 0<=e?this.data[e].key:null}this.ensureNoHighlight()},function(){this.highlight()},{name:"highlight",args:arguments,key:t,first:!0}).go()},me.keys=function(e,i){var n=[],a=m.getMapData(this);if(!a||!a.complete)throw"Can't access data until binding complete.";function s(e){var t,a=[];i?(t=e.areas(),$.each(t,function(e,t){a=a.concat(t.keys)})):a.push(e.key),$.each(a,function(e,t){$.inArray(t,n)<0&&n.push(t)})}return a&&a.complete?("string"==typeof e?i?s(a.getDataForKey(e)):n=[a.getKeysForGroup(e)]:(i=e,this.each(function(e,t){"AREA"===t.nodeName&&s(a.getDataForArea(t))})),n.join(",")):""},me.select=function(){me.set.call(this,!0)},me.deselect=function(){me.set.call(this,!1)},me.set=function(i,n,e){var s,o,r,c,l=e;function h(e){e&&$.inArray(e,c)<0&&(c.push(e),r+=(""===r?"":",")+e.key)}function p(a){$.each(c,function(e,t){t=function(e){var t=i;if(e){switch(i){case!0:e.select(l);break;case!1:e.deselect(!0);break;default:t=e.toggle(l)}return t}}(t);a.options.boundList&&m.setBoundListProperties(a.options,m.getBoundList(a.options,r),t)}),i||a.removeSelectionFinish()}return this.filter("img,area").each(function(e,t){var a;(o=m.getMapData(t))!==s&&(s&&p(s),c=[],r=""),o&&(a="","IMG"===t.nodeName.toUpperCase()?m.queueCommand(o,$(t),"set",[i,n,l])||(n instanceof Array?n.length&&(a=n.join(",")):a=n,a&&$.each(u.split(a),function(e,t){h(o.getDataForKey(t.toString())),s=o})):(l=n,m.queueCommand(o,$(t),"set",[i,l])||(h(o.getDataForArea(t)),s=o)))}),o&&p(o),this},me.unbind=function(e){return new m.Method(this,function(){this.clearEvents(),this.clearMapData(e),removeMap(this)},null,{name:"unbind",args:arguments}).go()},me.rebind=function(t){return new m.Method(this,function(){var e=this;e.complete=!1,e.configureOptions(t),e.bindImages().then(function(){e.buildDataset(!0),e.complete=!0})},null,{name:"rebind",args:arguments}).go()},me.get_options=function(e,t){var a=u.isBool(e)?e:t;return new m.Method(this,function(){var e=$.extend({},this.options);return a&&(e.render_select=u.updateProps({},m.render_defaults,e,e.render_select),e.render_highlight=u.updateProps({},m.render_defaults,e,e.render_highlight)),e},function(){return a?this.effectiveOptions():this.options},{name:"get_options",args:arguments,first:!0,allowAsync:!0,key:e}).go()},me.set_options=function(e){return new m.Method(this,function(){merge_options(this,e)},null,{name:"set_options",args:arguments}).go()},me.unload=function(){for(var e=m.map_cache.length-1;0<=e;e--)m.map_cache[e]&&me.unbind.call($(m.map_cache[e].image));me.graphics=null},me.snapshot=function(){return new m.Method(this,function(){$.each(this.data,function(e,t){t.selected=!1}),this.base_canvas=this.graphics.createVisibleCanvas(this),$(this.image).before(this.base_canvas)},null,{name:"snapshot"}).go()},me.state=function(){var a,i=null;return $(this).each(function(e,t){if("IMG"===t.nodeName)return(a=m.getMapData(t))&&(i=a.state()),!1}),i},me.bind=function(s){return this.each(function(e,t){var a,i=$(t),n=m.getMapData(t);if(n){if(me.unbind.apply(i),!n.complete)return i.on(),!0;n=null}if(t=(a=this.getAttribute("usemap"))&&$('map[name="'+a.substr(1)+'"]'),!(i.is("img")&&a&&0')[0]},clearHighlight:function(){var e=this.map_data.overlay_canvas;e.getContext("2d").clearRect(0,0,e.width,e.height)},refreshSelections:function(){var e=this.map_data,t=e.base_canvas;e.base_canvas=this.createVisibleCanvas(e),h(e.base_canvas).hide(),h(t).before(e.base_canvas),e.redrawSelections(),h(e.base_canvas).show(),h(t).remove()}},s={renderShape:function(e,t,a){var i,n=this,s=e.coords(),o=n.elementName?'name="'+n.elementName+'" ':"",r=a?'class="'+a+'" ':"",c='',l=t.stroke?" strokeweight="+t.strokeWidth+' stroked="t" strokecolor="#'+t.strokeColor+'"':' stroked="f"',u=t.fill?' filled="t"':' filled="f"';switch(e.shape){case"rect":i="'+c+"";break;case"poly":i="'+c+"";break;case"circ":case"circle":i="'+c+""}return e=h(i),h(n.canvas).append(e),e},render:function(){var a,i=this;return h.each(this.shapes,function(e,t){i.renderShape(t.mapArea,t.options)}),this.masks.length&&h.each(this.masks,function(e,t){a=c.updateProps({},t.options,{fillOpacity:1,fillColor:t.options.fillColorMask}),i.renderShape(t.mapArea,a,"mapster_mask")}),this.active=!1,this.canvas},createCanvasFor:function(e){var t=e.scaleInfo.width,e=e.scaleInfo.height;return h('')[0]},clearHighlight:function(){h(this.map_data.overlay_canvas).children().remove()},removeSelections:function(e){(0<=e?h(this.map_data.base_canvas).find('[name="static_'+e.toString()+'"]'):h(this.map_data.base_canvas).children()).remove()}},h.each(["renderShape","addAltImage","render","createCanvasFor","clearHighlight","removeSelections","refreshSelections"],function(e,t){var a;i[t]=(a=t,function(){return i[a]=(r.hasCanvas()?n:s)[a]||o,i[a].apply(this,arguments)})})}(jQuery),function(s){"use strict";var e=s.mapster,n=e.utils,t=[];e.MapImages=function(e){this.owner=e,this.clear()},e.MapImages.prototype={constructor:e.MapImages,slice:function(){return t.slice.apply(this,arguments)},splice:function(){return t.slice.apply(this.status,arguments),t.slice.apply(this,arguments)},complete:function(){return s.inArray(!1,this.status)<0},_add:function(e){e=t.push.call(this,e)-1;return this.status[e]=!1,e},indexOf:function(e){return n.indexOf(this,e)},clear:function(){var a=this;a.ids&&0").addClass("mapster_el").hide(),a=n._add(e[0]),e.on("load",function(e){n.imageLoaded.call(n,e)}).on("error",function(e){n.imageLoadError.call(n,e)}),e.attr("src",i)}else a=n._add(s(e)[0]);if(t){if(this[t])throw t+" is already used or is not available as an altImage alias.";n.ids.push(t),n[t]=n[a]}return a}},bind:function(){var t=this,a=t.owner.options.configTimeout/200,i=function(){for(var e=t.length;0
'),u.wrapClass&&(!0===u.wrapClass?a.addClass(s[0].className):a.addClass(u.wrapClass))),l.wrapper=a,l.scaleInfo=c=p.scaleMap(l.images[0],l.images[1],u.scaleMap),l.base_canvas=t=l.graphics.createVisibleCanvas(l),l.overlay_canvas=r=l.graphics.createVisibleCanvas(l),e=y(l.images[1]).addClass("mapster_el "+l.images[0].className).attr({id:null,usemap:null}),(n=p.size(l.images[0])).complete&&e.css({width:n.width,height:n.height}),l.buildDataset(),c={display:"block",position:"relative",padding:0,width:c.width,height:c.height},u.wrapCss&&y.extend(c,u.wrapCss),s.parent()[0]!==l.wrapper[0]&&s.before(l.wrapper),a.css(c),y(l.images.slice(2)).hide(),i=1;iw&&t[1]>b)return v=t,!(o=!0)}),o||(v=[l,u])),v}}(jQuery),function(d){"use strict";var f=d.mapster,m=f.utils,e=f.MapArea.prototype;f.utils.getScaleInfo=function(e,t){var a;return t?.98<(a=e.width/t.width||e.height/t.height)&&a<1.02&&(a=1):(a=1,t=e),{scale:1!==a,scalePct:a,realWidth:t.width,realHeight:t.height,width:e.width,height:e.height,ratio:e.width/e.height}},f.utils.scaleMap=function(e,t,a){e=m.size(e),t=m.size(t,!0);if(!t.complete())throw"Another script, such as an extension, appears to be interfering with image loading. Please let us know about this.";return e.complete()||(e=t),this.getScaleInfo(e,a?t:null)},f.MapData.prototype.resize=function(t,a,i,n){var s,o,r,c,e,l=this;function u(e,t,a){f.hasCanvas()?(e.width=t,e.height=a):(d(e).width(t),d(e).height(a))}function h(){var e;u(l.overlay_canvas,t,a),0<=c&&((e=l.data[c]).tempOptions={fade:!1},l.getDataForKey(e.key).highlight(),e.tempOptions=null),u(l.base_canvas,t,a),l.redrawSelections(),l.currentAction="",m.isFunction(n)&&n(),l.processCommandQueue()}function p(){d(l.image).css(r),l.scaleInfo=m.getScaleInfo({width:t,height:a},{width:l.scaleInfo.realWidth,height:l.scaleInfo.realHeight}),d.each(l.data,function(e,t){d.each(t.areas(),function(e,t){t.resize()})})}n=n||i,l.scaleInfo.width===t&&l.scaleInfo.height===a||(c=l.highlightId,t||(e=a/l.scaleInfo.realHeight,t=Math.round(l.scaleInfo.realWidth*e)),a||(e=t/l.scaleInfo.realWidth,a=Math.round(l.scaleInfo.realHeight*e)),r={width:String(t)+"px",height:String(a)+"px"},f.hasCanvas()||d(l.base_canvas).children().remove(),e=d(l.wrapper).find(".mapster_el").add(l.wrapper),i?(o=[],l.currentAction="resizing",e.each(function(e,t){s=m.defer(),o.push(s),d(t).animate(r,{duration:i,complete:s.resolve,easing:"linear"})}),s=m.defer(),o.push(s),m.when.all(o).then(h),p(),s.resolve()):(e.css(r),p(),h()))},f.MapArea=m.subclass(f.MapArea,function(){this.base.init(),this.owner.scaleInfo.scale&&this.resize()}),e.coords=function(e,t){var a,i=[],n=e||this.owner.scaleInfo.scalePct,s=t||0;if(1===n&&0===t)return this.originalCoords;for(a=0;a
',showToolTip:!1,toolTipFade:!0,toolTipClose:["area-mouseout","image-mouseout"],onShowToolTip:null,onHideToolTip:null}),l.extend(e.area_defaults,{toolTip:null,toolTipClose:null}),e.MapData.prototype.clearToolTip=function(){this.activeToolTip&&(this.activeToolTip.stop().remove(),this.activeToolTip=null,this.activeToolTipID=null,u.ifFunction(this.options.onHideToolTip,this))},e.AreaData.prototype.showToolTip=function(e,t){var a,i,n,s,o=this,r=o.owner,c=o.effectiveOptions();if(t=t?l.extend({},t):{},e=e||c.toolTip,i=t.closeEvents||c.toolTipClose||r.options.toolTipClose||"tooltip-click",s=void 0!==t.template?t.template:r.options.toolTipContainer,t.closeEvents="string"==typeof i?i=u.split(i):i,t.fadeDuration=t.fadeDuration||(r.options.toolTipFade?r.options.fadeDuration||c.fadeDuration:0),n=o.area||l.map(o.areas(),function(e){return e.area}),r.activeToolTipID!==o.areaId)return r.clearToolTip(),r.activeToolTip=a=h(e,s,t.css),r.activeToolTipID=o.areaId,e=function(){r.clearToolTip()},p(i,"area-click","click",l(r.map),null,e),p(i,"tooltip-click","click",a,null,e),p(i,"image-mouseout","mouseout",l(r.image),function(e){return e.relatedTarget&&"AREA"!==e.relatedTarget.nodeName&&e.relatedTarget!==o.area},e),d(a,n,r.image,t.container,s),u.ifFunction(r.options.onShowToolTip,o.area,{toolTip:a,options:{},areaOptions:c,key:o.key,selected:o.isSelected()}),a},e.impl.tooltip=function(i,n){return new e.Method(this,function(){var e,t,a=this;i?(t=l(i),a.activeToolTipID!==t[0]&&(a.clearToolTip(),a.activeToolTip=e=h(s(n),n.template||a.options.toolTipContainer,n.css),a.activeToolTipID=t[0],p(["tooltip-click"],"tooltip-click","click",e,null,function(){a.clearToolTip()}),a.activeToolTip=d(e,t,a.image,n.container,n))):a.clearToolTip()},function(){l.isPlainObject(i)&&!n&&(n=i),this.showToolTip(s(n),n)},{name:"tooltip",args:arguments,key:i}).go()}}(jQuery)}); //# sourceMappingURL=jquery.imagemapster.min.js.map \ No newline at end of file diff --git a/dist/jquery.imagemapster.min.js.map b/dist/jquery.imagemapster.min.js.map index 5aac7f8..997c2aa 100644 --- a/dist/jquery.imagemapster.min.js.map +++ b/dist/jquery.imagemapster.min.js.map @@ -1 +1 @@ -{"version":3,"file":"jquery.imagemapster.min.js","sources":["jquery.imagemapster.js"],"names":["factory","define","amd","module","exports","root","jQuery","undefined","window","require","addShapeGroupImpl","graphics","areaData","options","me","md","map_data","isMask","$","each","areas","i","e","nohref","noHrefIsMask","addShape","hex_to_decimal","hex","Math","max","min","parseInt","css3color","color","opacity","substr","noop","p","canvasMethods","vmlMethods","m","u","ap","configureAltImages","opts","mi","images","hasCanvas","altImages","add","concat","obj","render_highlight","render_select","i2","e2","altImage","altImageId","area_options","updateProps","area_defaults","mouseout","ar","getDataForArea","this","currentAreaId","relatedTarget","area","queueMouseEvent","delay","deferred","when","defer","activeAreaEvent","clearTimeout","reject","owner","currentAction","setTimeout","areaId","highlightId","resolve","mouseoutDelay","then","clearEffects","isFunction","onMouseout","call","key","selected","isSelected","click","list","list_target","newSelectionState","canChangeState","cbResult","that","blur","preventDefault","clickNavigate","href","location","clickArea","target","isSelectable","isDeselectable","getBoundList","onClick","listTarget","isBool","attr","toggle","boundList","length","setBoundListProperties","areaOpts","effectiveOptions","includeKeys","split","getDataForKey","toString","createToolTip","html","template","css","tooltip","clone","append","extend","display","position","hide","show","bindToolTipClose","bindOption","event","beforeClose","onClose","event_name","inArray","off","on","showToolTip","image","container","tooltipCss","ttopts","corners","areaCorners","outerWidth","outerHeight","left","top","offsetx","offsety","fadeDuration","actalOpacity","zindex","addClass","fader","setOpacity","getHtmlFromOptions","jquery","content","setupListener","supportsPassive","Object","defineProperty","get","addEventListener","removeEventListener","ns","type","listener","includes","console","warn","passive","special","touchstart","setup","_","touchend","mapster_version","elements","lastKey","fade_func","fn","mapster","method","impl","utils","apply","Array","prototype","slice","arguments","error","bind","version","render_defaults","fade","fill","fillColor","fillColorMask","fillOpacity","highlight","stroke","strokeColor","strokeOpacity","strokeWidth","defaults","wrapClass","wrapCss","onGetList","sortList","listenToList","mapKey","mapValue","singleSelect","listKey","listSelectedAttribute","listSelectedClass","onMouseover","onStateChange","onConfigured","configTimeout","scaleMap","safeLoad","shared_defaults","staticState","canvas_style","padding","border","map_cache","hooks","addHook","name","callback","push","callHooks","context","all","deferredArray","Promise","promise","catch","subclass","BaseClass","constr","Subclass","args","base","init","constructor","asArray","text","cb","el","arr","trim","splice","_target","_template","isEmptyObject","onlyProps","prop","src","isPlainObject","isElement","o","HTMLElement","nodeType","nodeName","indexOf","indexOfProp","val","result","boolOrDefault","def","isUndef","ifFunction","size","raw","width","naturalWidth","imgWidth","height","naturalHeight","imgHeight","complete","style","op","endOp","duration","index","cbIntervals","key_list","is","removeClass","getMapDataIndex","img","id","tagName","toLowerCase","parent","getMapData","queueCommand","command","commands","unload","capProp","toUpperCase","jqwidth","Method","func_map","func_area","output","input","first","allowAsync","go","data","area_list","len","getData","addMap","removeMap","hasVml","a","appendTo","b","firstChild","behavior","has","adj","remove","namespaces","document","d","g_vml_","getContext","merge_areas","map_areas","merge_options","temp_opts","getSelected","defaultReturn","ensureNoHighlight","keys","keyList","addUniqueKeys","ad","getKeysForGroup","join","select","set","deselect","lastMap","addArea","finishSetForMap","newState","setSelection","removeSelectionFinish","filter","unbind","preserveState","clearEvents","clearMapData","rebind","configureOptions","bindImages","buildDataset","get_options","effective","eff","set_options","snapshot","base_canvas","createVisibleCanvas","before","state","usemap","map","getAttribute","MapData","initialize","useCanvas","shapes","value","v","createStyleSheet","addRule","test","eval","Graphics","active","canvas","masks","begin","elementName","c","mapArea","mapData","createCanvasFor","addShapeGroup","mode","effectiveRenderOptions","overlay_canvas","render","find","not","renderShape","offset","coords","shape","rect","moveTo","lineTo","arc","PI","addAltImage","beginPath","closePath","clip","globalAlpha","altImageOpacity","drawImage","scaleInfo","maskCanvas","maskContext","hasMasks","shapeCanvas","shapeContext","clearRect","save","lineWidth","fillStyle","restore","s","strokeStyle","globalCompositeOperation","clearHighlight","refreshSelections","canvas_temp","redrawSelections","cssclass","el_name","el_class","t_fill","w","h","children","removeSelections","area_id","MapImages","clear","status","_add","ids","imageLoaded","imageLoadError","retry","triesLeft","check","isLoaded","imgTimeout","resolver","imgCssText","cssText","mapAreas","_xref","_tooltip_events","mouseover","arData","getAllDataForArea","isNotRendered","toolTip","toolTipClose","activeToolTip","clearToolTip","isActive","resizing","zoomed","zoomedArea","wrapId","_idFromKey","hasOwnProperty","atMost","isPrimary","getPrimaryKeysForMapAreas","changeState","setHighlightId","clearSelections","setAreaOptions","drawSelections","key_arr","drawSelection","isSelectedOrStatic","imgCopy","wrap","sort_func","sorted_list","scale","parentId","substring","className","wrapper","sort","processCommandQueue","sel","j","$area","curKey","mapAreaId","group_value","dataItem","default_group","addAreaData","AreaData","String","configure","MapArea","areaDataXref","areasXref","mousedown","cur","_clearCanvases","reset","clearTooltip","g","optsCache","constuctor","partial","allOpts","state_type","areaEl","originalCoords","parseFloat","pos","found","minX","minY","maxX","maxY","bestMinX","bestMaxX","bestMinY","bestMaxY","curX","curY","nest","rootx","rooty","iCoords","radius","angle","body","cos","sin","getScaleInfo","actual","pct","scalePct","realWidth","realHeight","ratio","imageRaw","vis","resize","promises","newsize","sizeCanvas","finishResize","tempOptions","resizeMapData","round","els","animate","easing","percent","coordOffset","newCoords","toolTipContainer","toolTipFade","onShowToolTip","onHideToolTip","stop","activeToolTipID","closeOpts","closeEvents","tipClosed","areaOptions"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"jquery.imagemapster.min.js","sources":["jquery.imagemapster.js"],"names":["factory","define","amd","module","exports","root","jQuery","undefined","window","require","$","setupListener","supportsPassive","opts","Object","defineProperty","get","addEventListener","removeEventListener","e","ns","type","listener","includes","console","warn","passive","event","special","touchstart","setup","_","touchend","mapster_version","elements","lastKey","fade_func","fn","mapster","method","m","impl","utils","isFunction","apply","this","Array","prototype","slice","call","arguments","error","bind","version","render_defaults","isSelectable","isDeselectable","fade","fadeDuration","fill","fillColor","fillColorMask","fillOpacity","highlight","stroke","strokeColor","strokeOpacity","strokeWidth","includeKeys","altImage","altImageId","altImages","defaults","clickNavigate","wrapClass","wrapCss","onGetList","sortList","listenToList","mapKey","mapValue","singleSelect","listKey","listSelectedAttribute","listSelectedClass","onClick","onMouseover","onMouseout","mouseoutDelay","onStateChange","boundList","onConfigured","configTimeout","noHrefIsMask","scaleMap","safeLoad","areas","shared_defaults","render_highlight","render_select","staticState","selected","area_defaults","isMask","canvas_style","position","left","top","padding","border","hasCanvas","map_cache","hooks","addHook","name","callback","push","callHooks","context","each","when","all","deferredArray","Promise","defer","promise","resolve","reject","then","catch","subclass","BaseClass","constr","Subclass","me","args","base","init","constructor","asArray","obj","split","text","cb","el","arr","i","length","trim","splice","updateProps","_target","_template","target","template","isEmptyObject","onlyProps","prop","src","p","inArray","isPlainObject","extend","isElement","o","HTMLElement","nodeType","nodeName","indexOf","indexOfProp","val","result","boolOrDefault","def","isBool","isUndef","ifFunction","that","size","image","raw","u","width","naturalWidth","imgWidth","height","naturalHeight","imgHeight","complete","setOpacity","opacity","style","css","fader","op","endOp","duration","index","cbIntervals","setTimeout","getBoundList","key_list","key","list","is","add","setBoundListProperties","addClass","removeClass","getMapDataIndex","img","id","tagName","toLowerCase","parent","attr","getMapData","queueCommand","map_data","command","currentAction","commands","unload","off","ap","capProp","substr","toUpperCase","jqwidth","Method","func_map","func_area","output","input","first","allowAsync","go","data","ar","area_list","len","getData","addMap","removeMap","hasVml","a","appendTo","html","b","firstChild","behavior","has","adj","remove","namespaces","document","d","g_vml_","getContext","merge_areas","map_areas","options","getDataForKey","merge_options","temp_opts","area_options","md","getSelected","isSelected","defaultReturn","highlightId","ensureNoHighlight","keys","keyList","addUniqueKeys","ad","concat","getKeysForGroup","getDataForArea","join","select","set","deselect","lastMap","addArea","finishSetForMap","newState","toggle","setSelection","removeSelectionFinish","filter","toString","unbind","preserveState","clearEvents","clearMapData","rebind","configureOptions","bindImages","buildDataset","get_options","effective","eff","effectiveOptions","set_options","graphics","snapshot","base_canvas","createVisibleCanvas","before","state","usemap","on","map","getAttribute","MapData","initialize","useCanvas","shapes","value","v","createStyleSheet","addRule","test","eval","canvasMethods","vmlMethods","addShapeGroupImpl","areaData","nohref","addShape","hex_to_decimal","hex","Math","max","min","parseInt","css3color","color","noop","Graphics","active","canvas","masks","begin","elementName","c","mapArea","mapData","createCanvasFor","addShapeGroup","mode","effectiveRenderOptions","areaId","overlay_canvas","render","find","not","renderShape","offset","coords","shape","rect","moveTo","lineTo","arc","PI","addAltImage","beginPath","closePath","clip","globalAlpha","altImageOpacity","drawImage","owner","scaleInfo","maskCanvas","maskContext","hasMasks","shapeCanvas","shapeContext","clearRect","save","lineWidth","fillStyle","restore","s","images","strokeStyle","globalCompositeOperation","clearHighlight","refreshSelections","canvas_temp","hide","redrawSelections","show","cssclass","el_name","el_class","t_fill","append","w","h","children","removeSelections","area_id","MapImages","clear","status","_add","ids","imageLoaded","imageLoadError","triesLeft","check","isLoaded","imgTimeout","deferred","resolver","clearTimeout","configureAltImages","mi","e2","mouseout","currentAreaId","relatedTarget","area","queueMouseEvent","delay","activeAreaEvent","clearEffects","click","list_target","newSelectionState","canChangeState","cbResult","blur","preventDefault","href","location","clickArea","listTarget","areaOpts","imgCssText","cssText","mapAreas","_xref","_tooltip_events","mouseover","arData","getAllDataForArea","isNotRendered","showToolTip","toolTip","toolTipClose","activeToolTip","clearToolTip","isActive","resizing","zoomed","zoomedArea","wrapId","_idFromKey","hasOwnProperty","atMost","isPrimary","getPrimaryKeysForMapAreas","changeState","setHighlightId","clearSelections","setAreaOptions","drawSelections","key_arr","drawSelection","isSelectedOrStatic","imgCopy","wrap","sort_func","sorted_list","scale","parentId","substring","className","wrapper","display","sort","processCommandQueue","sel","j","$area","curKey","mapAreaId","group_value","dataItem","default_group","addAreaData","AreaData","String","configure","MapArea","areaDataXref","areasXref","mousedown","cur","_clearCanvases","reset","clearTooltip","g","optsCache","constuctor","partial","allOpts","state_type","areaEl","originalCoords","parseFloat","areaCorners","container","pos","found","minX","minY","maxX","maxY","bestMinX","bestMaxX","bestMinY","bestMaxY","curX","curY","nest","rootx","rooty","iCoords","radius","angle","offsetx","offsety","body","cos","sin","getScaleInfo","actual","pct","scalePct","realWidth","realHeight","ratio","imageRaw","vis","resize","promises","newsize","sizeCanvas","finishResize","tempOptions","resizeMapData","round","els","animate","easing","percent","coordOffset","newCoords","createToolTip","tooltip","clone","bindToolTipClose","bindOption","beforeClose","onClose","event_name","tooltipCss","ttopts","corners","outerWidth","outerHeight","actalOpacity","zindex","getHtmlFromOptions","jquery","content","toolTipContainer","toolTipFade","onShowToolTip","onHideToolTip","stop","activeToolTipID","closeOpts","closeEvents","tipClosed","areaOptions"],"mappings":"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"} \ No newline at end of file diff --git a/examples/altimages.html b/examples/altimages.html index 807d5a2..4cedc4d 100644 --- a/examples/altimages.html +++ b/examples/altimages.html @@ -1,164 +1,686 @@ - - - - AltImages Demo - - - - - - - -

AltImages Demo

-

- click to bind az with altimage -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + AltImages Demo + + + + + + + +

AltImages Demo

+

+ click to bind az with altimage +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/multiple-maps.html b/examples/multiple-maps.html index 1bc36a9..a0e77b6 100644 --- a/examples/multiple-maps.html +++ b/examples/multiple-maps.html @@ -4,10 +4,7 @@ Multiple Maps Demo - + - - - - - - - - -

Shapes Demo (For Full IE Compatibility)

-

While masks sort of work for Internet Explorer 6-8, if you really want to create areas within areas, you can use polygons to split -a shape into two connecting parts and combine them as a group. This example is created by rendering the outer circle in two -"c-shaped" halves and rendering the inner circle separately. The other "shapes" demo uses masks and much less markup to do the same thing, -but more complex situations where the inner area is not a solid color might not work as well in IE.

-

  • The outer circle is selected independently from the inner circle.
  • -
  • The rightmost rectangle is selected with the center circle (and has independent rendering options), but does not itself respond to mouse events.
  • -
-

- - - - - - - - - - - - - - - - - - - - + + + Shapes Demo - IE + + + + + + + + + +

Shapes Demo (For Full IE Compatibility)

+

+ While masks sort of work for Internet Explorer 6-8, if you really want to + create areas within areas, you can use polygons to split a shape into two + connecting parts and combine them as a group. This example is created by + rendering the outer circle in two "c-shaped" halves and rendering the + inner circle separately. The other "shapes" demo uses masks and much less + markup to do the same thing, but more complex situations where the inner + area is not a solid color might not work as well in IE. +

+
    +
  • The outer circle is selected independently from the inner circle.
  • +
  • + The rightmost rectangle is selected with the center circle (and has + independent rendering options), but does not itself respond to mouse + events. +
  • +
+ + + + + + + + + + + + + + + + + + diff --git a/examples/shapes.html b/examples/shapes.html index 6496ce1..30f2c97 100644 --- a/examples/shapes.html +++ b/examples/shapes.html @@ -1,107 +1,151 @@ - - - -Shapes Demo - - - - - - - - - - - - -

Shapes Demo

-

This demo shows some advanced features using multiple groups and exclusions.

-

  • The outer circle is selected independently from the inner circle.
  • -
  • The inner circle area is used twice - once as an exclusion from the outer circle to create the ring effect, and again as its own -area. This is done by specifying two different groups in its "mapKey" and then specifying options for both to create the desired effect.
  • -
  • The small circle within the ring when highlighed is another exclusion and shows excluding multiple areas.
  • -
  • The rightmost rectangle is selected with the center circle (and has independent rendering options), but does not itself respond to mouse events.
  • -
-

-

-With Internet Explorer 6-8, masking is not possible. In this case the effect is simulated by rendering the masked area with a specific color -that can be defined with the "fillColorMask" option. This option is ignored when using HTML5 canvases. The default is white, which is used -for the inner circle, and 2nd small circle in the ring is set specifically to match the color of the ring area. -

- - - - - - - - - - - - - - - - + + + + Shapes Demo + + + + + + + + + + + +

Shapes Demo

+

+ This demo shows some advanced features using multiple groups and + exclusions. +

+
    +
  • The outer circle is selected independently from the inner circle.
  • +
  • + The inner circle area is used twice - once as an exclusion from the + outer circle to create the ring effect, and again as its own area. This + is done by specifying two different groups in its "mapKey" and then + specifying options for both to create the desired effect. +
  • +
  • + The small circle within the ring when highlighed is another exclusion + and shows excluding multiple areas. +
  • +
  • + The rightmost rectangle is selected with the center circle (and has + independent rendering options), but does not itself respond to mouse + events. +
  • +
+

+ With Internet Explorer 6-8, masking is not possible. In this case the + effect is simulated by rendering the masked area with a specific color + that can be defined with the "fillColorMask" option. This option is + ignored when using HTML5 canvases. The default is white, which is used for + the inner circle, and 2nd small circle in the ring is set specifically to + match the color of the ring area. +

+ + + + + + + + + + + + + + diff --git a/examples/stylesheets/base.css b/examples/stylesheets/base.css index f8af87c..6fa496c 100644 --- a/examples/stylesheets/base.css +++ b/examples/stylesheets/base.css @@ -1,8 +1,8 @@ body { - font-family: Arial, Helvetica; - font-size: 12px; + font-family: Arial, Helvetica; + font-size: 12px; } .navmenu { - margin: 20px 0 20px 0; -} \ No newline at end of file + margin: 20px 0 20px 0; +} diff --git a/examples/tooltips.html b/examples/tooltips.html index 6d58b87..25d0ef2 100644 --- a/examples/tooltips.html +++ b/examples/tooltips.html @@ -1,153 +1,185 @@ - - - - -Tooltip Examples - - - - - - - - - - - - - - -

Advanced ToolTip Example

-

- This shows how to use new ToolTip features in 1.2.6 to render tooltips manually, and for any area (inside or outside an imagemap) -

- - - - - - - - - - - - - - - -
-This is a div. This is some content. Thi -
- - + + + + + Tooltip Examples + + + + + + + + + + + + + +

Advanced ToolTip Example

+

+ This shows how to use new ToolTip features in 1.2.6 to render tooltips + manually, and for any area (inside or outside an imagemap) +

+ + + + + + + + + + + + + + +
+ This is a div. + This is some content. Thi +
+ + diff --git a/examples/usa.html b/examples/usa.html index 4b71342..526a956 100644 --- a/examples/usa.html +++ b/examples/usa.html @@ -1,561 +1,1230 @@ - - - - -USA Demo - - - - - - - - - - - - - -

USA Demo

- -
-

Disclaimer: This demo is old and the code is stylistically bad and probably difficult to understand. I strongly recommend using the online demos at the project web site - as references for learning how to use imagemapster. I have kept this in the project mostly as a way to play with different options, - but the code is a poor example of style and I would not advise using it as a learning tool. -

-
- -
-
- -
-
- -
- Using This Demonstration -
  • Change rendering options for highlight or select and click "update" to cause them to take effect.
  • -
  • If you change the "Highlight Fill" or "Selection Fill" option, the map and all other options will be reset. (As of version 1.2.4.043 the fill type can't be changed after the map is bound.)
  • -
  • Click a State label next to the checkbox in the list on the right to highlight the State.
  • -
  • Washington has a staticState === true meaning it is always selected
  • -
  • Oregon has a staticState === false meaning it is not selectable via mouse but can be selected programmatically via the list on right.
  • -
  • Alaska is marked selectable === false meaning it cannot be un/selected via mouse but can be selected programmatically via list on right.
  • -
  • Maine is marked isSelected === true meaning it is selected on initial load but both mouse & programmatic access can alter its state.
  • -
-

Choose the style in which highlights will be rendered. For fill, "normal" uses solid color fills. The other options use an alternate image as the source of the fill. - "Alternate image" requires HTML5 canvas support. In browsers that do not support it, typically Internet Explorer < 9, it will always look the same as "normal."

- - Make changes to the options below and click here to update the map: - - - - - - - - - - - - - - - - - -
-

Image Size

-
- You can change the size of an image just by changing the width or height attributes. Size (enter width only, max. 720):
-
-

Highlight Types

-

Selection Styles

Options

- Choose the style in which highlights will be rendered.

-
Highlight fill:

-
Stroke (border):
-
Stroke width:
-
Stroke opacity:
-
Fill opacity:
-
- Choose the style in which selections will be rendered.

-
Selection fill:

-
Stroke:
-
Stroke width:
-
Stroke opacity:
-
Fill opacity:
-
-
- Enable/disable toolTips. [disabled]
- Enable/disable singleSelect. [disabled]
- Enable/disable isDeselectable. [enabled]
- - Unbind the map.
- Rebind the map.
- Click here to unbind, preserving state.
- Mouseout delay: -

Get Data Out

- Click here to show selected items: - -
- - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + USA Demo + + + + + + + + + + +

USA Demo

+ +
+

+ Disclaimer: This demo is old and the code is stylistically bad + and probably difficult to understand. I strongly recommend using + the online demos at + the project web site + as references for learning how to use imagemapster. I have kept this in + the project mostly as a way to play with different options, but the code + is a poor example of style and I would not advise using it as a learning + tool. +

+
+ +
+
+ +
+
+
+ Using This Demonstration +
    +
  • + Change rendering options for highlight or select and click "update" + to cause them to take effect. +
  • +
  • + If you change the "Highlight Fill" or "Selection Fill" option, the + map and all other options will be reset. (As of version 1.2.4.043 + the fill type can't be changed after the map is bound.) +
  • +
  • + Click a State label next to the checkbox in the list on the right to + highlight the State. +
  • +
  • + Washington has a staticState === true meaning it is always selected +
  • +
  • + Oregon has a staticState === false meaning it is not selectable via + mouse but can be selected programmatically via the list on right. +
  • +
  • + Alaska is marked selectable === false meaning it cannot be + un/selected via mouse but can be selected programmatically via list + on right. +
  • +
  • + Maine is marked isSelected === true meaning it is selected on + initial load but both mouse & programmatic access can alter its + state. +
  • +
+

+ Choose the style in which highlights will be rendered. For fill, + "normal" uses solid color fills. The other options use an alternate + image as the source of the fill. "Alternate image" requires HTML5 + canvas support. In browsers that do not support it, typically Internet + Explorer < 9, it will always look the same as "normal." +

+ + Make changes to the options below and click here to update the map: + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Image Size

+
+ You can change the size of an image just by changing the + width or height attributes. Size (enter width only, max. 720): +
+
+

Highlight Types

+

Selection Styles

Options

+ Choose the style in which highlights will be rendered.

+
Highlight fill:
+
+
Stroke (border):
+
+ +
+
Stroke width:
+
+ +
+
Stroke opacity:
+
+ +
+
Fill opacity:
+
+
+ Choose the style in which selections will be rendered.

+
Selection fill:
+
+
Stroke:
+
+ +
+
Stroke width:
+
+ +
+
Stroke opacity:
+
+ +
+
Fill opacity:
+
+
+
+ Enable/disable toolTips. [disabled]
+ Enable/disable singleSelect. + [disabled]
+ Enable/disable isDeselectable. + [enabled]
+ + Unbind the map.
+ Rebind the map.
+ Click here to unbind, preserving state.
+ Mouseout delay: + +

Get Data Out

+ Click here to show selected items: + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/vegetables.html b/examples/vegetables.html index 7f0360d..1555ae4 100644 --- a/examples/vegetables.html +++ b/examples/vegetables.html @@ -1,134 +1,187 @@ - - - - -Vegetables Demo - - - - - - - - - - -

Vegetables Demo

- -
-

Simple example demonstrating singleClick which perm234its only one area to be selected, and changing a tooltip on the fly. - the dip has a tooltip, which is different if asparagus is selected. This also shows how to use area-specific options to control - the styling of each area individually. Some areas use different colors for selection to ensure that the highlight is visible - even though each area is a substantially different color. - -

-
- -
-
-
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + Vegetables Demo + + + + + + + + + + +

Vegetables Demo

+ +
+

+ Simple example demonstrating singleClick which perm234its only + one area to be selected, and changing a tooltip on the fly. the dip has + a tooltip, which is different if asparagus is selected. This also shows + how to use area-specific options to control the styling of each area + individually. Some areas use different colors for selection to ensure + that the highlight is visible even though each area is a substantially + different color. +

+
+ +
+
+
+
+ + + + + + + + + + + + + + + diff --git a/gruntfile.js b/gruntfile.js index 33d6f0f..c11488c 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,7 +1,6 @@ -'use strict'; - +// eslint-disable-next-line strict, no-undef module.exports = function (grunt) { - + // eslint-disable-next-line no-undef require('jit-grunt')(grunt, { 'bump-only': 'grunt-bump', 'bump-commit': 'grunt-bump' @@ -9,7 +8,8 @@ module.exports = function (grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - banner: '/*!\n' + + banner: + '/*!\n' + '* <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + '* Copyright (c) 2011 - <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n' + @@ -29,15 +29,12 @@ module.exports = function (grunt) { 'src/areadata.js', 'src/areacorners.js', 'src/scale.js', - 'src/tooltip.js', + 'src/tooltip.js' ], dest: 'build/jquery.<%= pkg.name %>.jquery.js' }, zepto: { - src: [ - 'src/zepto.js', - '<%= concat.jquery.src %>' - ], + src: ['src/zepto.js', '<%= concat.jquery.src %>'], dest: 'build/jquery.<%= pkg.name %>.zepto.js' }, jquerydist: { @@ -55,7 +52,7 @@ module.exports = function (grunt) { }, src: '<%= umd.zepto.options.dest %>', dest: 'dist/jquery.<%= pkg.name %>.zepto.js' - }, + } }, uglify: { options: { @@ -71,7 +68,7 @@ module.exports = function (grunt) { zepto: { src: '<%= concat.zeptodist.dest %>', dest: 'dist/jquery.<%= pkg.name %>.zepto.min.js' - }, + } }, umd: { jquery: { @@ -100,13 +97,15 @@ module.exports = function (grunt) { tests: { options: { port: 9101, - open: 'http://<%= connect.options.hostname %>:<%= connect.tests.options.port %>/tests/imagemapster-test-runner.html' + open: + 'http://<%= connect.options.hostname %>:<%= connect.tests.options.port %>/tests/imagemapster-test-runner.html' } }, examples: { options: { port: 9102, - open: 'http://<%= connect.options.hostname %>:<%= connect.examples.options.port %>/examples/index.html' + open: + 'http://<%= connect.options.hostname %>:<%= connect.examples.options.port %>/examples/index.html' } } }, @@ -119,20 +118,20 @@ module.exports = function (grunt) { tasks: ['jquery'], options: { livereload: '<%= connect.options.livereload %>' - }, + } }, examples: { files: ['examples/**/*.html', 'examples/**/*.css'], options: { livereload: '<%= connect.options.livereload %>' - }, + } }, tests: { files: ['tests/**/*.js', 'tests/**/*.html'], options: { livereload: '<%= connect.options.livereload %>' - }, - }, + } + } }, docco: { source: { @@ -167,14 +166,43 @@ module.exports = function (grunt) { npm: { command: 'npm publish' } - }, + }, + eslint: { + options: { + failOnError: true, + extensions: ['.js', '.html'] + }, + target: ['.'] + } }); - grunt.registerTask('default', ['clean', 'concat:iife', 'concat:dist', 'uglify']); - grunt.registerTask('fullbuild', ['clean', 'concat:jquery', 'concat:zepto', 'umd', 'concat:jquerydist', 'concat:zeptodist', 'uglify']); + grunt.registerTask('default', ['build']); + grunt.registerTask('fullbuild', [ + 'clean', + 'eslint', + 'concat:jquery', + 'concat:zepto', + 'umd:jquery', + 'umd:zepto', + 'concat:jquerydist', + 'concat:zeptodist', + 'uglify' + ]); grunt.registerTask('build', ['jquery', 'uglify:jquery']); - grunt.registerTask('jquery', ['clean', 'concat:jquery', 'umd:jquery', 'concat:jquerydist']); - grunt.registerTask('zepto', ['clean', 'concat:zepto', 'umd:zepto', 'concat:zeptodist']); + grunt.registerTask('jquery', [ + 'clean', + 'eslint', + 'concat:jquery', + 'umd:jquery', + 'concat:jquerydist' + ]); + grunt.registerTask('zepto', [ + 'clean', + 'eslint', + 'concat:zepto', + 'umd:zepto', + 'concat:zeptodist' + ]); grunt.registerTask('debug', ['jquery', 'connect', 'watch']); grunt.registerTask('example', ['jquery', 'connect:examples', 'watch']); grunt.registerTask('test', ['jquery', 'connect:tests', 'watch']); @@ -183,8 +211,12 @@ module.exports = function (grunt) { grunt.registerTask('patch', ['preBump', 'bump-only:patch', 'postBump']); grunt.registerTask('minor', ['preBump', 'bump-only:minor', 'postBump']); grunt.registerTask('major', ['preBump', 'bump-only:major', 'postBump']); - grunt.registerTask('prerelease', ['preBump', 'bump-only:prerelease', 'postBump']); + grunt.registerTask('prerelease', [ + 'preBump', + 'bump-only:prerelease', + 'postBump' + ]); grunt.registerTask('prepatch', ['preBump', 'bump-only:prepatch', 'postBump']); grunt.registerTask('preminor', ['preBump', 'bump-only:preminor', 'postBump']); grunt.registerTask('premajor', ['preBump', 'bump-only:premajor', 'postBump']); -}; \ No newline at end of file +}; diff --git a/jqueryplugin.hbs b/jqueryplugin.hbs index 56ca68a..24fc621 100644 --- a/jqueryplugin.hbs +++ b/jqueryplugin.hbs @@ -1,29 +1,29 @@ (function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof module === 'object' && module.exports) { - // Node/CommonJS - module.exports = function( root, jQuery ) { - if ( jQuery === undefined ) { - // require('jQuery') returns a factory that requires window to - // build a jQuery instance, we normalize how we use modules - // that require this pattern but the window provided is a noop - // if it's defined (how jquery works) - if ( typeof window !== 'undefined' ) { - jQuery = require('jquery'); - } - else { - jQuery = require('jquery')(root); - } - } - factory(jQuery); - return jQuery; - }; - } else { - // Browser globals - factory(jQuery); - } + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = function( root, jQuery ) { + if ( jQuery === undefined ) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if ( typeof window !== 'undefined' ) { + jQuery = require('jquery'); + } + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; + } else { + // Browser globals + factory(jQuery); + } }(function (jQuery) { {{{code}}} })); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8d1d7be..aab4d41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,63 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@eslint/eslintrc": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -20,6 +77,30 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "alphabet": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/alphabet/-/alphabet-1.0.0.tgz", @@ -59,19 +140,25 @@ "annotate": "^0.9.1" } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "color-convert": "^1.9.0" } }, "argparse": { @@ -81,14 +168,6 @@ "dev": true, "requires": { "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - } } }, "arr-diff": { @@ -142,6 +221,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -316,6 +401,12 @@ "unset-value": "^1.0.0" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -324,6 +415,47 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "class-utils": { @@ -366,18 +498,18 @@ } }, "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { @@ -387,9 +519,9 @@ "dev": true }, "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.0.0.tgz", + "integrity": "sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA==", "dev": true }, "component-emitter": { @@ -414,6 +546,23 @@ "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "connect-livereload": { @@ -440,6 +589,17 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -447,12 +607,12 @@ "dev": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, "decode-uri-component": { @@ -461,6 +621,12 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -533,6 +699,74 @@ "underscore": ">= 1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", + "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "entities": "^2.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "dev": true, + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, + "domelementtype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", + "dev": true + }, + "domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz", + "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", + "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "dev": true, + "requires": { + "domelementtype": "^2.1.0" + } + } + } + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -557,6 +791,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -572,6 +812,21 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + }, "error": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", @@ -593,12 +848,176 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.3.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^6.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-config-jquery": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-jquery/-/eslint-config-jquery-3.0.0.tgz", + "integrity": "sha512-VDdRAIlNq1EM5P7J4JGQSCnZEIvIlNGGTUTCPT2wQNZ2GT69rsAwSIqZVcoiyZbwY7TaaMwLOxwSjqm+DEUjbA==", + "dev": true + }, + "eslint-config-prettier": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", + "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", + "dev": true + }, + "eslint-plugin-html": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.1.1.tgz", + "integrity": "sha512-JSe3ZDb7feKMnQM27XWGeoIjvP4oWQMJD9GZ6wW67J7/plVL87NK72RBwlvfc3tTZiYUchHhxAwtgEd1GdofDA==", + "dev": true, + "requires": { + "htmlparser2": "^5.0.1" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -638,6 +1057,15 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -655,6 +1083,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -759,6 +1193,24 @@ } } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -778,6 +1230,15 @@ "object-assign": "^4.1.0" } }, + "file-entry-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -814,6 +1275,23 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "findup-sync": { @@ -859,6 +1337,22 @@ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", "dev": true }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", + "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -913,6 +1407,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -948,6 +1448,15 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -970,6 +1479,26 @@ "ini": "^1.3.4", "is-windows": "^1.0.1", "which": "^1.2.14" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" } }, "globule": { @@ -1046,6 +1575,14 @@ "dev": true, "requires": { "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "grunt-contrib-clean": { @@ -1088,6 +1625,12 @@ "source-map": "^0.5.3" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -1107,6 +1650,15 @@ "supports-color": "^2.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -1152,15 +1704,6 @@ "uri-path": "^1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1171,36 +1714,6 @@ "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -1236,6 +1749,16 @@ "docco": "~0.7.0" } }, + "grunt-eslint": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-23.0.0.tgz", + "integrity": "sha512-QqHSAiGF08EVD7YlD4OSRWuLRaDvpsRdTptwy9WaxUXE+03mCLVA/lEaR6SHWehF7oUwIqCEjaNONeeeWlB4LQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "eslint": "^7.0.0" + } + }, "grunt-known-options": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", @@ -1277,6 +1800,17 @@ "lodash": "~4.17.20", "underscore.string": "~3.3.5", "which": "~1.3.0" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "grunt-shell": { @@ -1296,15 +1830,6 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1316,27 +1841,6 @@ "supports-color": "^5.3.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -1345,15 +1849,6 @@ "requires": { "ansi-regex": "^4.1.0" } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -1413,12 +1908,20 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-value": { @@ -1474,6 +1977,18 @@ "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", "dev": true }, + "htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + } + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -1521,6 +2036,28 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1645,13 +2182,19 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -1746,6 +2289,12 @@ "integrity": "sha1-AIw6f+Hpa9DYTiYOofoXg0V/ecI=", "dev": true }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -1756,6 +2305,18 @@ "esprima": "^4.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1780,6 +2341,16 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "libumd": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/libumd/-/libumd-0.9.0.tgz", @@ -1820,6 +2391,15 @@ "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } } } }, @@ -1841,6 +2421,15 @@ "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -1883,6 +2472,12 @@ "pretty-bytes": "^3.0.0" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -1902,6 +2497,15 @@ "supports-color": "^2.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -2005,12 +2609,29 @@ "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "nanomatch": { @@ -2032,6 +2653,12 @@ "to-regex": "^3.0.1" } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -2076,6 +2703,14 @@ "dev": true, "requires": { "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } } }, "number-is-nan": { @@ -2210,6 +2845,20 @@ "is-wsl": "^1.1.0" } }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -2232,6 +2881,15 @@ "os-tmpdir": "^1.0.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -2268,9 +2926,9 @@ "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -2321,6 +2979,12 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "pretty-bytes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", @@ -2336,16 +3000,22 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "dev": true }, "querystring": { @@ -2412,6 +3082,12 @@ "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -2424,6 +3100,12 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", @@ -2444,6 +3126,12 @@ "global-modules": "^1.0.0" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -2493,10 +3181,13 @@ "dev": true }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "send": { "version": "0.17.1", @@ -2519,6 +3210,23 @@ "statuses": "~1.5.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2565,6 +3273,23 @@ "http-errors": "~1.6.2", "mime-types": "~2.1.17", "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "serve-static": { @@ -2614,6 +3339,58 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -2630,6 +3407,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -2647,6 +3433,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -2756,9 +3548,9 @@ } }, "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "static-extend": { @@ -2810,6 +3602,17 @@ "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2820,23 +3623,67 @@ } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ajv": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "timers-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz", @@ -2868,12 +3715,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true } } }, @@ -2925,6 +3766,21 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "uglify-js": { "version": "3.12.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz", @@ -3023,6 +3879,15 @@ } } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "uri-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", @@ -3043,6 +3908,14 @@ "requires": { "punycode": "1.3.2", "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } } }, "use": { @@ -3080,6 +3953,12 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", + "dev": true + }, "v8flags": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", @@ -3121,14 +4000,20 @@ } }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -3157,6 +4042,12 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } } diff --git a/package.json b/package.json index 7def41c..6044109 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,10 @@ "jquery": ">=1.7.0" }, "devDependencies": { + "eslint": "^7.18.0", + "eslint-config-jquery": "^3.0.0", + "eslint-config-prettier": "^7.1.0", + "eslint-plugin-html": "^6.1.1", "grunt": "^1.3.0", "grunt-bump": "^0.8.0", "grunt-contrib-clean": "^2.0.0", @@ -55,6 +59,7 @@ "grunt-contrib-uglify": "^5.0.0", "grunt-contrib-watch": "^1.1.0", "grunt-docco": "^0.5.0", + "grunt-eslint": "^23.0.0", "grunt-shell": "^3.0.1", "grunt-umd": "^3.0.0", "jit-grunt": "^0.10.0" diff --git a/src/areacorners.js b/src/areacorners.js index 2e60d33..49176e6 100644 --- a/src/areacorners.js +++ b/src/areacorners.js @@ -3,148 +3,183 @@ */ (function ($) { - var u=$.mapster.utils; - - - /** - * Compute positions that will place a target with dimensions [width,height] outside - * but near the boundaries of the elements "elements". When an imagemap is passed, the - * - * @param {Element|Element[]} elements An element or an array of elements (such as a jQuery object) - * @param {Element} image The image to which area elements are bound, if this is an image map. - * @param {Element} container The contianer in which the target must be constrained (or document, if missing) - * @param {int} width The width of the target object - * @return {object} a structure with the x and y positions - */ - u.areaCorners = function (elements, image, container, width, height) { - var pos,found, minX, minY, maxX, maxY, bestMinX, bestMaxX, bestMinY, bestMaxY, curX, curY, nest, j, - offsetx=0, - offsety=0, - rootx, - rooty, - iCoords,radius,angle,el, - coords=[]; - - // if a single element was passed, map it to an array - - elements = elements.length ? - elements: - [elements]; - - container = container ? - $(container): - $(document.body); - - // get the relative root of calculation - - pos = container.offset(); - rootx = pos.left; - rooty = pos.top; - - // with areas, all we know about is relative to the top-left corner of the image. We need to add an offset compared to - // the actual container. After this calculation, offsetx/offsety can be added to either the area coords, or the target's - // absolute position to get the correct top/left boundaries of the container. - - if (image) { - pos = $(image).offset(); - offsetx = pos.left; - offsety = pos.top; - } - - // map the coordinates of any type of shape to a poly and use the logic. simpler than using three different - // calculation methods. Circles use a 20 degree increment for this estimation. - - for (j=0;j= 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; - } + // map area positions to it's real position in the container + for (j = 0; j < coords.length; j += 2) { + coords[j] = parseInt(coords[j], 10) + offsetx; + coords[j + 1] = parseInt(coords[j + 1], 10) + offsety; } - - // 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]; - } + } else { + el = $(el); + pos = el.position(); + coords.push( + pos.left, + pos.top, + pos.left + el.width(), + pos.top, + pos.left + el.width(), + pos.top + el.height(), + pos.left, + pos.top + el.height() + ); + } + } + + minX = minY = bestMinX = bestMinY = 999999; + maxX = maxY = bestMaxX = bestMaxY = -1; + + for (j = coords.length - 2; j >= 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 (_, e) { + if (!found && e[0] > rootx && e[1] > rooty) { + nest = e; + found = true; + return false; + } } - return nest; - }; -} (jQuery)); + ); + + // default to lower-right corner if nothing fit inside the boundaries of the image + + if (!found) { + nest = [maxX, maxY]; + } + } + return nest; + }; +})(jQuery); diff --git a/src/areadata.js b/src/areadata.js index d91a6a8..0bca1fb 100644 --- a/src/areadata.js +++ b/src/areadata.js @@ -3,297 +3,296 @@ */ (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) { + 'use strict'; + + 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(); + } - // cache the current options, and map the altImageId if an altimage - // was passed + // 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.optsCache = $.extend(me.effectiveRenderOptions('select'), options, { + altImageId: o.images.add(options.altImage) + }); + } - me.drawSelection(); + me.drawSelection(); - me.selected = true; - me.changeState('select', true); - } + me.selected = true; + me.changeState('select', true); + } - if (o.options.singleSelect) { - o.graphics.refreshSelections(); - } + 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 - */ + /** + * 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); + function deselect(partial) { + var me = this; + me.selected = false; + me.changeState('select', false); - // release information about last area options when deselecting. + // release information about last area options when deselecting. - me.optsCache=null; - me.owner.graphics.removeSelections(me.areaId); + 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 + // 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(); - } + 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(); + } + + /** + * 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 < this.areasXref.length; i++) { + result.push(this.owner.mapAreas[this.areasXref[i]]); + } + return result; + }, + // return all coordinates for all areas + coords: function (offset) { + var coords = []; + $.each(this.areas(), function (_, el) { + coords = coords.concat(el.coords(offset)); + }); + return coords; + }, + reset: function () { + $.each(this.areas(), function (_, e) { + e.reset(); + }); + this.areasXref = []; + this.options = null; + }, + // Return the effective selected state of an area, incorporating staticState + isSelectedOrStatic: function () { + var o = this.effectiveOptions(); + return u.isBool(o.staticState) ? o.staticState : this.isSelected(); + }, + isSelected: function () { + return u.isBool(this.selected) + ? this.selected + : u.isBool(this.owner.area_options.selected) + ? this.owner.area_options.selected + : false; + }, + isSelectable: function () { + return u.isBool(this.effectiveOptions().staticState) + ? false + : u.isBool(this.owner.options.staticState) + ? false + : u.boolOrDefault(this.effectiveOptions().isSelectable, true); + }, + isDeselectable: function () { + return u.isBool(this.effectiveOptions().staticState) + ? false + : u.isBool(this.owner.options.staticState) + ? false + : u.boolOrDefault(this.effectiveOptions().isDeselectable, true); + }, + isNotRendered: function () { + var area = $(this.area); + return ( + area.attr('nohref') || + !area.attr('href') || + this.effectiveOptions().isMask + ); + }, /** - * An AreaData object; represents a conceptual area that can be composed of - * one or more MapArea objects + * Return the overall options effective for this area. + * This should get the default options, and merge in area-specific options, finally + * overlaying options passed by parameter * - * @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 + * @param {[type]} options options which will supercede all other options for this area + * @return {[type]} the combined options */ - 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 - }); - }; + effectiveOptions: function (options) { + var opts = u.updateProps( + {}, + this.owner.area_options, + this.options, + options || {}, + { + id: this.areaId + } + ); + + opts.selected = this.isSelected(); + + return opts; + }, /** - * The public API for AreaData object + * Return the options effective for this area for a "render" or "highlight" mode. + * This should get the default options, merge in the areas-specific options, + * and then the mode-specific options. + * @param {string} mode 'render' or 'highlight' + * @param {[type]} options options which will supercede all other options for this area + * @return {[type]} the combined options */ - m.AreaData.prototype = { - constuctor: m.AreaData, - select: select, - deselect: deselect, - toggle: toggle, - areas: function() { - var i,result=[]; - for (i=0;i= 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"); - }, - /** - * Basic indexOf implementation for IE7-8. Though we use $.inArray, some jQuery versions will try to - * use a prototpye on the calling object, defeating the purpose of using $.inArray in the first place. - * - * This will be replaced with the array prototype if it's available. - * - * @param {Array} arr The array to search - * @param {Object} target The item to search for - * @return {Number} The index of the item, or -1 if not found - */ - indexOf: function(arr,target){ - if (Array.prototype.indexOf) { - return Array.prototype.indexOf.call(arr, target); - } else { - for(var i=0; i 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).prop(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; + defer: function () { + // Deferred is frequently referred to as an anti-pattern largely + // due to error handling, however to avoid reworking existing + // APIs and support backwards compat, creating a "deferred" + // polyfill via native promise + var Deferred = function () { + // TODO: Promise breaks ES5 support + // eslint-disable-next-line no-undef + this.promise = new Promise( + function (resolve, reject) { + this.resolve = resolve; + this.reject = reject; + }.bind(this) + ); + + this.then = this.promise.then.bind(this.promise); + this.catch = this.promise.catch.bind(this.promise); + }; + return new Deferred(); + } + }, + defer: function () { + return this.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++) { + // backwards compat for $.trim which would return empty string on null + // which theoertically should not happen here + el = arr[i] ? arr[i].trim() : ''; + 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 (_, 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]; + } } - if (!map_data.complete || map_data.currentAction) { - map_data.commands.push( - { - that: that, - command: command, - args: args - }); - return true; + }); + }); + return target; + }, + isElement: function (o) { + return typeof HTMLElement === 'object' + ? o instanceof HTMLElement + : o && + typeof o === 'object' && + o.nodeType === 1 && + typeof o.nodeName === 'string'; + }, + /** + * Basic indexOf implementation for IE7-8. Though we use $.inArray, some jQuery versions will try to + * use a prototpye on the calling object, defeating the purpose of using $.inArray in the first place. + * + * This will be replaced with the array prototype if it's available. + * + * @param {Array} arr The array to search + * @param {Object} target The item to search for + * @return {Number} The index of the item, or -1 if not found + */ + indexOf: function (arr, target) { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(arr, target); + } else { + for (var i = 0; i < arr.length; i++) { + if (arr[i] === target) { + return i; } + } + return -1; + } + }, + + // 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; - }, - unload: function () { - this.impl.unload(); - this.utils = null; - this.impl = null; - $.fn.mapster = null; - $.mapster = null; - $('*').off(); + } + }); + 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'; + }, + isFunction: function (obj) { + return typeof obj === 'function'; + }, + // 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 (this.isFunction(obj)) { + obj.call(that, args); } - }; - - // 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]; + }, + 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; + } }; - - }); - - /** - * 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; + }, + + /** + * 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 (_, e) { + if (typeof e.opacity !== 'undefined') { + e.opacity = opacity; } else { - return this.output; + $(e).css('opacity', opacity); } + }); } - }; - - $.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 >= map_data.index; i--) { - m.map_cache[i].index--; + }, + + // 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; } - }; - - /** - * 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 - */ + endOp = endOp || 1; - function hasVml() { - var a = $('
').appendTo('body'); - a.html(''); + op = + op + endOp / cbIntervals > endOp - 0.01 + ? endOp + : op + endOp / cbIntervals; - var b = a[0].firstChild; - b.style.behavior = "url(#default#VML)"; - var has = b ? typeof b.adj === "object" : true; - a.remove(); - return has; + 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 (_, e) { + for (index = 0; index < list.length; index++) { + key = list[index]; + if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { + result = result.add(e); + } } - - /** - * 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; + }); + 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 (_, e) { + if (opts.listSelectedClass) { + if (selected) { + $(e).addClass(opts.listSelectedClass); + } else { + $(e).removeClass(opts.listSelectedClass); + } } - - /** - * 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; + if (opts.listSelectedAttribute) { + $(e).prop(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; + $('*').off(); + } + }; + + // 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 (_, 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; + } - /** - * 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); - } - }); + 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; + } } - 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); + } + // if there were areas, call the area function for each unique group + $(area_list).each(function (_, 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 >= map_data.index; i--) { + m.map_cache[i].index--; } + }; - // 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); - } - }); - } + /** + * 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 + */ - 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(','); + 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; + } - }; - me.select = function () { - me.set.call(this, true); - }; - me.deselect = function () { - me.set.call(this, false); - }; + /** + * 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; + } - /** - * 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) { - var newState = selected; - if (ar) { - switch (selected) { - case true: - ar.select(opts); break; - case false: - ar.deselect(true); break; - default: - newState = ar.toggle(opts); break; - } - return newState; - } - } - 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) { - var newState = setSelection(el); - if (map_data.options.boundList) { - m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), newState); - } - }); - if (!selected) { - map_data.removeSelectionFinish(); - } + /** + * 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. - this.filter('img,area').each(function (i,e) { - var keys; - map_data = m.getMapData(e); + return d && d.g_vml_ + ? false + : $('')[0].getContext + ? true + : false; + } - if (map_data !== lastMap) { - if (lastMap) { - finishSetForMap(lastMap); - } + /** + * 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 + */ - area_list = []; - key_list=''; - } + function merge_areas(map_data, areas) { + var ar, + index, + map_areas = map_data.options.areas; + + if (areas) { + $.each(areas, function (_, 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(); + }; - 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; - } - - } - } - }); + // 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 (_, e) { + keys = keys.concat(e.keys); + }); + } + $.each(keys, function (_, 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 (_, 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); + }; - if (map_data) { - finishSetForMap(map_data); - } + /** + * 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) { + var newState = selected; + if (ar) { + switch (selected) { + case true: + ar.select(opts); + break; + case false: + ar.deselect(true); + break; + default: + newState = ar.toggle(opts); + break; + } + return newState; + } + } + 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 (_, el) { + var newState = setSelection(el); + if (map_data.options.boundList) { + m.setBoundListProperties( + map_data.options, + m.getBoundList(map_data.options, key_list), + newState + ); + } + }); + if (!selected) { + map_data.removeSelectionFinish(); + } + } - 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(); - }; + this.filter('img,area').each(function (_, e) { + var keys; + map_data = m.getMapData(e); + if (map_data !== lastMap) { + if (lastMap) { + finishSetForMap(lastMap); + } - // 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(); - }; + area_list = []; + key_list = ''; + } - // 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)); + 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 (_, key) { + addArea(map_data.getDataForKey(key.toString())); + lastMap = map_data; + }); + } } - me.graphics = null; - }; + } else { + opts = key; + if (!m.queueCommand(map_data, $(e), 'set', [selected, opts])) { + addArea(map_data.getDataForArea(e)); + lastMap = map_data; + } + } + } + }); - 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(); - }; + if (map_data) { + finishSetForMap(map_data); + } - // 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; - }; + 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.bind = function (options) { + // 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; + }; - return this.each(function (i,e) { - var img, map, usemap, md; + me.snapshot = function () { + return new m.Method( + this, + function () { + $.each(this.data, function (_, e) { + e.selected = false; + }); - // save ref to this image even if we can't access it yet. commands will be queued - img = $(e); + this.base_canvas = this.graphics.createVisibleCanvas(this); + $(this.image).before(this.base_canvas); + }, + null, + { name: 'snapshot' } + ).go(); + }; - md = m.getMapData(e); + // do not queue this function + + me.state = function () { + var md, + result = null; + $(this).each(function (_, e) { + if (e.nodeName === 'IMG') { + md = m.getMapData(e); + if (md) { + result = md.state(); + } + return false; + } + }); + return result; + }; - // if already bound completely, do a total rebind + me.bind = function (options) { + return this.each(function (_, e) { + var img, map, usemap, md; - if (md) { - me.unbind.apply(img); - if (!md.complete) { - // will be queued - img.on(); - return true; - } - md = null; - } + // save ref to this image even if we can't access it yet. commands will be queued + img = $(e); - // 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. + md = m.getMapData(e); - usemap = this.getAttribute('usemap'); - map = usemap && $('map[name="' + usemap.substr(1) + '"]'); - if (!(img.is('img') && usemap && map.length > 0)) { - return true; - } + // if already bound completely, do a total rebind - // sorry - your image must have border:0, things are too unpredictable otherwise. - img.css('border', 0); + if (md) { + me.unbind.apply(img); + if (!md.complete) { + // will be queued + img.on(); + return true; + } + md = null; + } - if (!md) { - md = new m.MapData(this, options); + // 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. - md.index = addMap(md); - md.map = map; - md.bindImages().then(function() { - md.initialize(); - }); - } - }); - }; + usemap = this.getAttribute('usemap'); + map = usemap && $('map[name="' + usemap.substr(1) + '"]'); + if (!(img.is('img') && usemap && map.length > 0)) { + return true; + } - me.init = function (useCanvas) { - var style, shapes; + // sorry - your image must have border:0, things are too unpredictable otherwise. + img.css('border', 0); - // for testing/debugging, use of canvas can be forced by initializing - // manually with "true" or "false". But generally we test for it. + if (!md) { + md = new m.MapData(this, options); - 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(); - } + md.index = addMap(md); + md.map = map; + md.bindImages().then(function () { + md.initialize(); + }); + } + }); + }; - return m.hasVml.value; - }; + me.init = function (useCanvas) { + var style, shapes; - $.extend(m.defaults, m.render_defaults,m.shared_defaults); - $.extend(m.area_defaults, m.render_defaults,m.shared_defaults); + // for testing/debugging, use of canvas can be forced by initializing + // manually with "true" or "false". But generally we test for it. - }; - me.test = function (obj) { - return eval(obj); - }; - return me; - } ()); + 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 (_, el) { + style.addRule( + 'v\\:' + el, + 'behavior: url(#default#VML); antialias:true' + ); + }); + } + m.hasVml.value = hasVml(); + } - $.mapster.impl.init(); + return m.hasVml.value; + }; + $.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; + })(); -} (jQuery)); + $.mapster.impl.init(); +})(jQuery); diff --git a/src/graphics.js b/src/graphics.js index 330c8eb..dd56465 100644 --- a/src/graphics.js +++ b/src/graphics.js @@ -1,444 +1,546 @@ -/* graphics.js - Graphics object handles all rendering. +/* + graphics.js + Graphics object handles all rendering. */ + (function ($) { - var p, m=$.mapster, - u=m.utils, - canvasMethods, - vmlMethods; + 'use strict'; + + 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 (_, 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, /** - * 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 + * 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) */ - 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. + begin: function (canvas, elementName) { + var c = $(canvas); - $.each(areaData.areas(), function (i,e) { - options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); - me.addShape(e, options); - }); + this.elementName = elementName; + this.canvas = canvas; - // it's faster just to manipulate the passed options isMask property and restore it, than to - // copy the object each time + this.width = c.width(); + this.height = c.height(); + this.shapes = []; + this.masks = []; + this.active = true; + }, - options.isMask=isMask; + /** + * 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 }); + }, - /** - * Convert a hex value to decimal - * @param {string} hex A hexadecimal toString - * @return {int} Integer represenation of the hex string + /** + * 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 */ - 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 + ')'; - } + createVisibleCanvas: function (mapData) { + return $(this.createCanvasFor(mapData)) + .addClass('mapster_el') + .css(m.canvas_style)[0]; + }, + /** - * An object associated with a particular map_data instance to manage renderin. - * @param {MapData} map_data The MapData object bound to this instance + * 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 */ - 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); - - } + 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 (_, 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 (_, 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 (_, 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(); + }); - // 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(); - } - } + // render strokes at end since masks get stroked too - }; - - // 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)); - }); + $.each(me.shapes.concat(me.masks), function (_, 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(); -} (jQuery)); + 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 (_, e) { + me.renderShape(e.mapArea, e.options); + }); + + if (this.masks.length) { + $.each(this.masks, function (_, 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 (_, e) { + p[e] = (function (method) { + return function () { + p[method] = + (m.hasCanvas() ? canvasMethods[method] : vmlMethods[method]) || + noop; + + return p[method].apply(this, arguments); + }; + })(e); + } + ); +})(jQuery); diff --git a/src/jqueryextensions.js b/src/jqueryextensions.js index f1e1e5f..5e92af1 100644 --- a/src/jqueryextensions.js +++ b/src/jqueryextensions.js @@ -1,45 +1,55 @@ +/* + jqueryextensions.js + Extend/intercept jquery behavior +*/ + (function ($) { - // Test via a getter in the options object to see if the passive property is accessed - var supportsPassive = false; - try { - var opts = Object.defineProperty({}, 'passive', { - get: function() { - supportsPassive = true; - } - }); - window.addEventListener("testPassive.mapster", function() {}, opts); - window.removeEventListener("testPassive.mapster", function() {}, opts); - } catch (e) {} + 'use strict'; + + // Test via a getter in the options object to see if the passive property is accessed + var supportsPassive = false; + try { + var opts = Object.defineProperty({}, 'passive', { + get: function () { + supportsPassive = true; + return true; + } + }); + window.addEventListener('testPassive.mapster', function () {}, opts); + window.removeEventListener('testPassive.mapster', function () {}, opts); + } catch (e) { + // intentionally ignored + } - if (supportsPassive) { - // In order to not interrupt scrolling on touch devices - // we commit to not calling preventDefault from within listeners - // There is a plan to handle this natively in jQuery 4.0 but for - // now we are on our own. - // TODO: Migrate to jQuery 4.0 approach if/when released - // https://www.chromestatus.com/feature/5745543795965952 - // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md - // https://github.com/jquery/jquery/issues/2871#issuecomment-175175180 - // https://jsbin.com/bupesajoza/edit?html,js,output - var setupListener = function(ns, type, listener) { - if (ns.includes("noPreventDefault")) { - this.addEventListener(type, listener, { passive: true }); - } else { - console.warn("non-passive events - listener not added"); - return false; - } - }; + if (supportsPassive) { + // In order to not interrupt scrolling on touch devices + // we commit to not calling preventDefault from within listeners + // There is a plan to handle this natively in jQuery 4.0 but for + // now we are on our own. + // TODO: Migrate to jQuery 4.0 approach if/when released + // https://www.chromestatus.com/feature/5745543795965952 + // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md + // https://github.com/jquery/jquery/issues/2871#issuecomment-175175180 + // https://jsbin.com/bupesajoza/edit?html,js,output + var setupListener = function (ns, type, listener) { + if (ns.includes('noPreventDefault')) { + window.addEventListener(type, listener, { passive: true }); + } else { + console.warn('non-passive events - listener not added'); + return false; + } + }; - // special events for noPreventDefault - $.event.special.touchstart = { - setup: function (_, ns, listener) { - return setupListener(ns, "touchstart", listener); - } - }; - $.event.special.touchend = { - setup: function (_, ns, listener) { - return setupListener(ns, "touchend", listener); - } - }; - } -}(jQuery)); + // special events for noPreventDefault + $.event.special.touchstart = { + setup: function (_, ns, listener) { + return setupListener(ns, 'touchstart', listener); + } + }; + $.event.special.touchend = { + setup: function (_, ns, listener) { + return setupListener(ns, 'touchend', listener); + } + }; + } +})(jQuery); diff --git a/src/mapdata.js b/src/mapdata.js index 8943a45..cfae88f 100644 --- a/src/mapdata.js +++ b/src/mapdata.js @@ -1,926 +1,962 @@ -/* mapdata.js - the MapData object, repesents an instance of a single bound imagemap +/* + mapdata.js + The MapData object, repesents an instance of a single bound imagemap */ - (function ($) { + 'use strict'; + + 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 (_, e) { + $.each(getOptionImages(e), function (_, e2) { + if (e2 && e2.altImage) { + e2.altImageId = mi.add(e2.altImage); + } + }); + }); + } - var m = $.mapster, - u = m.utils; + // 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) { + deferred.reject(); + } else { + 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; + } - /** - * Set default values for MapData object properties - * @param {MapData} me The MapData object - */ + if (me.currentAreaId === ar.areaId) { + return; + } + if (me.highlightId !== ar.areaId) { + me.clearEffects(); + + ar.highlight(); - 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 + if (me.options.showToolTip) { + $.each(arData, function (_, e) { + if (e.effectiveOptions().toolTip) { + e.showToolTip(); + } }); + } } - /** - * 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]; + me.currentAreaId = ar.areaId; + + if (u.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; } - /** - * 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; + newArea = me.getDataForArea(e.relatedTarget); - // add alt images + if (newArea === ar) { + return; + } - if (m.hasCanvas()) { - // map altImage library first + me.currentAreaId = -1; + ar.area = null; - $.each(opts.altImages || {}, function(i,e) { - mi.add(e,i); - }); + queueMouseEvent(me, opts.mouseoutDelay, ar).then(me.clearEffects); - // now find everything else + if (u.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 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 (u.isFunction(opts.onClick)) { + cbResult = opts.onClick.call(that, { + e: e, + listTarget: list_target, + key: ar.key, + selected: newSelectionState + }); - $.each([opts].concat(opts.areas),function(i,e) { - $.each(getOptionImages(e),function(i2,e2) { - if (e2 && e2.altImage) { - e2.altImageId=mi.add(e2.altImage); - } - }); - }); + if (u.isBool(cbResult)) { + if (!cbResult) { + return false; + } + target = $(ar.area).attr('href'); + if (target !== '#') { + window.location.href = target; + return false; + } } - - // set area_options - me.area_options = u.updateProps({}, // default options for any MapArea - m.area_defaults, - opts); + } + + if (canChangeState) { + 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 (_, e) { + var ar = me.getDataForKey(e.toString()); + if (!ar.options.isMask) { + clickArea(ar); + } + }); + } } - /** - * 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) { + mousedown.call(this, e); - deferred = deferred || u.when.defer(); + if (opts.clickNavigate && ar.href) { + window.location.href = ar.href; + return; + } - 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) { - deferred.reject(); - } else { - 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; + if (ar && !ar.owner.currentAction) { + opts = me.options; + clickArea(ar); } + } - /** - * 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 - */ + /** + * 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; - function mousedown(e) { - if (!m.hasCanvas()) { - this.blur(); - } - e.preventDefault(); - } + // (Image) main map image - /** - * 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] - */ + me.image = image; - function mouseover(me,e) { - var arData = me.getAllDataForArea(this), - ar=arData.length ? arData[0] : null; + me.images = new m.MapImages(me); + me.graphics = new m.Graphics(me); - // mouseover events are ignored entirely while resizing, though we do care about mouseout events - // and must queue the action to keep things clean. + // 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. - if (!ar || ar.isNotRendered() || ar.owner.currentAction) { - return; - } + me.imgCssText = image.style.cssText || null; - if (me.currentAreaId === ar.areaId) { - return; - } - if (me.highlightId !== ar.areaId) { - me.clearEffects(); + initializeDefaults(me); - ar.highlight(); + me.configureOptions(options); - if (me.options.showToolTip) { - $.each(arData,function(i,e) { - if (e.effectiveOptions().toolTip) { - e.showToolTip(); - } - }); - } - } + // create context-bound event handlers from our private functions - me.currentAreaId = ar.areaId; + 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); + }; + }; - if (u.isFunction(me.options.onMouseover)) { - me.options.onMouseover.call(this, - { - e: e, - options:ar.effectiveOptions(), - key: ar.key, - selected: ar.isSelected() - }); - } - } + m.MapData.prototype = { + constructor: m.MapData, /** - * Mouseout event. - * - * @param {MapData} me The MapData context - * @param {EventData} e jQuery event data - * @return {[type]} [description] + * Set target.options from defaults + options + * @param {[type]} target The target + * @param {[type]} options The options to merge */ - function mouseout(me,e) { - var newArea, - ar = me.getDataForArea(this), - opts = me.options; - - - if (me.currentAreaId<0 || !ar) { - return; - } + configureOptions: function (options) { + this.options = u.updateProps({}, m.defaults, options); + }, - newArea=me.getDataForArea(e.relatedTarget); + /** + * Ensure all images are loaded + * @return {Promise} A promise that resolves when the images have finished loading (or fail) + */ - if (newArea === ar) { - return; - } + bindImages: function () { + var me = this, + mi = me.images; - me.currentAreaId = -1; - ar.area=null; + // reset the images if this is a rebind - queueMouseEvent(me,opts.mouseoutDelay,ar) - .then(me.clearEffects); + 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); + } - if (u.isFunction(opts.onMouseout)) { - opts.onMouseout.call(this, - { - e: e, - options: opts, - key: ar.key, - selected: ar.isSelected() - }); - } + configureAltImages(me); - } + return me.images.bind(); + }, /** - * Clear any active tooltip or highlight - * - * @param {MapData} me The MapData context - * @param {EventData} e jQuery event data - * @return {[type]} [description] + * Test whether an async action is currently in progress + * @return {Boolean} true or false indicating state */ - function clearEffects(me) { - var opts = me.options; - - me.ensureNoHighlight(); - - if (opts.toolTipClose - && $.inArray('area-mouseout', opts.toolTipClose) >= 0 - && me.activeToolTip) - { - me.clearToolTip(); - } - } + isActive: function () { + return !this.complete || this.currentAction; + }, /** - * Mouse click event handler + * Return an object indicating the various states. This isn't really used by + * production code. * - * @param {MapData} me The MapData context - * @param {EventData} e jQuery event data - * @return {[type]} [description] + * @return {object} An object with properties for various states */ - function click(me,e) { - var selected, list, list_target, newSelectionState, canChangeState, cbResult, - that = this, - ar = me.getDataForArea(this), - opts = me.options; + state: function () { + return { + complete: this.complete, + resizing: this.currentAction === 'resizing', + zoomed: this.zoomed, + zoomedArea: this.zoomedArea, + scaleInfo: this.scaleInfo + }; + }, - 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 (u.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; - } - } - } + /** + * Get a unique ID for the wrapper of this imagemapster + * @return {string} A string that is unique to this image + */ - if (canChangeState) { - selected = ar.toggle(); - } + wrapId: function () { + return 'mapster_wrap_' + this.index; + }, + _idFromKey: function (key) { + return typeof key === 'string' && + Object.prototype.hasOwnProperty.call(this._xref, key) + ? this._xref[key] + : -1; + }, - if (opts.boundList && opts.boundList.length > 0) { - m.setBoundListProperties(opts, list_target, ar.isSelected()); - } + /** + * Return a comma-separated string of all selected keys + * @return {string} CSV of all keys that are currently selected + */ - 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); - } - }); - } + getSelected: function () { + var result = ''; + $.each(this.data, function (_, e) { + if (e.isSelected()) { + result += (result ? ',' : '') + this.key; } + }); + return result; + }, - mousedown.call(this,e); - - if (opts.clickNavigate && ar.href) { - window.location.href=ar.href; - return; - } + /** + * 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 + */ - if (ar && !ar.owner.currentAction) { - opts = me.options; - clickArea(ar); + 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])]; + if (ar) { + 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)]; + }, /** - * Prototype for a MapData object, representing an ImageMapster bound object - * @param {Element} image an IMG element - * @param {object} options ImageMapster binding options + * 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 */ - m.MapData = function (image, options) - { - var me = this; - // (Image) main map image + getKeysForGroup: function (key) { + var ar = this.getDataForKey(key); - me.image = image; + return !ar + ? '' + : ar.isPrimary + ? ar.key + : this.getPrimaryKeysForMapAreas(ar.areas()).join(','); + }, - 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 + /** + * 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 + */ - 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); }; - }; + getPrimaryKeysForMapAreas: function (areas) { + var keys = []; + $.each(areas, function (_, 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; + }, - m.MapData.prototype = { - constructor: m.MapData, + /** + * Clear all active selections on this map + */ - /** - * Set target.options from defaults + options - * @param {[type]} target The target - * @param {[type]} options The options to merge - */ + clearSelections: function () { + $.each(this.data, function (_, e) { + if (e.selected) { + e.deselect(true); + } + }); + this.removeSelectionFinish(); + }, - configureOptions: function(options) { - this.options= u.updateProps({}, m.defaults, options); - }, + /** + * Set area options from an array of option data. + * + * @param {object[]} areas An array of objects containing area-specific options + */ - /** - * Ensure all images are loaded - * @return {Promise} A promise that resolves when the images have finished loading (or fail) - */ + setAreaOptions: function (areas) { + var i, area_options, ar; + areas = areas || []; - bindImages: function() { - var me=this, - mi = me.images; + // refer by: map_data.options[map_data.data[x].area_option_id] - // reset the images if this is a rebind + 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); - if (mi.length>2) { - mi.splice(2); - } else if (mi.length===0) { + // TODO: will not deselect areas that were previously selected, so this only works + // for an initial bind. - // 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); + 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 (_, 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 + }); + } - 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])]; - if (ar){ - 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); - } - } - } + me.buildDataset(); - 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); + // now that we have processed all the areas, set css for wrapper, scale map if needed - 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; - } + css = { + display: 'block', + position: 'relative', + padding: 0, + width: scale.width, + height: scale.height + }; - img = $(me.image); + 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); + } - parentId = img.parent().attr('id'); + wrap.css(css); - // create a div wrapper only if there's not already a wrapper, otherwise, own it + // move all generated images into the wrapper for easy removal later - 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.slice(2)).hide(); + for (i = 1; i < me.images.length; i++) { + wrap.append(me.images[i]); + } - // 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.images[1].style.cssText = me.image.style.cssText; - me.scaleInfo = scale = u.scaleMap(me.images[0],me.images[1], opts.scaleMap); + wrap + .append(base_canvas) + .append(overlay_canvas) + .append(img.css(m.canvas_style)); - me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); - me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); + // images[0] is the original image with map, images[1] is the copy/background that is visible - // 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}); + u.setOpacity(me.images[0], 0); + $(me.images[1]).show(); - size=u.size(me.images[0]); + u.setOpacity(me.images[1], 1); - 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.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; + }; + } - 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'; - } + sorted_list.sort(function (a, b) { + a = a.value; + b = b.value; + return sort_func(a, b); + }); + } - // 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).off('.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.on('click.mapster', me.click) - .on('mouseover.mapster touchstart.mapster.noPreventDefault', me.mouseover) - .on('mouseout.mapster touchend.mapster.noPreventDefault', me.mouseout) - .on('mousedown.mapster', me.mousedown); - - - - } - - // store an ID with each area. - $area.data("mapster", mapAreaId+1); - } + 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).off('.mapster'); + + for (mapAreaId = 0; mapAreaId < areas.length; mapAreaId++) { + area_id = 0; + area = areas[mapAreaId]; + $area = $(area); + + // skip areas with no coords - selector broken for older ie + if (!area.coords) { + continue; + } + // Create a key if none was assigned by the user - // TODO listenToList - // if (opts.listenToList && opts.nitG) { - // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); - // } + if (default_group) { + curKey = String(mapAreaId); + $area.attr('data-mapster-key', curKey); + } else { + curKey = area.getAttribute(opts.mapKey); + } - // populate areas from config options - me.setAreaOptions(opts.areas); - me.redrawSelections(); + // conditions for which the area will be bound to mouse events + // only bind to areas that don't have nohref. ie 6&7 cannot detect the presence of nohref, so we have to also not bind if href is missing. - }, - processCommandQueue: function() { + if (rebind) { + mapArea = me.mapAreas[$area.data('mapster') - 1]; + mapArea.configure(curKey); + } else { + mapArea = new m.MapArea(me, area, curKey); + me.mapAreas.push(mapArea); + } - 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') - .off('.mapster'); - $(this.images) - .off('.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(); + keys = mapArea.keys; // converted to an array by mapArea + + // Iterate through each mapKey assigned to this area + for (j = keys.length - 1; j >= 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); + } - 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; + href = $area.attr('href'); + if (href && href !== '#' && !dataItem.href) { + dataItem.href = href; + } - g.refreshSelections(); - // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect - g.clearHighlight(); + if (!mapArea.nohref) { + $area + .on('click.mapster', me.click) + .on( + 'mouseover.mapster touchstart.mapster.noPreventDefault', + me.mouseover + ) + .on( + 'mouseout.mapster touchend.mapster.noPreventDefault', + me.mouseout + ) + .on('mousedown.mapster', me.mousedown); } - }; -} (jQuery)); + + // 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').off('.mapster'); + $(this.images).off('.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 (_, 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); diff --git a/src/mapimage.js b/src/mapimage.js index 27e51a9..48f17fa 100644 --- a/src/mapimage.js +++ b/src/mapimage.js @@ -1,277 +1,278 @@ -/* mapimage.js - the MapImage object, repesents an instance of a single bound imagemap +/* + mapimage.js + The MapImage object, repesents an instance of a single bound imagemap */ (function ($) { + 'use strict'; + + 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; + }, - var m = $.mapster, - u = m.utils, - ap=[]; /** - * An object encapsulating all the images used by a MapData. + * 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; + }, - 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 u.indexOf(this,image); - }, - - /** - * 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=[]; + /** + * Save an image in the images array and return its index + * @param {Image} image An Image object + * @return {int} the index of the image + */ - /** - * Length property for array-like behavior, set to zero when initializing. Array prototype - * methods will update it after that. - * - * @type {int} - */ + _add: function (image) { + var index = ap.push.call(this, image) - 1; + this.status[index] = false; + return index; + }, - me.length=0; + /** + * 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 + */ - /** - * the loaded status of the corresponding image - * @type {boolean[]} - */ + indexOf: function (image) { + return u.indexOf(this, image); + }, - me.status=[]; + /** + * Clear this object and reset it to its initial state after binding. + */ + clear: function () { + var me = this; - // actually erase the images + if (me.ids && me.ids.length > 0) { + $.each(me.ids, function (_, e) { + delete me[e]; + }); + } - me.splice(0); + /** + * A list of the cross-reference IDs bound to this object + * @type {string[]} + */ - }, + me.ids = []; - /** - * 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 - */ + /** + * Length property for array-like behavior, set to zero when initializing. Array prototype + * methods will update it after that. + * + * @type {int} + */ - add: function(image,id) { - var index,src,me = this; + me.length = 0; - if (!image) { return; } + /** + * the loaded status of the corresponding image + * @type {boolean[]} + */ - if (typeof image === 'string') { - src = image; - image = me[src]; - if (typeof image==='object') { - return me.indexOf(image); - } + me.status = []; - image = $('') - .addClass('mapster_el') - .hide(); + // actually erase the images - index=me._add(image[0]); + me.splice(0); + }, - image - .on('load',function(e) { - me.imageLoaded.call(me,e); - }) - .on('error',function(e) { - me.imageLoadError.call(me,e); - }); + /** + * 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 + */ - image.attr('src', src); - } else { + add: function (image, id) { + var index, + src, + me = this; - // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src + if (!image) { + return; + } - 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; - }, + if (typeof image === 'string') { + src = image; + image = me[src]; + if (typeof image === 'object') { + return me.indexOf(image); + } - /** - * 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 - */ + image = $('').addClass('mapster_el').hide(); + + index = me._add(image[0]); + + image + .on('load', function (e) { + me.imageLoaded.call(me, e); + }) + .on('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: function(retry) { - var me = this, - promise, - triesLeft = me.owner.options.configTimeout / 200, + /** + * Bind the images in this object, + * @return {Promise} a promise that resolves when the images have finished loading + */ - /* A recursive function to continue checking that the images have been + bind: function () { + 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; + check = function () { + var i; - // refresh status of images + // refresh status of images - i=me.length; + 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); - } - } + 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(); - promise = me.deferred=u.defer(); + check(); + return promise; + }, - check(); - return promise; - }, + resolve: function () { + var me = this, + resolver = me.deferred; - 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(); + } + }, - 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 + */ - /** - * Event handler for image onload - * @param {object} e jQuery event data - */ + imageLoaded: function (e) { + var me = this, + index = me.indexOf(e.target); - 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(); + } + } + }, - if (index>=0) { + /** + * Event handler for onload error + * @param {object} e jQuery event data + */ - 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. + 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 + */ - return status[index]; - } - }; - } (jQuery)); + 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); diff --git a/src/scale.js b/src/scale.js index a67425b..e1487fc 100644 --- a/src/scale.js +++ b/src/scale.js @@ -1,231 +1,241 @@ -/* scale.js: resize and zoom functionality - requires areacorners.js +/* + scale.js + Resize and zoom functionality + Requires areacorners.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 (u.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; + 'use strict'; + + 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 }; - 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; + }; + // 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 (u.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 } - var x= (new m.Method(this, - function () { - this.resize(width, height, duration, callback); - }, - null, - { - name: 'resize', - args: arguments - } - )).go(); - return x; - }; - -/* + ); + $.each(me.data, function (_, e) { + $.each(e.areas(), function (_, 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 (_, 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 || {}; @@ -319,8 +329,6 @@ key: key } )).go(); - - }; */ -} (jQuery)); +})(jQuery); diff --git a/src/tooltip.js b/src/tooltip.js index 597a916..bc0d846 100644 --- a/src/tooltip.js +++ b/src/tooltip.js @@ -1,223 +1,230 @@ -/* tooltip.js - tooltip functionality - requires areacorners.js +/* + 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(); + 'use strict'; + + 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); } - - /** - * 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); - } + // 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; } - /** - * Hide and remove active tooltips - * - * @param {MapData} this The mapdata object to which the tooltips belong - */ + tooltip.css(tooltipCss).addClass('mapster_tooltip'); - m.MapData.prototype.clearToolTip = function() { - if (this.activeToolTip) { - this.activeToolTip.stop().remove(); - this.activeToolTip = null; - this.activeToolTipID = null; - u.ifFunction(this.options.onHideToolTip, this); + 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.off(event_name).on(event_name, function (e) { + if (!beforeClose || beforeClose.call(this, e)) { + target.off('.mapster-tooltip'); + if (onClose) { + onClose.call(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.off(event_name) - .on(event_name, function (e) { - if (!beforeClose || beforeClose.call(this,e)) { - target.off('.mapster-tooltip'); - if (onClose) { - onClose.call(this); - } - } - }); - - return { - object: target, - event: event_name - }; - } + 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; } - /** - * 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.left += options.offsetx || 0; + ttopts.top += options.offsety || 0; - ttopts.css= options.css; - ttopts.fadeDuration = options.fadeDuration; + ttopts.css = options.css; + ttopts.fadeDuration = options.fadeDuration; - showToolTipImpl(tooltip,ttopts); + showToolTipImpl(tooltip, ttopts); - return tooltip; - } + 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. @@ -234,169 +241,215 @@ * @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() + 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; }); - return tooltip; - }; + if (md.activeToolTipID === ad.areaId) { + return; + } + md.clearToolTip(); - /** - * 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) { + md.activeToolTip = tooltip = createToolTip(content, template, options.css); - // see if any html was passed as either the options object itself, or the content property + md.activeToolTipID = ad.areaId; - return (options ? - ((typeof options === 'string' || options.jquery) ? - options : - options.content) : - null); - } + tipClosed = function () { + md.clearToolTip(); + }; - /** - * 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 - */ + 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() + }); - 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; + 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(); } - - this.showToolTip(getHtmlFromOptions(options),options); - }, - { - name: 'tooltip', - args: arguments, - key: key + ); + + md.activeToolTip = tooltip = showToolTip( + tooltip, + target, + md.image, + options.container, + options + ); } - )).go(); - }; -} (jQuery)); + }, + function areaData() { + if ($.isPlainObject(key) && !options) { + options = key; + } + + this.showToolTip(getHtmlFromOptions(options), options); + }, + { + name: 'tooltip', + args: arguments, + key: key + } + ).go(); + }; +})(jQuery); diff --git a/src/zepto.js b/src/zepto.js index 9cce210..92d23c0 100644 --- a/src/zepto.js +++ b/src/zepto.js @@ -1,103 +1,109 @@ -/* zepto.js +/* + zepto.js Monkey patch for Zepto to add some methods ImageMapster needs */ -/*global Zepto: true, jQuery: true */ - if (window.Zepto) { - jQuery = Zepto; + // eslint-disable-next-line no-global-assign + jQuery = window.Zepto; - (function ($) { - var hasOwn = Object.prototype.hasOwnProperty; + (function ($) { + 'use strict'; - $.css=function( elem, name ) { + var hasOwn = Object.prototype.hasOwnProperty; - return getComputedStyle(elem,name); - }; + $.css = function (elem, name) { + return getComputedStyle(elem, name); + }; - $.trim = function (str) { - return str.replace(/^\s+/, '').replace(/\s+$/, ''); - }; - $.inArray = function (target, arr) { - return arr.indexOf(target); - }; - /*lint-ignore-start*/ - $.isEmptyObject=function(obj) { - for ( var name in obj ) { - return false; - } - return true; - }; - $.isWindow = function(obj) { - return obj && typeof obj === "object" && "setInterval" in obj; - }; + $.trim = function (str) { + return str.replace(/^\s+/, '').replace(/\s+$/, ''); + }; + $.inArray = function (target, arr) { + return arr.indexOf(target); + }; + /*lint-ignore-start*/ + $.isEmptyObject = function (obj) { + for (var name in obj) { + return false; + } + return true; + }; + $.isWindow = function (obj) { + return obj && typeof obj === 'object' && 'setInterval' in obj; + }; - $.isPlainObject= function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || typeof obj !== "object" || obj.nodeType || $.isWindow( obj ) ) { - return false; - } + $.isPlainObject = function (obj) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if (!obj || typeof obj !== 'object' || obj.nodeType || $.isWindow(obj)) { + return false; + } - try { - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } + try { + // Not own constructor property must be Object + if ( + obj.constructor && + !hasOwn.call(obj, 'constructor') && + !hasOwn.call(obj.constructor.prototype, 'isPrototypeOf') + ) { + return false; + } + } catch (e) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. - var key; - for ( key in obj ) {} + var key; + for (key in obj) { + // intentionally ignored + } - return typeof key === 'undefined' || hasOwn.call( obj, key ); - } - /*lint-ignore-end*/ - $.fn.clone = function () { - var ret = $(); - this.each(function () { - ret = ret.add(this.cloneNode(true)); - }); - return ret; - }; - $.fn.stop=function() { - return this; - }; - $.fn.elOrEmpty = function () { - return this.length ? this[0] : {}; - }; - $.each(["Height", "Width"], function( i, name ) { - //var type = name.toLowerCase(); - // outerHeight and outerWidth - //$.fn[ "outer" + name ] = function( margin ) { - // var elem = this[0], - // cl=margin?"margin":"border"; - // return elem ? - // elem.style ? - // (parseFloat( $.css( elem, cl+'-top-'+type)+ ) : - // this[ type ]() : - // null; - // }; - $.fn["outer"+name]=function() { - return this[name.toLowerCase()](); - }; - }); - $.fn.position = $.fn.position || function () { - var el = this.elOrEmpty(); - return { - left: this.left, - top: this.top - }; + return typeof key === 'undefined' || hasOwn.call(obj, key); + }; + /*lint-ignore-end*/ + $.fn.clone = function () { + var ret = $(); + this.each(function () { + ret = ret.add(this.cloneNode(true)); + }); + return ret; + }; + $.fn.stop = function () { + return this; + }; + $.fn.elOrEmpty = function () { + return this.length ? this[0] : {}; + }; + $.each(['Height', 'Width'], function (_, name) { + //var type = name.toLowerCase(); + // outerHeight and outerWidth + //$.fn[ "outer" + name ] = function( margin ) { + // var elem = this[0], + // cl=margin?"margin":"border"; + // return elem ? + // elem.style ? + // (parseFloat( $.css( elem, cl+'-top-'+type)+ ) : + // this[ type ]() : + // null; + // }; + $.fn['outer' + name] = function () { + return this[name.toLowerCase()](); + }; + }); + $.fn.position = + $.fn.position || + function () { + return { + left: this.left, + top: this.top }; - $.browser = {}; - $.browser.msie = false; - } (jQuery)); + }; + $.browser = {}; + $.browser.msie = false; + })(jQuery); } diff --git a/tests/README.md b/tests/README.md index 142e4da..8d9dbaa 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,4 +1,4 @@ -#### Tests for ImageMapster +# Tests for ImageMapster 5/15/2012 @@ -8,4 +8,3 @@ I am in the process of revising the test suite to use IQTest, a promise-aware te The dev code (1.2.4.067) is considered stable; it will become an official release when I'm finished updating the tests.. The old tests (loaded from `test.html`) are no longer supported. They are still here until I finish the update but should not be considered functional. - diff --git a/tests/core.tests.js b/tests/core.tests.js index da24920..dd936ab 100644 --- a/tests/core.tests.js +++ b/tests/core.tests.js @@ -1,110 +1,168 @@ -/*global Test: true, iqtest */ -/*jslint onevar: false */ - -this.tests = this.tests || []; - -this.tests.push( - iqtest.create("utility","tests for core/common functionality") - .add("Utility Functions", function (a, r) { - var result, - mu = $.mapster.utils; - - a.isTrue(mu.isBool(true), "isBool returns true=true"); - a.isTrue(mu.isBool(false), "isBool returns false=true"); - - a.isFalse(mu.isBool(null), "isBool returns null=false"); - - a.equals(mu.boolOrDefault(true), true, "boolOrDefault(true) returns true"); - a.equals(mu.boolOrDefault(false), false, "boolOrDefault(false) returns false"); - a.equals(mu.boolOrDefault("something"), false, "boolOrDefault('something') (a truthy value) returns false"); - a.equals(mu.boolOrDefault(null), false, "boolOrDefault(null) (a falsy value) returns false"); - a.equals(mu.boolOrDefault(true, "foo"), true, "boolOrDefault(true) with default value returns true"); - a.equals(mu.boolOrDefault(false, "foo"), false, "boolOrDefault(false) with default value returns false"); - a.equals(mu.boolOrDefault("something", "foo"), "foo", "boolOrDefault('something') (a falsy value) with default value returns default"); - a.equals(mu.boolOrDefault(undefined, "foo"), "foo", "boolOrDefault(undefined) (a falsy value) with default value returns default"); - - // test the extend-like function - - var obj = { a: "a", b: "b" }; - var otherObj = { a: "a2", b: "b2", c: "c" }; - var arrObj = { a: [1, 2], b: { a: "a2", b: "b2"} }; - - result = mu.updateProps({}, arrObj); - a.equals([1, 2], result.a, "Array copied as array"); - - result = mu.updateProps(obj, otherObj); - - a.equals(result, { a: "a2", b: "b2" }, "Merge with extra properties - no add"); - - // input object should be affected - a.equals(obj, { a: "a2", b: "b2" }, "Test input object following merge matches output"); - - result = mu.updateProps(otherObj, obj, otherObj); - a.equals(result, { a: "a2", b: "b2", c: "c" }, "Merge with extra properties - add"); - - otherObj = { a: "a3" }; - result = mu.updateProps(obj, otherObj); - - // ut.assertPropsEq(function () { return u.updateProps(result, otherObj); }, { a: "a3", b: "b2", c: "c" }, "Merge with missing properties"); - - // test several at once - obj = { a: "unchanged-a", b: "unchanged-b" }; - otherObj = { b: "b4" }; - var otherObj2 = { a: "a4" }; - - a.equals(mu.updateProps(obj, otherObj, otherObj2), { a: "a4", b: "b4" }, "Merge with mutiple inputs"); - - var templateObj = { p1: "prop1", p2: "prop2" }; - otherObj = { p1: "newProp1", p3: "prop3", p4: "prop4" }; - - a.equals(mu.updateProps({}, templateObj, otherObj), { p1: "newProp1", p2: "prop2" }, "Template works."); - - var expectedResult = { p1: "newProp1", p2: "prop2", p4: "prop4" }; - //ut.assertPropsEq(u.updateProps({},templateObj, otherObj, ), expectedResult, "Ignore works."); - - templateObj.p3 = { subp1: "subprop1", subp2: "subprop2" }; - templateObj.p4 = null; - - result = { }; - expectedResult.p3 = otherObj.p3; - - mu.updateProps(result, templateObj, otherObj); - a.equals(result, expectedResult, "Copying a sub-object - start"); - - delete otherObj.p3; - result.p3 = { existing: "bar" }; - - expectedResult.p3 = templateObj.p3; - expectedResult.p3.existing = "bar"; - - mu.updateProps(result, templateObj, otherObj); - a.equals(result, expectedResult, "Deep works"); - - // test indexOfProp - - obj = { test: "test" }; - var arr = [{ name: "test1", value: "value1" }, { name: "test2", value: "value2" }, { name: "test3", value: obj}]; - - var index = mu.indexOfProp(arr, "name", "test2"); - a.equals(index, 1, "arrayIndexOfProp returns correct value for string"); - - index = mu.indexOfProp(arr, "value", obj); - a.equals(index, 2, "arrayIndexOfProp returns correct value for object & last element"); - - index = mu.indexOfProp(arr, "name", "test1"); - a.equals(index, 0, "arrayIndexOfProp returns correct value for first element"); - - index = mu.indexOfProp(arr, "foo", "bar"); - a.equals(index, -1, "Missing property handled correctly"); - - index = mu.indexOfProp(arr, "name", "bar"); - a.equals(index, -1, "Missing property value handled correctly"); - - - - })); - - - - - +/* global iqtest */ + +this.tests = this.tests || []; + +this.tests.push( + iqtest + .create('utility', 'tests for core/common functionality') + .add('Utility Functions', function (a) { + 'use strict'; + + var result, + mu = $.mapster.utils; + + a.isTrue(mu.isBool(true), 'isBool returns true=true'); + a.isTrue(mu.isBool(false), 'isBool returns false=true'); + + a.isFalse(mu.isBool(null), 'isBool returns null=false'); + + a.equals( + mu.boolOrDefault(true), + true, + 'boolOrDefault(true) returns true' + ); + a.equals( + mu.boolOrDefault(false), + false, + 'boolOrDefault(false) returns false' + ); + a.equals( + mu.boolOrDefault('something'), + false, + "boolOrDefault('something') (a truthy value) returns false" + ); + a.equals( + mu.boolOrDefault(null), + false, + 'boolOrDefault(null) (a falsy value) returns false' + ); + a.equals( + mu.boolOrDefault(true, 'foo'), + true, + 'boolOrDefault(true) with default value returns true' + ); + a.equals( + mu.boolOrDefault(false, 'foo'), + false, + 'boolOrDefault(false) with default value returns false' + ); + a.equals( + mu.boolOrDefault('something', 'foo'), + 'foo', + "boolOrDefault('something') (a falsy value) with default value returns default" + ); + a.equals( + mu.boolOrDefault(undefined, 'foo'), + 'foo', + 'boolOrDefault(undefined) (a falsy value) with default value returns default' + ); + + // test the extend-like function + + var obj = { a: 'a', b: 'b' }, + otherObj = { a: 'a2', b: 'b2', c: 'c' }, + arrObj = { a: [1, 2], b: { a: 'a2', b: 'b2' } }; + + result = mu.updateProps({}, arrObj); + a.equals([1, 2], result.a, 'Array copied as array'); + + result = mu.updateProps(obj, otherObj); + + a.equals( + result, + { a: 'a2', b: 'b2' }, + 'Merge with extra properties - no add' + ); + + // input object should be affected + a.equals( + obj, + { a: 'a2', b: 'b2' }, + 'Test input object following merge matches output' + ); + + result = mu.updateProps(otherObj, obj, otherObj); + a.equals( + result, + { a: 'a2', b: 'b2', c: 'c' }, + 'Merge with extra properties - add' + ); + + otherObj = { a: 'a3' }; + result = mu.updateProps(obj, otherObj); + + // ut.assertPropsEq(function () { return u.updateProps(result, otherObj); }, { a: "a3", b: "b2", c: "c" }, "Merge with missing properties"); + + // test several at once + obj = { a: 'unchanged-a', b: 'unchanged-b' }; + otherObj = { b: 'b4' }; + var otherObj2 = { a: 'a4' }; + + a.equals( + mu.updateProps(obj, otherObj, otherObj2), + { a: 'a4', b: 'b4' }, + 'Merge with mutiple inputs' + ); + + var templateObj = { p1: 'prop1', p2: 'prop2' }; + otherObj = { p1: 'newProp1', p3: 'prop3', p4: 'prop4' }; + + a.equals( + mu.updateProps({}, templateObj, otherObj), + { p1: 'newProp1', p2: 'prop2' }, + 'Template works.' + ); + + var expectedResult = { p1: 'newProp1', p2: 'prop2', p4: 'prop4' }; + //ut.assertPropsEq(u.updateProps({},templateObj, otherObj, ), expectedResult, "Ignore works."); + + templateObj.p3 = { subp1: 'subprop1', subp2: 'subprop2' }; + templateObj.p4 = null; + + result = {}; + expectedResult.p3 = otherObj.p3; + + mu.updateProps(result, templateObj, otherObj); + a.equals(result, expectedResult, 'Copying a sub-object - start'); + + delete otherObj.p3; + result.p3 = { existing: 'bar' }; + + expectedResult.p3 = templateObj.p3; + expectedResult.p3.existing = 'bar'; + + mu.updateProps(result, templateObj, otherObj); + a.equals(result, expectedResult, 'Deep works'); + + // test indexOfProp + + obj = { test: 'test' }; + var arr = [ + { name: 'test1', value: 'value1' }, + { name: 'test2', value: 'value2' }, + { name: 'test3', value: obj } + ], + index = mu.indexOfProp(arr, 'name', 'test2'); + a.equals(index, 1, 'arrayIndexOfProp returns correct value for string'); + + index = mu.indexOfProp(arr, 'value', obj); + a.equals( + index, + 2, + 'arrayIndexOfProp returns correct value for object & last element' + ); + + index = mu.indexOfProp(arr, 'name', 'test1'); + a.equals( + index, + 0, + 'arrayIndexOfProp returns correct value for first element' + ); + + index = mu.indexOfProp(arr, 'foo', 'bar'); + a.equals(index, -1, 'Missing property handled correctly'); + + index = mu.indexOfProp(arr, 'name', 'bar'); + a.equals(index, -1, 'Missing property value handled correctly'); + }) +); diff --git a/tests/data.tests.js b/tests/data.tests.js index 345b041..6f868bd 100644 --- a/tests/data.tests.js +++ b/tests/data.tests.js @@ -1,43 +1,56 @@ -/*global iqtest, u, map_options, image, group_setup */ -/*jslint onevar: false */ - -this.tests = this.tests || []; - -this.tests.push( - iqtest.create("data","data access features (non-UI)") - .add("'keys' method", function (a, r) { - var map; - // create a promise from the "onConfigured" callback - this.when(function(cb) { - map=image.mapster( - u.extend({},map_options,{ - onConfigured: cb - } - )); - }).then(function() { - - var keys=map.mapster('keys','TX'); - a.equals('TX',keys,"Got primary key for something with only one key"); - - keys=map.mapster('keys','ME'); - a.equals('ME',keys,"Got primary key for something with multiple keys"); - - keys=map.mapster('keys','new-england'); - a.collectionEquals('ME,VT,NH,CT,RI,MA',keys,"Got primary key for something with multiple keys"); - - keys=map.mapster('keys','new-england',true); - a.collectionEquals('ME,VT,NH,CT,RI,MA,new-england,really-cold',keys,"Got primary key for something with multiple keys"); - - keys = $('area[state="HI"]').mapster('keys'); - a.equals('HI',keys,"Got primary key from an area"); - - var areas = $('area[state="HI"],area[state*="new-england"]'); - keys = areas.mapster('keys'); - a.collectionEquals('HI,ME,VT,NH,CT,RI,MA',keys,"Got primary key for something with multiple keys"); - }); - })); - - - - - +/* global iqtest, u, map_options, image */ + +this.tests = this.tests || []; + +this.tests.push( + iqtest + .create('data', 'data access features (non-UI)') + .add("'keys' method", function (a) { + 'use strict'; + + var map; + // create a promise from the "onConfigured" callback + this.when(function (cb) { + map = image.mapster( + u.extend({}, map_options, { + onConfigured: cb + }) + ); + }).then(function () { + var keys = map.mapster('keys', 'TX'); + a.equals('TX', keys, 'Got primary key for something with only one key'); + + keys = map.mapster('keys', 'ME'); + a.equals( + 'ME', + keys, + 'Got primary key for something with multiple keys' + ); + + keys = map.mapster('keys', 'new-england'); + a.collectionEquals( + 'ME,VT,NH,CT,RI,MA', + keys, + 'Got primary key for something with multiple keys' + ); + + keys = map.mapster('keys', 'new-england', true); + a.collectionEquals( + 'ME,VT,NH,CT,RI,MA,new-england,really-cold', + keys, + 'Got primary key for something with multiple keys' + ); + + keys = $('area[state="HI"]').mapster('keys'); + a.equals('HI', keys, 'Got primary key from an area'); + + var areas = $('area[state="HI"],area[state*="new-england"]'); + keys = areas.mapster('keys'); + a.collectionEquals( + 'HI,ME,VT,NH,CT,RI,MA', + keys, + 'Got primary key for something with multiple keys' + ); + }); + }) +); diff --git a/tests/events.tests.js b/tests/events.tests.js index 9402b75..586f4e6 100644 --- a/tests/events.tests.js +++ b/tests/events.tests.js @@ -1,83 +1,99 @@ -/*global Test: true, iqtest, image, map_options, areas */ -/*jslint onevar: false */ - -this.tests = this.tests || []; - -this.tests.push( - iqtest.create("events","tests for imagemapster events") - .add("Mouse Events", function (a, r) { - - var me = this, - getPromise = function(name) { - return me.promises(name); - }, - map = image.mapster($.extend(map_options, { - onConfigured: getPromise("configured").resolve - })); - - function setCallback(opt,cb) { - var obj = {}; - obj[opt]=function(e) { - e.this_context = this; - cb(e); - }; - map.mapster('set_options', obj); - } - - getPromise("configured").then(function() { - setCallback('onMouseover',getPromise("mouseover1").resolve); - areas.find('area[state="NV"]').first().trigger("mouseover"); - }); - - getPromise("mouseover1").then(function(e) { - a.truthy(e, "Mouseover fired for Nevada"); - a.equals(e.selected, false, "Selected state returned correctly"); - a.equals(e.key, "NV", "Key returned correctly"); - - setCallback('onMouseover',getPromise("mouseover2").resolve); - areas.find('area[state="AK"]').first().trigger("mouseover"); - }); - - - getPromise("mouseover2").then(function(e) { - a.truthy(e, "Mouseover fired for Alaska"); - a.equals(e.selected, true, "Selected state returned correctly"); - a.equals(e.key, "AK", "Key returned correctly"); - - setCallback('onMouseout',getPromise("mouseout1").resolve); - areas.find('area[state="AK"]').first().trigger("mouseout"); - }); - - getPromise("mouseout1").then(function(e) { - a.truthy(e, "Mouseout fired for Nevada"); - a.equals("AK",e.key,"Correct key returned by mouseout"); - a.equals(e.selected, true, "Selected state returned correctly"); - - setCallback('onClick',getPromise("click1").resolve); - areas.find('area[state="GA"]').first().trigger("click"); - }); - - - getPromise("click1").then(function(e) { - a.equals(e.key, "GA", "Click callback fired for Georgia, and key was correct"); - a.equals(e.selected, true, "Click callback fired for Georgia, and selected was correct"); - a.equals(e.this_context, areas.find('area[state="GA"]')[0], "Click callback fired for Georgia, and 'this' was correct"); - - setCallback('onClick',getPromise("click2").resolve); - areas.find('area[state="OR"]').first().trigger("click"); - }); - - getPromise("click2").then(function(e) { - a.equals(e.key, "OR", "Click callback fired for Oregon, and key was correct"); - a.equals(e.selected, false, "Click callback fired for Oregon, and selected was correct"); - getPromise("finished").resolve(); - }); - - a.resolves(getPromise("finished"),"The last test was run"); - - })); - - - - - +/* global iqtest, image, map_options, areas */ + +this.tests = this.tests || []; + +this.tests.push( + iqtest + .create('events', 'tests for imagemapster events') + .add('Mouse Events', function (a) { + 'use strict'; + + var me = this, + getPromise = function (name) { + return me.promises(name); + }, + map = image.mapster( + $.extend(map_options, { + onConfigured: getPromise('configured').resolve + }) + ); + + function setCallback(opt, cb) { + var obj = {}; + obj[opt] = function (e) { + e.this_context = this; + cb(e); + }; + map.mapster('set_options', obj); + } + + getPromise('configured').then(function () { + setCallback('onMouseover', getPromise('mouseover1').resolve); + areas.find('area[state="NV"]').first().trigger('mouseover'); + }); + + getPromise('mouseover1').then(function (e) { + a.truthy(e, 'Mouseover fired for Nevada'); + a.equals(e.selected, false, 'Selected state returned correctly'); + a.equals(e.key, 'NV', 'Key returned correctly'); + + setCallback('onMouseover', getPromise('mouseover2').resolve); + areas.find('area[state="AK"]').first().trigger('mouseover'); + }); + + getPromise('mouseover2').then(function (e) { + a.truthy(e, 'Mouseover fired for Alaska'); + a.equals(e.selected, true, 'Selected state returned correctly'); + a.equals(e.key, 'AK', 'Key returned correctly'); + + setCallback('onMouseout', getPromise('mouseout1').resolve); + areas.find('area[state="AK"]').first().trigger('mouseout'); + }); + + getPromise('mouseout1').then(function (e) { + a.truthy(e, 'Mouseout fired for Nevada'); + a.equals('AK', e.key, 'Correct key returned by mouseout'); + a.equals(e.selected, true, 'Selected state returned correctly'); + + setCallback('onClick', getPromise('click1').resolve); + areas.find('area[state="GA"]').first().trigger('click'); + }); + + getPromise('click1').then(function (e) { + a.equals( + e.key, + 'GA', + 'Click callback fired for Georgia, and key was correct' + ); + a.equals( + e.selected, + true, + 'Click callback fired for Georgia, and selected was correct' + ); + a.equals( + e.this_context, + areas.find('area[state="GA"]')[0], + "Click callback fired for Georgia, and 'this' was correct" + ); + + setCallback('onClick', getPromise('click2').resolve); + areas.find('area[state="OR"]').first().trigger('click'); + }); + + getPromise('click2').then(function (e) { + a.equals( + e.key, + 'OR', + 'Click callback fired for Oregon, and key was correct' + ); + a.equals( + e.selected, + false, + 'Click callback fired for Oregon, and selected was correct' + ); + getPromise('finished').resolve(); + }); + + a.resolves(getPromise('finished'), 'The last test was run'); + }) +); diff --git a/tests/global.tests.js b/tests/global.tests.js index 8954570..a3bf5f1 100644 --- a/tests/global.tests.js +++ b/tests/global.tests.js @@ -1,71 +1,76 @@ -/* - -Shared resources, setup & teardown. This must be included for all tests. - -*/ - -/*global Test: true, iqtest */ -/*jslint onevar: false */ - -var image, areas, map_options, map_copy, - u = iqtest.impl.utility; - -$(document).ready(function() { - map_copy= $('#usa_image').clone(); -}); - -var group_setup=function() { - // start with a clean mapster slate - $('img').mapster('unbind'); - - // always start with a clean map for each group - $('#usa_image').replaceWith(map_copy.clone()); - - image = $('#usa_image'); - areas = $('#usa_image_map'); - - map_options = { - isSelectable: true, - singleSelect: false, - mapKey: 'state', - mapValue: 'full', - listKey: 'name', - listSelectedAttribute: 'checked', - sortList: "asc", - showToolTip: true, - toolTipClose: ["area-mouseout"], - areas: [ - { - key: "TX", - selected: true - } - , - { - key: "AK", - isSelectable: false, - selected: true - } - , - { - key: "WA", - staticState: true - } - , - { - key: "OR", - staticState: false - }, - { - key: "CA", - toolTip: $('
Don\'t mess with Louisiana. Why ? Click here for more info.
') - } - ] - }; -}; - -// create a default setup - -iqtest.configure({ - setup: group_setup -}); - +/* + +Shared resources, setup & teardown. This must be included for all tests. + +*/ + +/* global iqtest */ + +var image, + areas, + map_options, + map_copy, + u = iqtest.impl.utility; // eslint-disable-line no-unused-vars + +$(document).ready(function () { + 'use strict'; + + map_copy = $('#usa_image').clone(); +}); + +var group_setup = function () { + 'use strict'; + + // start with a clean mapster slate + $('img').mapster('unbind'); + + // always start with a clean map for each group + $('#usa_image').replaceWith(map_copy.clone()); + + image = $('#usa_image'); // eslint-disable-line no-unused-vars + areas = $('#usa_image_map'); // eslint-disable-line no-unused-vars + + // eslint-disable-next-line no-unused-vars + map_options = { + isSelectable: true, + singleSelect: false, + mapKey: 'state', + mapValue: 'full', + listKey: 'name', + listSelectedAttribute: 'checked', + sortList: 'asc', + showToolTip: true, + toolTipClose: ['area-mouseout'], + areas: [ + { + key: 'TX', + selected: true + }, + { + key: 'AK', + isSelectable: false, + selected: true + }, + { + key: 'WA', + staticState: true + }, + { + key: 'OR', + staticState: false + }, + { + key: 'CA', + toolTip: $( + '
Don\'t mess with Louisiana. Why ? Click here for more info.
' + ) + } + ] + }; +}; + +// create a default setup + +iqtest.configure({ + setup: group_setup +}); diff --git a/tests/imagemapster-test-runner.html b/tests/imagemapster-test-runner.html index b7b350f..7e3d615 100644 --- a/tests/imagemapster-test-runner.html +++ b/tests/imagemapster-test-runner.html @@ -1,294 +1,832 @@ - - + ImageMapster Browser Test Runner - + - - - - - - - - - - - + + + + + + + + + + + + - - - - - }); - - - - - - -

ImageMapster Test Runner

-
-Choose tests to run: - select all  - run tests   - -
    -
- - rerun last group -
- -
-
-
-
- -show/hide test image - - - - - \ No newline at end of file + } + + +

ImageMapster Test Runner

+
+ Choose tests to run: + select all  + run tests   + +
    + + rerun last group +
    + +
    +
    + + show/hide test image + + + + diff --git a/tests/migrated.tests.js b/tests/migrated.tests.js index e1c508b..6f0b7bc 100644 --- a/tests/migrated.tests.js +++ b/tests/migrated.tests.js @@ -1,242 +1,350 @@ -/*global iqtest, map_options */ -/*jslint onevar: false */ - -this.tests = this.tests || []; -(function() { - - function attrMatches(jq, attr, matches) { - var list = matches.split(','), result = $(); - jq.each(function () { - for (var i = 0; i < list.length; i++) { - if ($(this).is("[" + attr + "='" + list[i] + "']")) { - result = result.add(this); - i = list.length; - } - } - }); - return result; - } - - this.tests.push( - iqtest.create("basic","manipulation tests - migrated from old test suite (not organized)") - .add("Migrated tests", function (a, r) { - var me=this, - getPromise= function(name) { - return me.promises(name); - },mu = $.mapster.utils; - - // Save current state to see if we cleaned up properly later - var domCount = $('#test_elements *').length; - var map = $('img').mapster(); - - // // testing with no canvas on a browser that doesn't support it anyway doesn't make sense, regular test will cover it - // var has_canvas = (document.namespaces && document.namespaces.g_vml_) ? false : - // $('')[0].getContext ? true : false; - - // if (!has_canvas && disableCanvas) { - // map.mapster('unbind'); - // a.pass("The browser does not support canvases: this test was skipped.") - // return; - // } - - map.mapster('unbind'); - - // var oldHasCanvas = $.mapster.hasCanvas; - // if (disableCanvas) { - // $.mapster.hasCanvas=false; - // } - - - // test using only bound images - var isObfuscated = map.mapster("test","typeof m === 'undefined'"); - if (!isObfuscated) { - map = $('img').mapster(map_options); - a.equals(1,map.mapster("test", "typeof m !== 'undefined' && m.map_cache && m.map_cache.length"), - "Only imagemap bound images were obtained on generic create"); - - map = $('img,div').mapster({ mapKey: "state" }); - - a.equals(1,map.mapster("test", "typeof m !== 'undefined' && m.map_cache && m.map_cache.length"), - "Only imagemap bound images were obtained on generic create with other elements"); - - - } - map = $("#usa_image").mapster($.extend(map_options, { - onConfigured: getPromise("configured").resolve - })); - - - getPromise("configured").then(function() { - - var initialOpts = mu.updateProps({}, $.mapster.defaults, map_options); - var opts = map.mapster('get_options'); - a.equals(opts, initialOpts, "Options retrieved match initial options"); - - // todo - test new options options - //opts = map.mapster('get_options',null,true); - //initialOpts.render_select = u.mergeObjects({template:$.mapster.render_defaults }); - - var newOpts = { isSelectable: false, areas: [{ key: 'MT', isDeselectable: false}] }; - map.mapster('set_options', newOpts); - opts = map.mapster('get_options'); - - // to compare this we have to ignore areas, since they won't be the same object - - var expectedNewOpts = $.extend({},initialOpts); - expectedNewOpts.isSelectable = false; - - a.propertyValueEquals(opts,expectedNewOpts, "Options retrieved match updated value"); - a.equals(opts.areas.length, 6, "Area option was added"); - - // restore original options before continuing - opts = map.mapster('set_options', { isSelectable: true, areas: [{ key: 'MT', isDeselectable: true}] }); - - a.equals(!!map.mapster, true, "Plugin returns jQuery object"); - a.equals(map, $("#usa_image"), "Plugin returns jquery same object as invocation"); - - // order is not guaranteed - this is the order the areas are created. - var selected = map.mapster('get'); - - // This test should NOT show "WA" because StaticState items are not considered "selected" - - a.collectionEquals(selected, "AK,TX", "Initially selected items returned with 'get'"); - - - selected = map.mapster('get', 'TX'); - a.equals(selected, true, "Initially selected single item returned true with 'get'"); - selected = map.mapster('get', 'ME'); - a.equals(selected, false, "Initially deselected single item returned false with 'get'"); - - - // Test setting/getting via area - - // AK was already selected, should be ignored - - attrMatches($('area'), "state", "AK,HI,LA").mapster('set', true); - var area_sel = map.mapster('get'); - a.collectionEquals(area_sel, "HI,AK,LA,TX", "Set using area works"); - - map.mapster('set', false, 'LA,TX'); - a.collectionEquals("HI,AK", map.mapster('get'), "unset using keys works"); - - map.mapster('set', true, 'ME,OH,TX'); - a.collectionEquals("HI,AK,ME,OH,TX", map.mapster('get'), "set using keys works"); - - // test toggling: AK should go off, MT should go on - var areas = $('area[state=AK]').first(); - areas = areas.add($('area[state=MT]').first()); - areas.mapster('set'); - a.collectionEquals("HI,ME,OH,TX,MT", map.mapster('get'), "toggling keys works"); - - // test clicking - $('area[state="AZ"]').first().click(); - selected = map.mapster('get', 'AZ'); - a.equals(true, selected, "Click-selected area returned 'get'"); - a.collectionEquals("HI,ME,OH,TX,MT,AZ", map.mapster('get'), "Complete list returned with 'get'"); - - /// try to click select "staticstate areas - - $('area[state="OR"]').first().click(); - selected = map.mapster('get', 'OR'); - a.equals(selected, false, "Cannot select 'staticState=false' area with click"); - - selected = map.mapster('get', 'WA'); - a.equals(selected, false, "staticState=true area is considered not selected"); - - opts = map.mapster('get_options', 'WA'); - a.equals(opts.staticState, true, "get effective options returned correct static state for WA"); - - opts = map.mapster('get_options', 'OR'); - a.equals(opts.staticState, false, "get effective options returned correct static state for OR"); - - - $('area[state="WA"]').first().click(); - selected = map.mapster('get', 'WA'); - a.equals(selected, false, "Cannot change selection state of 'staticState=true' area with click"); - - // do it programatically - - map.mapster('set', true, 'OR'); - selected = map.mapster('get', 'OR'); - a.equals(selected, true, "Can select 'staticState=false' area with 'set'"); - - map.mapster('set', false, 'WA'); - a.equals(map.mapster('get', 'WA'), false, "Can deselect staticState=true' area with 'set'"); - - // test rebind - newOpts = map.mapster('get_options'); - newOpts.singleSelect = true; - map.mapster('rebind', newOpts); - a.collectionEquals(map.mapster('get'), 'TX,AK', "Rebind with singleSelect reverted to original state"); - - map.mapster('set', true, "MI"); - a.equals(map.mapster('get'), 'MI', "Single select worked."); - - map.mapster('set_options', { isDeselectable: false }); - $('area[state="MI"]').first().click(); - a.equals(map.mapster('get', 'MI'), true, "Cannot deselect single selected item with isDeselectable=false"); - - $('area[state="UT"]').first().click(); - - a.equals(map.mapster('get'), 'UT', "New single state selected"); - - map.mapster('set_options', { singleSelect: false, isDeselectable: true, areas: [{ key: 'ME', isDeselectable: false}] }); - - $('area[state="UT"]').first().click(); - a.equals(map.mapster('get', 'UT'), false, "Was able to deselect item after removing singleSelect"); - - map.mapster('set', true, "CA,HI,ME"); - - - $('area[state="ME"]').first().click(); - a.equals(map.mapster('get', 'ME'), true, "Could not deselect one item marked as !isDeselectable"); - $('area[state="CA"]').first().click(); - a.equals(map.mapster('get', 'CA'), false, "Could deselect other items "); - - // Test manual highlighting - - a.equals(map.mapster('highlight'), null, "nothing is highlighted"); - - $('area[state="CA"]').first().mapster('highlight'); - - a.equals(map.mapster('highlight'), "CA", "highlighted manually"); - - map.mapster('highlight', "LA"); - - a.equals(map.mapster('highlight'), "LA", "highlighted manually using other technique"); - - map.mapster('highlight', false); - - a.equals(map.mapster('highlight'), null, "everything unhighlighted"); - - // restore internal canvas setting or these tests won't work - // if (disableCanvas) { - // map.mapster('test', 'has_canvas=true'); - // } else { - - // // cleanup tests - skip to play with map afterwards - // // return; - - // if (has_canvas) { - // a.equals($('canvas').length, 2, 'There are 2 canvases.'); - // map.mapster(map_options); - // a.equals($('canvas').length, 2, 'There are 2 canvases (recreate was clean)'); - // } - // } - map.mapster('unbind'); - a.equals($('canvas').length, 0, 'No canvases remain after an unbind.'); - - a.equals($('#test_elements *').length, domCount, "# elements in DOM is the same."); - - // if (disableCanvas) { - // $.mapster.hasCanvas=oldHasCanvas; - // } - - }); - - })); - -}()); - - - +/* global iqtest, map_options */ + +this.tests = this.tests || []; + +function attrMatches(jq, attr, matches) { + 'use strict'; + + var list = matches.split(','), + result = $(); + jq.each(function () { + for (var i = 0; i < list.length; i++) { + if ($(this).is('[' + attr + "='" + list[i] + "']")) { + result = result.add(this); + i = list.length; + } + } + }); + return result; +} + +this.tests.push( + iqtest + .create( + 'basic', + 'manipulation tests - migrated from old test suite (not organized)' + ) + .add('Migrated tests', function (a) { + 'use strict'; + + var me = this, + getPromise = function (name) { + return me.promises(name); + }, + mu = $.mapster.utils, + // Save current state to see if we cleaned up properly later + domCount = $('#test_elements *').length, + map = $('img').mapster(); + + // // testing with no canvas on a browser that doesn't support it anyway doesn't make sense, regular test will cover it + // var has_canvas = (document.namespaces && document.namespaces.g_vml_) ? false : + // $('')[0].getContext ? true : false; + + // if (!has_canvas && disableCanvas) { + // map.mapster('unbind'); + // a.pass("The browser does not support canvases: this test was skipped.") + // return; + // } + + map.mapster('unbind'); + + // var oldHasCanvas = $.mapster.hasCanvas; + // if (disableCanvas) { + // $.mapster.hasCanvas=false; + // } + + // test using only bound images + var isObfuscated = map.mapster('test', "typeof m === 'undefined'"); + if (!isObfuscated) { + map = $('img').mapster(map_options); + a.equals( + 1, + map.mapster( + 'test', + "typeof m !== 'undefined' && m.map_cache && m.map_cache.length" + ), + 'Only imagemap bound images were obtained on generic create' + ); + + map = $('img,div').mapster({ mapKey: 'state' }); + + a.equals( + 1, + map.mapster( + 'test', + "typeof m !== 'undefined' && m.map_cache && m.map_cache.length" + ), + 'Only imagemap bound images were obtained on generic create with other elements' + ); + } + map = $('#usa_image').mapster( + $.extend(map_options, { + onConfigured: getPromise('configured').resolve + }) + ); + + getPromise('configured').then(function () { + var initialOpts = mu.updateProps({}, $.mapster.defaults, map_options), + opts = map.mapster('get_options'); + a.equals(opts, initialOpts, 'Options retrieved match initial options'); + + // todo - test new options options + //opts = map.mapster('get_options',null,true); + //initialOpts.render_select = u.mergeObjects({template:$.mapster.render_defaults }); + + var newOpts = { + isSelectable: false, + areas: [{ key: 'MT', isDeselectable: false }] + }; + map.mapster('set_options', newOpts); + opts = map.mapster('get_options'); + + // to compare this we have to ignore areas, since they won't be the same object + + var expectedNewOpts = $.extend({}, initialOpts); + expectedNewOpts.isSelectable = false; + + a.propertyValueEquals( + opts, + expectedNewOpts, + 'Options retrieved match updated value' + ); + a.equals(opts.areas.length, 6, 'Area option was added'); + + // restore original options before continuing + opts = map.mapster('set_options', { + isSelectable: true, + areas: [{ key: 'MT', isDeselectable: true }] + }); + + a.equals(!!map.mapster, true, 'Plugin returns jQuery object'); + a.equals( + map, + $('#usa_image'), + 'Plugin returns jquery same object as invocation' + ); + + // order is not guaranteed - this is the order the areas are created. + var selected = map.mapster('get'); + + // This test should NOT show "WA" because StaticState items are not considered "selected" + + a.collectionEquals( + selected, + 'AK,TX', + "Initially selected items returned with 'get'" + ); + + selected = map.mapster('get', 'TX'); + a.equals( + selected, + true, + "Initially selected single item returned true with 'get'" + ); + selected = map.mapster('get', 'ME'); + a.equals( + selected, + false, + "Initially deselected single item returned false with 'get'" + ); + + // Test setting/getting via area + + // AK was already selected, should be ignored + + attrMatches($('area'), 'state', 'AK,HI,LA').mapster('set', true); + var area_sel = map.mapster('get'); + a.collectionEquals(area_sel, 'HI,AK,LA,TX', 'Set using area works'); + + map.mapster('set', false, 'LA,TX'); + a.collectionEquals( + 'HI,AK', + map.mapster('get'), + 'unset using keys works' + ); + + map.mapster('set', true, 'ME,OH,TX'); + a.collectionEquals( + 'HI,AK,ME,OH,TX', + map.mapster('get'), + 'set using keys works' + ); + + // test toggling: AK should go off, MT should go on + var areas = $('area[state=AK]').first(); + areas = areas.add($('area[state=MT]').first()); + areas.mapster('set'); + a.collectionEquals( + 'HI,ME,OH,TX,MT', + map.mapster('get'), + 'toggling keys works' + ); + + // test clicking + $('area[state="AZ"]').first().click(); + selected = map.mapster('get', 'AZ'); + a.equals(true, selected, "Click-selected area returned 'get'"); + a.collectionEquals( + 'HI,ME,OH,TX,MT,AZ', + map.mapster('get'), + "Complete list returned with 'get'" + ); + + /// try to click select "staticstate areas + + $('area[state="OR"]').first().click(); + selected = map.mapster('get', 'OR'); + a.equals( + selected, + false, + "Cannot select 'staticState=false' area with click" + ); + + selected = map.mapster('get', 'WA'); + a.equals( + selected, + false, + 'staticState=true area is considered not selected' + ); + + opts = map.mapster('get_options', 'WA'); + a.equals( + opts.staticState, + true, + 'get effective options returned correct static state for WA' + ); + + opts = map.mapster('get_options', 'OR'); + a.equals( + opts.staticState, + false, + 'get effective options returned correct static state for OR' + ); + + $('area[state="WA"]').first().click(); + selected = map.mapster('get', 'WA'); + a.equals( + selected, + false, + "Cannot change selection state of 'staticState=true' area with click" + ); + + // do it programatically + + map.mapster('set', true, 'OR'); + selected = map.mapster('get', 'OR'); + a.equals( + selected, + true, + "Can select 'staticState=false' area with 'set'" + ); + + map.mapster('set', false, 'WA'); + a.equals( + map.mapster('get', 'WA'), + false, + "Can deselect staticState=true' area with 'set'" + ); + + // test rebind + newOpts = map.mapster('get_options'); + newOpts.singleSelect = true; + map.mapster('rebind', newOpts); + a.collectionEquals( + map.mapster('get'), + 'TX,AK', + 'Rebind with singleSelect reverted to original state' + ); + + map.mapster('set', true, 'MI'); + a.equals(map.mapster('get'), 'MI', 'Single select worked.'); + + map.mapster('set_options', { isDeselectable: false }); + $('area[state="MI"]').first().click(); + a.equals( + map.mapster('get', 'MI'), + true, + 'Cannot deselect single selected item with isDeselectable=false' + ); + + $('area[state="UT"]').first().click(); + + a.equals(map.mapster('get'), 'UT', 'New single state selected'); + + map.mapster('set_options', { + singleSelect: false, + isDeselectable: true, + areas: [{ key: 'ME', isDeselectable: false }] + }); + + $('area[state="UT"]').first().click(); + a.equals( + map.mapster('get', 'UT'), + false, + 'Was able to deselect item after removing singleSelect' + ); + + map.mapster('set', true, 'CA,HI,ME'); + + $('area[state="ME"]').first().click(); + a.equals( + map.mapster('get', 'ME'), + true, + 'Could not deselect one item marked as !isDeselectable' + ); + $('area[state="CA"]').first().click(); + a.equals( + map.mapster('get', 'CA'), + false, + 'Could deselect other items ' + ); + + // Test manual highlighting + + a.equals(map.mapster('highlight'), null, 'nothing is highlighted'); + + $('area[state="CA"]').first().mapster('highlight'); + + a.equals(map.mapster('highlight'), 'CA', 'highlighted manually'); + + map.mapster('highlight', 'LA'); + + a.equals( + map.mapster('highlight'), + 'LA', + 'highlighted manually using other technique' + ); + + map.mapster('highlight', false); + + a.equals(map.mapster('highlight'), null, 'everything unhighlighted'); + + // restore internal canvas setting or these tests won't work + // if (disableCanvas) { + // map.mapster('test', 'has_canvas=true'); + // } else { + + // // cleanup tests - skip to play with map afterwards + // // return; + + // if (has_canvas) { + // a.equals($('canvas').length, 2, 'There are 2 canvases.'); + // map.mapster(map_options); + // a.equals($('canvas').length, 2, 'There are 2 canvases (recreate was clean)'); + // } + // } + map.mapster('unbind'); + a.equals($('canvas').length, 0, 'No canvases remain after an unbind.'); + + a.equals( + $('#test_elements *').length, + domCount, + '# elements in DOM is the same.' + ); + + // if (disableCanvas) { + // $.mapster.hasCanvas=oldHasCanvas; + // } + }); + }) +); diff --git a/tests/redist/common.utils.1.0.js b/tests/redist/common.utils.1.0.js index a4d6a3f..ef5ed84 100644 --- a/tests/redist/common.utils.1.0.js +++ b/tests/redist/common.utils.1.0.js @@ -1,321 +1,320 @@ -/* common.utils.js: a core framework library of utilities and polyfills. - - This adds utility functions into a namespace u. - - Standard polyfills are automatically added to their prototypes. The following nonstandard prototype - changes are made: - - String.format - String.split with trim option - Array.contains - Array.first - - You can remove the call to u.polyfill to prevent the nonstandard changes. - - Version 1.0 - James Treworgy -*/ - -/*global define, require, module */ -/*jslint curly: false */ -(function (define) { - define(function () { - var u,nativeSplit=String.prototype.split; - - /* General puropose functions */ - - function isBool(obj) { - return typeof obj === 'boolean'; - } - function isString(obj) { - return typeof obj === 'string'; - } - function isUndefined(obj) { - return typeof obj === 'undefined'; - } - function isArray(obj) { - return obj && obj.constructor === Array; - } - - /* prototype extension functions - these must be called with a context */ - - // trim a string leading & trailing whitespace - function stringTrim() { - return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - } - - // generic iterator. when trim is true, string values are trimmed. - function forEach(cb, trim) { - var coll = this, - i, val; - if (isString(coll)) { - coll = coll.split(','); - } - if (isArray(coll)) { - for (i = 0; i < coll.length; i++) { - val = isString(coll[i]) ? - stringTrim.call(coll[i]) : - coll[i]; - - if (cb.call(val, i, val) === false) { - break; - } - } - } else { - for (i in coll) { - if (coll.hasOwnProperty(i)) { - if (cb.call(coll[i], i, coll[i]) === false) { - break; - } - } - } - } - } - // string format function - function format() { - var args = (arguments.length === 1 && $.isArray(arguments[0])) ? - arguments[0] : - arguments; - return this.replace(/\{(\d+)\}/g, function (match, number) { - var num = parseInt(number,10); - return !isUndefined(args[num]) - ? String(args[num]) - : match; - }); - } - - // a split function that trims its results. any 'true' bool parameter will be interpreted as a flag to trim - function stringSplit(delimiter, trimResults) { - var result = [], - delim = isString(delimiter) ? - delimiter : ',', - trim = isBool(delimiter) ? - delimiter : - isBool(trimResults) ? - trimResults : false; - - forEach.call(nativeSplit.call(this,delim || ','), function (i, e) { - result.push(trim ? stringTrim(e) : e); - }); - return result; - } - - // polyfills - - function arrayForEach(action, that) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this) - action.call(that, this[i], i, this); - } - - function arrayIndexOf(find, i /*opt*/) { - if (i === undefined) i = 0; - if (i < 0) i += this.length; - if (i < 0) i = 0; - for (var n = this.length; i < n; i++) - if (i in this && this[i] === find) - return i; - return -1; - } - - // returns true if the element exists - function arrayContains(val) { - return arrayIndexOf.call(this, val) >= 0; - } - - // NONSTANDARD - - // return the first element where filter returns true - function arrayFirst(filter) { - var i,undef; - - if (!filter) { - return this.length>0 ? this[0] : undef; - } - - for (i = 0; i < this.length; i++) { - if (filter.call(this[i], i, this[i])) { - return this[i]; - } - } - return undef; - } - - function arrayLastIndexOf(find, i /*opt*/) { - if (i === undefined) i = this.length - 1; - if (i < 0) i += this.length; - if (i > this.length - 1) i = this.length - 1; - for (i++; i-- > 0; ) /* i++ because from-argument is sadly inclusive */ - if (i in this && this[i] === find) - return i; - return -1; - } - - function arrayMap(mapper, that /*opt*/) { - var n, i, other = new Array(this.length); - for (i = 0, n = this.length; i < n; i++) - if (i in this) - other[i] = mapper.call(that, this[i], i, this); - return other; - } - - function arrayFilter(filter, that /*opt*/) { - var i, n, other = [], v; - for (i = 0, n = this.length; i < n; i++) - if (i in this && filter.call(that, v = this[i], i, this)) - other.push(v); - return other; - } - - function arrayEvery(tester, that /*opt*/) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this && !tester.call(that, this[i], i, this)) - return false; - return true; - } - - function arraySome(tester, that /*opt*/) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this && tester.call(that, this[i], i, this)) - return true; - return false; - } - - u = { - // when onlyInSource is true, properties will not be added - only updated - // passing a falsy value as the target results in a new object being created - // and onlyInTarget is irrelevant - extend: function (target) { - var prop, source, sources, i, - li = arguments.length, - lastBool = u.isBool(arguments[li - 1]), - len = lastBool ? - li - 2 : li - 1, - emptyTarget = !target, - onlyInTarget = lastBool ? - arguments[len + 1] : false; - - target = target || {}; - - sources = u.toArray(arguments, 1, len + 1); - - for (i = 0; i < sources.length; i++) { - source = sources[i]; - for (prop in source) { - if (source.hasOwnProperty(prop) - && (emptyTarget || !onlyInTarget || target.hasOwnProperty(prop))) { - target[prop] = source[prop]; - } - } - // start honoring onlyInTarget after the first source - emptyTarget = false; - } - return target; - }, - // copy selected properties to a new object - filterProps: function (source, what) { - var target = {}, - props = u.isArray(what) ? - what : - what.split(','); - - u.each(props, function (i, prop) { - target[prop] = source[prop]; - }); - return target; - }, - toArray: function (arrLike, first, last) { - return Array.prototype.slice.call(arrLike, first || 0, last || arrLike.length); - }, - isArray: isArray, - arrayIndexOf: function (arr, val) { - return arrayIndexOf.call(arr, val); - }, - inArray: function (arr, val) { - return arrayContains.call(arr, val); - }, - isFunction: function (obj) { - return typeof obj === 'function'; - }, - isUndefined: isUndefined, - isString: isString, - isBool: isBool, - isValueType: function (obj) { - return u.inArray(['boolean', 'number', 'string'], typeof (obj)); - }, - trim: function (str) { - return stringTrim.call(str); - }, - //split with trim (why would you want it any other way?) - split: function (str, delim) { - return stringSplit.call(str, delim); - }, - // replaces {0}.. {n} with the ordinal valued parameter. You can also pass an - // array instead of multiple parameters - format: function () { - return format.apply(arguments[0], u.toArray(arguments, 1)); - }, - // usual each, if you happen to pass a string, it will split it on commas. - // it will always trim string values in an array. - each: function (coll, cb) { - return forEach.call(coll, cb); - }, - donothing: function () { }, - // add nonstandard polyfills - polyfill: function () { - - Array.prototype.contains = arrayContains; - Array.prototype.first = arrayFirst; - - String.prototype.format = format; - - // always replace split, ours is better - if (String.prototype.split !== stringSplit) { - String.prototype.split = stringSplit; - } - } - - }; - - // add required polyfills - - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = arrayIndexOf; - } - if (!Array.prototype.trim) { - Array.prototype.trim = stringTrim; - } - if (!Array.prototype.lastIndexOf) { - Array.prototype.lastIndexOf = arrayLastIndexOf; - } - if (!Array.prototype.forEach) { - Array.prototype.forEach = arrayForEach; - } - if (!Array.prototype.filter) { - Array.prototype.filter = arrayFilter; - } - if (!Array.prototype.every) { - Array.prototype.every = arrayEvery; - } - if (!Array.prototype.some) { - Array.prototype.some = arraySome; - } - if (!Array.prototype.map) { - Array.prototype.map = arrayMap; - } - u.polyfill(); - - return u; - }); -} (typeof define === 'function' - ? define - : function (factory) { - if (typeof module !== 'undefined') { - module.exports = factory(); - } else { - this.common = this.common || {}; - this.common.utils = factory(); - } - } -// Boilerplate for AMD, Node, and browser global +/* common.utils.js: a core framework library of utilities and polyfills. + + This adds utility functions into a namespace u. + + Standard polyfills are automatically added to their prototypes. The following nonstandard prototype + changes are made: + + String.format + String.split with trim option + Array.contains + Array.first + + You can remove the call to u.polyfill to prevent the nonstandard changes. + + Version 1.0 + James Treworgy +*/ + +/* global define, require, module */ +(function (define) { + define(function () { + var u,nativeSplit=String.prototype.split; + + /* General puropose functions */ + + function isBool(obj) { + return typeof obj === 'boolean'; + } + function isString(obj) { + return typeof obj === 'string'; + } + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + function isArray(obj) { + return obj && obj.constructor === Array; + } + + /* prototype extension functions - these must be called with a context */ + + // trim a string leading & trailing whitespace + function stringTrim() { + return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + } + + // generic iterator. when trim is true, string values are trimmed. + function forEach(cb, trim) { + var coll = this, + i, val; + if (isString(coll)) { + coll = coll.split(','); + } + if (isArray(coll)) { + for (i = 0; i < coll.length; i++) { + val = isString(coll[i]) ? + stringTrim.call(coll[i]) : + coll[i]; + + if (cb.call(val, i, val) === false) { + break; + } + } + } else { + for (i in coll) { + if (coll.hasOwnProperty(i)) { + if (cb.call(coll[i], i, coll[i]) === false) { + break; + } + } + } + } + } + // string format function + function format() { + var args = (arguments.length === 1 && $.isArray(arguments[0])) ? + arguments[0] : + arguments; + return this.replace(/\{(\d+)\}/g, function (match, number) { + var num = parseInt(number,10); + return !isUndefined(args[num]) + ? String(args[num]) + : match; + }); + } + + // a split function that trims its results. any 'true' bool parameter will be interpreted as a flag to trim + function stringSplit(delimiter, trimResults) { + var result = [], + delim = isString(delimiter) ? + delimiter : ',', + trim = isBool(delimiter) ? + delimiter : + isBool(trimResults) ? + trimResults : false; + + forEach.call(nativeSplit.call(this,delim || ','), function (i, e) { + result.push(trim ? stringTrim(e) : e); + }); + return result; + } + + // polyfills + + function arrayForEach(action, that) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this) + action.call(that, this[i], i, this); + } + + function arrayIndexOf(find, i /*opt*/) { + if (i === undefined) i = 0; + if (i < 0) i += this.length; + if (i < 0) i = 0; + for (var n = this.length; i < n; i++) + if (i in this && this[i] === find) + return i; + return -1; + } + + // returns true if the element exists + function arrayContains(val) { + return arrayIndexOf.call(this, val) >= 0; + } + + // NONSTANDARD + + // return the first element where filter returns true + function arrayFirst(filter) { + var i,undef; + + if (!filter) { + return this.length>0 ? this[0] : undef; + } + + for (i = 0; i < this.length; i++) { + if (filter.call(this[i], i, this[i])) { + return this[i]; + } + } + return undef; + } + + function arrayLastIndexOf(find, i /*opt*/) { + if (i === undefined) i = this.length - 1; + if (i < 0) i += this.length; + if (i > this.length - 1) i = this.length - 1; + for (i++; i-- > 0; ) /* i++ because from-argument is sadly inclusive */ + if (i in this && this[i] === find) + return i; + return -1; + } + + function arrayMap(mapper, that /*opt*/) { + var n, i, other = new Array(this.length); + for (i = 0, n = this.length; i < n; i++) + if (i in this) + other[i] = mapper.call(that, this[i], i, this); + return other; + } + + function arrayFilter(filter, that /*opt*/) { + var i, n, other = [], v; + for (i = 0, n = this.length; i < n; i++) + if (i in this && filter.call(that, v = this[i], i, this)) + other.push(v); + return other; + } + + function arrayEvery(tester, that /*opt*/) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this && !tester.call(that, this[i], i, this)) + return false; + return true; + } + + function arraySome(tester, that /*opt*/) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this && tester.call(that, this[i], i, this)) + return true; + return false; + } + + u = { + // when onlyInSource is true, properties will not be added - only updated + // passing a falsy value as the target results in a new object being created + // and onlyInTarget is irrelevant + extend: function (target) { + var prop, source, sources, i, + li = arguments.length, + lastBool = u.isBool(arguments[li - 1]), + len = lastBool ? + li - 2 : li - 1, + emptyTarget = !target, + onlyInTarget = lastBool ? + arguments[len + 1] : false; + + target = target || {}; + + sources = u.toArray(arguments, 1, len + 1); + + for (i = 0; i < sources.length; i++) { + source = sources[i]; + for (prop in source) { + if (source.hasOwnProperty(prop) + && (emptyTarget || !onlyInTarget || target.hasOwnProperty(prop))) { + target[prop] = source[prop]; + } + } + // start honoring onlyInTarget after the first source + emptyTarget = false; + } + return target; + }, + // copy selected properties to a new object + filterProps: function (source, what) { + var target = {}, + props = u.isArray(what) ? + what : + what.split(','); + + u.each(props, function (i, prop) { + target[prop] = source[prop]; + }); + return target; + }, + toArray: function (arrLike, first, last) { + return Array.prototype.slice.call(arrLike, first || 0, last || arrLike.length); + }, + isArray: isArray, + arrayIndexOf: function (arr, val) { + return arrayIndexOf.call(arr, val); + }, + inArray: function (arr, val) { + return arrayContains.call(arr, val); + }, + isFunction: function (obj) { + return typeof obj === 'function'; + }, + isUndefined: isUndefined, + isString: isString, + isBool: isBool, + isValueType: function (obj) { + return u.inArray(['boolean', 'number', 'string'], typeof (obj)); + }, + trim: function (str) { + return stringTrim.call(str); + }, + //split with trim (why would you want it any other way?) + split: function (str, delim) { + return stringSplit.call(str, delim); + }, + // replaces {0}.. {n} with the ordinal valued parameter. You can also pass an + // array instead of multiple parameters + format: function () { + return format.apply(arguments[0], u.toArray(arguments, 1)); + }, + // usual each, if you happen to pass a string, it will split it on commas. + // it will always trim string values in an array. + each: function (coll, cb) { + return forEach.call(coll, cb); + }, + donothing: function () { }, + // add nonstandard polyfills + polyfill: function () { + + Array.prototype.contains = arrayContains; + Array.prototype.first = arrayFirst; + + String.prototype.format = format; + + // always replace split, ours is better + if (String.prototype.split !== stringSplit) { + String.prototype.split = stringSplit; + } + } + + }; + + // add required polyfills + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = arrayIndexOf; + } + if (!Array.prototype.trim) { + Array.prototype.trim = stringTrim; + } + if (!Array.prototype.lastIndexOf) { + Array.prototype.lastIndexOf = arrayLastIndexOf; + } + if (!Array.prototype.forEach) { + Array.prototype.forEach = arrayForEach; + } + if (!Array.prototype.filter) { + Array.prototype.filter = arrayFilter; + } + if (!Array.prototype.every) { + Array.prototype.every = arrayEvery; + } + if (!Array.prototype.some) { + Array.prototype.some = arraySome; + } + if (!Array.prototype.map) { + Array.prototype.map = arrayMap; + } + u.polyfill(); + + return u; + }); +} (typeof define === 'function' + ? define + : function (factory) { + if (typeof module !== 'undefined') { + module.exports = factory(); + } else { + this.common = this.common || {}; + this.common.utils = factory(); + } + } +// Boilerplate for AMD, Node, and browser global )); \ No newline at end of file diff --git a/tests/redist/excanvas.js b/tests/redist/excanvas.js index f693207..308b1be 100644 --- a/tests/redist/excanvas.js +++ b/tests/redist/excanvas.js @@ -1,35 +1,35 @@ -document.createElement("canvas").getContext||function(){function Z(){return this.context_||(this.context_=new C(this))}function $(a,b){var c=P.call(arguments,2);return function(){return a.apply(b,c.concat(P.call(arguments)))}}function Q(a){return String(a).replace(/&/g,"&").replace(/"/g,""")}function R(a,b,c){a.namespaces[b]||a.namespaces.add(b,c,"#default#VML")}function S(a){R(a,"g_vml_","urn:schemas-microsoft-com:vml");R(a,"g_o_","urn:schemas-microsoft-com:office:office");if(!a.styleSheets.ex_canvas_){a= -a.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}function aa(a){var b=a.srcElement;switch(a.propertyName){case "width":b.getContext().clearRect();b.style.width=b.attributes.width.nodeValue+"px";b.firstChild.style.width=b.clientWidth+"px";break;case "height":b.getContext().clearRect();b.style.height=b.attributes.height.nodeValue+"px";b.firstChild.style.height=b.clientHeight+"px";break}}function ba(a){a= -a.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}function D(){return[[1,0,0],[0,1,0],[0,0,1]]}function u(a,b){for(var c=D(),d=0;d<3;d++)for(var e=0;e<3;e++){for(var f=0,h=0;h<3;h++)f+=a[d][h]*b[h][e];c[d][e]=f}return c}function T(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX; -b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.globalAlpha=a.globalAlpha;b.font=a.font;b.textAlign=a.textAlign;b.textBaseline=a.textBaseline;b.arcScaleX_=a.arcScaleX_;b.arcScaleY_=a.arcScaleY_;b.lineScale_=a.lineScale_}function U(a){var b=a.indexOf("(",3),c=a.indexOf(")",b+1);b=a.substring(b+1,c).split(",");if(b.length!=4||a.charAt(3)!="a")b[3]=1;return b}function E(a){return parseFloat(a)/100}function F(a,b,c){return Math.min(c,Math.max(b,a))}function ca(a){var b,c;c=parseFloat(a[0])/ -360%360;c<0&&c++;b=F(E(a[1]),0,1);a=F(E(a[2]),0,1);if(b==0)b=a=c=a;else{var d=a<0.5?a*(1+b):a+b-a*b,e=2*a-d;b=G(e,d,c+1/3);a=G(e,d,c);c=G(e,d,c-1/3)}return"#"+v[Math.floor(b*255)]+v[Math.floor(a*255)]+v[Math.floor(c*255)]}function G(a,b,c){c<0&&c++;c>1&&c--;return 6*c<1?a+(b-a)*6*c:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a}function H(a){if(a in I)return I[a];var b,c=1;a=String(a);if(a.charAt(0)=="#")b=a;else if(/^rgb/.test(a)){c=U(a);b="#";for(var d,e=0;e<3;e++){d=c[e].indexOf("%")!=-1?Math.floor(E(c[e])* -255):+c[e];b+=v[F(d,0,255)]}c=+c[3]}else if(/^hsl/.test(a)){c=U(a);b=ca(c);c=c[3]}else b=da[a]||a;return I[a]={color:b,alpha:c}}function ea(a){if(J[a])return J[a];var b=document.createElement("div").style;try{b.font=a}catch(c){}return J[a]={style:b.fontStyle||w.style,variant:b.fontVariant||w.variant,weight:b.fontWeight||w.weight,size:b.fontSize||w.size,family:b.fontFamily||w.family}}function fa(a,b){var c={};for(var d in a)c[d]=a[d];b=parseFloat(b.currentStyle.fontSize);d=parseFloat(a.size);c.size= -typeof a.size=="number"?a.size:a.size.indexOf("px")!=-1?d:a.size.indexOf("em")!=-1?b*d:a.size.indexOf("%")!=-1?b/100*d:a.size.indexOf("pt")!=-1?d/0.75:b;c.size*=0.981;return c}function ga(a){return a.style+" "+a.variant+" "+a.weight+" "+a.size+"px "+a.family}function ha(a){return ia[a]||"square"}function C(a){this.m_=D();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=l*1;this.globalAlpha= -1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=a;var b="width:"+a.clientWidth+"px;height:"+a.clientHeight+"px;overflow:hidden;position:absolute",c=a.ownerDocument.createElement("div");c.style.cssText=b;a.appendChild(c);b=c.cloneNode(false);b.style.backgroundColor="red";b.style.filter="alpha(opacity=0)";a.appendChild(b);this.element_=c;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}function V(a,b,c,d){a.currentPath_.push({type:"bezierCurveTo",cp1x:b.x, -cp1y:b.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});a.currentX_=d.x;a.currentY_=d.y}function W(a,b){var c=H(a.strokeStyle),d=c.color;c=c.alpha*a.globalAlpha;var e=a.lineScale_*a.lineWidth;if(e<1)c*=e;b.push("')}function X(a,b,c,d){var e=a.fillStyle,f=a.arcScaleX_,h=a.arcScaleY_,j=d.x-c.x,m=d.y-c.y;if(e instanceof x){var k=0;d={x:0,y:0};var q=0, -o=1;if(e.type_=="gradient"){k=e.x1_/f;c=e.y1_/h;var n=p(a,e.x0_/f,e.y0_/h);k=p(a,k,c);k=Math.atan2(k.x-n.x,k.y-n.y)*180/Math.PI;if(k<0)k+=360;if(k<1.0E-6)k=0}else{n=p(a,e.x0_,e.y0_);d={x:(n.x-c.x)/j,y:(n.y-c.y)/m};j/=f*l;m/=h*l;o=r.max(j,m);q=2*e.r0_/o;o=2*e.r1_/o-q}f=e.colors_;f.sort(function(A,ja){return A.offset-ja.offset});h=f.length;n=f[0].color;c=f[h-1].color;j=f[0].alpha*a.globalAlpha;a=f[h-1].alpha*a.globalAlpha;m=[];for(var s=0;s')}else if(e instanceof K)j&&m&&b.push("');else{e=H(a.fillStyle);b.push('')}}function p(a,b,c){a=a.m_;return{x:l*(b*a[0][0]+c*a[1][0]+ -a[2][0])-t,y:l*(b*a[0][1]+c*a[1][1]+a[2][1])-t}}function ka(a){return isFinite(a[0][0])&&isFinite(a[0][1])&&isFinite(a[1][0])&&isFinite(a[1][1])&&isFinite(a[2][0])&&isFinite(a[2][1])}function z(a,b,c){if(ka(b)){a.m_=b;if(c)a.lineScale_=la(ma(b[0][0]*b[1][1]-b[0][1]*b[1][0]))}}function x(a){this.type_=a;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}function K(a,b){na(a);switch(b){case "repeat":case null:case "":this.repetition_="repeat";break;case "repeat-x":case "repeat-y":case "no-repeat":this.repetition_= -b;break;default:L("SYNTAX_ERR")}this.src_=a.src;this.width_=a.width;this.height_=a.height}function L(a){throw new M(a);}function na(a){if(!a||a.nodeType!=1||a.tagName!="IMG")L("TYPE_MISMATCH_ERR");a.readyState!="complete"&&L("INVALID_STATE_ERR")}function M(a){this.code=this[a];this.message=a+": DOM Exception "+this.code}var r=Math,i=r.round,N=r.sin,O=r.cos,ma=r.abs,la=r.sqrt,l=10,t=l/2;navigator.userAgent.match(/MSIE ([\d.]+)?/);var P=Array.prototype.slice;S(document);var Y={init:function(a){a=a|| -document;a.createElement("canvas");a.attachEvent("onreadystatechange",$(this.init_,this,a))},init_:function(a){a=a.getElementsByTagName("canvas");for(var b=0;b','","");this.element_.insertAdjacentHTML("BeforeEnd",n.join(""))};g.stroke=function(a){var b=[];b.push("d.x)d.x=f.x;if(c.y==null||f.yd.y)d.y=f.y}}b.push(' ">');a?X(this,b,c,d):W(this,b);b.push("");this.element_.insertAdjacentHTML("beforeEnd",b.join(""))};g.fill=function(){this.stroke(true)};g.closePath=function(){this.currentPath_.push({type:"close"})};g.save=function(){var a={};T(this,a);this.aStack_.push(a);this.mStack_.push(this.m_);this.m_=u(D(),this.m_)};g.restore=function(){if(this.aStack_.length){T(this.aStack_.pop(), -this);this.m_=this.mStack_.pop()}};g.translate=function(a,b){z(this,u([[1,0,0],[0,1,0],[a,b,1]],this.m_),false)};g.rotate=function(a){var b=O(a);a=N(a);z(this,u([[b,a,0],[-a,b,0],[0,0,1]],this.m_),false)};g.scale=function(a,b){this.arcScaleX_*=a;this.arcScaleY_*=b;z(this,u([[a,0,0],[0,b,0],[0,0,1]],this.m_),true)};g.transform=function(a,b,c,d,e,f){z(this,u([[a,b,0],[c,d,0],[e,f,1]],this.m_),true)};g.setTransform=function(a,b,c,d,e,f){z(this,[[a,b,0],[c,d,0],[e,f,1]],true)};g.drawText_=function(a, -b,c,d,e){var f=this.m_;d=0;var h=1E3,j={x:0,y:0},m=[],k=fa(ea(this.font),this.element_),q=ga(k),o=this.element_.currentStyle,n=this.textAlign.toLowerCase();switch(n){case "left":case "center":case "right":break;case "end":n=o.direction=="ltr"?"right":"left";break;case "start":n=o.direction=="rtl"?"right":"left";break;default:n="left"}switch(this.textBaseline){case "hanging":case "top":j.y=k.size/1.75;break;case "middle":break;default:case null:case "alphabetic":case "ideographic":case "bottom":j.y= --k.size/2.25;break}switch(n){case "right":d=1E3;h=0.05;break;case "center":d=h=500;break}b=p(this,b+j.x,c+j.y);m.push('');e?W(this,m):X(this,m,{x:-d,y:0},{x:h,y:k.size});e=f[0][0].toFixed(3)+","+f[1][0].toFixed(3)+","+f[0][1].toFixed(3)+","+f[1][1].toFixed(3)+",0,0";b=i(b.x/l)+","+i(b.y/l);m.push('','','');this.element_.insertAdjacentHTML("beforeEnd",m.join(""))};g.fillText=function(a,b,c,d){this.drawText_(a,b,c,d,false)};g.strokeText=function(a,b,c,d){this.drawText_(a,b,c,d,true)};g.measureText=function(a){if(!this.textMeasureEl_){this.element_.insertAdjacentHTML("beforeEnd",''); -this.textMeasureEl_=this.element_.lastChild}var b=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(b.createTextNode(a));return{width:this.textMeasureEl_.offsetWidth}};g.clip=function(){};g.arcTo=function(){};g.createPattern=function(a,b){return new K(a,b)};x.prototype.addColorStop=function(a,b){b=H(b);this.colors_.push({offset:a,color:b.color,alpha:b.alpha})};g=M.prototype=new Error;g.INDEX_SIZE_ERR=1;g.DOMSTRING_SIZE_ERR= -2;g.HIERARCHY_REQUEST_ERR=3;g.WRONG_DOCUMENT_ERR=4;g.INVALID_CHARACTER_ERR=5;g.NO_DATA_ALLOWED_ERR=6;g.NO_MODIFICATION_ALLOWED_ERR=7;g.NOT_FOUND_ERR=8;g.NOT_SUPPORTED_ERR=9;g.INUSE_ATTRIBUTE_ERR=10;g.INVALID_STATE_ERR=11;g.SYNTAX_ERR=12;g.INVALID_MODIFICATION_ERR=13;g.NAMESPACE_ERR=14;g.INVALID_ACCESS_ERR=15;g.VALIDATION_ERR=16;g.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=Y;CanvasRenderingContext2D=C;CanvasGradient=x;CanvasPattern=K;DOMException=M}(); +document.createElement("canvas").getContext||function(){function Z(){return this.context_||(this.context_=new C(this))}function $(a,b){var c=P.call(arguments,2);return function(){return a.apply(b,c.concat(P.call(arguments)))}}function Q(a){return String(a).replace(/&/g,"&").replace(/"/g,""")}function R(a,b,c){a.namespaces[b]||a.namespaces.add(b,c,"#default#VML")}function S(a){R(a,"g_vml_","urn:schemas-microsoft-com:vml");R(a,"g_o_","urn:schemas-microsoft-com:office:office");if(!a.styleSheets.ex_canvas_){a= +a.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}function aa(a){var b=a.srcElement;switch(a.propertyName){case "width":b.getContext().clearRect();b.style.width=b.attributes.width.nodeValue+"px";b.firstChild.style.width=b.clientWidth+"px";break;case "height":b.getContext().clearRect();b.style.height=b.attributes.height.nodeValue+"px";b.firstChild.style.height=b.clientHeight+"px";break}}function ba(a){a= +a.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}function D(){return[[1,0,0],[0,1,0],[0,0,1]]}function u(a,b){for(var c=D(),d=0;d<3;d++)for(var e=0;e<3;e++){for(var f=0,h=0;h<3;h++)f+=a[d][h]*b[h][e];c[d][e]=f}return c}function T(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX; +b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.globalAlpha=a.globalAlpha;b.font=a.font;b.textAlign=a.textAlign;b.textBaseline=a.textBaseline;b.arcScaleX_=a.arcScaleX_;b.arcScaleY_=a.arcScaleY_;b.lineScale_=a.lineScale_}function U(a){var b=a.indexOf("(",3),c=a.indexOf(")",b+1);b=a.substring(b+1,c).split(",");if(b.length!=4||a.charAt(3)!="a")b[3]=1;return b}function E(a){return parseFloat(a)/100}function F(a,b,c){return Math.min(c,Math.max(b,a))}function ca(a){var b,c;c=parseFloat(a[0])/ +360%360;c<0&&c++;b=F(E(a[1]),0,1);a=F(E(a[2]),0,1);if(b==0)b=a=c=a;else{var d=a<0.5?a*(1+b):a+b-a*b,e=2*a-d;b=G(e,d,c+1/3);a=G(e,d,c);c=G(e,d,c-1/3)}return"#"+v[Math.floor(b*255)]+v[Math.floor(a*255)]+v[Math.floor(c*255)]}function G(a,b,c){c<0&&c++;c>1&&c--;return 6*c<1?a+(b-a)*6*c:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a}function H(a){if(a in I)return I[a];var b,c=1;a=String(a);if(a.charAt(0)=="#")b=a;else if(/^rgb/.test(a)){c=U(a);b="#";for(var d,e=0;e<3;e++){d=c[e].indexOf("%")!=-1?Math.floor(E(c[e])* +255):+c[e];b+=v[F(d,0,255)]}c=+c[3]}else if(/^hsl/.test(a)){c=U(a);b=ca(c);c=c[3]}else b=da[a]||a;return I[a]={color:b,alpha:c}}function ea(a){if(J[a])return J[a];var b=document.createElement("div").style;try{b.font=a}catch(c){}return J[a]={style:b.fontStyle||w.style,variant:b.fontVariant||w.variant,weight:b.fontWeight||w.weight,size:b.fontSize||w.size,family:b.fontFamily||w.family}}function fa(a,b){var c={};for(var d in a)c[d]=a[d];b=parseFloat(b.currentStyle.fontSize);d=parseFloat(a.size);c.size= +typeof a.size=="number"?a.size:a.size.indexOf("px")!=-1?d:a.size.indexOf("em")!=-1?b*d:a.size.indexOf("%")!=-1?b/100*d:a.size.indexOf("pt")!=-1?d/0.75:b;c.size*=0.981;return c}function ga(a){return a.style+" "+a.variant+" "+a.weight+" "+a.size+"px "+a.family}function ha(a){return ia[a]||"square"}function C(a){this.m_=D();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=l*1;this.globalAlpha= +1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=a;var b="width:"+a.clientWidth+"px;height:"+a.clientHeight+"px;overflow:hidden;position:absolute",c=a.ownerDocument.createElement("div");c.style.cssText=b;a.appendChild(c);b=c.cloneNode(false);b.style.backgroundColor="red";b.style.filter="alpha(opacity=0)";a.appendChild(b);this.element_=c;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}function V(a,b,c,d){a.currentPath_.push({type:"bezierCurveTo",cp1x:b.x, +cp1y:b.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});a.currentX_=d.x;a.currentY_=d.y}function W(a,b){var c=H(a.strokeStyle),d=c.color;c=c.alpha*a.globalAlpha;var e=a.lineScale_*a.lineWidth;if(e<1)c*=e;b.push("')}function X(a,b,c,d){var e=a.fillStyle,f=a.arcScaleX_,h=a.arcScaleY_,j=d.x-c.x,m=d.y-c.y;if(e instanceof x){var k=0;d={x:0,y:0};var q=0, +o=1;if(e.type_=="gradient"){k=e.x1_/f;c=e.y1_/h;var n=p(a,e.x0_/f,e.y0_/h);k=p(a,k,c);k=Math.atan2(k.x-n.x,k.y-n.y)*180/Math.PI;if(k<0)k+=360;if(k<1.0E-6)k=0}else{n=p(a,e.x0_,e.y0_);d={x:(n.x-c.x)/j,y:(n.y-c.y)/m};j/=f*l;m/=h*l;o=r.max(j,m);q=2*e.r0_/o;o=2*e.r1_/o-q}f=e.colors_;f.sort(function(A,ja){return A.offset-ja.offset});h=f.length;n=f[0].color;c=f[h-1].color;j=f[0].alpha*a.globalAlpha;a=f[h-1].alpha*a.globalAlpha;m=[];for(var s=0;s')}else if(e instanceof K)j&&m&&b.push("');else{e=H(a.fillStyle);b.push('')}}function p(a,b,c){a=a.m_;return{x:l*(b*a[0][0]+c*a[1][0]+ +a[2][0])-t,y:l*(b*a[0][1]+c*a[1][1]+a[2][1])-t}}function ka(a){return isFinite(a[0][0])&&isFinite(a[0][1])&&isFinite(a[1][0])&&isFinite(a[1][1])&&isFinite(a[2][0])&&isFinite(a[2][1])}function z(a,b,c){if(ka(b)){a.m_=b;if(c)a.lineScale_=la(ma(b[0][0]*b[1][1]-b[0][1]*b[1][0]))}}function x(a){this.type_=a;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}function K(a,b){na(a);switch(b){case "repeat":case null:case "":this.repetition_="repeat";break;case "repeat-x":case "repeat-y":case "no-repeat":this.repetition_= +b;break;default:L("SYNTAX_ERR")}this.src_=a.src;this.width_=a.width;this.height_=a.height}function L(a){throw new M(a);}function na(a){if(!a||a.nodeType!=1||a.tagName!="IMG")L("TYPE_MISMATCH_ERR");a.readyState!="complete"&&L("INVALID_STATE_ERR")}function M(a){this.code=this[a];this.message=a+": DOM Exception "+this.code}var r=Math,i=r.round,N=r.sin,O=r.cos,ma=r.abs,la=r.sqrt,l=10,t=l/2;navigator.userAgent.match(/MSIE ([\d.]+)?/);var P=Array.prototype.slice;S(document);var Y={init:function(a){a=a|| +document;a.createElement("canvas");a.attachEvent("onreadystatechange",$(this.init_,this,a))},init_:function(a){a=a.getElementsByTagName("canvas");for(var b=0;b','","");this.element_.insertAdjacentHTML("BeforeEnd",n.join(""))};g.stroke=function(a){var b=[];b.push("d.x)d.x=f.x;if(c.y==null||f.yd.y)d.y=f.y}}b.push(' ">');a?X(this,b,c,d):W(this,b);b.push("");this.element_.insertAdjacentHTML("beforeEnd",b.join(""))};g.fill=function(){this.stroke(true)};g.closePath=function(){this.currentPath_.push({type:"close"})};g.save=function(){var a={};T(this,a);this.aStack_.push(a);this.mStack_.push(this.m_);this.m_=u(D(),this.m_)};g.restore=function(){if(this.aStack_.length){T(this.aStack_.pop(), +this);this.m_=this.mStack_.pop()}};g.translate=function(a,b){z(this,u([[1,0,0],[0,1,0],[a,b,1]],this.m_),false)};g.rotate=function(a){var b=O(a);a=N(a);z(this,u([[b,a,0],[-a,b,0],[0,0,1]],this.m_),false)};g.scale=function(a,b){this.arcScaleX_*=a;this.arcScaleY_*=b;z(this,u([[a,0,0],[0,b,0],[0,0,1]],this.m_),true)};g.transform=function(a,b,c,d,e,f){z(this,u([[a,b,0],[c,d,0],[e,f,1]],this.m_),true)};g.setTransform=function(a,b,c,d,e,f){z(this,[[a,b,0],[c,d,0],[e,f,1]],true)};g.drawText_=function(a, +b,c,d,e){var f=this.m_;d=0;var h=1E3,j={x:0,y:0},m=[],k=fa(ea(this.font),this.element_),q=ga(k),o=this.element_.currentStyle,n=this.textAlign.toLowerCase();switch(n){case "left":case "center":case "right":break;case "end":n=o.direction=="ltr"?"right":"left";break;case "start":n=o.direction=="rtl"?"right":"left";break;default:n="left"}switch(this.textBaseline){case "hanging":case "top":j.y=k.size/1.75;break;case "middle":break;default:case null:case "alphabetic":case "ideographic":case "bottom":j.y= +-k.size/2.25;break}switch(n){case "right":d=1E3;h=0.05;break;case "center":d=h=500;break}b=p(this,b+j.x,c+j.y);m.push('');e?W(this,m):X(this,m,{x:-d,y:0},{x:h,y:k.size});e=f[0][0].toFixed(3)+","+f[1][0].toFixed(3)+","+f[0][1].toFixed(3)+","+f[1][1].toFixed(3)+",0,0";b=i(b.x/l)+","+i(b.y/l);m.push('','','');this.element_.insertAdjacentHTML("beforeEnd",m.join(""))};g.fillText=function(a,b,c,d){this.drawText_(a,b,c,d,false)};g.strokeText=function(a,b,c,d){this.drawText_(a,b,c,d,true)};g.measureText=function(a){if(!this.textMeasureEl_){this.element_.insertAdjacentHTML("beforeEnd",''); +this.textMeasureEl_=this.element_.lastChild}var b=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(b.createTextNode(a));return{width:this.textMeasureEl_.offsetWidth}};g.clip=function(){};g.arcTo=function(){};g.createPattern=function(a,b){return new K(a,b)};x.prototype.addColorStop=function(a,b){b=H(b);this.colors_.push({offset:a,color:b.color,alpha:b.alpha})};g=M.prototype=new Error;g.INDEX_SIZE_ERR=1;g.DOMSTRING_SIZE_ERR= +2;g.HIERARCHY_REQUEST_ERR=3;g.WRONG_DOCUMENT_ERR=4;g.INVALID_CHARACTER_ERR=5;g.NO_DATA_ALLOWED_ERR=6;g.NO_MODIFICATION_ALLOWED_ERR=7;g.NOT_FOUND_ERR=8;g.NOT_SUPPORTED_ERR=9;g.INUSE_ATTRIBUTE_ERR=10;g.INVALID_STATE_ERR=11;g.SYNTAX_ERR=12;g.INVALID_MODIFICATION_ERR=13;g.NAMESPACE_ERR=14;g.INVALID_ACCESS_ERR=15;g.VALIDATION_ERR=16;g.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=Y;CanvasRenderingContext2D=C;CanvasGradient=x;CanvasPattern=K;DOMException=M}(); diff --git a/tests/redist/iqtest-browser-default.js b/tests/redist/iqtest-browser-default.js index 2f474ef..0b8d268 100644 --- a/tests/redist/iqtest-browser-default.js +++ b/tests/redist/iqtest-browser-default.js @@ -1,3696 +1,3689 @@ -/** NOTE: MODIFIED - must not catch errors for use in IQTest */ -/*lint-ignore-file*/ - -/** @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.0.2 - */ - -(function(define) { -define(function() { - var freeze, reduceArray, undef; - - /** - * No-Op function used in method replacement - * @private - */ - function noop() {} - - /** - * Allocate a new Array of size n - * @private - * @param n {number} size of new Array - * @returns {Array} - */ - function allocateArray(n) { - return new Array(n); - } - - /** - * Use freeze if it exists - * @function - * @private - */ - freeze = Object.freeze || function(o) { return o; }; - - // 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; - }; - - /** - * Trusted Promise constructor. A Promise created from this constructor is - * a trusted when.js promise. Any other duck-typed promise is considered - * untrusted. - */ - function Promise() {} - - /** - * 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) { - checkCallbacks(arguments); - - var nextValue; - if (!when.debug) { - try { - nextValue = callback && callback(value); - return promise(nextValue === undef ? value : nextValue); - } catch(e) { - return rejected(e); - } - } else { - nextValue = callback && callback(value); - return promise(nextValue === undef ? value : nextValue); - } - }; - - // Not frozen because this should never be exposed - // to callers - return 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) { - checkCallbacks(arguments); - - var nextValue; - if (!when.debug) { - try { - if(errback) { - nextValue = errback(reason); - return promise(nextValue === undef ? reason : nextValue) - } - - return rejected(reason); - - } catch(e) { - return rejected(e); - } - } else { - if(errback) { - nextValue = errback(reason); - return promise(nextValue === undef ? reason : nextValue) - } - - return rejected(reason); - - } - }; - - // Not frozen because this should never be exposed - // to callers - return p; - } - - /** - * Helper that checks arrayOfCallbacks to ensure that each element is either - * a function, or null or undefined. - * - * @param arrayOfCallbacks {Array} array to check - * @throws {Error} if any element of arrayOfCallbacks is something other than - * a Functions, null, or undefined. - */ - function checkCallbacks(arrayOfCallbacks) { - var arg, i = arrayOfCallbacks.length; - while(i) { - arg = arrayOfCallbacks[--i]; - if (arg != null && typeof arg != 'function') throw new Error('callback is not a function'); - } - } - - /** - * 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) { - // Check parameters and fail immediately if any supplied parameter - // is not null/undefined and is also not a function. - // That is, any non-null/undefined parameter must be a function. - checkCallbacks(arguments); - - 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 - * @augments Resolver - * @augments Promise - */ - deferred = {}; - - // Promise and Resolver parts - // Freeze Promise and Resolver APIs - - /** - * The Promise API - * @namespace Promise - * @name Promise - */ - 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} - * @namespace Resolver - * @name Resolver - * @memberOf Deferred - * @name resolver - * @type {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) { - 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} 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) { - var results, promise; - - results = allocateArray(promisesOrValues.length); - promise = reduce(promisesOrValues, reduceIntoArray, results); - - return when(promise, 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} 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(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 promisesOrValues {Array} 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(promisesOrValues, mapFunc) { - - var results, i; - - // Since we know the resulting length, we can preallocate the results - // array to avoid array expansions. - i = promisesOrValues.length; - results = allocateArray(i); - - // 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) { - 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, but reduceFunc - * may return either a value or a {@link Promise}, *and* initialValue may - * be a {@link Promise} for the starting value. - * - * @memberOf when - * - * @param promisesOrValues {Array} array of anything, may contain a mix - * of {@link Promise}s and values - * @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(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 >= 3) args.push(initialValue); - - return promise(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) { - resolver.resolve(useResolveValue ? resolveValue : val); - }, - resolver.reject, - resolver.progress - ); - } - - // - // Public API - // - - when.defer = defer; - - when.isPromise = isPromise; - when.some = some; - when.all = all; - when.any = any; - - when.reduce = reduce; - when.map = map; - - when.chain = chain; - - // when true, will not trap errors - - when.debug = false; - return when; -}); -})(typeof define == 'function' - ? define - : function (factory) { typeof module != 'undefined' - ? (module.exports = factory()) - : (this.when = factory()); - } - // Boilerplate for AMD, Node, and browser global -);/** @license MIT License (c) copyright B Cavalier & J Hann */ - -/** - * timeout.js - * - * Helper that returns a promise that rejects after a specified timeout, - * if not explicitly resolved or rejected before that. - * - * @author brian@hovercraftstudios.com - */ - -(function(define) { -define(['./when'], function(when) { - - var undef; - - /** - * Returns a new promise that will automatically reject after msec if - * the supplied promise doesn't resolve or reject before that. - * - * Usage: - * - * var d = when.defer(); - * // Setup d however you need - * - * // return a new promise that will timeout if we don't resolve/reject first - * return timeout(d, 1000); - * - * @param promise anything - any promise or value that should trigger - * the returned promise to resolve or reject before the msec timeout - * @param msec {Number} timeout in milliseconds - * - * @returns {Promise} - */ - return function timeout(promise, msec) { - var deferred, timeout; - - deferred = when.defer(); - - timeout = setTimeout(function onTimeout() { - timeout && deferred.reject(new Error('timed out')); - }, msec); - - function cancelTimeout() { - clearTimeout(timeout); - timeout = undef; - } - - when(promise, deferred.resolve, deferred.reject); - - return deferred.then( - function(value) { - cancelTimeout(); - return value; - }, - function(reason) { - cancelTimeout(); - throw reason; - } - ); - }; - -}); -})(typeof define == 'function' - ? define - : function (deps, factory) { typeof module != 'undefined' - ? (module.exports = factory(require('./when'))) - : (this.when_timeout = factory(this.when)); - } - // Boilerplate for AMD, Node, and browser global -); - -var buster = (function (setTimeout, B) { - var isNode = typeof require == "function" && typeof module == "object"; - var div = typeof document != "undefined" && document.createElement("div"); - var F = function () {}; - - var buster = { - bind: function bind(obj, methOrProp) { - var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; - var args = Array.prototype.slice.call(arguments, 2); - return function () { - var allArgs = args.concat(Array.prototype.slice.call(arguments)); - return method.apply(obj, allArgs); - }; - }, - - partial: function partial(fn) { - var args = [].slice.call(arguments, 1); - return function () { - return fn.apply(this, args.concat([].slice.call(arguments))); - }; - }, - - create: function create(object) { - F.prototype = object; - return new F(); - }, - - extend: function extend(target) { - if (!target) { return; } - for (var i = 1, l = arguments.length, prop; i < l; ++i) { - for (prop in arguments[i]) { - target[prop] = arguments[i][prop]; - } - } - return target; - }, - - nextTick: function nextTick(callback) { - if (typeof process != "undefined" && process.nextTick) { - return process.nextTick(callback); - } - setTimeout(callback, 0); - }, - - functionName: function functionName(func) { - if (!func) return ""; - if (func.displayName) return func.displayName; - if (func.name) return func.name; - var matches = func.toString().match(/function\s+([^\(]+)/m); - return matches && matches[1] || ""; - }, - - isNode: function isNode(obj) { - if (!div) return false; - try { - obj.appendChild(div); - obj.removeChild(div); - } catch (e) { - return false; - } - return true; - }, - - isElement: function isElement(obj) { - return obj && obj.nodeType === 1 && buster.isNode(obj); - }, - - isArray: function isArray(arr) { - return Object.prototype.toString.call(arr) == "[object Array]"; - }, - - flatten: function flatten(arr) { - var result = [], arr = arr || []; - for (var i = 0, l = arr.length; i < l; ++i) { - result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); - } - return result; - }, - - each: function each(arr, callback) { - for (var i = 0, l = arr.length; i < l; ++i) { - callback(arr[i]); - } - }, - - map: function map(arr, callback) { - var results = []; - for (var i = 0, l = arr.length; i < l; ++i) { - results.push(callback(arr[i])); - } - return results; - }, - - parallel: function parallel(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); - callback = null; - } - } - if (fns.length == 0) { return cb(null, []); } - var remaining = fns.length, results = []; - function makeDone(num) { - return function done(err, result) { - if (err) { return cb(err); } - results[num] = result; - if (--remaining == 0) { cb(null, results); } - }; - } - for (var i = 0, l = fns.length; i < l; ++i) { - fns[i](makeDone(i)); - } - }, - - series: function series(fns, callback) { - function cb(err, res) { - if (typeof callback == "function") { - callback(err, res); - } - } - var remaining = fns.slice(); - var results = []; - function callNext() { - if (remaining.length == 0) return cb(null, results); - var promise = remaining.shift()(next); - if (promise && typeof promise.then == "function") { - promise.then(buster.partial(next, null), next); - } - } - function next(err, result) { - if (err) return cb(err); - results.push(result); - callNext(); - } - callNext(); - }, - - countdown: function countdown(num, done) { - return function () { - if (--num == 0) done(); - }; - } - }; - - if (Array.prototype.some) { - buster.some = function (arr, fn, thisp) { - return arr.some(fn, thisp); - }; - } else { - // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some - buster.some = function (arr, fun, thisp) { - "use strict"; - if (arr == null) { throw new TypeError(); } - arr = Object(arr); - var len = arr.length >>> 0; - if (typeof fun !== "function") { throw new TypeError(); } - - for (var i = 0; i < len; i++) { - if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { - return true; - } - } - - return false; - }; - } - - if (isNode) { - module.exports = buster; - buster.eventEmitter = require("./buster-event-emitter"); - Object.defineProperty(buster, "defineVersionGetter", { - get: function () { - return require("./define-version-getter"); - } - }); - } - - return buster.extend(B || {}, buster); -}(setTimeout, buster)); -/*jslint eqeqeq: false, onevar: false, plusplus: false*/ -/*global buster, require, module*/ -if (typeof require == "function" && typeof module == "object") { - var buster = require("./buster-core"); -} - -(function () { - function eventListeners(eventEmitter, event) { - if (!eventEmitter.listeners) { - eventEmitter.listeners = {}; - } - - if (!eventEmitter.listeners[event]) { - eventEmitter.listeners[event] = []; - } - - return eventEmitter.listeners[event]; - } - - function throwLater(event, error) { - buster.nextTick(function () { - error.message = event + " listener threw error: " + error.message; - throw error; - }); - } - - function addSupervisor(emitter, listener, thisObject) { - if (!emitter.supervisors) { emitter.supervisors = []; } - emitter.supervisors.push({ - listener: listener, - thisObject: thisObject - }); - } - - function notifyListener(emitter, event, listener, args) { - try { - listener.listener.apply(listener.thisObject || emitter, args); - } catch (e) { - throwLater(event, e); - } - } - - buster.eventEmitter = { - create: function () { - return buster.create(this); - }, - - addListener: function addListener(event, listener, thisObject) { - if (typeof event === "function") { - return addSupervisor(this, event, listener); - } - if (typeof listener != "function") { - throw new TypeError("Listener is not function"); - } - eventListeners(this, event).push({ - listener: listener, - thisObject: thisObject - }); - }, - - once: function once(event, listener, thisObject) { - var self = this; - this.addListener(event, listener); - - var wrapped = function () { - self.removeListener(event, listener); - self.removeListener(event, wrapped); - }; - this.addListener(event, wrapped); - }, - - hasListener: function hasListener(event, listener, thisObject) { - var listeners = eventListeners(this, event); - - for (var i = 0, l = listeners.length; i < l; i++) { - if (listeners[i].listener === listener && - listeners[i].thisObject === thisObject) { - return true; - } - } - - return false; - }, - - removeListener: function (event, listener) { - var listeners = eventListeners(this, event); - - for (var i = 0, l = listeners.length; i < l; ++i) { - if (listeners[i].listener == listener) { - listeners.splice(i, 1); - return; - } - } - }, - - emit: function emit(event) { - var listeners = eventListeners(this, event); - var args = Array.prototype.slice.call(arguments, 1); - - for (var i = 0, l = listeners.length; i < l; i++) { - notifyListener(this, event, listeners[i], args); - } - - listeners = this.supervisors || []; - args = Array.prototype.slice.call(arguments); - for (i = 0, l = listeners.length; i < l; ++i) { - notifyListener(this, event, listeners[i], args); - } - }, - - bind: function (object, events) { - var method; - - if (!events) { - for (method in object) { - if (object.hasOwnProperty(method) && typeof object[method] == "function") { - this.addListener(method, object[method], object); - } - } - } else if (typeof events == "string" || - Object.prototype.toString.call(events) == "[object Array]") { - events = typeof events == "string" ? [events] : events; - - for (var i = 0, l = events.length; i < l; ++i) { - this.addListener(events[i], object[events[i]], object); - } - } else { - for (var prop in events) { - if (events.hasOwnProperty(prop)) { - method = events[prop]; - - if (typeof method == "function") { - object[buster.functionName(method) || prop] = method; - } else { - method = object[events[prop]]; - } - - this.addListener(prop, method, object); - } - } - } - - return object; - } - }; - - buster.eventEmitter.on = buster.eventEmitter.addListener; -}()); - -if (typeof module != "undefined") { - module.exports = buster.eventEmitter; -} -/*jslint eqeqeq: false, onevar: false, plusplus: false*/ -/*global buster, require, module*/ -(function () { - var isCommonJS = typeof require == "function" && typeof module == "object"; - if (isCommonJS) buster = require("buster-core"); - var toString = Object.prototype.toString; - var slice = Array.prototype.slice; - var assert, refute, ba = buster.assertions = buster.eventEmitter.create(); - - if (isCommonJS) { - module.exports = buster.assertions; - } - - function countAssertion() { - if (typeof ba.count != "number") { - ba.count = 0; - } - - ba.count += 1; - } - - ba.count = countAssertion; - - function assertEnoughArguments(name, args, num) { - if (args.length < num) { - ba.fail("[" + name + "] Expected to receive at least " + - num + " argument" + (num > 1 ? "s" : "")); - return false; - } - - return true; - } - - function defineAssertion(type, name, func, fl, messageValues) { - ba[type][name] = function () { - var fullName = type + "." + name; - countAssertion(); - if (!assertEnoughArguments(fullName, arguments, fl || func.length)) return; - - var failed = false; - - var ctx = { - fail: function () { - failed = true; - var failArgs = [type, name].concat(slice.call(arguments)); - fail.apply(this, failArgs); - return true; - } - }; - - var args = slice.call(arguments, 0); - - if (typeof messageValues == "function") { - args = messageValues.apply(this, args); - } - - if (!func.apply(ctx, arguments)) { - return fail.apply(ctx, [type, name, "message"].concat(args)); - } - - if (!failed) { - ba.emit.apply(ba, ["pass", fullName].concat(args)); - } - }; - } - - ba.add = function (name, options) { - var refuteArgs; - - if (options.refute) { - refuteArgs = options.refute.length; - } else { - refuteArgs = options.assert.length; - options.refute = function () { - return !options.assert.apply(this, arguments); - }; - } - - var values = options && options.values; // TODO: Remove - defineAssertion("assert", name, options.assert, options.assert.length, values); - defineAssertion("refute", name, options.refute, refuteArgs, values); - - assert[name].message = options.assertMessage; - refute[name].message = options.refuteMessage; - - if (options.expectation) { - if (ba.expect && ba.expect.wrapAssertion) { - ba.expect.wrapAssertion(name, options.expectation); - } else { - assert[name].expectationName = options.expectation; - refute[name].expectationName = options.expectation; - } - } - }; - - function interpolate(string, property, value) { - return string.replace(new RegExp("\\$\\{" + property + "\\}", "g"), value); - } - - function interpolatePosArg(message, values) { - var value; - values = values || []; - - for (var i = 0, l = values.length; i < l; i++) { - message = interpolate(message, i, ba.format(values[i])); - } - - return message; - } - - function interpolateProperties(msg, properties) { - for (var prop in properties) { - msg = interpolate(msg, prop, ba.format(properties[prop])); - } - - return msg || ""; - } - - function fail(type, assertion, msg) { - delete this.fail; - var message = interpolateProperties( - interpolatePosArg(ba[type][assertion][msg] || msg, - [].slice.call(arguments, 3)), this); - ba.fail("[" + type + "." + assertion + "] " + message); - } - - function isDate(value) { - // Duck typed dates, allows objects to take on the role of dates - // without actually being dates - return typeof value.getTime == "function" && - value.getTime() == value.valueOf(); - } - - ba.isDate = isDate; - - // Fixes NaN === NaN (should be true) and - // -0 === +0 (should be false) - // http://wiki.ecmascript.org/doku.php?id=harmony:egal - function egal(x, y) { - if (x === y) { - // 0 === -0, but they are not identical - return x !== 0 || 1 / x === 1 / y; - } - - // NaN !== NaN, but they are identical. - // NaNs are the only non-reflexive value, i.e., if x !== x, - // then x is a NaN. - // isNaN is broken: it converts its argument to number, so - // isNaN("foo") => true - return x !== x && y !== y; - } - - function areEqual(expected, actual) { - if (egal(expected, actual)) { - return true; - } - - // Elements are only equal if expected === actual - if (buster.isElement(expected) || buster.isElement(actual)) { - return false; - } - - // null and undefined only pass for null === null and - // undefined === undefined - /*jsl: ignore*/ - if (expected == null || actual == null) { - return actual === expected; - } - /*jsl: end*/ - - if (isDate(expected) || isDate(actual)) { - return isDate(expected) && isDate(actual) && - expected.getTime() == actual.getTime(); - } - - var useCoercingEquality = typeof expected != "object" || typeof actual != "object"; - - if (expected instanceof RegExp && actual instanceof RegExp) { - if (expected.toString() != actual.toString()) { - return false; - } - - useCoercingEquality = false; - } - - // Arrays can only be equal to arrays - var expectedStr = toString.call(expected); - var actualStr = toString.call(actual); - - // Coerce and compare when primitives are involved - if (useCoercingEquality) { - return expectedStr != "[object Array]" && actualStr != "[object Array]" && - expected == actual; - } - - var expectedKeys = ba.keys(expected); - var actualKeys = ba.keys(actual); - - if (isArguments(expected) || isArguments(actual)) { - if (expected.length != actual.length) { - return false; - } - } else { - if (typeof expected != typeof actual || expectedStr != actualStr || - expectedKeys.length != actualKeys.length) { - return false; - } - } - - var key; - - for (var i = 0, l = expectedKeys.length; i < l; i++) { - key = expectedKeys[i]; - - if (!Object.prototype.hasOwnProperty.call(actual, key) || - !areEqual(expected[key], actual[key])) { - return false; - } - } - - return true; - } - - ba.deepEqual = areEqual; - - assert = ba.assert = function assert(actual, message) { - countAssertion(); - if (!assertEnoughArguments("assert", arguments, 1)) return; - - if (!actual) { - var val = ba.format(actual) - ba.fail(message || "[assert] Expected " + val + " to be truthy"); - } else { - ba.emit("pass", "assert", message || "", actual); - } - }; - - assert.toString = function () { - return "buster.assert"; - }; - - refute = ba.refute = function (actual, message) { - countAssertion(); - if (!assertEnoughArguments("refute", arguments, 1)) return; - - if (actual) { - var val = ba.format(actual) - ba.fail(message || "[refute] Expected " + val + " to be falsy"); - } else { - ba.emit("pass", "refute", message || "", actual); - } - }; - - assert.message = "[assert] Expected ${0} to be truthy"; - ba.count = 0; - - ba.fail = function (message) { - var exception = new Error(message); - exception.name = "AssertionError"; - - try { - throw exception; - } catch (e) { - ba.emit("failure", e); - } - - if (typeof ba.throwOnFailure != "boolean" || ba.throwOnFailure) { - throw exception; - } - }; - - ba.format = function (object) { - return "" + object; - }; - - function msg(message) { - if (!message) { return ""; } - return message + (/[.:!?]$/.test(message) ? " " : ": "); - } - - function actualAndExpectedMessageValues(actual, expected, message) { - return [actual, expected, msg(message)] - } - - function actualMessageValues(actual) { - return [actual, msg(arguments[1])]; - } - - function actualAndTypeOfMessageValues(actual) { - return [actual, typeof actual, msg(arguments[1])]; - } - - ba.add("same", { - assert: function (actual, expected) { - return egal(actual, expected); - }, - refute: function (actual, expected) { - return !egal(actual, expected); - }, - assertMessage: "${2}${0} expected to be the same object as ${1}", - refuteMessage: "${2}${0} expected not to be the same object as ${1}", - expectation: "toBe", - values: actualAndExpectedMessageValues - }); - - function multiLineStringDiff(actual, expected, message) { - if (actual == expected) return true; - - var message = interpolatePosArg(assert.equals.multiLineStringHeading, [message]), - actualLines = actual.split("\n"), - expectedLines = expected.split("\n"), - lineCount = Math.max(expectedLines.length, actualLines.length), - lines = []; - - for (var i = 0; i < lineCount; ++i) { - if (expectedLines[i] != actualLines[i]) { - lines.push("line " + (i + 1) + ": " + (expectedLines[i] || "") + - "\nwas: " + (actualLines[i] || "")); - } - } - - ba.fail("[assert.equals] " + message + lines.join("\n\n")); - return false; - } - - ba.add("equals", { - assert: function (actual, expected) { - if (typeof actual == "string" && typeof expected == "string" && - (actual.indexOf("\n") >= 0 || expected.indexOf("\n") >= 0)) { - var message = msg(arguments[2]); - return multiLineStringDiff.call(this, actual, expected, message); - } - - return areEqual(actual, expected); - }, - - refute: function (actual, expected) { - return !areEqual(actual, expected); - }, - - assertMessage: "${2}${0} expected to be equal to ${1}", - refuteMessage: "${2}${0} expected not to be equal to ${1}", - expectation: "toEqual", - values: actualAndExpectedMessageValues - }); - - assert.equals.multiLineStringHeading = "${0}Expected multi-line strings to be equal:\n"; - - ba.add("greater", { - assert: function (actual, expected) { - return actual > expected; - }, - - assertMessage: "${2}Expected ${0} to be greater than ${1}", - refuteMessage: "${2}Expected ${0} to be less than or equal to ${1}", - expectation: "toBeGreaterThan", - values: actualAndExpectedMessageValues - }); - - ba.add("less", { - assert: function (actual, expected) { - return actual < expected; - }, - - assertMessage: "${2}Expected ${0} to be less than ${1}", - refuteMessage: "${2}Expected ${0} to be greater than or equal to ${1}", - expectation: "toBeLessThan", - values: actualAndExpectedMessageValues - }); - - ba.add("defined", { - assert: function (actual) { - return typeof actual != "undefined"; - }, - assertMessage: "${2}Expected to be defined", - refuteMessage: "${2}Expected ${0} (${1}) not to be defined", - expectation: "toBeDefined", - values: actualAndTypeOfMessageValues - }); - - ba.add("isNull", { - assert: function (actual) { - return actual === null; - }, - assertMessage: "${1}Expected ${0} to be null", - refuteMessage: "${1}Expected not to be null", - expectation: "toBeNull", - values: actualMessageValues - }); - - function match(object, matcher) { - if (matcher && typeof matcher.test == "function") { - return matcher.test(object); - } - - if (typeof matcher == "function") { - return matcher(object) === true; - } - - if (typeof matcher == "string") { - matcher = matcher.toLowerCase(); - return !!object && ("" + object).toLowerCase().indexOf(matcher) >= 0; - } - - if (typeof matcher == "number") { - return matcher == object; - } - - if (typeof matcher == "boolean") { - return matcher === object; - } - - if (matcher && typeof matcher == "object") { - for (var prop in matcher) { - if (!match(object[prop], matcher[prop])) { - return false; - } - } - - return true; - } - - throw new Error("Matcher (" + ba.format(matcher) + ") was not a " + - "string, a number, a function, a boolean or an object"); - } - - ba.match = match; - - ba.add("match", { - assert: function (actual, matcher) { - var passed; - - try { - passed = match(actual, matcher); - } catch (e) { - return this.fail("exceptionMessage", e.message, msg(arguments[2])); - } - - return passed; - }, - - refute: function (actual, matcher) { - var passed; - - try { - passed = match(actual, matcher); - } catch (e) { - return this.fail("exceptionMessage", e.message); - } - - return !passed; - }, - - assertMessage: "${2}${0} expected to match ${1}", - refuteMessage: "${2}${0} expected not to match ${1}", - expectation: "toMatch", - values: actualAndExpectedMessageValues - }); - - assert.match.exceptionMessage = "${1}${0}"; - refute.match.exceptionMessage = "${1}${0}"; - - ba.add("isObject", { - assert: function (actual) { - return typeof actual == "object" && !!actual; - }, - assertMessage: "${2}${0} (${1}) expected to be object and not null", - refuteMessage: "${2}${0} expected to be null or not an object", - expectation: "toBeObject", - values: actualAndTypeOfMessageValues - }); - - ba.add("isFunction", { - assert: function (actual) { - return typeof actual == "function"; - }, - assertMessage: "${2}${0} (${1}) expected to be function", - refuteMessage: "${2}${0} expected not to be function", - expectation: "toBeFunction", - values: function (actual) { - return [("" + actual).replace("\n", ""), typeof actual, msg(arguments[1])]; - } - }); - - ba.add("isTrue", { - assert: function (actual) { - return actual === true; - }, - assertMessage: "${1}Expected ${0} to be true", - refuteMessage: "${1}Expected ${0} to not be true", - expectation: "toBeTrue", - values: actualMessageValues - }); - - ba.add("isFalse", { - assert: function (actual) { - return actual === false; - }, - assertMessage: "${1}Expected ${0} to be false", - refuteMessage: "${1}Expected ${0} to not be false", - expectation: "toBeFalse", - values: actualMessageValues - }); - - ba.add("isString", { - assert: function (actual) { - return typeof actual == "string"; - }, - assertMessage: "${2}Expected ${0} (${1}) to be string", - refuteMessage: "${2}Expected ${0} not to be string", - expectation: "toBeString", - values: actualAndTypeOfMessageValues - }); - - ba.add("isBoolean", { - assert: function (actual) { - return typeof actual == "boolean"; - }, - assertMessage: "${2}Expected ${0} (${1}) to be boolean", - refuteMessage: "${2}Expected ${0} not to be boolean", - expectation: "toBeBoolean", - values: actualAndTypeOfMessageValues - }); - - ba.add("isNumber", { - assert: function (actual) { - return typeof actual == "number" && !isNaN(actual); - }, - assertMessage: "${2}Expected ${0} (${1}) to be a non-NaN number", - refuteMessage: "${2}Expected ${0} to be NaN or another non-number value", - expectation: "toBeNumber", - values: actualAndTypeOfMessageValues - }); - - ba.add("isNaN", { - assert: function (actual) { - return typeof actual == "number" && isNaN(actual); - }, - assertMessage: "${2}Expected ${0} to be NaN", - refuteMessage: "${2}Expected not to be NaN", - expectation: "toBeNaN", - values: actualAndTypeOfMessageValues - }); - - ba.add("isArray", { - assert: function (actual) { - return toString.call(actual) == "[object Array]"; - }, - assertMessage: "${2}Expected ${0} to be array", - refuteMessage: "${2}Expected ${0} not to be array", - expectation: "toBeArray", - values: actualAndTypeOfMessageValues - }); - - function isArrayLike(object) { - return toString.call(object) == "[object Array]" || - (!!object && typeof object.length == "number" && - typeof object.splice == "function") || - ba.isArguments(object); - } - - ba.isArrayLike = isArrayLike; - - ba.add("isArrayLike", { - assert: function (actual) { - return isArrayLike(actual); - }, - assertMessage: "${2}Expected ${0} to be array like", - refuteMessage: "${2}Expected ${0} not to be array like", - expectation: "toBeArrayLike", - values: actualAndTypeOfMessageValues - }); - - function captureException(callback) { - try { - callback(); - } catch (e) { - return e; - } - - return null; - } - - ba.captureException = captureException; - - assert.exception = function (callback, exception, message) { - countAssertion(); - if (!assertEnoughArguments("assert.exception", arguments, 1)) return - - if (!callback) { - return; - } - - var err = captureException(callback); - message = msg(message); - - if (!err) { - if (exception) { - return fail.call({}, "assert", "exception", "typeNoExceptionMessage", - message, exception); - } else { - return fail.call({}, "assert", "exception", "message", - message, exception); - } - } - - if (exception && err.name != exception) { - if (typeof window != "undefined" && typeof console != "undefined") { - console.log(err); - } - - return fail.call({}, "assert", "exception", "typeFailMessage", - message, exception, err.name, err.message); - } - - ba.emit("pass", "assert.exception", message, callback, exception); - }; - - assert.exception.typeNoExceptionMessage = "${0}Expected ${1} but no exception was thrown"; - assert.exception.message = "${0}Expected exception"; - assert.exception.typeFailMessage = "${0}Expected ${1} but threw ${2} (${3})"; - assert.exception.expectationName = "toThrow"; - - refute.exception = function (callback) { - countAssertion(); - if (!assertEnoughArguments("refute.exception", arguments, 1)) return; - - var err = captureException(callback); - - if (err) { - fail.call({}, "refute", "exception", "message", - msg(arguments[1]), err.name, err.message, callback); - } else { - ba.emit("pass", "refute.exception", callback); - } - }; - - refute.exception.message = "${0}Expected not to throw but threw ${1} (${2})"; - refute.exception.expectationName = "toThrow"; - - ba.add("near", { - assert: function (actual, expected, delta) { - return Math.abs(actual - expected) <= delta; - }, - assertMessage: "${3}Expected ${0} to be equal to ${1} +/- ${2}", - refuteMessage: "${3}Expected ${0} not to be equal to ${1} +/- ${2}", - expectation: "toBeNear", - values: function (actual, expected, delta, message) { - return [actual, expected, delta, msg(message)]; - } - }); - - ba.add("hasPrototype", { - assert: function (actual, protoObj) { - return protoObj.isPrototypeOf(actual); - }, - assertMessage: "${2}Expected ${0} to have ${1} on its prototype chain", - refuteMessage: "${2}Expected ${0} not to have ${1} on its prototype chain", - expectation: "toHavePrototype", - values: actualAndExpectedMessageValues - }); - - ba.add("contains", { - assert: function (haystack, needle) { - for (var i = 0; i < haystack.length; i++) { - if (haystack[i] === needle) { - return true; - } - } - return false; - }, - assertMessage: "${2}Expected [${0}] to contain ${1}", - refuteMessage: "${2}Expected [${0}] not to contain ${1}", - expectation: "toContain", - values: actualAndExpectedMessageValues - }); - - ba.add("tagName", { - assert: function (element, tagName) { - if (!element.tagName) { - return this.fail("noTagNameMessage", tagName, element, msg(arguments[2])); - } - - return tagName.toLowerCase && - tagName.toLowerCase() == element.tagName.toLowerCase(); - }, - assertMessage: "${2}Expected tagName to be ${0} but was ${1}", - refuteMessage: "${2}Expected tagName not to be ${0}", - expectation: "toHaveTagName", - values: function (element, tagName, message) { - return [tagName, element.tagName, msg(message)]; - } - }); - - assert.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; - refute.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; - - function indexOf(arr, item) { - for (var i = 0, l = arr.length; i < l; i++) { - if (arr[i] == item) { - return i; - } - } - - return -1; - } - - ba.add("className", { - assert: function (element, className) { - if (typeof element.className == "undefined") { - return this.fail("noClassNameMessage", className, element, msg(arguments[2])); - } - - var expected = typeof className == "string" ? className.split(" ") : className; - var actual = element.className.split(" "); - - for (var i = 0, l = expected.length; i < l; i++) { - if (indexOf(actual, expected[i]) < 0) { - return false; - } - } - - return true; - }, - assertMessage: "${2}Expected object's className to include ${0} but was ${1}", - refuteMessage: "${2}Expected object's className not to include ${0}", - expectation: "toHaveClassName", - values: function (element, className, message) { - return [className, element.className, msg(message)]; - } - }); - - assert.className.noClassNameMessage = "${2}Expected object to have className property"; - refute.className.noClassNameMessage = "${2}Expected object to have className property"; - - if (typeof module != "undefined") { - ba.expect = function () { - ba.expect = require("./buster-assertions/expect"); - return ba.expect.apply(exports, arguments); - }; - } - - function isArguments(obj) { - if (typeof obj != "object" || typeof obj.length != "number" || - toString.call(obj) == "[object Array]") { - return false; - } - - if (typeof obj.callee == "function") { - return true; - } - - try { - obj[obj.length] = 6; - delete obj[obj.length]; - } catch (e) { - return true; - } - - return false; - } - - ba.isArguments = isArguments; - - if (Object.keys) { - ba.keys = function (obj) { - return Object.keys(obj) - }; - } else { - ba.keys = function (object) { - var keys = []; - - for (var prop in object) { - if (Object.prototype.hasOwnProperty.call(object, prop)) { - keys.push(prop); - } - } - - return keys; - } - } -}()); -/* common.utils.js: a core framework library of utilities and polyfills. - -This adds utility functions into a namespace u. - -Standard polyfills are automatically added to their prototypes. The following nonstandard prototype -changes are made: - -String.format -String.split with trim option -Array.contains -Array.first - -You can remove the call to u.polyfill to prevent the nonstandard changes. - -Version 1.0.1 - 6/19/1012 - -James Treworgy -*/ - -/*global define, require, module */ -/*jslint curly: false, expr: true */ -(function (define) { - define(function () { - var u; - - /* General puropose functions */ - - function isBool(obj) { - return typeof obj === 'boolean'; - } - function isString(obj) { - return typeof obj === 'string'; - } - function isUndefined(obj) { - return typeof obj === 'undefined'; - } - function isArray(obj) { - return obj && obj.constructor === Array; - } - - /* prototype extension functions - these must be called with a context */ - - // trim a string leading & trailing whitespace - function stringTrim() { - return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - } - - // generic iterator. when trim is true, string values are trimmed. - function forEach(cb, trim) { - var coll = this, - i, val; - if (!coll) return; - - if (isString(coll)) { - coll = coll.split(','); - } - if (isArray(coll)) { - for (i = 0; i < coll.length; i++) { - val = isString(coll[i]) ? - stringTrim.call(coll[i]) : - coll[i]; - - if (cb.call(val, i, val) === false) { - break; - } - } - } else { - for (i in coll) { - if (coll.hasOwnProperty(i)) { - if (cb.call(coll[i], i, coll[i]) === false) { - break; - } - } - } - } - } - // string format function - function format() { - var args = (arguments.length === 1 && isArray(arguments[0])) ? - arguments[0] : - arguments; - return this.replace(/\{(\d+)\}/g, function (match, number) { - var num = parseInt(number, 10); - return !isUndefined(args[num]) - ? String(args[num]) - : match; - }); - } - - // a split function that trims its results. any 'true' bool parameter will be interpreted as a flag to trim - function stringSplit(delimiter, trimResults) { - var result = [], - delim = isString(delimiter) ? - delimiter : ',', - trim = isBool(delimiter) ? - delimiter : - isBool(trimResults) ? - trimResults : false; - - forEach.call(String.prototype.split.call(this, delim || ','), function (i, e) { - result.push(trim ? stringTrim(e) : e); - }); - return result; - } - - // polyfills - - function arrayForEach(action, that) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this) - action.call(that, this[i], i, this); - } - - function arrayIndexOf(find, i /*opt*/) { - if (i === undefined) i = 0; - if (i < 0) i += this.length; - if (i < 0) i = 0; - for (var n = this.length; i < n; i++) - if (i in this && this[i] === find) - return i; - return -1; - } - - // returns true if the element exists - function arrayContains(val) { - return arrayIndexOf.call(this, val) >= 0; - } - - // NONSTANDARD - - // return the first element where filter returns true - function arrayFirst(filter) { - var i, undef; - - if (!filter) { - return this.length > 0 ? this[0] : undef; - } - - for (i = 0; i < this.length; i++) { - if (filter.call(this[i], i, this[i])) { - return this[i]; - } - } - return undef; - } - - // return the first element where filter returns true - function arrayLast(filter) { - var i, undef; - - if (!filter) { - return this.length > 0 ? this[this.length-1] : undef; - } - - for (i = this.length; i >0; i--) { - if (filter.call(this[i], i, this[i])) { - return this[i]; - } - } - return undef; - } - - function arrayLastIndexOf(find, i /*opt*/) { - if (i === undefined) i = this.length - 1; - if (i < 0) i += this.length; - if (i > this.length - 1) i = this.length - 1; - for (i++; i-- > 0; ) /* i++ because from-argument is sadly inclusive */ - if (i in this && this[i] === find) - return i; - return -1; - } - - function arrayMap(mapper, that /*opt*/) { - var n, i, other = new Array(this.length); - for (i = 0, n = this.length; i < n; i++) - if (i in this) - other[i] = mapper.call(that, this[i], i, this); - return other; - } - - function arrayFilter(filter, that /*opt*/) { - var i, n, other = [], v; - for (i = 0, n = this.length; i < n; i++) - if (i in this && filter.call(that, v = this[i], i, this)) - other.push(v); - return other; - } - - function arrayEvery(tester, that /*opt*/) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this && !tester.call(that, this[i], i, this)) - return false; - return true; - } - - function arraySome(tester, that /*opt*/) { - for (var i = 0, n = this.length; i < n; i++) - if (i in this && tester.call(that, this[i], i, this)) - return true; - return false; - } - - u = { - // when onlyInTarget is true, properties will not be added - only updated - // passing a falsy value as the target results in a new object being created - // and onlyInTarget is ignored (properties must be added to a new object) - - extend: function (target /*[,onlyInTarget]*/) { - var prop, source, sources, i, - li = arguments.length, - lastBool = u.isBool(arguments[li - 1]), - len = lastBool ? - li - 2 : li - 1, - emptyTarget = !target, - onlyInTarget = lastBool ? - arguments[len + 1] : false; - - target = target || {}; - - sources = u.toArray(arguments, 1, len + 1); - - for (i = 0; i < sources.length; i++) { - source = sources[i]; - for (prop in source) { - if (source.hasOwnProperty(prop) - && (emptyTarget || !onlyInTarget || target.hasOwnProperty(prop))) { - target[prop] = source[prop]; - } - } - // start honoring onlyInTarget after the first source - emptyTarget = false; - } - return target; - }, - // copy selected properties to a new object - filterProps: function (source, what) { - var target = {}, - props = u.isArray(what) ? - what : - what.split(','); - - u.each(props, function (i, prop) { - target[prop] = source[prop]; - }); - return target; - }, - toArray: function (arrLike, first, last) { - return Array.prototype.slice.call(arrLike, first || 0, last || arrLike.length); - }, - isArray: isArray, - arrayIndexOf: function (arr, val) { - return arrayIndexOf.call(arr, val); - }, - inArray: function (arr, val) { - return arrayContains.call(arr, val); - }, - isFunction: function (obj) { - return typeof obj === 'function'; - }, - isUndefined: isUndefined, - isString: isString, - isBool: isBool, - isValueType: function (obj) { - return u.inArray(['boolean', 'number', 'string'], typeof (obj)); - }, - trim: function (str) { - return stringTrim.call(str); - }, - //split with trim (why would you want it any other way?) - split: function (str, delim) { - return stringSplit.call(str, delim); - }, - // replaces {0}.. {n} with the ordinal valued parameter. You can also pass an - // array instead of multiple parameters - format: function () { - return format.apply(arguments[0], u.toArray(arguments, 1)); - }, - // usual each, if you happen to pass a string, it will split it on commas. - // it will always trim string values in an array. - each: function (coll, cb) { - coll && forEach.call(coll, cb); - }, - first: function(arr,filter) { - - return arrayFirst.call(arr,filter); - }, - last: function(arr,filter) { - return arrayLast.call(arr,filter); - }, - donothing: function () { }, - // add nonstandard polyfills - polyfill: function () { - - Array.prototype.contains = arrayContains; - Array.prototype.first = arrayFirst; - Array.prototype.last = arrayLast; - String.prototype.format = format; - } - - }; - - // add required polyfills - - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = arrayIndexOf; - } - if (!Array.prototype.trim) { - Array.prototype.trim = stringTrim; - } - if (!Array.prototype.lastIndexOf) { - Array.prototype.lastIndexOf = arrayLastIndexOf; - } - if (!Array.prototype.forEach) { - Array.prototype.forEach = arrayForEach; - } - if (!Array.prototype.filter) { - Array.prototype.filter = arrayFilter; - } - if (!Array.prototype.every) { - Array.prototype.every = arrayEvery; - } - if (!Array.prototype.some) { - Array.prototype.some = arraySome; - } - if (!Array.prototype.map) { - Array.prototype.map = arrayMap; - } - u.polyfill(); - - return u; - }); -} (typeof define === 'function' - ? define - : function (factory) { - if (typeof module !== 'undefined') { - module.exports = factory(); - } else { - this.common = this.common || {}; - this.common.utils = factory(); - } - } -// Boilerplate for AMD, Node, and browser global -)); -/* -IqTest: A javascript testing framework that promises to be easy - -(c) 2012 James Treworgy -MIT License -*/ - -/*jslint novar:true, onevar: false, debug: true */ -/*global alert, define, require, module, buster, u */ - - -(function(define) { - -define(['./iqtest'], function(u,when,when_timeout, iq_asserts, buster_asserts, utils) { - var options,iqtestApi,Test, TestGroup, Assert, - globalDefaults = { - setup: null, - teardown: null, - timeout: 10 - }, - // default values for TestGroup - groupDefaults = { - - // an id or name - name: "test-group", - - // detailed desription of the test - desc: "Unnamed Test Group", - - // when true, will not trap errors in the test itself. - debug: false, - // timeout for function execution (null inherits) - timeout: null, - // functions to run on setup or teardown - setup: null, - teardown: null - - }, - // default values for Test and also defines allowed options - testDefaults = { - name: "test", - desc: "Unnamed Test", - func: null, - timeout: null, - // there is a test-specific debug option so that currently running tests will remain - // in non-debug mode after another one fails - debug: false - }, - // list of methods to import from buster - busterMethods="same,equals,typeOf,defined,isNull,match,isObject,isFunction,exception,tagName,className,isTrue,isFalse", - initialized=false, - // metadata collected about the assertions - assertionData={}; - - // get the last argument if it's a string - function assertMessage(assertion,args) { - var argCount= assertionData[assertion.split('.')[1]].args; - return argCount < args.length ? - String(args[argCount]) : - 'an anonymous test'; - } - // Map all asserts - - function captureMethodArgs(method,func) - { - // get metadata by examining error message - var matches=0, - reg=/^.*?Expected.*?([0-9]) argument[s]?\s*$/; - - try { - func(); - } - catch(err) - { - matches=reg.test(err.message) ? parseInt(RegExp.$1,10) : 0; - } - assertionData[method]={ - args: matches - }; - } - - function initialize() - { - var ba,tp; - if (initialized) { - return; - } - // Create a single default assertion so this works with no includes. - // Other non-buster methods can be found in iqtest.assertions.js - - iq_asserts.push({ - truthy: function(obj,message) { - u.expectOpts(arguments,1); - return { - passed: !!obj, - err: u.formatAssert(message,'The object {0} is {not}truthy',String(obj)) - }; - }, - // has special handling - the actual asserion doesn't know if it was given a promise, only the output. - // the queue funciton must verify - resolves: function(obj,message) { - u.expectOpts(arguments,1); - return { - passed: typeof obj !== 'undefined', - err: u.formatAssert(message,'The object {0} did {not}resolve',String(obj)) - }; - } - }); - - // Map buster.assertions.assert & refute to Test protype "assert" & "refute". - // Also map the assertions directly to test - - tp=Test.prototype; - ba=buster.assertions; - - u.each(["assert","refute"],function(i,type) { - - // create proto.assert - - var asserts = {}; - - // map methods from buster - - u.each(busterMethods.split(','),function(j,method) { - captureMethodArgs(method,ba[type][method]); - asserts[method] = function () { - var that=((this.test && this.test instanceof Test) ? this.test : this); - return that.queueTest(ba[type][method],type+"."+method,u.toArray(arguments)); - }; - }); - - // map builtin methods from the array. Ignore ones that have already been defined. - - u.each(iq_asserts,function(i,iqa) { - u.each(iqa, function(method,func) { - if (!asserts[method]) { - captureMethodArgs(method,func); - asserts[method]=function() { - var that=((this.test && this.test instanceof Test) ? this.test : this); - return that.queueBooleanTest(func, - type+"."+method, - u.toArray(arguments), - type==='refute'); - }; - } - }); - }); - - // copy asserts to main Test object (do it before we update with the utilities, those are FROM the main object) - - if (type==='assert') { - u.extend(tp,asserts); - } - // map utilities - - u.each(["backpromise","callback","then"],function(i,method) { - asserts[method]=function() { - return this.test[method].apply(this.test,u.toArray(arguments)); - }; - }); - - // finally update the prototype, and create this.assert() and this.refute() functions - - tp[type]=function(obj,message) { - return tp[type].truthy.apply(this,u.toArray(arguments)); - }; - - u.extend(tp[type],asserts); - - initialized=true; - }); - } - - // u needs to be imported from common.utils - - u.extend(u, { - event: function(func,that,parm) { - if (u.isFunction(func)) { - func.call(that,parm); - } - }, - // throw an error if the 'args' array has fewer than 'expected' elements. - expectOpts: function(args,expected) { - if ((args ? args.length : 0) < expected) { - throw({ - name: "AssertionError", - type: "iq", - message: u.format("Expected to receive at least {0} argument",expected.toString()) - }); - } - }, - // standardize the format of the output from assertions - formatAssert: function(message,reason,parms) { - return !reason ? '' : - (message ? message+': ':'') + - u.format(reason,u.isArray(parms) ? parms : u.toArray(arguments,2)); - } - - }); - - /* Begin Main Code */ - - /* Shared functions */ - - function doEvent(obj,method /* [,parms] */ ) { - if (obj[method]) { - obj[method].apply(this,u.toArray(arguments,2)); - } - } - - // return a delegate to a function with the specified context - // if any additional arguments are passed when the func is actually called, they come first - - function bind(context,func /*[,args]*/) { - var args=u.toArray(arguments,2); - return function() { - // "arguments" here is what the delegate was ultimately invoked with. - var finalArgs = u.toArray(arguments).concat(args); - if (finalArgs.length>0) { - func.apply(context,finalArgs); - } else { - func.call(context); - } - }; - } - // create a new prototype from arbitrary arguments - - function construct(constructor, args) { - function F() { - return constructor.apply(this, args); - } - F.prototype = constructor.prototype; - return new F(); - } - /* Functions for the TestGroup prototype */ - - function groupAddTest(test) { - var hasDesc, func, description, - testData, - me=this; - - function addTest(test) { - me.tests.push(test); - test.group=me; - test.id = me.tests.length; - } - if (test.constructor === TestGroup) { - u.each(test.tests,function(i,e) { - addTest(e); - }); - } else if (test.constructor === Test) { - addTest(test); - } else if (typeof test === 'string') { - description = arguments[1]; - func = arguments[2]; - hasDesc = !!func; - testData = { - name: test, - desc: hasDesc ? description : '', - func: hasDesc ? func : description, - debug: me.debug, - timeout: me.timeout - }; - addTest(new Test(testData)); - } - return me; - } - - // function must be called with a context of - function testFinished(group,test) { - var groupResult; - - if (test.promise!==test._lastPromise) { - test._lastPromise=test.promise; - test._lastPromise.then(function() { - testFinished(group,test); - },function(err) { - test.testerror(err,true); - testFinished(group,test); - }); - return; - } - if (!u.isBool(test.passed)) { - test.passed = (test.count===test.countPassed); - } - test._allPass &= test.passed; - - test.doWriterEvent("testEnd",u.filterProps(test,"count,passed")); - - doEvent.call(test,test,"teardown"); - - // see if all tests have been resolved - - groupResult=true; - u.each(group.tests,function(i,e) { - if (!u.isBool(e.passed)) { - groupResult=null; - return false; - } else { - if (!e.passed) { - groupResult=false; - } - } - }); - - if (u.isBool(groupResult)) { - group.passed = groupResult; - group.doWriterEvent("groupEnd"); - doEvent.call(this,this,"teardown"); - group.promise.resolve(); - } - } - - - function groupRun() { - var me=this; - - doEvent.call(this,this,"setup"); - - this.reset(); - u.each(me.tests,function(i,test) { - test.reset(); - }); - - this.doWriterEvent("groupStart",u.filterProps(me,"name,desc")); - - u.each(me.tests,function(i,test) { - var assert=u.extend({},test.assert,{test: test}), - refute = u.extend({},test.refute,{test: test}); - - doEvent.call(test,test,"setup"); - test.doWriterEvent("testStart",u.filterProps(test,"name")); - - if (test.debug) { - test.func.call(test,assert,refute); - } else { - try { - test.func.call(test,assert,refute); - } - catch(err) - { - test.testerror(u.format("An error occurred in your test code: {0}",err),true); - } - } - - // wait for everything to finish by binding to the last promise, and deferring each time - // it changes as a result of a callback or something. - // this is really quite nasty, I have not figured out a better way to do it yet - - test._lastPromise=test.promise; - test._allPass=true; - - // bind to the last promise in the chain. The finishing function will detect if - // anything else has been added. - - test.promise.then(function() { - testFinished(me,test); - },function(err) { - test.testerror(err,true); - testFinished(me,test); - }); - }); - return this; - } - // call function "event" that is a member of each element in activeWriters, with args - // should be called with the sender event context - function doWriterEvent(event,args) { - var me = this; - u.each(this.group.writers,function(i,e) { - var target = e[event]; - if (u.isFunction(target)) { - target.apply(e,[me].concat(args)); - } - }); - - } - - TestGroup = function (name, desc, options) { - initialize(); - - var opts = name && typeof name === 'object' ? name: - desc && typeof desc === 'object' ? desc : - options || {}; - - if (typeof desc === 'string') { - opts.name=name; - } - if (typeof desc === 'string') { - opts.desc=desc; - } - u.extend(this, - u.extend(null,groupDefaults,opts,true) - ); - - // active ouput writers - this.writers=[]; - - // private methods - - this.doEvent = doEvent; - - // uniform interface for both tests & groups for accessing the group & event emitter - this.group = this; - this.doWriterEvent = function() { - doWriterEvent.apply(this,u.toArray(arguments)); - }; - - this.clear(); - }; - - TestGroup.prototype = { - constructor: TestGroup, - // Add a new test to this group. This can be a Test object, or a - // add: function(name [,description],func) - add: groupAddTest, - // run the tests that have been added to this test group - // return the group object, which is also a promise that resolves when - // the group is finished running. - run: groupRun, - // a promise that resolves when a "run" operation finishes - then: function() { - return this.promise.then.apply(this,u.toArray(arguments)); - }, - configure: function(options) { - if (typeof options === 'string') { - options = {name: options}; - } - var allowed = u.extend({},groupDefaults); - u.extend(allowed,options,true); - u.extend(this,allowed); - return this; - }, - reset: function() { - this.promise=when.defer(); - this.passed=null; - return this; - }, - clear: function() { - this.tests = []; - this.reset(); - return this; - }, - // activate a named writer - writer: function(id /*,writer-args*/) { - var w, - proto = iqtestApi.writers[id]; - if (!proto) { - throw("There is no output writer with id '{0}'".format(w)); - } - - w=construct(proto,u.toArray(arguments,1)); - w.owner=this; - this.writers.push(w); - return this; - }, - // events - groupStart: u.donothing, - groupEnd: u.donothing - }; - - // A test object. After running tests, the "results" contains an array of strings describing - // failures. - Test = function (options) { - var me=this; - - u.extend(me, testDefaults); - u.extend(me, options, true); - me.id=null; - me.group=null; - me.clear(); - - this.doWriterEvent = function() { - doWriterEvent.apply(this,u.toArray(arguments)); - }; - }; - - Test.prototype = { - constructor: Test, - // set timeout only for the next test - impl: {}, - reset: function() { - u.extend(this, { - //domino: when.defer(), - promise: when.defer(), - results: [], - count: 0, - countPassed: 0, - countFailed: 0, - nextThen: [], - cbPromise: null, - resolver: null, - stopped: false, - passed: null, - userPromises: {} - }); - - // resolve immediately to start the chain when the first thing is added - this.promise.resolve(); - }, - clear: function() { - this.setDebug(false); - this.userPromises={}; - }, - setDebug: function(active,count) { - this.debug=u.isBool(active) ? active : true; - when.debug = this.debug; - if (active) { - if (typeof count==='number') { - this.debugCount= count; - } - } else { - this.debugCount=-1; - } - }, - nextIsProblemAssertion: function() { - return this.debug && !this.stopped && this.debugCount>=0 - && this.debugCount === this.count-1; - }, - timeoutOnce: function(seconds) { - var me=this, - originalTimeout = me.timeout; - me.then(function() { - me.timeout = seconds; - }); - me.afterNext(function() { - me.timeout = originalTimeout; - }); - }, - // set options for this test - configure: function(options) { - if (typeof options === 'string') { - options = {name: options}; - } - var allowedOpts =u.extend({},testDefaults); - // update with current options, then with options passed - u.extend(allowedOpts,this,options,true); - u.extend(this,allowedOpts); - }, - // queue a callback to attach to the next thing queued. - then: function(callback,errFunc) { - var me=this, - errback=errFunc || function(err) { - // failures of everything end up here - if we've already broken for a particular reason then - // stop logging all the inevitable timeouts. - me.testerror(err,false); - }, - prev = me.promise, - next = when.defer(); - - me.promise = next; - prev.then(function(val) { - - try { - if (me.nextIsProblemAssertion()) { - // the next event is the one causing you trouble - debugger; - } - callback(val); - } - catch(err){ - me.testerror("An error occurred during a 'then' clause of an assertion: "+String(err),true); - } - },errback); - - - when.chain(prev,next); - - return me; - }, - chain: function(callback,errback) { - var next = when.defer(), - prev = this.promise; - - this.promise = next.promise; - prev.then(callback,errback || bind(this,this.testerror)); - when.chain(prev,next); - return this; - }, - //TODO - afterNext: function(callback,errback) { - this.nextThen.push({callback:callback, errback:errback}); - }, - startTest: function (info) { - this.count++; - // cache the active assertion data - this.assertionInfo = u.extend({},info, - { - count: this.count - }); - this.doWriterEvent("itemStart",this.assertionInfo); - this.itemRunning=true; - }, - endTest: function (result) { - // TODO: Option allowing logging of passed tests - var output=result; - if (!this.itemRunning) { - this.testerror("Error: test was not running when endTest called: " + result.desc); - return; - } - if (!result.passed) { - output=this.addResult(result); - this.countFailed++; - } else { - this.countPassed++; - } - this.itemRunning=false; - this.doWriterEvent("itemEnd",output); - }, - // queue a test that returns true or false - // will wrap it & pass on to queueTest - queueBooleanTest: function(module,assertion,args,invert) { - // make a copy of the object to keep in this closure - var testArgs = u.toArray(args); - return this.queueTest(function() { - var result= module.apply(null,testArgs); - - if (result.passed === invert) { - throw({ - name: "AssertionError", - type: "iq", - message: u.format(result.err.replace('{not}','{0}'),invert?'not ':'') - }); - } - }, - assertion, - args); - }, - // queue a test that throws an error - queueTest: function(module,assertion,args) - { - var me=this, - // internal methods are wrapped in tryTest - get their args as the 2nd arg in "args." Argh! - cbPos, - assertionName=assertion.split('.')[1], - methodArgs=assertionData[assertionName].args, - hasMagicCallback, - deferred, - next,prev, - pending=[]; - - if (me.stopped) { - return me; - } - - // check for the "magic" callback. If me.cbPromise exists, then it was hopefully created by the parameters - // for this method. This is slightly brittle because there's no direct binding of the particular promise to - // this particular method, but the single-threaded nature of javascript should cause this to work fine. - // I can't see any substantive risk here and it is extraordinarily convenient. - - if (me.cbPromise) { - hasMagicCallback=true; - deferred = deferred || when.defer(); - - if (methodArgs===1) { - cbPos=0; - } else if (args[0] && !args[1]) { - cbPos=1; - } else if (args[1] && !args[0]) { - cbPos=0; - } else { - me.testerror(u.format('I couldn\'t figure out what to do with your magic callback. ' - + 'For this test you may need to define it explicitly.' - + '[{0}] {1}',assertion,assertMessage(args))); - return; - } - - me.cbPromise.then(function(response) { - args[cbPos]=response; - },function(err) { - deferred.reject('The callback failed. ' + (err ? u.format('Reason: {0}',String(err)) :'')); - }); - pending.push(me.cbPromise); - me.cbPromise=null; - } - - // special case - must check argument for "resolves" - - if (assertionName==='resolves' && !when.isPromise(args[0])) { - throw("The argument passed to 'resolves' was not a promise."); - } - - // check all the arguments to this assertion for promises or callbacks; if any are found, - // add to our list of things to do before resolving this assertion. - - u.each(args,function(i,arg) { - - // wait for any promises - if (when.isPromise(arg)) { - - deferred = deferred || when.defer(); - - if (i===0 && hasMagicCallback) { - deferred.reject("You're using magic callback but you've also defined a promise as the first argument of your assert."); - } - arg.then(function(response) - { - args[i]=response; - }); - pending.push(arg); - } - - }); - - // queue a promise that will emit events when the test has started. - - this.chain(function() { - me.startTest({ - desc: assertMessage(assertion,args), - assertion: assertion - }); - }); - - - // if there are pending promises, then wait for all those events (in addition to the last promise) - // before resolving. Add a timeout on top of it if necessary. - - if (pending.length) { - pending.push(me.promise); - - me.promise = me.timeout ? - when_timeout(deferred.promise,me.timeout*1000): - deferred.promise; - - when.chain(when.all(pending),deferred); - } - - - // link each test to a new resolver so failures will break the chain at that point - // some tests don't have an "actual" part (e.g. pass,fail). - - next = when.defer(); - prev = me.promise; - me.promise = next.promise; - prev.then(function resolve(value) { - // check if a value was passed - it is likely the cb parm - if (me.runTest.call(me,module,assertion,args)) { - next.resolve(); - } else { - next.reject("The test failed"); - } - },function reject(err) { - if (me.itemRunning) { - me.endTest({ - passed: false, - err: String(err) - }); - } - next.reject("The test was stopped because an assertion failed."); - }); - - - - //me.resolver=deferred.resolver; - - return me; - }, - // run a named test using the arguments in array args - runTest: function (module,assertion,args) { - // should return an object [err: error message, desc: description of test passed in] - var result={ - assertion: assertion, - err: '', - passed: true - }; - - //args is an object mapped to the relevant parms for any assert - try { - module.apply(null, args); - } - catch(err) - { - // rethrow anything that isn't a test failure - it will be caught and dealt with higher up. - if (err.name !== 'AssertionError') { - if (this.debug) { - debugger; - // Continue execution to try the assertion again - module.apply(null, args); - } else { - this.setDebug(); - err.message = (err.message || err.type) + ". Debugging has been enabled."; - } - } - if (err.type==='iq') { - err.message = u.format('[{0}] {1}',assertion,err.message); - } - result.err = err.message; - result.passed=false; - } - - this.endTest(result); - return result.passed; - }, - /// add the current test results as properties to the object passed in - addResult: function (result) { - - var output = u.extend({}, result), - passfail = output.passed ? - "passed" : - "failed"; - - // "Test #[1] [assertEq] [passed|failed] [with result "message"] [in test "test"] - - output.count = this.count; - output.fulltext = u.format('Test #{0} {1} {2} {3}{4}', - this.count, - this.assertionInfo.assertion, - passfail, - output.passed ? - '' : - ': ' + result.err, - - u.format(' in test "{0}"', this.assertionInfo.desc) - - ); - - this.results.push(output); - return output; - - }, - - // when the debugging parm is true, will enable debugging for the group - testerror: function(err, debug) { - var me=this; - try { - - if (me.stopped) { - return; - } - me.stopped=true; - me.passed=false; - - this.doWriterEvent("testLog", - u.format('{0}. {1}',String(err), - debug ? 'Debugging is enabled if you start again.' : '' )); - } - catch(e) { - // this is basically a fatal error. Not much else to do. - debugger; - - } - - if (debug) { - me.setDebug(true,me.count); - } - - - }, - - // create a callback that the next assert will wait for, optionally expiring. - callback: function(target,timeout) { - var me=this, - t = timeout || me.timeout, - deferred = when.defer(); - - // if no timeout is specified, the actual function is already wrapped by a timeout so not needed - - me.cbPromise= - t ? - when_timeout(deferred, t * 1000) : - deferred; - - return function() { - var value; - if (!target) { - value=true; - } else { - if (me.debug) { - value=target.apply(this,u.toArray(arguments)); - } else { - try - { - value=target.apply(this,u.toArray(arguments)); - } - catch(err) - { - me.testerror("An error occurred in your callback(): "+String(err),true); - deferred.reject(value); - return; - } - } - } - deferred.resolve(value); - }; - }, - - /* creates a promise bound to the resolution of a callback, and adds it to the - assertion queue. usage (note "callback" parameter) - - this.when(function(callback) { - doSomething(arg1,arg2,callback) - }).then(function(response) { - a.equals(expected,response) - }); - - */ - - when: function(func,timeout) { - var me=this, - t=timeout || me.timeout, - next = when.defer(), - last = me.promise; - - - me.promise = t ? when_timeout(next, t*1000) : next; - - // this promise will chain upon successful resolution of the callback to "next" - // however we still need a failure handler for "last" becase an error in "func" - // could cause it to never resolve. This is better than timing out. - - last.then(function() { - func.call(me,next.resolve); - }).then(null,function(err) { - me.testerror("An error occured during a 'when' operand: " + String(err),true); - next.reject(); - }); - - return me; - }, - - // create a new deferred object (same as when.defer) and bind completion of the tests to its - // resolution - - defer: function(callback,timeout) { - var me = this, - next = when.defer(), - t = timeout || me.timeout; - - // just replace the active promise -- there is no dependency on the prior - // promise because user code is responsible for resolving this promise. - - me.promise = t ? when_timeout(next, t*1000) : next; - - if (callback) { - // we don't need to bind an error handler to the callback because this is now - // the last promise on the chain. - next.then(callback); - } - return next; - }, - - /** - * Return a new promise identified by name, or an existing one with that name - * @param {string} name A name to identify this promist - * @return {[type]} A new or existing promist - */ - promises: function(name) { - if (typeof this.userPromises[name]==='undefined') { - this.userPromises[name]=when.defer(); - } - return this.userPromises[name]; - }, - // return a promise from a function that has a callback parameter - backpromise: function(func,callback,timeout) { - var defer=when.defer(), - me=this, - t=timeout || me.timeout, - cb=function() { - var value; - if (callback) { - if (me.debug) { - value=callback.apply(this,u.toArray(arguments)); - } else { - try - { - value=callback.apply(this,u.toArray(arguments)); - } - catch(err) - { - me.testerror("An error occurred in your backpromise() callback: "+err,true); - defer.reject(value); - return; - } - } - } - defer.resolve(value); - }; - - if (me.debug) { - func.call(me,cb); - } else { - try - { - func.call(me,cb); - } - catch(err) - { - me.testerror("An error occurred in your backpromise() function: "+err,true); - defer.reject(); - return; - } - } - - - return t ? when_timeout(defer, t*1000) : defer; - } - - }; - - // Global configuration - - options = u.extend({},globalDefaults); - - // PUBLIC API - - iqtestApi = { - // Create & return a new test group and configure with the options passed - create: function(name,desc,groupOpts) { - var finalOpts = u.extend({ - timeout: options.timeout, - setup: options.setup, - teardown: options.teardown - },groupOpts); - - var group = new TestGroup(name,desc,finalOpts); - return group; - }, - add: function () { - return this.add.apply(this,u.toArray(arguments)); - }, - extend: function(assertions) { - iq_asserts.push(assertions); - }, - // configure global options. - configure: function(newOpts) { - u.extend(options,newOpts,true); - }, - options: options, - // library of available writers; each should be a prototype that can be instantiated and exposing the - // correct api (see html implementation) - writers: {}, - impl: { - TestGroup: TestGroup, - Test: Test, - Assert: Assert, - utility: u - } - }; - return iqtestApi; -}); -}(typeof define === 'function' - ? define - : function (deps, factory) { - if (typeof module !== 'undefined') { - module.exports = factory(require('./when'), - require('./timeout'), - require('./buster-assertions'), - require('./common.utils') - ); - } else { - if (!this.iqtest_assertions) { - this.iqtest_assertions=[]; - } - this.iqtest = factory(this.common.utils,this.when,this.when_timeout,this.iqtest_assertions, - this.buster ? this.buster.assert : null); - } - } -)); /* -Assertions for IQ Test (other than buster) -This requires iqtest to use its utility methods so must be included afterwards - -Each assertion should throw an error when called with no args: "Expected 1 argument[s]" -This is necessary for iqtest to determine the position of the "message" argument - */ - -/*jslint eqeqeq:false */ -/*global define, require, module */ - - (function(define) { -define(function(iqtest) { - var u=iqtest.impl.utility, - output = u.formatAssert; - - // return true if exactly equal, or are not value types (e.g. ignore non-value types) - function valuesEqual(expected,actual) { - if (expected===actual) { - return true; - } else if (typeof expected !== typeof actual) { - return false; - } else if (!u.isValueType(expected)) { - return true; - } - } - function propertiesEqual(expected,actual,message, ignoreObjects) { - var reason='',count=0,actualCount=0; - if (typeof expected !== 'object' || typeof actual !== 'object') { - reason = u.format('the objects are not both objects'); - } else { - u.each(expected,function(prop,e) - { - if (typeof actual[prop]==='undefined') { - reason = u.format('the expected object has a property "{0}"" which does not exist on the actual object',prop); - return false; - } - if (ignoreObjects ? - !valuesEqual(actual[prop],expected[prop]) : - actual[prop]!==expected[prop]) { - reason = u.format('the expected object property "{0}" has value "{1}" which does not match the actual value "{2}"',prop,expected[prop],actual[prop]); - return false; - } - count++; - }); - if (!reason) { - u.each(actual,function() { - actualCount++; - }); - if (count!=actualCount) { - reason = u.format('the expected object has {0} properties, the actual has {1}',count,actualCount); - } - } - } - return { - passed: !reason, - err: output(message,reason) - }; - } - - /* Custom asserions: each should return an object with the format shown below. The err message must appear for both - positive and negative assertions, with a parameter for adding the word "not." Use the output function to do this - as in the example */ - - function contentsEqual(expected,actual,message) { - var result, - reason, index, - isArr = u.isArray(actual), - actualArr=actual, - expectedArr=expected; - - - // return first index at which arrays differ - function arraysEqual(obj1,obj2) { - for (var i=0;i=0) { - if (!isArr) { - index = '"'+getOrdinalName(actual,index)+'"'; - } - reason=u.format('sorted objects are {not}different at element {0}, expected "{1}" vs. actual "{2}"',index,expectedArr[index],actualArr[index]); - } - } - } else { - result = propertiesEqual(expected,actual,message); - } - } - - return result || { - passed: !reason, - err: output(message,reason) - }; - } - /// compare only value-typed properties - function valuePropertiesEqual(expected,actual,message) { - u.expectOpts(arguments,2); - return propertiesEqual(expected,actual,message,true); - } - - return { - // Two things should have the same contents. If this is an object, the values of each property must be identical. - // if an array, they must have the same elements, but order is irrelevant. - // If a string, it is split on commas and treated as a CSV. - collectionEquals: contentsEqual, - propertyEquals: propertiesEqual, - propertyValueEquals: valuePropertiesEqual - }; - - -}); -}(typeof define === 'function' - ? define - : function (factory) { - if (typeof module !== 'undefined') { - module.exports = factory(require('./iqtest')); - } else { - this.iqtest_assertions.push(factory(this.iqtest)); - } - } - // Boilerplate for AMD, Node, and browser global -)); - - -/* - An HTML output writer for iqtest - - Uses options on the TestGroup: - groupTemplate: {name} = group name - testTemplate: {name} = test name, {desc} = simple failure description - itemTemplate: {fulltext} - - This should append itself to iqtest.writers.xxx - -*/ -/*global iqtest, when */ -/*jslint smarttabs:true */ - -(function(iqtest) { - var u = iqtest.impl.utility, - options = { - group:'

    Starting test group "":

    '+ - '
    ', - testStart: '

    Starting test "":

    ', - testEnd: '

    assertions passed.

    ', - itemStart: '#: ' - +' ""....
    ', - itemEnd: '
    ', - log: '
    ', - // the following are just formats - resultSuccess: '', - resultFail:'', - showPassed: false - - }; - - // replace every element in "el" containing class "test-*" with the value of properties "*" - function tmpReplace(el,obj){ - var sel,replaceEl,prop; - for (prop in obj) { - if (obj.hasOwnProperty(prop)) { - sel = '.test-'+prop.toLowerCase(); - replaceEl = $(sel,el); - if (!replaceEl.length) { - replaceEl=$(el).filter(sel); - } - replaceEl.empty().append(obj[prop]); - } - } - return el; - } - - function getResultOutput(passed) { - var tmpl = passed ? options.resultSuccess: options.resultFail; - - return $(tmpl).text(passed ? "Passed":"Failed"); - } - - /* Implementation */ - - function groupStart(group) { - var groupWrapper = tmpReplace($(options.group).clone(),{ - name: group.name, - groupstatus: "Running" - }); - - this.container.append(groupWrapper); - - // should be the innermost div - this.groupWrapper = groupWrapper; - this.groupContainer = $(u.last(groupWrapper.find('div:only-child'))); - } - function groupEnd(group) { - tmpReplace(this.groupWrapper,{ - groupstatus: getResultOutput(group.passed) - }); - } - - function testStart(test) { - - var testData = this.getTestData(test.id), - content= tmpReplace($(options.testStart).clone(),{ - name: test.name, - teststatus: "Running" - }); - - testData.testWrapper = content; - testData.testContainer = content.filter('div'); - - this.groupContainer.append(content); - } - - function testEnd(test) { - var testData = this.getTestData(test.id), - content = tmpReplace($(options.testEnd).clone(),{ - count: test.count - }); - - testData.testContainer.append(content); - - tmpReplace(testData.testWrapper, { - teststatus: getResultOutput(test.passed) - }); - } - - function itemStart(test,testinfo) - { - var testData = this.getTestData(test.id), - tempItem= tmpReplace($(options.itemStart).clone(),{ - number: testinfo.count, - assertion: testinfo.assertion, - message: testinfo.desc - }); - - testData.itemTarget = tempItem; - testData.testContainer.append(tempItem); - } - function itemEnd(test,response) { - // this can get called without itemStart (prob should create a different kind of event for errors but...) - var testData = this.getTestData(test.id); - - if (response.passed) { - if (!options.showPassed) { - testData.itemTarget.remove(); - } - } else { - testData.itemTarget.replaceWith(tmpReplace($(options.itemEnd).clone(),{ - failmessage: response.fulltext - })); - } - testData.itemTarget=null; - response.written=true; - } - function testLog(test,message) { - var testData = this.getTestData(test.id); - testData.testContainer.append(tmpReplace($(options.log).clone(),{ - logmessage: message - })); - } - // function render() { - // var me=this; - - // u.each(me.results,function(i,result) { - // if (!result.written) { - // .event(this.group.itemEnd,this.group,result); - // } - // }); - // } - - // ensure that errors don't ever cause a promise to fail. errors in the harness should always - // cause execution to stop. - - function safeMethod(method) { - return function() { - try { - method.apply(this,u.toArray(arguments)); - } - catch(err) { - when.debug=true; - throw err; - } - }; - } - function HtmlWriter(container, opts) { - // when added to a TestGroup, the group should assign itself to owner - this.owner=null; - this.container=container; - this.tests={}; - - if (typeof opts==='object') { - $.extend(options,opts); - } - } - - HtmlWriter.prototype = { - constructor: HtmlWriter, - groupStart: safeMethod(groupStart), - groupEnd: safeMethod(groupEnd), - testStart: safeMethod(testStart), - testEnd: safeMethod(testEnd), - itemStart: safeMethod(itemStart), - itemEnd: safeMethod(itemEnd), - testLog: safeMethod(testLog), - // internal api - getTestData: function(id) { - if (!this.tests[id]) { - this.tests[id]={}; - } - return this.tests[id]; - } - }; - - - iqtest.writers.html = HtmlWriter; - -}(iqtest)); - +/** NOTE: MODIFIED - must not catch errors for use in IQTest */ + +/** @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.0.2 + */ + +(function(define) { +define(function() { + var freeze, reduceArray, undef; + + /** + * No-Op function used in method replacement + * @private + */ + function noop() {} + + /** + * Allocate a new Array of size n + * @private + * @param n {number} size of new Array + * @returns {Array} + */ + function allocateArray(n) { + return new Array(n); + } + + /** + * Use freeze if it exists + * @function + * @private + */ + freeze = Object.freeze || function(o) { return o; }; + + // 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; + }; + + /** + * Trusted Promise constructor. A Promise created from this constructor is + * a trusted when.js promise. Any other duck-typed promise is considered + * untrusted. + */ + function Promise() {} + + /** + * 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) { + checkCallbacks(arguments); + + var nextValue; + if (!when.debug) { + try { + nextValue = callback && callback(value); + return promise(nextValue === undef ? value : nextValue); + } catch(e) { + return rejected(e); + } + } else { + nextValue = callback && callback(value); + return promise(nextValue === undef ? value : nextValue); + } + }; + + // Not frozen because this should never be exposed + // to callers + return 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) { + checkCallbacks(arguments); + + var nextValue; + if (!when.debug) { + try { + if(errback) { + nextValue = errback(reason); + return promise(nextValue === undef ? reason : nextValue) + } + + return rejected(reason); + + } catch(e) { + return rejected(e); + } + } else { + if(errback) { + nextValue = errback(reason); + return promise(nextValue === undef ? reason : nextValue) + } + + return rejected(reason); + + } + }; + + // Not frozen because this should never be exposed + // to callers + return p; + } + + /** + * Helper that checks arrayOfCallbacks to ensure that each element is either + * a function, or null or undefined. + * + * @param arrayOfCallbacks {Array} array to check + * @throws {Error} if any element of arrayOfCallbacks is something other than + * a Functions, null, or undefined. + */ + function checkCallbacks(arrayOfCallbacks) { + var arg, i = arrayOfCallbacks.length; + while(i) { + arg = arrayOfCallbacks[--i]; + if (arg != null && typeof arg != 'function') throw new Error('callback is not a function'); + } + } + + /** + * 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) { + // Check parameters and fail immediately if any supplied parameter + // is not null/undefined and is also not a function. + // That is, any non-null/undefined parameter must be a function. + checkCallbacks(arguments); + + 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 + * @augments Resolver + * @augments Promise + */ + deferred = {}; + + // Promise and Resolver parts + // Freeze Promise and Resolver APIs + + /** + * The Promise API + * @namespace Promise + * @name Promise + */ + 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} + * @namespace Resolver + * @name Resolver + * @memberOf Deferred + * @name resolver + * @type {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) { + 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} 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) { + var results, promise; + + results = allocateArray(promisesOrValues.length); + promise = reduce(promisesOrValues, reduceIntoArray, results); + + return when(promise, 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} 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(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 promisesOrValues {Array} 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(promisesOrValues, mapFunc) { + + var results, i; + + // Since we know the resulting length, we can preallocate the results + // array to avoid array expansions. + i = promisesOrValues.length; + results = allocateArray(i); + + // 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) { + 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, but reduceFunc + * may return either a value or a {@link Promise}, *and* initialValue may + * be a {@link Promise} for the starting value. + * + * @memberOf when + * + * @param promisesOrValues {Array} array of anything, may contain a mix + * of {@link Promise}s and values + * @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(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 >= 3) args.push(initialValue); + + return promise(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) { + resolver.resolve(useResolveValue ? resolveValue : val); + }, + resolver.reject, + resolver.progress + ); + } + + // + // Public API + // + + when.defer = defer; + + when.isPromise = isPromise; + when.some = some; + when.all = all; + when.any = any; + + when.reduce = reduce; + when.map = map; + + when.chain = chain; + + // when true, will not trap errors + + when.debug = false; + return when; +}); +})(typeof define == 'function' + ? define + : function (factory) { typeof module != 'undefined' + ? (module.exports = factory()) + : (this.when = factory()); + } + // Boilerplate for AMD, Node, and browser global +);/** @license MIT License (c) copyright B Cavalier & J Hann */ + +/** + * timeout.js + * + * Helper that returns a promise that rejects after a specified timeout, + * if not explicitly resolved or rejected before that. + * + * @author brian@hovercraftstudios.com + */ + +(function(define) { +define(['./when'], function(when) { + + var undef; + + /** + * Returns a new promise that will automatically reject after msec if + * the supplied promise doesn't resolve or reject before that. + * + * Usage: + * + * var d = when.defer(); + * // Setup d however you need + * + * // return a new promise that will timeout if we don't resolve/reject first + * return timeout(d, 1000); + * + * @param promise anything - any promise or value that should trigger + * the returned promise to resolve or reject before the msec timeout + * @param msec {Number} timeout in milliseconds + * + * @returns {Promise} + */ + return function timeout(promise, msec) { + var deferred, timeout; + + deferred = when.defer(); + + timeout = setTimeout(function onTimeout() { + timeout && deferred.reject(new Error('timed out')); + }, msec); + + function cancelTimeout() { + clearTimeout(timeout); + timeout = undef; + } + + when(promise, deferred.resolve, deferred.reject); + + return deferred.then( + function(value) { + cancelTimeout(); + return value; + }, + function(reason) { + cancelTimeout(); + throw reason; + } + ); + }; + +}); +})(typeof define == 'function' + ? define + : function (deps, factory) { typeof module != 'undefined' + ? (module.exports = factory(require('./when'))) + : (this.when_timeout = factory(this.when)); + } + // Boilerplate for AMD, Node, and browser global +); + +var buster = (function (setTimeout, B) { + var isNode = typeof require == "function" && typeof module == "object"; + var div = typeof document != "undefined" && document.createElement("div"); + var F = function () {}; + + var buster = { + bind: function bind(obj, methOrProp) { + var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; + var args = Array.prototype.slice.call(arguments, 2); + return function () { + var allArgs = args.concat(Array.prototype.slice.call(arguments)); + return method.apply(obj, allArgs); + }; + }, + + partial: function partial(fn) { + var args = [].slice.call(arguments, 1); + return function () { + return fn.apply(this, args.concat([].slice.call(arguments))); + }; + }, + + create: function create(object) { + F.prototype = object; + return new F(); + }, + + extend: function extend(target) { + if (!target) { return; } + for (var i = 1, l = arguments.length, prop; i < l; ++i) { + for (prop in arguments[i]) { + target[prop] = arguments[i][prop]; + } + } + return target; + }, + + nextTick: function nextTick(callback) { + if (typeof process != "undefined" && process.nextTick) { + return process.nextTick(callback); + } + setTimeout(callback, 0); + }, + + functionName: function functionName(func) { + if (!func) return ""; + if (func.displayName) return func.displayName; + if (func.name) return func.name; + var matches = func.toString().match(/function\s+([^\(]+)/m); + return matches && matches[1] || ""; + }, + + isNode: function isNode(obj) { + if (!div) return false; + try { + obj.appendChild(div); + obj.removeChild(div); + } catch (e) { + return false; + } + return true; + }, + + isElement: function isElement(obj) { + return obj && obj.nodeType === 1 && buster.isNode(obj); + }, + + isArray: function isArray(arr) { + return Object.prototype.toString.call(arr) == "[object Array]"; + }, + + flatten: function flatten(arr) { + var result = [], arr = arr || []; + for (var i = 0, l = arr.length; i < l; ++i) { + result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); + } + return result; + }, + + each: function each(arr, callback) { + for (var i = 0, l = arr.length; i < l; ++i) { + callback(arr[i]); + } + }, + + map: function map(arr, callback) { + var results = []; + for (var i = 0, l = arr.length; i < l; ++i) { + results.push(callback(arr[i])); + } + return results; + }, + + parallel: function parallel(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + callback = null; + } + } + if (fns.length == 0) { return cb(null, []); } + var remaining = fns.length, results = []; + function makeDone(num) { + return function done(err, result) { + if (err) { return cb(err); } + results[num] = result; + if (--remaining == 0) { cb(null, results); } + }; + } + for (var i = 0, l = fns.length; i < l; ++i) { + fns[i](makeDone(i)); + } + }, + + series: function series(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + } + } + var remaining = fns.slice(); + var results = []; + function callNext() { + if (remaining.length == 0) return cb(null, results); + var promise = remaining.shift()(next); + if (promise && typeof promise.then == "function") { + promise.then(buster.partial(next, null), next); + } + } + function next(err, result) { + if (err) return cb(err); + results.push(result); + callNext(); + } + callNext(); + }, + + countdown: function countdown(num, done) { + return function () { + if (--num == 0) done(); + }; + } + }; + + if (Array.prototype.some) { + buster.some = function (arr, fn, thisp) { + return arr.some(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some + buster.some = function (arr, fun, thisp) { + "use strict"; + if (arr == null) { throw new TypeError(); } + arr = Object(arr); + var len = arr.length >>> 0; + if (typeof fun !== "function") { throw new TypeError(); } + + for (var i = 0; i < len; i++) { + if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + return true; + } + } + + return false; + }; + } + + if (isNode) { + module.exports = buster; + buster.eventEmitter = require("./buster-event-emitter"); + Object.defineProperty(buster, "defineVersionGetter", { + get: function () { + return require("./define-version-getter"); + } + }); + } + + return buster.extend(B || {}, buster); +}(setTimeout, buster)); +/* global buster, require, module */ +if (typeof require == "function" && typeof module == "object") { + var buster = require("./buster-core"); +} + +(function () { + function eventListeners(eventEmitter, event) { + if (!eventEmitter.listeners) { + eventEmitter.listeners = {}; + } + + if (!eventEmitter.listeners[event]) { + eventEmitter.listeners[event] = []; + } + + return eventEmitter.listeners[event]; + } + + function throwLater(event, error) { + buster.nextTick(function () { + error.message = event + " listener threw error: " + error.message; + throw error; + }); + } + + function addSupervisor(emitter, listener, thisObject) { + if (!emitter.supervisors) { emitter.supervisors = []; } + emitter.supervisors.push({ + listener: listener, + thisObject: thisObject + }); + } + + function notifyListener(emitter, event, listener, args) { + try { + listener.listener.apply(listener.thisObject || emitter, args); + } catch (e) { + throwLater(event, e); + } + } + + buster.eventEmitter = { + create: function () { + return buster.create(this); + }, + + addListener: function addListener(event, listener, thisObject) { + if (typeof event === "function") { + return addSupervisor(this, event, listener); + } + if (typeof listener != "function") { + throw new TypeError("Listener is not function"); + } + eventListeners(this, event).push({ + listener: listener, + thisObject: thisObject + }); + }, + + once: function once(event, listener, thisObject) { + var self = this; + this.addListener(event, listener); + + var wrapped = function () { + self.removeListener(event, listener); + self.removeListener(event, wrapped); + }; + this.addListener(event, wrapped); + }, + + hasListener: function hasListener(event, listener, thisObject) { + var listeners = eventListeners(this, event); + + for (var i = 0, l = listeners.length; i < l; i++) { + if (listeners[i].listener === listener && + listeners[i].thisObject === thisObject) { + return true; + } + } + + return false; + }, + + removeListener: function (event, listener) { + var listeners = eventListeners(this, event); + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i].listener == listener) { + listeners.splice(i, 1); + return; + } + } + }, + + emit: function emit(event) { + var listeners = eventListeners(this, event); + var args = Array.prototype.slice.call(arguments, 1); + + for (var i = 0, l = listeners.length; i < l; i++) { + notifyListener(this, event, listeners[i], args); + } + + listeners = this.supervisors || []; + args = Array.prototype.slice.call(arguments); + for (i = 0, l = listeners.length; i < l; ++i) { + notifyListener(this, event, listeners[i], args); + } + }, + + bind: function (object, events) { + var method; + + if (!events) { + for (method in object) { + if (object.hasOwnProperty(method) && typeof object[method] == "function") { + this.addListener(method, object[method], object); + } + } + } else if (typeof events == "string" || + Object.prototype.toString.call(events) == "[object Array]") { + events = typeof events == "string" ? [events] : events; + + for (var i = 0, l = events.length; i < l; ++i) { + this.addListener(events[i], object[events[i]], object); + } + } else { + for (var prop in events) { + if (events.hasOwnProperty(prop)) { + method = events[prop]; + + if (typeof method == "function") { + object[buster.functionName(method) || prop] = method; + } else { + method = object[events[prop]]; + } + + this.addListener(prop, method, object); + } + } + } + + return object; + } + }; + + buster.eventEmitter.on = buster.eventEmitter.addListener; +}()); + +if (typeof module != "undefined") { + module.exports = buster.eventEmitter; +} +/* global buster, require, module */ +(function () { + var isCommonJS = typeof require == "function" && typeof module == "object"; + if (isCommonJS) buster = require("buster-core"); + var toString = Object.prototype.toString; + var slice = Array.prototype.slice; + var assert, refute, ba = buster.assertions = buster.eventEmitter.create(); + + if (isCommonJS) { + module.exports = buster.assertions; + } + + function countAssertion() { + if (typeof ba.count != "number") { + ba.count = 0; + } + + ba.count += 1; + } + + ba.count = countAssertion; + + function assertEnoughArguments(name, args, num) { + if (args.length < num) { + ba.fail("[" + name + "] Expected to receive at least " + + num + " argument" + (num > 1 ? "s" : "")); + return false; + } + + return true; + } + + function defineAssertion(type, name, func, fl, messageValues) { + ba[type][name] = function () { + var fullName = type + "." + name; + countAssertion(); + if (!assertEnoughArguments(fullName, arguments, fl || func.length)) return; + + var failed = false; + + var ctx = { + fail: function () { + failed = true; + var failArgs = [type, name].concat(slice.call(arguments)); + fail.apply(this, failArgs); + return true; + } + }; + + var args = slice.call(arguments, 0); + + if (typeof messageValues == "function") { + args = messageValues.apply(this, args); + } + + if (!func.apply(ctx, arguments)) { + return fail.apply(ctx, [type, name, "message"].concat(args)); + } + + if (!failed) { + ba.emit.apply(ba, ["pass", fullName].concat(args)); + } + }; + } + + ba.add = function (name, options) { + var refuteArgs; + + if (options.refute) { + refuteArgs = options.refute.length; + } else { + refuteArgs = options.assert.length; + options.refute = function () { + return !options.assert.apply(this, arguments); + }; + } + + var values = options && options.values; // TODO: Remove + defineAssertion("assert", name, options.assert, options.assert.length, values); + defineAssertion("refute", name, options.refute, refuteArgs, values); + + assert[name].message = options.assertMessage; + refute[name].message = options.refuteMessage; + + if (options.expectation) { + if (ba.expect && ba.expect.wrapAssertion) { + ba.expect.wrapAssertion(name, options.expectation); + } else { + assert[name].expectationName = options.expectation; + refute[name].expectationName = options.expectation; + } + } + }; + + function interpolate(string, property, value) { + return string.replace(new RegExp("\\$\\{" + property + "\\}", "g"), value); + } + + function interpolatePosArg(message, values) { + var value; + values = values || []; + + for (var i = 0, l = values.length; i < l; i++) { + message = interpolate(message, i, ba.format(values[i])); + } + + return message; + } + + function interpolateProperties(msg, properties) { + for (var prop in properties) { + msg = interpolate(msg, prop, ba.format(properties[prop])); + } + + return msg || ""; + } + + function fail(type, assertion, msg) { + delete this.fail; + var message = interpolateProperties( + interpolatePosArg(ba[type][assertion][msg] || msg, + [].slice.call(arguments, 3)), this); + ba.fail("[" + type + "." + assertion + "] " + message); + } + + function isDate(value) { + // Duck typed dates, allows objects to take on the role of dates + // without actually being dates + return typeof value.getTime == "function" && + value.getTime() == value.valueOf(); + } + + ba.isDate = isDate; + + // Fixes NaN === NaN (should be true) and + // -0 === +0 (should be false) + // http://wiki.ecmascript.org/doku.php?id=harmony:egal + function egal(x, y) { + if (x === y) { + // 0 === -0, but they are not identical + return x !== 0 || 1 / x === 1 / y; + } + + // NaN !== NaN, but they are identical. + // NaNs are the only non-reflexive value, i.e., if x !== x, + // then x is a NaN. + // isNaN is broken: it converts its argument to number, so + // isNaN("foo") => true + return x !== x && y !== y; + } + + function areEqual(expected, actual) { + if (egal(expected, actual)) { + return true; + } + + // Elements are only equal if expected === actual + if (buster.isElement(expected) || buster.isElement(actual)) { + return false; + } + + // null and undefined only pass for null === null and + // undefined === undefined + /*jsl: ignore*/ + if (expected == null || actual == null) { + return actual === expected; + } + /*jsl: end*/ + + if (isDate(expected) || isDate(actual)) { + return isDate(expected) && isDate(actual) && + expected.getTime() == actual.getTime(); + } + + var useCoercingEquality = typeof expected != "object" || typeof actual != "object"; + + if (expected instanceof RegExp && actual instanceof RegExp) { + if (expected.toString() != actual.toString()) { + return false; + } + + useCoercingEquality = false; + } + + // Arrays can only be equal to arrays + var expectedStr = toString.call(expected); + var actualStr = toString.call(actual); + + // Coerce and compare when primitives are involved + if (useCoercingEquality) { + return expectedStr != "[object Array]" && actualStr != "[object Array]" && + expected == actual; + } + + var expectedKeys = ba.keys(expected); + var actualKeys = ba.keys(actual); + + if (isArguments(expected) || isArguments(actual)) { + if (expected.length != actual.length) { + return false; + } + } else { + if (typeof expected != typeof actual || expectedStr != actualStr || + expectedKeys.length != actualKeys.length) { + return false; + } + } + + var key; + + for (var i = 0, l = expectedKeys.length; i < l; i++) { + key = expectedKeys[i]; + + if (!Object.prototype.hasOwnProperty.call(actual, key) || + !areEqual(expected[key], actual[key])) { + return false; + } + } + + return true; + } + + ba.deepEqual = areEqual; + + assert = ba.assert = function assert(actual, message) { + countAssertion(); + if (!assertEnoughArguments("assert", arguments, 1)) return; + + if (!actual) { + var val = ba.format(actual) + ba.fail(message || "[assert] Expected " + val + " to be truthy"); + } else { + ba.emit("pass", "assert", message || "", actual); + } + }; + + assert.toString = function () { + return "buster.assert"; + }; + + refute = ba.refute = function (actual, message) { + countAssertion(); + if (!assertEnoughArguments("refute", arguments, 1)) return; + + if (actual) { + var val = ba.format(actual) + ba.fail(message || "[refute] Expected " + val + " to be falsy"); + } else { + ba.emit("pass", "refute", message || "", actual); + } + }; + + assert.message = "[assert] Expected ${0} to be truthy"; + ba.count = 0; + + ba.fail = function (message) { + var exception = new Error(message); + exception.name = "AssertionError"; + + try { + throw exception; + } catch (e) { + ba.emit("failure", e); + } + + if (typeof ba.throwOnFailure != "boolean" || ba.throwOnFailure) { + throw exception; + } + }; + + ba.format = function (object) { + return "" + object; + }; + + function msg(message) { + if (!message) { return ""; } + return message + (/[.:!?]$/.test(message) ? " " : ": "); + } + + function actualAndExpectedMessageValues(actual, expected, message) { + return [actual, expected, msg(message)] + } + + function actualMessageValues(actual) { + return [actual, msg(arguments[1])]; + } + + function actualAndTypeOfMessageValues(actual) { + return [actual, typeof actual, msg(arguments[1])]; + } + + ba.add("same", { + assert: function (actual, expected) { + return egal(actual, expected); + }, + refute: function (actual, expected) { + return !egal(actual, expected); + }, + assertMessage: "${2}${0} expected to be the same object as ${1}", + refuteMessage: "${2}${0} expected not to be the same object as ${1}", + expectation: "toBe", + values: actualAndExpectedMessageValues + }); + + function multiLineStringDiff(actual, expected, message) { + if (actual == expected) return true; + + var message = interpolatePosArg(assert.equals.multiLineStringHeading, [message]), + actualLines = actual.split("\n"), + expectedLines = expected.split("\n"), + lineCount = Math.max(expectedLines.length, actualLines.length), + lines = []; + + for (var i = 0; i < lineCount; ++i) { + if (expectedLines[i] != actualLines[i]) { + lines.push("line " + (i + 1) + ": " + (expectedLines[i] || "") + + "\nwas: " + (actualLines[i] || "")); + } + } + + ba.fail("[assert.equals] " + message + lines.join("\n\n")); + return false; + } + + ba.add("equals", { + assert: function (actual, expected) { + if (typeof actual == "string" && typeof expected == "string" && + (actual.indexOf("\n") >= 0 || expected.indexOf("\n") >= 0)) { + var message = msg(arguments[2]); + return multiLineStringDiff.call(this, actual, expected, message); + } + + return areEqual(actual, expected); + }, + + refute: function (actual, expected) { + return !areEqual(actual, expected); + }, + + assertMessage: "${2}${0} expected to be equal to ${1}", + refuteMessage: "${2}${0} expected not to be equal to ${1}", + expectation: "toEqual", + values: actualAndExpectedMessageValues + }); + + assert.equals.multiLineStringHeading = "${0}Expected multi-line strings to be equal:\n"; + + ba.add("greater", { + assert: function (actual, expected) { + return actual > expected; + }, + + assertMessage: "${2}Expected ${0} to be greater than ${1}", + refuteMessage: "${2}Expected ${0} to be less than or equal to ${1}", + expectation: "toBeGreaterThan", + values: actualAndExpectedMessageValues + }); + + ba.add("less", { + assert: function (actual, expected) { + return actual < expected; + }, + + assertMessage: "${2}Expected ${0} to be less than ${1}", + refuteMessage: "${2}Expected ${0} to be greater than or equal to ${1}", + expectation: "toBeLessThan", + values: actualAndExpectedMessageValues + }); + + ba.add("defined", { + assert: function (actual) { + return typeof actual != "undefined"; + }, + assertMessage: "${2}Expected to be defined", + refuteMessage: "${2}Expected ${0} (${1}) not to be defined", + expectation: "toBeDefined", + values: actualAndTypeOfMessageValues + }); + + ba.add("isNull", { + assert: function (actual) { + return actual === null; + }, + assertMessage: "${1}Expected ${0} to be null", + refuteMessage: "${1}Expected not to be null", + expectation: "toBeNull", + values: actualMessageValues + }); + + function match(object, matcher) { + if (matcher && typeof matcher.test == "function") { + return matcher.test(object); + } + + if (typeof matcher == "function") { + return matcher(object) === true; + } + + if (typeof matcher == "string") { + matcher = matcher.toLowerCase(); + return !!object && ("" + object).toLowerCase().indexOf(matcher) >= 0; + } + + if (typeof matcher == "number") { + return matcher == object; + } + + if (typeof matcher == "boolean") { + return matcher === object; + } + + if (matcher && typeof matcher == "object") { + for (var prop in matcher) { + if (!match(object[prop], matcher[prop])) { + return false; + } + } + + return true; + } + + throw new Error("Matcher (" + ba.format(matcher) + ") was not a " + + "string, a number, a function, a boolean or an object"); + } + + ba.match = match; + + ba.add("match", { + assert: function (actual, matcher) { + var passed; + + try { + passed = match(actual, matcher); + } catch (e) { + return this.fail("exceptionMessage", e.message, msg(arguments[2])); + } + + return passed; + }, + + refute: function (actual, matcher) { + var passed; + + try { + passed = match(actual, matcher); + } catch (e) { + return this.fail("exceptionMessage", e.message); + } + + return !passed; + }, + + assertMessage: "${2}${0} expected to match ${1}", + refuteMessage: "${2}${0} expected not to match ${1}", + expectation: "toMatch", + values: actualAndExpectedMessageValues + }); + + assert.match.exceptionMessage = "${1}${0}"; + refute.match.exceptionMessage = "${1}${0}"; + + ba.add("isObject", { + assert: function (actual) { + return typeof actual == "object" && !!actual; + }, + assertMessage: "${2}${0} (${1}) expected to be object and not null", + refuteMessage: "${2}${0} expected to be null or not an object", + expectation: "toBeObject", + values: actualAndTypeOfMessageValues + }); + + ba.add("isFunction", { + assert: function (actual) { + return typeof actual == "function"; + }, + assertMessage: "${2}${0} (${1}) expected to be function", + refuteMessage: "${2}${0} expected not to be function", + expectation: "toBeFunction", + values: function (actual) { + return [("" + actual).replace("\n", ""), typeof actual, msg(arguments[1])]; + } + }); + + ba.add("isTrue", { + assert: function (actual) { + return actual === true; + }, + assertMessage: "${1}Expected ${0} to be true", + refuteMessage: "${1}Expected ${0} to not be true", + expectation: "toBeTrue", + values: actualMessageValues + }); + + ba.add("isFalse", { + assert: function (actual) { + return actual === false; + }, + assertMessage: "${1}Expected ${0} to be false", + refuteMessage: "${1}Expected ${0} to not be false", + expectation: "toBeFalse", + values: actualMessageValues + }); + + ba.add("isString", { + assert: function (actual) { + return typeof actual == "string"; + }, + assertMessage: "${2}Expected ${0} (${1}) to be string", + refuteMessage: "${2}Expected ${0} not to be string", + expectation: "toBeString", + values: actualAndTypeOfMessageValues + }); + + ba.add("isBoolean", { + assert: function (actual) { + return typeof actual == "boolean"; + }, + assertMessage: "${2}Expected ${0} (${1}) to be boolean", + refuteMessage: "${2}Expected ${0} not to be boolean", + expectation: "toBeBoolean", + values: actualAndTypeOfMessageValues + }); + + ba.add("isNumber", { + assert: function (actual) { + return typeof actual == "number" && !isNaN(actual); + }, + assertMessage: "${2}Expected ${0} (${1}) to be a non-NaN number", + refuteMessage: "${2}Expected ${0} to be NaN or another non-number value", + expectation: "toBeNumber", + values: actualAndTypeOfMessageValues + }); + + ba.add("isNaN", { + assert: function (actual) { + return typeof actual == "number" && isNaN(actual); + }, + assertMessage: "${2}Expected ${0} to be NaN", + refuteMessage: "${2}Expected not to be NaN", + expectation: "toBeNaN", + values: actualAndTypeOfMessageValues + }); + + ba.add("isArray", { + assert: function (actual) { + return toString.call(actual) == "[object Array]"; + }, + assertMessage: "${2}Expected ${0} to be array", + refuteMessage: "${2}Expected ${0} not to be array", + expectation: "toBeArray", + values: actualAndTypeOfMessageValues + }); + + function isArrayLike(object) { + return toString.call(object) == "[object Array]" || + (!!object && typeof object.length == "number" && + typeof object.splice == "function") || + ba.isArguments(object); + } + + ba.isArrayLike = isArrayLike; + + ba.add("isArrayLike", { + assert: function (actual) { + return isArrayLike(actual); + }, + assertMessage: "${2}Expected ${0} to be array like", + refuteMessage: "${2}Expected ${0} not to be array like", + expectation: "toBeArrayLike", + values: actualAndTypeOfMessageValues + }); + + function captureException(callback) { + try { + callback(); + } catch (e) { + return e; + } + + return null; + } + + ba.captureException = captureException; + + assert.exception = function (callback, exception, message) { + countAssertion(); + if (!assertEnoughArguments("assert.exception", arguments, 1)) return + + if (!callback) { + return; + } + + var err = captureException(callback); + message = msg(message); + + if (!err) { + if (exception) { + return fail.call({}, "assert", "exception", "typeNoExceptionMessage", + message, exception); + } else { + return fail.call({}, "assert", "exception", "message", + message, exception); + } + } + + if (exception && err.name != exception) { + if (typeof window != "undefined" && typeof console != "undefined") { + console.log(err); + } + + return fail.call({}, "assert", "exception", "typeFailMessage", + message, exception, err.name, err.message); + } + + ba.emit("pass", "assert.exception", message, callback, exception); + }; + + assert.exception.typeNoExceptionMessage = "${0}Expected ${1} but no exception was thrown"; + assert.exception.message = "${0}Expected exception"; + assert.exception.typeFailMessage = "${0}Expected ${1} but threw ${2} (${3})"; + assert.exception.expectationName = "toThrow"; + + refute.exception = function (callback) { + countAssertion(); + if (!assertEnoughArguments("refute.exception", arguments, 1)) return; + + var err = captureException(callback); + + if (err) { + fail.call({}, "refute", "exception", "message", + msg(arguments[1]), err.name, err.message, callback); + } else { + ba.emit("pass", "refute.exception", callback); + } + }; + + refute.exception.message = "${0}Expected not to throw but threw ${1} (${2})"; + refute.exception.expectationName = "toThrow"; + + ba.add("near", { + assert: function (actual, expected, delta) { + return Math.abs(actual - expected) <= delta; + }, + assertMessage: "${3}Expected ${0} to be equal to ${1} +/- ${2}", + refuteMessage: "${3}Expected ${0} not to be equal to ${1} +/- ${2}", + expectation: "toBeNear", + values: function (actual, expected, delta, message) { + return [actual, expected, delta, msg(message)]; + } + }); + + ba.add("hasPrototype", { + assert: function (actual, protoObj) { + return protoObj.isPrototypeOf(actual); + }, + assertMessage: "${2}Expected ${0} to have ${1} on its prototype chain", + refuteMessage: "${2}Expected ${0} not to have ${1} on its prototype chain", + expectation: "toHavePrototype", + values: actualAndExpectedMessageValues + }); + + ba.add("contains", { + assert: function (haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return true; + } + } + return false; + }, + assertMessage: "${2}Expected [${0}] to contain ${1}", + refuteMessage: "${2}Expected [${0}] not to contain ${1}", + expectation: "toContain", + values: actualAndExpectedMessageValues + }); + + ba.add("tagName", { + assert: function (element, tagName) { + if (!element.tagName) { + return this.fail("noTagNameMessage", tagName, element, msg(arguments[2])); + } + + return tagName.toLowerCase && + tagName.toLowerCase() == element.tagName.toLowerCase(); + }, + assertMessage: "${2}Expected tagName to be ${0} but was ${1}", + refuteMessage: "${2}Expected tagName not to be ${0}", + expectation: "toHaveTagName", + values: function (element, tagName, message) { + return [tagName, element.tagName, msg(message)]; + } + }); + + assert.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; + refute.tagName.noTagNameMessage = "${2}Expected ${1} to have tagName property"; + + function indexOf(arr, item) { + for (var i = 0, l = arr.length; i < l; i++) { + if (arr[i] == item) { + return i; + } + } + + return -1; + } + + ba.add("className", { + assert: function (element, className) { + if (typeof element.className == "undefined") { + return this.fail("noClassNameMessage", className, element, msg(arguments[2])); + } + + var expected = typeof className == "string" ? className.split(" ") : className; + var actual = element.className.split(" "); + + for (var i = 0, l = expected.length; i < l; i++) { + if (indexOf(actual, expected[i]) < 0) { + return false; + } + } + + return true; + }, + assertMessage: "${2}Expected object's className to include ${0} but was ${1}", + refuteMessage: "${2}Expected object's className not to include ${0}", + expectation: "toHaveClassName", + values: function (element, className, message) { + return [className, element.className, msg(message)]; + } + }); + + assert.className.noClassNameMessage = "${2}Expected object to have className property"; + refute.className.noClassNameMessage = "${2}Expected object to have className property"; + + if (typeof module != "undefined") { + ba.expect = function () { + ba.expect = require("./buster-assertions/expect"); + return ba.expect.apply(exports, arguments); + }; + } + + function isArguments(obj) { + if (typeof obj != "object" || typeof obj.length != "number" || + toString.call(obj) == "[object Array]") { + return false; + } + + if (typeof obj.callee == "function") { + return true; + } + + try { + obj[obj.length] = 6; + delete obj[obj.length]; + } catch (e) { + return true; + } + + return false; + } + + ba.isArguments = isArguments; + + if (Object.keys) { + ba.keys = function (obj) { + return Object.keys(obj) + }; + } else { + ba.keys = function (object) { + var keys = []; + + for (var prop in object) { + if (Object.prototype.hasOwnProperty.call(object, prop)) { + keys.push(prop); + } + } + + return keys; + } + } +}()); +/* common.utils.js: a core framework library of utilities and polyfills. + +This adds utility functions into a namespace u. + +Standard polyfills are automatically added to their prototypes. The following nonstandard prototype +changes are made: + +String.format +String.split with trim option +Array.contains +Array.first + +You can remove the call to u.polyfill to prevent the nonstandard changes. + +Version 1.0.1 - 6/19/1012 + +James Treworgy +*/ + +/* global define, require, module */ +(function (define) { + define(function () { + var u; + + /* General puropose functions */ + + function isBool(obj) { + return typeof obj === 'boolean'; + } + function isString(obj) { + return typeof obj === 'string'; + } + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + function isArray(obj) { + return obj && obj.constructor === Array; + } + + /* prototype extension functions - these must be called with a context */ + + // trim a string leading & trailing whitespace + function stringTrim() { + return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + } + + // generic iterator. when trim is true, string values are trimmed. + function forEach(cb, trim) { + var coll = this, + i, val; + if (!coll) return; + + if (isString(coll)) { + coll = coll.split(','); + } + if (isArray(coll)) { + for (i = 0; i < coll.length; i++) { + val = isString(coll[i]) ? + stringTrim.call(coll[i]) : + coll[i]; + + if (cb.call(val, i, val) === false) { + break; + } + } + } else { + for (i in coll) { + if (coll.hasOwnProperty(i)) { + if (cb.call(coll[i], i, coll[i]) === false) { + break; + } + } + } + } + } + // string format function + function format() { + var args = (arguments.length === 1 && isArray(arguments[0])) ? + arguments[0] : + arguments; + return this.replace(/\{(\d+)\}/g, function (match, number) { + var num = parseInt(number, 10); + return !isUndefined(args[num]) + ? String(args[num]) + : match; + }); + } + + // a split function that trims its results. any 'true' bool parameter will be interpreted as a flag to trim + function stringSplit(delimiter, trimResults) { + var result = [], + delim = isString(delimiter) ? + delimiter : ',', + trim = isBool(delimiter) ? + delimiter : + isBool(trimResults) ? + trimResults : false; + + forEach.call(String.prototype.split.call(this, delim || ','), function (i, e) { + result.push(trim ? stringTrim(e) : e); + }); + return result; + } + + // polyfills + + function arrayForEach(action, that) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this) + action.call(that, this[i], i, this); + } + + function arrayIndexOf(find, i /*opt*/) { + if (i === undefined) i = 0; + if (i < 0) i += this.length; + if (i < 0) i = 0; + for (var n = this.length; i < n; i++) + if (i in this && this[i] === find) + return i; + return -1; + } + + // returns true if the element exists + function arrayContains(val) { + return arrayIndexOf.call(this, val) >= 0; + } + + // NONSTANDARD + + // return the first element where filter returns true + function arrayFirst(filter) { + var i, undef; + + if (!filter) { + return this.length > 0 ? this[0] : undef; + } + + for (i = 0; i < this.length; i++) { + if (filter.call(this[i], i, this[i])) { + return this[i]; + } + } + return undef; + } + + // return the first element where filter returns true + function arrayLast(filter) { + var i, undef; + + if (!filter) { + return this.length > 0 ? this[this.length-1] : undef; + } + + for (i = this.length; i >0; i--) { + if (filter.call(this[i], i, this[i])) { + return this[i]; + } + } + return undef; + } + + function arrayLastIndexOf(find, i /*opt*/) { + if (i === undefined) i = this.length - 1; + if (i < 0) i += this.length; + if (i > this.length - 1) i = this.length - 1; + for (i++; i-- > 0; ) /* i++ because from-argument is sadly inclusive */ + if (i in this && this[i] === find) + return i; + return -1; + } + + function arrayMap(mapper, that /*opt*/) { + var n, i, other = new Array(this.length); + for (i = 0, n = this.length; i < n; i++) + if (i in this) + other[i] = mapper.call(that, this[i], i, this); + return other; + } + + function arrayFilter(filter, that /*opt*/) { + var i, n, other = [], v; + for (i = 0, n = this.length; i < n; i++) + if (i in this && filter.call(that, v = this[i], i, this)) + other.push(v); + return other; + } + + function arrayEvery(tester, that /*opt*/) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this && !tester.call(that, this[i], i, this)) + return false; + return true; + } + + function arraySome(tester, that /*opt*/) { + for (var i = 0, n = this.length; i < n; i++) + if (i in this && tester.call(that, this[i], i, this)) + return true; + return false; + } + + u = { + // when onlyInTarget is true, properties will not be added - only updated + // passing a falsy value as the target results in a new object being created + // and onlyInTarget is ignored (properties must be added to a new object) + + extend: function (target /*[,onlyInTarget]*/) { + var prop, source, sources, i, + li = arguments.length, + lastBool = u.isBool(arguments[li - 1]), + len = lastBool ? + li - 2 : li - 1, + emptyTarget = !target, + onlyInTarget = lastBool ? + arguments[len + 1] : false; + + target = target || {}; + + sources = u.toArray(arguments, 1, len + 1); + + for (i = 0; i < sources.length; i++) { + source = sources[i]; + for (prop in source) { + if (source.hasOwnProperty(prop) + && (emptyTarget || !onlyInTarget || target.hasOwnProperty(prop))) { + target[prop] = source[prop]; + } + } + // start honoring onlyInTarget after the first source + emptyTarget = false; + } + return target; + }, + // copy selected properties to a new object + filterProps: function (source, what) { + var target = {}, + props = u.isArray(what) ? + what : + what.split(','); + + u.each(props, function (i, prop) { + target[prop] = source[prop]; + }); + return target; + }, + toArray: function (arrLike, first, last) { + return Array.prototype.slice.call(arrLike, first || 0, last || arrLike.length); + }, + isArray: isArray, + arrayIndexOf: function (arr, val) { + return arrayIndexOf.call(arr, val); + }, + inArray: function (arr, val) { + return arrayContains.call(arr, val); + }, + isFunction: function (obj) { + return typeof obj === 'function'; + }, + isUndefined: isUndefined, + isString: isString, + isBool: isBool, + isValueType: function (obj) { + return u.inArray(['boolean', 'number', 'string'], typeof (obj)); + }, + trim: function (str) { + return stringTrim.call(str); + }, + //split with trim (why would you want it any other way?) + split: function (str, delim) { + return stringSplit.call(str, delim); + }, + // replaces {0}.. {n} with the ordinal valued parameter. You can also pass an + // array instead of multiple parameters + format: function () { + return format.apply(arguments[0], u.toArray(arguments, 1)); + }, + // usual each, if you happen to pass a string, it will split it on commas. + // it will always trim string values in an array. + each: function (coll, cb) { + coll && forEach.call(coll, cb); + }, + first: function(arr,filter) { + + return arrayFirst.call(arr,filter); + }, + last: function(arr,filter) { + return arrayLast.call(arr,filter); + }, + donothing: function () { }, + // add nonstandard polyfills + polyfill: function () { + + Array.prototype.contains = arrayContains; + Array.prototype.first = arrayFirst; + Array.prototype.last = arrayLast; + String.prototype.format = format; + } + + }; + + // add required polyfills + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = arrayIndexOf; + } + if (!Array.prototype.trim) { + Array.prototype.trim = stringTrim; + } + if (!Array.prototype.lastIndexOf) { + Array.prototype.lastIndexOf = arrayLastIndexOf; + } + if (!Array.prototype.forEach) { + Array.prototype.forEach = arrayForEach; + } + if (!Array.prototype.filter) { + Array.prototype.filter = arrayFilter; + } + if (!Array.prototype.every) { + Array.prototype.every = arrayEvery; + } + if (!Array.prototype.some) { + Array.prototype.some = arraySome; + } + if (!Array.prototype.map) { + Array.prototype.map = arrayMap; + } + u.polyfill(); + + return u; + }); +} (typeof define === 'function' + ? define + : function (factory) { + if (typeof module !== 'undefined') { + module.exports = factory(); + } else { + this.common = this.common || {}; + this.common.utils = factory(); + } + } +// Boilerplate for AMD, Node, and browser global +)); +/* +IqTest: A javascript testing framework that promises to be easy + +(c) 2012 James Treworgy +MIT License +*/ + +/* global alert, define, require, module, buster, u */ + + +(function(define) { + +define(['./iqtest'], function(u,when,when_timeout, iq_asserts, buster_asserts, utils) { + var options,iqtestApi,Test, TestGroup, Assert, + globalDefaults = { + setup: null, + teardown: null, + timeout: 10 + }, + // default values for TestGroup + groupDefaults = { + + // an id or name + name: "test-group", + + // detailed desription of the test + desc: "Unnamed Test Group", + + // when true, will not trap errors in the test itself. + debug: false, + // timeout for function execution (null inherits) + timeout: null, + // functions to run on setup or teardown + setup: null, + teardown: null + + }, + // default values for Test and also defines allowed options + testDefaults = { + name: "test", + desc: "Unnamed Test", + func: null, + timeout: null, + // there is a test-specific debug option so that currently running tests will remain + // in non-debug mode after another one fails + debug: false + }, + // list of methods to import from buster + busterMethods="same,equals,typeOf,defined,isNull,match,isObject,isFunction,exception,tagName,className,isTrue,isFalse", + initialized=false, + // metadata collected about the assertions + assertionData={}; + + // get the last argument if it's a string + function assertMessage(assertion,args) { + var argCount= assertionData[assertion.split('.')[1]].args; + return argCount < args.length ? + String(args[argCount]) : + 'an anonymous test'; + } + // Map all asserts + + function captureMethodArgs(method,func) + { + // get metadata by examining error message + var matches=0, + reg=/^.*?Expected.*?([0-9]) argument[s]?\s*$/; + + try { + func(); + } + catch(err) + { + matches=reg.test(err.message) ? parseInt(RegExp.$1,10) : 0; + } + assertionData[method]={ + args: matches + }; + } + + function initialize() + { + var ba,tp; + if (initialized) { + return; + } + // Create a single default assertion so this works with no includes. + // Other non-buster methods can be found in iqtest.assertions.js + + iq_asserts.push({ + truthy: function(obj,message) { + u.expectOpts(arguments,1); + return { + passed: !!obj, + err: u.formatAssert(message,'The object {0} is {not}truthy',String(obj)) + }; + }, + // has special handling - the actual asserion doesn't know if it was given a promise, only the output. + // the queue funciton must verify + resolves: function(obj,message) { + u.expectOpts(arguments,1); + return { + passed: typeof obj !== 'undefined', + err: u.formatAssert(message,'The object {0} did {not}resolve',String(obj)) + }; + } + }); + + // Map buster.assertions.assert & refute to Test protype "assert" & "refute". + // Also map the assertions directly to test + + tp=Test.prototype; + ba=buster.assertions; + + u.each(["assert","refute"],function(i,type) { + + // create proto.assert + + var asserts = {}; + + // map methods from buster + + u.each(busterMethods.split(','),function(j,method) { + captureMethodArgs(method,ba[type][method]); + asserts[method] = function () { + var that=((this.test && this.test instanceof Test) ? this.test : this); + return that.queueTest(ba[type][method],type+"."+method,u.toArray(arguments)); + }; + }); + + // map builtin methods from the array. Ignore ones that have already been defined. + + u.each(iq_asserts,function(i,iqa) { + u.each(iqa, function(method,func) { + if (!asserts[method]) { + captureMethodArgs(method,func); + asserts[method]=function() { + var that=((this.test && this.test instanceof Test) ? this.test : this); + return that.queueBooleanTest(func, + type+"."+method, + u.toArray(arguments), + type==='refute'); + }; + } + }); + }); + + // copy asserts to main Test object (do it before we update with the utilities, those are FROM the main object) + + if (type==='assert') { + u.extend(tp,asserts); + } + // map utilities + + u.each(["backpromise","callback","then"],function(i,method) { + asserts[method]=function() { + return this.test[method].apply(this.test,u.toArray(arguments)); + }; + }); + + // finally update the prototype, and create this.assert() and this.refute() functions + + tp[type]=function(obj,message) { + return tp[type].truthy.apply(this,u.toArray(arguments)); + }; + + u.extend(tp[type],asserts); + + initialized=true; + }); + } + + // u needs to be imported from common.utils + + u.extend(u, { + event: function(func,that,parm) { + if (u.isFunction(func)) { + func.call(that,parm); + } + }, + // throw an error if the 'args' array has fewer than 'expected' elements. + expectOpts: function(args,expected) { + if ((args ? args.length : 0) < expected) { + throw({ + name: "AssertionError", + type: "iq", + message: u.format("Expected to receive at least {0} argument",expected.toString()) + }); + } + }, + // standardize the format of the output from assertions + formatAssert: function(message,reason,parms) { + return !reason ? '' : + (message ? message+': ':'') + + u.format(reason,u.isArray(parms) ? parms : u.toArray(arguments,2)); + } + + }); + + /* Begin Main Code */ + + /* Shared functions */ + + function doEvent(obj,method /* [,parms] */ ) { + if (obj[method]) { + obj[method].apply(this,u.toArray(arguments,2)); + } + } + + // return a delegate to a function with the specified context + // if any additional arguments are passed when the func is actually called, they come first + + function bind(context,func /*[,args]*/) { + var args=u.toArray(arguments,2); + return function() { + // "arguments" here is what the delegate was ultimately invoked with. + var finalArgs = u.toArray(arguments).concat(args); + if (finalArgs.length>0) { + func.apply(context,finalArgs); + } else { + func.call(context); + } + }; + } + // create a new prototype from arbitrary arguments + + function construct(constructor, args) { + function F() { + return constructor.apply(this, args); + } + F.prototype = constructor.prototype; + return new F(); + } + /* Functions for the TestGroup prototype */ + + function groupAddTest(test) { + var hasDesc, func, description, + testData, + me=this; + + function addTest(test) { + me.tests.push(test); + test.group=me; + test.id = me.tests.length; + } + if (test.constructor === TestGroup) { + u.each(test.tests,function(i,e) { + addTest(e); + }); + } else if (test.constructor === Test) { + addTest(test); + } else if (typeof test === 'string') { + description = arguments[1]; + func = arguments[2]; + hasDesc = !!func; + testData = { + name: test, + desc: hasDesc ? description : '', + func: hasDesc ? func : description, + debug: me.debug, + timeout: me.timeout + }; + addTest(new Test(testData)); + } + return me; + } + + // function must be called with a context of + function testFinished(group,test) { + var groupResult; + + if (test.promise!==test._lastPromise) { + test._lastPromise=test.promise; + test._lastPromise.then(function() { + testFinished(group,test); + },function(err) { + test.testerror(err,true); + testFinished(group,test); + }); + return; + } + if (!u.isBool(test.passed)) { + test.passed = (test.count===test.countPassed); + } + test._allPass &= test.passed; + + test.doWriterEvent("testEnd",u.filterProps(test,"count,passed")); + + doEvent.call(test,test,"teardown"); + + // see if all tests have been resolved + + groupResult=true; + u.each(group.tests,function(i,e) { + if (!u.isBool(e.passed)) { + groupResult=null; + return false; + } else { + if (!e.passed) { + groupResult=false; + } + } + }); + + if (u.isBool(groupResult)) { + group.passed = groupResult; + group.doWriterEvent("groupEnd"); + doEvent.call(this,this,"teardown"); + group.promise.resolve(); + } + } + + + function groupRun() { + var me=this; + + doEvent.call(this,this,"setup"); + + this.reset(); + u.each(me.tests,function(i,test) { + test.reset(); + }); + + this.doWriterEvent("groupStart",u.filterProps(me,"name,desc")); + + u.each(me.tests,function(i,test) { + var assert=u.extend({},test.assert,{test: test}), + refute = u.extend({},test.refute,{test: test}); + + doEvent.call(test,test,"setup"); + test.doWriterEvent("testStart",u.filterProps(test,"name")); + + if (test.debug) { + test.func.call(test,assert,refute); + } else { + try { + test.func.call(test,assert,refute); + } + catch(err) + { + test.testerror(u.format("An error occurred in your test code: {0}",err),true); + } + } + + // wait for everything to finish by binding to the last promise, and deferring each time + // it changes as a result of a callback or something. + // this is really quite nasty, I have not figured out a better way to do it yet + + test._lastPromise=test.promise; + test._allPass=true; + + // bind to the last promise in the chain. The finishing function will detect if + // anything else has been added. + + test.promise.then(function() { + testFinished(me,test); + },function(err) { + test.testerror(err,true); + testFinished(me,test); + }); + }); + return this; + } + // call function "event" that is a member of each element in activeWriters, with args + // should be called with the sender event context + function doWriterEvent(event,args) { + var me = this; + u.each(this.group.writers,function(i,e) { + var target = e[event]; + if (u.isFunction(target)) { + target.apply(e,[me].concat(args)); + } + }); + + } + + TestGroup = function (name, desc, options) { + initialize(); + + var opts = name && typeof name === 'object' ? name: + desc && typeof desc === 'object' ? desc : + options || {}; + + if (typeof desc === 'string') { + opts.name=name; + } + if (typeof desc === 'string') { + opts.desc=desc; + } + u.extend(this, + u.extend(null,groupDefaults,opts,true) + ); + + // active ouput writers + this.writers=[]; + + // private methods + + this.doEvent = doEvent; + + // uniform interface for both tests & groups for accessing the group & event emitter + this.group = this; + this.doWriterEvent = function() { + doWriterEvent.apply(this,u.toArray(arguments)); + }; + + this.clear(); + }; + + TestGroup.prototype = { + constructor: TestGroup, + // Add a new test to this group. This can be a Test object, or a + // add: function(name [,description],func) + add: groupAddTest, + // run the tests that have been added to this test group + // return the group object, which is also a promise that resolves when + // the group is finished running. + run: groupRun, + // a promise that resolves when a "run" operation finishes + then: function() { + return this.promise.then.apply(this,u.toArray(arguments)); + }, + configure: function(options) { + if (typeof options === 'string') { + options = {name: options}; + } + var allowed = u.extend({},groupDefaults); + u.extend(allowed,options,true); + u.extend(this,allowed); + return this; + }, + reset: function() { + this.promise=when.defer(); + this.passed=null; + return this; + }, + clear: function() { + this.tests = []; + this.reset(); + return this; + }, + // activate a named writer + writer: function(id /*,writer-args*/) { + var w, + proto = iqtestApi.writers[id]; + if (!proto) { + throw("There is no output writer with id '{0}'".format(w)); + } + + w=construct(proto,u.toArray(arguments,1)); + w.owner=this; + this.writers.push(w); + return this; + }, + // events + groupStart: u.donothing, + groupEnd: u.donothing + }; + + // A test object. After running tests, the "results" contains an array of strings describing + // failures. + Test = function (options) { + var me=this; + + u.extend(me, testDefaults); + u.extend(me, options, true); + me.id=null; + me.group=null; + me.clear(); + + this.doWriterEvent = function() { + doWriterEvent.apply(this,u.toArray(arguments)); + }; + }; + + Test.prototype = { + constructor: Test, + // set timeout only for the next test + impl: {}, + reset: function() { + u.extend(this, { + //domino: when.defer(), + promise: when.defer(), + results: [], + count: 0, + countPassed: 0, + countFailed: 0, + nextThen: [], + cbPromise: null, + resolver: null, + stopped: false, + passed: null, + userPromises: {} + }); + + // resolve immediately to start the chain when the first thing is added + this.promise.resolve(); + }, + clear: function() { + this.setDebug(false); + this.userPromises={}; + }, + setDebug: function(active,count) { + this.debug=u.isBool(active) ? active : true; + when.debug = this.debug; + if (active) { + if (typeof count==='number') { + this.debugCount= count; + } + } else { + this.debugCount=-1; + } + }, + nextIsProblemAssertion: function() { + return this.debug && !this.stopped && this.debugCount>=0 + && this.debugCount === this.count-1; + }, + timeoutOnce: function(seconds) { + var me=this, + originalTimeout = me.timeout; + me.then(function() { + me.timeout = seconds; + }); + me.afterNext(function() { + me.timeout = originalTimeout; + }); + }, + // set options for this test + configure: function(options) { + if (typeof options === 'string') { + options = {name: options}; + } + var allowedOpts =u.extend({},testDefaults); + // update with current options, then with options passed + u.extend(allowedOpts,this,options,true); + u.extend(this,allowedOpts); + }, + // queue a callback to attach to the next thing queued. + then: function(callback,errFunc) { + var me=this, + errback=errFunc || function(err) { + // failures of everything end up here - if we've already broken for a particular reason then + // stop logging all the inevitable timeouts. + me.testerror(err,false); + }, + prev = me.promise, + next = when.defer(); + + me.promise = next; + prev.then(function(val) { + + try { + if (me.nextIsProblemAssertion()) { + // the next event is the one causing you trouble + debugger; + } + callback(val); + } + catch(err){ + me.testerror("An error occurred during a 'then' clause of an assertion: "+String(err),true); + } + },errback); + + + when.chain(prev,next); + + return me; + }, + chain: function(callback,errback) { + var next = when.defer(), + prev = this.promise; + + this.promise = next.promise; + prev.then(callback,errback || bind(this,this.testerror)); + when.chain(prev,next); + return this; + }, + //TODO + afterNext: function(callback,errback) { + this.nextThen.push({callback:callback, errback:errback}); + }, + startTest: function (info) { + this.count++; + // cache the active assertion data + this.assertionInfo = u.extend({},info, + { + count: this.count + }); + this.doWriterEvent("itemStart",this.assertionInfo); + this.itemRunning=true; + }, + endTest: function (result) { + // TODO: Option allowing logging of passed tests + var output=result; + if (!this.itemRunning) { + this.testerror("Error: test was not running when endTest called: " + result.desc); + return; + } + if (!result.passed) { + output=this.addResult(result); + this.countFailed++; + } else { + this.countPassed++; + } + this.itemRunning=false; + this.doWriterEvent("itemEnd",output); + }, + // queue a test that returns true or false + // will wrap it & pass on to queueTest + queueBooleanTest: function(module,assertion,args,invert) { + // make a copy of the object to keep in this closure + var testArgs = u.toArray(args); + return this.queueTest(function() { + var result= module.apply(null,testArgs); + + if (result.passed === invert) { + throw({ + name: "AssertionError", + type: "iq", + message: u.format(result.err.replace('{not}','{0}'),invert?'not ':'') + }); + } + }, + assertion, + args); + }, + // queue a test that throws an error + queueTest: function(module,assertion,args) + { + var me=this, + // internal methods are wrapped in tryTest - get their args as the 2nd arg in "args." Argh! + cbPos, + assertionName=assertion.split('.')[1], + methodArgs=assertionData[assertionName].args, + hasMagicCallback, + deferred, + next,prev, + pending=[]; + + if (me.stopped) { + return me; + } + + // check for the "magic" callback. If me.cbPromise exists, then it was hopefully created by the parameters + // for this method. This is slightly brittle because there's no direct binding of the particular promise to + // this particular method, but the single-threaded nature of javascript should cause this to work fine. + // I can't see any substantive risk here and it is extraordinarily convenient. + + if (me.cbPromise) { + hasMagicCallback=true; + deferred = deferred || when.defer(); + + if (methodArgs===1) { + cbPos=0; + } else if (args[0] && !args[1]) { + cbPos=1; + } else if (args[1] && !args[0]) { + cbPos=0; + } else { + me.testerror(u.format('I couldn\'t figure out what to do with your magic callback. ' + + 'For this test you may need to define it explicitly.' + + '[{0}] {1}',assertion,assertMessage(args))); + return; + } + + me.cbPromise.then(function(response) { + args[cbPos]=response; + },function(err) { + deferred.reject('The callback failed. ' + (err ? u.format('Reason: {0}',String(err)) :'')); + }); + pending.push(me.cbPromise); + me.cbPromise=null; + } + + // special case - must check argument for "resolves" + + if (assertionName==='resolves' && !when.isPromise(args[0])) { + throw("The argument passed to 'resolves' was not a promise."); + } + + // check all the arguments to this assertion for promises or callbacks; if any are found, + // add to our list of things to do before resolving this assertion. + + u.each(args,function(i,arg) { + + // wait for any promises + if (when.isPromise(arg)) { + + deferred = deferred || when.defer(); + + if (i===0 && hasMagicCallback) { + deferred.reject("You're using magic callback but you've also defined a promise as the first argument of your assert."); + } + arg.then(function(response) + { + args[i]=response; + }); + pending.push(arg); + } + + }); + + // queue a promise that will emit events when the test has started. + + this.chain(function() { + me.startTest({ + desc: assertMessage(assertion,args), + assertion: assertion + }); + }); + + + // if there are pending promises, then wait for all those events (in addition to the last promise) + // before resolving. Add a timeout on top of it if necessary. + + if (pending.length) { + pending.push(me.promise); + + me.promise = me.timeout ? + when_timeout(deferred.promise,me.timeout*1000): + deferred.promise; + + when.chain(when.all(pending),deferred); + } + + + // link each test to a new resolver so failures will break the chain at that point + // some tests don't have an "actual" part (e.g. pass,fail). + + next = when.defer(); + prev = me.promise; + me.promise = next.promise; + prev.then(function resolve(value) { + // check if a value was passed - it is likely the cb parm + if (me.runTest.call(me,module,assertion,args)) { + next.resolve(); + } else { + next.reject("The test failed"); + } + },function reject(err) { + if (me.itemRunning) { + me.endTest({ + passed: false, + err: String(err) + }); + } + next.reject("The test was stopped because an assertion failed."); + }); + + + + //me.resolver=deferred.resolver; + + return me; + }, + // run a named test using the arguments in array args + runTest: function (module,assertion,args) { + // should return an object [err: error message, desc: description of test passed in] + var result={ + assertion: assertion, + err: '', + passed: true + }; + + //args is an object mapped to the relevant parms for any assert + try { + module.apply(null, args); + } + catch(err) + { + // rethrow anything that isn't a test failure - it will be caught and dealt with higher up. + if (err.name !== 'AssertionError') { + if (this.debug) { + debugger; + // Continue execution to try the assertion again + module.apply(null, args); + } else { + this.setDebug(); + err.message = (err.message || err.type) + ". Debugging has been enabled."; + } + } + if (err.type==='iq') { + err.message = u.format('[{0}] {1}',assertion,err.message); + } + result.err = err.message; + result.passed=false; + } + + this.endTest(result); + return result.passed; + }, + /// add the current test results as properties to the object passed in + addResult: function (result) { + + var output = u.extend({}, result), + passfail = output.passed ? + "passed" : + "failed"; + + // "Test #[1] [assertEq] [passed|failed] [with result "message"] [in test "test"] + + output.count = this.count; + output.fulltext = u.format('Test #{0} {1} {2} {3}{4}', + this.count, + this.assertionInfo.assertion, + passfail, + output.passed ? + '' : + ': ' + result.err, + + u.format(' in test "{0}"', this.assertionInfo.desc) + + ); + + this.results.push(output); + return output; + + }, + + // when the debugging parm is true, will enable debugging for the group + testerror: function(err, debug) { + var me=this; + try { + + if (me.stopped) { + return; + } + me.stopped=true; + me.passed=false; + + this.doWriterEvent("testLog", + u.format('{0}. {1}',String(err), + debug ? 'Debugging is enabled if you start again.' : '' )); + } + catch(e) { + // this is basically a fatal error. Not much else to do. + debugger; + + } + + if (debug) { + me.setDebug(true,me.count); + } + + + }, + + // create a callback that the next assert will wait for, optionally expiring. + callback: function(target,timeout) { + var me=this, + t = timeout || me.timeout, + deferred = when.defer(); + + // if no timeout is specified, the actual function is already wrapped by a timeout so not needed + + me.cbPromise= + t ? + when_timeout(deferred, t * 1000) : + deferred; + + return function() { + var value; + if (!target) { + value=true; + } else { + if (me.debug) { + value=target.apply(this,u.toArray(arguments)); + } else { + try + { + value=target.apply(this,u.toArray(arguments)); + } + catch(err) + { + me.testerror("An error occurred in your callback(): "+String(err),true); + deferred.reject(value); + return; + } + } + } + deferred.resolve(value); + }; + }, + + /* creates a promise bound to the resolution of a callback, and adds it to the + assertion queue. usage (note "callback" parameter) + + this.when(function(callback) { + doSomething(arg1,arg2,callback) + }).then(function(response) { + a.equals(expected,response) + }); + + */ + + when: function(func,timeout) { + var me=this, + t=timeout || me.timeout, + next = when.defer(), + last = me.promise; + + + me.promise = t ? when_timeout(next, t*1000) : next; + + // this promise will chain upon successful resolution of the callback to "next" + // however we still need a failure handler for "last" becase an error in "func" + // could cause it to never resolve. This is better than timing out. + + last.then(function() { + func.call(me,next.resolve); + }).then(null,function(err) { + me.testerror("An error occured during a 'when' operand: " + String(err),true); + next.reject(); + }); + + return me; + }, + + // create a new deferred object (same as when.defer) and bind completion of the tests to its + // resolution + + defer: function(callback,timeout) { + var me = this, + next = when.defer(), + t = timeout || me.timeout; + + // just replace the active promise -- there is no dependency on the prior + // promise because user code is responsible for resolving this promise. + + me.promise = t ? when_timeout(next, t*1000) : next; + + if (callback) { + // we don't need to bind an error handler to the callback because this is now + // the last promise on the chain. + next.then(callback); + } + return next; + }, + + /** + * Return a new promise identified by name, or an existing one with that name + * @param {string} name A name to identify this promist + * @return {[type]} A new or existing promist + */ + promises: function(name) { + if (typeof this.userPromises[name]==='undefined') { + this.userPromises[name]=when.defer(); + } + return this.userPromises[name]; + }, + // return a promise from a function that has a callback parameter + backpromise: function(func,callback,timeout) { + var defer=when.defer(), + me=this, + t=timeout || me.timeout, + cb=function() { + var value; + if (callback) { + if (me.debug) { + value=callback.apply(this,u.toArray(arguments)); + } else { + try + { + value=callback.apply(this,u.toArray(arguments)); + } + catch(err) + { + me.testerror("An error occurred in your backpromise() callback: "+err,true); + defer.reject(value); + return; + } + } + } + defer.resolve(value); + }; + + if (me.debug) { + func.call(me,cb); + } else { + try + { + func.call(me,cb); + } + catch(err) + { + me.testerror("An error occurred in your backpromise() function: "+err,true); + defer.reject(); + return; + } + } + + + return t ? when_timeout(defer, t*1000) : defer; + } + + }; + + // Global configuration + + options = u.extend({},globalDefaults); + + // PUBLIC API + + iqtestApi = { + // Create & return a new test group and configure with the options passed + create: function(name,desc,groupOpts) { + var finalOpts = u.extend({ + timeout: options.timeout, + setup: options.setup, + teardown: options.teardown + },groupOpts); + + var group = new TestGroup(name,desc,finalOpts); + return group; + }, + add: function () { + return this.add.apply(this,u.toArray(arguments)); + }, + extend: function(assertions) { + iq_asserts.push(assertions); + }, + // configure global options. + configure: function(newOpts) { + u.extend(options,newOpts,true); + }, + options: options, + // library of available writers; each should be a prototype that can be instantiated and exposing the + // correct api (see html implementation) + writers: {}, + impl: { + TestGroup: TestGroup, + Test: Test, + Assert: Assert, + utility: u + } + }; + return iqtestApi; +}); +}(typeof define === 'function' + ? define + : function (deps, factory) { + if (typeof module !== 'undefined') { + module.exports = factory(require('./when'), + require('./timeout'), + require('./buster-assertions'), + require('./common.utils') + ); + } else { + if (!this.iqtest_assertions) { + this.iqtest_assertions=[]; + } + this.iqtest = factory(this.common.utils,this.when,this.when_timeout,this.iqtest_assertions, + this.buster ? this.buster.assert : null); + } + } +)); /* +Assertions for IQ Test (other than buster) +This requires iqtest to use its utility methods so must be included afterwards + +Each assertion should throw an error when called with no args: "Expected 1 argument[s]" +This is necessary for iqtest to determine the position of the "message" argument + */ + +/* global define, require, module */ + + (function(define) { +define(function(iqtest) { + var u=iqtest.impl.utility, + output = u.formatAssert; + + // return true if exactly equal, or are not value types (e.g. ignore non-value types) + function valuesEqual(expected,actual) { + if (expected===actual) { + return true; + } else if (typeof expected !== typeof actual) { + return false; + } else if (!u.isValueType(expected)) { + return true; + } + } + function propertiesEqual(expected,actual,message, ignoreObjects) { + var reason='',count=0,actualCount=0; + if (typeof expected !== 'object' || typeof actual !== 'object') { + reason = u.format('the objects are not both objects'); + } else { + u.each(expected,function(prop,e) + { + if (typeof actual[prop]==='undefined') { + reason = u.format('the expected object has a property "{0}"" which does not exist on the actual object',prop); + return false; + } + if (ignoreObjects ? + !valuesEqual(actual[prop],expected[prop]) : + actual[prop]!==expected[prop]) { + reason = u.format('the expected object property "{0}" has value "{1}" which does not match the actual value "{2}"',prop,expected[prop],actual[prop]); + return false; + } + count++; + }); + if (!reason) { + u.each(actual,function() { + actualCount++; + }); + if (count!=actualCount) { + reason = u.format('the expected object has {0} properties, the actual has {1}',count,actualCount); + } + } + } + return { + passed: !reason, + err: output(message,reason) + }; + } + + /* Custom asserions: each should return an object with the format shown below. The err message must appear for both + positive and negative assertions, with a parameter for adding the word "not." Use the output function to do this + as in the example */ + + function contentsEqual(expected,actual,message) { + var result, + reason, index, + isArr = u.isArray(actual), + actualArr=actual, + expectedArr=expected; + + + // return first index at which arrays differ + function arraysEqual(obj1,obj2) { + for (var i=0;i=0) { + if (!isArr) { + index = '"'+getOrdinalName(actual,index)+'"'; + } + reason=u.format('sorted objects are {not}different at element {0}, expected "{1}" vs. actual "{2}"',index,expectedArr[index],actualArr[index]); + } + } + } else { + result = propertiesEqual(expected,actual,message); + } + } + + return result || { + passed: !reason, + err: output(message,reason) + }; + } + /// compare only value-typed properties + function valuePropertiesEqual(expected,actual,message) { + u.expectOpts(arguments,2); + return propertiesEqual(expected,actual,message,true); + } + + return { + // Two things should have the same contents. If this is an object, the values of each property must be identical. + // if an array, they must have the same elements, but order is irrelevant. + // If a string, it is split on commas and treated as a CSV. + collectionEquals: contentsEqual, + propertyEquals: propertiesEqual, + propertyValueEquals: valuePropertiesEqual + }; + + +}); +}(typeof define === 'function' + ? define + : function (factory) { + if (typeof module !== 'undefined') { + module.exports = factory(require('./iqtest')); + } else { + this.iqtest_assertions.push(factory(this.iqtest)); + } + } + // Boilerplate for AMD, Node, and browser global +)); + + +/* + An HTML output writer for iqtest + + Uses options on the TestGroup: + groupTemplate: {name} = group name + testTemplate: {name} = test name, {desc} = simple failure description + itemTemplate: {fulltext} + + This should append itself to iqtest.writers.xxx + +*/ +/* global iqtest, when */ + +(function(iqtest) { + var u = iqtest.impl.utility, + options = { + group:'

    Starting test group "":

    '+ + '
    ', + testStart: '

    Starting test "":

    ', + testEnd: '

    assertions passed.

    ', + itemStart: '#: ' + +' ""....
    ', + itemEnd: '
    ', + log: '
    ', + // the following are just formats + resultSuccess: '', + resultFail:'', + showPassed: false + + }; + + // replace every element in "el" containing class "test-*" with the value of properties "*" + function tmpReplace(el,obj){ + var sel,replaceEl,prop; + for (prop in obj) { + if (obj.hasOwnProperty(prop)) { + sel = '.test-'+prop.toLowerCase(); + replaceEl = $(sel,el); + if (!replaceEl.length) { + replaceEl=$(el).filter(sel); + } + replaceEl.empty().append(obj[prop]); + } + } + return el; + } + + function getResultOutput(passed) { + var tmpl = passed ? options.resultSuccess: options.resultFail; + + return $(tmpl).text(passed ? "Passed":"Failed"); + } + + /* Implementation */ + + function groupStart(group) { + var groupWrapper = tmpReplace($(options.group).clone(),{ + name: group.name, + groupstatus: "Running" + }); + + this.container.append(groupWrapper); + + // should be the innermost div + this.groupWrapper = groupWrapper; + this.groupContainer = $(u.last(groupWrapper.find('div:only-child'))); + } + function groupEnd(group) { + tmpReplace(this.groupWrapper,{ + groupstatus: getResultOutput(group.passed) + }); + } + + function testStart(test) { + + var testData = this.getTestData(test.id), + content= tmpReplace($(options.testStart).clone(),{ + name: test.name, + teststatus: "Running" + }); + + testData.testWrapper = content; + testData.testContainer = content.filter('div'); + + this.groupContainer.append(content); + } + + function testEnd(test) { + var testData = this.getTestData(test.id), + content = tmpReplace($(options.testEnd).clone(),{ + count: test.count + }); + + testData.testContainer.append(content); + + tmpReplace(testData.testWrapper, { + teststatus: getResultOutput(test.passed) + }); + } + + function itemStart(test,testinfo) + { + var testData = this.getTestData(test.id), + tempItem= tmpReplace($(options.itemStart).clone(),{ + number: testinfo.count, + assertion: testinfo.assertion, + message: testinfo.desc + }); + + testData.itemTarget = tempItem; + testData.testContainer.append(tempItem); + } + function itemEnd(test,response) { + // this can get called without itemStart (prob should create a different kind of event for errors but...) + var testData = this.getTestData(test.id); + + if (response.passed) { + if (!options.showPassed) { + testData.itemTarget.remove(); + } + } else { + testData.itemTarget.replaceWith(tmpReplace($(options.itemEnd).clone(),{ + failmessage: response.fulltext + })); + } + testData.itemTarget=null; + response.written=true; + } + function testLog(test,message) { + var testData = this.getTestData(test.id); + testData.testContainer.append(tmpReplace($(options.log).clone(),{ + logmessage: message + })); + } + // function render() { + // var me=this; + + // u.each(me.results,function(i,result) { + // if (!result.written) { + // .event(this.group.itemEnd,this.group,result); + // } + // }); + // } + + // ensure that errors don't ever cause a promise to fail. errors in the harness should always + // cause execution to stop. + + function safeMethod(method) { + return function() { + try { + method.apply(this,u.toArray(arguments)); + } + catch(err) { + when.debug=true; + throw err; + } + }; + } + function HtmlWriter(container, opts) { + // when added to a TestGroup, the group should assign itself to owner + this.owner=null; + this.container=container; + this.tests={}; + + if (typeof opts==='object') { + $.extend(options,opts); + } + } + + HtmlWriter.prototype = { + constructor: HtmlWriter, + groupStart: safeMethod(groupStart), + groupEnd: safeMethod(groupEnd), + testStart: safeMethod(testStart), + testEnd: safeMethod(testEnd), + itemStart: safeMethod(itemStart), + itemEnd: safeMethod(itemEnd), + testLog: safeMethod(testLog), + // internal api + getTestData: function(id) { + if (!this.tests[id]) { + this.tests[id]={}; + } + return this.tests[id]; + } + }; + + + iqtest.writers.html = HtmlWriter; + +}(iqtest)); + diff --git a/tests/redist/jquery.1.7.1.js b/tests/redist/jquery.1.7.1.js index 99301f1..520a285 100644 --- a/tests/redist/jquery.1.7.1.js +++ b/tests/redist/jquery.1.7.1.js @@ -1,9266 +1,9266 @@ -/*! -* jQuery JavaScript Library v1.7.1 -* http://jquery.com/ -* -* Copyright 2011, John Resig -* Dual licensed under the MIT or GPL Version 2 licenses. -* http://jquery.org/license -* -* Includes Sizzle.js -* http://sizzlejs.com/ -* Copyright 2011, The Dojo Foundation -* Released under the MIT, BSD, and GPL Licenses. -* -* Date: Mon Nov 21 21:11:03 2011 -0500 -*/ -(function (window, undefined) { - - // Use the correct document accordingly with window argument (sandbox) - var document = window.document, - navigator = window.navigator, - location = window.location; - var jQuery = (function () { - - // Define a local copy of jQuery - var jQuery = function (selector, context) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init(selector, context, rootjQuery); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Matches dashed string for camelizing - rdashAlpha = /-([a-z]|[0-9])/ig, - rmsPrefix = /^-ms-/, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function (all, letter) { - return (letter + "").toUpperCase(); - }, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // The deferred used on DOM ready - readyList, - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - - jQuery.fn = jQuery.prototype = { - constructor: jQuery, - init: function (selector, context, rootjQuery) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if (!selector) { - return this; - } - - // Handle $(DOMElement) - if (selector.nodeType) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if (selector === "body" && !context && document.body) { - this.context = document; - this[0] = document.body; - this.selector = selector; - this.length = 1; - return this; - } - - // Handle HTML strings - if (typeof selector === "string") { - // Are we dealing with HTML string or an ID? - if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [null, selector, null]; - - } else { - match = quickExpr.exec(selector); - } - - // Verify a match, and that no context was specified for #id - if (match && (match[1] || !context)) { - - // HANDLE: $(html) -> $(array) - if (match[1]) { - context = context instanceof jQuery ? context[0] : context; - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec(selector); - - if (ret) { - if (jQuery.isPlainObject(context)) { - selector = [document.createElement(ret[1])]; - jQuery.fn.attr.call(selector, context, true); - - } else { - selector = [doc.createElement(ret[1])]; - } - - } else { - ret = jQuery.buildFragment([match[1]], [doc]); - selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; - } - - return jQuery.merge(this, selector); - - // HANDLE: $("#id") - } else { - elem = document.getElementById(match[2]); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if (elem && elem.parentNode) { - // Handle the case where IE and Opera return items - // by name instead of ID - if (elem.id !== match[2]) { - return rootjQuery.find(selector); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if (!context || context.jquery) { - return (context || rootjQuery).find(selector); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor(context).find(selector); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if (jQuery.isFunction(selector)) { - return rootjQuery.ready(selector); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray(selector, this); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.7.1", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function () { - return this.length; - }, - - toArray: function () { - return slice.call(this, 0); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function (num) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - (num < 0 ? this[this.length + num] : this[num]); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function (elems, name, selector) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if (jQuery.isArray(elems)) { - push.apply(ret, elems); - - } else { - jQuery.merge(ret, elems); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if (name === "find") { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if (name) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function (callback, args) { - return jQuery.each(this, callback, args); - }, - - ready: function (fn) { - // Attach the listeners - jQuery.bindReady(); - - // Add the callback - readyList.add(fn); - - return this; - }, - - eq: function (i) { - i = +i; - return i === -1 ? - this.slice(i) : - this.slice(i, i + 1); - }, - - first: function () { - return this.eq(0); - }, - - last: function () { - return this.eq(-1); - }, - - slice: function () { - return this.pushStack(slice.apply(this, arguments), - "slice", slice.call(arguments).join(",")); - }, - - map: function (callback) { - return this.pushStack(jQuery.map(this, function (elem, i) { - return callback.call(elem, i, elem); - })); - }, - - end: function () { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice - }; - - // Give the init function the jQuery prototype for later instantiation - jQuery.fn.init.prototype = jQuery.fn; - - jQuery.extend = jQuery.fn.extend = function () { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if (typeof target === "boolean") { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if (typeof target !== "object" && !jQuery.isFunction(target)) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if (length === i) { - target = this; - --i; - } - - for (; i < length; i++) { - // Only deal with non-null/undefined values - if ((options = arguments[i]) != null) { - // Extend the base object - for (name in options) { - src = target[name]; - copy = options[name]; - - // Prevent never-ending loop - if (target === copy) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { - if (copyIsArray) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[name] = jQuery.extend(deep, clone, copy); - - // Don't bring in undefined values - } else if (copy !== undefined) { - target[name] = copy; - } - } - } - } - - // Return the modified object - return target; - }; - - jQuery.extend({ - noConflict: function (deep) { - if (window.$ === jQuery) { - window.$ = _$; - } - - if (deep && window.jQuery === jQuery) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function (hold) { - if (hold) { - jQuery.readyWait++; - } else { - jQuery.ready(true); - } - }, - - // Handle when the DOM is ready - ready: function (wait) { - // Either a released hold or an DOMready/load event and not yet ready - if ((wait === true && ! --jQuery.readyWait) || (wait !== true && !jQuery.isReady)) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if (!document.body) { - return setTimeout(jQuery.ready, 1); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if (wait !== true && --jQuery.readyWait > 0) { - return; - } - - // If there are functions bound, to execute - readyList.fireWith(document, [jQuery]); - - // Trigger any bound ready events - if (jQuery.fn.trigger) { - jQuery(document).trigger("ready").off("ready"); - } - } - }, - - bindReady: function () { - if (readyList) { - return; - } - - readyList = jQuery.Callbacks("once memory"); - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if (document.readyState === "complete") { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout(jQuery.ready, 1); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if (document.addEventListener) { - // Use the handy event callback - document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); - - // A fallback to window.onload, that will always work - window.addEventListener("load", jQuery.ready, false); - - // If IE event model is used - } else if (document.attachEvent) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent("onreadystatechange", DOMContentLoaded); - - // A fallback to window.onload, that will always work - window.attachEvent("onload", jQuery.ready); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch (e) { } - - if (document.documentElement.doScroll && toplevel) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function (obj) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function (obj) { - return jQuery.type(obj) === "array"; - }, - - // A crude way of determining if an object is a window - isWindow: function (obj) { - return obj && typeof obj === "object" && "setInterval" in obj; - }, - - isNumeric: function (obj) { - return !isNaN(parseFloat(obj)) && isFinite(obj); - }, - - type: function (obj) { - return obj == null ? - String(obj) : - class2type[toString.call(obj)] || "object"; - }, - - isPlainObject: function (obj) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if (!obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { - return false; - } - - try { - // Not own constructor property must be Object - if (obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { - return false; - } - } catch (e) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for (key in obj) { } - - return key === undefined || hasOwn.call(obj, key); - }, - - isEmptyObject: function (obj) { - for (var name in obj) { - return false; - } - return true; - }, - - error: function (msg) { - throw new Error(msg); - }, - - parseJSON: function (data) { - if (typeof data !== "string" || !data) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim(data); - - // Attempt to parse using the native JSON parser first - if (window.JSON && window.JSON.parse) { - return window.JSON.parse(data); - } - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if (rvalidchars.test(data.replace(rvalidescape, "@") - .replace(rvalidtokens, "]") - .replace(rvalidbraces, ""))) { - - return (new Function("return " + data))(); - - } - jQuery.error("Invalid JSON: " + data); - }, - - // Cross-browser xml parsing - parseXML: function (data) { - var xml, tmp; - try { - if (window.DOMParser) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString(data, "text/xml"); - } else { // IE - xml = new ActiveXObject("Microsoft.XMLDOM"); - xml.async = "false"; - xml.loadXML(data); - } - } catch (e) { - xml = undefined; - } - if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length) { - jQuery.error("Invalid XML: " + data); - } - return xml; - }, - - noop: function () { }, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function (data) { - if (data && rnotwhite.test(data)) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - (window.execScript || function (data) { - window["eval"].call(window, data); - })(data); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function (string) { - return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); - }, - - nodeName: function (elem, name) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function (object, callback, args) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction(object); - - if (args) { - if (isObj) { - for (name in object) { - if (callback.apply(object[name], args) === false) { - break; - } - } - } else { - for (; i < length; ) { - if (callback.apply(object[i++], args) === false) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if (isObj) { - for (name in object) { - if (callback.call(object[name], name, object[name]) === false) { - break; - } - } - } else { - for (; i < length; ) { - if (callback.call(object[i], i, object[i++]) === false) { - break; - } - } - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function (text) { - return text == null ? - "" : - trim.call(text); - } : - - // Otherwise use our own trimming functionality - function (text) { - return text == null ? - "" : - text.toString().replace(trimLeft, "").replace(trimRight, ""); - }, - - // results is for internal usage only - makeArray: function (array, results) { - var ret = results || []; - - if (array != null) { - // The window, strings (and functions) also have 'length' - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type(array); - - if (array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow(array)) { - push.call(ret, array); - } else { - jQuery.merge(ret, array); - } - } - - return ret; - }, - - inArray: function (elem, array, i) { - var len; - - if (array) { - if (indexOf) { - return indexOf.call(array, elem, i); - } - - len = array.length; - i = i ? i < 0 ? Math.max(0, len + i) : i : 0; - - for (; i < len; i++) { - // Skip accessing in sparse arrays - if (i in array && array[i] === elem) { - return i; - } - } - } - - return -1; - }, - - merge: function (first, second) { - var i = first.length, - j = 0; - - if (typeof second.length === "number") { - for (var l = second.length; j < l; j++) { - first[i++] = second[j]; - } - - } else { - while (second[j] !== undefined) { - first[i++] = second[j++]; - } - } - - first.length = i; - - return first; - }, - - grep: function (elems, callback, inv) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for (var i = 0, length = elems.length; i < length; i++) { - retVal = !!callback(elems[i], i); - if (inv !== retVal) { - ret.push(elems[i]); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function (elems, callback, arg) { - var value, key, ret = [], - i = 0, - length = elems.length, - // jquery objects are treated as arrays - isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ((length > 0 && elems[0] && elems[length - 1]) || length === 0 || jQuery.isArray(elems)); - - // Go through the array, translating each of the items to their - if (isArray) { - for (; i < length; i++) { - value = callback(elems[i], i, arg); - - if (value != null) { - ret[ret.length] = value; - } - } - - // Go through every key on the object, - } else { - for (key in elems) { - value = callback(elems[key], key, arg); - - if (value != null) { - ret[ret.length] = value; - } - } - } - - // Flatten any nested arrays - return ret.concat.apply([], ret); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function (fn, context) { - if (typeof context === "string") { - var tmp = fn[context]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if (!jQuery.isFunction(fn)) { - return undefined; - } - - // Simulated bind - var args = slice.call(arguments, 2), - proxy = function () { - return fn.apply(context, args.concat(slice.call(arguments))); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can optionally be executed if it's a function - access: function (elems, key, value, exec, fn, pass) { - var length = elems.length; - - // Setting many attributes - if (typeof key === "object") { - for (var k in key) { - jQuery.access(elems, k, key[k], exec, fn, value); - } - return elems; - } - - // Setting one attribute - if (value !== undefined) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for (var i = 0; i < length; i++) { - fn(elems[i], key, exec ? value.call(elems[i], i, fn(elems[i], key)) : value, pass); - } - - return elems; - } - - // Getting an attribute - return length ? fn(elems[0], key) : undefined; - }, - - now: function () { - return (new Date()).getTime(); - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function (ua) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec(ua) || - ropera.exec(ua) || - rmsie.exec(ua) || - ua.indexOf("compatible") < 0 && rmozilla.exec(ua) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - sub: function () { - function jQuerySub(selector, context) { - return new jQuerySub.fn.init(selector, context); - } - jQuery.extend(true, jQuerySub, this); - jQuerySub.superclass = this; - jQuerySub.fn = jQuerySub.prototype = this(); - jQuerySub.fn.constructor = jQuerySub; - jQuerySub.sub = this.sub; - jQuerySub.fn.init = function init(selector, context) { - if (context && context instanceof jQuery && !(context instanceof jQuerySub)) { - context = jQuerySub(context); - } - - return jQuery.fn.init.call(this, selector, context, rootjQuerySub); - }; - jQuerySub.fn.init.prototype = jQuerySub.fn; - var rootjQuerySub = jQuerySub(document); - return jQuerySub; - }, - - browser: {} - }); - - // Populate the class2type map - jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function (i, name) { - class2type["[object " + name + "]"] = name.toLowerCase(); - }); - - browserMatch = jQuery.uaMatch(userAgent); - if (browserMatch.browser) { - jQuery.browser[browserMatch.browser] = true; - jQuery.browser.version = browserMatch.version; - } - - // Deprecated, use jQuery.browser.webkit instead - if (jQuery.browser.webkit) { - jQuery.browser.safari = true; - } - - // IE doesn't match non-breaking spaces with \s - if (rnotwhite.test("\xA0")) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; - } - - // All jQuery objects should point back to these - rootjQuery = jQuery(document); - - // Cleanup functions for the document ready method - if (document.addEventListener) { - DOMContentLoaded = function () { - document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); - jQuery.ready(); - }; - - } else if (document.attachEvent) { - DOMContentLoaded = function () { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if (document.readyState === "complete") { - document.detachEvent("onreadystatechange", DOMContentLoaded); - jQuery.ready(); - } - }; - } - - // The DOM ready check for Internet Explorer - function doScrollCheck() { - if (jQuery.isReady) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch (e) { - setTimeout(doScrollCheck, 1); - return; - } - - // and execute any waiting functions - jQuery.ready(); - } - - return jQuery; - - })(); - - - // String to Object flags format cache - var flagsCache = {}; - - // Convert String-formatted flags into Object-formatted ones and store in cache - function createFlags(flags) { - var object = flagsCache[flags] = {}, - i, length; - flags = flags.split(/\s+/); - for (i = 0, length = flags.length; i < length; i++) { - object[flags[i]] = true; - } - return object; - } - - /* - * Create a callback list using the following parameters: - * - * flags: an optional list of space-separated flags that will change how - * the callback list behaves - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible flags: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ - jQuery.Callbacks = function (flags) { - - // Convert flags from String-formatted to Object-formatted - // (we check in cache first) - flags = flags ? (flagsCache[flags] || createFlags(flags)) : {}; - - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Add one or several callbacks to the list - add = function (args) { - var i, - length, - elem, - type, - actual; - for (i = 0, length = args.length; i < length; i++) { - elem = args[i]; - type = jQuery.type(elem); - if (type === "array") { - // Inspect recursively - add(elem); - } else if (type === "function") { - // Add if not in unique mode and callback is not in - if (!flags.unique || !self.has(elem)) { - list.push(elem); - } - } - } - }, - // Fire callbacks - fire = function (context, args) { - args = args || []; - memory = !flags.memory || [context, args]; - firing = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - for (; list && firingIndex < firingLength; firingIndex++) { - if (list[firingIndex].apply(context, args) === false && flags.stopOnFalse) { - memory = true; // Mark as halted - break; - } - } - firing = false; - if (list) { - if (!flags.once) { - if (stack && stack.length) { - memory = stack.shift(); - self.fireWith(memory[0], memory[1]); - } - } else if (memory === true) { - self.disable(); - } else { - list = []; - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function () { - if (list) { - var length = list.length; - add(arguments); - // Do we need to add the callbacks to the - // current firing batch? - if (firing) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if (memory && memory !== true) { - firingStart = length; - fire(memory[0], memory[1]); - } - } - return this; - }, - // Remove a callback from the list - remove: function () { - if (list) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for (; argIndex < argLength; argIndex++) { - for (var i = 0; i < list.length; i++) { - if (args[argIndex] === list[i]) { - // Handle firingIndex and firingLength - if (firing) { - if (i <= firingLength) { - firingLength--; - if (i <= firingIndex) { - firingIndex--; - } - } - } - // Remove the element - list.splice(i--, 1); - // If we have some unicity property then - // we only need to do this once - if (flags.unique) { - break; - } - } - } - } - } - return this; - }, - // Control if a given callback is in the list - has: function (fn) { - if (list) { - var i = 0, - length = list.length; - for (; i < length; i++) { - if (fn === list[i]) { - return true; - } - } - } - return false; - }, - // Remove all callbacks from the list - empty: function () { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function () { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function () { - return !list; - }, - // Lock the list in its current state - lock: function () { - stack = undefined; - if (!memory || memory === true) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function () { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function (context, args) { - if (stack) { - if (firing) { - if (!flags.once) { - stack.push([context, args]); - } - } else if (!(flags.once && memory)) { - fire(context, args); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function () { - self.fireWith(this, arguments); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function () { - return !!memory; - } - }; - - return self; - }; - - - - - var // Static reference to slice - sliceDeferred = [].slice; - - jQuery.extend({ - - Deferred: function (func) { - var doneList = jQuery.Callbacks("once memory"), - failList = jQuery.Callbacks("once memory"), - progressList = jQuery.Callbacks("memory"), - state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList - }, - promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - - state: function () { - return state; - }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function (doneCallbacks, failCallbacks, progressCallbacks) { - deferred.done(doneCallbacks).fail(failCallbacks).progress(progressCallbacks); - return this; - }, - always: function () { - deferred.done.apply(deferred, arguments).fail.apply(deferred, arguments); - return this; - }, - pipe: function (fnDone, fnFail, fnProgress) { - return jQuery.Deferred(function (newDefer) { - jQuery.each({ - done: [fnDone, "resolve"], - fail: [fnFail, "reject"], - progress: [fnProgress, "notify"] - }, function (handler, data) { - var fn = data[0], - action = data[1], - returned; - if (jQuery.isFunction(fn)) { - deferred[handler](function () { - returned = fn.apply(this, arguments); - if (returned && jQuery.isFunction(returned.promise)) { - returned.promise().then(newDefer.resolve, newDefer.reject, newDefer.notify); - } else { - newDefer[action + "With"](this === deferred ? newDefer : this, [returned]); - } - }); - } else { - deferred[handler](newDefer[action]); - } - }); - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function (obj) { - if (obj == null) { - obj = promise; - } else { - for (var key in promise) { - obj[key] = promise[key]; - } - } - return obj; - } - }, - deferred = promise.promise({}), - key; - - for (key in lists) { - deferred[key] = lists[key].fire; - deferred[key + "With"] = lists[key].fireWith; - } - - // Handle state - deferred.done(function () { - state = "resolved"; - }, failList.disable, progressList.lock).fail(function () { - state = "rejected"; - }, doneList.disable, progressList.lock); - - // Call given func if any - if (func) { - func.call(deferred, deferred); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function (firstParam) { - var args = sliceDeferred.call(arguments, 0), - i = 0, - length = args.length, - pValues = new Array(length), - count = length, - pCount = length, - deferred = length <= 1 && firstParam && jQuery.isFunction(firstParam.promise) ? - firstParam : - jQuery.Deferred(), - promise = deferred.promise(); - function resolveFunc(i) { - return function (value) { - args[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value; - if (!(--count)) { - deferred.resolveWith(deferred, args); - } - }; - } - function progressFunc(i) { - return function (value) { - pValues[i] = arguments.length > 1 ? sliceDeferred.call(arguments, 0) : value; - deferred.notifyWith(promise, pValues); - }; - } - if (length > 1) { - for (; i < length; i++) { - if (args[i] && args[i].promise && jQuery.isFunction(args[i].promise)) { - args[i].promise().then(resolveFunc(i), deferred.reject, progressFunc(i)); - } else { - --count; - } - } - if (!count) { - deferred.resolveWith(deferred, args); - } - } else if (deferred !== firstParam) { - deferred.resolveWith(deferred, length ? [firstParam] : []); - } - return promise; - } - }); - - - - - jQuery.support = (function () { - - var support, - all, - a, - select, - opt, - input, - marginDiv, - fragment, - tds, - events, - eventName, - i, - isSupported, - div = document.createElement("div"), - documentElement = document.documentElement; - - // Preliminary tests - div.setAttribute("className", "t"); - div.innerHTML = "
    a"; - - all = div.getElementsByTagName("*"); - a = div.getElementsByTagName("a")[0]; - - // Can't get basic test support - if (!all || !all.length || !a) { - return {}; - } - - // First batch of supports tests - select = document.createElement("select"); - opt = select.appendChild(document.createElement("option")); - input = div.getElementsByTagName("input")[0]; - - support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: (div.firstChild.nodeType === 3), - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test(a.getAttribute("style")), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: (a.getAttribute("href") === "/a"), - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55/.test(a.style.opacity), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: (input.value === "on"), - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", - - // Tests for enctype support on a form(#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode(true).outerHTML !== "<:nav>", - - // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true - }; - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode(true).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - } catch (e) { - support.deleteExpando = false; - } - - if (!div.addEventListener && div.attachEvent && div.fireEvent) { - div.attachEvent("onclick", function () { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode(true).fireEvent("onclick"); - } - - // Check if a radio maintains its value - // after being appended to the DOM - input = document.createElement("input"); - input.value = "t"; - input.setAttribute("type", "radio"); - support.radioValue = input.value === "t"; - - input.setAttribute("checked", "checked"); - div.appendChild(input); - fragment = document.createDocumentFragment(); - fragment.appendChild(div.lastChild); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - fragment.removeChild(input); - fragment.appendChild(div); - - div.innerHTML = ""; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if (window.getComputedStyle) { - marginDiv = document.createElement("div"); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.style.width = "2px"; - div.appendChild(marginDiv); - support.reliableMarginRight = - (parseInt((window.getComputedStyle(marginDiv, null) || { marginRight: 0 }).marginRight, 10) || 0) === 0; - } - - // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP - if (div.attachEvent) { - for (i in { - submit: 1, - change: 1, - focusin: 1 - }) { - eventName = "on" + i; - isSupported = (eventName in div); - if (!isSupported) { - div.setAttribute(eventName, "return;"); - isSupported = (typeof div[eventName] === "function"); - } - support[i + "Bubbles"] = isSupported; - } - } - - fragment.removeChild(div); - - // Null elements to avoid leaks in IE - fragment = select = opt = marginDiv = div = input = null; - - // Run tests that need a body at doc ready - jQuery(function () { - var container, outer, inner, table, td, offsetSupport, - conMarginTop, ptlm, vb, style, html, - body = document.getElementsByTagName("body")[0]; - - if (!body) { - // Return for frameset docs that don't have a body - return; - } - - conMarginTop = 1; - ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; - vb = "visibility:hidden;border:0;"; - style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; - html = "
    " + - "" + - "
    "; - - container = document.createElement("div"); - container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; - body.insertBefore(container, body.firstChild); - - // Construct the test element - div = document.createElement("div"); - container.appendChild(div); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - div.innerHTML = "
    t
    "; - tds = div.getElementsByTagName("td"); - isSupported = (tds[0].offsetHeight === 0); - - tds[0].style.display = ""; - tds[1].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE <= 8 fail this test) - support.reliableHiddenOffsets = isSupported && (tds[0].offsetHeight === 0); - - // Figure out if the W3C box model works as expected - div.innerHTML = ""; - div.style.width = div.style.paddingLeft = "1px"; - jQuery.boxModel = support.boxModel = div.offsetWidth === 2; - - if (typeof div.style.zoom !== "undefined") { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - support.inlineBlockNeedsLayout = (div.offsetWidth === 2); - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "
    "; - support.shrinkWrapBlocks = (div.offsetWidth !== 2); - } - - div.style.cssText = ptlm + vb; - div.innerHTML = html; - - outer = div.firstChild; - inner = outer.firstChild; - td = outer.nextSibling.firstChild.firstChild; - - offsetSupport = { - doesNotAddBorder: (inner.offsetTop !== 5), - doesAddBorderForTableAndCells: (td.offsetTop === 5) - }; - - inner.style.position = "fixed"; - inner.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - offsetSupport.fixedPosition = (inner.offsetTop === 20 || inner.offsetTop === 15); - inner.style.position = inner.style.top = ""; - - outer.style.overflow = "hidden"; - outer.style.position = "relative"; - - offsetSupport.subtractsBorderForOverflowNotVisible = (inner.offsetTop === -5); - offsetSupport.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== conMarginTop); - - body.removeChild(container); - div = container = null; - - jQuery.extend(support, offsetSupport); - }); - - return support; - })(); - - - - - var rbrace = /^(?:\{.*\}|\[.*\])$/, - rmultiDash = /([A-Z])/g; - - jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + (jQuery.fn.jquery + Math.random()).replace(/\D/g, ""), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function (elem) { - elem = elem.nodeType ? jQuery.cache[elem[jQuery.expando]] : elem[jQuery.expando]; - return !!elem && !isEmptyDataObject(elem); - }, - - data: function (elem, name, data, pvt /* Internal Use Only */) { - if (!jQuery.acceptData(elem)) { - return; - } - - var privateCache, thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[internalKey] : elem[internalKey] && internalKey, - isEvents = name === "events"; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ((!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined) { - return; - } - - if (!id) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if (isNode) { - elem[internalKey] = id = ++jQuery.uuid; - } else { - id = internalKey; - } - } - - if (!cache[id]) { - cache[id] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if (!isNode) { - cache[id].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if (typeof name === "object" || typeof name === "function") { - if (pvt) { - cache[id] = jQuery.extend(cache[id], name); - } else { - cache[id].data = jQuery.extend(cache[id].data, name); - } - } - - privateCache = thisCache = cache[id]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if (!pvt) { - if (!thisCache.data) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if (data !== undefined) { - thisCache[jQuery.camelCase(name)] = data; - } - - // Users should not attempt to inspect the internal events object using jQuery.data, - // it is undocumented and subject to change. But does anyone listen? No. - if (isEvents && !thisCache[name]) { - return privateCache.events; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if (getByName) { - - // First Try to find as-is property data - ret = thisCache[name]; - - // Test for null|undefined property data - if (ret == null) { - - // Try to find the camelCased property - ret = thisCache[jQuery.camelCase(name)]; - } - } else { - ret = thisCache; - } - - return ret; - }, - - removeData: function (elem, name, pvt /* Internal Use Only */) { - if (!jQuery.acceptData(elem)) { - return; - } - - var thisCache, i, l, - - // Reference to internal data cache key - internalKey = jQuery.expando, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[internalKey] : internalKey; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if (!cache[id]) { - return; - } - - if (name) { - - thisCache = pvt ? cache[id] : cache[id].data; - - if (thisCache) { - - // Support array or space separated string names for data keys - if (!jQuery.isArray(name)) { - - // try the string as a key before any manipulation - if (name in thisCache) { - name = [name]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase(name); - if (name in thisCache) { - name = [name]; - } else { - name = name.split(" "); - } - } - } - - for (i = 0, l = name.length; i < l; i++) { - delete thisCache[name[i]]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if (!(pvt ? isEmptyDataObject : jQuery.isEmptyObject)(thisCache)) { - return; - } - } - } - - // See jQuery.data for more information - if (!pvt) { - delete cache[id].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if (!isEmptyDataObject(cache[id])) { - return; - } - } - - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - // Ensure that `cache` is not a window object #10080 - if (jQuery.support.deleteExpando || !cache.setInterval) { - delete cache[id]; - } else { - cache[id] = null; - } - - // We destroyed the cache and need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - if (isNode) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if (jQuery.support.deleteExpando) { - delete elem[internalKey]; - } else if (elem.removeAttribute) { - elem.removeAttribute(internalKey); - } else { - elem[internalKey] = null; - } - } - }, - - // For internal use only. - _data: function (elem, name, data) { - return jQuery.data(elem, name, data, true); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function (elem) { - if (elem.nodeName) { - var match = jQuery.noData[elem.nodeName.toLowerCase()]; - - if (match) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } - }); - - jQuery.fn.extend({ - data: function (key, value) { - var parts, attr, name, - data = null; - - if (typeof key === "undefined") { - if (this.length) { - data = jQuery.data(this[0]); - - if (this[0].nodeType === 1 && !jQuery._data(this[0], "parsedAttrs")) { - attr = this[0].attributes; - for (var i = 0, l = attr.length; i < l; i++) { - name = attr[i].name; - - if (name.indexOf("data-") === 0) { - name = jQuery.camelCase(name.substring(5)); - - dataAttr(this[0], name, data[name]); - } - } - jQuery._data(this[0], "parsedAttrs", true); - } - } - - return data; - - } else if (typeof key === "object") { - return this.each(function () { - jQuery.data(this, key); - }); - } - - parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if (value === undefined) { - data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - // Try to fetch any internally stored data first - if (data === undefined && this.length) { - data = jQuery.data(this[0], key); - data = dataAttr(this[0], key, data); - } - - return data === undefined && parts[1] ? - this.data(parts[0]) : - data; - - } else { - return this.each(function () { - var self = jQuery(this), - args = [parts[0], value]; - - self.triggerHandler("setData" + parts[1] + "!", args); - jQuery.data(this, key, value); - self.triggerHandler("changeData" + parts[1] + "!", args); - }); - } - }, - - removeData: function (key) { - return this.each(function () { - jQuery.removeData(this, key); - }); - } - }); - - function dataAttr(elem, key, data) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if (data === undefined && elem.nodeType === 1) { - - var name = "data-" + key.replace(rmultiDash, "-$1").toLowerCase(); - - data = elem.getAttribute(name); - - if (typeof data === "string") { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - jQuery.isNumeric(data) ? parseFloat(data) : - rbrace.test(data) ? jQuery.parseJSON(data) : - data; - } catch (e) { } - - // Make sure we set the data so it isn't changed later - jQuery.data(elem, key, data); - - } else { - data = undefined; - } - } - - return data; - } - - // checks a cache object for emptiness - function isEmptyDataObject(obj) { - for (var name in obj) { - - // if the public data object is empty, the private is still empty - if (name === "data" && jQuery.isEmptyObject(obj[name])) { - continue; - } - if (name !== "toJSON") { - return false; - } - } - - return true; - } - - - - - function handleQueueMarkDefer(elem, type, src) { - var deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - defer = jQuery._data(elem, deferDataKey); - if (defer && - (src === "queue" || !jQuery._data(elem, queueDataKey)) && - (src === "mark" || !jQuery._data(elem, markDataKey))) { - // Give room for hard-coded callbacks to fire first - // and eventually mark/queue something else on the element - setTimeout(function () { - if (!jQuery._data(elem, queueDataKey) && - !jQuery._data(elem, markDataKey)) { - jQuery.removeData(elem, deferDataKey, true); - defer.fire(); - } - }, 0); - } - } - - jQuery.extend({ - - _mark: function (elem, type) { - if (elem) { - type = (type || "fx") + "mark"; - jQuery._data(elem, type, (jQuery._data(elem, type) || 0) + 1); - } - }, - - _unmark: function (force, elem, type) { - if (force !== true) { - type = elem; - elem = force; - force = false; - } - if (elem) { - type = type || "fx"; - var key = type + "mark", - count = force ? 0 : ((jQuery._data(elem, key) || 1) - 1); - if (count) { - jQuery._data(elem, key, count); - } else { - jQuery.removeData(elem, key, true); - handleQueueMarkDefer(elem, type, "mark"); - } - } - }, - - queue: function (elem, type, data) { - var q; - if (elem) { - type = (type || "fx") + "queue"; - q = jQuery._data(elem, type); - - // Speed up dequeue by getting out quickly if this is just a lookup - if (data) { - if (!q || jQuery.isArray(data)) { - q = jQuery._data(elem, type, jQuery.makeArray(data)); - } else { - q.push(data); - } - } - return q || []; - } - }, - - dequeue: function (elem, type) { - type = type || "fx"; - - var queue = jQuery.queue(elem, type), - fn = queue.shift(), - hooks = {}; - - // If the fx queue is dequeued, always remove the progress sentinel - if (fn === "inprogress") { - fn = queue.shift(); - } - - if (fn) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if (type === "fx") { - queue.unshift("inprogress"); - } - - jQuery._data(elem, type + ".run", hooks); - fn.call(elem, function () { - jQuery.dequeue(elem, type); - }, hooks); - } - - if (!queue.length) { - jQuery.removeData(elem, type + "queue " + type + ".run", true); - handleQueueMarkDefer(elem, type, "queue"); - } - } - }); - - jQuery.fn.extend({ - queue: function (type, data) { - if (typeof type !== "string") { - data = type; - type = "fx"; - } - - if (data === undefined) { - return jQuery.queue(this[0], type); - } - return this.each(function () { - var queue = jQuery.queue(this, type, data); - - if (type === "fx" && queue[0] !== "inprogress") { - jQuery.dequeue(this, type); - } - }); - }, - dequeue: function (type) { - return this.each(function () { - jQuery.dequeue(this, type); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function (time, type) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue(type, function (next, hooks) { - var timeout = setTimeout(next, time); - hooks.stop = function () { - clearTimeout(timeout); - }; - }); - }, - clearQueue: function (type) { - return this.queue(type || "fx", []); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function (type, object) { - if (typeof type !== "string") { - object = type; - type = undefined; - } - type = type || "fx"; - var defer = jQuery.Deferred(), - elements = this, - i = elements.length, - count = 1, - deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - tmp; - function resolve() { - if (!(--count)) { - defer.resolveWith(elements, [elements]); - } - } - while (i--) { - if ((tmp = jQuery.data(elements[i], deferDataKey, undefined, true) || - (jQuery.data(elements[i], queueDataKey, undefined, true) || - jQuery.data(elements[i], markDataKey, undefined, true)) && - jQuery.data(elements[i], deferDataKey, jQuery.Callbacks("once memory"), true))) { - count++; - tmp.add(resolve); - } - } - resolve(); - return defer.promise(); - } - }); - - - - - var rclass = /[\n\t\r]/g, - rspace = /\s+/, - rreturn = /\r/g, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - nodeHook, boolHook, fixSpecified; - - jQuery.fn.extend({ - attr: function (name, value) { - return jQuery.access(this, name, value, true, jQuery.attr); - }, - - removeAttr: function (name) { - return this.each(function () { - jQuery.removeAttr(this, name); - }); - }, - - prop: function (name, value) { - return jQuery.access(this, name, value, true, jQuery.prop); - }, - - removeProp: function (name) { - name = jQuery.propFix[name] || name; - return this.each(function () { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[name] = undefined; - delete this[name]; - } catch (e) { } - }); - }, - - addClass: function (value) { - var classNames, i, l, elem, - setClass, c, cl; - - if (jQuery.isFunction(value)) { - return this.each(function (j) { - jQuery(this).addClass(value.call(this, j, this.className)); - }); - } - - if (value && typeof value === "string") { - classNames = value.split(rspace); - - for (i = 0, l = this.length; i < l; i++) { - elem = this[i]; - - if (elem.nodeType === 1) { - if (!elem.className && classNames.length === 1) { - elem.className = value; - - } else { - setClass = " " + elem.className + " "; - - for (c = 0, cl = classNames.length; c < cl; c++) { - if (! ~setClass.indexOf(" " + classNames[c] + " ")) { - setClass += classNames[c] + " "; - } - } - elem.className = jQuery.trim(setClass); - } - } - } - } - - return this; - }, - - removeClass: function (value) { - var classNames, i, l, elem, className, c, cl; - - if (jQuery.isFunction(value)) { - return this.each(function (j) { - jQuery(this).removeClass(value.call(this, j, this.className)); - }); - } - - if ((value && typeof value === "string") || value === undefined) { - classNames = (value || "").split(rspace); - - for (i = 0, l = this.length; i < l; i++) { - elem = this[i]; - - if (elem.nodeType === 1 && elem.className) { - if (value) { - className = (" " + elem.className + " ").replace(rclass, " "); - for (c = 0, cl = classNames.length; c < cl; c++) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = jQuery.trim(className); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function (value, stateVal) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if (jQuery.isFunction(value)) { - return this.each(function (i) { - jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal); - }); - } - - return this.each(function () { - if (type === "string") { - // toggle individual class names - var className, - i = 0, - self = jQuery(this), - state = stateVal, - classNames = value.split(rspace); - - while ((className = classNames[i++])) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass(className); - self[state ? "addClass" : "removeClass"](className); - } - - } else if (type === "undefined" || type === "boolean") { - if (this.className) { - // store className if set - jQuery._data(this, "__className__", this.className); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery._data(this, "__className__") || ""; - } - }); - }, - - hasClass: function (selector) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for (; i < l; i++) { - if (this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf(className) > -1) { - return true; - } - } - - return false; - }, - - val: function (value) { - var hooks, ret, isFunction, - elem = this[0]; - - if (!arguments.length) { - if (elem) { - hooks = jQuery.valHooks[elem.nodeName.toLowerCase()] || jQuery.valHooks[elem.type]; - - if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction(value); - - return this.each(function (i) { - var self = jQuery(this), val; - - if (this.nodeType !== 1) { - return; - } - - if (isFunction) { - val = value.call(this, i, self.val()); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if (val == null) { - val = ""; - } else if (typeof val === "number") { - val += ""; - } else if (jQuery.isArray(val)) { - val = jQuery.map(val, function (value) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[this.nodeName.toLowerCase()] || jQuery.valHooks[this.type]; - - // If set returns undefined, fall back to normal setting - if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { - this.value = val; - } - }); - } - }); - - jQuery.extend({ - valHooks: { - option: { - get: function (elem) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - }, - select: { - get: function (elem) { - var value, i, max, option, - index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if (index < 0) { - return null; - } - - // Loop through all the selected options - i = one ? index : 0; - max = one ? index + 1 : options.length; - for (; i < max; i++) { - option = options[i]; - - // Don't return options that are disabled or in a disabled optgroup - if (option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup"))) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if (one) { - return value; - } - - // Multi-Selects return an array - values.push(value); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if (one && !values.length && options.length) { - return jQuery(options[index]).val(); - } - - return values; - }, - - set: function (elem, value) { - var values = jQuery.makeArray(value); - - jQuery(elem).find("option").each(function () { - this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0; - }); - - if (!values.length) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function (elem, name, value, pass) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if (!elem || nType === 3 || nType === 8 || nType === 2) { - return; - } - - if (pass && name in jQuery.attrFn) { - return jQuery(elem)[name](value); - } - - // Fallback to prop when attributes are not supported - if (typeof elem.getAttribute === "undefined") { - return jQuery.prop(elem, name, value); - } - - notxml = nType !== 1 || !jQuery.isXMLDoc(elem); - - // All attributes are lowercase - // Grab necessary hook if one is defined - if (notxml) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[name] || (rboolean.test(name) ? boolHook : nodeHook); - } - - if (value !== undefined) { - - if (value === null) { - jQuery.removeAttr(elem, name); - return; - - } else if (hooks && "set" in hooks && notxml && (ret = hooks.set(elem, value, name)) !== undefined) { - return ret; - - } else { - elem.setAttribute(name, "" + value); - return value; - } - - } else if (hooks && "get" in hooks && notxml && (ret = hooks.get(elem, name)) !== null) { - return ret; - - } else { - - ret = elem.getAttribute(name); - - // Non-existent attributes return null, we normalize to undefined - return ret === null ? - undefined : - ret; - } - }, - - removeAttr: function (elem, value) { - var propName, attrNames, name, l, - i = 0; - - if (value && elem.nodeType === 1) { - attrNames = value.toLowerCase().split(rspace); - l = attrNames.length; - - for (; i < l; i++) { - name = attrNames[i]; - - if (name) { - propName = jQuery.propFix[name] || name; - - // See #9699 for explanation of this approach (setting first, then removal) - jQuery.attr(elem, name, ""); - elem.removeAttribute(getSetAttribute ? name : propName); - - // Set corresponding property to false for boolean attributes - if (rboolean.test(name) && propName in elem) { - elem[propName] = false; - } - } - } - } - }, - - attrHooks: { - type: { - set: function (elem, value) { - // We can't allow the type property to be changed (since it causes problems in IE) - if (rtype.test(elem.nodeName) && elem.parentNode) { - jQuery.error("type property can't be changed"); - } else if (!jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input")) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to it's default in case type is set after value - // This is for element creation - var val = elem.value; - elem.setAttribute("type", value); - if (val) { - elem.value = val; - } - return value; - } - } - }, - // Use the value property for back compat - // Use the nodeHook for button elements in IE6/7 (#1954) - value: { - get: function (elem, name) { - if (nodeHook && jQuery.nodeName(elem, "button")) { - return nodeHook.get(elem, name); - } - return name in elem ? - elem.value : - null; - }, - set: function (elem, value, name) { - if (nodeHook && jQuery.nodeName(elem, "button")) { - return nodeHook.set(elem, value, name); - } - // Does not return so that setAttribute is also used - elem.value = value; - } - } - }, - - propFix: { - tabindex: "tabIndex", - readonly: "readOnly", - "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" - }, - - prop: function (elem, name, value) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if (!elem || nType === 3 || nType === 8 || nType === 2) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc(elem); - - if (notxml) { - // Fix name and attach hooks - name = jQuery.propFix[name] || name; - hooks = jQuery.propHooks[name]; - } - - if (value !== undefined) { - if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { - return ret; - - } else { - return (elem[name] = value); - } - - } else { - if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { - return ret; - - } else { - return elem[name]; - } - } - }, - - propHooks: { - tabIndex: { - get: function (elem) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - var attributeNode = elem.getAttributeNode("tabindex"); - - return attributeNode && attributeNode.specified ? - parseInt(attributeNode.value, 10) : - rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href ? - 0 : - undefined; - } - } - } - }); - - // Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) - jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; - - // Hook for boolean attributes - boolHook = { - get: function (elem, name) { - // Align boolean attributes with corresponding properties - // Fall back to attribute presence where some booleans are not supported - var attrNode, - property = jQuery.prop(elem, name); - return property === true || typeof property !== "boolean" && (attrNode = elem.getAttributeNode(name)) && attrNode.nodeValue !== false ? - name.toLowerCase() : - undefined; - }, - set: function (elem, value, name) { - var propName; - if (value === false) { - // Remove boolean attributes when set to false - jQuery.removeAttr(elem, name); - } else { - // value is true since we know at this point it's type boolean and not false - // Set boolean attributes to the same name and set the DOM property - propName = jQuery.propFix[name] || name; - if (propName in elem) { - // Only set the IDL specifically if it already exists on the element - elem[propName] = true; - } - - elem.setAttribute(name, name.toLowerCase()); - } - return name; - } - }; - - // IE6/7 do not support getting/setting some attributes with get/setAttribute - if (!getSetAttribute) { - - fixSpecified = { - name: true, - id: true - }; - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function (elem, name) { - var ret; - ret = elem.getAttributeNode(name); - return ret && (fixSpecified[name] ? ret.nodeValue !== "" : ret.specified) ? - ret.nodeValue : - undefined; - }, - set: function (elem, value, name) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode(name); - if (!ret) { - ret = document.createAttribute(name); - elem.setAttributeNode(ret); - } - return (ret.nodeValue = value + ""); - } - }; - - // Apply the nodeHook to tabindex - jQuery.attrHooks.tabindex.set = nodeHook.set; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each(["width", "height"], function (i, name) { - jQuery.attrHooks[name] = jQuery.extend(jQuery.attrHooks[name], { - set: function (elem, value) { - if (value === "") { - elem.setAttribute(name, "auto"); - return value; - } - } - }); - }); - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function (elem, value, name) { - if (value === "") { - value = "false"; - } - nodeHook.set(elem, value, name); - } - }; - } - - - // Some attributes require a special call on IE - if (!jQuery.support.hrefNormalized) { - jQuery.each(["href", "src", "width", "height"], function (i, name) { - jQuery.attrHooks[name] = jQuery.extend(jQuery.attrHooks[name], { - get: function (elem) { - var ret = elem.getAttribute(name, 2); - return ret === null ? undefined : ret; - } - }); - }); - } - - if (!jQuery.support.style) { - jQuery.attrHooks.style = { - get: function (elem) { - // Return undefined in the case of empty string - // Normalize to lowercase since IE uppercases css property names - return elem.style.cssText.toLowerCase() || undefined; - }, - set: function (elem, value) { - return (elem.style.cssText = "" + value); - } - }; - } - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if (!jQuery.support.optSelected) { - jQuery.propHooks.selected = jQuery.extend(jQuery.propHooks.selected, { - get: function (elem) { - var parent = elem.parentNode; - - if (parent) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if (parent.parentNode) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }); - } - - // IE6/7 call enctype encoding - if (!jQuery.support.enctype) { - jQuery.propFix.enctype = "encoding"; - } - - // Radios and checkboxes getter/setter - if (!jQuery.support.checkOn) { - jQuery.each(["radio", "checkbox"], function () { - jQuery.valHooks[this] = { - get: function (elem) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); - } - jQuery.each(["radio", "checkbox"], function () { - jQuery.valHooks[this] = jQuery.extend(jQuery.valHooks[this], { - set: function (elem, value) { - if (jQuery.isArray(value)) { - return (elem.checked = jQuery.inArray(jQuery(elem).val(), value) >= 0); - } - } - }); - }); - - - - - var rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, - rhoverHack = /\bhover(\.\S+)?\b/, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, - quickParse = function (selector) { - var quick = rquickIs.exec(selector); - if (quick) { - // 0 1 2 3 - // [ _, tag, id, class ] - quick[1] = (quick[1] || "").toLowerCase(); - quick[3] = quick[3] && new RegExp("(?:^|\\s)" + quick[3] + "(?:\\s|$)"); - } - return quick; - }, - quickIs = function (elem, m) { - var attrs = elem.attributes || {}; - return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || (attrs.id || {}).value === m[2]) && - (!m[3] || m[3].test((attrs["class"] || {}).value)) - ); - }, - hoverHack = function (events) { - return jQuery.event.special.hover ? events : events.replace(rhoverHack, "mouseenter$1 mouseleave$1"); - }; - - /* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ - jQuery.event = { - - add: function (elem, types, handler, data, selector) { - - var elemData, eventHandle, events, - t, tns, type, namespaces, handleObj, - handleObjIn, quick, handlers, special; - - // Don't attach events to noData or text/comment nodes (allow plain objects tho) - if (elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if (handler.handler) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if (!handler.guid) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - events = elemData.events; - if (!events) { - elemData.events = events = {}; - } - eventHandle = elemData.handle; - if (!eventHandle) { - elemData.handle = eventHandle = function (e) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply(eventHandle.elem, arguments) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = jQuery.trim(hoverHack(types)).split(" "); - for (t = 0; t < types.length; t++) { - - tns = rtypenamespace.exec(types[t]) || []; - type = tns[1]; - namespaces = (tns[2] || "").split(".").sort(); - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[type] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = (selector ? special.delegateType : special.bindType) || type; - - // Update special based on newly reset type - special = jQuery.event.special[type] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: tns[1], - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - quick: quickParse(selector), - namespace: namespaces.join(".") - }, handleObjIn); - - // Init the event handler queue if we're the first - handlers = events[type]; - if (!handlers) { - handlers = events[type] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { - // Bind the global event handler to the element - if (elem.addEventListener) { - elem.addEventListener(type, eventHandle, false); - - } else if (elem.attachEvent) { - elem.attachEvent("on" + type, eventHandle); - } - } - } - - if (special.add) { - special.add.call(elem, handleObj); - - if (!handleObj.handler.guid) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if (selector) { - handlers.splice(handlers.delegateCount++, 0, handleObj); - } else { - handlers.push(handleObj); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[type] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function (elem, types, handler, selector, mappedTypes) { - - var elemData = jQuery.hasData(elem) && jQuery._data(elem), - t, tns, type, origType, namespaces, origCount, - j, events, special, handle, eventType, handleObj; - - if (!elemData || !(events = elemData.events)) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = jQuery.trim(hoverHack(types || "")).split(" "); - for (t = 0; t < types.length; t++) { - tns = rtypenamespace.exec(types[t]) || []; - type = origType = tns[1]; - namespaces = tns[2]; - - // Unbind all events (on this namespace, if provided) for the element - if (!type) { - for (type in events) { - jQuery.event.remove(elem, type + types[t], handler, selector, true); - } - continue; - } - - special = jQuery.event.special[type] || {}; - type = (selector ? special.delegateType : special.bindType) || type; - eventType = events[type] || []; - origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - - // Remove matching events - for (j = 0; j < eventType.length; j++) { - handleObj = eventType[j]; - - if ((mappedTypes || origType === handleObj.origType) && - (!handler || handler.guid === handleObj.guid) && - (!namespaces || namespaces.test(handleObj.namespace)) && - (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { - eventType.splice(j--, 1); - - if (handleObj.selector) { - eventType.delegateCount--; - } - if (special.remove) { - special.remove.call(elem, handleObj); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if (eventType.length === 0 && origCount !== eventType.length) { - if (!special.teardown || special.teardown.call(elem, namespaces) === false) { - jQuery.removeEvent(elem, type, elemData.handle); - } - - delete events[type]; - } - } - - // Remove the expando if it's no longer used - if (jQuery.isEmptyObject(events)) { - handle = elemData.handle; - if (handle) { - handle.elem = null; - } - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery.removeData(elem, ["events", "handle"], true); - } - }, - - // Events that are safe to short-circuit if no handlers are attached. - // Native DOM events should not be added, they may have inline handlers. - customEvent: { - "getData": true, - "setData": true, - "changeData": true - }, - - trigger: function (event, data, elem, onlyHandlers) { - // Don't do events on text and comment nodes - if (elem && (elem.nodeType === 3 || elem.nodeType === 8)) { - return; - } - - // Event object or event type - var type = event.type || event, - namespaces = [], - cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if (rfocusMorph.test(type + jQuery.event.triggered)) { - return; - } - - if (type.indexOf("!") >= 0) { - // Exclusive events trigger only for the exact event (no namespaces) - type = type.slice(0, -1); - exclusive = true; - } - - if (type.indexOf(".") >= 0) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - - if ((!elem || jQuery.event.customEvent[type]) && !jQuery.event.global[type]) { - // No jQuery handlers for this event type, and it can't have inline handlers - return; - } - - // Caller can pass in an Event, Object, or just an event type string - event = typeof event === "object" ? - // jQuery.Event object - event[jQuery.expando] ? event : - // Object literal - new jQuery.Event(type, event) : - // Just the event type (string) - new jQuery.Event(type); - - event.type = type; - event.isTrigger = true; - event.exclusive = exclusive; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - ontype = type.indexOf(":") < 0 ? "on" + type : ""; - - // Handle a global trigger - if (!elem) { - - // TODO: Stop taunting the data cache; remove global events and always attach to document - cache = jQuery.cache; - for (i in cache) { - if (cache[i].events && cache[i].events[type]) { - jQuery.event.trigger(event, data, cache[i].handle.elem, true); - } - } - return; - } - - // Clean up the event in case it is being reused - event.result = undefined; - if (!event.target) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data != null ? jQuery.makeArray(data) : []; - data.unshift(event); - - // Allow special events to draw outside the lines - special = jQuery.event.special[type] || {}; - if (special.trigger && special.trigger.apply(elem, data) === false) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - eventPath = [[elem, special.bindType || type]]; - if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { - - bubbleType = special.delegateType || type; - cur = rfocusMorph.test(bubbleType + type) ? elem : elem.parentNode; - old = null; - for (; cur; cur = cur.parentNode) { - eventPath.push([cur, bubbleType]); - old = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if (old && old === elem.ownerDocument) { - eventPath.push([old.defaultView || old.parentWindow || window, bubbleType]); - } - } - - // Fire handlers on the event path - for (i = 0; i < eventPath.length && !event.isPropagationStopped(); i++) { - - cur = eventPath[i][0]; - event.type = eventPath[i][1]; - - handle = (jQuery._data(cur, "events") || {})[event.type] && jQuery._data(cur, "handle"); - if (handle) { - handle.apply(cur, data); - } - // Note that this is a bare JS function and not a jQuery handler - handle = ontype && cur[ontype]; - if (handle && jQuery.acceptData(cur) && handle.apply(cur, data) === false) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if (!onlyHandlers && !event.isDefaultPrevented()) { - - if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && - !(type === "click" && jQuery.nodeName(elem, "a")) && jQuery.acceptData(elem)) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - // IE<9 dies on focus/blur to hidden element (#1486) - if (ontype && elem[type] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow(elem)) { - - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ontype]; - - if (old) { - elem[ontype] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[type](); - jQuery.event.triggered = undefined; - - if (old) { - elem[ontype] = old; - } - } - } - } - - return event.result; - }, - - dispatch: function (event) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix(event || window.event); - - var handlers = ((jQuery._data(this, "events") || {})[event.type] || []), - delegateCount = handlers.delegateCount, - args = [].slice.call(arguments, 0), - run_all = !event.exclusive && !event.namespace, - handlerQueue = [], - i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Determine handlers that should run if there are delegated events - // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) - if (delegateCount && !event.target.disabled && !(event.button && event.type === "click")) { - - // Pregenerate a single jQuery object for reuse with .is() - jqcur = jQuery(this); - jqcur.context = this.ownerDocument || this; - - for (cur = event.target; cur != this; cur = cur.parentNode || this) { - selMatch = {}; - matches = []; - jqcur[0] = cur; - for (i = 0; i < delegateCount; i++) { - handleObj = handlers[i]; - sel = handleObj.selector; - - if (selMatch[sel] === undefined) { - selMatch[sel] = ( - handleObj.quick ? quickIs(cur, handleObj.quick) : jqcur.is(sel) - ); - } - if (selMatch[sel]) { - matches.push(handleObj); - } - } - if (matches.length) { - handlerQueue.push({ elem: cur, matches: matches }); - } - } - } - - // Add the remaining (directly-bound) handlers - if (handlers.length > delegateCount) { - handlerQueue.push({ elem: this, matches: handlers.slice(delegateCount) }); - } - - // Run delegates first; they may want to stop propagation beneath us - for (i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++) { - matched = handlerQueue[i]; - event.currentTarget = matched.elem; - - for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++) { - handleObj = matched.matches[j]; - - // Triggered event must either 1) be non-exclusive and have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if (run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test(handleObj.namespace)) { - - event.data = handleObj.data; - event.handleObj = handleObj; - - ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler) - .apply(matched.elem, args); - - if (ret !== undefined) { - event.result = ret; - if (ret === false) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - return event.result; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** - props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function (event, original) { - - // Add which for key events - if (event.which == null) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function (event, original) { - var eventDoc, doc, body, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if (event.pageX == null && original.clientX != null) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = original.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add relatedTarget, if necessary - if (!event.relatedTarget && fromElement) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if (!event.which && button !== undefined) { - event.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0))); - } - - return event; - } - }, - - fix: function (event) { - if (event[jQuery.expando]) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, - originalEvent = event, - fixHook = jQuery.event.fixHooks[event.type] || {}, - copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; - - event = jQuery.Event(originalEvent); - - for (i = copy.length; i; ) { - prop = copy[--i]; - event[prop] = originalEvent[prop]; - } - - // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) - if (!event.target) { - event.target = originalEvent.srcElement || document; - } - - // Target should not be a text node (#504, Safari) - if (event.target.nodeType === 3) { - event.target = event.target.parentNode; - } - - // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) - if (event.metaKey === undefined) { - event.metaKey = event.ctrlKey; - } - - return fixHook.filter ? fixHook.filter(event, originalEvent) : event; - }, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady - }, - - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - - focus: { - delegateType: "focusin" - }, - blur: { - delegateType: "focusout" - }, - - beforeunload: { - setup: function (data, namespaces, eventHandle) { - // We only want to do this special case on windows - if (jQuery.isWindow(this)) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function (namespaces, eventHandle) { - if (this.onbeforeunload === eventHandle) { - this.onbeforeunload = null; - } - } - } - }, - - simulate: function (type, elem, event, bubble) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if (bubble) { - jQuery.event.trigger(e, null, elem); - } else { - jQuery.event.dispatch.call(elem, e); - } - if (e.isDefaultPrevented()) { - event.preventDefault(); - } - } - }; - - // Some plugins are using, but it's undocumented/deprecated and will be removed. - // The 1.7 special event interface should provide all the hooks needed now. - jQuery.event.handle = jQuery.event.dispatch; - - jQuery.removeEvent = document.removeEventListener ? - function (elem, type, handle) { - if (elem.removeEventListener) { - elem.removeEventListener(type, handle, false); - } - } : - function (elem, type, handle) { - if (elem.detachEvent) { - elem.detachEvent("on" + type, handle); - } - }; - - jQuery.Event = function (src, props) { - // Allow instantiation without the 'new' keyword - if (!(this instanceof jQuery.Event)) { - return new jQuery.Event(src, props); - } - - // Event object - if (src && src.type) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if (props) { - jQuery.extend(this, props); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[jQuery.expando] = true; - }; - - function returnFalse() { - return false; - } - function returnTrue() { - return true; - } - - // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding - // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html - jQuery.Event.prototype = { - preventDefault: function () { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if (!e) { - return; - } - - // if preventDefault exists run it on the original event - if (e.preventDefault) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function () { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if (!e) { - return; - } - // if stopPropagation exists run it on the original event - if (e.stopPropagation) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function () { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse - }; - - // Create mouseenter/leave events using mouseover/out and event-time checks - jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" - }, function (orig, fix) { - jQuery.event.special[orig] = { - delegateType: fix, - bindType: fix, - - handle: function (event) { - var target = this, - related = event.relatedTarget, - handleObj = event.handleObj, - selector = handleObj.selector, - ret; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if (!related || (related !== target && !jQuery.contains(target, related))) { - event.type = handleObj.origType; - ret = handleObj.handler.apply(this, arguments); - event.type = fix; - } - return ret; - } - }; - }); - - // IE submit delegation - if (!jQuery.support.submitBubbles) { - - jQuery.event.special.submit = { - setup: function () { - // Only need this for delegated form submit events - if (jQuery.nodeName(this, "form")) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add(this, "click._submit keypress._submit", function (e) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName(elem, "input") || jQuery.nodeName(elem, "button") ? elem.form : undefined; - if (form && !form._submit_attached) { - jQuery.event.add(form, "submit._submit", function (event) { - // If form was submitted by the user, bubble the event up the tree - if (this.parentNode && !event.isTrigger) { - jQuery.event.simulate("submit", this.parentNode, event, true); - } - }); - form._submit_attached = true; - } - }); - // return undefined since we don't need an event listener - }, - - teardown: function () { - // Only need this for delegated form submit events - if (jQuery.nodeName(this, "form")) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove(this, "._submit"); - } - }; - } - - // IE change delegation and checkbox/radio fix - if (!jQuery.support.changeBubbles) { - - jQuery.event.special.change = { - - setup: function () { - - if (rformElems.test(this.nodeName)) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if (this.type === "checkbox" || this.type === "radio") { - jQuery.event.add(this, "propertychange._change", function (event) { - if (event.originalEvent.propertyName === "checked") { - this._just_changed = true; - } - }); - jQuery.event.add(this, "click._change", function (event) { - if (this._just_changed && !event.isTrigger) { - this._just_changed = false; - jQuery.event.simulate("change", this, event, true); - } - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add(this, "beforeactivate._change", function (e) { - var elem = e.target; - - if (rformElems.test(elem.nodeName) && !elem._change_attached) { - jQuery.event.add(elem, "change._change", function (event) { - if (this.parentNode && !event.isSimulated && !event.isTrigger) { - jQuery.event.simulate("change", this.parentNode, event, true); - } - }); - elem._change_attached = true; - } - }); - }, - - handle: function (event) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if (this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox")) { - return event.handleObj.handler.apply(this, arguments); - } - }, - - teardown: function () { - jQuery.event.remove(this, "._change"); - - return rformElems.test(this.nodeName); - } - }; - } - - // Create "bubbling" focus and blur events - if (!jQuery.support.focusinBubbles) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function (orig, fix) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function (event) { - jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true); - }; - - jQuery.event.special[fix] = { - setup: function () { - if (attaches++ === 0) { - document.addEventListener(orig, handler, true); - } - }, - teardown: function () { - if (--attaches === 0) { - document.removeEventListener(orig, handler, true); - } - } - }; - }); - } - - jQuery.fn.extend({ - - on: function (types, selector, data, fn, /*INTERNAL*/one) { - var origFn, type; - - // Types can be a map of types/handlers - if (typeof types === "object") { - // ( types-Object, selector, data ) - if (typeof selector !== "string") { - // ( types-Object, data ) - data = selector; - selector = undefined; - } - for (type in types) { - this.on(type, selector, data, types[type], one); - } - return this; - } - - if (data == null && fn == null) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if (fn == null) { - if (typeof selector === "string") { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if (fn === false) { - fn = returnFalse; - } else if (!fn) { - return this; - } - - if (one === 1) { - origFn = fn; - fn = function (event) { - // Can use an empty set, since event contains the info - jQuery().off(event); - return origFn.apply(this, arguments); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); - } - return this.each(function () { - jQuery.event.add(this, types, fn, data, selector); - }); - }, - one: function (types, selector, data, fn) { - return this.on.call(this, types, selector, data, fn, 1); - }, - off: function (types, selector, fn) { - if (types && types.preventDefault && types.handleObj) { - // ( event ) dispatched jQuery.Event - var handleObj = types.handleObj; - jQuery(types.delegateTarget).off( - handleObj.namespace ? handleObj.type + "." + handleObj.namespace : handleObj.type, - handleObj.selector, - handleObj.handler - ); - return this; - } - if (typeof types === "object") { - // ( types-object [, selector] ) - for (var type in types) { - this.off(type, selector, types[type]); - } - return this; - } - if (selector === false || typeof selector === "function") { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if (fn === false) { - fn = returnFalse; - } - return this.each(function () { - jQuery.event.remove(this, types, fn, selector); - }); - }, - - bind: function (types, data, fn) { - return this.on(types, null, data, fn); - }, - unbind: function (types, fn) { - return this.off(types, null, fn); - }, - - live: function (types, data, fn) { - jQuery(this.context).on(types, this.selector, data, fn); - return this; - }, - die: function (types, fn) { - jQuery(this.context).off(types, this.selector || "**", fn); - return this; - }, - - delegate: function (selector, types, data, fn) { - return this.on(types, selector, data, fn); - }, - undelegate: function (selector, types, fn) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length == 1 ? this.off(selector, "**") : this.off(types, selector, fn); - }, - - trigger: function (type, data) { - return this.each(function () { - jQuery.event.trigger(type, data, this); - }); - }, - triggerHandler: function (type, data) { - if (this[0]) { - return jQuery.event.trigger(type, data, this[0], true); - } - }, - - toggle: function (fn) { - // Save reference to arguments for access in closure - var args = arguments, - guid = fn.guid || jQuery.guid++, - i = 0, - toggler = function (event) { - // Figure out which function to execute - var lastToggle = (jQuery._data(this, "lastToggle" + fn.guid) || 0) % i; - jQuery._data(this, "lastToggle" + fn.guid, lastToggle + 1); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[lastToggle].apply(this, arguments) || false; - }; - - // link all the functions, so any of them can unbind this click handler - toggler.guid = guid; - while (i < args.length) { - args[i++].guid = guid; - } - - return this.click(toggler); - }, - - hover: function (fnOver, fnOut) { - return this.mouseenter(fnOver).mouseleave(fnOut || fnOver); - } - }); - - jQuery.each(("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu").split(" "), function (i, name) { - - // Handle event binding - jQuery.fn[name] = function (data, fn) { - if (fn == null) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.on(name, null, data, fn) : - this.trigger(name); - }; - - if (jQuery.attrFn) { - jQuery.attrFn[name] = true; - } - - if (rkeyEvent.test(name)) { - jQuery.event.fixHooks[name] = jQuery.event.keyHooks; - } - - if (rmouseEvent.test(name)) { - jQuery.event.fixHooks[name] = jQuery.event.mouseHooks; - } - }); - - - - /*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ - (function () { - - var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - expando = "sizcache" + (Math.random() + '').replace('.', ''), - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rReturn = /\r\n/g, - rNonWord = /\W/; - - // Here we check if the JavaScript engine is using some sort of - // optimization where it does not always call our comparision - // function. If that is the case, discard the hasDuplicate value. - // Thus far that includes Google Chrome. - [0, 0].sort(function () { - baseHasDuplicate = false; - return 0; - }); - - var Sizzle = function (selector, context, results, seed) { - results = results || []; - context = context || document; - - var origContext = context; - - if (context.nodeType !== 1 && context.nodeType !== 9) { - return []; - } - - if (!selector || typeof selector !== "string") { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML(context), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec(""); - m = chunker.exec(soFar); - - if (m) { - soFar = m[3]; - - parts.push(m[1]); - - if (m[2]) { - extra = m[3]; - break; - } - } - } while (m); - - if (parts.length > 1 && origPOS.exec(selector)) { - - if (parts.length === 2 && Expr.relative[parts[0]]) { - set = posProcess(parts[0] + parts[1], context, seed); - - } else { - set = Expr.relative[parts[0]] ? - [context] : - Sizzle(parts.shift(), context); - - while (parts.length) { - selector = parts.shift(); - - if (Expr.relative[selector]) { - selector += parts.shift(); - } - - set = posProcess(selector, set, seed); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if (!seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1])) { - - ret = Sizzle.find(parts.shift(), context, contextXML); - context = ret.expr ? - Sizzle.filter(ret.expr, ret.set)[0] : - ret.set[0]; - } - - if (context) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed)} : - Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML); - - set = ret.expr ? - Sizzle.filter(ret.expr, ret.set) : - ret.set; - - if (parts.length > 0) { - checkSet = makeArray(set); - - } else { - prune = false; - } - - while (parts.length) { - cur = parts.pop(); - pop = cur; - - if (!Expr.relative[cur]) { - cur = ""; - } else { - pop = parts.pop(); - } - - if (pop == null) { - pop = context; - } - - Expr.relative[cur](checkSet, pop, contextXML); - } - - } else { - checkSet = parts = []; - } - } - - if (!checkSet) { - checkSet = set; - } - - if (!checkSet) { - Sizzle.error(cur || selector); - } - - if (toString.call(checkSet) === "[object Array]") { - if (!prune) { - results.push.apply(results, checkSet); - - } else if (context && context.nodeType === 1) { - for (i = 0; checkSet[i] != null; i++) { - if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) { - results.push(set[i]); - } - } - - } else { - for (i = 0; checkSet[i] != null; i++) { - if (checkSet[i] && checkSet[i].nodeType === 1) { - results.push(set[i]); - } - } - } - - } else { - makeArray(checkSet, results); - } - - if (extra) { - Sizzle(extra, origContext, results, seed); - Sizzle.uniqueSort(results); - } - - return results; - }; - - Sizzle.uniqueSort = function (results) { - if (sortOrder) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); - - if (hasDuplicate) { - for (var i = 1; i < results.length; i++) { - if (results[i] === results[i - 1]) { - results.splice(i--, 1); - } - } - } - } - - return results; - }; - - Sizzle.matches = function (expr, set) { - return Sizzle(expr, null, null, set); - }; - - Sizzle.matchesSelector = function (node, expr) { - return Sizzle(expr, null, null, [node]).length > 0; - }; - - Sizzle.find = function (expr, context, isXML) { - var set, i, len, match, type, left; - - if (!expr) { - return []; - } - - for (i = 0, len = Expr.order.length; i < len; i++) { - type = Expr.order[i]; - - if ((match = Expr.leftMatch[type].exec(expr))) { - left = match[1]; - match.splice(1, 1); - - if (left.substr(left.length - 1) !== "\\") { - match[1] = (match[1] || "").replace(rBackslash, ""); - set = Expr.find[type](match, context, isXML); - - if (set != null) { - expr = expr.replace(Expr.match[type], ""); - break; - } - } - } - } - - if (!set) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName("*") : - []; - } - - return { set: set, expr: expr }; - }; - - Sizzle.filter = function (expr, set, inplace, not) { - var match, anyFound, - type, found, item, filter, left, - i, pass, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); - - while (expr && set.length) { - for (type in Expr.filter) { - if ((match = Expr.leftMatch[type].exec(expr)) != null && match[2]) { - filter = Expr.filter[type]; - left = match[1]; - - anyFound = false; - - match.splice(1, 1); - - if (left.substr(left.length - 1) === "\\") { - continue; - } - - if (curLoop === result) { - result = []; - } - - if (Expr.preFilter[type]) { - match = Expr.preFilter[type](match, curLoop, inplace, result, not, isXMLFilter); - - if (!match) { - anyFound = found = true; - - } else if (match === true) { - continue; - } - } - - if (match) { - for (i = 0; (item = curLoop[i]) != null; i++) { - if (item) { - found = filter(item, match, i, curLoop); - pass = not ^ found; - - if (inplace && found != null) { - if (pass) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if (pass) { - result.push(item); - anyFound = true; - } - } - } - } - - if (found !== undefined) { - if (!inplace) { - curLoop = result; - } - - expr = expr.replace(Expr.match[type], ""); - - if (!anyFound) { - return []; - } - - break; - } - } - } - - // Improper expression - if (expr === old) { - if (anyFound == null) { - Sizzle.error(expr); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; - }; - - Sizzle.error = function (msg) { - throw new Error("Syntax error, unrecognized expression: " + msg); - }; - - /** - * Utility function for retreiving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ - var getText = Sizzle.getText = function (elem) { - var i, node, - nodeType = elem.nodeType, - ret = ""; - - if (nodeType) { - if (nodeType === 1 || nodeType === 9) { - // Use textContent || innerText for elements - if (typeof elem.textContent === 'string') { - return elem.textContent; - } else if (typeof elem.innerText === 'string') { - // Replace IE's carriage returns - return elem.innerText.replace(rReturn, ''); - } else { - // Traverse it's children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText(elem); - } - } - } else if (nodeType === 3 || nodeType === 4) { - return elem.nodeValue; - } - } else { - - // If no nodeType, this is expected to be an array - for (i = 0; (node = elem[i]); i++) { - // Do not traverse comment nodes - if (node.nodeType !== 8) { - ret += getText(node); - } - } - } - return ret; - }; - - var Expr = Sizzle.selectors = { - order: ["ID", "NAME", "TAG"], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function (elem) { - return elem.getAttribute("href"); - }, - type: function (elem) { - return elem.getAttribute("type"); - } - }, - - relative: { - "+": function (checkSet, part) { - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if (isTag) { - part = part.toLowerCase(); - } - - for (var i = 0, l = checkSet.length, elem; i < l; i++) { - if ((elem = checkSet[i])) { - while ((elem = elem.previousSibling) && elem.nodeType !== 1) { } - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if (isPartStrNotTag) { - Sizzle.filter(part, checkSet, true); - } - }, - - ">": function (checkSet, part) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if (isPartStr && !rNonWord.test(part)) { - part = part.toLowerCase(); - - for (; i < l; i++) { - elem = checkSet[i]; - - if (elem) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for (; i < l; i++) { - elem = checkSet[i]; - - if (elem) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if (isPartStr) { - Sizzle.filter(part, checkSet, true); - } - } - }, - - "": function (checkSet, part, isXML) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if (typeof part === "string" && !rNonWord.test(part)) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - - "~": function (checkSet, part, isXML) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if (typeof part === "string" && !rNonWord.test(part)) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - - find: { - ID: function (match, context, isXML) { - if (typeof context.getElementById !== "undefined" && !isXML) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function (match, context) { - if (typeof context.getElementsByName !== "undefined") { - var ret = [], - results = context.getElementsByName(match[1]); - - for (var i = 0, l = results.length; i < l; i++) { - if (results[i].getAttribute("name") === match[1]) { - ret.push(results[i]); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function (match, context) { - if (typeof context.getElementsByTagName !== "undefined") { - return context.getElementsByTagName(match[1]); - } - } - }, - preFilter: { - CLASS: function (match, curLoop, inplace, result, not, isXML) { - match = " " + match[1].replace(rBackslash, "") + " "; - - if (isXML) { - return match; - } - - for (var i = 0, elem; (elem = curLoop[i]) != null; i++) { - if (elem) { - if (not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0)) { - if (!inplace) { - result.push(elem); - } - - } else if (inplace) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function (match) { - return match[1].replace(rBackslash, ""); - }, - - TAG: function (match, curLoop) { - return match[1].replace(rBackslash, "").toLowerCase(); - }, - - CHILD: function (match) { - if (match[1] === "nth") { - if (!match[2]) { - Sizzle.error(match[0]); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test(match[2]) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if (match[2]) { - Sizzle.error(match[0]); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function (match, curLoop, inplace, result, not, isXML) { - var name = match[1] = match[1].replace(rBackslash, ""); - - if (!isXML && Expr.attrMap[name]) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = (match[4] || match[5] || "").replace(rBackslash, ""); - - if (match[2] === "~=") { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function (match, curLoop, inplace, result, not) { - if (match[1] === "not") { - // If we're dealing with a complex expression, or a simple one - if ((chunker.exec(match[3]) || "").length > 1 || /^\w/.test(match[3])) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if (!inplace) { - result.push.apply(result, ret); - } - - return false; - } - - } else if (Expr.match.POS.test(match[0]) || Expr.match.CHILD.test(match[0])) { - return true; - } - - return match; - }, - - POS: function (match) { - match.unshift(true); - - return match; - } - }, - - filters: { - enabled: function (elem) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function (elem) { - return elem.disabled === true; - }, - - checked: function (elem) { - return elem.checked === true; - }, - - selected: function (elem) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if (elem.parentNode) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function (elem) { - return !!elem.firstChild; - }, - - empty: function (elem) { - return !elem.firstChild; - }, - - has: function (elem, i, match) { - return !!Sizzle(match[3], elem).length; - }, - - header: function (elem) { - return (/h\d/i).test(elem.nodeName); - }, - - text: function (elem) { - var attr = elem.getAttribute("type"), type = elem.type; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && "text" === type && (attr === type || attr === null); - }, - - radio: function (elem) { - return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function (elem) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function (elem) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function (elem) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function (elem) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function (elem) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function (elem) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function (elem) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && "button" === elem.type || name === "button"; - }, - - input: function (elem) { - return (/input|select|textarea|button/i).test(elem.nodeName); - }, - - focus: function (elem) { - return elem === elem.ownerDocument.activeElement; - } - }, - setFilters: { - first: function (elem, i) { - return i === 0; - }, - - last: function (elem, i, match, array) { - return i === array.length - 1; - }, - - even: function (elem, i) { - return i % 2 === 0; - }, - - odd: function (elem, i) { - return i % 2 === 1; - }, - - lt: function (elem, i, match) { - return i < match[3] - 0; - }, - - gt: function (elem, i, match) { - return i > match[3] - 0; - }, - - nth: function (elem, i, match) { - return match[3] - 0 === i; - }, - - eq: function (elem, i, match) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function (elem, match, i, array) { - var name = match[1], - filter = Expr.filters[name]; - - if (filter) { - return filter(elem, i, match, array); - - } else if (name === "contains") { - return (elem.textContent || elem.innerText || getText([elem]) || "").indexOf(match[3]) >= 0; - - } else if (name === "not") { - var not = match[3]; - - for (var j = 0, l = not.length; j < l; j++) { - if (not[j] === elem) { - return false; - } - } - - return true; - - } else { - Sizzle.error(name); - } - }, - - CHILD: function (elem, match) { - var first, last, - doneName, parent, cache, - count, diff, - type = match[1], - node = elem; - - switch (type) { - case "only": - case "first": - while ((node = node.previousSibling)) { - if (node.nodeType === 1) { - return false; - } - } - - if (type === "first") { - return true; - } - - node = elem; - - case "last": - while ((node = node.nextSibling)) { - if (node.nodeType === 1) { - return false; - } - } - - return true; - - case "nth": - first = match[2]; - last = match[3]; - - if (first === 1 && last === 0) { - return true; - } - - doneName = match[0]; - parent = elem.parentNode; - - if (parent && (parent[expando] !== doneName || !elem.nodeIndex)) { - count = 0; - - for (node = parent.firstChild; node; node = node.nextSibling) { - if (node.nodeType === 1) { - node.nodeIndex = ++count; - } - } - - parent[expando] = doneName; - } - - diff = elem.nodeIndex - last; - - if (first === 0) { - return diff === 0; - - } else { - return (diff % first === 0 && diff / first >= 0); - } - } - }, - - ID: function (elem, match) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function (elem, match) { - return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; - }, - - CLASS: function (elem, match) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf(match) > -1; - }, - - ATTR: function (elem, match) { - var name = match[1], - result = Sizzle.attr ? - Sizzle.attr(elem, name) : - Expr.attrHandle[name] ? - Expr.attrHandle[name](elem) : - elem[name] != null ? - elem[name] : - elem.getAttribute(name), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - !type && Sizzle.attr ? - result != null : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function (elem, match, i, array) { - var name = match[2], - filter = Expr.setFilters[name]; - - if (filter) { - return filter(elem, i, match, array); - } - } - } - }; - - var origPOS = Expr.match.POS, - fescape = function (all, num) { - return "\\" + (num - 0 + 1); - }; - - for (var type in Expr.match) { - Expr.match[type] = new RegExp(Expr.match[type].source + (/(?![^\[]*\])(?![^\(]*\))/.source)); - Expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + Expr.match[type].source.replace(/\\(\d+)/g, fescape)); - } - - var makeArray = function (array, results) { - array = Array.prototype.slice.call(array, 0); - - if (results) { - results.push.apply(results, array); - return results; - } - - return array; - }; - - // Perform a simple check to determine if the browser is capable of - // converting a NodeList to an array using builtin methods. - // Also verifies that the returned array holds DOM nodes - // (which is not the case in the Blackberry browser) - try { - Array.prototype.slice.call(document.documentElement.childNodes, 0)[0].nodeType; - - // Provide a fallback method if it does not work - } catch (e) { - makeArray = function (array, results) { - var i = 0, - ret = results || []; - - if (toString.call(array) === "[object Array]") { - Array.prototype.push.apply(ret, array); - - } else { - if (typeof array.length === "number") { - for (var l = array.length; i < l; i++) { - ret.push(array[i]); - } - - } else { - for (; array[i]; i++) { - ret.push(array[i]); - } - } - } - - return ret; - }; - } - - var sortOrder, siblingCheck; - - if (document.documentElement.compareDocumentPosition) { - sortOrder = function (a, b) { - if (a === b) { - hasDuplicate = true; - return 0; - } - - if (!a.compareDocumentPosition || !b.compareDocumentPosition) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - - } else { - sortOrder = function (a, b) { - // The nodes are identical, we can exit early - if (a === b) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if (a.sourceIndex && b.sourceIndex) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if (aup === bup) { - return siblingCheck(a, b); - - // If no parents were found then the nodes are disconnected - } else if (!aup) { - return -1; - - } else if (!bup) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while (cur) { - ap.unshift(cur); - cur = cur.parentNode; - } - - cur = bup; - - while (cur) { - bp.unshift(cur); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for (var i = 0; i < al && i < bl; i++) { - if (ap[i] !== bp[i]) { - return siblingCheck(ap[i], bp[i]); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck(a, bp[i], -1) : - siblingCheck(ap[i], b, 1); - }; - - siblingCheck = function (a, b, ret) { - if (a === b) { - return ret; - } - - var cur = a.nextSibling; - - while (cur) { - if (cur === b) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; - } - - // Check to see if the browser returns elements by name when - // querying by getElementById (and provide a workaround) - (function () { - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore(form, root.firstChild); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if (document.getElementById(id)) { - Expr.find.ID = function (match, context, isXML) { - if (typeof context.getElementById !== "undefined" && !isXML) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function (elem, match) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild(form); - - // release memory in IE - root = form = null; - })(); - - (function () { - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild(document.createComment("")); - - // Make sure no comments are found - if (div.getElementsByTagName("*").length > 0) { - Expr.find.TAG = function (match, context) { - var results = context.getElementsByTagName(match[1]); - - // Filter out possible comments - if (match[1] === "*") { - var tmp = []; - - for (var i = 0; results[i]; i++) { - if (results[i].nodeType === 1) { - tmp.push(results[i]); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - - if (div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#") { - - Expr.attrHandle.href = function (elem) { - return elem.getAttribute("href", 2); - }; - } - - // release memory in IE - div = null; - })(); - - if (document.querySelectorAll) { - (function () { - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "

    "; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if (div.querySelectorAll && div.querySelectorAll(".TEST").length === 0) { - return; - } - - Sizzle = function (query, context, extra, seed) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if (!seed && !Sizzle.isXML(context)) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(query); - - if (match && (context.nodeType === 1 || context.nodeType === 9)) { - // Speed-up: Sizzle("TAG") - if (match[1]) { - return makeArray(context.getElementsByTagName(query), extra); - - // Speed-up: Sizzle(".CLASS") - } else if (match[2] && Expr.find.CLASS && context.getElementsByClassName) { - return makeArray(context.getElementsByClassName(match[2]), extra); - } - } - - if (context.nodeType === 9) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if (query === "body" && context.body) { - return makeArray([context.body], extra); - - // Speed-up: Sizzle("#ID") - } else if (match && match[3]) { - var elem = context.getElementById(match[3]); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if (elem && elem.parentNode) { - // Handle the case where IE and Opera return items - // by name instead of ID - if (elem.id === match[3]) { - return makeArray([elem], extra); - } - - } else { - return makeArray([], extra); - } - } - - try { - return makeArray(context.querySelectorAll(query), extra); - } catch (qsaError) { } - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if (context.nodeType === 1 && context.nodeName.toLowerCase() !== "object") { - var oldContext = context, - old = context.getAttribute("id"), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test(query); - - if (!old) { - context.setAttribute("id", nid); - } else { - nid = nid.replace(/'/g, "\\$&"); - } - if (relativeHierarchySelector && hasParent) { - context = context.parentNode; - } - - try { - if (!relativeHierarchySelector || hasParent) { - return makeArray(context.querySelectorAll("[id='" + nid + "'] " + query), extra); - } - - } catch (pseudoError) { - } finally { - if (!old) { - oldContext.removeAttribute("id"); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for (var prop in oldSizzle) { - Sizzle[prop] = oldSizzle[prop]; - } - - // release memory in IE - div = null; - })(); - } - - (function () { - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - - if (matches) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9 fails this) - var disconnectedMatch = !matches.call(document.createElement("div"), "div"), - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call(document.documentElement, "[test!='']:sizzle"); - - } catch (pseudoError) { - pseudoWorks = true; - } - - Sizzle.matchesSelector = function (node, expr) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if (!Sizzle.isXML(node)) { - try { - if (pseudoWorks || !Expr.match.PSEUDO.test(expr) && !/!=/.test(expr)) { - var ret = matches.call(node, expr); - - // IE 9's matchesSelector returns false on disconnected nodes - if (ret || !disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9, so check for that - node.document && node.document.nodeType !== 11) { - return ret; - } - } - } catch (e) { } - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } - })(); - - (function () { - var div = document.createElement("div"); - - div.innerHTML = "
    "; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if (!div.getElementsByClassName || div.getElementsByClassName("e").length === 0) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if (div.getElementsByClassName("e").length === 1) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function (match, context, isXML) { - if (typeof context.getElementsByClassName !== "undefined" && !isXML) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; - })(); - - function dirNodeCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { - for (var i = 0, l = checkSet.length; i < l; i++) { - var elem = checkSet[i]; - - if (elem) { - var match = false; - - elem = elem[dir]; - - while (elem) { - if (elem[expando] === doneName) { - match = checkSet[elem.sizset]; - break; - } - - if (elem.nodeType === 1 && !isXML) { - elem[expando] = doneName; - elem.sizset = i; - } - - if (elem.nodeName.toLowerCase() === cur) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } - } - - function dirCheck(dir, cur, doneName, checkSet, nodeCheck, isXML) { - for (var i = 0, l = checkSet.length; i < l; i++) { - var elem = checkSet[i]; - - if (elem) { - var match = false; - - elem = elem[dir]; - - while (elem) { - if (elem[expando] === doneName) { - match = checkSet[elem.sizset]; - break; - } - - if (elem.nodeType === 1) { - if (!isXML) { - elem[expando] = doneName; - elem.sizset = i; - } - - if (typeof cur !== "string") { - if (elem === cur) { - match = true; - break; - } - - } else if (Sizzle.filter(cur, [elem]).length > 0) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } - } - - if (document.documentElement.contains) { - Sizzle.contains = function (a, b) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - - } else if (document.documentElement.compareDocumentPosition) { - Sizzle.contains = function (a, b) { - return !!(a.compareDocumentPosition(b) & 16); - }; - - } else { - Sizzle.contains = function () { - return false; - }; - } - - Sizzle.isXML = function (elem) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; - }; - - var posProcess = function (selector, context, seed) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ((match = Expr.match.PSEUDO.exec(selector))) { - later += match[0]; - selector = selector.replace(Expr.match.PSEUDO, ""); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for (var i = 0, l = root.length; i < l; i++) { - Sizzle(selector, root[i], tmpSet, seed); - } - - return Sizzle.filter(later, tmpSet); - }; - - // EXPOSE - // Override sizzle attribute retrieval - Sizzle.attr = jQuery.attr; - Sizzle.selectors.attrMap = {}; - jQuery.find = Sizzle; - jQuery.expr = Sizzle.selectors; - jQuery.expr[":"] = jQuery.expr.filters; - jQuery.unique = Sizzle.uniqueSort; - jQuery.text = Sizzle.getText; - jQuery.isXMLDoc = Sizzle.isXML; - jQuery.contains = Sizzle.contains; - - - })(); - - - var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - - jQuery.fn.extend({ - find: function (selector) { - var self = this, - i, l; - - if (typeof selector !== "string") { - return jQuery(selector).filter(function () { - for (i = 0, l = self.length; i < l; i++) { - if (jQuery.contains(self[i], this)) { - return true; - } - } - }); - } - - var ret = this.pushStack("", "find", selector), - length, n, r; - - for (i = 0, l = this.length; i < l; i++) { - length = ret.length; - jQuery.find(selector, this[i], ret); - - if (i > 0) { - // Make sure that the results are unique - for (n = length; n < ret.length; n++) { - for (r = 0; r < length; r++) { - if (ret[r] === ret[n]) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function (target) { - var targets = jQuery(target); - return this.filter(function () { - for (var i = 0, l = targets.length; i < l; i++) { - if (jQuery.contains(this, targets[i])) { - return true; - } - } - }); - }, - - not: function (selector) { - return this.pushStack(winnow(this, selector, false), "not", selector); - }, - - filter: function (selector) { - return this.pushStack(winnow(this, selector, true), "filter", selector); - }, - - is: function (selector) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - POS.test(selector) ? - jQuery(selector, this.context).index(this[0]) >= 0 : - jQuery.filter(selector, this).length > 0 : - this.filter(selector).length > 0); - }, - - closest: function (selectors, context) { - var ret = [], i, l, cur = this[0]; - - // Array (deprecated as of jQuery 1.7) - if (jQuery.isArray(selectors)) { - var level = 1; - - while (cur && cur.ownerDocument && cur !== context) { - for (i = 0; i < selectors.length; i++) { - - if (jQuery(cur).is(selectors[i])) { - ret.push({ selector: selectors[i], elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - - return ret; - } - - // String - var pos = POS.test(selectors) || typeof selectors !== "string" ? - jQuery(selectors, context || this.context) : - 0; - - for (i = 0, l = this.length; i < l; i++) { - cur = this[i]; - - while (cur) { - if (pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors)) { - ret.push(cur); - break; - - } else { - cur = cur.parentNode; - if (!cur || !cur.ownerDocument || cur === context || cur.nodeType === 11) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique(ret) : ret; - - return this.pushStack(ret, "closest", selectors); - }, - - // Determine the position of an element within - // the matched set of elements - index: function (elem) { - - // No argument, return index in parent - if (!elem) { - return (this[0] && this[0].parentNode) ? this.prevAll().length : -1; - } - - // index in selector - if (typeof elem === "string") { - return jQuery.inArray(this[0], jQuery(elem)); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this); - }, - - add: function (selector, context) { - var set = typeof selector === "string" ? - jQuery(selector, context) : - jQuery.makeArray(selector && selector.nodeType ? [selector] : selector), - all = jQuery.merge(this.get(), set); - - return this.pushStack(isDisconnected(set[0]) || isDisconnected(all[0]) ? - all : - jQuery.unique(all)); - }, - - andSelf: function () { - return this.add(this.prevObject); - } - }); - - // A painfully simple check to see if an element is disconnected - // from a document (should be improved, where feasible). - function isDisconnected(node) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; - } - - jQuery.each({ - parent: function (elem) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function (elem) { - return jQuery.dir(elem, "parentNode"); - }, - parentsUntil: function (elem, i, until) { - return jQuery.dir(elem, "parentNode", until); - }, - next: function (elem) { - return jQuery.nth(elem, 2, "nextSibling"); - }, - prev: function (elem) { - return jQuery.nth(elem, 2, "previousSibling"); - }, - nextAll: function (elem) { - return jQuery.dir(elem, "nextSibling"); - }, - prevAll: function (elem) { - return jQuery.dir(elem, "previousSibling"); - }, - nextUntil: function (elem, i, until) { - return jQuery.dir(elem, "nextSibling", until); - }, - prevUntil: function (elem, i, until) { - return jQuery.dir(elem, "previousSibling", until); - }, - siblings: function (elem) { - return jQuery.sibling(elem.parentNode.firstChild, elem); - }, - children: function (elem) { - return jQuery.sibling(elem.firstChild); - }, - contents: function (elem) { - return jQuery.nodeName(elem, "iframe") ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray(elem.childNodes); - } - }, function (name, fn) { - jQuery.fn[name] = function (until, selector) { - var ret = jQuery.map(this, fn, until); - - if (!runtil.test(name)) { - selector = until; - } - - if (selector && typeof selector === "string") { - ret = jQuery.filter(selector, ret); - } - - ret = this.length > 1 && !guaranteedUnique[name] ? jQuery.unique(ret) : ret; - - if ((this.length > 1 || rmultiselector.test(selector)) && rparentsprev.test(name)) { - ret = ret.reverse(); - } - - return this.pushStack(ret, name, slice.call(arguments).join(",")); - }; - }); - - jQuery.extend({ - filter: function (expr, elems, not) { - if (not) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function (elem, dir, until) { - var matched = [], - cur = elem[dir]; - - while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery(cur).is(until))) { - if (cur.nodeType === 1) { - matched.push(cur); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function (cur, result, dir, elem) { - result = result || 1; - var num = 0; - - for (; cur; cur = cur[dir]) { - if (cur.nodeType === 1 && ++num === result) { - break; - } - } - - return cur; - }, - - sibling: function (n, elem) { - var r = []; - - for (; n; n = n.nextSibling) { - if (n.nodeType === 1 && n !== elem) { - r.push(n); - } - } - - return r; - } - }); - - // Implement the identical functionality for filter and not - function winnow(elements, qualifier, keep) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - - if (jQuery.isFunction(qualifier)) { - return jQuery.grep(elements, function (elem, i) { - var retVal = !!qualifier.call(elem, i, elem); - return retVal === keep; - }); - - } else if (qualifier.nodeType) { - return jQuery.grep(elements, function (elem, i) { - return (elem === qualifier) === keep; - }); - - } else if (typeof qualifier === "string") { - var filtered = jQuery.grep(elements, function (elem) { - return elem.nodeType === 1; - }); - - if (isSimple.test(qualifier)) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter(qualifier, filtered); - } - } - - return jQuery.grep(elements, function (elem, i) { - return (jQuery.inArray(elem, qualifier) >= 0) === keep; - }); - } - - - - - function createSafeFragment(document) { - var list = nodeNames.split("|"), - safeFrag = document.createDocumentFragment(); - - if (safeFrag.createElement) { - while (list.length) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; - } - - var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /", ""], - legend: [1, "
    ", "
    "], - thead: [1, "", "
    "], - tr: [2, "", "
    "], - td: [3, "", "
    "], - col: [2, "", "
    "], - area: [1, "", ""], - _default: [0, "", ""] - }, - safeFragment = createSafeFragment(document); - - wrapMap.optgroup = wrapMap.option; - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; - wrapMap.th = wrapMap.td; - - // IE can't serialize and