From 2a39fd09ebe7f4fc8cd2ab0b39bbb0e466f357c3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 May 2011 15:11:55 -0700 Subject: [PATCH] Bring Javelin into Phabricator via git submodule, not copy-and-paste Summary: Javelin is currently embedded in Phabricator via copy-and-paste of prebuilt packages. This is not so great. Pull it in as a submodule instead and make all the Phabriator resources declare proper dependency trees. Add Javelin linting. Test Plan: I tried to run through pretty much all the JS functionality on the site. This is still a high-risk change, but I did a pretty thorough test Differential: inline comments, revealing diffs, list tokenizers, comment preview, editing/deleting comments, add review action. Maniphest: list tokenizer, comment actions Herald: rule editing, tokenizers, add/remove rows Reviewed By: tomo Reviewers: aran, tomo, mroch, jungejason, tuomaspelkonen CC: aran, tomo, epriestley Differential Revision: 223 --- .arcconfig | 2 +- .gitmodules | 3 + externals/javelin | 1 + scripts/celerity_mapper.php | 33 +- src/__celerity_resource_map__.php | 527 ++- src/__phutil_library_map__.php | 4 + .../celerity/map/CelerityResourceMap.php | 16 + .../CelerityStaticResourceResponse.php | 27 +- .../lint/engine/PhabricatorLintEngine.php | 45 + src/infrastructure/lint/engine/__init__.php | 14 + .../javelin/PhabricatorJavelinLinter.php | 207 ++ .../lint/linter/javelin/__init__.php | 18 + .../AphrontTokenizerTemplateView.php | 2 +- .../tokenizer/AphrontFormTokenizerControl.php | 14 - src/view/form/control/tokenizer/__init__.php | 1 - .../standard/PhabricatorStandardPageView.php | 6 +- .../js/application/core/MultirowRowManager.js | 5 +- .../application/core/behavior-dark-console.js | 5 + .../js/application/core/behavior-error-log.js | 2 +- .../core/behavior-object-selector.js | 6 +- .../js/application/core/behavior-tokenizer.js | 8 +- .../js/application/core/behavior-workflow.js | 4 +- .../differential/behavior-add-reviewers.js | 6 +- .../differential/behavior-comment-preview.js | 10 +- .../differential/behavior-diff-radios.js | 4 +- .../behavior-edit-inline-comments.js | 16 +- .../differential/behavior-populate.js | 7 +- .../behavior-show-all-comments.js | 6 +- .../differential/behavior-show-more.js | 8 +- .../application/diffusion/behavior-jump-to.js | 5 +- .../diffusion/behavior-pull-lastmodified.js | 7 +- .../js/application/herald/HeraldRuleEditor.js | 13 +- .../js/application/herald/PathTypeahead.js | 8 +- .../application/herald/herald-rule-editor.js | 2 +- .../behavior-transaction-controls.js | 32 +- .../js/application/owners/OwnersPathEditor.js | 9 +- .../application/owners/owners-path-editor.js | 2 +- webroot/rsrc/js/javelin | 1 + webroot/rsrc/js/javelin/init.dev.js | 179 - webroot/rsrc/js/javelin/init.min.js | 2 - webroot/rsrc/js/javelin/javelin.dev.js | 2955 ----------------- webroot/rsrc/js/javelin/javelin.min.js | 3 - webroot/rsrc/js/javelin/typeahead.dev.js | 1123 ------- webroot/rsrc/js/javelin/typeahead.min.js | 3 - webroot/rsrc/js/javelin/workflow.dev.js | 242 -- webroot/rsrc/js/javelin/workflow.min.js | 3 - 46 files changed, 880 insertions(+), 4716 deletions(-) create mode 100644 .gitmodules create mode 160000 externals/javelin create mode 100644 src/infrastructure/lint/engine/PhabricatorLintEngine.php create mode 100644 src/infrastructure/lint/engine/__init__.php create mode 100644 src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php create mode 100644 src/infrastructure/lint/linter/javelin/__init__.php create mode 120000 webroot/rsrc/js/javelin delete mode 100644 webroot/rsrc/js/javelin/init.dev.js delete mode 100644 webroot/rsrc/js/javelin/init.min.js delete mode 100644 webroot/rsrc/js/javelin/javelin.dev.js delete mode 100644 webroot/rsrc/js/javelin/javelin.min.js delete mode 100644 webroot/rsrc/js/javelin/typeahead.dev.js delete mode 100644 webroot/rsrc/js/javelin/typeahead.min.js delete mode 100644 webroot/rsrc/js/javelin/workflow.dev.js delete mode 100644 webroot/rsrc/js/javelin/workflow.min.js diff --git a/.arcconfig b/.arcconfig index 517c73216a..fa1f72054f 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,7 +1,7 @@ { "project_id" : "phabricator", "conduit_uri" : "https://secure.phabricator.com/api/", - "lint_engine" : "PhutilLintEngine", + "lint_engine" : "PhabricatorLintEngine", "unit_engine" : "PhutilUnitTestEngine", "copyright_holder" : "Facebook, Inc.", "remote_hooks_installed" : true, diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..4ee5d11f66 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "externals/javelin"] + path = externals/javelin + url = git://github.com/epriestley/javelin.git diff --git a/externals/javelin b/externals/javelin new file mode 160000 index 0000000000..099162f62b --- /dev/null +++ b/externals/javelin @@ -0,0 +1 @@ +Subproject commit 099162f62bbb154c2d906cbe3c7ab3e58e89be7e diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php index b852e8ee09..ec010b9074 100755 --- a/scripts/celerity_mapper.php +++ b/scripts/celerity_mapper.php @@ -2,6 +2,32 @@ array( + 'javelin-util', + 'javelin-install', + 'javelin-event', + 'javelin-stratcom', + 'javelin-behavior', + 'javelin-request', + 'javelin-vector', + 'javelin-dom', + 'javelin-json', + 'javelin-uri', + ), + 'typeahead.pkg.js' => array( + 'javelin-typeahead', + 'javelin-typeahead-normalizer', + 'javelin-typeahead-source', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-tokenizer', + 'javelin-behavior-aphront-basic-tokenizer', + ), + 'workflow.pkg.js' => array( + 'javelin-mask', + 'javelin-workflow', + 'javelin-behavior-workflow', + ), 'core.pkg.css' => array( 'phabricator-core-css', 'phabricator-core-buttons-css', @@ -14,6 +40,7 @@ $package_spec = array( 'aphront-crumbs-view-css', 'aphront-tokenizer-control-css', 'aphront-typeahead-control-css', + 'aphront-list-filter-view-css', 'phabricator-directory-css', @@ -63,6 +90,7 @@ $files = id(new FileFinder($root)) ->withType('f') ->withSuffix('js') ->withSuffix('css') + ->withFollowSymlinks(true) ->setGenerateChecksums(true) ->find(); @@ -101,9 +129,10 @@ foreach ($file_map as $path => $info) { $provides = array_filter($provides); $requires = array_filter($requires); - if (count($provides) !== 1) { + if (count($provides) > 1) { + // NOTE: Documentation-only JS is permitted to @provide no targets. throw new Exception( - "File {$path} must @provide exactly one Celerity target."); + "File {$path} must @provide at most one Celerity target."); } $provides = reset($provides); diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 2f08f070de..f54b4aae6e 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -235,14 +235,19 @@ celerity_register_resource_map(array( ), 'herald-rule-editor' => array( - 'uri' => '/res/ec8e2110/rsrc/js/application/herald/HeraldRuleEditor.js', + 'uri' => '/res/f3122b0a/rsrc/js/application/herald/HeraldRuleEditor.js', 'type' => 'js', 'requires' => array( 0 => 'multirow-row-manager', - 1 => 'javelin-lib-dev', - 2 => 'javelin-typeahead-dev', - 3 => 'path-typeahead', + 1 => 'javelin-install', + 2 => 'javelin-typeahead', + 3 => 'javelin-util', + 4 => 'javelin-dom', + 5 => 'javelin-tokenizer', + 6 => 'javelin-typeahead-preloaded-source', + 7 => 'javelin-stratcom', + 8 => 'javelin-json', ), 'disk' => '/rsrc/js/application/herald/HeraldRuleEditor.js', ), @@ -255,248 +260,463 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/herald/herald-test.css', ), - 'javelin-behavior-aphront-basic-tokenizer' => + 'javelin-behavior' => array( - 'uri' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', + 'uri' => '/res/dc576a49/rsrc/js/javelin/lib/behavior.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + ), + 'disk' => '/rsrc/js/javelin/lib/behavior.js', + ), + 'javelin-behavior-aphront-basic-tokenizer' => + array( + 'uri' => '/res/d48d2732/rsrc/js/application/core/behavior-tokenizer.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-typeahead', + 2 => 'javelin-tokenizer', + 3 => 'javelin-typeahead-preloaded-source', + 4 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', ), - 'javelin-behavior-dark-console' => + 0 => array( - 'uri' => '/res/020b0265/rsrc/js/application/core/behavior-dark-console.js', + 'uri' => '/res/e3d992aa/rsrc/js/javelin/docs/Base.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', + ), + 'disk' => '/rsrc/js/javelin/docs/Base.js', + ), + 'javelin-behavior-dark-console' => + array( + 'uri' => '/res/447bd50a/rsrc/js/application/core/behavior-dark-console.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-util', + 3 => 'javelin-dom', + 4 => 'javelin-request', ), 'disk' => '/rsrc/js/application/core/behavior-dark-console.js', ), 'javelin-behavior-differential-add-reviewers' => array( - 'uri' => '/res/330154e4/rsrc/js/application/differential/behavior-add-reviewers.js', + 'uri' => '/res/fa2f29c4/rsrc/js/application/differential/behavior-add-reviewers.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-tokenizer', + 3 => 'javelin-typeahead', + 4 => 'javelin-typeahead-preloaded-source', ), 'disk' => '/rsrc/js/application/differential/behavior-add-reviewers.js', ), 'javelin-behavior-differential-diff-radios' => array( - 'uri' => '/res/fdeb3823/rsrc/js/application/differential/behavior-diff-radios.js', + 'uri' => '/res/d3365dba/rsrc/js/application/differential/behavior-diff-radios.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-diff-radios.js', ), 'javelin-behavior-differential-edit-inline-comments' => array( - 'uri' => '/res/6a6f38e6/rsrc/js/application/differential/behavior-edit-inline-comments.js', + 'uri' => '/res/f18ee6ae/rsrc/js/application/differential/behavior-edit-inline-comments.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', + 3 => 'javelin-workflow', + 4 => 'javelin-vector', ), 'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js', ), 'javelin-behavior-differential-feedback-preview' => array( - 'uri' => '/res/8695d8b8/rsrc/js/application/differential/behavior-comment-preview.js', + 'uri' => '/res/139c85b8/rsrc/js/application/differential/behavior-comment-preview.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', + 3 => 'javelin-request', + 4 => 'javelin-util', ), 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', ), 'javelin-behavior-differential-populate' => array( - 'uri' => '/res/a13dcd7e/rsrc/js/application/differential/behavior-populate.js', + 'uri' => '/res/d4d4fd9d/rsrc/js/application/differential/behavior-populate.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-request', + 2 => 'javelin-util', + 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-populate.js', ), 'javelin-behavior-differential-show-all-comments' => array( - 'uri' => '/res/2a3592b8/rsrc/js/application/differential/behavior-show-all-comments.js', + 'uri' => '/res/4d34a1e7/rsrc/js/application/differential/behavior-show-all-comments.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js', ), 'javelin-behavior-differential-show-more' => array( - 'uri' => '/res/ea998002/rsrc/js/application/differential/behavior-show-more.js', + 'uri' => '/res/6af8c5bb/rsrc/js/application/differential/behavior-show-more.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-request', + 3 => 'javelin-util', + 4 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/differential/behavior-show-more.js', ), 'javelin-behavior-diffusion-jump-to' => array( - 'uri' => '/res/4f3f6cdc/rsrc/js/application/diffusion/behavior-jump-to.js', + 'uri' => '/res/2a8ca30b/rsrc/js/application/diffusion/behavior-jump-to.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-util', + 2 => 'javelin-vector', + 3 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/diffusion/behavior-jump-to.js', ), 'javelin-behavior-diffusion-pull-lastmodified' => array( - 'uri' => '/res/6a5e7374/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', + 'uri' => '/res/f3e3f3a6/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-util', + 3 => 'javelin-request', ), 'disk' => '/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', ), 'javelin-behavior-error-log' => array( - 'uri' => '/res/c57a323f/rsrc/js/application/core/behavior-error-log.js', + 'uri' => '/res/ad4e82d4/rsrc/js/application/core/behavior-error-log.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-dom', ), 'disk' => '/rsrc/js/application/core/behavior-error-log.js', ), 'javelin-behavior-herald-rule-editor' => array( - 'uri' => '/res/48108130/rsrc/js/application/herald/herald-rule-editor.js', + 'uri' => '/res/f18bcd5e/rsrc/js/application/herald/herald-rule-editor.js', 'type' => 'js', 'requires' => array( 0 => 'herald-rule-editor', - 1 => 'javelin-lib-dev', + 1 => 'javelin-behavior', ), 'disk' => '/rsrc/js/application/herald/herald-rule-editor.js', ), 'javelin-behavior-maniphest-transaction-controls' => array( - 'uri' => '/res/fc6a8722/rsrc/js/application/maniphest/behavior-transaction-controls.js', + 'uri' => '/res/f2eae88a/rsrc/js/application/maniphest/behavior-transaction-controls.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-tokenizer', + 3 => 'javelin-typeahead', + 4 => 'javelin-typeahead-preloaded-source', ), 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js', ), 'javelin-behavior-owners-path-editor' => array( - 'uri' => '/res/7568aa22/rsrc/js/application/owners/owners-path-editor.js', + 'uri' => '/res/b379a0d4/rsrc/js/application/owners/owners-path-editor.js', 'type' => 'js', 'requires' => array( 0 => 'owners-path-editor', - 1 => 'javelin-lib-dev', + 1 => 'javelin-behavior', ), 'disk' => '/rsrc/js/application/owners/owners-path-editor.js', ), 'javelin-behavior-phabricator-object-selector' => array( - 'uri' => '/res/85d0769b/rsrc/js/application/core/behavior-object-selector.js', + 'uri' => '/res/e899a55e/rsrc/js/application/core/behavior-object-selector.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-request', + 3 => 'javelin-util', + 4 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/core/behavior-object-selector.js', ), 'javelin-behavior-workflow' => array( - 'uri' => '/res/15446e7e/rsrc/js/application/core/behavior-workflow.js', + 'uri' => '/res/b5bc59cb/rsrc/js/application/core/behavior-workflow.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-behavior', + 1 => 'javelin-stratcom', + 2 => 'javelin-workflow', ), 'disk' => '/rsrc/js/application/core/behavior-workflow.js', ), - 'javelin-init-prod' => + 'javelin-dom' => array( - 'uri' => '/res/1267c868/rsrc/js/javelin/init.min.js', + 'uri' => '/res/21c1392d/rsrc/js/javelin/lib/DOM.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-vector', + 3 => 'javelin-stratcom', ), - 'disk' => '/rsrc/js/javelin/init.min.js', + 'disk' => '/rsrc/js/javelin/lib/DOM.js', ), - 'javelin-lib-dev' => + 'javelin-event' => array( - 'uri' => '/res/a0e7a5e9/rsrc/js/javelin/javelin.dev.js', + 'uri' => '/res/807b95e6/rsrc/js/javelin/core/Event.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', ), - 'disk' => '/rsrc/js/javelin/javelin.dev.js', + 'disk' => '/rsrc/js/javelin/core/Event.js', ), - 'javelin-lib-prod' => + 'javelin-install' => array( - 'uri' => '/res/2f2b3b2e/rsrc/js/javelin/javelin.min.js', + 'uri' => '/res/c11fe5b3/rsrc/js/javelin/core/install.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-util', ), - 'disk' => '/rsrc/js/javelin/javelin.min.js', + 'disk' => '/rsrc/js/javelin/core/install.js', + ), + 'javelin-json' => + array( + 'uri' => '/res/62c8cc8d/rsrc/js/javelin/lib/JSON.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + ), + 'disk' => '/rsrc/js/javelin/lib/JSON.js', ), 'javelin-magical-init' => array( - 'uri' => '/res/76614f84/rsrc/js/javelin/init.dev.js', + 'uri' => '/res/6d53e259/rsrc/js/javelin/core/init.js', 'type' => 'js', 'requires' => array( ), - 'disk' => '/rsrc/js/javelin/init.dev.js', + 'disk' => '/rsrc/js/javelin/core/init.js', ), - 'javelin-typeahead-dev' => + 'javelin-mask' => array( - 'uri' => '/res/6de6ae59/rsrc/js/javelin/typeahead.dev.js', + 'uri' => '/res/ba2f665a/rsrc/js/javelin/lib/Mask.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', + 1 => 'javelin-vector', + 2 => 'javelin-dom', ), - 'disk' => '/rsrc/js/javelin/typeahead.dev.js', + 'disk' => '/rsrc/js/javelin/lib/Mask.js', ), - 'javelin-typeahead-prod' => + 'javelin-request' => array( - 'uri' => '/res/69d5fad1/rsrc/js/javelin/typeahead.min.js', + 'uri' => '/res/3947083d/rsrc/js/javelin/lib/Request.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', + 1 => 'javelin-stratcom', + 2 => 'javelin-util', + 3 => 'javelin-behavior', ), - 'disk' => '/rsrc/js/javelin/typeahead.min.js', + 'disk' => '/rsrc/js/javelin/lib/Request.js', ), - 'javelin-workflow-dev' => + 'javelin-stratcom' => array( - 'uri' => '/res/c6b17f93/rsrc/js/javelin/workflow.dev.js', + 'uri' => '/res/3421b115/rsrc/js/javelin/core/Stratcom.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-install', + 1 => 'javelin-event', + 2 => 'javelin-util', + 3 => 'javelin-magical-init', ), - 'disk' => '/rsrc/js/javelin/workflow.dev.js', + 'disk' => '/rsrc/js/javelin/core/Stratcom.js', ), - 'javelin-workflow-prod' => + 'javelin-tokenizer' => array( - 'uri' => '/res/b758e0a0/rsrc/js/javelin/workflow.min.js', + 'uri' => '/res/74fe92c6/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js', 'type' => 'js', 'requires' => array( + 0 => 'javelin-typeahead', + 1 => 'javelin-dom', + 2 => 'javelin-util', + 3 => 'javelin-stratcom', + 4 => 'javelin-vector', + 5 => 'javelin-install', + 6 => 'javelin-typeahead-preloaded-source', ), - 'disk' => '/rsrc/js/javelin/workflow.min.js', + 'disk' => '/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js', + ), + 'javelin-typeahead' => + array( + 'uri' => '/res/5a701345/rsrc/js/javelin/lib/control/typeahead/Typeahead.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + 2 => 'javelin-vector', + 3 => 'javelin-util', + ), + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/Typeahead.js', + ), + 'javelin-typeahead-normalizer' => + array( + 'uri' => '/res/8d49e2de/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + ), + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', + ), + 'javelin-typeahead-ondemand-source' => + array( + 'uri' => '/res/00b46be8/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-stratcom', + 3 => 'javelin-request', + 4 => 'javelin-typeahead-source', + ), + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', + ), + 'javelin-typeahead-preloaded-source' => + array( + 'uri' => '/res/aefaf410/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-stratcom', + 3 => 'javelin-request', + 4 => 'javelin-typeahead-source', + ), + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', + ), + 'javelin-typeahead-source' => + array( + 'uri' => '/res/b1184e7d/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-typeahead-normalizer', + ), + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js', + ), + 'javelin-uri' => + array( + 'uri' => '/res/03448af9/rsrc/js/javelin/lib/URI.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + ), + 'disk' => '/rsrc/js/javelin/lib/URI.js', + ), + 'javelin-util' => + array( + 'uri' => '/res/031851eb/rsrc/js/javelin/core/util.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-magical-init', + ), + 'disk' => '/rsrc/js/javelin/core/util.js', + ), + 'javelin-vector' => + array( + 'uri' => '/res/184e9d71/rsrc/js/javelin/lib/Vector.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-event', + ), + 'disk' => '/rsrc/js/javelin/lib/Vector.js', + ), + 'javelin-workflow' => + array( + 'uri' => '/res/24389bc8/rsrc/js/javelin/lib/Workflow.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-stratcom', + 1 => 'javelin-request', + 2 => 'javelin-dom', + 3 => 'javelin-vector', + 4 => 'javelin-install', + 5 => 'javelin-util', + 6 => 'javelin-mask', + 7 => 'javelin-uri', + ), + 'disk' => '/rsrc/js/javelin/lib/Workflow.js', ), 'mainphest-task-detail-css' => array( @@ -527,24 +747,28 @@ celerity_register_resource_map(array( ), 'multirow-row-manager' => array( - 'uri' => '/res/330d076b/rsrc/js/application/core/MultirowRowManager.js', + 'uri' => '/res/cae26c67/rsrc/js/application/core/MultirowRowManager.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', + 0 => 'javelin-install', + 1 => 'javelin-stratcom', + 2 => 'javelin-dom', + 3 => 'javelin-util', ), 'disk' => '/rsrc/js/application/core/MultirowRowManager.js', ), 'owners-path-editor' => array( - 'uri' => '/res/b01c1ca9/rsrc/js/application/owners/OwnersPathEditor.js', + 'uri' => '/res/003f3d3f/rsrc/js/application/owners/OwnersPathEditor.js', 'type' => 'js', 'requires' => array( 0 => 'multirow-row-manager', - 1 => 'javelin-lib-dev', - 2 => 'javelin-typeahead-dev', - 3 => 'path-typeahead', + 1 => 'javelin-install', + 2 => 'path-typeahead', + 3 => 'javelin-dom', + 4 => 'javelin-util', ), 'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js', ), @@ -559,12 +783,16 @@ celerity_register_resource_map(array( ), 'path-typeahead' => array( - 'uri' => '/res/42fb76c3/rsrc/js/application/herald/PathTypeahead.js', + 'uri' => '/res/594d2576/rsrc/js/application/herald/PathTypeahead.js', 'type' => 'js', 'requires' => array( - 0 => 'javelin-lib-dev', - 1 => 'javelin-typeahead-dev', + 0 => 'javelin-install', + 1 => 'javelin-typeahead', + 2 => 'javelin-dom', + 3 => 'javelin-request', + 4 => 'javelin-typeahead-ondemand-source', + 5 => 'javelin-util', ), 'disk' => '/rsrc/js/application/herald/PathTypeahead.js', ), @@ -653,30 +881,7 @@ celerity_register_resource_map(array( ), array ( 'packages' => array ( - '4270730a' => - array ( - 'name' => 'core.pkg.css', - 'symbols' => - array ( - 0 => 'phabricator-core-css', - 1 => 'phabricator-core-buttons-css', - 2 => 'phabricator-standard-page-view', - 3 => 'aphront-dialog-view-css', - 4 => 'aphront-form-view-css', - 5 => 'aphront-panel-view-css', - 6 => 'aphront-side-nav-view-css', - 7 => 'aphront-table-view-css', - 8 => 'aphront-crumbs-view-css', - 9 => 'aphront-tokenizer-control-css', - 10 => 'aphront-typeahead-control-css', - 11 => 'phabricator-directory-css', - 12 => 'phabricator-remarkup-css', - 13 => 'syntax-highlighting-css', - ), - 'uri' => '/res/pkg/4270730a/core.pkg.css', - 'type' => 'css', - ), - '6c786373' => + '3b698834' => array ( 'name' => 'differential.pkg.js', 'symbols' => @@ -687,7 +892,19 @@ celerity_register_resource_map(array( 3 => 'javelin-behavior-differential-show-more', 4 => 'javelin-behavior-differential-diff-radios', ), - 'uri' => '/res/pkg/6c786373/differential.pkg.js', + 'uri' => '/res/pkg/3b698834/differential.pkg.js', + 'type' => 'js', + ), + '71a78877' => + array ( + 'name' => 'workflow.pkg.js', + 'symbols' => + array ( + 0 => 'javelin-mask', + 1 => 'javelin-workflow', + 2 => 'javelin-behavior-workflow', + ), + 'uri' => '/res/pkg/71a78877/workflow.pkg.js', 'type' => 'js', ), '8e4ef51b' => @@ -707,6 +924,46 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/8e4ef51b/differential.pkg.css', 'type' => 'css', ), + 'a44a7841' => + array ( + 'name' => 'typeahead.pkg.js', + 'symbols' => + array ( + 0 => 'javelin-typeahead', + 1 => 'javelin-typeahead-normalizer', + 2 => 'javelin-typeahead-source', + 3 => 'javelin-typeahead-preloaded-source', + 4 => 'javelin-typeahead-ondemand-source', + 5 => 'javelin-tokenizer', + 6 => 'javelin-behavior-aphront-basic-tokenizer', + ), + 'uri' => '/res/pkg/a44a7841/typeahead.pkg.js', + 'type' => 'js', + ), + 'c4276ad7' => + array ( + 'name' => 'core.pkg.css', + 'symbols' => + array ( + 0 => 'phabricator-core-css', + 1 => 'phabricator-core-buttons-css', + 2 => 'phabricator-standard-page-view', + 3 => 'aphront-dialog-view-css', + 4 => 'aphront-form-view-css', + 5 => 'aphront-panel-view-css', + 6 => 'aphront-side-nav-view-css', + 7 => 'aphront-table-view-css', + 8 => 'aphront-crumbs-view-css', + 9 => 'aphront-tokenizer-control-css', + 10 => 'aphront-typeahead-control-css', + 11 => 'aphront-list-filter-view-css', + 12 => 'phabricator-directory-css', + 13 => 'phabricator-remarkup-css', + 14 => 'syntax-highlighting-css', + ), + 'uri' => '/res/pkg/c4276ad7/core.pkg.css', + 'type' => 'css', + ), 'eadf6ec3' => array ( 'name' => 'diffusion.pkg.css', @@ -717,17 +974,37 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/eadf6ec3/diffusion.pkg.css', 'type' => 'css', ), + 'fc6ed8bc' => + array ( + 'name' => 'javelin.pkg.js', + 'symbols' => + array ( + 0 => 'javelin-util', + 1 => 'javelin-install', + 2 => 'javelin-event', + 3 => 'javelin-stratcom', + 4 => 'javelin-behavior', + 5 => 'javelin-request', + 6 => 'javelin-vector', + 7 => 'javelin-dom', + 8 => 'javelin-json', + 9 => 'javelin-uri', + ), + 'uri' => '/res/pkg/fc6ed8bc/javelin.pkg.js', + 'type' => 'js', + ), ), 'reverse' => array ( - 'aphront-crumbs-view-css' => '4270730a', - 'aphront-dialog-view-css' => '4270730a', - 'aphront-form-view-css' => '4270730a', - 'aphront-panel-view-css' => '4270730a', - 'aphront-side-nav-view-css' => '4270730a', - 'aphront-table-view-css' => '4270730a', - 'aphront-tokenizer-control-css' => '4270730a', - 'aphront-typeahead-control-css' => '4270730a', + 'aphront-crumbs-view-css' => 'c4276ad7', + 'aphront-dialog-view-css' => 'c4276ad7', + 'aphront-form-view-css' => 'c4276ad7', + 'aphront-list-filter-view-css' => 'c4276ad7', + 'aphront-panel-view-css' => 'c4276ad7', + 'aphront-side-nav-view-css' => 'c4276ad7', + 'aphront-table-view-css' => 'c4276ad7', + 'aphront-tokenizer-control-css' => 'c4276ad7', + 'aphront-typeahead-control-css' => 'c4276ad7', 'differential-changeset-view-css' => '8e4ef51b', 'differential-core-view-css' => '8e4ef51b', 'differential-revision-add-comment-css' => '8e4ef51b', @@ -737,16 +1014,36 @@ celerity_register_resource_map(array( 'differential-revision-history-css' => '8e4ef51b', 'differential-table-of-contents-css' => '8e4ef51b', 'diffusion-commit-view-css' => 'eadf6ec3', - 'javelin-behavior-differential-diff-radios' => '6c786373', - 'javelin-behavior-differential-edit-inline-comments' => '6c786373', - 'javelin-behavior-differential-feedback-preview' => '6c786373', - 'javelin-behavior-differential-populate' => '6c786373', - 'javelin-behavior-differential-show-more' => '6c786373', - 'phabricator-core-buttons-css' => '4270730a', - 'phabricator-core-css' => '4270730a', - 'phabricator-directory-css' => '4270730a', - 'phabricator-remarkup-css' => '4270730a', - 'phabricator-standard-page-view' => '4270730a', - 'syntax-highlighting-css' => '4270730a', + 'javelin-behavior' => 'fc6ed8bc', + 'javelin-behavior-aphront-basic-tokenizer' => 'a44a7841', + 'javelin-behavior-differential-diff-radios' => '3b698834', + 'javelin-behavior-differential-edit-inline-comments' => '3b698834', + 'javelin-behavior-differential-feedback-preview' => '3b698834', + 'javelin-behavior-differential-populate' => '3b698834', + 'javelin-behavior-differential-show-more' => '3b698834', + 'javelin-behavior-workflow' => '71a78877', + 'javelin-dom' => 'fc6ed8bc', + 'javelin-event' => 'fc6ed8bc', + 'javelin-install' => 'fc6ed8bc', + 'javelin-json' => 'fc6ed8bc', + 'javelin-mask' => '71a78877', + 'javelin-request' => 'fc6ed8bc', + 'javelin-stratcom' => 'fc6ed8bc', + 'javelin-tokenizer' => 'a44a7841', + 'javelin-typeahead' => 'a44a7841', + 'javelin-typeahead-normalizer' => 'a44a7841', + 'javelin-typeahead-ondemand-source' => 'a44a7841', + 'javelin-typeahead-preloaded-source' => 'a44a7841', + 'javelin-typeahead-source' => 'a44a7841', + 'javelin-uri' => 'fc6ed8bc', + 'javelin-util' => 'fc6ed8bc', + 'javelin-vector' => 'fc6ed8bc', + 'javelin-workflow' => '71a78877', + 'phabricator-core-buttons-css' => 'c4276ad7', + 'phabricator-core-css' => 'c4276ad7', + 'phabricator-directory-css' => 'c4276ad7', + 'phabricator-remarkup-css' => 'c4276ad7', + 'phabricator-standard-page-view' => 'c4276ad7', + 'syntax-highlighting-css' => 'c4276ad7', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7e16878725..d5a4a209ba 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -318,6 +318,8 @@ phutil_register_library_map(array( 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', + 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin', + 'PhabricatorLintEngine' => 'infrastructure/lint/engine', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', @@ -731,6 +733,8 @@ phutil_register_library_map(array( 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', + 'PhabricatorJavelinLinter' => 'ArcanistLinter', + 'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', diff --git a/src/infrastructure/celerity/map/CelerityResourceMap.php b/src/infrastructure/celerity/map/CelerityResourceMap.php index a6ca2f22d3..8e02226fb3 100644 --- a/src/infrastructure/celerity/map/CelerityResourceMap.php +++ b/src/infrastructure/celerity/map/CelerityResourceMap.php @@ -21,6 +21,7 @@ final class CelerityResourceMap { private static $instance; private $resourceMap; private $packageMap; + private $reverseMap; public static function getInstance() { if (empty(self::$instance)) { @@ -107,6 +108,21 @@ final class CelerityResourceMap { return $paths; } + public function lookupSymbolInformation($symbol) { + return idx($this->resourceMap, $symbol); + } + + public function lookupFileInformation($path) { + if (empty($this->reverseMap)) { + $this->reverseMap = array(); + foreach ($this->resourceMap as $symbol => $data) { + $data['provides'] = $symbol; + $this->reverseMap[$data['disk']] = $data; + } + } + return idx($this->reverseMap, $path); + } + } function celerity_register_resource_map(array $map, array $package_map) { diff --git a/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php b/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php index c8956afaaa..ee8af7dd16 100644 --- a/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php +++ b/src/infrastructure/celerity/response/CelerityStaticResourceResponse.php @@ -25,6 +25,7 @@ final class CelerityStaticResourceResponse { private $metadata = array(); private $metadataBlock = 0; private $behaviors = array(); + private $hasRendered = array(); public function __construct() { if (isset($_REQUEST['__metablock__'])) { @@ -64,14 +65,36 @@ final class CelerityStaticResourceResponse { return $this; } + public function renderSingleResource($symbol) { + $map = CelerityResourceMap::getInstance(); + $resolved = $map->resolveResources(array($symbol)); + $packaged = $map->packageResources($resolved); + return $this->renderPackagedResources($packaged); + } + public function renderResourcesOfType($type) { $this->resolveResources(); - $output = array(); + + $resources = array(); foreach ($this->packaged as $resource) { if ($resource['type'] == $type) { - $output[] = $this->renderResource($resource); + $resources[] = $resource; } } + + return $this->renderPackagedResources($resources); + } + + private function renderPackagedResources(array $resources) { + $output = array(); + foreach ($resources as $resource) { + if (isset($this->hasRendered[$resource['uri']])) { + continue; + } + $this->hasRendered[$resource['uri']] = true; + + $output[] = $this->renderResource($resource); + } return implode("\n", $output); } diff --git a/src/infrastructure/lint/engine/PhabricatorLintEngine.php b/src/infrastructure/lint/engine/PhabricatorLintEngine.php new file mode 100644 index 0000000000..3ecb4cc01d --- /dev/null +++ b/src/infrastructure/lint/engine/PhabricatorLintEngine.php @@ -0,0 +1,45 @@ +getPaths(); + + foreach ($paths as $key => $path) { + if (!$this->pathExists($path)) { + unset($paths[$key]); + } + } + + $javelin_linter = new PhabricatorJavelinLinter(); + $linters[] = $javelin_linter; + + foreach ($paths as $path) { + if (preg_match('/\.js$/', $path)) { + $javelin_linter->addPath($path); + $javelin_linter->addData($path, $this->loadData($path)); + } + } + + return $linters; + } + +} diff --git a/src/infrastructure/lint/engine/__init__.php b/src/infrastructure/lint/engine/__init__.php new file mode 100644 index 0000000000..b5b399e79c --- /dev/null +++ b/src/infrastructure/lint/engine/__init__.php @@ -0,0 +1,14 @@ +newSymbolsFuture($path); + $futures[$path] = $future; + } + + foreach (Futures($futures)->limit(8) as $path => $future) { + $this->symbols[$path] = $future->resolvex(); + } + } + + public function getLinterName() { + return 'JAVELIN'; + } + + public function getLintSeverityMap() { + return array(); + } + + public function getLintNameMap() { + return array( + self::LINT_PRIVATE_ACCESS => 'Private Method/Member Access', + self::LINT_MISSING_DEPENDENCY => 'Missing Javelin Dependency', + self::LINT_UNNECESSARY_DEPENDENCY => 'Unnecessary Javelin Dependency', + self::LINT_UNKNOWN_DEPENDENCY => 'Unknown Javelin Dependency', + ); + } + + public function lintPath($path) { + + list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); + foreach ($uses as $symbol => $line) { + $parts = explode('.', $symbol); + foreach ($parts as $part) { + if ($part[0] == '_' && $part[1] != '_') { + $base = implode('.', array_slice($parts, 0, 2)); + if (!array_key_exists($base, $installs)) { + $this->raiseLintAtLine( + $line, + 0, + self::LINT_PRIVATE_ACCESS, + "This file accesses private symbol '{$symbol}' across file ". + "boundaries. You may only access private members and methods ". + "from the file where they are defined."); + } + break; + } + } + } + + if ($this->getEngine()->getCommitHookMode()) { + // Don't do the dependency checks in commit-hook mode because we won't + // have an available working copy. + return; + } + + $external_classes = array(); + foreach ($uses as $symbol => $line) { + $parts = explode('.', $symbol); + $class = implode('.', array_slice($parts, 0, 2)); + if (!array_key_exists($class, $external_classes) && + !array_key_exists($class, $installs)) { + $external_classes[$class] = $line; + } + } + + $celerity = CelerityResourceMap::getInstance(); + + $info = $celerity->lookupFileInformation(substr($path, strlen('webroot'))); + + $need = $external_classes; + + $requires = $info['requires']; + + foreach ($requires as $key => $name) { + $symbol_info = $celerity->lookupSymbolInformation($name); + if (!$symbol_info) { + $this->raiseLintAtLine( + 0, + 0, + self::LINT_UNKNOWN_DEPENDENCY, + "This file @requires component '{$name}', but it does not ". + "exist. You may need to rebuild the Celerity map."); + unset($requires[$key]); + continue; + } + + $symbol_path = 'webroot'.$symbol_info['disk']; + list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath( + $symbol_path); + if (array_intersect_key($req_install, $external_classes)) { + $need = array_diff_key($need, $req_install); + unset($requires[$key]); + } + } + + foreach ($need as $class => $line) { + $this->raiseLintAtLine( + $line, + 0, + self::LINT_MISSING_DEPENDENCY, + "This file uses '{$class}' but does not @requires the component ". + "which installs it. You may need to rebuild the Celerity map."); + } + + foreach ($requires as $component) { + $this->raiseLintAtLine( + $line, + 0, + self::LINT_UNNECESSARY_DEPENDENCY, + "This file @requires component '{$component}' but does not use ". + "anything it provides."); + } + } + + private function loadSymbols($path) { + if (empty($this->symbols[$path])) { + $this->symbols[$path] = $this->newSymbolsFuture($path)->resolvex(); + } + return $this->symbols[$path]; + } + + private function newSymbolsFuture($path) { + $root = dirname(phutil_get_library_root('phabricator')); + + $support = $root.'/externals/javelin/support'; + $javelinsymbols = $support.'/javelinsymbols/javelinsymbols'; + + $future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path)); + $future->write($this->getData($path)); + return $future; + } + + private function getUsedAndInstalledSymbolsForPath($path) { + list($symbols) = $this->loadSymbols($path); + + $symbols = explode("\n", trim($symbols)); + + $uses = array(); + $installs = array(); + foreach ($symbols as $line) { + $matches = null; + if (!preg_match('/^([?+])([^:]*):(\d+)$/', $line, $matches)) { + throw new Exception( + "Received malformed output from `javelinsymbols`."); + } + $type = $matches[1]; + $symbol = $matches[2]; + $line = $matches[3]; + + switch ($type) { + case '?': + $uses[$symbol] = $line; + break; + case '+': + $installs['JX.'.$symbol] = $line; + break; + } + } + + $contents = $this->getData($path); + + $matches = null; + $count = preg_match_all( + '/@javelin-installs\W+(\S+)/', + $contents, + $matches, + PREG_PATTERN_ORDER); + + if ($count) { + foreach ($matches[1] as $symbol) { + $installs[$symbol] = 0; + } + } + + return array($uses, $installs); + } + +} diff --git a/src/infrastructure/lint/linter/javelin/__init__.php b/src/infrastructure/lint/linter/javelin/__init__.php new file mode 100644 index 0000000000..07bbd32124 --- /dev/null +++ b/src/infrastructure/lint/linter/javelin/__init__.php @@ -0,0 +1,18 @@ + true, 'name' => $name, 'class' => 'jx-tokenizer-input', - 'sigil' => 'tokenizer', + 'sigil' => 'tokenizer-input', 'style' => 'width: 0px;', 'disabled' => 'disabled', 'type' => 'text', diff --git a/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php b/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php index 267deec0d3..623dedaa94 100644 --- a/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php +++ b/src/view/form/control/tokenizer/AphrontFormTokenizerControl.php @@ -42,23 +42,9 @@ class AphrontFormTokenizerControl extends AphrontFormControl { } protected function renderInput() { - require_celerity_resource('javelin-typeahead-dev'); - $name = $this->getName(); $values = nonempty($this->getValue(), array()); - $input = javelin_render_tag( - 'input', - array( - 'mustcapture' => true, - 'name' => $name, - 'class' => 'jx-tokenizer-input', - 'sigil' => 'tokenizer', - 'style' => 'width: 0px;', - 'disabled' => 'disabled', - 'type' => 'text', - )); - if ($this->getID()) { $id = $this->getID(); } else { diff --git a/src/view/form/control/tokenizer/__init__.php b/src/view/form/control/tokenizer/__init__.php index 38136a7f0c..9decd685ab 100644 --- a/src/view/form/control/tokenizer/__init__.php +++ b/src/view/form/control/tokenizer/__init__.php @@ -8,7 +8,6 @@ phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/javelin/api'); -phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/control/tokenizer'); phutil_require_module('phabricator', 'view/form/control/base'); diff --git a/src/view/page/standard/PhabricatorStandardPageView.php b/src/view/page/standard/PhabricatorStandardPageView.php index 2227d3b477..eb9965ed03 100644 --- a/src/view/page/standard/PhabricatorStandardPageView.php +++ b/src/view/page/standard/PhabricatorStandardPageView.php @@ -89,9 +89,6 @@ class PhabricatorStandardPageView extends AphrontPageView { require_celerity_resource('phabricator-core-buttons-css'); require_celerity_resource('phabricator-standard-page-view'); - require_celerity_resource('javelin-lib-dev'); - require_celerity_resource('javelin-workflow-dev'); - Javelin::initBehavior('workflow', array()); if ($console) { @@ -118,8 +115,7 @@ class PhabricatorStandardPageView extends AphrontPageView { 'window.__DEV__=1;'. ''. $response->renderResourcesOfType('css'). - ''; + $response->renderSingleResource('javelin-magical-init'); $request = $this->getRequest(); if ($request) { diff --git a/webroot/rsrc/js/application/core/MultirowRowManager.js b/webroot/rsrc/js/application/core/MultirowRowManager.js index 45d1bf368a..319ff353c2 100644 --- a/webroot/rsrc/js/application/core/MultirowRowManager.js +++ b/webroot/rsrc/js/application/core/MultirowRowManager.js @@ -1,5 +1,8 @@ /** - * @requires javelin-lib-dev + * @requires javelin-install + * javelin-stratcom + * javelin-dom + * javelin-util * @provides multirow-row-manager * @javelin */ diff --git a/webroot/rsrc/js/application/core/behavior-dark-console.js b/webroot/rsrc/js/application/core/behavior-dark-console.js index 810d0cf6fd..083fa418d0 100644 --- a/webroot/rsrc/js/application/core/behavior-dark-console.js +++ b/webroot/rsrc/js/application/core/behavior-dark-console.js @@ -1,5 +1,10 @@ /** * @provides javelin-behavior-dark-console + * @requires javelin-behavior + * javelin-stratcom + * javelin-util + * javelin-dom + * javelin-request */ JX.behavior('dark-console', function(config) { diff --git a/webroot/rsrc/js/application/core/behavior-error-log.js b/webroot/rsrc/js/application/core/behavior-error-log.js index 5f2d64ed1c..e2989b08be 100644 --- a/webroot/rsrc/js/application/core/behavior-error-log.js +++ b/webroot/rsrc/js/application/core/behavior-error-log.js @@ -1,6 +1,6 @@ /** * @provides javelin-behavior-error-log - * @requires javelin-lib-dev + * @requires javelin-dom */ var current_details = null; diff --git a/webroot/rsrc/js/application/core/behavior-object-selector.js b/webroot/rsrc/js/application/core/behavior-object-selector.js index 722c2a5b92..0d99049900 100644 --- a/webroot/rsrc/js/application/core/behavior-object-selector.js +++ b/webroot/rsrc/js/application/core/behavior-object-selector.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-phabricator-object-selector - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-dom + * javelin-request + * javelin-util + * javelin-stratcom */ JX.behavior('phabricator-object-selector', function(config) { diff --git a/webroot/rsrc/js/application/core/behavior-tokenizer.js b/webroot/rsrc/js/application/core/behavior-tokenizer.js index 825d44ed9c..fcaa7174d3 100644 --- a/webroot/rsrc/js/application/core/behavior-tokenizer.js +++ b/webroot/rsrc/js/application/core/behavior-tokenizer.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-aphront-basic-tokenizer - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-typeahead + * javelin-tokenizer + * javelin-typeahead-preloaded-source + * javelin-dom */ JX.behavior('aphront-basic-tokenizer', function(config) { @@ -10,7 +14,7 @@ JX.behavior('aphront-basic-tokenizer', function(config) { var typeahead = new JX.Typeahead( root, - JX.DOM.find(root, 'input', 'tokenizer')); + JX.DOM.find(root, 'input', 'tokenizer-input')); typeahead.setDatasource(datasource); var tokenizer = new JX.Tokenizer(root); diff --git a/webroot/rsrc/js/application/core/behavior-workflow.js b/webroot/rsrc/js/application/core/behavior-workflow.js index de6fe9b9df..5b4d8155c3 100644 --- a/webroot/rsrc/js/application/core/behavior-workflow.js +++ b/webroot/rsrc/js/application/core/behavior-workflow.js @@ -1,6 +1,8 @@ /** * @provides javelin-behavior-workflow - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-stratcom + * javelin-workflow */ JX.behavior('workflow', function() { diff --git a/webroot/rsrc/js/application/differential/behavior-add-reviewers.js b/webroot/rsrc/js/application/differential/behavior-add-reviewers.js index 43d245bf6a..a90f9924d3 100644 --- a/webroot/rsrc/js/application/differential/behavior-add-reviewers.js +++ b/webroot/rsrc/js/application/differential/behavior-add-reviewers.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-differential-add-reviewers - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-dom + * javelin-tokenizer + * javelin-typeahead + * javelin-typeahead-preloaded-source */ JX.behavior('differential-add-reviewers', function(config) { diff --git a/webroot/rsrc/js/application/differential/behavior-comment-preview.js b/webroot/rsrc/js/application/differential/behavior-comment-preview.js index 3644bf6727..3bb48b0da5 100644 --- a/webroot/rsrc/js/application/differential/behavior-comment-preview.js +++ b/webroot/rsrc/js/application/differential/behavior-comment-preview.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-differential-feedback-preview - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * javelin-request + * javelin-util */ JX.behavior('differential-feedback-preview', function(config) { @@ -28,7 +32,7 @@ JX.behavior('differential-feedback-preview', function(config) { cval = content.value; request = new JX.Request(config.uri, function(r) { - preview && JX.DOM.setContent(preview, JX.HTML(r)); + preview && JX.DOM.setContent(preview, JX.$H(r)); min = new Date().getTime() + 500; defer && defer.stop(); defer = JX.defer(check, 500); @@ -51,7 +55,7 @@ JX.behavior('differential-feedback-preview', function(config) { function refreshInlinePreview() { new JX.Request(config.inlineuri, function(r) { - JX.DOM.setContent(JX.$(config.inline), JX.HTML(r)); + JX.DOM.setContent(JX.$(config.inline), JX.$H(r)); }) .setTimeout(5000) .send(); diff --git a/webroot/rsrc/js/application/differential/behavior-diff-radios.js b/webroot/rsrc/js/application/differential/behavior-diff-radios.js index 29ad8bbd09..a91ae7906b 100644 --- a/webroot/rsrc/js/application/differential/behavior-diff-radios.js +++ b/webroot/rsrc/js/application/differential/behavior-diff-radios.js @@ -1,6 +1,8 @@ /** * @provides javelin-behavior-differential-diff-radios - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom */ JX.behavior('differential-diff-radios', function(config) { diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index bac90bde47..ecbd876d5e 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-differential-edit-inline-comments - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * javelin-workflow + * javelin-vector */ JX.behavior('differential-edit-inline-comments', function(config) { @@ -27,9 +31,9 @@ JX.behavior('differential-edit-inline-comments', function(config) { } var code = target.nextSibling; - var pos = JX.$V(top).add(1 + JX.$V.getDim(target).x, 0); - var dim = JX.$V.getDim(code).add(-4, 0); - dim.y = (JX.$V(bot).y - pos.y) + JX.$V.getDim(bot).y; + var pos = JX.$V(top).add(1 + JX.Vector.getDim(target).x, 0); + var dim = JX.Vector.getDim(code).add(-4, 0); + dim.y = (JX.$V(bot).y - pos.y) + JX.Vector.getDim(bot).y; pos.setPos(reticle); dim.setDim(reticle); @@ -48,7 +52,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { } function drawInlineComment(table, anchor, r) { - copyRows(table, JX.$N('div', JX.HTML(r.markup)), anchor); + copyRows(table, JX.$N('div', JX.$H(r.markup)), anchor); finishSelect(); } @@ -245,7 +249,7 @@ JX.behavior('differential-edit-inline-comments', function(config) { var data = { op: e.getNode('differential-inline-edit') ? 'edit' : 'delete', id: e.getNodeData('differential-inline-comment').id, - on_right: e.getNodeData('differential-inline-comment').on_right, + on_right: e.getNodeData('differential-inline-comment').on_right }; new JX.Workflow(config.uri, data) .setHandler(function(r) { diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index 0712252c56..604f914d91 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -1,12 +1,15 @@ /** * @provides javelin-behavior-differential-populate - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-request + * javelin-util + * javelin-dom */ JX.behavior('differential-populate', function(config) { function onresponse(target, response) { - JX.DOM.replace(JX.$(target), JX.HTML(response)); + JX.DOM.replace(JX.$(target), JX.$H(response)); } for (var k in config.registry) { diff --git a/webroot/rsrc/js/application/differential/behavior-show-all-comments.js b/webroot/rsrc/js/application/differential/behavior-show-all-comments.js index ce26650d49..13512c5431 100644 --- a/webroot/rsrc/js/application/differential/behavior-show-all-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-show-all-comments.js @@ -1,6 +1,8 @@ /** * @provides javelin-behavior-differential-show-all-comments - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom */ JX.behavior('differential-show-all-comments', function(config) { @@ -11,7 +13,7 @@ JX.behavior('differential-show-all-comments', function(config) { function(e) { JX.DOM.setContent( e.getNode('differential-all-comments-container'), - JX.HTML(config.markup)); + JX.$H(config.markup)); e.kill(); }); diff --git a/webroot/rsrc/js/application/differential/behavior-show-more.js b/webroot/rsrc/js/application/differential/behavior-show-more.js index 18c53575cc..a2e0534319 100644 --- a/webroot/rsrc/js/application/differential/behavior-show-more.js +++ b/webroot/rsrc/js/application/differential/behavior-show-more.js @@ -1,12 +1,16 @@ /** * @provides javelin-behavior-differential-show-more - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-dom + * javelin-request + * javelin-util + * javelin-stratcom */ JX.behavior('differential-show-more', function(config) { function onresponse(origin, response) { - var div = JX.$N('div', {}, JX.HTML(response)); + var div = JX.$N('div', {}, JX.$H(response)); var anchor = origin.getNode('context-target'); var root = anchor.parentNode; copyRows(root, div, anchor); diff --git a/webroot/rsrc/js/application/diffusion/behavior-jump-to.js b/webroot/rsrc/js/application/diffusion/behavior-jump-to.js index c6ddfc7340..46604a4d66 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-jump-to.js +++ b/webroot/rsrc/js/application/diffusion/behavior-jump-to.js @@ -1,6 +1,9 @@ /** * @provides javelin-behavior-diffusion-jump-to - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-util + * javelin-vector + * javelin-dom */ JX.behavior('diffusion-jump-to', function(config) { diff --git a/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js b/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js index ae351eb680..01e8238718 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js +++ b/webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js @@ -1,6 +1,9 @@ /** * @provides javelin-behavior-diffusion-pull-lastmodified - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-dom + * javelin-util + * javelin-request */ JX.behavior('diffusion-pull-lastmodified', function(config) { @@ -9,7 +12,7 @@ JX.behavior('diffusion-pull-lastmodified', function(config) { new JX.Request(uri, JX.bind(config[uri], function(r) { for (var k in r) { if (this[k]) { - JX.DOM.setContent(JX.$(this[k]), JX.HTML(r[k])); + JX.DOM.setContent(JX.$(this[k]), JX.$H(r[k])); } } })).send(); diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js index bfd9789a2e..5f14d0748e 100644 --- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js +++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js @@ -1,8 +1,13 @@ /** * @requires multirow-row-manager - * javelin-lib-dev - * javelin-typeahead-dev - * path-typeahead + * javelin-install + * javelin-typeahead + * javelin-util + * javelin-dom + * javelin-tokenizer + * javelin-typeahead-preloaded-source + * javelin-stratcom + * javelin-json * @provides herald-rule-editor * @javelin */ @@ -257,7 +262,7 @@ JX.install('HeraldRuleEditor', { _newTokenizer : function(type) { var template = JX.$N( 'div', - new JX.HTML(this._config.template.markup)); + JX.$H(this._config.template.markup)); template = template.firstChild; template.id = ''; diff --git a/webroot/rsrc/js/application/herald/PathTypeahead.js b/webroot/rsrc/js/application/herald/PathTypeahead.js index 86e7ab0641..7ef3a9b094 100644 --- a/webroot/rsrc/js/application/herald/PathTypeahead.js +++ b/webroot/rsrc/js/application/herald/PathTypeahead.js @@ -1,6 +1,10 @@ /** - * @requires javelin-lib-dev - * javelin-typeahead-dev + * @requires javelin-install + * javelin-typeahead + * javelin-dom + * javelin-request + * javelin-typeahead-ondemand-source + * javelin-util * @provides path-typeahead * @javelin */ diff --git a/webroot/rsrc/js/application/herald/herald-rule-editor.js b/webroot/rsrc/js/application/herald/herald-rule-editor.js index c3b76191df..ff4f17ecc0 100644 --- a/webroot/rsrc/js/application/herald/herald-rule-editor.js +++ b/webroot/rsrc/js/application/herald/herald-rule-editor.js @@ -1,6 +1,6 @@ /** * @requires herald-rule-editor - * javelin-lib-dev + * javelin-behavior * @provides javelin-behavior-herald-rule-editor * @javelin */ diff --git a/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js b/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js index f384c2aadb..6a53bdb240 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js +++ b/webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js @@ -1,6 +1,10 @@ /** * @provides javelin-behavior-maniphest-transaction-controls - * @requires javelin-lib-dev + * @requires javelin-behavior + * javelin-dom + * javelin-tokenizer + * javelin-typeahead + * javelin-typeahead-preloaded-source */ JX.behavior('maniphest-transaction-controls', function(config) { @@ -51,31 +55,5 @@ JX.behavior('maniphest-transaction-controls', function(config) { } }); -/* - - var root = JX.$(config.tokenizer); - var datasource = new JX.TypeaheadPreloadedSource(config.src); - - var typeahead = new JX.Typeahead(root); - typeahead.setDatasource(datasource); - - var tokenizer = new JX.Tokenizer(root); - tokenizer.setTypeahead(typeahead); - tokenizer.start(); - - JX.DOM.listen( - JX.$(config.select), - 'change', - null, - function(e) { - if (JX.$(config.select).value == 'add_reviewers') { - JX.DOM.show(JX.$(config.row)); - tokenizer.refresh(); - } else { - JX.DOM.hide(JX.$(config.row)); - } - }); - -*/ }); diff --git a/webroot/rsrc/js/application/owners/OwnersPathEditor.js b/webroot/rsrc/js/application/owners/OwnersPathEditor.js index a3c0db7066..8db02cb016 100644 --- a/webroot/rsrc/js/application/owners/OwnersPathEditor.js +++ b/webroot/rsrc/js/application/owners/OwnersPathEditor.js @@ -1,8 +1,9 @@ /** * @requires multirow-row-manager - * javelin-lib-dev - * javelin-typeahead-dev + * javelin-install * path-typeahead + * javelin-dom + * javelin-util * @provides owners-path-editor * @javelin */ @@ -105,7 +106,7 @@ JX.install('OwnersPathEditor', { var repo_cell = JX.$N('td', {}, repo_select); var typeahead_cell = JX.$N( 'td', - JX.HTML(this._inputTemplate)); + JX.$H(this._inputTemplate)); // Text input for path. var path_input = JX.DOM.find(typeahead_cell, 'input'); @@ -113,7 +114,7 @@ JX.install('OwnersPathEditor', { path_input, { value : path_ref.path || "", - name : "path[" + this._count + "]", + name : "path[" + this._count + "]" }); // The Typeahead requires a display div called hardpoint. diff --git a/webroot/rsrc/js/application/owners/owners-path-editor.js b/webroot/rsrc/js/application/owners/owners-path-editor.js index 55f1f7ee38..1f3d9d7b03 100644 --- a/webroot/rsrc/js/application/owners/owners-path-editor.js +++ b/webroot/rsrc/js/application/owners/owners-path-editor.js @@ -1,6 +1,6 @@ /** * @requires owners-path-editor - * javelin-lib-dev + * javelin-behavior * @provides javelin-behavior-owners-path-editor * @javelin */ diff --git a/webroot/rsrc/js/javelin b/webroot/rsrc/js/javelin new file mode 120000 index 0000000000..5702caab9c --- /dev/null +++ b/webroot/rsrc/js/javelin @@ -0,0 +1 @@ +../../../externals/javelin/src/ \ No newline at end of file diff --git a/webroot/rsrc/js/javelin/init.dev.js b/webroot/rsrc/js/javelin/init.dev.js deleted file mode 100644 index 402f8e3b5f..0000000000 --- a/webroot/rsrc/js/javelin/init.dev.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Javelin core; installs Javelin and Stratcom event delegation. - * - * @provides javelin-magical-init - * @nopackage - * - * @javelin-installs JX.__rawEventQueue - * @javelin-installs JX.__simulate - * @javelin-installs JX.enableDispatch - * @javelin-installs JX.onload - * - * @javelin - */ -(function() { - - - if (window.JX) { - return; - } - - window.JX = {}; - window['__DEV__'] = window['__DEV__'] || 0; - - var loaded = false; - var onload = []; - var master_event_queue = []; - var root = document.documentElement; - var has_add_event_listener = !!root.addEventListener; - - JX.__rawEventQueue = function(what) { - master_event_queue.push(what); - - // Evade static analysis - JX.Stratcom - var Stratcom = JX['Stratcom']; - if (Stratcom && Stratcom.ready) { - // Empty the queue now so that exceptions don't cause us to repeatedly - // try to handle events. - var local_queue = master_event_queue; - master_event_queue = []; - for (var ii = 0; ii < local_queue.length; ++ii) { - var evt = local_queue[ii]; - - // Sometimes IE gives us events which throw when ".type" is accessed; - // just ignore them since we can't meaningfully dispatch them. TODO: - // figure out where these are coming from. - try { var test = evt.type; } catch (x) { continue; } - - if (!loaded && evt.type == 'domready') { - document.body && (document.body.id = null); - loaded = true; - - for (var ii = 0; ii < onload.length; ii++) { - onload[ii](); - } - - } - - Stratcom.dispatch(evt); - } - } else { - var target = what.srcElement || what.target; - if (target && - (what.type in {click: 1, submit: 1}) && - target.getAttribute && - target.getAttribute('data-mustcapture') === '1') { - what.returnValue = false; - what.preventDefault && what.preventDefault(); - document.body.id = 'event_capture'; - - // For versions of IE that use attachEvent, the event object is somehow - // stored globally by reference, and all the references we push to the - // master_event_queue will always refer to the most recent event. We - // work around this by popping the useless global event off the queue, - // and pushing a clone of the event that was just fired using the IE's - // proprietary createEventObject function. - // see: http://msdn.microsoft.com/en-us/library/ms536390(v=vs.85).aspx - if (!add_event_listener && document.createEventObject) { - master_event_queue.pop(); - master_event_queue.push(document.createEventObject(what)); - } - - return false; - } - } - } - - JX.enableDispatch = function(target, type) { - if (target.addEventListener) { - target.addEventListener(type, JX.__rawEventQueue, true); - } else if (target.attachEvent) { - target.attachEvent('on' + type, JX.__rawEventQueue); - } - }; - - var document_events = [ - 'click', - 'change', - 'keypress', - 'mousedown', - 'mouseover', - 'mouseout', - 'mouseup', - 'keydown', - 'drop', - 'dragenter', - 'dragleave', - 'dragover' - ]; - - // Simulate focus and blur in old versions of IE using focusin and focusout - // TODO: Document the gigantic IE mess here with focus/blur. - // TODO: beforeactivate/beforedeactivate? - // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html - if (!has_add_event_listener) { - document_events.push('focusin', 'focusout'); - } - - // Opera is multilol: it propagates focus / blur odd, and submit differently - if (window.opera) { - document_events.push('focus', 'blur'); - } else { - document_events.push('submit'); - } - - for (var ii = 0; ii < document_events.length; ++ii) { - JX.enableDispatch(root, document_events[ii]); - } - - // In particular, we're interested in capturing window focus/blur here so - // long polls can abort when the window is not focused. - var window_events = [ - ('onpagehide' in window) ? 'pagehide' : 'unload', - 'resize', - 'focus', - 'blur' - ]; - - for (var ii = 0; ii < window_events.length; ++ii) { - JX.enableDispatch(window, window_events[ii]); - } - - JX.__simulate = function(node, event) { - if (!has_add_event_listener) { - var e = {target: node, type: event}; - JX.__rawEventQueue(e); - if (e.returnValue === false) { - return false; - } - } - }; - - if (has_add_event_listener) { - document.addEventListener('DOMContentLoaded', function() { - JX.__rawEventQueue({type: 'domready'}); - }, true); - } else { - var ready = - "if (this.readyState == 'complete') {" + - "JX.__rawEventQueue({type: 'domready'});" + - "}"; - - document.write( - '<\/sc' + 'ript\>'); - } - - JX.onload = function(func) { - if (loaded) { - func(); - } else { - onload.push(func); - } - } - - -})(); diff --git a/webroot/rsrc/js/javelin/init.min.js b/webroot/rsrc/js/javelin/init.min.js deleted file mode 100644 index 0ba48fc10f..0000000000 --- a/webroot/rsrc/js/javelin/init.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/** @provides javelin-init-prod */ -(function(){if(window.JX)return;window.JX={};window.__DEV__=window.__DEV__||0;var d=false;var f=[];var e=[];var h=document.documentElement;var b=!!h.addEventListener;JX.__rawEventQueue=function(o){e.push(o);var j=JX.Stratcom;if(j&&j.ready){var m=e;e=[];for(var l=0;l<\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})(); diff --git a/webroot/rsrc/js/javelin/javelin.dev.js b/webroot/rsrc/js/javelin/javelin.dev.js deleted file mode 100644 index 7ecd05e469..0000000000 --- a/webroot/rsrc/js/javelin/javelin.dev.js +++ /dev/null @@ -1,2955 +0,0 @@ -/** @provides javelin-lib-dev */ - -/** - * Javelin utility functions. - * - * @provides javelin-util - * - * @javelin-installs JX.$A - * @javelin-installs JX.$AX - * @javelin-installs JX.copy - * @javelin-installs JX.bind - * @javelin-installs JX.bag - * @javelin-installs JX.keys - * @javelin-installs JX.defer - * @javelin-installs JX.go - * @javelin-installs JX.log - * - * @javelin - */ - - -/** - * Convert an array-like object (usually ##arguments##) into a real Array. An - * "array-like object" is something with a ##length## property and numerical - * keys. The most common use for this is to let you call Array functions on the - * magical ##arguments## object. - * - * JX.$A(arguments).slice(1); - * - * @param obj Array, or array-like object. - * @return Array Actual array. - */ -JX.$A = function(mysterious_arraylike_object) { - // NOTE: This avoids the Array.slice() trick because some bizarre COM object - // I dug up somewhere was freaking out when I tried to do it and it made me - // very upset, so do not replace this with Array.slice() cleverness. - var r = []; - for (var ii = 0; ii < mysterious_arraylike_object.length; ii++) { - r.push(mysterious_arraylike_object[ii]); - } - return r; -}; - - -/** - * Cast a value into an array, by wrapping scalars into singletons. If the - * argument is an array, it is returned unmodified. If it is a scalar, an array - * with a single element is returned. For example: - * - * JX.$AX([3]); // Returns [3]. - * JX.$AX(3); // Returns [3]. - * - * Note that this function uses an "instanceof Array" check so you may need to - * convert array-like objects (such as ##arguments## and Array instances from - * iframes) into real arrays with @{JX.$A()}. - * - * @param wild Scalar or Array. - * @return Array If the argument was a scalar, an Array with the argument as - * its only element. Otherwise, the original Array. - * - */ -JX.$AX = function(maybe_scalar) { - return (maybe_scalar instanceof Array) ? maybe_scalar : [maybe_scalar]; -}; - - -/** - * Copy properties from one object to another. Note: does not copy the - * ##toString## property or anything else which isn't enumerable or is somehow - * magic or just doesn't work. But it's usually what you want. If properties - * already exist, they are overwritten. - * - * var cat = { - * ears: 'clean', - * paws: 'clean', - * nose: 'DIRTY OH NOES' - * }; - * var more = { - * nose: 'clean', - * tail: 'clean' - * }; - * - * JX.copy(cat, more); - * - * // cat is now: - * // { - * // ears: 'clean', - * // paws: 'clean', - * // nose: 'clean', - * // tail: 'clean' - * // } - * - * @param obj Destination object, which properties should be copied to. - * @param obj Source object, which properties should be copied from. - * @return obj Destination object. - */ -JX.copy = function(copy_dst, copy_src) { - for (var k in copy_src) { - copy_dst[k] = copy_src[k]; - } - return copy_dst; -}; - - -/** - * Create a function which invokes another function with a bound context and - * arguments (i.e., partial function application) when called; king of all - * functions. - * - * Bind performs context binding (letting you select what the value of ##this## - * will be when a function is invoked) and partial function application (letting - * you create some function which calls another one with bound arguments). - * - * = Context Binding = - * - * Normally, when you call ##obj.method()##, the magic ##this## object will be - * the ##obj## you invoked the method from. This can be undesirable when you - * need to pass a callback to another function. For instance: - * - * COUNTEREXAMPLE - * var dog = new JX.Dog(); - * dog.barkNow(); // Makes the dog bark. - * - * JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work! - * - * This doesn't work because ##this## is ##window## when the function is - * later invoked; @{JX.Stratcom.listen()} does not know about the context - * object ##dog##. The solution is to pass a function with a bound context - * object: - * - * var dog = new JX.Dog(); - * var bound_function = JX.bind(dog, dog.barkNow); - * - * JX.Stratcom.listen('click', 'bark', bound_function); - * - * ##bound_function## is a function with ##dog## bound as ##this##; ##this## - * will always be ##dog## when the function is called, no matter what - * property chain it is invoked from. - * - * You can also pass ##null## as the context argument to implicitly bind - * ##window##. - * - * = Partial Function Application = - * - * @{JX.bind()} also performs partial function application, which allows you - * to bind one or more arguments to a function. For instance, if we have a - * simple function which adds two numbers: - * - * function add(a, b) { return a + b; } - * add(3, 4); // 7 - * - * Suppose we want a new function, like this: - * - * function add3(b) { return 3 + b; } - * add3(4); // 7 - * - * Instead of doing this, we can define ##add3()## in terms of ##add()## by - * binding the value ##3## to the ##a## argument: - * - * var add3_bound = JX.bind(null, add, 3); - * add3_bound(4); // 7 - * - * Zero or more arguments may be bound in this way. This is particularly useful - * when using closures in a loop: - * - * COUNTEREXAMPLE - * for (var ii = 0; ii < button_list.length; ii++) { - * button_list[ii].onclick = function() { - * JX.log('You clicked button number '+ii+'!'); // Fails! - * }; - * } - * - * This doesn't work; all the buttons report the highest number when clicked. - * This is because the local ##ii## is captured by the closure. Instead, bind - * the current value of ##ii##: - * - * var func = function(button_num) { - * JX.log('You clicked button number '+button_num+'!'); - * } - * for (var ii = 0; ii < button_list.length; ii++) { - * button_list[ii].onclick = JX.bind(null, func, ii); - * } - * - * @param obj|null Context object to bind as ##this##. - * @param function Function to bind context and arguments to. - * @param ... Zero or more arguments to bind. - * @return function New function which invokes the original function with - * bound context and arguments when called. - */ -JX.bind = function(context, func, more) { - - if (__DEV__) { - if (typeof func != 'function') { - throw new Error( - 'JX.bind(context, , ...): '+ - 'Attempting to bind something that is not a function.'); - } - } - - var bound = JX.$A(arguments).slice(2); - return function() { - return func.apply(context || window, bound.concat(JX.$A(arguments))); - } -}; - - -/** - * "Bag of holding"; function that does nothing. Primarily, it's used as a - * placeholder when you want something to be callable but don't want it to - * actually have an effect. - * - * @return void - */ -JX.bag = function() { - // \o\ \o/ /o/ woo dance party -}; - - -/** - * Convert an object's keys into a list. For example: - * - * JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars'] - * - * @param obj Object to retrieve keys from. - * @return list List of keys. - */ -JX.keys = function(obj) { - var r = []; - for (var k in obj) { - r.push(k); - } - return r; -}; - - -/** - * Defer a function for later execution, similar to ##setTimeout()##. Returns - * an object with a ##stop()## method, which cancels the deferred call. - * - * var ref = JX.defer(yell, 3000); // Yell in 3 seconds. - * // ... - * ref.stop(); // Cancel the yell. - * - * @param function Function to invoke after the timeout. - * @param int? Timeout, in milliseconds. If this value is omitted, the - * function will be invoked once control returns to the browser - * event loop, as with ##setTimeout(func, 0)##. - * @return obj An object with a ##stop()## method, which cancels function - * execution. - */ -JX.defer = function(func, timeout) { - var t = setTimeout(func, timeout || 0); - return {stop : function() { clearTimeout(t); }} -}; - - -/** - * Redirect the browser to another page by changing the window location. - * - * @param string Optional URI to redirect the browser to. If no URI is - * provided, the current page will be reloaded. - * @return void - */ -JX.go = function(uri) { - - // Foil static analysis, etc. Strictly speaking, JX.go() doesn't really need - // to be in javelin-utils so we could do this properly at some point. - JX['Stratcom'] && JX['Stratcom'].invoke('go', null, {uri: uri}); - - (uri && (window.location = uri)) || window.location.reload(true); -}; - - -if (__DEV__) { - if (!window.console || !window.console.log) { - if (window.opera && window.opera.postError) { - window.console = {log: function(m) { window.opera.postError(m); }}; - } else { - window.console = {log: function(m) { }}; - } - } - - /** - * Print a message to the browser debugging console (like Firebug). This - * method exists only in ##__DEV__##. - * - * @param string Message to print to the browser debugging console. - * @return void - */ - JX.log = function(message) { - window.console.log(message); - } - - window.alert = (function(native_alert) { - var recent_alerts = []; - var in_alert = false; - return function(msg) { - if (in_alert) { - JX.log( - 'alert(...): '+ - 'discarded reentrant alert.'); - return; - } - in_alert = true; - recent_alerts.push(new Date().getTime()); - - if (recent_alerts.length > 3) { - recent_alerts.splice(0, recent_alerts.length - 3); - } - - if (recent_alerts.length >= 3 && - (recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) { - if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) { - window.alert = JX.bag; - } - } else { - // Note that we can't .apply() the IE6 version of this "function". - native_alert(msg); - } - in_alert = false; - } - })(window.alert); - -} -/** - * @requires javelin-util - * @provides javelin-install - * @javelin-installs JX.install - * @javelin - */ - -/** - * Install a class into the Javelin ("JX") namespace. The first argument is the - * name of the class you want to install, and the second is a map of these - * attributes (all of which are optional): - * - * - ##construct## //(function)// Class constructor. If you don't provide one, - * one will be created for you (but it will be very boring). - * - ##extend## //(string)// The name of another JX-namespaced class to extend - * via prototypal inheritance. - * - ##members## //(map)// A map of instance methods and properties. - * - ##statics## //(map)// A map of static methods and properties. - * - ##initialize## //(function)// A function which will be run once, after - * this class has been installed. - * - ##properties## //(map)// A map of properties that should have instance - * getters and setters automatically generated for them. The key is the - * property name and the value is its default value. For instance, if you - * provide the property "size", the installed class will have the methods - * "getSize()" and "setSize()". It will **NOT** have a property ".size" - * and no guarantees are made about where install is actually chosing to - * store the data. The motivation here is to let you cheaply define a - * stable interface and refine it later as necessary. - * - ##events## //(list)// List of event types this class is capable of - * emitting. - * - * For example: - * - * JX.install('Dog', { - * construct : function(name) { - * this.setName(name); - * }, - * members : { - * bark : function() { - * // ... - * } - * }, - * properites : { - * name : null, - * } - * }); - * - * This creates a new ##Dog## class in the ##JX## namespace: - * - * var d = new JX.Dog(); - * d.bark(); - * - * Javelin classes are normal Javascript functions and generally behave in - * the expected way. Some properties and methods are automatically added to - * all classes: - * - * - ##instance.__id__## Globally unique identifier attached to each instance. - * - ##instance.__super__## Reference to the parent class constructor, if one - * exists. Allows use of ##this.__super__.apply(this, ...)## to call the - * superclass's constructor. - * - ##instance.__parent__## Reference to the parent class prototype, if one - * exists. Allows use of ##this.__parent__.someMethod.apply(this, ...)## - * to call the superclass's methods. - * - ##prototype.__class__## Reference to the class constructor. - * - ##constructor.__path__## List of path tokens used emit events. It is - * probably never useful to access this directly. - * - ##constructor.__readable__## //DEV ONLY!// Readable class name. You could - * plausibly use this when constructing error messages. - * - ##constructor.__events__## //DEV ONLY!// List of events supported by - * this class. - * - ##constructor.listen()## Listen to all instances of this class. See - * @{JX.Base}. - * - ##instance.listen()## Listen to one instance of this class. See - * @{JX.Base}. - * - ##instance.invoke()## Invoke an event from an instance. See @{JX.Base}. - * - * - * @param string Name of the class to install. It will appear in the JX - * "namespace" (e.g., JX.Pancake). - * @param map Map of properties, see method documentation. - * @return void - * - * @author epriestley - */ -JX.install = function(new_name, new_junk) { - - if (typeof JX.install._nextObjectID == 'undefined') { - JX.install._nextObjectID = 0; - } - - // If we've already installed this, something is up. - if (new_name in JX) { - if (__DEV__) { - throw new Error( - 'JX.install("' + new_name + '", ...): ' + - 'trying to reinstall something that has already been installed.'); - } - return; - } - - // Since we may end up loading things out of order (e.g., Dog extends Animal - // but we load Dog first) we need to keep a list of things that we've been - // asked to install but haven't yet been able to install around. - if (!JX.install._queue) { - JX.install._queue = []; - } - JX.install._queue.push([new_name, new_junk]); - do { - var junk; - var name = null; - for (var ii = 0; ii < JX.install._queue.length; ++ii) { - junk = JX.install._queue[ii][1]; - if (junk.extend && !JX[junk.extend]) { - // We need to extend something that we haven't been able to install - // yet, so just keep this in queue. - continue; - } - - // Install time! First, get this out of the queue. - name = JX.install._queue[ii][0]; - JX.install._queue.splice(ii, 1); - --ii; - - if (__DEV__) { - var valid = { - construct : 1, - statics : 1, - members : 1, - extend : 1, - initialize: 1, - properties : 1, - events : 1, - canCallAsFunction : 1 - }; - for (var k in junk) { - if (!(k in valid)) { - throw new Error( - 'JX.install("' + name + '", {"' + k + '": ...}): ' + - 'trying to install unknown property `' + k + '`.'); - } - } - if (junk.constructor !== {}.constructor) { - throw new Error( - 'JX.install("' + name + '", {"constructor": ...}): ' + - 'property `constructor` should be called `construct`.'); - } - } - - // First, build the constructor. If construct is just a function, this - // won't change its behavior (unless you have provided a really awesome - // function, in which case it will correctly punish you for your attempt - // at creativity). - JX[name] = (function(name, junk) { - var result = function() { - this.__id__ = '__obj__' + (++JX.install._nextObjectID); - this.__super__ = JX[junk.extend] || JX.bag; - this.__parent__ = JX[name].prototype; - if (JX[name].__prototyping__) { - return; - } - return (junk.construct || JX.bag).apply(this, arguments); - // TODO: Allow mixins to initialize here? - // TODO: Also, build mixins? - }; - - if (__DEV__) { - if (!junk.canCallAsFunction) { - var inner = result; - result = function() { - if (this === window || this === JX) { - throw new Error("<" + JX[name].__readable__ + ">: " + - "Tried to construct an instance " + - "without the 'new' operator. Either use " + - "'new' or set 'canCallAsFunction' where you " + - "install the class."); - } - return inner.apply(this, arguments); - }; - } - } - return result; - })(name, junk); - - // Copy in all the static methods and properties. - JX.copy(JX[name], junk.statics); - - if (__DEV__) { - JX[name].__readable__ = 'JX.' + name; - } - - JX[name].__prototyping__ = 0; - - var proto; - if (junk.extend) { - JX[junk.extend].__prototyping__++; - proto = JX[name].prototype = new JX[junk.extend](); - JX[junk.extend].__prototyping__--; - } else { - proto = JX[name].prototype = {}; - } - - proto.__class__ = JX[name]; - - // Build getters and setters from the `prop' map. - for (var k in (junk.properties || {})) { - var base = k.charAt(0).toUpperCase()+k.substr(1); - var prop = '__auto__' + k; - proto[prop] = junk.properties[k]; - proto['set' + base] = (function(prop) { - return function(v) { - this[prop] = v; - return this; - } - })(prop); - - proto['get' + base] = (function(prop) { - return function() { - return this[prop]; - } - })(prop); - } - - if (__DEV__) { - - // Check for aliasing in default values of members. If we don't do this, - // you can run into a problem like this: - // - // JX.install('List', { members : { stuff : [] }}); - // - // var i_love = new JX.List(); - // var i_hate = new JX.List(); - // - // i_love.stuff.push('Psyduck'); // I love psyduck! - // JX.log(i_hate.stuff); // Show stuff I hate. - // - // This logs ["Psyduck"] because the push operation modifies - // JX.List.prototype.stuff, which is what both i_love.stuff and - // i_hate.stuff resolve to. To avoid this, set the default value to - // null (or any other scalar) and do "this.stuff = [];" in the - // constructor. - - for (var member_name in junk.members) { - if (junk.extend && member_name[0] == '_') { - throw new Error( - 'JX.install("' + name + '", ...): ' + - 'installed member "' + member_name + '" must not be named with ' + - 'a leading underscore because it is in a subclass. Variables ' + - 'are analyzed and crushed one file at a time, and crushed ' + - 'member variables in subclasses alias crushed member variables ' + - 'in superclasses. Remove the underscore, refactor the class so ' + - 'it does not extend anything, or fix the minifier to be ' + - 'capable of safely crushing subclasses.'); - } - var member_value = junk.members[member_name]; - if (typeof member_value == 'object' && member_value !== null) { - throw new Error( - 'JX.install("' + name + '", ...): ' + - 'installed member "' + member_name + '" is not a scalar or ' + - 'function. Prototypal inheritance in Javascript aliases object ' + - 'references across instances so all instances are initialized ' + - 'to point at the exact same object. This is almost certainly ' + - 'not what you intended. Make this member static to share it ' + - 'across instances, or initialize it in the constructor to ' + - 'prevent reference aliasing and give each instance its own ' + - 'copy of the value.'); - } - } - } - - - // This execution order intentionally allows you to override methods - // generated from the "properties" initializer. - JX.copy(proto, junk.members); - - - // Build this ridiculous event model thing. Basically, this defines - // two instance methods, invoke() and listen(), and one static method, - // listen(). If you listen to an instance you get events for that - // instance; if you listen to a class you get events for all instances - // of that class (including instances of classes which extend it). - // - // This is rigged up through Stratcom. Each class has a path component - // like "class:Dog", and each object has a path component like - // "obj:23". When you invoke on an object, it emits an event with - // a path that includes its class, all parent classes, and its object - // ID. - // - // Calling listen() on an instance listens for just the object ID. - // Calling listen() on a class listens for that class's name. This - // has the effect of working properly, but installing them is pretty - // messy. - if (junk.events && junk.events.length) { - - var parent = JX[junk.extend] || {}; - - // If we're in dev, we build up a list of valid events (for this - // class or some parent class) and then check them whenever we try - // to listen or invoke. - if (__DEV__) { - var valid_events = parent.__events__ || {}; - for (var ii = 0; ii < junk.events.length; ++ii) { - valid_events[junk.events[ii]] = true; - } - JX[name].__events__ = valid_events; - } - - // Build the class name chain. - JX[name].__name__ = 'class:' + name; - var ancestry = parent.__path__ || []; - JX[name].__path__ = ancestry.concat([JX[name].__name__]); - - proto.invoke = function(type) { - if (__DEV__) { - if (!(type in this.__class__.__events__)) { - throw new Error( - this.__class__.__readable__ + '.invoke("' + type + '", ...): ' + - 'invalid event type. Valid event types are: ' + - JX.keys(this.__class__.__events__).join(', ') + '.'); - } - } - // Here and below, this nonstandard access notation is used to mask - // these callsites from the static analyzer. JX.Stratcom is always - // available by the time we hit these execution points. - return JX['Stratcom'].invoke( - 'obj:' + type, - this.__class__.__path__.concat([this.__id__]), - {args : JX.$A(arguments).slice(1)}); - }; - - proto.listen = function(type, callback) { - if (__DEV__) { - if (!(type in this.__class__.__events__)) { - throw new Error( - this.__class__.__readable__ + '.listen("' + type + '", ...): ' + - 'invalid event type. Valid event types are: ' + - JX.keys(this.__class__.__events__).join(', ') + '.'); - } - } - return JX['Stratcom'].listen( - 'obj:' + type, - this.__id__, - JX.bind(this, function(e) { - return callback.apply(this, e.getData().args); - })); - }; - - JX[name].listen = function(type, callback) { - if (__DEV__) { - if (!(type in this.__events__)) { - throw new Error( - this.__readable__ + '.listen("' + type + '", ...): ' + - 'invalid event type. Valid event types are: ' + - JX.keys(this.__events__).join(', ') + '.'); - } - } - return JX['Stratcom'].listen( - 'obj:' + type, - this.__name__, - JX.bind(this, function(e) { - return callback.apply(this, e.getData().args); - })); - }; - } else if (__DEV__) { - var error_message = - 'class does not define any events. Pass an "events" property to ' + - 'JX.install() to define events.'; - JX[name].listen = JX[name].listen || function() { - throw new Error( - this.__readable__ + '.listen(...): ' + - error_message); - }; - JX[name].invoke = JX[name].invoke || function() { - throw new Error( - this.__readable__ + '.invoke(...): ' + - error_message); - }; - proto.listen = proto.listen || function() { - throw new Error( - this.__class__.__readable__ + '.listen(...): ' + - error_message); - }; - proto.invoke = proto.invoke || function() { - throw new Error( - this.__class__.__readable__ + '.invoke(...): ' + - error_message); - }; - } - - // Finally, run the init function if it was provided. - (junk.initialize || JX.bag)(); - } - - // In effect, this exits the loop as soon as we didn't make any progress - // installing things, which means we've installed everything we have the - // dependencies for. - } while (name); -} -/** - * @requires javelin-install - * @provides javelin-event - * @javelin - */ - -/** - * A generic event, routed by @{JX.Stratcom}. All events within Javelin are - * represented by a {@JX.Event}, regardless of whether they originate from - * a native DOM event (like a mouse click) or are custom application events. - * - * Events have a propagation model similar to native Javascript events, in that - * they can be stopped with stop() (which stops them from continuing to - * propagate to other handlers) or prevented with prevent() (which prevents them - * from taking their default action, like following a link). You can do both at - * once with kill(). - * - * @author epriestley - * @task stop Stopping Event Behaviors - * @task info Getting Event Information - */ -JX.install('Event', { - members : { - - /** - * Stop an event from continuing to propagate. No other handler will - * receive this event, but its default behavior will still occur. See - * ""Using Events"" for more information on the distinction between - * 'stopping' and 'preventing' an event. See also prevent() (which prevents - * an event but does not stop it) and kill() (which stops and prevents an - * event). - * - * @return this - * @task stop - */ - stop : function() { - var r = this.getRawEvent(); - if (r) { - r.cancelBubble = true; - r.stopPropagation && r.stopPropagation(); - } - this.setStopped(true); - return this; - }, - - - /** - * Prevent an event's default action. This depends on the event type, but - * the common default actions are following links, submitting forms, - * and typing text. Event prevention is generally used when you have a link - * or form which work properly without Javascript but have a specialized - * Javascript behavior. When you intercept the event and make the behavior - * occur, you prevent it to keep the browser from following the link. - * - * Preventing an event does not stop it from propagating, so other handlers - * will still receive it. See ""Using Events"" for more information on the - * distinction between 'stopping' and 'preventing' an event. See also - * stop() (which stops an event but does not prevent it) and kill() - * (which stops and prevents an event). - * - * @return this - * @task stop - */ - prevent : function() { - var r = this.getRawEvent(); - if (r) { - r.returnValue = false; - r.preventDefault && r.preventDefault(); - } - this.setPrevented(true); - return this; - }, - - - /** - * Stop and prevent an event, which stops it from propagating and prevents - * its defualt behavior. This is a convenience function, see stop() and - * prevent() for information on what it means to stop or prevent an event. - * - * @return this - * @task stop - */ - kill : function() { - this.prevent(); - this.stop(); - return this; - }, - - - /** - * Get the special key (like tab or return), if any, associated with this - * event. Browsers report special keys differently; this method allows you - * to identify a keypress in a browser-agnostic way. Note that this detects - * only some special keys: delete, tab, return escape, left, up, right, - * down. - * - * For example, if you want to react to the escape key being pressed, you - * could install a listener like this: - * - * JX.Stratcom.listen('keydown', 'example', function(e) { - * if (e.getSpecialKey() == 'esc') { - * JX.log("You pressed 'Escape'! Well done! Bravo!"); - * } - * }); - * - * @return string|null ##null## if there is no associated special key, - * or one of the strings 'delete', 'tab', 'return', - * 'esc', 'left', 'up', 'right', or 'down'. - * @task info - */ - getSpecialKey : function() { - var r = this.getRawEvent(); - if (!r || r.shiftKey) { - return null; - } - - return JX.Event._keymap[r.keyCode] || null; - }, - - - /** - * Get the node corresponding to the specified key in this event's node map. - * This is a simple helper method that makes the API for accessing nodes - * less ugly. - * - * JX.Stratcom.listen('click', 'tag:a', function(e) { - * var a = e.getNode('tag:a'); - * // do something with the link that was clicked - * }); - * - * @param string sigil or stratcom node key - * @return node|null Node mapped to the specified key, or null if it the - * key does not exist. The available keys include: - * - 'tag:'+tag - first node of each type - * - 'id:'+id - all nodes with an id - * - sigil - first node of each sigil - * @task info - */ - getNode : function(key) { - return this.getNodes()[key] || null; - }, - - - /** - * Get the metadata associated with the node that corresponds to the key - * in this event's node map. This is a simple helper method that makes - * the API for accessing metadata associated with specific nodes less ugly. - * - * JX.Stratcom.listen('click', 'tag:a', function(event) { - * var anchorData = event.getNodeData('tag:a'); - * // do something with the metadata of the link that was clicked - * }); - * - * @param string sigil or stratcom node key - * @return dict dictionary of the node's metadata - * @task info - */ - getNodeData : function(key) { - // Evade static analysis - JX.Stratcom - return JX['Stratcom'].getData(this.getNode(key)); - } - }, - - statics : { - _keymap : { - 8 : 'delete', - 9 : 'tab', - 13 : 'return', - 27 : 'esc', - 37 : 'left', - 38 : 'up', - 39 : 'right', - 40 : 'down', - 63232 : 'up', - 63233 : 'down', - 62234 : 'left', - 62235 : 'right' - } - }, - - properties : { - - /** - * Native Javascript event which generated this @{JX.Event}. Not every - * event is generated by a native event, so there may be ##null## in - * this field. - * - * @type Event|null - * @task info - */ - rawEvent : null, - - /** - * String describing the event type, like 'click' or 'mousedown'. This - * may also be an application or object event. - * - * @type string - * @task info - */ - type : null, - - /** - * If available, the DOM node where this event occurred. For example, if - * this event is a click on a button, the target will be the button which - * was clicked. Application events will not have a target, so this property - * will return the value ##null##. - * - * @type DOMNode|null - * @task info - */ - target : null, - - /** - * Metadata attached to nodes associated with this event. - * - * For native events, the DOM is walked from the event target to the root - * element. Each sigil which is encountered while walking up the tree is - * added to the map as a key. If the node has associated metainformation, - * it is set as the value; otherwise, the value is null. - * - * @type dict - * @task info - */ - data : null, - - /** - * Sigil path this event was activated from. TODO: explain this - * - * @type list - * @task info - */ - path : [], - - /** - * True if propagation of the event has been stopped. See stop(). - * - * @type bool - * @task stop - */ - stopped : false, - - /** - * True if default behavior of the event has been prevented. See prevent(). - * - * @type bool - * @task stop - */ - prevented : false, - - /** - * @task info - */ - nodes : {} - }, - - /** - * @{JX.Event} installs a toString() method in ##__DEV__## which allows you to - * log or print events and get a reasonable representation of them: - * - * Event<'click', ['path', 'stuff'], [object HTMLDivElement]> - */ - initialize : function() { - if (__DEV__) { - JX.Event.prototype.toString = function() { - var path = '['+this.getPath().join(', ')+']'; - return 'Event<'+this.getType()+', '+path+', '+this.getTarget()+'>'; - } - } - } -}); -/** - * @requires javelin-install javelin-event javelin-util javelin-magical-init - * @provides javelin-stratcom - * @javelin - */ - -/** - * Javelin strategic command, the master event delegation core. This class is - * a sort of hybrid between Arbiter and traditional event delegation, and - * serves to route event information to handlers in a general way. - * - * Each Javelin :JX.Event has a 'type', which may be a normal Javascript type - * (for instance, a click or a keypress) or an application-defined type. It - * also has a "path", based on the path in the DOM from the root node to the - * event target. Note that, while the type is required, the path may be empty - * (it often will be for application-defined events which do not originate - * from the DOM). - * - * The path is determined by walking down the tree to the event target and - * looking for nodes that have been tagged with metadata. These names are used - * to build the event path, and unnamed nodes are ignored. Each named node may - * also have data attached to it. - * - * Listeners specify one or more event types they are interested in handling, - * and, optionally, one or more paths. A listener will only receive events - * which occurred on paths it is listening to. See listen() for more details. - * - * @author epriestley - * - * @task invoke Invoking Events - * @task listen Listening to Events - * @task handle Responding to Events - * @task sigil Managing Sigils - * @task meta Managing Metadata - * @task internal Internals - */ -JX.install('Stratcom', { - statics : { - ready : false, - _targets : {}, - _handlers : [], - _need : {}, - _auto : '*', - _data : {}, - _execContext : [], - _typeMap : {focusin: 'focus', focusout: 'blur'}, - - /** - * Node metadata is stored in a series of blocks to prevent collisions - * between indexes that are generated on the server side (and potentially - * concurrently). Block 0 is for metadata on the initial page load, block 1 - * is for metadata added at runtime with JX.Stratcom.siglize(), and blocks - * 2 and up are for metadata generated from other sources (e.g. JX.Request). - * Use allocateMetadataBlock() to reserve a block, and mergeData() to fill - * a block with data. - * - * When a JX.Request is sent, a block is allocated for it and any metadata - * it returns is filled into that block. - */ - _dataBlock : 2, - - /** - * Within each datablock, data is identified by a unique index. The data - * pointer (data-meta attribute) on a node looks like this: - * - * 1_2 - * - * ...where 1 is the block, and 2 is the index within that block. Normally, - * blocks are filled on the server side, so index allocation takes place - * there. However, when data is provided with JX.Stratcom.addData(), we - * need to allocate indexes on the client. - */ - _dataIndex : 0, - - /** - * Dispatch a simple event that does not have a corresponding native event - * object. It is unusual to call this directly. Generally, you will instead - * dispatch events from an object using the invoke() method present on all - * objects. See @{JX.Base.invoke()} for documentation. - * - * @param string Event type. - * @param list? Optionally, a path to attach to the event. This is - * rarely meaingful for simple events. - * @param object? Optionally, arbitrary data to send with the event. - * @return @{JX.Event} The event object which was dispatched to listeners. - * The main use of this is to test whether any - * listeners prevented the event. - * @task invoke - */ - invoke : function(type, path, data) { - var proxy = new JX.Event() - .setType(type) - .setData(data || {}) - .setPath(path || []); - - return this._dispatchProxy(proxy); - }, - - - /** - * Listen for events on given paths. Specify one or more event types, and - * zero or more paths to filter on. If you don't specify a path, you will - * receive all events of the given type: - * - * // Listen to all clicks. - * JX.Stratcom.listen('click', null, handler); - * - * This will notify you of all clicks anywhere in the document (unless - * they are intercepted and killed by a higher priority handler before they - * get to you). - * - * Often, you may be interested in only clicks on certain elements. You - * can specify the paths you're interested in to filter out events which - * you do not want to be notified of. - * - * // Listen to all clicks inside elements annotated "news-feed". - * JX.Stratcom.listen('click', 'news-feed', handler); - * - * By adding more elements to the path, you can create a finer-tuned - * filter: - * - * // Listen to only "like" clicks inside "news-feed". - * JX.Stratcom.listen('click', ['news-feed', 'like'], handler); - * - * - * TODO: Further explain these shenanigans. - * - * @param string|list Event type (or list of event names) to - * listen for. For example, ##'click'## or - * ##['keydown', 'keyup']##. - * - * @param wild Sigil paths to listen for this event on. See discussion - * in method documentation. - * - * @param function Callback to invoke when this event is triggered. It - * should have the signature ##f(:JX.Event e)##. - * - * @return object A reference to the installed listener. You can later - * remove the listener by calling this object's remove() - * method. - * @author epriestley - * @task listen - */ - listen : function(types, paths, func) { - - if (__DEV__) { - if (arguments.length == 4) { - throw new Error( - 'JX.Stratcom.listen(...): '+ - 'requires exactly 3 arguments. Did you mean JX.DOM.listen?'); - } - if (arguments.length != 3) { - throw new Error( - 'JX.Stratcom.listen(...): '+ - 'requires exactly 3 arguments.'); - } - if (typeof func != 'function') { - throw new Error( - 'JX.Stratcom.listen(...): '+ - 'callback is not a function.'); - } - } - - var ids = []; - - types = JX.$AX(types); - - if (!paths) { - paths = this._auto; - } - if (!(paths instanceof Array)) { - paths = [[paths]]; - } else if (!(paths[0] instanceof Array)) { - paths = [paths]; - } - - // To listen to multiple event types on multiple paths, we just install - // the same listener a whole bunch of times: if we install for two - // event types on three paths, we'll end up with six references to the - // listener. - // - // TODO: we'll call your listener twice if you install on two paths where - // one path is a subset of another. The solution is "don't do that", but - // it would be nice to verify that the caller isn't doing so, in __DEV__. - for (var ii = 0; ii < types.length; ++ii) { - var type = types[ii]; - if (('onpagehide' in window) && type == 'unload') { - // If we use "unload", we break the bfcache ("Back-Forward Cache") in - // Safari and Firefox. The BFCache makes using the back/forward - // buttons really fast since the pages can come out of magical - // fairyland instead of over the network, so use "pagehide" as a proxy - // for "unload" in these browsers. - type = 'pagehide'; - } - if (!(type in this._targets)) { - this._targets[type] = {}; - } - var type_target = this._targets[type]; - for (var jj = 0; jj < paths.length; ++jj) { - var path = paths[jj]; - var id = this._handlers.length; - this._handlers.push(func); - this._need[id] = path.length; - ids.push(id); - for (var kk = 0; kk < path.length; ++kk) { - if (__DEV__) { - if (path[kk] == 'tag:#document') { - throw new Error( - 'JX.Stratcom.listen(..., "tag:#document", ...): ' + - 'listen for all events using null, not "tag:#document"'); - } - if (path[kk] == 'tag:window') { - throw new Error( - 'JX.Stratcom.listen(..., "tag:window", ...): ' + - 'listen for window events using null, not "tag:window"'); - } - } - if (!type_target[path[kk]]) { - type_target[path[kk]] = []; - } - type_target[path[kk]].push(id); - } - } - } - - return { - remove : function() { - for (var ii = 0; ii < ids.length; ii++) { - delete JX.Stratcom._handlers[ids[ii]]; - } - } - }; - }, - - - /** - * Dispatch a native Javascript event through the Stratcom control flow. - * Generally, this is automatically called for you by the master dipatcher - * installed by ##init.js##. When you want to dispatch an application event, - * you should instead call invoke(). - * - * @param Event Native event for dispatch. - * @return :JX.Event Dispatched :JX.Event. - * @task internal - */ - dispatch : function(event) { - var path = []; - var nodes = {}; - var push = function(key, node) { - // we explicitly only store the first occurrence of each key - if (!nodes.hasOwnProperty(key)) { - nodes[key] = node; - path.push(key); - } - }; - - var target = event.srcElement || event.target; - - // Since you can only listen by tag, id or sigil, which are all - // attributes of an Element (the DOM interface), we unset the target - // if it isn't an Element (window and Document are Nodes but not Elements) - if (!target || !target.getAttribute) { - target = null; - } - - var cursor = target; - while (cursor && cursor.getAttribute) { - push('tag:' + cursor.nodeName.toLowerCase(), cursor); - - var id = cursor.id; - if (id) { - push('id:' + id, cursor); - } - - var sigils = cursor.getAttribute('data-sigil'); - if (sigils) { - sigils = sigils.split(' '); - for (var ii = 0; ii < sigils.length; ii++) { - push(sigils[ii], cursor); - } - } - - cursor = cursor.parentNode; - } - - var etype = event.type; - if (etype in this._typeMap) { - etype = this._typeMap[etype]; - } - - var proxy = new JX.Event() - .setRawEvent(event) - .setType(etype) - .setTarget(target) - .setNodes(nodes) - .setPath(path.reverse()); - -// JX.log('~> '+proxy.toString()); - - return this._dispatchProxy(proxy); - }, - - - /** - * Dispatch a previously constructed proxy :JX.Event. - * - * @param :JX.Event Event to dispatch. - * @return :JX.Event Returns the event argument. - * @task internal - */ - _dispatchProxy : function(proxy) { - - var scope = this._targets[proxy.getType()]; - - if (!scope) { - return proxy; - } - - var path = proxy.getPath(); - var len = path.length; - var hits = {}; - var matches; - - for (var root = -1; root < len; ++root) { - if (root == -1) { - matches = scope[this._auto]; - } else { - matches = scope[path[root]]; - } - if (!matches) { - continue; - } - for (var ii = 0; ii < matches.length; ++ii) { - hits[matches[ii]] = (hits[matches[ii]] || 0) + 1; - } - } - - var exec = []; - - for (var k in hits) { - if (hits[k] == this._need[k]) { - var handler = this._handlers[k]; - if (handler) { - exec.push(handler); - } - } - } - - this._execContext.push({ - handlers: exec, - event: proxy, - cursor: 0 - }); - - this.pass(); - - this._execContext.pop(); - - return proxy; - }, - - /** - * Pass on an event, allowing other handlers to process it. The use case - * here is generally something like: - * - * if (JX.Stratcom.pass()) { - * // something else handled the event - * return; - * } - * // handle the event - * event.prevent(); - * - * This allows you to install event handlers that operate at a lower - * effective priority, and provide a default behavior which is overridable - * by listeners. - * - * @return bool True if the event was stopped or prevented by another - * handler. - * @task handle - */ - pass : function() { - var context = this._execContext[this._execContext.length - 1]; - while (context.cursor < context.handlers.length) { - var cursor = context.cursor; - ++context.cursor; - (context.handlers[cursor] || JX.bag)(context.event); - if (context.event.getStopped()) { - break; - } - } - return context.event.getStopped() || context.event.getPrevented(); - }, - - - /** - * Retrieve the event (if any) which is currently being dispatched. - * - * @return :JX.Event|null Event which is currently being dispatched, or - * null if there is no active dispatch. - * @task handle - */ - context : function() { - var len = this._execContext.length; - if (!len) { - return null; - } - return this._execContext[len - 1].event; - }, - - - /** - * Merge metadata. You must call this (even if you have no metadata) to - * start the Stratcom queue. - * - * @param int The datablock to merge data into. - * @param dict Dictionary of metadata. - * @return void - * @task internal - */ - mergeData : function(block, data) { - this._data[block] = data; - if (block == 0) { - JX.Stratcom.ready = true; - JX.__rawEventQueue({type: 'start-queue'}); - } - }, - - - /** - * Determine if a node has a specific sigil. - * - * @param Node Node to test. - * @param string Sigil to check for. - * @return bool True if the node has the sigil. - * - * @task sigil - */ - hasSigil : function(node, sigil) { - if (__DEV__) { - if (!node || !node.getAttribute) { - throw new Error( - 'JX.Stratcom.hasSigil(, ...): ' + - 'node is not an element. Most likely, you\'re passing window or ' + - 'document, which are not elements and can\'t have sigils.'); - } - } - - var sigils = node.getAttribute('data-sigil'); - return sigils && (' ' + sigils + ' ').indexOf(' ' + sigil + ' ') > -1; - }, - - - /** - * Add a sigil to a node. - * - * @param Node Node to add the sigil to. - * @param string Sigil to name the node with. - * @return void - * @task sigil - */ - addSigil: function(node, sigil) { - if (__DEV__) { - if (!node || !node.getAttribute) { - throw new Error( - 'JX.Stratcom.addSigil(, ...): ' + - 'node is not an element. Most likely, you\'re passing window or ' + - 'document, which are not elements and can\'t have sigils.'); - } - } - - var sigils = node.getAttribute('data-sigil'); - if (sigils && !JX.Stratcom.hasSigil(node, sigil)) { - sigil = sigils + ' ' + sigil; - } - - node.setAttribute('data-sigil', sigil); - }, - - - /** - * Retrieve a node's metadata. - * - * @param Node Node from which to retrieve data. - * @return object Data attached to the node. If no data has been attached - * to the node yet, an empty object will be returned, but - * subsequent calls to this method will always retrieve the - * same object. - * @task meta - */ - getData : function(node) { - if (__DEV__) { - if (!node || !node.getAttribute) { - throw new Error( - 'JX.Stratcom.getData(): ' + - 'node is not an element. Most likely, you\'re passing window or ' + - 'document, which are not elements and can\'t have data.'); - } - } - - var meta_id = (node.getAttribute('data-meta') || '').split('_'); - if (meta_id[0] && meta_id[1]) { - var block = this._data[meta_id[0]]; - var index = meta_id[1]; - if (block && (index in block)) { - return block[index]; - } - } - - var data = {}; - if (!this._data[1]) { // data block 1 is reserved for JavaScript - this._data[1] = {}; - } - this._data[1][this._dataIndex] = data; - node.setAttribute('data-meta', '1_' + (this._dataIndex++)); - return data; - }, - - - /** - * Add data to a node's metadata. - * - * @param Node Node which data should be attached to. - * @param object Data to add to the node's metadata. - * @return object Data attached to the node that is returned by - * JX.Stratcom.getData(). - * @task meta - */ - addData : function(node, data) { - if (__DEV__) { - if (!node || !node.getAttribute) { - throw new Error( - 'JX.Stratcom.addData(, ...): ' + - 'node is not an element. Most likely, you\'re passing window or ' + - 'document, which are not elements and can\'t have sigils.'); - } - if (!data || typeof data != 'object') { - throw new Error( - 'JX.Stratcom.addData(..., ): ' + - 'data to attach to node is not an object. You must use ' + - 'objects, not primitives, for metadata.'); - } - } - - return JX.copy(JX.Stratcom.getData(node), data); - }, - - - /** - * @task internal - */ - allocateMetadataBlock : function() { - return this._dataBlock++; - } - } -}); -/** - * @provides javelin-behavior - * - * @javelin-installs JX.behavior - * @javelin-installs JX.initBehaviors - * - * @javelin - */ - -JX.behavior = function(name, control_function) { - if (__DEV__) { - if (JX.behavior._behaviors.hasOwnProperty(name)) { - throw new Error( - 'JX.behavior("'+name+'", ...): '+ - 'behavior is already registered.'); - } - if (!control_function) { - throw new Error( - 'JX.behavior("'+name+'", ): '+ - 'initialization function is required.'); - } - if (typeof control_function != 'function') { - throw new Error( - 'JX.behavior("'+name+'", ): '+ - 'initialization function is not a function.'); - } - } - JX.behavior._behaviors[name] = control_function; -}; - - -JX.initBehaviors = function(map) { - for (var name in map) { - if (__DEV__) { - if (!(name in JX.behavior._behaviors)) { - throw new Error( - 'JX.initBehavior("'+name+'", ...): '+ - 'behavior is not registered.'); - } - } - var configs = map[name]; - if (!configs.length) { - if (JX.behavior._initialized.hasOwnProperty(name)) { - continue; - } else { - configs = [null]; - } - } - for (var ii = 0; ii < configs.length; ii++) { - JX.behavior._behaviors[name](configs[ii]); - } - JX.behavior._initialized[name] = true; - } -}; - -!function(JX) { - JX.behavior._behaviors = {}; - JX.behavior._initialized = {}; -}(JX); -/** - * @requires javelin-install - * javelin-stratcom - * javelin-util - * javelin-behavior - * @provides javelin-request - * @javelin - */ - -/** - * Make basic AJAX XMLHTTPRequests. - */ -JX.install('Request', { - construct : function(uri, handler) { - this.setURI(uri); - if (handler) { - this.listen('done', handler); - } - }, - - events : ['send', 'done', 'error', 'finally'], - - members : { - - _xhrkey : null, - _transport : null, - _finished : false, - _block : null, - - send : function() { - var xport = null; - - try { - try { - xport = new XMLHttpRequest(); - } catch (x) { - xport = new ActiveXObject("Msxml2.XMLHTTP"); - } - } catch (x) { - xport = new ActiveXObject("Microsoft.XMLHTTP"); - } - - this._transport = xport; - this._xhrkey = JX.Request._xhr.length; - JX.Request._xhr.push(this); - - xport.onreadystatechange = JX.bind(this, this._onreadystatechange); - - var data = this.getData() || {}; - data.__ajax__ = true; - - this._block = JX.Stratcom.allocateMetadataBlock(); - data.__metablock__ = this._block; - - var q = (this.getDataSerializer() || - JX.Request.defaultDataSerializer)(data); - var uri = this.getURI(); - var method = this.getMethod().toUpperCase(); - - if (method == 'GET') { - uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q; - } - - this.invoke('send', this); - - if (this.getTimeout()) { - this._timer = JX.defer( - JX.bind( - this, - this._fail, - JX.Request.ERROR_TIMEOUT), - this.getTimeout()); - } - - xport.open(method, uri, true); - - if (__DEV__) { - if (this.getFile()) { - if (method != 'POST') { - throw new Error( - 'JX.Request.send(): ' + - 'attempting to send a file over GET. You must use POST.'); - } - if (this.getData()) { - throw new Error( - 'JX.Request.send(): ' + - 'attempting to send data and a file. You can not send both ' + - 'at once.'); - } - } - } - - if (method == 'POST') { - if (this.getFile()) { - xport.send(this.getFile()); - } else { - xport.setRequestHeader( - 'Content-Type', - 'application/x-www-form-urlencoded'); - xport.send(q); - } - } else { - xport.send(null); - } - }, - - abort : function() { - this._cleanup(); - }, - - _onreadystatechange : function() { - var xport = this._transport; - try { - if (this._finished) { - return; - } - if (xport.readyState != 4) { - return; - } - if (xport.status < 200 || xport.status >= 300) { - this._fail(); - return; - } - - if (__DEV__) { - if (!xport.responseText.length) { - throw new Error( - 'JX.Request("'+this.getURI()+'", ...): '+ - 'server returned an empty response.'); - } - if (xport.responseText.indexOf('for (;;);') != 0) { - throw new Error( - 'JX.Request("'+this.getURI()+'", ...): '+ - 'server returned an invalid response.'); - } - } - - var text = xport.responseText.substring('for (;;);'.length); - var response = eval('('+text+')'); - } catch (exception) { - - if (__DEV__) { - JX.log( - 'JX.Request("'+this.getURI()+'", ...): '+ - 'caught exception processing response: '+exception); - } - this._fail(); - return; - } - - try { - if (response.error) { - this._fail(response.error); - } else { - JX.Stratcom.mergeData( - this._block, - response.javelin_metadata || {}); - this._done(response); - JX.initBehaviors(response.javelin_behaviors || {}); - } - } catch (exception) { - // In Firefox+Firebug, at least, something eats these. :/ - JX.defer(function() { - throw exception; - }); - } - }, - - _fail : function(error) { - this._cleanup(); - - this.invoke('error', error, this); - this.invoke('finally'); - }, - - _done : function(response) { - this._cleanup(); - - if (response.onload) { - for (var ii = 0; ii < response.onload.length; ii++) { - (new Function(response.onload[ii]))(); - } - } - - this.invoke('done', this.getRaw() ? response : response.payload, this); - this.invoke('finally'); - }, - - _cleanup : function() { - this._finished = true; - delete JX.Request._xhr[this._xhrkey]; - this._timer && this._timer.stop(); - this._transport.abort(); - } - - }, - - statics : { - _xhr : [], - shutdown : function() { - for (var ii = 0; ii < JX.Request._xhr.length; ii++) { - try { - JX.Request._xhr[ii] && JX.Request._xhr[ii].abort(); - } catch (x) { - // Ignore. - } - } - JX.Request._xhr = []; - }, - ERROR_TIMEOUT : -9000, - defaultDataSerializer : function(data) { - var uri = []; - for (var k in data) { - uri.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k])); - } - return uri.join('&'); - } - }, - - properties : { - URI : null, - data : null, - dataSerializer : null, - /** - * Configure which HTTP method to use for the request. Permissible values - * are "POST" (default) or "GET". - * - * @param string HTTP method, one of "POST" or "GET". - */ - method : 'POST', - file : null, - raw : false, - - /** - * Configure a timeout, in milliseconds. If the request has not resolved - * (either with success or with an error) within the provided timeframe, - * it will automatically fail with error JX.Request.ERROR_TIMEOUT. - * - * @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds). - */ - timeout : null - }, - - initialize : function() { - JX.Stratcom.listen('unload', null, JX.Request.shutdown); - } - -}); - -/** - * @requires javelin-install javelin-event - * @provides javelin-vector - * @javelin - */ - -/** - * Query and update positions and dimensions of nodes (and other things) - * within a document. 'V' stands for 'Vector'. Each vector has two elements, - * 'x' and 'y', which usually represent width/height (a "dimension vector") or - * left/top (a "position vector"). - * - * Vectors are used to manage the sizes and positions of elements, events, - * the document, and the viewport (the visible section of the document, i.e. - * how much of the page the user can actually see in their browser window). - * Unlike most Javelin classes, @{JX.$V} exposes two bare properties, 'x' and - * 'y'. You can read and manipulate these directly: - * - * // Give the user information about elements when they click on them. - * JX.Stratcom.listen( - * 'click', - * null, - * function(e) { - * var p = JX.$V(e); - * var d = JX.$V.getDim(e.getTarget()); - * - * alert('You clicked at <'+p.x+','+p.y'>; the element you clicked '+ - * 'is '+d.x+' pixels wide and '+d.y+' pixels high.'); - * }); - * - * You can also update positions and dimensions using vectors: - * - * // When the user clicks on something, make it 10px wider and 10px taller. - * JX.Stratcom.listen( - * 'click', - * null, - * function(e) { - * var t = e.getTarget(); - * JX.$V(t).add(10, 10).setDim(t); - * }); - * - * Additionally, vectors can be used to query document and viewport information: - * - * var v = JX.$V.getViewport(); // Viewport (window) width and height. - * var d = JX.$V.getDocument(); // Document width and height. - * var visible_area = parseInt(100 * (v.x * v.y) / (d.x * d.y), 10); - * alert('You can currently see '+visible_area'+ percent of the document.'); - * - * @author epriestley - * - * @task query Querying Positions and Dimensions - * @task update Changing Positions and Dimensions - * @task manip Manipulating Vectors - * - */ -JX.install('$V', { - - /** - * Construct a vector, either from explicit coordinates or from a node - * or event. You can pass two Numbers to construct an explicit vector: - * - * var v = JX.$V(35, 42); - * - * Otherwise, you can pass a @{JX.Event} or a Node to implicitly construct a - * vector: - * - * var u = JX.$V(some_event); - * var v = JX.$V(some_node); - * - * These are just like calling getPos() on the @{JX.Event} or Node. - * - * For convenience, @{JX.$V()} constructs a new vector even without the 'new' - * keyword. That is, these are equivalent: - * - * var q = new JX.$V(x, y); - * var r = JX.$V(x, y); - * - * Methods like getScroll(), getViewport() and getDocument() also create - * new vectors. - * - * Once you have a vector, you can manipulate it with add(): - * - * var u = JX.$V(35, 42); - * var v = u.add(5, -12); // v = <40, 30> - * - * @param wild 'x' component of the vector, or a @{JX.Event}, or a Node. - * @param Number? If providing an 'x' component, the 'y' component of the - * vector. - * @return @{JX.$V} Specified vector. - * @task query - */ - construct : function(x, y) { - if (this == JX || this == window) { - return new JX.$V(x, y); - } - if (typeof y == 'undefined') { - return JX.$V.getPos(x); - } - - this.x = parseFloat(x); - this.y = parseFloat(y); - }, - canCallAsFunction : true, - members : { - x : null, - y : null, - - /** - * Move a node around by setting the position of a Node to the vector's - * coordinates. For instance, if you want to move an element to the top left - * corner of the document, you could do this (assuming it has 'position: - * absolute'): - * - * JX.$V(0, 0).setPos(node); - * - * @param Node Node to move. - * @return this - * @task update - */ - setPos : function(node) { - node.style.left = (this.x === null) ? '' : (parseInt(this.x, 10) + 'px'); - node.style.top = (this.y === null) ? '' : (parseInt(this.y, 10) + 'px'); - return this; - }, - - /** - * Change the size of a node by setting its dimensions to the vector's - * coordinates. For instance, if you want to change an element to be 100px - * by 100px: - * - * JX.$V(100, 100).setDim(node); - * - * Or if you want to expand a node's dimensions by 50px: - * - * JX.$V(node).add(50, 50).setDim(node); - * - * @param Node Node to resize. - * @return this - * @task update - */ - setDim : function(node) { - node.style.width = - (this.x === null) - ? '' - : (parseInt(this.x, 10) + 'px'); - node.style.height = - (this.y === null) - ? '' - : (parseInt(this.y, 10) + 'px'); - return this; - }, - - /** - * Change a vector's x and y coordinates by adding numbers to them, or - * adding the coordinates of another vector. For example: - * - * var u = JX.$V(3, 4).add(100, 200); // u = <103, 204> - * - * You can also add another vector: - * - * var q = JX.$V(777, 999); - * var r = JX.$V(1000, 2000); - * var s = q.add(r); // s = <1777, 2999> - * - * Note that this method returns a new vector. It does not modify the - * 'this' vector. - * - * @param wild Value to add to the vector's x component, or another - * vector. - * @param Number? Value to add to the vector's y component. - * @return @{JX.$V} New vector, with summed components. - * @task manip - */ - add : function(x, y) { - if (x instanceof JX.$V) { - return this.add(x.x, x.y); - } - return JX.$V(this.x + parseFloat(x), this.y + parseFloat(y)); - } - }, - statics : { - _viewport: null, - - /** - * Determine where in a document an element is (or where an event, like - * a click, occurred) by building a new vector containing the position of a - * Node or @{JX.Event}. The 'x' component of the vector will correspond to - * the pixel offset of the argument relative to the left edge of the - * document, and the 'y' component will correspond to the pixel offset of - * the argument relative to the top edge of the document. Note that all - * vectors are generated in document coordinates, so the scroll position - * does not affect them. - * - * See also getDim(), used to determine an element's dimensions. - * - * @param Node|@{JX.Event} Node or event to determine the position of. - * @return @{JX.$V} New vector with the argument's position. - * @task query - */ - getPos : function(node) { - - JX.Event && (node instanceof JX.Event) && (node = node.getRawEvent()); - - if (('pageX' in node) || ('clientX' in node)) { - var c = JX.$V._viewport; - return JX.$V( - node.pageX || (node.clientX + c.scrollLeft), - node.pageY || (node.clientY + c.scrollTop)); - } - - var x = node.offsetLeft; - var y = node.offsetTop; - while (node.offsetParent && (node.offsetParent != document.body)) { - node = node.offsetParent; - x += node.offsetLeft; - y += node.offsetTop; - } - - return JX.$V(x, y); - }, - - /** - * Determine the width and height of a node by building a new vector with - * dimension information. The 'x' component of the vector will correspond - * to the element's width in pixels, and the 'y' component will correspond - * to its height in pixels. - * - * See also getPos(), used to determine an element's position. - * - * @param Node Node to determine the display size of. - * @return @{JX.$V} New vector with the node's dimensions. - * @task query - */ - getDim : function(node) { - return JX.$V(node.offsetWidth, node.offsetHeight); - }, - - /** - * Determine the current scroll position by building a new vector where - * the 'x' component corresponds to how many pixels the user has scrolled - * from the left edge of the document, and the 'y' component corresponds to - * how many pixels the user has scrolled from the top edge of the document. - * - * See also getViewport(), used to determine the size of the viewport. - * - * @return @{JX.$V} New vector with the document scroll position. - * @task query - */ - getScroll : function() { - // We can't use $V._viewport here because there's diversity between - // browsers with respect to where position/dimension and scroll position - // information is stored. - var b = document.body; - var e = document.documentElement; - return JX.$V(b.scrollLeft || e.scrollLeft, b.scrollTop || e.scrollTop); - }, - - /** - * Determine the size of the viewport (basically, the browser window) by - * building a new vector where the 'x' component corresponds to the width - * of the viewport in pixels and the 'y' component corresponds to the height - * of the viewport in pixels. - * - * See also getScroll(), used to determine the position of the viewport, and - * getDocument(), used to determine the size of the entire document. - * - * @return @{JX.$V} New vector with the viewport dimensions. - * @task query - */ - getViewport : function() { - var c = JX.$V._viewport; - var w = window; - - return JX.$V( - w.innerWidth || c.clientWidth || 0, - w.innerHeight || c.clientHeight || 0 - ); - }, - - /** - * Determine the size of the document, including any area outside the - * current viewport which the user would need to scroll in order to see, by - * building a new vector where the 'x' component corresponds to the document - * width in pixels and the 'y' component corresponds to the document height - * in pixels. - * - * @return @{JX.$V} New vector with the document dimensions. - * @task query - */ - getDocument : function() { - var c = JX.$V._viewport; - return JX.$V(c.scrollWidth || 0, c.scrollHeight || 0); - } - }, - - /** - * On initialization, the browser-dependent viewport root is determined and - * stored. - * - * In ##__DEV__##, @{JX.$V} installs a toString() method so vectors print in a - * debuggable way: - * - * <23, 92> - * - * @return void - */ - initialize : function() { - var c = ((c = document) && (c = c.documentElement)) || - ((c = document) && (c = c.body)) - JX.$V._viewport = c; - - if (__DEV__) { - JX.$V.prototype.toString = function() { - return '<'+this.x+', '+this.y+'>'; - } - } - - } -}); -/** - * @requires javelin-install javelin-util javelin-vector javelin-stratcom - * @provides javelin-dom - * - * @javelin-installs JX.$ - * @javelin-installs JX.$N - * - * @javelin - */ - - -/** - * Select an element by its "id" attribute, like ##document.getElementById()##. - * For example: - * - * var node = JX.$('some_id'); - * - * This will select the node with the specified "id" attribute: - * - * LANG=HTML - *
...
- * - * If the specified node does not exist, @{JX.$()} will throw ##JX.$.NotFound##. - * For other ways to select nodes from the document, see @{JX.DOM.scry()} and - * @{JX.DOM.find()}. - * - * @param string "id" attribute to select from the document. - * @return Node Node with the specified "id" attribute. - */ -JX.$ = function(id) { - - if (__DEV__) { - if (!id) { - throw new Error('Empty ID passed to JX.$()!'); - } - } - - var node = document.getElementById(id); - if (!node || (node.id != id)) { - if (__DEV__) { - if (node && (node.id != id)) { - throw new Error( - 'JX.$("'+id+'"): '+ - 'document.getElementById() returned an element without the '+ - 'correct ID. This usually means that the element you are trying '+ - 'to select is being masked by a form with the same value in its '+ - '"name" attribute.'); - } - } - throw JX.$.NotFound; - } - - return node; -}; - -JX.$.NotFound = {}; -if (__DEV__) { - // If we're in dev, upgrade this object into an Error so that it will - // print something useful if it escapes the stack after being thrown. - JX.$.NotFound = new Error( - 'JX.$() or JX.DOM.find() call matched no nodes.'); -} - - -/** - * Upcast a string into an HTML object so it is treated as markup instead of - * plain text. See @{JX.$N} for discussion of Javelin's security model. Every - * time you call this function you potentially open up a security hole. Avoid - * its use wherever possible. - * - * This class intentionally supports only a subset of HTML because many browsers - * named "Internet Explorer" have awkward restrictions around what they'll - * accept for conversion to document fragments. Alter your datasource to emit - * valid HTML within this subset if you run into an unsupported edge case. All - * the edge cases are crazy and you should always be reasonably able to emit - * a cohesive tag instead of an unappendable fragment. - * - * @task build String into HTML - * @task nodes HTML into Nodes - */ -JX.install('HTML', { - - /** - * Build a new HTML object from a trustworthy string. - * - * @task build - * @param string A string which you want to be treated as HTML, because you - * know it is from a trusted source and any data in it has been - * properly escaped. - * @return JX.HTML HTML object, suitable for use with @{JX.$N}. - */ - construct : function(str) { - if (this == JX || this == window) { - return new JX.HTML(str); - } - - if (__DEV__) { - var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup', - 'caption', 'tr', 'th', 'td', 'option']; - - var evil_stuff = new RegExp('^\\s*<('+tags.join('|')+')\\b', 'i'); - var match = null; - if (match = str.match(evil_stuff)) { - throw new Error( - 'JX.HTML("<'+match[1]+'>..."): '+ - 'call initializes an HTML object with an invalid partial fragment '+ - 'and can not be converted into DOM nodes. The enclosing tag of an '+ - 'HTML content string must be appendable to a document fragment. '+ - 'For example, is allowed but or are not.'); - } - - var really_evil = /..."): '+ - 'call initializes an HTML object with an embedded script tag! '+ - 'Are you crazy?! Do NOT do this!!!'); - } - - var wont_work = /..."): '+ - 'call initializes an HTML object with an embedded tag. IE '+ - 'will not do the right thing with this.'); - } - - // TODO(epriestley): May need to deny