@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+877 -4714
+1 -1
.arcconfig
··· 1 1 { 2 2 "project_id" : "phabricator", 3 3 "conduit_uri" : "https://secure.phabricator.com/api/", 4 - "lint_engine" : "PhutilLintEngine", 4 + "lint_engine" : "PhabricatorLintEngine", 5 5 "unit_engine" : "PhutilUnitTestEngine", 6 6 "copyright_holder" : "Facebook, Inc.", 7 7 "remote_hooks_installed" : true,
+3
.gitmodules
··· 1 + [submodule "externals/javelin"] 2 + path = externals/javelin 3 + url = git://github.com/epriestley/javelin.git
+31 -2
scripts/celerity_mapper.php
··· 2 2 <?php 3 3 4 4 $package_spec = array( 5 + 'javelin.pkg.js' => array( 6 + 'javelin-util', 7 + 'javelin-install', 8 + 'javelin-event', 9 + 'javelin-stratcom', 10 + 'javelin-behavior', 11 + 'javelin-request', 12 + 'javelin-vector', 13 + 'javelin-dom', 14 + 'javelin-json', 15 + 'javelin-uri', 16 + ), 17 + 'typeahead.pkg.js' => array( 18 + 'javelin-typeahead', 19 + 'javelin-typeahead-normalizer', 20 + 'javelin-typeahead-source', 21 + 'javelin-typeahead-preloaded-source', 22 + 'javelin-typeahead-ondemand-source', 23 + 'javelin-tokenizer', 24 + 'javelin-behavior-aphront-basic-tokenizer', 25 + ), 26 + 'workflow.pkg.js' => array( 27 + 'javelin-mask', 28 + 'javelin-workflow', 29 + 'javelin-behavior-workflow', 30 + ), 5 31 'core.pkg.css' => array( 6 32 'phabricator-core-css', 7 33 'phabricator-core-buttons-css', ··· 14 40 'aphront-crumbs-view-css', 15 41 'aphront-tokenizer-control-css', 16 42 'aphront-typeahead-control-css', 43 + 'aphront-list-filter-view-css', 17 44 18 45 'phabricator-directory-css', 19 46 ··· 63 90 ->withType('f') 64 91 ->withSuffix('js') 65 92 ->withSuffix('css') 93 + ->withFollowSymlinks(true) 66 94 ->setGenerateChecksums(true) 67 95 ->find(); 68 96 ··· 101 129 $provides = array_filter($provides); 102 130 $requires = array_filter($requires); 103 131 104 - if (count($provides) !== 1) { 132 + if (count($provides) > 1) { 133 + // NOTE: Documentation-only JS is permitted to @provide no targets. 105 134 throw new Exception( 106 - "File {$path} must @provide exactly one Celerity target."); 135 + "File {$path} must @provide at most one Celerity target."); 107 136 } 108 137 109 138 $provides = reset($provides);
+410 -113
src/__celerity_resource_map__.php
··· 235 235 ), 236 236 'herald-rule-editor' => 237 237 array( 238 - 'uri' => '/res/ec8e2110/rsrc/js/application/herald/HeraldRuleEditor.js', 238 + 'uri' => '/res/f3122b0a/rsrc/js/application/herald/HeraldRuleEditor.js', 239 239 'type' => 'js', 240 240 'requires' => 241 241 array( 242 242 0 => 'multirow-row-manager', 243 - 1 => 'javelin-lib-dev', 244 - 2 => 'javelin-typeahead-dev', 245 - 3 => 'path-typeahead', 243 + 1 => 'javelin-install', 244 + 2 => 'javelin-typeahead', 245 + 3 => 'javelin-util', 246 + 4 => 'javelin-dom', 247 + 5 => 'javelin-tokenizer', 248 + 6 => 'javelin-typeahead-preloaded-source', 249 + 7 => 'javelin-stratcom', 250 + 8 => 'javelin-json', 246 251 ), 247 252 'disk' => '/rsrc/js/application/herald/HeraldRuleEditor.js', 248 253 ), ··· 255 260 ), 256 261 'disk' => '/rsrc/css/application/herald/herald-test.css', 257 262 ), 263 + 'javelin-behavior' => 264 + array( 265 + 'uri' => '/res/dc576a49/rsrc/js/javelin/lib/behavior.js', 266 + 'type' => 'js', 267 + 'requires' => 268 + array( 269 + ), 270 + 'disk' => '/rsrc/js/javelin/lib/behavior.js', 271 + ), 258 272 'javelin-behavior-aphront-basic-tokenizer' => 259 273 array( 260 - 'uri' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', 274 + 'uri' => '/res/d48d2732/rsrc/js/application/core/behavior-tokenizer.js', 261 275 'type' => 'js', 262 276 'requires' => 263 277 array( 264 - 0 => 'javelin-lib-dev', 278 + 0 => 'javelin-behavior', 279 + 1 => 'javelin-typeahead', 280 + 2 => 'javelin-tokenizer', 281 + 3 => 'javelin-typeahead-preloaded-source', 282 + 4 => 'javelin-dom', 265 283 ), 266 284 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', 267 285 ), 286 + 0 => 287 + array( 288 + 'uri' => '/res/e3d992aa/rsrc/js/javelin/docs/Base.js', 289 + 'type' => 'js', 290 + 'requires' => 291 + array( 292 + 0 => 'javelin-install', 293 + ), 294 + 'disk' => '/rsrc/js/javelin/docs/Base.js', 295 + ), 268 296 'javelin-behavior-dark-console' => 269 297 array( 270 - 'uri' => '/res/020b0265/rsrc/js/application/core/behavior-dark-console.js', 298 + 'uri' => '/res/447bd50a/rsrc/js/application/core/behavior-dark-console.js', 271 299 'type' => 'js', 272 300 'requires' => 273 301 array( 302 + 0 => 'javelin-behavior', 303 + 1 => 'javelin-stratcom', 304 + 2 => 'javelin-util', 305 + 3 => 'javelin-dom', 306 + 4 => 'javelin-request', 274 307 ), 275 308 'disk' => '/rsrc/js/application/core/behavior-dark-console.js', 276 309 ), 277 310 'javelin-behavior-differential-add-reviewers' => 278 311 array( 279 - 'uri' => '/res/330154e4/rsrc/js/application/differential/behavior-add-reviewers.js', 312 + 'uri' => '/res/fa2f29c4/rsrc/js/application/differential/behavior-add-reviewers.js', 280 313 'type' => 'js', 281 314 'requires' => 282 315 array( 283 - 0 => 'javelin-lib-dev', 316 + 0 => 'javelin-behavior', 317 + 1 => 'javelin-dom', 318 + 2 => 'javelin-tokenizer', 319 + 3 => 'javelin-typeahead', 320 + 4 => 'javelin-typeahead-preloaded-source', 284 321 ), 285 322 'disk' => '/rsrc/js/application/differential/behavior-add-reviewers.js', 286 323 ), 287 324 'javelin-behavior-differential-diff-radios' => 288 325 array( 289 - 'uri' => '/res/fdeb3823/rsrc/js/application/differential/behavior-diff-radios.js', 326 + 'uri' => '/res/d3365dba/rsrc/js/application/differential/behavior-diff-radios.js', 290 327 'type' => 'js', 291 328 'requires' => 292 329 array( 293 - 0 => 'javelin-lib-dev', 330 + 0 => 'javelin-behavior', 331 + 1 => 'javelin-stratcom', 332 + 2 => 'javelin-dom', 294 333 ), 295 334 'disk' => '/rsrc/js/application/differential/behavior-diff-radios.js', 296 335 ), 297 336 'javelin-behavior-differential-edit-inline-comments' => 298 337 array( 299 - 'uri' => '/res/6a6f38e6/rsrc/js/application/differential/behavior-edit-inline-comments.js', 338 + 'uri' => '/res/f18ee6ae/rsrc/js/application/differential/behavior-edit-inline-comments.js', 300 339 'type' => 'js', 301 340 'requires' => 302 341 array( 303 - 0 => 'javelin-lib-dev', 342 + 0 => 'javelin-behavior', 343 + 1 => 'javelin-stratcom', 344 + 2 => 'javelin-dom', 345 + 3 => 'javelin-workflow', 346 + 4 => 'javelin-vector', 304 347 ), 305 348 'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js', 306 349 ), 307 350 'javelin-behavior-differential-feedback-preview' => 308 351 array( 309 - 'uri' => '/res/8695d8b8/rsrc/js/application/differential/behavior-comment-preview.js', 352 + 'uri' => '/res/139c85b8/rsrc/js/application/differential/behavior-comment-preview.js', 310 353 'type' => 'js', 311 354 'requires' => 312 355 array( 313 - 0 => 'javelin-lib-dev', 356 + 0 => 'javelin-behavior', 357 + 1 => 'javelin-stratcom', 358 + 2 => 'javelin-dom', 359 + 3 => 'javelin-request', 360 + 4 => 'javelin-util', 314 361 ), 315 362 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', 316 363 ), 317 364 'javelin-behavior-differential-populate' => 318 365 array( 319 - 'uri' => '/res/a13dcd7e/rsrc/js/application/differential/behavior-populate.js', 366 + 'uri' => '/res/d4d4fd9d/rsrc/js/application/differential/behavior-populate.js', 320 367 'type' => 'js', 321 368 'requires' => 322 369 array( 323 - 0 => 'javelin-lib-dev', 370 + 0 => 'javelin-behavior', 371 + 1 => 'javelin-request', 372 + 2 => 'javelin-util', 373 + 3 => 'javelin-dom', 324 374 ), 325 375 'disk' => '/rsrc/js/application/differential/behavior-populate.js', 326 376 ), 327 377 'javelin-behavior-differential-show-all-comments' => 328 378 array( 329 - 'uri' => '/res/2a3592b8/rsrc/js/application/differential/behavior-show-all-comments.js', 379 + 'uri' => '/res/4d34a1e7/rsrc/js/application/differential/behavior-show-all-comments.js', 330 380 'type' => 'js', 331 381 'requires' => 332 382 array( 333 - 0 => 'javelin-lib-dev', 383 + 0 => 'javelin-behavior', 384 + 1 => 'javelin-stratcom', 385 + 2 => 'javelin-dom', 334 386 ), 335 387 'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js', 336 388 ), 337 389 'javelin-behavior-differential-show-more' => 338 390 array( 339 - 'uri' => '/res/ea998002/rsrc/js/application/differential/behavior-show-more.js', 391 + 'uri' => '/res/6af8c5bb/rsrc/js/application/differential/behavior-show-more.js', 340 392 'type' => 'js', 341 393 'requires' => 342 394 array( 343 - 0 => 'javelin-lib-dev', 395 + 0 => 'javelin-behavior', 396 + 1 => 'javelin-dom', 397 + 2 => 'javelin-request', 398 + 3 => 'javelin-util', 399 + 4 => 'javelin-stratcom', 344 400 ), 345 401 'disk' => '/rsrc/js/application/differential/behavior-show-more.js', 346 402 ), 347 403 'javelin-behavior-diffusion-jump-to' => 348 404 array( 349 - 'uri' => '/res/4f3f6cdc/rsrc/js/application/diffusion/behavior-jump-to.js', 405 + 'uri' => '/res/2a8ca30b/rsrc/js/application/diffusion/behavior-jump-to.js', 350 406 'type' => 'js', 351 407 'requires' => 352 408 array( 353 - 0 => 'javelin-lib-dev', 409 + 0 => 'javelin-behavior', 410 + 1 => 'javelin-util', 411 + 2 => 'javelin-vector', 412 + 3 => 'javelin-dom', 354 413 ), 355 414 'disk' => '/rsrc/js/application/diffusion/behavior-jump-to.js', 356 415 ), 357 416 'javelin-behavior-diffusion-pull-lastmodified' => 358 417 array( 359 - 'uri' => '/res/6a5e7374/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', 418 + 'uri' => '/res/f3e3f3a6/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', 360 419 'type' => 'js', 361 420 'requires' => 362 421 array( 363 - 0 => 'javelin-lib-dev', 422 + 0 => 'javelin-behavior', 423 + 1 => 'javelin-dom', 424 + 2 => 'javelin-util', 425 + 3 => 'javelin-request', 364 426 ), 365 427 'disk' => '/rsrc/js/application/diffusion/behavior-pull-lastmodified.js', 366 428 ), 367 429 'javelin-behavior-error-log' => 368 430 array( 369 - 'uri' => '/res/c57a323f/rsrc/js/application/core/behavior-error-log.js', 431 + 'uri' => '/res/ad4e82d4/rsrc/js/application/core/behavior-error-log.js', 370 432 'type' => 'js', 371 433 'requires' => 372 434 array( 373 - 0 => 'javelin-lib-dev', 435 + 0 => 'javelin-dom', 374 436 ), 375 437 'disk' => '/rsrc/js/application/core/behavior-error-log.js', 376 438 ), 377 439 'javelin-behavior-herald-rule-editor' => 378 440 array( 379 - 'uri' => '/res/48108130/rsrc/js/application/herald/herald-rule-editor.js', 441 + 'uri' => '/res/f18bcd5e/rsrc/js/application/herald/herald-rule-editor.js', 380 442 'type' => 'js', 381 443 'requires' => 382 444 array( 383 445 0 => 'herald-rule-editor', 384 - 1 => 'javelin-lib-dev', 446 + 1 => 'javelin-behavior', 385 447 ), 386 448 'disk' => '/rsrc/js/application/herald/herald-rule-editor.js', 387 449 ), 388 450 'javelin-behavior-maniphest-transaction-controls' => 389 451 array( 390 - 'uri' => '/res/fc6a8722/rsrc/js/application/maniphest/behavior-transaction-controls.js', 452 + 'uri' => '/res/f2eae88a/rsrc/js/application/maniphest/behavior-transaction-controls.js', 391 453 'type' => 'js', 392 454 'requires' => 393 455 array( 394 - 0 => 'javelin-lib-dev', 456 + 0 => 'javelin-behavior', 457 + 1 => 'javelin-dom', 458 + 2 => 'javelin-tokenizer', 459 + 3 => 'javelin-typeahead', 460 + 4 => 'javelin-typeahead-preloaded-source', 395 461 ), 396 462 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js', 397 463 ), 398 464 'javelin-behavior-owners-path-editor' => 399 465 array( 400 - 'uri' => '/res/7568aa22/rsrc/js/application/owners/owners-path-editor.js', 466 + 'uri' => '/res/b379a0d4/rsrc/js/application/owners/owners-path-editor.js', 401 467 'type' => 'js', 402 468 'requires' => 403 469 array( 404 470 0 => 'owners-path-editor', 405 - 1 => 'javelin-lib-dev', 471 + 1 => 'javelin-behavior', 406 472 ), 407 473 'disk' => '/rsrc/js/application/owners/owners-path-editor.js', 408 474 ), 409 475 'javelin-behavior-phabricator-object-selector' => 410 476 array( 411 - 'uri' => '/res/85d0769b/rsrc/js/application/core/behavior-object-selector.js', 477 + 'uri' => '/res/e899a55e/rsrc/js/application/core/behavior-object-selector.js', 412 478 'type' => 'js', 413 479 'requires' => 414 480 array( 415 - 0 => 'javelin-lib-dev', 481 + 0 => 'javelin-behavior', 482 + 1 => 'javelin-dom', 483 + 2 => 'javelin-request', 484 + 3 => 'javelin-util', 485 + 4 => 'javelin-stratcom', 416 486 ), 417 487 'disk' => '/rsrc/js/application/core/behavior-object-selector.js', 418 488 ), 419 489 'javelin-behavior-workflow' => 420 490 array( 421 - 'uri' => '/res/15446e7e/rsrc/js/application/core/behavior-workflow.js', 491 + 'uri' => '/res/b5bc59cb/rsrc/js/application/core/behavior-workflow.js', 422 492 'type' => 'js', 423 493 'requires' => 424 494 array( 425 - 0 => 'javelin-lib-dev', 495 + 0 => 'javelin-behavior', 496 + 1 => 'javelin-stratcom', 497 + 2 => 'javelin-workflow', 426 498 ), 427 499 'disk' => '/rsrc/js/application/core/behavior-workflow.js', 428 500 ), 429 - 'javelin-init-prod' => 501 + 'javelin-dom' => 430 502 array( 431 - 'uri' => '/res/1267c868/rsrc/js/javelin/init.min.js', 503 + 'uri' => '/res/21c1392d/rsrc/js/javelin/lib/DOM.js', 432 504 'type' => 'js', 433 505 'requires' => 434 506 array( 507 + 0 => 'javelin-install', 508 + 1 => 'javelin-util', 509 + 2 => 'javelin-vector', 510 + 3 => 'javelin-stratcom', 435 511 ), 436 - 'disk' => '/rsrc/js/javelin/init.min.js', 512 + 'disk' => '/rsrc/js/javelin/lib/DOM.js', 437 513 ), 438 - 'javelin-lib-dev' => 514 + 'javelin-event' => 439 515 array( 440 - 'uri' => '/res/a0e7a5e9/rsrc/js/javelin/javelin.dev.js', 516 + 'uri' => '/res/807b95e6/rsrc/js/javelin/core/Event.js', 441 517 'type' => 'js', 442 518 'requires' => 443 519 array( 520 + 0 => 'javelin-install', 444 521 ), 445 - 'disk' => '/rsrc/js/javelin/javelin.dev.js', 522 + 'disk' => '/rsrc/js/javelin/core/Event.js', 446 523 ), 447 - 'javelin-lib-prod' => 524 + 'javelin-install' => 448 525 array( 449 - 'uri' => '/res/2f2b3b2e/rsrc/js/javelin/javelin.min.js', 526 + 'uri' => '/res/c11fe5b3/rsrc/js/javelin/core/install.js', 527 + 'type' => 'js', 528 + 'requires' => 529 + array( 530 + 0 => 'javelin-util', 531 + ), 532 + 'disk' => '/rsrc/js/javelin/core/install.js', 533 + ), 534 + 'javelin-json' => 535 + array( 536 + 'uri' => '/res/62c8cc8d/rsrc/js/javelin/lib/JSON.js', 450 537 'type' => 'js', 451 538 'requires' => 452 539 array( 540 + 0 => 'javelin-install', 541 + 1 => 'javelin-util', 453 542 ), 454 - 'disk' => '/rsrc/js/javelin/javelin.min.js', 543 + 'disk' => '/rsrc/js/javelin/lib/JSON.js', 455 544 ), 456 545 'javelin-magical-init' => 457 546 array( 458 - 'uri' => '/res/76614f84/rsrc/js/javelin/init.dev.js', 547 + 'uri' => '/res/6d53e259/rsrc/js/javelin/core/init.js', 548 + 'type' => 'js', 549 + 'requires' => 550 + array( 551 + ), 552 + 'disk' => '/rsrc/js/javelin/core/init.js', 553 + ), 554 + 'javelin-mask' => 555 + array( 556 + 'uri' => '/res/ba2f665a/rsrc/js/javelin/lib/Mask.js', 557 + 'type' => 'js', 558 + 'requires' => 559 + array( 560 + 0 => 'javelin-install', 561 + 1 => 'javelin-vector', 562 + 2 => 'javelin-dom', 563 + ), 564 + 'disk' => '/rsrc/js/javelin/lib/Mask.js', 565 + ), 566 + 'javelin-request' => 567 + array( 568 + 'uri' => '/res/3947083d/rsrc/js/javelin/lib/Request.js', 569 + 'type' => 'js', 570 + 'requires' => 571 + array( 572 + 0 => 'javelin-install', 573 + 1 => 'javelin-stratcom', 574 + 2 => 'javelin-util', 575 + 3 => 'javelin-behavior', 576 + ), 577 + 'disk' => '/rsrc/js/javelin/lib/Request.js', 578 + ), 579 + 'javelin-stratcom' => 580 + array( 581 + 'uri' => '/res/3421b115/rsrc/js/javelin/core/Stratcom.js', 582 + 'type' => 'js', 583 + 'requires' => 584 + array( 585 + 0 => 'javelin-install', 586 + 1 => 'javelin-event', 587 + 2 => 'javelin-util', 588 + 3 => 'javelin-magical-init', 589 + ), 590 + 'disk' => '/rsrc/js/javelin/core/Stratcom.js', 591 + ), 592 + 'javelin-tokenizer' => 593 + array( 594 + 'uri' => '/res/74fe92c6/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js', 595 + 'type' => 'js', 596 + 'requires' => 597 + array( 598 + 0 => 'javelin-typeahead', 599 + 1 => 'javelin-dom', 600 + 2 => 'javelin-util', 601 + 3 => 'javelin-stratcom', 602 + 4 => 'javelin-vector', 603 + 5 => 'javelin-install', 604 + 6 => 'javelin-typeahead-preloaded-source', 605 + ), 606 + 'disk' => '/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js', 607 + ), 608 + 'javelin-typeahead' => 609 + array( 610 + 'uri' => '/res/5a701345/rsrc/js/javelin/lib/control/typeahead/Typeahead.js', 611 + 'type' => 'js', 612 + 'requires' => 613 + array( 614 + 0 => 'javelin-install', 615 + 1 => 'javelin-dom', 616 + 2 => 'javelin-vector', 617 + 3 => 'javelin-util', 618 + ), 619 + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/Typeahead.js', 620 + ), 621 + 'javelin-typeahead-normalizer' => 622 + array( 623 + 'uri' => '/res/8d49e2de/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', 624 + 'type' => 'js', 625 + 'requires' => 626 + array( 627 + 0 => 'javelin-install', 628 + ), 629 + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js', 630 + ), 631 + 'javelin-typeahead-ondemand-source' => 632 + array( 633 + 'uri' => '/res/00b46be8/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', 459 634 'type' => 'js', 460 635 'requires' => 461 636 array( 637 + 0 => 'javelin-install', 638 + 1 => 'javelin-util', 639 + 2 => 'javelin-stratcom', 640 + 3 => 'javelin-request', 641 + 4 => 'javelin-typeahead-source', 462 642 ), 463 - 'disk' => '/rsrc/js/javelin/init.dev.js', 643 + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js', 464 644 ), 465 - 'javelin-typeahead-dev' => 645 + 'javelin-typeahead-preloaded-source' => 466 646 array( 467 - 'uri' => '/res/6de6ae59/rsrc/js/javelin/typeahead.dev.js', 647 + 'uri' => '/res/aefaf410/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', 468 648 'type' => 'js', 469 649 'requires' => 470 650 array( 651 + 0 => 'javelin-install', 652 + 1 => 'javelin-util', 653 + 2 => 'javelin-stratcom', 654 + 3 => 'javelin-request', 655 + 4 => 'javelin-typeahead-source', 471 656 ), 472 - 'disk' => '/rsrc/js/javelin/typeahead.dev.js', 657 + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js', 473 658 ), 474 - 'javelin-typeahead-prod' => 659 + 'javelin-typeahead-source' => 475 660 array( 476 - 'uri' => '/res/69d5fad1/rsrc/js/javelin/typeahead.min.js', 661 + 'uri' => '/res/b1184e7d/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js', 477 662 'type' => 'js', 478 663 'requires' => 479 664 array( 665 + 0 => 'javelin-install', 666 + 1 => 'javelin-util', 667 + 2 => 'javelin-dom', 668 + 3 => 'javelin-typeahead-normalizer', 480 669 ), 481 - 'disk' => '/rsrc/js/javelin/typeahead.min.js', 670 + 'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js', 671 + ), 672 + 'javelin-uri' => 673 + array( 674 + 'uri' => '/res/03448af9/rsrc/js/javelin/lib/URI.js', 675 + 'type' => 'js', 676 + 'requires' => 677 + array( 678 + 0 => 'javelin-install', 679 + 1 => 'javelin-util', 680 + ), 681 + 'disk' => '/rsrc/js/javelin/lib/URI.js', 682 + ), 683 + 'javelin-util' => 684 + array( 685 + 'uri' => '/res/031851eb/rsrc/js/javelin/core/util.js', 686 + 'type' => 'js', 687 + 'requires' => 688 + array( 689 + 0 => 'javelin-magical-init', 690 + ), 691 + 'disk' => '/rsrc/js/javelin/core/util.js', 482 692 ), 483 - 'javelin-workflow-dev' => 693 + 'javelin-vector' => 484 694 array( 485 - 'uri' => '/res/c6b17f93/rsrc/js/javelin/workflow.dev.js', 695 + 'uri' => '/res/184e9d71/rsrc/js/javelin/lib/Vector.js', 486 696 'type' => 'js', 487 697 'requires' => 488 698 array( 699 + 0 => 'javelin-install', 700 + 1 => 'javelin-event', 489 701 ), 490 - 'disk' => '/rsrc/js/javelin/workflow.dev.js', 702 + 'disk' => '/rsrc/js/javelin/lib/Vector.js', 491 703 ), 492 - 'javelin-workflow-prod' => 704 + 'javelin-workflow' => 493 705 array( 494 - 'uri' => '/res/b758e0a0/rsrc/js/javelin/workflow.min.js', 706 + 'uri' => '/res/24389bc8/rsrc/js/javelin/lib/Workflow.js', 495 707 'type' => 'js', 496 708 'requires' => 497 709 array( 710 + 0 => 'javelin-stratcom', 711 + 1 => 'javelin-request', 712 + 2 => 'javelin-dom', 713 + 3 => 'javelin-vector', 714 + 4 => 'javelin-install', 715 + 5 => 'javelin-util', 716 + 6 => 'javelin-mask', 717 + 7 => 'javelin-uri', 498 718 ), 499 - 'disk' => '/rsrc/js/javelin/workflow.min.js', 719 + 'disk' => '/rsrc/js/javelin/lib/Workflow.js', 500 720 ), 501 721 'mainphest-task-detail-css' => 502 722 array( ··· 527 747 ), 528 748 'multirow-row-manager' => 529 749 array( 530 - 'uri' => '/res/330d076b/rsrc/js/application/core/MultirowRowManager.js', 750 + 'uri' => '/res/cae26c67/rsrc/js/application/core/MultirowRowManager.js', 531 751 'type' => 'js', 532 752 'requires' => 533 753 array( 534 - 0 => 'javelin-lib-dev', 754 + 0 => 'javelin-install', 755 + 1 => 'javelin-stratcom', 756 + 2 => 'javelin-dom', 757 + 3 => 'javelin-util', 535 758 ), 536 759 'disk' => '/rsrc/js/application/core/MultirowRowManager.js', 537 760 ), 538 761 'owners-path-editor' => 539 762 array( 540 - 'uri' => '/res/b01c1ca9/rsrc/js/application/owners/OwnersPathEditor.js', 763 + 'uri' => '/res/003f3d3f/rsrc/js/application/owners/OwnersPathEditor.js', 541 764 'type' => 'js', 542 765 'requires' => 543 766 array( 544 767 0 => 'multirow-row-manager', 545 - 1 => 'javelin-lib-dev', 546 - 2 => 'javelin-typeahead-dev', 547 - 3 => 'path-typeahead', 768 + 1 => 'javelin-install', 769 + 2 => 'path-typeahead', 770 + 3 => 'javelin-dom', 771 + 4 => 'javelin-util', 548 772 ), 549 773 'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js', 550 774 ), ··· 559 783 ), 560 784 'path-typeahead' => 561 785 array( 562 - 'uri' => '/res/42fb76c3/rsrc/js/application/herald/PathTypeahead.js', 786 + 'uri' => '/res/594d2576/rsrc/js/application/herald/PathTypeahead.js', 563 787 'type' => 'js', 564 788 'requires' => 565 789 array( 566 - 0 => 'javelin-lib-dev', 567 - 1 => 'javelin-typeahead-dev', 790 + 0 => 'javelin-install', 791 + 1 => 'javelin-typeahead', 792 + 2 => 'javelin-dom', 793 + 3 => 'javelin-request', 794 + 4 => 'javelin-typeahead-ondemand-source', 795 + 5 => 'javelin-util', 568 796 ), 569 797 'disk' => '/rsrc/js/application/herald/PathTypeahead.js', 570 798 ), ··· 653 881 ), array ( 654 882 'packages' => 655 883 array ( 656 - '4270730a' => 657 - array ( 658 - 'name' => 'core.pkg.css', 659 - 'symbols' => 660 - array ( 661 - 0 => 'phabricator-core-css', 662 - 1 => 'phabricator-core-buttons-css', 663 - 2 => 'phabricator-standard-page-view', 664 - 3 => 'aphront-dialog-view-css', 665 - 4 => 'aphront-form-view-css', 666 - 5 => 'aphront-panel-view-css', 667 - 6 => 'aphront-side-nav-view-css', 668 - 7 => 'aphront-table-view-css', 669 - 8 => 'aphront-crumbs-view-css', 670 - 9 => 'aphront-tokenizer-control-css', 671 - 10 => 'aphront-typeahead-control-css', 672 - 11 => 'phabricator-directory-css', 673 - 12 => 'phabricator-remarkup-css', 674 - 13 => 'syntax-highlighting-css', 675 - ), 676 - 'uri' => '/res/pkg/4270730a/core.pkg.css', 677 - 'type' => 'css', 678 - ), 679 - '6c786373' => 884 + '3b698834' => 680 885 array ( 681 886 'name' => 'differential.pkg.js', 682 887 'symbols' => ··· 687 892 3 => 'javelin-behavior-differential-show-more', 688 893 4 => 'javelin-behavior-differential-diff-radios', 689 894 ), 690 - 'uri' => '/res/pkg/6c786373/differential.pkg.js', 895 + 'uri' => '/res/pkg/3b698834/differential.pkg.js', 896 + 'type' => 'js', 897 + ), 898 + '71a78877' => 899 + array ( 900 + 'name' => 'workflow.pkg.js', 901 + 'symbols' => 902 + array ( 903 + 0 => 'javelin-mask', 904 + 1 => 'javelin-workflow', 905 + 2 => 'javelin-behavior-workflow', 906 + ), 907 + 'uri' => '/res/pkg/71a78877/workflow.pkg.js', 691 908 'type' => 'js', 692 909 ), 693 910 '8e4ef51b' => ··· 707 924 'uri' => '/res/pkg/8e4ef51b/differential.pkg.css', 708 925 'type' => 'css', 709 926 ), 927 + 'a44a7841' => 928 + array ( 929 + 'name' => 'typeahead.pkg.js', 930 + 'symbols' => 931 + array ( 932 + 0 => 'javelin-typeahead', 933 + 1 => 'javelin-typeahead-normalizer', 934 + 2 => 'javelin-typeahead-source', 935 + 3 => 'javelin-typeahead-preloaded-source', 936 + 4 => 'javelin-typeahead-ondemand-source', 937 + 5 => 'javelin-tokenizer', 938 + 6 => 'javelin-behavior-aphront-basic-tokenizer', 939 + ), 940 + 'uri' => '/res/pkg/a44a7841/typeahead.pkg.js', 941 + 'type' => 'js', 942 + ), 943 + 'c4276ad7' => 944 + array ( 945 + 'name' => 'core.pkg.css', 946 + 'symbols' => 947 + array ( 948 + 0 => 'phabricator-core-css', 949 + 1 => 'phabricator-core-buttons-css', 950 + 2 => 'phabricator-standard-page-view', 951 + 3 => 'aphront-dialog-view-css', 952 + 4 => 'aphront-form-view-css', 953 + 5 => 'aphront-panel-view-css', 954 + 6 => 'aphront-side-nav-view-css', 955 + 7 => 'aphront-table-view-css', 956 + 8 => 'aphront-crumbs-view-css', 957 + 9 => 'aphront-tokenizer-control-css', 958 + 10 => 'aphront-typeahead-control-css', 959 + 11 => 'aphront-list-filter-view-css', 960 + 12 => 'phabricator-directory-css', 961 + 13 => 'phabricator-remarkup-css', 962 + 14 => 'syntax-highlighting-css', 963 + ), 964 + 'uri' => '/res/pkg/c4276ad7/core.pkg.css', 965 + 'type' => 'css', 966 + ), 710 967 'eadf6ec3' => 711 968 array ( 712 969 'name' => 'diffusion.pkg.css', ··· 717 974 'uri' => '/res/pkg/eadf6ec3/diffusion.pkg.css', 718 975 'type' => 'css', 719 976 ), 977 + 'fc6ed8bc' => 978 + array ( 979 + 'name' => 'javelin.pkg.js', 980 + 'symbols' => 981 + array ( 982 + 0 => 'javelin-util', 983 + 1 => 'javelin-install', 984 + 2 => 'javelin-event', 985 + 3 => 'javelin-stratcom', 986 + 4 => 'javelin-behavior', 987 + 5 => 'javelin-request', 988 + 6 => 'javelin-vector', 989 + 7 => 'javelin-dom', 990 + 8 => 'javelin-json', 991 + 9 => 'javelin-uri', 992 + ), 993 + 'uri' => '/res/pkg/fc6ed8bc/javelin.pkg.js', 994 + 'type' => 'js', 995 + ), 720 996 ), 721 997 'reverse' => 722 998 array ( 723 - 'aphront-crumbs-view-css' => '4270730a', 724 - 'aphront-dialog-view-css' => '4270730a', 725 - 'aphront-form-view-css' => '4270730a', 726 - 'aphront-panel-view-css' => '4270730a', 727 - 'aphront-side-nav-view-css' => '4270730a', 728 - 'aphront-table-view-css' => '4270730a', 729 - 'aphront-tokenizer-control-css' => '4270730a', 730 - 'aphront-typeahead-control-css' => '4270730a', 999 + 'aphront-crumbs-view-css' => 'c4276ad7', 1000 + 'aphront-dialog-view-css' => 'c4276ad7', 1001 + 'aphront-form-view-css' => 'c4276ad7', 1002 + 'aphront-list-filter-view-css' => 'c4276ad7', 1003 + 'aphront-panel-view-css' => 'c4276ad7', 1004 + 'aphront-side-nav-view-css' => 'c4276ad7', 1005 + 'aphront-table-view-css' => 'c4276ad7', 1006 + 'aphront-tokenizer-control-css' => 'c4276ad7', 1007 + 'aphront-typeahead-control-css' => 'c4276ad7', 731 1008 'differential-changeset-view-css' => '8e4ef51b', 732 1009 'differential-core-view-css' => '8e4ef51b', 733 1010 'differential-revision-add-comment-css' => '8e4ef51b', ··· 737 1014 'differential-revision-history-css' => '8e4ef51b', 738 1015 'differential-table-of-contents-css' => '8e4ef51b', 739 1016 'diffusion-commit-view-css' => 'eadf6ec3', 740 - 'javelin-behavior-differential-diff-radios' => '6c786373', 741 - 'javelin-behavior-differential-edit-inline-comments' => '6c786373', 742 - 'javelin-behavior-differential-feedback-preview' => '6c786373', 743 - 'javelin-behavior-differential-populate' => '6c786373', 744 - 'javelin-behavior-differential-show-more' => '6c786373', 745 - 'phabricator-core-buttons-css' => '4270730a', 746 - 'phabricator-core-css' => '4270730a', 747 - 'phabricator-directory-css' => '4270730a', 748 - 'phabricator-remarkup-css' => '4270730a', 749 - 'phabricator-standard-page-view' => '4270730a', 750 - 'syntax-highlighting-css' => '4270730a', 1017 + 'javelin-behavior' => 'fc6ed8bc', 1018 + 'javelin-behavior-aphront-basic-tokenizer' => 'a44a7841', 1019 + 'javelin-behavior-differential-diff-radios' => '3b698834', 1020 + 'javelin-behavior-differential-edit-inline-comments' => '3b698834', 1021 + 'javelin-behavior-differential-feedback-preview' => '3b698834', 1022 + 'javelin-behavior-differential-populate' => '3b698834', 1023 + 'javelin-behavior-differential-show-more' => '3b698834', 1024 + 'javelin-behavior-workflow' => '71a78877', 1025 + 'javelin-dom' => 'fc6ed8bc', 1026 + 'javelin-event' => 'fc6ed8bc', 1027 + 'javelin-install' => 'fc6ed8bc', 1028 + 'javelin-json' => 'fc6ed8bc', 1029 + 'javelin-mask' => '71a78877', 1030 + 'javelin-request' => 'fc6ed8bc', 1031 + 'javelin-stratcom' => 'fc6ed8bc', 1032 + 'javelin-tokenizer' => 'a44a7841', 1033 + 'javelin-typeahead' => 'a44a7841', 1034 + 'javelin-typeahead-normalizer' => 'a44a7841', 1035 + 'javelin-typeahead-ondemand-source' => 'a44a7841', 1036 + 'javelin-typeahead-preloaded-source' => 'a44a7841', 1037 + 'javelin-typeahead-source' => 'a44a7841', 1038 + 'javelin-uri' => 'fc6ed8bc', 1039 + 'javelin-util' => 'fc6ed8bc', 1040 + 'javelin-vector' => 'fc6ed8bc', 1041 + 'javelin-workflow' => '71a78877', 1042 + 'phabricator-core-buttons-css' => 'c4276ad7', 1043 + 'phabricator-core-css' => 'c4276ad7', 1044 + 'phabricator-directory-css' => 'c4276ad7', 1045 + 'phabricator-remarkup-css' => 'c4276ad7', 1046 + 'phabricator-standard-page-view' => 'c4276ad7', 1047 + 'syntax-highlighting-css' => 'c4276ad7', 751 1048 ), 752 1049 ));
+4
src/__phutil_library_map__.php
··· 318 318 'PhabricatorFileViewController' => 'applications/files/controller/view', 319 319 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 320 320 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 321 + 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin', 322 + 'PhabricatorLintEngine' => 'infrastructure/lint/engine', 321 323 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 322 324 'PhabricatorLoginController' => 'applications/auth/controller/login', 323 325 'PhabricatorLogoutController' => 'applications/auth/controller/logout', ··· 731 733 'PhabricatorFileUploadController' => 'PhabricatorFileController', 732 734 'PhabricatorFileViewController' => 'PhabricatorFileController', 733 735 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 736 + 'PhabricatorJavelinLinter' => 'ArcanistLinter', 737 + 'PhabricatorLintEngine' => 'PhutilLintEngine', 734 738 'PhabricatorLiskDAO' => 'LiskDAO', 735 739 'PhabricatorLoginController' => 'PhabricatorAuthController', 736 740 'PhabricatorLogoutController' => 'PhabricatorAuthController',
+16
src/infrastructure/celerity/map/CelerityResourceMap.php
··· 21 21 private static $instance; 22 22 private $resourceMap; 23 23 private $packageMap; 24 + private $reverseMap; 24 25 25 26 public static function getInstance() { 26 27 if (empty(self::$instance)) { ··· 105 106 } 106 107 107 108 return $paths; 109 + } 110 + 111 + public function lookupSymbolInformation($symbol) { 112 + return idx($this->resourceMap, $symbol); 113 + } 114 + 115 + public function lookupFileInformation($path) { 116 + if (empty($this->reverseMap)) { 117 + $this->reverseMap = array(); 118 + foreach ($this->resourceMap as $symbol => $data) { 119 + $data['provides'] = $symbol; 120 + $this->reverseMap[$data['disk']] = $data; 121 + } 122 + } 123 + return idx($this->reverseMap, $path); 108 124 } 109 125 110 126 }
+25 -2
src/infrastructure/celerity/response/CelerityStaticResourceResponse.php
··· 25 25 private $metadata = array(); 26 26 private $metadataBlock = 0; 27 27 private $behaviors = array(); 28 + private $hasRendered = array(); 28 29 29 30 public function __construct() { 30 31 if (isset($_REQUEST['__metablock__'])) { ··· 64 65 return $this; 65 66 } 66 67 68 + public function renderSingleResource($symbol) { 69 + $map = CelerityResourceMap::getInstance(); 70 + $resolved = $map->resolveResources(array($symbol)); 71 + $packaged = $map->packageResources($resolved); 72 + return $this->renderPackagedResources($packaged); 73 + } 74 + 67 75 public function renderResourcesOfType($type) { 68 76 $this->resolveResources(); 69 - $output = array(); 77 + 78 + $resources = array(); 70 79 foreach ($this->packaged as $resource) { 71 80 if ($resource['type'] == $type) { 72 - $output[] = $this->renderResource($resource); 81 + $resources[] = $resource; 73 82 } 83 + } 84 + 85 + return $this->renderPackagedResources($resources); 86 + } 87 + 88 + private function renderPackagedResources(array $resources) { 89 + $output = array(); 90 + foreach ($resources as $resource) { 91 + if (isset($this->hasRendered[$resource['uri']])) { 92 + continue; 93 + } 94 + $this->hasRendered[$resource['uri']] = true; 95 + 96 + $output[] = $this->renderResource($resource); 74 97 } 75 98 return implode("\n", $output); 76 99 }
+45
src/infrastructure/lint/engine/PhabricatorLintEngine.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class PhabricatorLintEngine extends PhutilLintEngine { 20 + 21 + public function buildLinters() { 22 + $linters = parent::buildLinters(); 23 + 24 + $paths = $this->getPaths(); 25 + 26 + foreach ($paths as $key => $path) { 27 + if (!$this->pathExists($path)) { 28 + unset($paths[$key]); 29 + } 30 + } 31 + 32 + $javelin_linter = new PhabricatorJavelinLinter(); 33 + $linters[] = $javelin_linter; 34 + 35 + foreach ($paths as $path) { 36 + if (preg_match('/\.js$/', $path)) { 37 + $javelin_linter->addPath($path); 38 + $javelin_linter->addData($path, $this->loadData($path)); 39 + } 40 + } 41 + 42 + return $linters; 43 + } 44 + 45 + }
+14
src/infrastructure/lint/engine/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('arcanist', 'lint/engine/phutil'); 10 + 11 + phutil_require_module('phabricator', 'infrastructure/lint/linter/javelin'); 12 + 13 + 14 + phutil_require_source('PhabricatorLintEngine.php');
+207
src/infrastructure/lint/linter/javelin/PhabricatorJavelinLinter.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class PhabricatorJavelinLinter extends ArcanistLinter { 20 + 21 + private $symbols = array(); 22 + 23 + const LINT_PRIVATE_ACCESS = 1; 24 + const LINT_MISSING_DEPENDENCY = 2; 25 + const LINT_UNNECESSARY_DEPENDENCY = 3; 26 + const LINT_UNKNOWN_DEPENDENCY = 4; 27 + 28 + public function willLintPaths(array $paths) { 29 + $futures = array(); 30 + foreach ($paths as $path) { 31 + $future = $this->newSymbolsFuture($path); 32 + $futures[$path] = $future; 33 + } 34 + 35 + foreach (Futures($futures)->limit(8) as $path => $future) { 36 + $this->symbols[$path] = $future->resolvex(); 37 + } 38 + } 39 + 40 + public function getLinterName() { 41 + return 'JAVELIN'; 42 + } 43 + 44 + public function getLintSeverityMap() { 45 + return array(); 46 + } 47 + 48 + public function getLintNameMap() { 49 + return array( 50 + self::LINT_PRIVATE_ACCESS => 'Private Method/Member Access', 51 + self::LINT_MISSING_DEPENDENCY => 'Missing Javelin Dependency', 52 + self::LINT_UNNECESSARY_DEPENDENCY => 'Unnecessary Javelin Dependency', 53 + self::LINT_UNKNOWN_DEPENDENCY => 'Unknown Javelin Dependency', 54 + ); 55 + } 56 + 57 + public function lintPath($path) { 58 + 59 + list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); 60 + foreach ($uses as $symbol => $line) { 61 + $parts = explode('.', $symbol); 62 + foreach ($parts as $part) { 63 + if ($part[0] == '_' && $part[1] != '_') { 64 + $base = implode('.', array_slice($parts, 0, 2)); 65 + if (!array_key_exists($base, $installs)) { 66 + $this->raiseLintAtLine( 67 + $line, 68 + 0, 69 + self::LINT_PRIVATE_ACCESS, 70 + "This file accesses private symbol '{$symbol}' across file ". 71 + "boundaries. You may only access private members and methods ". 72 + "from the file where they are defined."); 73 + } 74 + break; 75 + } 76 + } 77 + } 78 + 79 + if ($this->getEngine()->getCommitHookMode()) { 80 + // Don't do the dependency checks in commit-hook mode because we won't 81 + // have an available working copy. 82 + return; 83 + } 84 + 85 + $external_classes = array(); 86 + foreach ($uses as $symbol => $line) { 87 + $parts = explode('.', $symbol); 88 + $class = implode('.', array_slice($parts, 0, 2)); 89 + if (!array_key_exists($class, $external_classes) && 90 + !array_key_exists($class, $installs)) { 91 + $external_classes[$class] = $line; 92 + } 93 + } 94 + 95 + $celerity = CelerityResourceMap::getInstance(); 96 + 97 + $info = $celerity->lookupFileInformation(substr($path, strlen('webroot'))); 98 + 99 + $need = $external_classes; 100 + 101 + $requires = $info['requires']; 102 + 103 + foreach ($requires as $key => $name) { 104 + $symbol_info = $celerity->lookupSymbolInformation($name); 105 + if (!$symbol_info) { 106 + $this->raiseLintAtLine( 107 + 0, 108 + 0, 109 + self::LINT_UNKNOWN_DEPENDENCY, 110 + "This file @requires component '{$name}', but it does not ". 111 + "exist. You may need to rebuild the Celerity map."); 112 + unset($requires[$key]); 113 + continue; 114 + } 115 + 116 + $symbol_path = 'webroot'.$symbol_info['disk']; 117 + list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath( 118 + $symbol_path); 119 + if (array_intersect_key($req_install, $external_classes)) { 120 + $need = array_diff_key($need, $req_install); 121 + unset($requires[$key]); 122 + } 123 + } 124 + 125 + foreach ($need as $class => $line) { 126 + $this->raiseLintAtLine( 127 + $line, 128 + 0, 129 + self::LINT_MISSING_DEPENDENCY, 130 + "This file uses '{$class}' but does not @requires the component ". 131 + "which installs it. You may need to rebuild the Celerity map."); 132 + } 133 + 134 + foreach ($requires as $component) { 135 + $this->raiseLintAtLine( 136 + $line, 137 + 0, 138 + self::LINT_UNNECESSARY_DEPENDENCY, 139 + "This file @requires component '{$component}' but does not use ". 140 + "anything it provides."); 141 + } 142 + } 143 + 144 + private function loadSymbols($path) { 145 + if (empty($this->symbols[$path])) { 146 + $this->symbols[$path] = $this->newSymbolsFuture($path)->resolvex(); 147 + } 148 + return $this->symbols[$path]; 149 + } 150 + 151 + private function newSymbolsFuture($path) { 152 + $root = dirname(phutil_get_library_root('phabricator')); 153 + 154 + $support = $root.'/externals/javelin/support'; 155 + $javelinsymbols = $support.'/javelinsymbols/javelinsymbols'; 156 + 157 + $future = new ExecFuture($javelinsymbols.' # '.escapeshellarg($path)); 158 + $future->write($this->getData($path)); 159 + return $future; 160 + } 161 + 162 + private function getUsedAndInstalledSymbolsForPath($path) { 163 + list($symbols) = $this->loadSymbols($path); 164 + 165 + $symbols = explode("\n", trim($symbols)); 166 + 167 + $uses = array(); 168 + $installs = array(); 169 + foreach ($symbols as $line) { 170 + $matches = null; 171 + if (!preg_match('/^([?+])([^:]*):(\d+)$/', $line, $matches)) { 172 + throw new Exception( 173 + "Received malformed output from `javelinsymbols`."); 174 + } 175 + $type = $matches[1]; 176 + $symbol = $matches[2]; 177 + $line = $matches[3]; 178 + 179 + switch ($type) { 180 + case '?': 181 + $uses[$symbol] = $line; 182 + break; 183 + case '+': 184 + $installs['JX.'.$symbol] = $line; 185 + break; 186 + } 187 + } 188 + 189 + $contents = $this->getData($path); 190 + 191 + $matches = null; 192 + $count = preg_match_all( 193 + '/@javelin-installs\W+(\S+)/', 194 + $contents, 195 + $matches, 196 + PREG_PATTERN_ORDER); 197 + 198 + if ($count) { 199 + foreach ($matches[1] as $symbol) { 200 + $installs[$symbol] = 0; 201 + } 202 + } 203 + 204 + return array($uses, $installs); 205 + } 206 + 207 + }
+18
src/infrastructure/lint/linter/javelin/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('arcanist', 'lint/linter/base'); 10 + 11 + phutil_require_module('phabricator', 'infrastructure/celerity/map'); 12 + 13 + phutil_require_module('phutil', 'future'); 14 + phutil_require_module('phutil', 'future/exec'); 15 + phutil_require_module('phutil', 'moduleutils'); 16 + 17 + 18 + phutil_require_source('PhabricatorJavelinLinter.php');
+1 -1
src/view/control/tokenizer/AphrontTokenizerTemplateView.php
··· 63 63 'mustcapture' => true, 64 64 'name' => $name, 65 65 'class' => 'jx-tokenizer-input', 66 - 'sigil' => 'tokenizer', 66 + 'sigil' => 'tokenizer-input', 67 67 'style' => 'width: 0px;', 68 68 'disabled' => 'disabled', 69 69 'type' => 'text',
-14
src/view/form/control/tokenizer/AphrontFormTokenizerControl.php
··· 42 42 } 43 43 44 44 protected function renderInput() { 45 - require_celerity_resource('javelin-typeahead-dev'); 46 - 47 45 $name = $this->getName(); 48 46 $values = nonempty($this->getValue(), array()); 49 - 50 - $input = javelin_render_tag( 51 - 'input', 52 - array( 53 - 'mustcapture' => true, 54 - 'name' => $name, 55 - 'class' => 'jx-tokenizer-input', 56 - 'sigil' => 'tokenizer', 57 - 'style' => 'width: 0px;', 58 - 'disabled' => 'disabled', 59 - 'type' => 'text', 60 - )); 61 47 62 48 if ($this->getID()) { 63 49 $id = $this->getID();
-1
src/view/form/control/tokenizer/__init__.php
··· 8 8 9 9 phutil_require_module('phabricator', 'infrastructure/celerity/api'); 10 10 phutil_require_module('phabricator', 'infrastructure/javelin/api'); 11 - phutil_require_module('phabricator', 'infrastructure/javelin/markup'); 12 11 phutil_require_module('phabricator', 'view/control/tokenizer'); 13 12 phutil_require_module('phabricator', 'view/form/control/base'); 14 13
+1 -5
src/view/page/standard/PhabricatorStandardPageView.php
··· 89 89 require_celerity_resource('phabricator-core-buttons-css'); 90 90 require_celerity_resource('phabricator-standard-page-view'); 91 91 92 - require_celerity_resource('javelin-lib-dev'); 93 - require_celerity_resource('javelin-workflow-dev'); 94 - 95 92 Javelin::initBehavior('workflow', array()); 96 93 97 94 if ($console) { ··· 118 115 'window.__DEV__=1;'. 119 116 '</script>'. 120 117 $response->renderResourcesOfType('css'). 121 - '<script type="text/javascript" src="/rsrc/js/javelin/init.dev.js">'. 122 - '</script>'; 118 + $response->renderSingleResource('javelin-magical-init'); 123 119 124 120 $request = $this->getRequest(); 125 121 if ($request) {
+4 -1
webroot/rsrc/js/application/core/MultirowRowManager.js
··· 1 1 /** 2 - * @requires javelin-lib-dev 2 + * @requires javelin-install 3 + * javelin-stratcom 4 + * javelin-dom 5 + * javelin-util 3 6 * @provides multirow-row-manager 4 7 * @javelin 5 8 */
+5
webroot/rsrc/js/application/core/behavior-dark-console.js
··· 1 1 /** 2 2 * @provides javelin-behavior-dark-console 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-util 6 + * javelin-dom 7 + * javelin-request 3 8 */ 4 9 5 10 JX.behavior('dark-console', function(config) {
+1 -1
webroot/rsrc/js/application/core/behavior-error-log.js
··· 1 1 /** 2 2 * @provides javelin-behavior-error-log 3 - * @requires javelin-lib-dev 3 + * @requires javelin-dom 4 4 */ 5 5 6 6 var current_details = null;
+5 -1
webroot/rsrc/js/application/core/behavior-object-selector.js
··· 1 1 /** 2 2 * @provides javelin-behavior-phabricator-object-selector 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-request 6 + * javelin-util 7 + * javelin-stratcom 4 8 */ 5 9 6 10 JX.behavior('phabricator-object-selector', function(config) {
+6 -2
webroot/rsrc/js/application/core/behavior-tokenizer.js
··· 1 1 /** 2 2 * @provides javelin-behavior-aphront-basic-tokenizer 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-typeahead 5 + * javelin-tokenizer 6 + * javelin-typeahead-preloaded-source 7 + * javelin-dom 4 8 */ 5 9 6 10 JX.behavior('aphront-basic-tokenizer', function(config) { ··· 10 14 11 15 var typeahead = new JX.Typeahead( 12 16 root, 13 - JX.DOM.find(root, 'input', 'tokenizer')); 17 + JX.DOM.find(root, 'input', 'tokenizer-input')); 14 18 typeahead.setDatasource(datasource); 15 19 16 20 var tokenizer = new JX.Tokenizer(root);
+3 -1
webroot/rsrc/js/application/core/behavior-workflow.js
··· 1 1 /** 2 2 * @provides javelin-behavior-workflow 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-workflow 4 6 */ 5 7 6 8 JX.behavior('workflow', function() {
+5 -1
webroot/rsrc/js/application/differential/behavior-add-reviewers.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-add-reviewers 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-tokenizer 6 + * javelin-typeahead 7 + * javelin-typeahead-preloaded-source 4 8 */ 5 9 6 10 JX.behavior('differential-add-reviewers', function(config) {
+7 -3
webroot/rsrc/js/application/differential/behavior-comment-preview.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-feedback-preview 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 6 + * javelin-request 7 + * javelin-util 4 8 */ 5 9 6 10 JX.behavior('differential-feedback-preview', function(config) { ··· 28 32 cval = content.value; 29 33 30 34 request = new JX.Request(config.uri, function(r) { 31 - preview && JX.DOM.setContent(preview, JX.HTML(r)); 35 + preview && JX.DOM.setContent(preview, JX.$H(r)); 32 36 min = new Date().getTime() + 500; 33 37 defer && defer.stop(); 34 38 defer = JX.defer(check, 500); ··· 51 55 52 56 function refreshInlinePreview() { 53 57 new JX.Request(config.inlineuri, function(r) { 54 - JX.DOM.setContent(JX.$(config.inline), JX.HTML(r)); 58 + JX.DOM.setContent(JX.$(config.inline), JX.$H(r)); 55 59 }) 56 60 .setTimeout(5000) 57 61 .send();
+3 -1
webroot/rsrc/js/application/differential/behavior-diff-radios.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-diff-radios 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 4 6 */ 5 7 6 8 JX.behavior('differential-diff-radios', function(config) {
+10 -6
webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-edit-inline-comments 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 6 + * javelin-workflow 7 + * javelin-vector 4 8 */ 5 9 6 10 JX.behavior('differential-edit-inline-comments', function(config) { ··· 27 31 } 28 32 var code = target.nextSibling; 29 33 30 - var pos = JX.$V(top).add(1 + JX.$V.getDim(target).x, 0); 31 - var dim = JX.$V.getDim(code).add(-4, 0); 32 - dim.y = (JX.$V(bot).y - pos.y) + JX.$V.getDim(bot).y; 34 + var pos = JX.$V(top).add(1 + JX.Vector.getDim(target).x, 0); 35 + var dim = JX.Vector.getDim(code).add(-4, 0); 36 + dim.y = (JX.$V(bot).y - pos.y) + JX.Vector.getDim(bot).y; 33 37 34 38 pos.setPos(reticle); 35 39 dim.setDim(reticle); ··· 48 52 } 49 53 50 54 function drawInlineComment(table, anchor, r) { 51 - copyRows(table, JX.$N('div', JX.HTML(r.markup)), anchor); 55 + copyRows(table, JX.$N('div', JX.$H(r.markup)), anchor); 52 56 finishSelect(); 53 57 } 54 58 ··· 245 249 var data = { 246 250 op: e.getNode('differential-inline-edit') ? 'edit' : 'delete', 247 251 id: e.getNodeData('differential-inline-comment').id, 248 - on_right: e.getNodeData('differential-inline-comment').on_right, 252 + on_right: e.getNodeData('differential-inline-comment').on_right 249 253 }; 250 254 new JX.Workflow(config.uri, data) 251 255 .setHandler(function(r) {
+5 -2
webroot/rsrc/js/application/differential/behavior-populate.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-populate 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-request 5 + * javelin-util 6 + * javelin-dom 4 7 */ 5 8 6 9 JX.behavior('differential-populate', function(config) { 7 10 8 11 function onresponse(target, response) { 9 - JX.DOM.replace(JX.$(target), JX.HTML(response)); 12 + JX.DOM.replace(JX.$(target), JX.$H(response)); 10 13 } 11 14 12 15 for (var k in config.registry) {
+4 -2
webroot/rsrc/js/application/differential/behavior-show-all-comments.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-show-all-comments 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-dom 4 6 */ 5 7 6 8 JX.behavior('differential-show-all-comments', function(config) { ··· 11 13 function(e) { 12 14 JX.DOM.setContent( 13 15 e.getNode('differential-all-comments-container'), 14 - JX.HTML(config.markup)); 16 + JX.$H(config.markup)); 15 17 e.kill(); 16 18 }); 17 19
+6 -2
webroot/rsrc/js/application/differential/behavior-show-more.js
··· 1 1 /** 2 2 * @provides javelin-behavior-differential-show-more 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-request 6 + * javelin-util 7 + * javelin-stratcom 4 8 */ 5 9 6 10 JX.behavior('differential-show-more', function(config) { 7 11 8 12 function onresponse(origin, response) { 9 - var div = JX.$N('div', {}, JX.HTML(response)); 13 + var div = JX.$N('div', {}, JX.$H(response)); 10 14 var anchor = origin.getNode('context-target'); 11 15 var root = anchor.parentNode; 12 16 copyRows(root, div, anchor);
+4 -1
webroot/rsrc/js/application/diffusion/behavior-jump-to.js
··· 1 1 /** 2 2 * @provides javelin-behavior-diffusion-jump-to 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-util 5 + * javelin-vector 6 + * javelin-dom 4 7 */ 5 8 6 9 JX.behavior('diffusion-jump-to', function(config) {
+5 -2
webroot/rsrc/js/application/diffusion/behavior-pull-lastmodified.js
··· 1 1 /** 2 2 * @provides javelin-behavior-diffusion-pull-lastmodified 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-util 6 + * javelin-request 4 7 */ 5 8 6 9 JX.behavior('diffusion-pull-lastmodified', function(config) { ··· 9 12 new JX.Request(uri, JX.bind(config[uri], function(r) { 10 13 for (var k in r) { 11 14 if (this[k]) { 12 - JX.DOM.setContent(JX.$(this[k]), JX.HTML(r[k])); 15 + JX.DOM.setContent(JX.$(this[k]), JX.$H(r[k])); 13 16 } 14 17 } 15 18 })).send();
+9 -4
webroot/rsrc/js/application/herald/HeraldRuleEditor.js
··· 1 1 /** 2 2 * @requires multirow-row-manager 3 - * javelin-lib-dev 4 - * javelin-typeahead-dev 5 - * path-typeahead 3 + * javelin-install 4 + * javelin-typeahead 5 + * javelin-util 6 + * javelin-dom 7 + * javelin-tokenizer 8 + * javelin-typeahead-preloaded-source 9 + * javelin-stratcom 10 + * javelin-json 6 11 * @provides herald-rule-editor 7 12 * @javelin 8 13 */ ··· 257 262 _newTokenizer : function(type) { 258 263 var template = JX.$N( 259 264 'div', 260 - new JX.HTML(this._config.template.markup)); 265 + JX.$H(this._config.template.markup)); 261 266 template = template.firstChild; 262 267 template.id = ''; 263 268
+6 -2
webroot/rsrc/js/application/herald/PathTypeahead.js
··· 1 1 /** 2 - * @requires javelin-lib-dev 3 - * javelin-typeahead-dev 2 + * @requires javelin-install 3 + * javelin-typeahead 4 + * javelin-dom 5 + * javelin-request 6 + * javelin-typeahead-ondemand-source 7 + * javelin-util 4 8 * @provides path-typeahead 5 9 * @javelin 6 10 */
+1 -1
webroot/rsrc/js/application/herald/herald-rule-editor.js
··· 1 1 /** 2 2 * @requires herald-rule-editor 3 - * javelin-lib-dev 3 + * javelin-behavior 4 4 * @provides javelin-behavior-herald-rule-editor 5 5 * @javelin 6 6 */
+5 -27
webroot/rsrc/js/application/maniphest/behavior-transaction-controls.js
··· 1 1 /** 2 2 * @provides javelin-behavior-maniphest-transaction-controls 3 - * @requires javelin-lib-dev 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-tokenizer 6 + * javelin-typeahead 7 + * javelin-typeahead-preloaded-source 4 8 */ 5 9 6 10 JX.behavior('maniphest-transaction-controls', function(config) { ··· 51 55 } 52 56 }); 53 57 54 - /* 55 - 56 - var root = JX.$(config.tokenizer); 57 - var datasource = new JX.TypeaheadPreloadedSource(config.src); 58 - 59 - var typeahead = new JX.Typeahead(root); 60 - typeahead.setDatasource(datasource); 61 - 62 - var tokenizer = new JX.Tokenizer(root); 63 - tokenizer.setTypeahead(typeahead); 64 - tokenizer.start(); 65 - 66 - JX.DOM.listen( 67 - JX.$(config.select), 68 - 'change', 69 - null, 70 - function(e) { 71 - if (JX.$(config.select).value == 'add_reviewers') { 72 - JX.DOM.show(JX.$(config.row)); 73 - tokenizer.refresh(); 74 - } else { 75 - JX.DOM.hide(JX.$(config.row)); 76 - } 77 - }); 78 - 79 - */ 80 58 }); 81 59
+5 -4
webroot/rsrc/js/application/owners/OwnersPathEditor.js
··· 1 1 /** 2 2 * @requires multirow-row-manager 3 - * javelin-lib-dev 4 - * javelin-typeahead-dev 3 + * javelin-install 5 4 * path-typeahead 5 + * javelin-dom 6 + * javelin-util 6 7 * @provides owners-path-editor 7 8 * @javelin 8 9 */ ··· 105 106 var repo_cell = JX.$N('td', {}, repo_select); 106 107 var typeahead_cell = JX.$N( 107 108 'td', 108 - JX.HTML(this._inputTemplate)); 109 + JX.$H(this._inputTemplate)); 109 110 110 111 // Text input for path. 111 112 var path_input = JX.DOM.find(typeahead_cell, 'input'); ··· 113 114 path_input, 114 115 { 115 116 value : path_ref.path || "", 116 - name : "path[" + this._count + "]", 117 + name : "path[" + this._count + "]" 117 118 }); 118 119 119 120 // The Typeahead requires a display div called hardpoint.
+1 -1
webroot/rsrc/js/application/owners/owners-path-editor.js
··· 1 1 /** 2 2 * @requires owners-path-editor 3 - * javelin-lib-dev 3 + * javelin-behavior 4 4 * @provides javelin-behavior-owners-path-editor 5 5 * @javelin 6 6 */
+1
webroot/rsrc/js/javelin
··· 1 + ../../../externals/javelin/src/
-179
webroot/rsrc/js/javelin/init.dev.js
··· 1 - /** 2 - * Javelin core; installs Javelin and Stratcom event delegation. 3 - * 4 - * @provides javelin-magical-init 5 - * @nopackage 6 - * 7 - * @javelin-installs JX.__rawEventQueue 8 - * @javelin-installs JX.__simulate 9 - * @javelin-installs JX.enableDispatch 10 - * @javelin-installs JX.onload 11 - * 12 - * @javelin 13 - */ 14 - (function() { 15 - 16 - 17 - if (window.JX) { 18 - return; 19 - } 20 - 21 - window.JX = {}; 22 - window['__DEV__'] = window['__DEV__'] || 0; 23 - 24 - var loaded = false; 25 - var onload = []; 26 - var master_event_queue = []; 27 - var root = document.documentElement; 28 - var has_add_event_listener = !!root.addEventListener; 29 - 30 - JX.__rawEventQueue = function(what) { 31 - master_event_queue.push(what); 32 - 33 - // Evade static analysis - JX.Stratcom 34 - var Stratcom = JX['Stratcom']; 35 - if (Stratcom && Stratcom.ready) { 36 - // Empty the queue now so that exceptions don't cause us to repeatedly 37 - // try to handle events. 38 - var local_queue = master_event_queue; 39 - master_event_queue = []; 40 - for (var ii = 0; ii < local_queue.length; ++ii) { 41 - var evt = local_queue[ii]; 42 - 43 - // Sometimes IE gives us events which throw when ".type" is accessed; 44 - // just ignore them since we can't meaningfully dispatch them. TODO: 45 - // figure out where these are coming from. 46 - try { var test = evt.type; } catch (x) { continue; } 47 - 48 - if (!loaded && evt.type == 'domready') { 49 - document.body && (document.body.id = null); 50 - loaded = true; 51 - 52 - for (var ii = 0; ii < onload.length; ii++) { 53 - onload[ii](); 54 - } 55 - 56 - } 57 - 58 - Stratcom.dispatch(evt); 59 - } 60 - } else { 61 - var target = what.srcElement || what.target; 62 - if (target && 63 - (what.type in {click: 1, submit: 1}) && 64 - target.getAttribute && 65 - target.getAttribute('data-mustcapture') === '1') { 66 - what.returnValue = false; 67 - what.preventDefault && what.preventDefault(); 68 - document.body.id = 'event_capture'; 69 - 70 - // For versions of IE that use attachEvent, the event object is somehow 71 - // stored globally by reference, and all the references we push to the 72 - // master_event_queue will always refer to the most recent event. We 73 - // work around this by popping the useless global event off the queue, 74 - // and pushing a clone of the event that was just fired using the IE's 75 - // proprietary createEventObject function. 76 - // see: http://msdn.microsoft.com/en-us/library/ms536390(v=vs.85).aspx 77 - if (!add_event_listener && document.createEventObject) { 78 - master_event_queue.pop(); 79 - master_event_queue.push(document.createEventObject(what)); 80 - } 81 - 82 - return false; 83 - } 84 - } 85 - } 86 - 87 - JX.enableDispatch = function(target, type) { 88 - if (target.addEventListener) { 89 - target.addEventListener(type, JX.__rawEventQueue, true); 90 - } else if (target.attachEvent) { 91 - target.attachEvent('on' + type, JX.__rawEventQueue); 92 - } 93 - }; 94 - 95 - var document_events = [ 96 - 'click', 97 - 'change', 98 - 'keypress', 99 - 'mousedown', 100 - 'mouseover', 101 - 'mouseout', 102 - 'mouseup', 103 - 'keydown', 104 - 'drop', 105 - 'dragenter', 106 - 'dragleave', 107 - 'dragover' 108 - ]; 109 - 110 - // Simulate focus and blur in old versions of IE using focusin and focusout 111 - // TODO: Document the gigantic IE mess here with focus/blur. 112 - // TODO: beforeactivate/beforedeactivate? 113 - // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 114 - if (!has_add_event_listener) { 115 - document_events.push('focusin', 'focusout'); 116 - } 117 - 118 - // Opera is multilol: it propagates focus / blur odd, and submit differently 119 - if (window.opera) { 120 - document_events.push('focus', 'blur'); 121 - } else { 122 - document_events.push('submit'); 123 - } 124 - 125 - for (var ii = 0; ii < document_events.length; ++ii) { 126 - JX.enableDispatch(root, document_events[ii]); 127 - } 128 - 129 - // In particular, we're interested in capturing window focus/blur here so 130 - // long polls can abort when the window is not focused. 131 - var window_events = [ 132 - ('onpagehide' in window) ? 'pagehide' : 'unload', 133 - 'resize', 134 - 'focus', 135 - 'blur' 136 - ]; 137 - 138 - for (var ii = 0; ii < window_events.length; ++ii) { 139 - JX.enableDispatch(window, window_events[ii]); 140 - } 141 - 142 - JX.__simulate = function(node, event) { 143 - if (!has_add_event_listener) { 144 - var e = {target: node, type: event}; 145 - JX.__rawEventQueue(e); 146 - if (e.returnValue === false) { 147 - return false; 148 - } 149 - } 150 - }; 151 - 152 - if (has_add_event_listener) { 153 - document.addEventListener('DOMContentLoaded', function() { 154 - JX.__rawEventQueue({type: 'domready'}); 155 - }, true); 156 - } else { 157 - var ready = 158 - "if (this.readyState == 'complete') {" + 159 - "JX.__rawEventQueue({type: 'domready'});" + 160 - "}"; 161 - 162 - document.write( 163 - '<script' + 164 - ' defer="defer"' + 165 - ' src="javascript:void(0)"' + 166 - ' onreadystatechange="' + ready + '"' + 167 - '><\/sc' + 'ript\>'); 168 - } 169 - 170 - JX.onload = function(func) { 171 - if (loaded) { 172 - func(); 173 - } else { 174 - onload.push(func); 175 - } 176 - } 177 - 178 - 179 - })();
-2
webroot/rsrc/js/javelin/init.min.js
··· 1 - /** @provides javelin-init-prod */ 2 - (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<m.length;++l){var k=m[l];try{var test=k.type;}catch(p){continue;}if(!d&&k.type=='domready'){document.body&&(document.body.id=null);d=true;for(var l=0;l<f.length;l++)f[l]();}j.dispatch(k);}}else{var n=o.srcElement||o.target;if(n&&(o.type in {click:1,submit:1})&&n.getAttribute&&n.getAttribute('data-mustcapture')==='1'){o.returnValue=false;o.preventDefault&&o.preventDefault();document.body.id='event_capture';if(!add_event_listener&&document.createEventObject){e.pop();e.push(document.createEventObject(o));}return false;}}};JX.enableDispatch=function(j,k){if(j.addEventListener){j.addEventListener(k,JX.__rawEventQueue,true);}else if(j.attachEvent)j.attachEvent('on'+k,JX.__rawEventQueue);};var a=['click','change','keypress','mousedown','mouseover','mouseout','mouseup','keydown','drop','dragenter','dragleave','dragover'];if(!b)a.push('focusin','focusout');if(window.opera){a.push('focus','blur');}else a.push('submit');for(var c=0;c<a.length;++c)JX.enableDispatch(h,a[c]);var i=[('onpagehide' in window)?'pagehide':'unload','resize','focus','blur'];for(var c=0;c<i.length;++c)JX.enableDispatch(window,i[c]);JX.__simulate=function(k,event){if(!b){var j={target:k,type:event};JX.__rawEventQueue(j);if(j.returnValue===false)return false;}};if(b){document.addEventListener('DOMContentLoaded',function(){JX.__rawEventQueue({type:'domready'});},true);}else{var g="if (this.readyState == 'complete') {"+"JX.__rawEventQueue({type: 'domready'});"+"}";document.write('<script'+' defer="defer"'+' src="javascript:void(0)"'+' onreadystatechange="'+g+'"'+'><\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})();
-2955
webroot/rsrc/js/javelin/javelin.dev.js
··· 1 - /** @provides javelin-lib-dev */ 2 - 3 - /** 4 - * Javelin utility functions. 5 - * 6 - * @provides javelin-util 7 - * 8 - * @javelin-installs JX.$A 9 - * @javelin-installs JX.$AX 10 - * @javelin-installs JX.copy 11 - * @javelin-installs JX.bind 12 - * @javelin-installs JX.bag 13 - * @javelin-installs JX.keys 14 - * @javelin-installs JX.defer 15 - * @javelin-installs JX.go 16 - * @javelin-installs JX.log 17 - * 18 - * @javelin 19 - */ 20 - 21 - 22 - /** 23 - * Convert an array-like object (usually ##arguments##) into a real Array. An 24 - * "array-like object" is something with a ##length## property and numerical 25 - * keys. The most common use for this is to let you call Array functions on the 26 - * magical ##arguments## object. 27 - * 28 - * JX.$A(arguments).slice(1); 29 - * 30 - * @param obj Array, or array-like object. 31 - * @return Array Actual array. 32 - */ 33 - JX.$A = function(mysterious_arraylike_object) { 34 - // NOTE: This avoids the Array.slice() trick because some bizarre COM object 35 - // I dug up somewhere was freaking out when I tried to do it and it made me 36 - // very upset, so do not replace this with Array.slice() cleverness. 37 - var r = []; 38 - for (var ii = 0; ii < mysterious_arraylike_object.length; ii++) { 39 - r.push(mysterious_arraylike_object[ii]); 40 - } 41 - return r; 42 - }; 43 - 44 - 45 - /** 46 - * Cast a value into an array, by wrapping scalars into singletons. If the 47 - * argument is an array, it is returned unmodified. If it is a scalar, an array 48 - * with a single element is returned. For example: 49 - * 50 - * JX.$AX([3]); // Returns [3]. 51 - * JX.$AX(3); // Returns [3]. 52 - * 53 - * Note that this function uses an "instanceof Array" check so you may need to 54 - * convert array-like objects (such as ##arguments## and Array instances from 55 - * iframes) into real arrays with @{JX.$A()}. 56 - * 57 - * @param wild Scalar or Array. 58 - * @return Array If the argument was a scalar, an Array with the argument as 59 - * its only element. Otherwise, the original Array. 60 - * 61 - */ 62 - JX.$AX = function(maybe_scalar) { 63 - return (maybe_scalar instanceof Array) ? maybe_scalar : [maybe_scalar]; 64 - }; 65 - 66 - 67 - /** 68 - * Copy properties from one object to another. Note: does not copy the 69 - * ##toString## property or anything else which isn't enumerable or is somehow 70 - * magic or just doesn't work. But it's usually what you want. If properties 71 - * already exist, they are overwritten. 72 - * 73 - * var cat = { 74 - * ears: 'clean', 75 - * paws: 'clean', 76 - * nose: 'DIRTY OH NOES' 77 - * }; 78 - * var more = { 79 - * nose: 'clean', 80 - * tail: 'clean' 81 - * }; 82 - * 83 - * JX.copy(cat, more); 84 - * 85 - * // cat is now: 86 - * // { 87 - * // ears: 'clean', 88 - * // paws: 'clean', 89 - * // nose: 'clean', 90 - * // tail: 'clean' 91 - * // } 92 - * 93 - * @param obj Destination object, which properties should be copied to. 94 - * @param obj Source object, which properties should be copied from. 95 - * @return obj Destination object. 96 - */ 97 - JX.copy = function(copy_dst, copy_src) { 98 - for (var k in copy_src) { 99 - copy_dst[k] = copy_src[k]; 100 - } 101 - return copy_dst; 102 - }; 103 - 104 - 105 - /** 106 - * Create a function which invokes another function with a bound context and 107 - * arguments (i.e., partial function application) when called; king of all 108 - * functions. 109 - * 110 - * Bind performs context binding (letting you select what the value of ##this## 111 - * will be when a function is invoked) and partial function application (letting 112 - * you create some function which calls another one with bound arguments). 113 - * 114 - * = Context Binding = 115 - * 116 - * Normally, when you call ##obj.method()##, the magic ##this## object will be 117 - * the ##obj## you invoked the method from. This can be undesirable when you 118 - * need to pass a callback to another function. For instance: 119 - * 120 - * COUNTEREXAMPLE 121 - * var dog = new JX.Dog(); 122 - * dog.barkNow(); // Makes the dog bark. 123 - * 124 - * JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work! 125 - * 126 - * This doesn't work because ##this## is ##window## when the function is 127 - * later invoked; @{JX.Stratcom.listen()} does not know about the context 128 - * object ##dog##. The solution is to pass a function with a bound context 129 - * object: 130 - * 131 - * var dog = new JX.Dog(); 132 - * var bound_function = JX.bind(dog, dog.barkNow); 133 - * 134 - * JX.Stratcom.listen('click', 'bark', bound_function); 135 - * 136 - * ##bound_function## is a function with ##dog## bound as ##this##; ##this## 137 - * will always be ##dog## when the function is called, no matter what 138 - * property chain it is invoked from. 139 - * 140 - * You can also pass ##null## as the context argument to implicitly bind 141 - * ##window##. 142 - * 143 - * = Partial Function Application = 144 - * 145 - * @{JX.bind()} also performs partial function application, which allows you 146 - * to bind one or more arguments to a function. For instance, if we have a 147 - * simple function which adds two numbers: 148 - * 149 - * function add(a, b) { return a + b; } 150 - * add(3, 4); // 7 151 - * 152 - * Suppose we want a new function, like this: 153 - * 154 - * function add3(b) { return 3 + b; } 155 - * add3(4); // 7 156 - * 157 - * Instead of doing this, we can define ##add3()## in terms of ##add()## by 158 - * binding the value ##3## to the ##a## argument: 159 - * 160 - * var add3_bound = JX.bind(null, add, 3); 161 - * add3_bound(4); // 7 162 - * 163 - * Zero or more arguments may be bound in this way. This is particularly useful 164 - * when using closures in a loop: 165 - * 166 - * COUNTEREXAMPLE 167 - * for (var ii = 0; ii < button_list.length; ii++) { 168 - * button_list[ii].onclick = function() { 169 - * JX.log('You clicked button number '+ii+'!'); // Fails! 170 - * }; 171 - * } 172 - * 173 - * This doesn't work; all the buttons report the highest number when clicked. 174 - * This is because the local ##ii## is captured by the closure. Instead, bind 175 - * the current value of ##ii##: 176 - * 177 - * var func = function(button_num) { 178 - * JX.log('You clicked button number '+button_num+'!'); 179 - * } 180 - * for (var ii = 0; ii < button_list.length; ii++) { 181 - * button_list[ii].onclick = JX.bind(null, func, ii); 182 - * } 183 - * 184 - * @param obj|null Context object to bind as ##this##. 185 - * @param function Function to bind context and arguments to. 186 - * @param ... Zero or more arguments to bind. 187 - * @return function New function which invokes the original function with 188 - * bound context and arguments when called. 189 - */ 190 - JX.bind = function(context, func, more) { 191 - 192 - if (__DEV__) { 193 - if (typeof func != 'function') { 194 - throw new Error( 195 - 'JX.bind(context, <yuck>, ...): '+ 196 - 'Attempting to bind something that is not a function.'); 197 - } 198 - } 199 - 200 - var bound = JX.$A(arguments).slice(2); 201 - return function() { 202 - return func.apply(context || window, bound.concat(JX.$A(arguments))); 203 - } 204 - }; 205 - 206 - 207 - /** 208 - * "Bag of holding"; function that does nothing. Primarily, it's used as a 209 - * placeholder when you want something to be callable but don't want it to 210 - * actually have an effect. 211 - * 212 - * @return void 213 - */ 214 - JX.bag = function() { 215 - // \o\ \o/ /o/ woo dance party 216 - }; 217 - 218 - 219 - /** 220 - * Convert an object's keys into a list. For example: 221 - * 222 - * JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars'] 223 - * 224 - * @param obj Object to retrieve keys from. 225 - * @return list List of keys. 226 - */ 227 - JX.keys = function(obj) { 228 - var r = []; 229 - for (var k in obj) { 230 - r.push(k); 231 - } 232 - return r; 233 - }; 234 - 235 - 236 - /** 237 - * Defer a function for later execution, similar to ##setTimeout()##. Returns 238 - * an object with a ##stop()## method, which cancels the deferred call. 239 - * 240 - * var ref = JX.defer(yell, 3000); // Yell in 3 seconds. 241 - * // ... 242 - * ref.stop(); // Cancel the yell. 243 - * 244 - * @param function Function to invoke after the timeout. 245 - * @param int? Timeout, in milliseconds. If this value is omitted, the 246 - * function will be invoked once control returns to the browser 247 - * event loop, as with ##setTimeout(func, 0)##. 248 - * @return obj An object with a ##stop()## method, which cancels function 249 - * execution. 250 - */ 251 - JX.defer = function(func, timeout) { 252 - var t = setTimeout(func, timeout || 0); 253 - return {stop : function() { clearTimeout(t); }} 254 - }; 255 - 256 - 257 - /** 258 - * Redirect the browser to another page by changing the window location. 259 - * 260 - * @param string Optional URI to redirect the browser to. If no URI is 261 - * provided, the current page will be reloaded. 262 - * @return void 263 - */ 264 - JX.go = function(uri) { 265 - 266 - // Foil static analysis, etc. Strictly speaking, JX.go() doesn't really need 267 - // to be in javelin-utils so we could do this properly at some point. 268 - JX['Stratcom'] && JX['Stratcom'].invoke('go', null, {uri: uri}); 269 - 270 - (uri && (window.location = uri)) || window.location.reload(true); 271 - }; 272 - 273 - 274 - if (__DEV__) { 275 - if (!window.console || !window.console.log) { 276 - if (window.opera && window.opera.postError) { 277 - window.console = {log: function(m) { window.opera.postError(m); }}; 278 - } else { 279 - window.console = {log: function(m) { }}; 280 - } 281 - } 282 - 283 - /** 284 - * Print a message to the browser debugging console (like Firebug). This 285 - * method exists only in ##__DEV__##. 286 - * 287 - * @param string Message to print to the browser debugging console. 288 - * @return void 289 - */ 290 - JX.log = function(message) { 291 - window.console.log(message); 292 - } 293 - 294 - window.alert = (function(native_alert) { 295 - var recent_alerts = []; 296 - var in_alert = false; 297 - return function(msg) { 298 - if (in_alert) { 299 - JX.log( 300 - 'alert(...): '+ 301 - 'discarded reentrant alert.'); 302 - return; 303 - } 304 - in_alert = true; 305 - recent_alerts.push(new Date().getTime()); 306 - 307 - if (recent_alerts.length > 3) { 308 - recent_alerts.splice(0, recent_alerts.length - 3); 309 - } 310 - 311 - if (recent_alerts.length >= 3 && 312 - (recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) { 313 - if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) { 314 - window.alert = JX.bag; 315 - } 316 - } else { 317 - // Note that we can't .apply() the IE6 version of this "function". 318 - native_alert(msg); 319 - } 320 - in_alert = false; 321 - } 322 - })(window.alert); 323 - 324 - } 325 - /** 326 - * @requires javelin-util 327 - * @provides javelin-install 328 - * @javelin-installs JX.install 329 - * @javelin 330 - */ 331 - 332 - /** 333 - * Install a class into the Javelin ("JX") namespace. The first argument is the 334 - * name of the class you want to install, and the second is a map of these 335 - * attributes (all of which are optional): 336 - * 337 - * - ##construct## //(function)// Class constructor. If you don't provide one, 338 - * one will be created for you (but it will be very boring). 339 - * - ##extend## //(string)// The name of another JX-namespaced class to extend 340 - * via prototypal inheritance. 341 - * - ##members## //(map)// A map of instance methods and properties. 342 - * - ##statics## //(map)// A map of static methods and properties. 343 - * - ##initialize## //(function)// A function which will be run once, after 344 - * this class has been installed. 345 - * - ##properties## //(map)// A map of properties that should have instance 346 - * getters and setters automatically generated for them. The key is the 347 - * property name and the value is its default value. For instance, if you 348 - * provide the property "size", the installed class will have the methods 349 - * "getSize()" and "setSize()". It will **NOT** have a property ".size" 350 - * and no guarantees are made about where install is actually chosing to 351 - * store the data. The motivation here is to let you cheaply define a 352 - * stable interface and refine it later as necessary. 353 - * - ##events## //(list)// List of event types this class is capable of 354 - * emitting. 355 - * 356 - * For example: 357 - * 358 - * JX.install('Dog', { 359 - * construct : function(name) { 360 - * this.setName(name); 361 - * }, 362 - * members : { 363 - * bark : function() { 364 - * // ... 365 - * } 366 - * }, 367 - * properites : { 368 - * name : null, 369 - * } 370 - * }); 371 - * 372 - * This creates a new ##Dog## class in the ##JX## namespace: 373 - * 374 - * var d = new JX.Dog(); 375 - * d.bark(); 376 - * 377 - * Javelin classes are normal Javascript functions and generally behave in 378 - * the expected way. Some properties and methods are automatically added to 379 - * all classes: 380 - * 381 - * - ##instance.__id__## Globally unique identifier attached to each instance. 382 - * - ##instance.__super__## Reference to the parent class constructor, if one 383 - * exists. Allows use of ##this.__super__.apply(this, ...)## to call the 384 - * superclass's constructor. 385 - * - ##instance.__parent__## Reference to the parent class prototype, if one 386 - * exists. Allows use of ##this.__parent__.someMethod.apply(this, ...)## 387 - * to call the superclass's methods. 388 - * - ##prototype.__class__## Reference to the class constructor. 389 - * - ##constructor.__path__## List of path tokens used emit events. It is 390 - * probably never useful to access this directly. 391 - * - ##constructor.__readable__## //DEV ONLY!// Readable class name. You could 392 - * plausibly use this when constructing error messages. 393 - * - ##constructor.__events__## //DEV ONLY!// List of events supported by 394 - * this class. 395 - * - ##constructor.listen()## Listen to all instances of this class. See 396 - * @{JX.Base}. 397 - * - ##instance.listen()## Listen to one instance of this class. See 398 - * @{JX.Base}. 399 - * - ##instance.invoke()## Invoke an event from an instance. See @{JX.Base}. 400 - * 401 - * 402 - * @param string Name of the class to install. It will appear in the JX 403 - * "namespace" (e.g., JX.Pancake). 404 - * @param map Map of properties, see method documentation. 405 - * @return void 406 - * 407 - * @author epriestley 408 - */ 409 - JX.install = function(new_name, new_junk) { 410 - 411 - if (typeof JX.install._nextObjectID == 'undefined') { 412 - JX.install._nextObjectID = 0; 413 - } 414 - 415 - // If we've already installed this, something is up. 416 - if (new_name in JX) { 417 - if (__DEV__) { 418 - throw new Error( 419 - 'JX.install("' + new_name + '", ...): ' + 420 - 'trying to reinstall something that has already been installed.'); 421 - } 422 - return; 423 - } 424 - 425 - // Since we may end up loading things out of order (e.g., Dog extends Animal 426 - // but we load Dog first) we need to keep a list of things that we've been 427 - // asked to install but haven't yet been able to install around. 428 - if (!JX.install._queue) { 429 - JX.install._queue = []; 430 - } 431 - JX.install._queue.push([new_name, new_junk]); 432 - do { 433 - var junk; 434 - var name = null; 435 - for (var ii = 0; ii < JX.install._queue.length; ++ii) { 436 - junk = JX.install._queue[ii][1]; 437 - if (junk.extend && !JX[junk.extend]) { 438 - // We need to extend something that we haven't been able to install 439 - // yet, so just keep this in queue. 440 - continue; 441 - } 442 - 443 - // Install time! First, get this out of the queue. 444 - name = JX.install._queue[ii][0]; 445 - JX.install._queue.splice(ii, 1); 446 - --ii; 447 - 448 - if (__DEV__) { 449 - var valid = { 450 - construct : 1, 451 - statics : 1, 452 - members : 1, 453 - extend : 1, 454 - initialize: 1, 455 - properties : 1, 456 - events : 1, 457 - canCallAsFunction : 1 458 - }; 459 - for (var k in junk) { 460 - if (!(k in valid)) { 461 - throw new Error( 462 - 'JX.install("' + name + '", {"' + k + '": ...}): ' + 463 - 'trying to install unknown property `' + k + '`.'); 464 - } 465 - } 466 - if (junk.constructor !== {}.constructor) { 467 - throw new Error( 468 - 'JX.install("' + name + '", {"constructor": ...}): ' + 469 - 'property `constructor` should be called `construct`.'); 470 - } 471 - } 472 - 473 - // First, build the constructor. If construct is just a function, this 474 - // won't change its behavior (unless you have provided a really awesome 475 - // function, in which case it will correctly punish you for your attempt 476 - // at creativity). 477 - JX[name] = (function(name, junk) { 478 - var result = function() { 479 - this.__id__ = '__obj__' + (++JX.install._nextObjectID); 480 - this.__super__ = JX[junk.extend] || JX.bag; 481 - this.__parent__ = JX[name].prototype; 482 - if (JX[name].__prototyping__) { 483 - return; 484 - } 485 - return (junk.construct || JX.bag).apply(this, arguments); 486 - // TODO: Allow mixins to initialize here? 487 - // TODO: Also, build mixins? 488 - }; 489 - 490 - if (__DEV__) { 491 - if (!junk.canCallAsFunction) { 492 - var inner = result; 493 - result = function() { 494 - if (this === window || this === JX) { 495 - throw new Error("<" + JX[name].__readable__ + ">: " + 496 - "Tried to construct an instance " + 497 - "without the 'new' operator. Either use " + 498 - "'new' or set 'canCallAsFunction' where you " + 499 - "install the class."); 500 - } 501 - return inner.apply(this, arguments); 502 - }; 503 - } 504 - } 505 - return result; 506 - })(name, junk); 507 - 508 - // Copy in all the static methods and properties. 509 - JX.copy(JX[name], junk.statics); 510 - 511 - if (__DEV__) { 512 - JX[name].__readable__ = 'JX.' + name; 513 - } 514 - 515 - JX[name].__prototyping__ = 0; 516 - 517 - var proto; 518 - if (junk.extend) { 519 - JX[junk.extend].__prototyping__++; 520 - proto = JX[name].prototype = new JX[junk.extend](); 521 - JX[junk.extend].__prototyping__--; 522 - } else { 523 - proto = JX[name].prototype = {}; 524 - } 525 - 526 - proto.__class__ = JX[name]; 527 - 528 - // Build getters and setters from the `prop' map. 529 - for (var k in (junk.properties || {})) { 530 - var base = k.charAt(0).toUpperCase()+k.substr(1); 531 - var prop = '__auto__' + k; 532 - proto[prop] = junk.properties[k]; 533 - proto['set' + base] = (function(prop) { 534 - return function(v) { 535 - this[prop] = v; 536 - return this; 537 - } 538 - })(prop); 539 - 540 - proto['get' + base] = (function(prop) { 541 - return function() { 542 - return this[prop]; 543 - } 544 - })(prop); 545 - } 546 - 547 - if (__DEV__) { 548 - 549 - // Check for aliasing in default values of members. If we don't do this, 550 - // you can run into a problem like this: 551 - // 552 - // JX.install('List', { members : { stuff : [] }}); 553 - // 554 - // var i_love = new JX.List(); 555 - // var i_hate = new JX.List(); 556 - // 557 - // i_love.stuff.push('Psyduck'); // I love psyduck! 558 - // JX.log(i_hate.stuff); // Show stuff I hate. 559 - // 560 - // This logs ["Psyduck"] because the push operation modifies 561 - // JX.List.prototype.stuff, which is what both i_love.stuff and 562 - // i_hate.stuff resolve to. To avoid this, set the default value to 563 - // null (or any other scalar) and do "this.stuff = [];" in the 564 - // constructor. 565 - 566 - for (var member_name in junk.members) { 567 - if (junk.extend && member_name[0] == '_') { 568 - throw new Error( 569 - 'JX.install("' + name + '", ...): ' + 570 - 'installed member "' + member_name + '" must not be named with ' + 571 - 'a leading underscore because it is in a subclass. Variables ' + 572 - 'are analyzed and crushed one file at a time, and crushed ' + 573 - 'member variables in subclasses alias crushed member variables ' + 574 - 'in superclasses. Remove the underscore, refactor the class so ' + 575 - 'it does not extend anything, or fix the minifier to be ' + 576 - 'capable of safely crushing subclasses.'); 577 - } 578 - var member_value = junk.members[member_name]; 579 - if (typeof member_value == 'object' && member_value !== null) { 580 - throw new Error( 581 - 'JX.install("' + name + '", ...): ' + 582 - 'installed member "' + member_name + '" is not a scalar or ' + 583 - 'function. Prototypal inheritance in Javascript aliases object ' + 584 - 'references across instances so all instances are initialized ' + 585 - 'to point at the exact same object. This is almost certainly ' + 586 - 'not what you intended. Make this member static to share it ' + 587 - 'across instances, or initialize it in the constructor to ' + 588 - 'prevent reference aliasing and give each instance its own ' + 589 - 'copy of the value.'); 590 - } 591 - } 592 - } 593 - 594 - 595 - // This execution order intentionally allows you to override methods 596 - // generated from the "properties" initializer. 597 - JX.copy(proto, junk.members); 598 - 599 - 600 - // Build this ridiculous event model thing. Basically, this defines 601 - // two instance methods, invoke() and listen(), and one static method, 602 - // listen(). If you listen to an instance you get events for that 603 - // instance; if you listen to a class you get events for all instances 604 - // of that class (including instances of classes which extend it). 605 - // 606 - // This is rigged up through Stratcom. Each class has a path component 607 - // like "class:Dog", and each object has a path component like 608 - // "obj:23". When you invoke on an object, it emits an event with 609 - // a path that includes its class, all parent classes, and its object 610 - // ID. 611 - // 612 - // Calling listen() on an instance listens for just the object ID. 613 - // Calling listen() on a class listens for that class's name. This 614 - // has the effect of working properly, but installing them is pretty 615 - // messy. 616 - if (junk.events && junk.events.length) { 617 - 618 - var parent = JX[junk.extend] || {}; 619 - 620 - // If we're in dev, we build up a list of valid events (for this 621 - // class or some parent class) and then check them whenever we try 622 - // to listen or invoke. 623 - if (__DEV__) { 624 - var valid_events = parent.__events__ || {}; 625 - for (var ii = 0; ii < junk.events.length; ++ii) { 626 - valid_events[junk.events[ii]] = true; 627 - } 628 - JX[name].__events__ = valid_events; 629 - } 630 - 631 - // Build the class name chain. 632 - JX[name].__name__ = 'class:' + name; 633 - var ancestry = parent.__path__ || []; 634 - JX[name].__path__ = ancestry.concat([JX[name].__name__]); 635 - 636 - proto.invoke = function(type) { 637 - if (__DEV__) { 638 - if (!(type in this.__class__.__events__)) { 639 - throw new Error( 640 - this.__class__.__readable__ + '.invoke("' + type + '", ...): ' + 641 - 'invalid event type. Valid event types are: ' + 642 - JX.keys(this.__class__.__events__).join(', ') + '.'); 643 - } 644 - } 645 - // Here and below, this nonstandard access notation is used to mask 646 - // these callsites from the static analyzer. JX.Stratcom is always 647 - // available by the time we hit these execution points. 648 - return JX['Stratcom'].invoke( 649 - 'obj:' + type, 650 - this.__class__.__path__.concat([this.__id__]), 651 - {args : JX.$A(arguments).slice(1)}); 652 - }; 653 - 654 - proto.listen = function(type, callback) { 655 - if (__DEV__) { 656 - if (!(type in this.__class__.__events__)) { 657 - throw new Error( 658 - this.__class__.__readable__ + '.listen("' + type + '", ...): ' + 659 - 'invalid event type. Valid event types are: ' + 660 - JX.keys(this.__class__.__events__).join(', ') + '.'); 661 - } 662 - } 663 - return JX['Stratcom'].listen( 664 - 'obj:' + type, 665 - this.__id__, 666 - JX.bind(this, function(e) { 667 - return callback.apply(this, e.getData().args); 668 - })); 669 - }; 670 - 671 - JX[name].listen = function(type, callback) { 672 - if (__DEV__) { 673 - if (!(type in this.__events__)) { 674 - throw new Error( 675 - this.__readable__ + '.listen("' + type + '", ...): ' + 676 - 'invalid event type. Valid event types are: ' + 677 - JX.keys(this.__events__).join(', ') + '.'); 678 - } 679 - } 680 - return JX['Stratcom'].listen( 681 - 'obj:' + type, 682 - this.__name__, 683 - JX.bind(this, function(e) { 684 - return callback.apply(this, e.getData().args); 685 - })); 686 - }; 687 - } else if (__DEV__) { 688 - var error_message = 689 - 'class does not define any events. Pass an "events" property to ' + 690 - 'JX.install() to define events.'; 691 - JX[name].listen = JX[name].listen || function() { 692 - throw new Error( 693 - this.__readable__ + '.listen(...): ' + 694 - error_message); 695 - }; 696 - JX[name].invoke = JX[name].invoke || function() { 697 - throw new Error( 698 - this.__readable__ + '.invoke(...): ' + 699 - error_message); 700 - }; 701 - proto.listen = proto.listen || function() { 702 - throw new Error( 703 - this.__class__.__readable__ + '.listen(...): ' + 704 - error_message); 705 - }; 706 - proto.invoke = proto.invoke || function() { 707 - throw new Error( 708 - this.__class__.__readable__ + '.invoke(...): ' + 709 - error_message); 710 - }; 711 - } 712 - 713 - // Finally, run the init function if it was provided. 714 - (junk.initialize || JX.bag)(); 715 - } 716 - 717 - // In effect, this exits the loop as soon as we didn't make any progress 718 - // installing things, which means we've installed everything we have the 719 - // dependencies for. 720 - } while (name); 721 - } 722 - /** 723 - * @requires javelin-install 724 - * @provides javelin-event 725 - * @javelin 726 - */ 727 - 728 - /** 729 - * A generic event, routed by @{JX.Stratcom}. All events within Javelin are 730 - * represented by a {@JX.Event}, regardless of whether they originate from 731 - * a native DOM event (like a mouse click) or are custom application events. 732 - * 733 - * Events have a propagation model similar to native Javascript events, in that 734 - * they can be stopped with stop() (which stops them from continuing to 735 - * propagate to other handlers) or prevented with prevent() (which prevents them 736 - * from taking their default action, like following a link). You can do both at 737 - * once with kill(). 738 - * 739 - * @author epriestley 740 - * @task stop Stopping Event Behaviors 741 - * @task info Getting Event Information 742 - */ 743 - JX.install('Event', { 744 - members : { 745 - 746 - /** 747 - * Stop an event from continuing to propagate. No other handler will 748 - * receive this event, but its default behavior will still occur. See 749 - * ""Using Events"" for more information on the distinction between 750 - * 'stopping' and 'preventing' an event. See also prevent() (which prevents 751 - * an event but does not stop it) and kill() (which stops and prevents an 752 - * event). 753 - * 754 - * @return this 755 - * @task stop 756 - */ 757 - stop : function() { 758 - var r = this.getRawEvent(); 759 - if (r) { 760 - r.cancelBubble = true; 761 - r.stopPropagation && r.stopPropagation(); 762 - } 763 - this.setStopped(true); 764 - return this; 765 - }, 766 - 767 - 768 - /** 769 - * Prevent an event's default action. This depends on the event type, but 770 - * the common default actions are following links, submitting forms, 771 - * and typing text. Event prevention is generally used when you have a link 772 - * or form which work properly without Javascript but have a specialized 773 - * Javascript behavior. When you intercept the event and make the behavior 774 - * occur, you prevent it to keep the browser from following the link. 775 - * 776 - * Preventing an event does not stop it from propagating, so other handlers 777 - * will still receive it. See ""Using Events"" for more information on the 778 - * distinction between 'stopping' and 'preventing' an event. See also 779 - * stop() (which stops an event but does not prevent it) and kill() 780 - * (which stops and prevents an event). 781 - * 782 - * @return this 783 - * @task stop 784 - */ 785 - prevent : function() { 786 - var r = this.getRawEvent(); 787 - if (r) { 788 - r.returnValue = false; 789 - r.preventDefault && r.preventDefault(); 790 - } 791 - this.setPrevented(true); 792 - return this; 793 - }, 794 - 795 - 796 - /** 797 - * Stop and prevent an event, which stops it from propagating and prevents 798 - * its defualt behavior. This is a convenience function, see stop() and 799 - * prevent() for information on what it means to stop or prevent an event. 800 - * 801 - * @return this 802 - * @task stop 803 - */ 804 - kill : function() { 805 - this.prevent(); 806 - this.stop(); 807 - return this; 808 - }, 809 - 810 - 811 - /** 812 - * Get the special key (like tab or return), if any, associated with this 813 - * event. Browsers report special keys differently; this method allows you 814 - * to identify a keypress in a browser-agnostic way. Note that this detects 815 - * only some special keys: delete, tab, return escape, left, up, right, 816 - * down. 817 - * 818 - * For example, if you want to react to the escape key being pressed, you 819 - * could install a listener like this: 820 - * 821 - * JX.Stratcom.listen('keydown', 'example', function(e) { 822 - * if (e.getSpecialKey() == 'esc') { 823 - * JX.log("You pressed 'Escape'! Well done! Bravo!"); 824 - * } 825 - * }); 826 - * 827 - * @return string|null ##null## if there is no associated special key, 828 - * or one of the strings 'delete', 'tab', 'return', 829 - * 'esc', 'left', 'up', 'right', or 'down'. 830 - * @task info 831 - */ 832 - getSpecialKey : function() { 833 - var r = this.getRawEvent(); 834 - if (!r || r.shiftKey) { 835 - return null; 836 - } 837 - 838 - return JX.Event._keymap[r.keyCode] || null; 839 - }, 840 - 841 - 842 - /** 843 - * Get the node corresponding to the specified key in this event's node map. 844 - * This is a simple helper method that makes the API for accessing nodes 845 - * less ugly. 846 - * 847 - * JX.Stratcom.listen('click', 'tag:a', function(e) { 848 - * var a = e.getNode('tag:a'); 849 - * // do something with the link that was clicked 850 - * }); 851 - * 852 - * @param string sigil or stratcom node key 853 - * @return node|null Node mapped to the specified key, or null if it the 854 - * key does not exist. The available keys include: 855 - * - 'tag:'+tag - first node of each type 856 - * - 'id:'+id - all nodes with an id 857 - * - sigil - first node of each sigil 858 - * @task info 859 - */ 860 - getNode : function(key) { 861 - return this.getNodes()[key] || null; 862 - }, 863 - 864 - 865 - /** 866 - * Get the metadata associated with the node that corresponds to the key 867 - * in this event's node map. This is a simple helper method that makes 868 - * the API for accessing metadata associated with specific nodes less ugly. 869 - * 870 - * JX.Stratcom.listen('click', 'tag:a', function(event) { 871 - * var anchorData = event.getNodeData('tag:a'); 872 - * // do something with the metadata of the link that was clicked 873 - * }); 874 - * 875 - * @param string sigil or stratcom node key 876 - * @return dict dictionary of the node's metadata 877 - * @task info 878 - */ 879 - getNodeData : function(key) { 880 - // Evade static analysis - JX.Stratcom 881 - return JX['Stratcom'].getData(this.getNode(key)); 882 - } 883 - }, 884 - 885 - statics : { 886 - _keymap : { 887 - 8 : 'delete', 888 - 9 : 'tab', 889 - 13 : 'return', 890 - 27 : 'esc', 891 - 37 : 'left', 892 - 38 : 'up', 893 - 39 : 'right', 894 - 40 : 'down', 895 - 63232 : 'up', 896 - 63233 : 'down', 897 - 62234 : 'left', 898 - 62235 : 'right' 899 - } 900 - }, 901 - 902 - properties : { 903 - 904 - /** 905 - * Native Javascript event which generated this @{JX.Event}. Not every 906 - * event is generated by a native event, so there may be ##null## in 907 - * this field. 908 - * 909 - * @type Event|null 910 - * @task info 911 - */ 912 - rawEvent : null, 913 - 914 - /** 915 - * String describing the event type, like 'click' or 'mousedown'. This 916 - * may also be an application or object event. 917 - * 918 - * @type string 919 - * @task info 920 - */ 921 - type : null, 922 - 923 - /** 924 - * If available, the DOM node where this event occurred. For example, if 925 - * this event is a click on a button, the target will be the button which 926 - * was clicked. Application events will not have a target, so this property 927 - * will return the value ##null##. 928 - * 929 - * @type DOMNode|null 930 - * @task info 931 - */ 932 - target : null, 933 - 934 - /** 935 - * Metadata attached to nodes associated with this event. 936 - * 937 - * For native events, the DOM is walked from the event target to the root 938 - * element. Each sigil which is encountered while walking up the tree is 939 - * added to the map as a key. If the node has associated metainformation, 940 - * it is set as the value; otherwise, the value is null. 941 - * 942 - * @type dict<string, *> 943 - * @task info 944 - */ 945 - data : null, 946 - 947 - /** 948 - * Sigil path this event was activated from. TODO: explain this 949 - * 950 - * @type list<string> 951 - * @task info 952 - */ 953 - path : [], 954 - 955 - /** 956 - * True if propagation of the event has been stopped. See stop(). 957 - * 958 - * @type bool 959 - * @task stop 960 - */ 961 - stopped : false, 962 - 963 - /** 964 - * True if default behavior of the event has been prevented. See prevent(). 965 - * 966 - * @type bool 967 - * @task stop 968 - */ 969 - prevented : false, 970 - 971 - /** 972 - * @task info 973 - */ 974 - nodes : {} 975 - }, 976 - 977 - /** 978 - * @{JX.Event} installs a toString() method in ##__DEV__## which allows you to 979 - * log or print events and get a reasonable representation of them: 980 - * 981 - * Event<'click', ['path', 'stuff'], [object HTMLDivElement]> 982 - */ 983 - initialize : function() { 984 - if (__DEV__) { 985 - JX.Event.prototype.toString = function() { 986 - var path = '['+this.getPath().join(', ')+']'; 987 - return 'Event<'+this.getType()+', '+path+', '+this.getTarget()+'>'; 988 - } 989 - } 990 - } 991 - }); 992 - /** 993 - * @requires javelin-install javelin-event javelin-util javelin-magical-init 994 - * @provides javelin-stratcom 995 - * @javelin 996 - */ 997 - 998 - /** 999 - * Javelin strategic command, the master event delegation core. This class is 1000 - * a sort of hybrid between Arbiter and traditional event delegation, and 1001 - * serves to route event information to handlers in a general way. 1002 - * 1003 - * Each Javelin :JX.Event has a 'type', which may be a normal Javascript type 1004 - * (for instance, a click or a keypress) or an application-defined type. It 1005 - * also has a "path", based on the path in the DOM from the root node to the 1006 - * event target. Note that, while the type is required, the path may be empty 1007 - * (it often will be for application-defined events which do not originate 1008 - * from the DOM). 1009 - * 1010 - * The path is determined by walking down the tree to the event target and 1011 - * looking for nodes that have been tagged with metadata. These names are used 1012 - * to build the event path, and unnamed nodes are ignored. Each named node may 1013 - * also have data attached to it. 1014 - * 1015 - * Listeners specify one or more event types they are interested in handling, 1016 - * and, optionally, one or more paths. A listener will only receive events 1017 - * which occurred on paths it is listening to. See listen() for more details. 1018 - * 1019 - * @author epriestley 1020 - * 1021 - * @task invoke Invoking Events 1022 - * @task listen Listening to Events 1023 - * @task handle Responding to Events 1024 - * @task sigil Managing Sigils 1025 - * @task meta Managing Metadata 1026 - * @task internal Internals 1027 - */ 1028 - JX.install('Stratcom', { 1029 - statics : { 1030 - ready : false, 1031 - _targets : {}, 1032 - _handlers : [], 1033 - _need : {}, 1034 - _auto : '*', 1035 - _data : {}, 1036 - _execContext : [], 1037 - _typeMap : {focusin: 'focus', focusout: 'blur'}, 1038 - 1039 - /** 1040 - * Node metadata is stored in a series of blocks to prevent collisions 1041 - * between indexes that are generated on the server side (and potentially 1042 - * concurrently). Block 0 is for metadata on the initial page load, block 1 1043 - * is for metadata added at runtime with JX.Stratcom.siglize(), and blocks 1044 - * 2 and up are for metadata generated from other sources (e.g. JX.Request). 1045 - * Use allocateMetadataBlock() to reserve a block, and mergeData() to fill 1046 - * a block with data. 1047 - * 1048 - * When a JX.Request is sent, a block is allocated for it and any metadata 1049 - * it returns is filled into that block. 1050 - */ 1051 - _dataBlock : 2, 1052 - 1053 - /** 1054 - * Within each datablock, data is identified by a unique index. The data 1055 - * pointer (data-meta attribute) on a node looks like this: 1056 - * 1057 - * 1_2 1058 - * 1059 - * ...where 1 is the block, and 2 is the index within that block. Normally, 1060 - * blocks are filled on the server side, so index allocation takes place 1061 - * there. However, when data is provided with JX.Stratcom.addData(), we 1062 - * need to allocate indexes on the client. 1063 - */ 1064 - _dataIndex : 0, 1065 - 1066 - /** 1067 - * Dispatch a simple event that does not have a corresponding native event 1068 - * object. It is unusual to call this directly. Generally, you will instead 1069 - * dispatch events from an object using the invoke() method present on all 1070 - * objects. See @{JX.Base.invoke()} for documentation. 1071 - * 1072 - * @param string Event type. 1073 - * @param list? Optionally, a path to attach to the event. This is 1074 - * rarely meaingful for simple events. 1075 - * @param object? Optionally, arbitrary data to send with the event. 1076 - * @return @{JX.Event} The event object which was dispatched to listeners. 1077 - * The main use of this is to test whether any 1078 - * listeners prevented the event. 1079 - * @task invoke 1080 - */ 1081 - invoke : function(type, path, data) { 1082 - var proxy = new JX.Event() 1083 - .setType(type) 1084 - .setData(data || {}) 1085 - .setPath(path || []); 1086 - 1087 - return this._dispatchProxy(proxy); 1088 - }, 1089 - 1090 - 1091 - /** 1092 - * Listen for events on given paths. Specify one or more event types, and 1093 - * zero or more paths to filter on. If you don't specify a path, you will 1094 - * receive all events of the given type: 1095 - * 1096 - * // Listen to all clicks. 1097 - * JX.Stratcom.listen('click', null, handler); 1098 - * 1099 - * This will notify you of all clicks anywhere in the document (unless 1100 - * they are intercepted and killed by a higher priority handler before they 1101 - * get to you). 1102 - * 1103 - * Often, you may be interested in only clicks on certain elements. You 1104 - * can specify the paths you're interested in to filter out events which 1105 - * you do not want to be notified of. 1106 - * 1107 - * // Listen to all clicks inside elements annotated "news-feed". 1108 - * JX.Stratcom.listen('click', 'news-feed', handler); 1109 - * 1110 - * By adding more elements to the path, you can create a finer-tuned 1111 - * filter: 1112 - * 1113 - * // Listen to only "like" clicks inside "news-feed". 1114 - * JX.Stratcom.listen('click', ['news-feed', 'like'], handler); 1115 - * 1116 - * 1117 - * TODO: Further explain these shenanigans. 1118 - * 1119 - * @param string|list<string> Event type (or list of event names) to 1120 - * listen for. For example, ##'click'## or 1121 - * ##['keydown', 'keyup']##. 1122 - * 1123 - * @param wild Sigil paths to listen for this event on. See discussion 1124 - * in method documentation. 1125 - * 1126 - * @param function Callback to invoke when this event is triggered. It 1127 - * should have the signature ##f(:JX.Event e)##. 1128 - * 1129 - * @return object A reference to the installed listener. You can later 1130 - * remove the listener by calling this object's remove() 1131 - * method. 1132 - * @author epriestley 1133 - * @task listen 1134 - */ 1135 - listen : function(types, paths, func) { 1136 - 1137 - if (__DEV__) { 1138 - if (arguments.length == 4) { 1139 - throw new Error( 1140 - 'JX.Stratcom.listen(...): '+ 1141 - 'requires exactly 3 arguments. Did you mean JX.DOM.listen?'); 1142 - } 1143 - if (arguments.length != 3) { 1144 - throw new Error( 1145 - 'JX.Stratcom.listen(...): '+ 1146 - 'requires exactly 3 arguments.'); 1147 - } 1148 - if (typeof func != 'function') { 1149 - throw new Error( 1150 - 'JX.Stratcom.listen(...): '+ 1151 - 'callback is not a function.'); 1152 - } 1153 - } 1154 - 1155 - var ids = []; 1156 - 1157 - types = JX.$AX(types); 1158 - 1159 - if (!paths) { 1160 - paths = this._auto; 1161 - } 1162 - if (!(paths instanceof Array)) { 1163 - paths = [[paths]]; 1164 - } else if (!(paths[0] instanceof Array)) { 1165 - paths = [paths]; 1166 - } 1167 - 1168 - // To listen to multiple event types on multiple paths, we just install 1169 - // the same listener a whole bunch of times: if we install for two 1170 - // event types on three paths, we'll end up with six references to the 1171 - // listener. 1172 - // 1173 - // TODO: we'll call your listener twice if you install on two paths where 1174 - // one path is a subset of another. The solution is "don't do that", but 1175 - // it would be nice to verify that the caller isn't doing so, in __DEV__. 1176 - for (var ii = 0; ii < types.length; ++ii) { 1177 - var type = types[ii]; 1178 - if (('onpagehide' in window) && type == 'unload') { 1179 - // If we use "unload", we break the bfcache ("Back-Forward Cache") in 1180 - // Safari and Firefox. The BFCache makes using the back/forward 1181 - // buttons really fast since the pages can come out of magical 1182 - // fairyland instead of over the network, so use "pagehide" as a proxy 1183 - // for "unload" in these browsers. 1184 - type = 'pagehide'; 1185 - } 1186 - if (!(type in this._targets)) { 1187 - this._targets[type] = {}; 1188 - } 1189 - var type_target = this._targets[type]; 1190 - for (var jj = 0; jj < paths.length; ++jj) { 1191 - var path = paths[jj]; 1192 - var id = this._handlers.length; 1193 - this._handlers.push(func); 1194 - this._need[id] = path.length; 1195 - ids.push(id); 1196 - for (var kk = 0; kk < path.length; ++kk) { 1197 - if (__DEV__) { 1198 - if (path[kk] == 'tag:#document') { 1199 - throw new Error( 1200 - 'JX.Stratcom.listen(..., "tag:#document", ...): ' + 1201 - 'listen for all events using null, not "tag:#document"'); 1202 - } 1203 - if (path[kk] == 'tag:window') { 1204 - throw new Error( 1205 - 'JX.Stratcom.listen(..., "tag:window", ...): ' + 1206 - 'listen for window events using null, not "tag:window"'); 1207 - } 1208 - } 1209 - if (!type_target[path[kk]]) { 1210 - type_target[path[kk]] = []; 1211 - } 1212 - type_target[path[kk]].push(id); 1213 - } 1214 - } 1215 - } 1216 - 1217 - return { 1218 - remove : function() { 1219 - for (var ii = 0; ii < ids.length; ii++) { 1220 - delete JX.Stratcom._handlers[ids[ii]]; 1221 - } 1222 - } 1223 - }; 1224 - }, 1225 - 1226 - 1227 - /** 1228 - * Dispatch a native Javascript event through the Stratcom control flow. 1229 - * Generally, this is automatically called for you by the master dipatcher 1230 - * installed by ##init.js##. When you want to dispatch an application event, 1231 - * you should instead call invoke(). 1232 - * 1233 - * @param Event Native event for dispatch. 1234 - * @return :JX.Event Dispatched :JX.Event. 1235 - * @task internal 1236 - */ 1237 - dispatch : function(event) { 1238 - var path = []; 1239 - var nodes = {}; 1240 - var push = function(key, node) { 1241 - // we explicitly only store the first occurrence of each key 1242 - if (!nodes.hasOwnProperty(key)) { 1243 - nodes[key] = node; 1244 - path.push(key); 1245 - } 1246 - }; 1247 - 1248 - var target = event.srcElement || event.target; 1249 - 1250 - // Since you can only listen by tag, id or sigil, which are all 1251 - // attributes of an Element (the DOM interface), we unset the target 1252 - // if it isn't an Element (window and Document are Nodes but not Elements) 1253 - if (!target || !target.getAttribute) { 1254 - target = null; 1255 - } 1256 - 1257 - var cursor = target; 1258 - while (cursor && cursor.getAttribute) { 1259 - push('tag:' + cursor.nodeName.toLowerCase(), cursor); 1260 - 1261 - var id = cursor.id; 1262 - if (id) { 1263 - push('id:' + id, cursor); 1264 - } 1265 - 1266 - var sigils = cursor.getAttribute('data-sigil'); 1267 - if (sigils) { 1268 - sigils = sigils.split(' '); 1269 - for (var ii = 0; ii < sigils.length; ii++) { 1270 - push(sigils[ii], cursor); 1271 - } 1272 - } 1273 - 1274 - cursor = cursor.parentNode; 1275 - } 1276 - 1277 - var etype = event.type; 1278 - if (etype in this._typeMap) { 1279 - etype = this._typeMap[etype]; 1280 - } 1281 - 1282 - var proxy = new JX.Event() 1283 - .setRawEvent(event) 1284 - .setType(etype) 1285 - .setTarget(target) 1286 - .setNodes(nodes) 1287 - .setPath(path.reverse()); 1288 - 1289 - // JX.log('~> '+proxy.toString()); 1290 - 1291 - return this._dispatchProxy(proxy); 1292 - }, 1293 - 1294 - 1295 - /** 1296 - * Dispatch a previously constructed proxy :JX.Event. 1297 - * 1298 - * @param :JX.Event Event to dispatch. 1299 - * @return :JX.Event Returns the event argument. 1300 - * @task internal 1301 - */ 1302 - _dispatchProxy : function(proxy) { 1303 - 1304 - var scope = this._targets[proxy.getType()]; 1305 - 1306 - if (!scope) { 1307 - return proxy; 1308 - } 1309 - 1310 - var path = proxy.getPath(); 1311 - var len = path.length; 1312 - var hits = {}; 1313 - var matches; 1314 - 1315 - for (var root = -1; root < len; ++root) { 1316 - if (root == -1) { 1317 - matches = scope[this._auto]; 1318 - } else { 1319 - matches = scope[path[root]]; 1320 - } 1321 - if (!matches) { 1322 - continue; 1323 - } 1324 - for (var ii = 0; ii < matches.length; ++ii) { 1325 - hits[matches[ii]] = (hits[matches[ii]] || 0) + 1; 1326 - } 1327 - } 1328 - 1329 - var exec = []; 1330 - 1331 - for (var k in hits) { 1332 - if (hits[k] == this._need[k]) { 1333 - var handler = this._handlers[k]; 1334 - if (handler) { 1335 - exec.push(handler); 1336 - } 1337 - } 1338 - } 1339 - 1340 - this._execContext.push({ 1341 - handlers: exec, 1342 - event: proxy, 1343 - cursor: 0 1344 - }); 1345 - 1346 - this.pass(); 1347 - 1348 - this._execContext.pop(); 1349 - 1350 - return proxy; 1351 - }, 1352 - 1353 - /** 1354 - * Pass on an event, allowing other handlers to process it. The use case 1355 - * here is generally something like: 1356 - * 1357 - * if (JX.Stratcom.pass()) { 1358 - * // something else handled the event 1359 - * return; 1360 - * } 1361 - * // handle the event 1362 - * event.prevent(); 1363 - * 1364 - * This allows you to install event handlers that operate at a lower 1365 - * effective priority, and provide a default behavior which is overridable 1366 - * by listeners. 1367 - * 1368 - * @return bool True if the event was stopped or prevented by another 1369 - * handler. 1370 - * @task handle 1371 - */ 1372 - pass : function() { 1373 - var context = this._execContext[this._execContext.length - 1]; 1374 - while (context.cursor < context.handlers.length) { 1375 - var cursor = context.cursor; 1376 - ++context.cursor; 1377 - (context.handlers[cursor] || JX.bag)(context.event); 1378 - if (context.event.getStopped()) { 1379 - break; 1380 - } 1381 - } 1382 - return context.event.getStopped() || context.event.getPrevented(); 1383 - }, 1384 - 1385 - 1386 - /** 1387 - * Retrieve the event (if any) which is currently being dispatched. 1388 - * 1389 - * @return :JX.Event|null Event which is currently being dispatched, or 1390 - * null if there is no active dispatch. 1391 - * @task handle 1392 - */ 1393 - context : function() { 1394 - var len = this._execContext.length; 1395 - if (!len) { 1396 - return null; 1397 - } 1398 - return this._execContext[len - 1].event; 1399 - }, 1400 - 1401 - 1402 - /** 1403 - * Merge metadata. You must call this (even if you have no metadata) to 1404 - * start the Stratcom queue. 1405 - * 1406 - * @param int The datablock to merge data into. 1407 - * @param dict Dictionary of metadata. 1408 - * @return void 1409 - * @task internal 1410 - */ 1411 - mergeData : function(block, data) { 1412 - this._data[block] = data; 1413 - if (block == 0) { 1414 - JX.Stratcom.ready = true; 1415 - JX.__rawEventQueue({type: 'start-queue'}); 1416 - } 1417 - }, 1418 - 1419 - 1420 - /** 1421 - * Determine if a node has a specific sigil. 1422 - * 1423 - * @param Node Node to test. 1424 - * @param string Sigil to check for. 1425 - * @return bool True if the node has the sigil. 1426 - * 1427 - * @task sigil 1428 - */ 1429 - hasSigil : function(node, sigil) { 1430 - if (__DEV__) { 1431 - if (!node || !node.getAttribute) { 1432 - throw new Error( 1433 - 'JX.Stratcom.hasSigil(<non-element>, ...): ' + 1434 - 'node is not an element. Most likely, you\'re passing window or ' + 1435 - 'document, which are not elements and can\'t have sigils.'); 1436 - } 1437 - } 1438 - 1439 - var sigils = node.getAttribute('data-sigil'); 1440 - return sigils && (' ' + sigils + ' ').indexOf(' ' + sigil + ' ') > -1; 1441 - }, 1442 - 1443 - 1444 - /** 1445 - * Add a sigil to a node. 1446 - * 1447 - * @param Node Node to add the sigil to. 1448 - * @param string Sigil to name the node with. 1449 - * @return void 1450 - * @task sigil 1451 - */ 1452 - addSigil: function(node, sigil) { 1453 - if (__DEV__) { 1454 - if (!node || !node.getAttribute) { 1455 - throw new Error( 1456 - 'JX.Stratcom.addSigil(<non-element>, ...): ' + 1457 - 'node is not an element. Most likely, you\'re passing window or ' + 1458 - 'document, which are not elements and can\'t have sigils.'); 1459 - } 1460 - } 1461 - 1462 - var sigils = node.getAttribute('data-sigil'); 1463 - if (sigils && !JX.Stratcom.hasSigil(node, sigil)) { 1464 - sigil = sigils + ' ' + sigil; 1465 - } 1466 - 1467 - node.setAttribute('data-sigil', sigil); 1468 - }, 1469 - 1470 - 1471 - /** 1472 - * Retrieve a node's metadata. 1473 - * 1474 - * @param Node Node from which to retrieve data. 1475 - * @return object Data attached to the node. If no data has been attached 1476 - * to the node yet, an empty object will be returned, but 1477 - * subsequent calls to this method will always retrieve the 1478 - * same object. 1479 - * @task meta 1480 - */ 1481 - getData : function(node) { 1482 - if (__DEV__) { 1483 - if (!node || !node.getAttribute) { 1484 - throw new Error( 1485 - 'JX.Stratcom.getData(<non-element>): ' + 1486 - 'node is not an element. Most likely, you\'re passing window or ' + 1487 - 'document, which are not elements and can\'t have data.'); 1488 - } 1489 - } 1490 - 1491 - var meta_id = (node.getAttribute('data-meta') || '').split('_'); 1492 - if (meta_id[0] && meta_id[1]) { 1493 - var block = this._data[meta_id[0]]; 1494 - var index = meta_id[1]; 1495 - if (block && (index in block)) { 1496 - return block[index]; 1497 - } 1498 - } 1499 - 1500 - var data = {}; 1501 - if (!this._data[1]) { // data block 1 is reserved for JavaScript 1502 - this._data[1] = {}; 1503 - } 1504 - this._data[1][this._dataIndex] = data; 1505 - node.setAttribute('data-meta', '1_' + (this._dataIndex++)); 1506 - return data; 1507 - }, 1508 - 1509 - 1510 - /** 1511 - * Add data to a node's metadata. 1512 - * 1513 - * @param Node Node which data should be attached to. 1514 - * @param object Data to add to the node's metadata. 1515 - * @return object Data attached to the node that is returned by 1516 - * JX.Stratcom.getData(). 1517 - * @task meta 1518 - */ 1519 - addData : function(node, data) { 1520 - if (__DEV__) { 1521 - if (!node || !node.getAttribute) { 1522 - throw new Error( 1523 - 'JX.Stratcom.addData(<non-element>, ...): ' + 1524 - 'node is not an element. Most likely, you\'re passing window or ' + 1525 - 'document, which are not elements and can\'t have sigils.'); 1526 - } 1527 - if (!data || typeof data != 'object') { 1528 - throw new Error( 1529 - 'JX.Stratcom.addData(..., <nonobject>): ' + 1530 - 'data to attach to node is not an object. You must use ' + 1531 - 'objects, not primitives, for metadata.'); 1532 - } 1533 - } 1534 - 1535 - return JX.copy(JX.Stratcom.getData(node), data); 1536 - }, 1537 - 1538 - 1539 - /** 1540 - * @task internal 1541 - */ 1542 - allocateMetadataBlock : function() { 1543 - return this._dataBlock++; 1544 - } 1545 - } 1546 - }); 1547 - /** 1548 - * @provides javelin-behavior 1549 - * 1550 - * @javelin-installs JX.behavior 1551 - * @javelin-installs JX.initBehaviors 1552 - * 1553 - * @javelin 1554 - */ 1555 - 1556 - JX.behavior = function(name, control_function) { 1557 - if (__DEV__) { 1558 - if (JX.behavior._behaviors.hasOwnProperty(name)) { 1559 - throw new Error( 1560 - 'JX.behavior("'+name+'", ...): '+ 1561 - 'behavior is already registered.'); 1562 - } 1563 - if (!control_function) { 1564 - throw new Error( 1565 - 'JX.behavior("'+name+'", <nothing>): '+ 1566 - 'initialization function is required.'); 1567 - } 1568 - if (typeof control_function != 'function') { 1569 - throw new Error( 1570 - 'JX.behavior("'+name+'", <garbage>): '+ 1571 - 'initialization function is not a function.'); 1572 - } 1573 - } 1574 - JX.behavior._behaviors[name] = control_function; 1575 - }; 1576 - 1577 - 1578 - JX.initBehaviors = function(map) { 1579 - for (var name in map) { 1580 - if (__DEV__) { 1581 - if (!(name in JX.behavior._behaviors)) { 1582 - throw new Error( 1583 - 'JX.initBehavior("'+name+'", ...): '+ 1584 - 'behavior is not registered.'); 1585 - } 1586 - } 1587 - var configs = map[name]; 1588 - if (!configs.length) { 1589 - if (JX.behavior._initialized.hasOwnProperty(name)) { 1590 - continue; 1591 - } else { 1592 - configs = [null]; 1593 - } 1594 - } 1595 - for (var ii = 0; ii < configs.length; ii++) { 1596 - JX.behavior._behaviors[name](configs[ii]); 1597 - } 1598 - JX.behavior._initialized[name] = true; 1599 - } 1600 - }; 1601 - 1602 - !function(JX) { 1603 - JX.behavior._behaviors = {}; 1604 - JX.behavior._initialized = {}; 1605 - }(JX); 1606 - /** 1607 - * @requires javelin-install 1608 - * javelin-stratcom 1609 - * javelin-util 1610 - * javelin-behavior 1611 - * @provides javelin-request 1612 - * @javelin 1613 - */ 1614 - 1615 - /** 1616 - * Make basic AJAX XMLHTTPRequests. 1617 - */ 1618 - JX.install('Request', { 1619 - construct : function(uri, handler) { 1620 - this.setURI(uri); 1621 - if (handler) { 1622 - this.listen('done', handler); 1623 - } 1624 - }, 1625 - 1626 - events : ['send', 'done', 'error', 'finally'], 1627 - 1628 - members : { 1629 - 1630 - _xhrkey : null, 1631 - _transport : null, 1632 - _finished : false, 1633 - _block : null, 1634 - 1635 - send : function() { 1636 - var xport = null; 1637 - 1638 - try { 1639 - try { 1640 - xport = new XMLHttpRequest(); 1641 - } catch (x) { 1642 - xport = new ActiveXObject("Msxml2.XMLHTTP"); 1643 - } 1644 - } catch (x) { 1645 - xport = new ActiveXObject("Microsoft.XMLHTTP"); 1646 - } 1647 - 1648 - this._transport = xport; 1649 - this._xhrkey = JX.Request._xhr.length; 1650 - JX.Request._xhr.push(this); 1651 - 1652 - xport.onreadystatechange = JX.bind(this, this._onreadystatechange); 1653 - 1654 - var data = this.getData() || {}; 1655 - data.__ajax__ = true; 1656 - 1657 - this._block = JX.Stratcom.allocateMetadataBlock(); 1658 - data.__metablock__ = this._block; 1659 - 1660 - var q = (this.getDataSerializer() || 1661 - JX.Request.defaultDataSerializer)(data); 1662 - var uri = this.getURI(); 1663 - var method = this.getMethod().toUpperCase(); 1664 - 1665 - if (method == 'GET') { 1666 - uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q; 1667 - } 1668 - 1669 - this.invoke('send', this); 1670 - 1671 - if (this.getTimeout()) { 1672 - this._timer = JX.defer( 1673 - JX.bind( 1674 - this, 1675 - this._fail, 1676 - JX.Request.ERROR_TIMEOUT), 1677 - this.getTimeout()); 1678 - } 1679 - 1680 - xport.open(method, uri, true); 1681 - 1682 - if (__DEV__) { 1683 - if (this.getFile()) { 1684 - if (method != 'POST') { 1685 - throw new Error( 1686 - 'JX.Request.send(): ' + 1687 - 'attempting to send a file over GET. You must use POST.'); 1688 - } 1689 - if (this.getData()) { 1690 - throw new Error( 1691 - 'JX.Request.send(): ' + 1692 - 'attempting to send data and a file. You can not send both ' + 1693 - 'at once.'); 1694 - } 1695 - } 1696 - } 1697 - 1698 - if (method == 'POST') { 1699 - if (this.getFile()) { 1700 - xport.send(this.getFile()); 1701 - } else { 1702 - xport.setRequestHeader( 1703 - 'Content-Type', 1704 - 'application/x-www-form-urlencoded'); 1705 - xport.send(q); 1706 - } 1707 - } else { 1708 - xport.send(null); 1709 - } 1710 - }, 1711 - 1712 - abort : function() { 1713 - this._cleanup(); 1714 - }, 1715 - 1716 - _onreadystatechange : function() { 1717 - var xport = this._transport; 1718 - try { 1719 - if (this._finished) { 1720 - return; 1721 - } 1722 - if (xport.readyState != 4) { 1723 - return; 1724 - } 1725 - if (xport.status < 200 || xport.status >= 300) { 1726 - this._fail(); 1727 - return; 1728 - } 1729 - 1730 - if (__DEV__) { 1731 - if (!xport.responseText.length) { 1732 - throw new Error( 1733 - 'JX.Request("'+this.getURI()+'", ...): '+ 1734 - 'server returned an empty response.'); 1735 - } 1736 - if (xport.responseText.indexOf('for (;;);') != 0) { 1737 - throw new Error( 1738 - 'JX.Request("'+this.getURI()+'", ...): '+ 1739 - 'server returned an invalid response.'); 1740 - } 1741 - } 1742 - 1743 - var text = xport.responseText.substring('for (;;);'.length); 1744 - var response = eval('('+text+')'); 1745 - } catch (exception) { 1746 - 1747 - if (__DEV__) { 1748 - JX.log( 1749 - 'JX.Request("'+this.getURI()+'", ...): '+ 1750 - 'caught exception processing response: '+exception); 1751 - } 1752 - this._fail(); 1753 - return; 1754 - } 1755 - 1756 - try { 1757 - if (response.error) { 1758 - this._fail(response.error); 1759 - } else { 1760 - JX.Stratcom.mergeData( 1761 - this._block, 1762 - response.javelin_metadata || {}); 1763 - this._done(response); 1764 - JX.initBehaviors(response.javelin_behaviors || {}); 1765 - } 1766 - } catch (exception) { 1767 - // In Firefox+Firebug, at least, something eats these. :/ 1768 - JX.defer(function() { 1769 - throw exception; 1770 - }); 1771 - } 1772 - }, 1773 - 1774 - _fail : function(error) { 1775 - this._cleanup(); 1776 - 1777 - this.invoke('error', error, this); 1778 - this.invoke('finally'); 1779 - }, 1780 - 1781 - _done : function(response) { 1782 - this._cleanup(); 1783 - 1784 - if (response.onload) { 1785 - for (var ii = 0; ii < response.onload.length; ii++) { 1786 - (new Function(response.onload[ii]))(); 1787 - } 1788 - } 1789 - 1790 - this.invoke('done', this.getRaw() ? response : response.payload, this); 1791 - this.invoke('finally'); 1792 - }, 1793 - 1794 - _cleanup : function() { 1795 - this._finished = true; 1796 - delete JX.Request._xhr[this._xhrkey]; 1797 - this._timer && this._timer.stop(); 1798 - this._transport.abort(); 1799 - } 1800 - 1801 - }, 1802 - 1803 - statics : { 1804 - _xhr : [], 1805 - shutdown : function() { 1806 - for (var ii = 0; ii < JX.Request._xhr.length; ii++) { 1807 - try { 1808 - JX.Request._xhr[ii] && JX.Request._xhr[ii].abort(); 1809 - } catch (x) { 1810 - // Ignore. 1811 - } 1812 - } 1813 - JX.Request._xhr = []; 1814 - }, 1815 - ERROR_TIMEOUT : -9000, 1816 - defaultDataSerializer : function(data) { 1817 - var uri = []; 1818 - for (var k in data) { 1819 - uri.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k])); 1820 - } 1821 - return uri.join('&'); 1822 - } 1823 - }, 1824 - 1825 - properties : { 1826 - URI : null, 1827 - data : null, 1828 - dataSerializer : null, 1829 - /** 1830 - * Configure which HTTP method to use for the request. Permissible values 1831 - * are "POST" (default) or "GET". 1832 - * 1833 - * @param string HTTP method, one of "POST" or "GET". 1834 - */ 1835 - method : 'POST', 1836 - file : null, 1837 - raw : false, 1838 - 1839 - /** 1840 - * Configure a timeout, in milliseconds. If the request has not resolved 1841 - * (either with success or with an error) within the provided timeframe, 1842 - * it will automatically fail with error JX.Request.ERROR_TIMEOUT. 1843 - * 1844 - * @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds). 1845 - */ 1846 - timeout : null 1847 - }, 1848 - 1849 - initialize : function() { 1850 - JX.Stratcom.listen('unload', null, JX.Request.shutdown); 1851 - } 1852 - 1853 - }); 1854 - 1855 - /** 1856 - * @requires javelin-install javelin-event 1857 - * @provides javelin-vector 1858 - * @javelin 1859 - */ 1860 - 1861 - /** 1862 - * Query and update positions and dimensions of nodes (and other things) 1863 - * within a document. 'V' stands for 'Vector'. Each vector has two elements, 1864 - * 'x' and 'y', which usually represent width/height (a "dimension vector") or 1865 - * left/top (a "position vector"). 1866 - * 1867 - * Vectors are used to manage the sizes and positions of elements, events, 1868 - * the document, and the viewport (the visible section of the document, i.e. 1869 - * how much of the page the user can actually see in their browser window). 1870 - * Unlike most Javelin classes, @{JX.$V} exposes two bare properties, 'x' and 1871 - * 'y'. You can read and manipulate these directly: 1872 - * 1873 - * // Give the user information about elements when they click on them. 1874 - * JX.Stratcom.listen( 1875 - * 'click', 1876 - * null, 1877 - * function(e) { 1878 - * var p = JX.$V(e); 1879 - * var d = JX.$V.getDim(e.getTarget()); 1880 - * 1881 - * alert('You clicked at <'+p.x+','+p.y'>; the element you clicked '+ 1882 - * 'is '+d.x+' pixels wide and '+d.y+' pixels high.'); 1883 - * }); 1884 - * 1885 - * You can also update positions and dimensions using vectors: 1886 - * 1887 - * // When the user clicks on something, make it 10px wider and 10px taller. 1888 - * JX.Stratcom.listen( 1889 - * 'click', 1890 - * null, 1891 - * function(e) { 1892 - * var t = e.getTarget(); 1893 - * JX.$V(t).add(10, 10).setDim(t); 1894 - * }); 1895 - * 1896 - * Additionally, vectors can be used to query document and viewport information: 1897 - * 1898 - * var v = JX.$V.getViewport(); // Viewport (window) width and height. 1899 - * var d = JX.$V.getDocument(); // Document width and height. 1900 - * var visible_area = parseInt(100 * (v.x * v.y) / (d.x * d.y), 10); 1901 - * alert('You can currently see '+visible_area'+ percent of the document.'); 1902 - * 1903 - * @author epriestley 1904 - * 1905 - * @task query Querying Positions and Dimensions 1906 - * @task update Changing Positions and Dimensions 1907 - * @task manip Manipulating Vectors 1908 - * 1909 - */ 1910 - JX.install('$V', { 1911 - 1912 - /** 1913 - * Construct a vector, either from explicit coordinates or from a node 1914 - * or event. You can pass two Numbers to construct an explicit vector: 1915 - * 1916 - * var v = JX.$V(35, 42); 1917 - * 1918 - * Otherwise, you can pass a @{JX.Event} or a Node to implicitly construct a 1919 - * vector: 1920 - * 1921 - * var u = JX.$V(some_event); 1922 - * var v = JX.$V(some_node); 1923 - * 1924 - * These are just like calling getPos() on the @{JX.Event} or Node. 1925 - * 1926 - * For convenience, @{JX.$V()} constructs a new vector even without the 'new' 1927 - * keyword. That is, these are equivalent: 1928 - * 1929 - * var q = new JX.$V(x, y); 1930 - * var r = JX.$V(x, y); 1931 - * 1932 - * Methods like getScroll(), getViewport() and getDocument() also create 1933 - * new vectors. 1934 - * 1935 - * Once you have a vector, you can manipulate it with add(): 1936 - * 1937 - * var u = JX.$V(35, 42); 1938 - * var v = u.add(5, -12); // v = <40, 30> 1939 - * 1940 - * @param wild 'x' component of the vector, or a @{JX.Event}, or a Node. 1941 - * @param Number? If providing an 'x' component, the 'y' component of the 1942 - * vector. 1943 - * @return @{JX.$V} Specified vector. 1944 - * @task query 1945 - */ 1946 - construct : function(x, y) { 1947 - if (this == JX || this == window) { 1948 - return new JX.$V(x, y); 1949 - } 1950 - if (typeof y == 'undefined') { 1951 - return JX.$V.getPos(x); 1952 - } 1953 - 1954 - this.x = parseFloat(x); 1955 - this.y = parseFloat(y); 1956 - }, 1957 - canCallAsFunction : true, 1958 - members : { 1959 - x : null, 1960 - y : null, 1961 - 1962 - /** 1963 - * Move a node around by setting the position of a Node to the vector's 1964 - * coordinates. For instance, if you want to move an element to the top left 1965 - * corner of the document, you could do this (assuming it has 'position: 1966 - * absolute'): 1967 - * 1968 - * JX.$V(0, 0).setPos(node); 1969 - * 1970 - * @param Node Node to move. 1971 - * @return this 1972 - * @task update 1973 - */ 1974 - setPos : function(node) { 1975 - node.style.left = (this.x === null) ? '' : (parseInt(this.x, 10) + 'px'); 1976 - node.style.top = (this.y === null) ? '' : (parseInt(this.y, 10) + 'px'); 1977 - return this; 1978 - }, 1979 - 1980 - /** 1981 - * Change the size of a node by setting its dimensions to the vector's 1982 - * coordinates. For instance, if you want to change an element to be 100px 1983 - * by 100px: 1984 - * 1985 - * JX.$V(100, 100).setDim(node); 1986 - * 1987 - * Or if you want to expand a node's dimensions by 50px: 1988 - * 1989 - * JX.$V(node).add(50, 50).setDim(node); 1990 - * 1991 - * @param Node Node to resize. 1992 - * @return this 1993 - * @task update 1994 - */ 1995 - setDim : function(node) { 1996 - node.style.width = 1997 - (this.x === null) 1998 - ? '' 1999 - : (parseInt(this.x, 10) + 'px'); 2000 - node.style.height = 2001 - (this.y === null) 2002 - ? '' 2003 - : (parseInt(this.y, 10) + 'px'); 2004 - return this; 2005 - }, 2006 - 2007 - /** 2008 - * Change a vector's x and y coordinates by adding numbers to them, or 2009 - * adding the coordinates of another vector. For example: 2010 - * 2011 - * var u = JX.$V(3, 4).add(100, 200); // u = <103, 204> 2012 - * 2013 - * You can also add another vector: 2014 - * 2015 - * var q = JX.$V(777, 999); 2016 - * var r = JX.$V(1000, 2000); 2017 - * var s = q.add(r); // s = <1777, 2999> 2018 - * 2019 - * Note that this method returns a new vector. It does not modify the 2020 - * 'this' vector. 2021 - * 2022 - * @param wild Value to add to the vector's x component, or another 2023 - * vector. 2024 - * @param Number? Value to add to the vector's y component. 2025 - * @return @{JX.$V} New vector, with summed components. 2026 - * @task manip 2027 - */ 2028 - add : function(x, y) { 2029 - if (x instanceof JX.$V) { 2030 - return this.add(x.x, x.y); 2031 - } 2032 - return JX.$V(this.x + parseFloat(x), this.y + parseFloat(y)); 2033 - } 2034 - }, 2035 - statics : { 2036 - _viewport: null, 2037 - 2038 - /** 2039 - * Determine where in a document an element is (or where an event, like 2040 - * a click, occurred) by building a new vector containing the position of a 2041 - * Node or @{JX.Event}. The 'x' component of the vector will correspond to 2042 - * the pixel offset of the argument relative to the left edge of the 2043 - * document, and the 'y' component will correspond to the pixel offset of 2044 - * the argument relative to the top edge of the document. Note that all 2045 - * vectors are generated in document coordinates, so the scroll position 2046 - * does not affect them. 2047 - * 2048 - * See also getDim(), used to determine an element's dimensions. 2049 - * 2050 - * @param Node|@{JX.Event} Node or event to determine the position of. 2051 - * @return @{JX.$V} New vector with the argument's position. 2052 - * @task query 2053 - */ 2054 - getPos : function(node) { 2055 - 2056 - JX.Event && (node instanceof JX.Event) && (node = node.getRawEvent()); 2057 - 2058 - if (('pageX' in node) || ('clientX' in node)) { 2059 - var c = JX.$V._viewport; 2060 - return JX.$V( 2061 - node.pageX || (node.clientX + c.scrollLeft), 2062 - node.pageY || (node.clientY + c.scrollTop)); 2063 - } 2064 - 2065 - var x = node.offsetLeft; 2066 - var y = node.offsetTop; 2067 - while (node.offsetParent && (node.offsetParent != document.body)) { 2068 - node = node.offsetParent; 2069 - x += node.offsetLeft; 2070 - y += node.offsetTop; 2071 - } 2072 - 2073 - return JX.$V(x, y); 2074 - }, 2075 - 2076 - /** 2077 - * Determine the width and height of a node by building a new vector with 2078 - * dimension information. The 'x' component of the vector will correspond 2079 - * to the element's width in pixels, and the 'y' component will correspond 2080 - * to its height in pixels. 2081 - * 2082 - * See also getPos(), used to determine an element's position. 2083 - * 2084 - * @param Node Node to determine the display size of. 2085 - * @return @{JX.$V} New vector with the node's dimensions. 2086 - * @task query 2087 - */ 2088 - getDim : function(node) { 2089 - return JX.$V(node.offsetWidth, node.offsetHeight); 2090 - }, 2091 - 2092 - /** 2093 - * Determine the current scroll position by building a new vector where 2094 - * the 'x' component corresponds to how many pixels the user has scrolled 2095 - * from the left edge of the document, and the 'y' component corresponds to 2096 - * how many pixels the user has scrolled from the top edge of the document. 2097 - * 2098 - * See also getViewport(), used to determine the size of the viewport. 2099 - * 2100 - * @return @{JX.$V} New vector with the document scroll position. 2101 - * @task query 2102 - */ 2103 - getScroll : function() { 2104 - // We can't use $V._viewport here because there's diversity between 2105 - // browsers with respect to where position/dimension and scroll position 2106 - // information is stored. 2107 - var b = document.body; 2108 - var e = document.documentElement; 2109 - return JX.$V(b.scrollLeft || e.scrollLeft, b.scrollTop || e.scrollTop); 2110 - }, 2111 - 2112 - /** 2113 - * Determine the size of the viewport (basically, the browser window) by 2114 - * building a new vector where the 'x' component corresponds to the width 2115 - * of the viewport in pixels and the 'y' component corresponds to the height 2116 - * of the viewport in pixels. 2117 - * 2118 - * See also getScroll(), used to determine the position of the viewport, and 2119 - * getDocument(), used to determine the size of the entire document. 2120 - * 2121 - * @return @{JX.$V} New vector with the viewport dimensions. 2122 - * @task query 2123 - */ 2124 - getViewport : function() { 2125 - var c = JX.$V._viewport; 2126 - var w = window; 2127 - 2128 - return JX.$V( 2129 - w.innerWidth || c.clientWidth || 0, 2130 - w.innerHeight || c.clientHeight || 0 2131 - ); 2132 - }, 2133 - 2134 - /** 2135 - * Determine the size of the document, including any area outside the 2136 - * current viewport which the user would need to scroll in order to see, by 2137 - * building a new vector where the 'x' component corresponds to the document 2138 - * width in pixels and the 'y' component corresponds to the document height 2139 - * in pixels. 2140 - * 2141 - * @return @{JX.$V} New vector with the document dimensions. 2142 - * @task query 2143 - */ 2144 - getDocument : function() { 2145 - var c = JX.$V._viewport; 2146 - return JX.$V(c.scrollWidth || 0, c.scrollHeight || 0); 2147 - } 2148 - }, 2149 - 2150 - /** 2151 - * On initialization, the browser-dependent viewport root is determined and 2152 - * stored. 2153 - * 2154 - * In ##__DEV__##, @{JX.$V} installs a toString() method so vectors print in a 2155 - * debuggable way: 2156 - * 2157 - * <23, 92> 2158 - * 2159 - * @return void 2160 - */ 2161 - initialize : function() { 2162 - var c = ((c = document) && (c = c.documentElement)) || 2163 - ((c = document) && (c = c.body)) 2164 - JX.$V._viewport = c; 2165 - 2166 - if (__DEV__) { 2167 - JX.$V.prototype.toString = function() { 2168 - return '<'+this.x+', '+this.y+'>'; 2169 - } 2170 - } 2171 - 2172 - } 2173 - }); 2174 - /** 2175 - * @requires javelin-install javelin-util javelin-vector javelin-stratcom 2176 - * @provides javelin-dom 2177 - * 2178 - * @javelin-installs JX.$ 2179 - * @javelin-installs JX.$N 2180 - * 2181 - * @javelin 2182 - */ 2183 - 2184 - 2185 - /** 2186 - * Select an element by its "id" attribute, like ##document.getElementById()##. 2187 - * For example: 2188 - * 2189 - * var node = JX.$('some_id'); 2190 - * 2191 - * This will select the node with the specified "id" attribute: 2192 - * 2193 - * LANG=HTML 2194 - * <div id="some_id">...</div> 2195 - * 2196 - * If the specified node does not exist, @{JX.$()} will throw ##JX.$.NotFound##. 2197 - * For other ways to select nodes from the document, see @{JX.DOM.scry()} and 2198 - * @{JX.DOM.find()}. 2199 - * 2200 - * @param string "id" attribute to select from the document. 2201 - * @return Node Node with the specified "id" attribute. 2202 - */ 2203 - JX.$ = function(id) { 2204 - 2205 - if (__DEV__) { 2206 - if (!id) { 2207 - throw new Error('Empty ID passed to JX.$()!'); 2208 - } 2209 - } 2210 - 2211 - var node = document.getElementById(id); 2212 - if (!node || (node.id != id)) { 2213 - if (__DEV__) { 2214 - if (node && (node.id != id)) { 2215 - throw new Error( 2216 - 'JX.$("'+id+'"): '+ 2217 - 'document.getElementById() returned an element without the '+ 2218 - 'correct ID. This usually means that the element you are trying '+ 2219 - 'to select is being masked by a form with the same value in its '+ 2220 - '"name" attribute.'); 2221 - } 2222 - } 2223 - throw JX.$.NotFound; 2224 - } 2225 - 2226 - return node; 2227 - }; 2228 - 2229 - JX.$.NotFound = {}; 2230 - if (__DEV__) { 2231 - // If we're in dev, upgrade this object into an Error so that it will 2232 - // print something useful if it escapes the stack after being thrown. 2233 - JX.$.NotFound = new Error( 2234 - 'JX.$() or JX.DOM.find() call matched no nodes.'); 2235 - } 2236 - 2237 - 2238 - /** 2239 - * Upcast a string into an HTML object so it is treated as markup instead of 2240 - * plain text. See @{JX.$N} for discussion of Javelin's security model. Every 2241 - * time you call this function you potentially open up a security hole. Avoid 2242 - * its use wherever possible. 2243 - * 2244 - * This class intentionally supports only a subset of HTML because many browsers 2245 - * named "Internet Explorer" have awkward restrictions around what they'll 2246 - * accept for conversion to document fragments. Alter your datasource to emit 2247 - * valid HTML within this subset if you run into an unsupported edge case. All 2248 - * the edge cases are crazy and you should always be reasonably able to emit 2249 - * a cohesive tag instead of an unappendable fragment. 2250 - * 2251 - * @task build String into HTML 2252 - * @task nodes HTML into Nodes 2253 - */ 2254 - JX.install('HTML', { 2255 - 2256 - /** 2257 - * Build a new HTML object from a trustworthy string. 2258 - * 2259 - * @task build 2260 - * @param string A string which you want to be treated as HTML, because you 2261 - * know it is from a trusted source and any data in it has been 2262 - * properly escaped. 2263 - * @return JX.HTML HTML object, suitable for use with @{JX.$N}. 2264 - */ 2265 - construct : function(str) { 2266 - if (this == JX || this == window) { 2267 - return new JX.HTML(str); 2268 - } 2269 - 2270 - if (__DEV__) { 2271 - var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup', 2272 - 'caption', 'tr', 'th', 'td', 'option']; 2273 - 2274 - var evil_stuff = new RegExp('^\\s*<('+tags.join('|')+')\\b', 'i'); 2275 - var match = null; 2276 - if (match = str.match(evil_stuff)) { 2277 - throw new Error( 2278 - 'JX.HTML("<'+match[1]+'>..."): '+ 2279 - 'call initializes an HTML object with an invalid partial fragment '+ 2280 - 'and can not be converted into DOM nodes. The enclosing tag of an '+ 2281 - 'HTML content string must be appendable to a document fragment. '+ 2282 - 'For example, <table> is allowed but <tr> or <tfoot> are not.'); 2283 - } 2284 - 2285 - var really_evil = /<script\b/; 2286 - if (str.match(really_evil)) { 2287 - throw new Error( 2288 - 'JX.HTML("...<script>..."): '+ 2289 - 'call initializes an HTML object with an embedded script tag! '+ 2290 - 'Are you crazy?! Do NOT do this!!!'); 2291 - } 2292 - 2293 - var wont_work = /<object\b/; 2294 - if (str.match(wont_work)) { 2295 - throw new Error( 2296 - 'JX.HTML("...<object>..."): '+ 2297 - 'call initializes an HTML object with an embedded <object> tag. IE '+ 2298 - 'will not do the right thing with this.'); 2299 - } 2300 - 2301 - // TODO(epriestley): May need to deny <option> more broadly, see 2302 - // http://support.microsoft.com/kb/829907 and the whole mess in the 2303 - // heavy stack. But I seem to have gotten away without cloning into the 2304 - // documentFragment below, so this may be a nonissue. 2305 - } 2306 - 2307 - this._content = str; 2308 - }, 2309 - canCallAsFunction : true, 2310 - members : { 2311 - _content : null, 2312 - /** 2313 - * Convert the raw HTML string into a DOM node tree. 2314 - * 2315 - * @task nodes 2316 - * @return DocumentFragment A document fragment which contains the nodes 2317 - * corresponding to the HTML string you provided. 2318 - */ 2319 - getFragment : function() { 2320 - var wrapper = JX.$N('div'); 2321 - wrapper.innerHTML = this._content; 2322 - var fragment = document.createDocumentFragment(); 2323 - while (wrapper.firstChild) { 2324 - // TODO(epriestley): Do we need to do a bunch of cloning junk here? 2325 - // See heavy stack. I'm disconnecting the nodes instead; this seems 2326 - // to work but maybe my test case just isn't extensive enough. 2327 - fragment.appendChild(wrapper.removeChild(wrapper.firstChild)); 2328 - } 2329 - return fragment; 2330 - } 2331 - } 2332 - }); 2333 - 2334 - 2335 - /** 2336 - * Create a new DOM node with attributes and content. 2337 - * 2338 - * var link = JX.$N('a'); 2339 - * 2340 - * This creates a new, empty anchor tag without any attributes. The equivalent 2341 - * markup would be: 2342 - * 2343 - * LANG=HTML 2344 - * <a /> 2345 - * 2346 - * You can also specify attributes by passing a dictionary: 2347 - * 2348 - * JX.$N('a', {name: 'anchor'}); 2349 - * 2350 - * This is equivalent to: 2351 - * 2352 - * LANG=HTML 2353 - * <a name="anchor" /> 2354 - * 2355 - * Additionally, you can specify content: 2356 - * 2357 - * JX.$N( 2358 - * 'a', 2359 - * {href: 'http://www.javelinjs.com'}, 2360 - * 'Visit the Javelin Homepage'); 2361 - * 2362 - * This is equivalent to: 2363 - * 2364 - * LANG=HTML 2365 - * <a href="http://www.javelinjs.com">Visit the Javelin Homepage</a> 2366 - * 2367 - * If you only want to specify content, you can omit the attribute parameter. 2368 - * That is, these calls are equivalent: 2369 - * 2370 - * JX.$N('div', {}, 'Lorem ipsum...'); // No attributes. 2371 - * JX.$N('div', 'Lorem ipsum...') // Same as above. 2372 - * 2373 - * Both are equivalent to: 2374 - * 2375 - * LANG=HTML 2376 - * <div>Lorem ipsum...</div> 2377 - * 2378 - * Note that the content is treated as plain text, not HTML. This means it is 2379 - * safe to use untrusted strings: 2380 - * 2381 - * JX.$N('div', '<script src="evil.com" />'); 2382 - * 2383 - * This is equivalent to: 2384 - * 2385 - * LANG=HTML 2386 - * <div>&lt;script src="evil.com" /&gt;</div> 2387 - * 2388 - * That is, the content will be properly escaped and will not create a 2389 - * vulnerability. If you want to set HTML content, you can use @{JX.HTML}: 2390 - * 2391 - * JX.$N('div', JX.HTML(some_html)); 2392 - * 2393 - * **This is potentially unsafe**, so make sure you understand what you're 2394 - * doing. You should usually avoid passing HTML around in string form. See 2395 - * @{JX.HTML} for discussion. 2396 - * 2397 - * You can create new nodes with a Javelin sigil (and, optionally, metadata) by 2398 - * providing "sigil" and "metadata" keys in the attribute dictionary. 2399 - * 2400 - * @param string Tag name, like 'a' or 'div'. 2401 - * @param dict|string|@{JX.HTML}? Property dictionary, or content if you don't 2402 - * want to specify any properties. 2403 - * @param string|@{JX.HTML}? Content string (interpreted as plain text) 2404 - * or @{JX.HTML} object (interpreted as HTML, 2405 - * which may be dangerous). 2406 - * @return Node New node with whatever attributes and 2407 - * content were specified. 2408 - */ 2409 - JX.$N = function(tag, attr, content) { 2410 - if (typeof content == 'undefined' && 2411 - (typeof attr != 'object' || attr instanceof JX.HTML)) { 2412 - content = attr; 2413 - attr = {}; 2414 - } 2415 - 2416 - if (__DEV__) { 2417 - if (tag.toLowerCase() != tag) { 2418 - throw new Error( 2419 - '$N("'+tag+'", ...): '+ 2420 - 'tag name must be in lower case; '+ 2421 - 'use "'+tag.toLowerCase()+'", not "'+tag+'".'); 2422 - } 2423 - } 2424 - 2425 - var node = document.createElement(tag); 2426 - 2427 - if (attr.style) { 2428 - JX.copy(node.style, attr.style); 2429 - delete attr.style; 2430 - } 2431 - 2432 - if (attr.sigil) { 2433 - JX.Stratcom.addSigil(node, attr.sigil); 2434 - delete attr.sigil; 2435 - } 2436 - 2437 - if (attr.meta) { 2438 - JX.Stratcom.addData(node, attr.meta); 2439 - delete attr.meta; 2440 - } 2441 - 2442 - if (__DEV__) { 2443 - if (('metadata' in attr) || ('data' in attr)) { 2444 - throw new Error( 2445 - '$N(' + tag + ', ...): ' + 2446 - 'use the key "meta" to specify metadata, not "data" or "metadata".'); 2447 - } 2448 - } 2449 - 2450 - JX.copy(node, attr); 2451 - if (content) { 2452 - JX.DOM.setContent(node, content); 2453 - } 2454 - return node; 2455 - }; 2456 - 2457 - 2458 - /** 2459 - * Query and update the DOM. Everything here is static, this is essentially 2460 - * a collection of common utility functions. 2461 - * 2462 - * @task stratcom Attaching Event Listeners 2463 - * @task content Changing DOM Content 2464 - * @task nodes Updating Nodes 2465 - * @task test Testing DOM Properties 2466 - * @task convenience Convenience Methods 2467 - * @task query Finding Nodes in the DOM 2468 - * @task view Changing View State 2469 - */ 2470 - JX.install('DOM', { 2471 - statics : { 2472 - _autoid : 0, 2473 - _metrics : {}, 2474 - 2475 - /** 2476 - * @task content 2477 - */ 2478 - setContent : function(node, content) { 2479 - if (__DEV__) { 2480 - if (!JX.DOM.isNode(node)) { 2481 - throw new Error( 2482 - 'JX.DOM.setContent(<yuck>, ...): '+ 2483 - 'first argument must be a DOM node.'); 2484 - } 2485 - } 2486 - 2487 - while (node.firstChild) { 2488 - JX.DOM.remove(node.firstChild); 2489 - } 2490 - JX.DOM.appendContent(node, content); 2491 - }, 2492 - 2493 - 2494 - /** 2495 - * @task content 2496 - */ 2497 - prependContent : function(node, content) { 2498 - if (__DEV__) { 2499 - if (!JX.DOM.isNode(node)) { 2500 - throw new Error( 2501 - 'JX.DOM.prependContent(<junk>, ...): '+ 2502 - 'first argument must be a DOM node.'); 2503 - } 2504 - } 2505 - 2506 - this._insertContent(node, content, this._mechanismPrepend); 2507 - }, 2508 - 2509 - 2510 - /** 2511 - * @task content 2512 - */ 2513 - appendContent : function(node, content) { 2514 - if (__DEV__) { 2515 - if (!JX.DOM.isNode(node)) { 2516 - throw new Error( 2517 - 'JX.DOM.appendContent(<bleh>, ...): '+ 2518 - 'first argument must be a DOM node.'); 2519 - } 2520 - } 2521 - 2522 - this._insertContent(node, content, this._mechanismAppend); 2523 - }, 2524 - 2525 - 2526 - /** 2527 - * @task content 2528 - */ 2529 - _mechanismPrepend : function(node, content) { 2530 - node.insertBefore(content, node.firstChild); 2531 - }, 2532 - 2533 - 2534 - /** 2535 - * @task content 2536 - */ 2537 - _mechanismAppend : function(node, content) { 2538 - node.appendChild(content); 2539 - }, 2540 - 2541 - 2542 - /** 2543 - * @task content 2544 - */ 2545 - _insertContent : function(parent, content, mechanism) { 2546 - if (content === null || typeof content == 'undefined') { 2547 - return; 2548 - } 2549 - if (content instanceof JX.HTML) { 2550 - content = content.getFragment(); 2551 - } 2552 - if (content instanceof Array) { 2553 - for (var ii = 0; ii < content.length; ii++) { 2554 - var child = (typeof content[ii] == 'string') 2555 - ? document.createTextNode(content[ii]) 2556 - : content[ii]; 2557 - mechanism(parent, child); 2558 - } 2559 - } else if (content.nodeType) { 2560 - mechanism(parent, content); 2561 - } else { 2562 - mechanism(parent, document.createTextNode(content)); 2563 - } 2564 - }, 2565 - 2566 - 2567 - /** 2568 - * @task nodes 2569 - */ 2570 - remove : function(node) { 2571 - node.parentNode && JX.DOM.replace(node, null); 2572 - return node; 2573 - }, 2574 - 2575 - 2576 - /** 2577 - * @task nodes 2578 - */ 2579 - replace : function(node, replacement) { 2580 - if (__DEV__) { 2581 - if (!node.parentNode) { 2582 - throw new Error( 2583 - 'JX.DOM.replace(<node>, ...): '+ 2584 - 'node has no parent node, so it can not be replaced.'); 2585 - } 2586 - } 2587 - 2588 - var mechanism; 2589 - if (node.nextSibling) { 2590 - mechanism = JX.bind(node.nextSibling, function(parent, content) { 2591 - parent.insertBefore(content, this); 2592 - }); 2593 - } else { 2594 - mechanism = this._mechanismAppend; 2595 - } 2596 - var parent = node.parentNode; 2597 - node.parentNode.removeChild(node); 2598 - this._insertContent(parent, replacement, mechanism); 2599 - 2600 - return node; 2601 - }, 2602 - 2603 - 2604 - /** 2605 - * Retrieve the nearest parent node matching the desired sigil. 2606 - * @param Node The child element to search from 2607 - * @return The matching parent or null if no parent could be found 2608 - * @author jgabbard 2609 - */ 2610 - nearest : function(node, sigil) { 2611 - while (node && node.getAttribute && !JX.Stratcom.hasSigil(node, sigil)) { 2612 - node = node.parentNode; 2613 - } 2614 - return node; 2615 - }, 2616 - 2617 - 2618 - serialize : function(form) { 2619 - var elements = form.getElementsByTagName('*'); 2620 - var data = {}; 2621 - for (var ii = 0; ii < elements.length; ++ii) { 2622 - if (!elements[ii].name) { 2623 - continue; 2624 - } 2625 - var type = elements[ii].type; 2626 - var tag = elements[ii].tagName; 2627 - if ((type in {radio: 1, checkbox: 1} && elements[ii].checked) || 2628 - type in {text: 1, hidden: 1, password: 1} || 2629 - tag in {TEXTAREA: 1, SELECT: 1}) { 2630 - data[elements[ii].name] = elements[ii].value; 2631 - } 2632 - } 2633 - return data; 2634 - }, 2635 - 2636 - 2637 - /** 2638 - * Test if an object is a valid Node. 2639 - * 2640 - * @task test 2641 - * @param wild Something which might be a Node. 2642 - * @return bool True if the parameter is a DOM node. 2643 - */ 2644 - isNode : function(node) { 2645 - return !!(node && node.nodeName && (node !== window)); 2646 - }, 2647 - 2648 - 2649 - /** 2650 - * Test if an object is a node of some specific (or one of several) types. 2651 - * For example, this tests if the node is an ##<input />##, ##<select />##, 2652 - * or ##<textarea />##. 2653 - * 2654 - * JX.DOM.isType(node, ['input', 'select', 'textarea']); 2655 - * 2656 - * @task test 2657 - * @param wild Something which might be a Node. 2658 - * @param string|list One or more tags which you want to test for. 2659 - * @return bool True if the object is a node, and it's a node of one 2660 - * of the provided types. 2661 - */ 2662 - isType : function(node, of_type) { 2663 - node = ('' + (node.nodeName || '')).toUpperCase(); 2664 - of_type = JX.$AX(of_type); 2665 - for (var ii = 0; ii < of_type.length; ++ii) { 2666 - if (of_type[ii].toUpperCase() == node) { 2667 - return true; 2668 - } 2669 - } 2670 - return false; 2671 - }, 2672 - 2673 - /** 2674 - * Listen for events occuring beneath a specific node in the DOM. This is 2675 - * similar to @{JX.Stratcom.listen()}, but allows you to specify some node 2676 - * which serves as a scope instead of the default scope (the whole document) 2677 - * which you get if you install using @{JX.Stratcom.listen()} directly. For 2678 - * example, to listen for clicks on nodes with the sigil 'menu-item' below 2679 - * the root menu node: 2680 - * 2681 - * var the_menu = getReferenceToTheMenuNodeSomehow(); 2682 - * JX.DOM.listen(the_menu, 'click', 'menu-item', function(e) { ... }); 2683 - * 2684 - * @task stratcom 2685 - * @param Node The node to listen for events underneath. 2686 - * @param string|list One or more event types to listen for. 2687 - * @param list? A path to listen on, or a list of paths. 2688 - * @param function Callback to invoke when a matching event occurs. 2689 - * @return object A reference to the installed listener. You can later 2690 - * remove the listener by calling this object's remove() 2691 - * method. 2692 - */ 2693 - listen : function(node, type, path, callback) { 2694 - var id = ['id:' + JX.DOM.uniqID(node)]; 2695 - path = JX.$AX(path || []); 2696 - if (!path.length) { 2697 - path = id; 2698 - } else { 2699 - for (var ii = 0; ii < path.length; ii++) { 2700 - path[ii] = id.concat(JX.$AX(path[ii])); 2701 - } 2702 - } 2703 - return JX.Stratcom.listen(type, path, callback); 2704 - }, 2705 - 2706 - uniqID : function(node) { 2707 - if (!node.id) { 2708 - node.id = 'autoid_'+(++JX.DOM._autoid); 2709 - } 2710 - return node.id; 2711 - }, 2712 - 2713 - alterClass : function(node, className, add) { 2714 - var has = ((' '+node.className+' ').indexOf(' '+className+' ') > -1); 2715 - if (add && !has) { 2716 - node.className += ' '+className; 2717 - } else if (has && !add) { 2718 - node.className = node.className.replace( 2719 - new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), ' '); 2720 - } 2721 - }, 2722 - 2723 - htmlize : function(str) { 2724 - return (''+str) 2725 - .replace(/&/g, '&amp;') 2726 - .replace(/"/g, '&quot;') 2727 - .replace(/</g, '&lt;') 2728 - .replace(/>/g, '&gt;'); 2729 - }, 2730 - 2731 - 2732 - /** 2733 - * Show one or more elements, by removing their "display" style. This 2734 - * assumes you have hidden them with hide(), or explicitly set the style 2735 - * to "display: none;". 2736 - * 2737 - * @task convenience 2738 - * @param ... One or more nodes to remove "display" styles from. 2739 - * @return void 2740 - */ 2741 - show : function() { 2742 - if (__DEV__) { 2743 - for (var ii = 0; ii < arguments.length; ++ii) { 2744 - if (!arguments[ii]) { 2745 - throw new Error( 2746 - 'JX.DOM.show(...): ' + 2747 - 'one or more arguments were null or empty.'); 2748 - } 2749 - } 2750 - } 2751 - 2752 - for (var ii = 0; ii < arguments.length; ++ii) { 2753 - arguments[ii].style.display = ''; 2754 - } 2755 - }, 2756 - 2757 - 2758 - /** 2759 - * Hide one or more elements, by setting "display: none;" on them. This is 2760 - * a convenience method. See also show(). 2761 - * 2762 - * @task convenience 2763 - * @param ... One or more nodes to set "display: none" on. 2764 - * @return void 2765 - */ 2766 - hide : function() { 2767 - if (__DEV__) { 2768 - for (var ii = 0; ii < arguments.length; ++ii) { 2769 - if (!arguments[ii]) { 2770 - throw new Error( 2771 - 'JX.DOM.hide(...): ' + 2772 - 'one or more arguments were null or empty.'); 2773 - } 2774 - } 2775 - } 2776 - 2777 - for (var ii = 0; ii < arguments.length; ++ii) { 2778 - arguments[ii].style.display = 'none'; 2779 - } 2780 - }, 2781 - 2782 - textMetrics : function(node, pseudoclass, x) { 2783 - if (!this._metrics[pseudoclass]) { 2784 - var n = JX.$N( 2785 - 'var', 2786 - {className: pseudoclass}); 2787 - this._metrics[pseudoclass] = n; 2788 - } 2789 - var proxy = this._metrics[pseudoclass]; 2790 - document.body.appendChild(proxy); 2791 - proxy.style.width = x ? (x+'px') : ''; 2792 - JX.DOM.setContent( 2793 - proxy, 2794 - JX.HTML(JX.DOM.htmlize(node.value).replace(/\n/g, '<br />'))); 2795 - var metrics = JX.$V.getDim(proxy); 2796 - document.body.removeChild(proxy); 2797 - return metrics; 2798 - }, 2799 - 2800 - 2801 - /** 2802 - * Search the document for DOM nodes by providing a root node to look 2803 - * beneath, a tag name, and (optionally) a sigil. Nodes which match all 2804 - * specified conditions are returned. 2805 - * 2806 - * @task query 2807 - * 2808 - * @param Node Root node to search beneath. 2809 - * @param string Tag name, like 'a' or 'textarea'. 2810 - * @param string Optionally, a sigil which nodes are required to have. 2811 - * 2812 - * @return list List of matching nodes, which may be empty. 2813 - */ 2814 - scry : function(root, tagname, sigil) { 2815 - if (__DEV__) { 2816 - if (!JX.DOM.isNode(root)) { 2817 - throw new Error( 2818 - 'JX.DOM.scry(<yuck>, ...): '+ 2819 - 'first argument must be a DOM node.'); 2820 - } 2821 - } 2822 - 2823 - var nodes = root.getElementsByTagName(tagname); 2824 - if (!sigil) { 2825 - return JX.$A(nodes); 2826 - } 2827 - var result = []; 2828 - for (var ii = 0; ii < nodes.length; ii++) { 2829 - if (JX.Stratcom.hasSigil(nodes[ii], sigil)) { 2830 - result.push(nodes[ii]); 2831 - } 2832 - } 2833 - return result; 2834 - }, 2835 - 2836 - 2837 - /** 2838 - * Select a node uniquely identified by a root, tagname and sigil. This 2839 - * is similar to JX.DOM.scry() but expects exactly one result. It will 2840 - * throw JX.$.NotFound if it matches no results. 2841 - * 2842 - * @task query 2843 - * 2844 - * @param Node Root node to search beneath. 2845 - * @param string Tag name, like 'a' or 'textarea'. 2846 - * @param string Optionally, sigil which selected node must have. 2847 - * 2848 - * @return Node Node uniquely identified by the criteria. 2849 - */ 2850 - find : function(root, tagname, sigil) { 2851 - if (__DEV__) { 2852 - if (!JX.DOM.isNode(root)) { 2853 - throw new Error( 2854 - 'JX.DOM.find(<glop>, "'+tagname+'", "'+sigil+'"): '+ 2855 - 'first argument must be a DOM node.'); 2856 - } 2857 - } 2858 - 2859 - var result = JX.DOM.scry(root, tagname, sigil); 2860 - 2861 - if (__DEV__) { 2862 - if (result.length > 1) { 2863 - throw new Error( 2864 - 'JX.DOM.find(<node>, "'+tagname+'", "'+sigil+'"): '+ 2865 - 'matched more than one node.'); 2866 - } 2867 - } 2868 - 2869 - if (!result.length) { 2870 - throw JX.$.NotFound; 2871 - } 2872 - 2873 - return result[0]; 2874 - }, 2875 - 2876 - 2877 - /** 2878 - * Focus a node safely. This is just a convenience wrapper that allows you 2879 - * to avoid IE's habit of throwing when nearly any focus operation is 2880 - * invoked. 2881 - * 2882 - * @task convenience 2883 - * @param Node Node to move cursor focus to, if possible. 2884 - * @return void 2885 - */ 2886 - focus : function(node) { 2887 - try { node.focus(); } catch (lol_ie) {} 2888 - }, 2889 - 2890 - /** 2891 - * Scroll to the position of an element in the document. 2892 - * @task view 2893 - * @param Node Node to move document scroll position to, if possible. 2894 - * @return void 2895 - */ 2896 - scrollTo : function(node) { 2897 - window.scrollTo(0, JX.$V(node).y); 2898 - } 2899 - } 2900 - }); 2901 - 2902 - /** 2903 - * Simple JSON serializer. 2904 - * 2905 - * @requires javelin-install javelin-util 2906 - * @provides javelin-json 2907 - * @javelin 2908 - */ 2909 - 2910 - JX.install('JSON', { 2911 - statics : { 2912 - serialize : function(obj) { 2913 - if (__DEV__) { 2914 - try { 2915 - return JX.JSON._val(obj); 2916 - } catch (x) { 2917 - JX.log( 2918 - 'JX.JSON.serialize(...): '+ 2919 - 'caught exception while serializing object. ('+x+')'); 2920 - } 2921 - } else { 2922 - return JX.JSON._val(obj); 2923 - } 2924 - }, 2925 - _val : function(val) { 2926 - var out = []; 2927 - if (val === null) { 2928 - return 'null'; 2929 - } else if (val.push && val.pop) { 2930 - for (var ii = 0; ii < val.length; ii++) { 2931 - if (typeof val[ii] != 'undefined') { 2932 - out.push(JX.JSON._val(val[ii])); 2933 - } 2934 - } 2935 - return '['+out.join(',')+']'; 2936 - } else if (val === true) { 2937 - return 'true'; 2938 - } else if (val === false) { 2939 - return 'false'; 2940 - } else if (typeof val == 'string') { 2941 - return JX.JSON._esc(val); 2942 - } else if (typeof val == 'number') { 2943 - return val; 2944 - } else { 2945 - for (var k in val) { 2946 - out.push(JX.JSON._esc(k)+':'+JX.JSON._val(val[k])); 2947 - } 2948 - return '{'+out.join(',')+'}'; 2949 - } 2950 - }, 2951 - _esc : function(str) { 2952 - return '"'+str.replace(/\\/g, '\\\\').replace(/"/g, '\\"')+'"'; 2953 - } 2954 - } 2955 - });
-3
webroot/rsrc/js/javelin/javelin.min.js
··· 1 - /** @provides javelin-lib-prod */ 2 - 3 - JX.$A=function(b){var c=[];for(var a=0;a<b.length;a++)c.push(b[a]);return c;};JX.$AX=function(a){return (a instanceof Array)?a:[a];};JX.copy=function(a,b){for(var c in b)a[c]=b[c];return a;};JX.bind=function(b,c,d){var a=JX.$A(arguments).slice(2);return function(){return c.apply(b||window,a.concat(JX.$A(arguments)));};};JX.bag=function(){};JX.keys=function(b){var c=[];for(var a in b)c.push(a);return c;};JX.defer=function(a,c){var b=setTimeout(a,c||0);return {stop:function(){clearTimeout(b);}};};JX.go=function(a){JX.Stratcom&&JX.Stratcom.invoke('go',null,{uri:a});(a&&(window.location=a))||window.location.reload(true);};JX.install=function(h,g){if(typeof JX.install._a=='undefined')JX.install._a=0;if(h in JX)return;if(!JX.install._b)JX.install._b=[];JX.install._b.push([h,g]);do{var d;var f=null;for(var c=0;c<JX.install._b.length;++c){d=JX.install._b[c][1];if(d.extend&&!JX[d.extend])continue;f=JX.install._b[c][0];JX.install._b.splice(c,1);--c;JX[f]=(function(m,l){var n=function(){this.__id__='__obj__'+(++JX.install._a);this.__super__=JX[l.extend]||JX.bag;this.__parent__=JX[m].prototype;if(JX[m].__prototyping__)return;return (l.construct||JX.bag).apply(this,arguments);};return n;})(f,d);JX.copy(JX[f],d.statics);JX[f].__prototyping__=0;var k;if(d.extend){JX[d.extend].__prototyping__++;k=JX[f].prototype=new JX[d.extend]();JX[d.extend].__prototyping__--;}else k=JX[f].prototype={};k.__class__=JX[f];for(var e in (d.properties||{})){var b=e.charAt(0).toUpperCase()+e.substr(1);var j='__auto__'+e;k[j]=d.properties[e];k['set'+b]=(function(l){return function(m){this[l]=m;return this;};})(j);k['get'+b]=(function(l){return function(){return this[l];};})(j);}JX.copy(k,d.members);if(d.events&&d.events.length){var i=JX[d.extend]||{};JX[f].__name__='class:'+f;var a=i.__path__||[];JX[f].__path__=a.concat([JX[f].__name__]);k.invoke=function(l){return JX.Stratcom.invoke('obj:'+l,this.__class__.__path__.concat([this.__id__]),{args:JX.$A(arguments).slice(1)});};k.listen=function(m,l){return JX.Stratcom.listen('obj:'+m,this.__id__,JX.bind(this,function(n){return l.apply(this,n.getData().args);}));};JX[f].listen=function(m,l){return JX.Stratcom.listen('obj:'+m,this.__name__,JX.bind(this,function(n){return l.apply(this,n.getData().args);}));};}(d.initialize||JX.bag)();}}while(f);};JX.install('Event',{members:{stop:function(){var a=this.getRawEvent();if(a){a.cancelBubble=true;a.stopPropagation&&a.stopPropagation();}this.setStopped(true);return this;},prevent:function(){var a=this.getRawEvent();if(a){a.returnValue=false;a.preventDefault&&a.preventDefault();}this.setPrevented(true);return this;},kill:function(){this.prevent();this.stop();return this;},getSpecialKey:function(){var a=this.getRawEvent();if(!a||a.shiftKey)return null;return JX.Event._c[a.keyCode]||null;},getNode:function(a){return this.getNodes()[a]||null;},getNodeData:function(a){return JX.Stratcom.getData(this.getNode(a));}},statics:{_c:{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:{rawEvent:null,type:null,target:null,data:null,path:[],stopped:false,prevented:false,nodes:{}},initialize:function(){}});JX.install('Stratcom',{statics:{ready:false,_d:{},_e:[],_f:{},_g:'*',_h:{},_i:[],_j:{focusin:'focus',focusout:'blur'},_k:2,_l:0,invoke:function(d,b,a){var c=new JX.Event().setType(d).setData(a||{}).setPath(b||[]);return this._m(c);},listen:function(k,h,a){var c=[];k=JX.$AX(k);if(!h)h=this._g;if(!(h instanceof Array)){h=[[h]];}else if(!(h[0] instanceof Array))h=[h];for(var d=0;d<k.length;++d){var i=k[d];if(('onpagehide' in window)&&i=='unload')i='pagehide';if(!(i in this._d))this._d[i]={};var j=this._d[i];for(var e=0;e<h.length;++e){var g=h[e];var b=this._e.length;this._e.push(a);this._f[b]=g.length;c.push(b);for(var f=0;f<g.length;++f){if(!j[g[f]])j[g[f]]=[];j[g[f]].push(b);}}}return {remove:function(){for(var l=0;l<c.length;l++)delete JX.Stratcom._e[c[l]];}};},dispatch:function(event){var f=[];var e={};var h=function(k,l){if(!e.hasOwnProperty(k)){e[k]=l;f.push(k);}};var j=event.srcElement||event.target;if(!j||!j.getAttribute)j=null;var a=j;while(a&&a.getAttribute){h('tag:'+a.nodeName.toLowerCase(),a);var c=a.id;if(c)h('id:'+c,a);var i=a.getAttribute('data-sigil');if(i){i=i.split(' ');for(var d=0;d<i.length;d++)h(i[d],a);}a=a.parentNode;}var b=event.type;if(b in this._j)b=this._j[b];var g=new JX.Event().setRawEvent(event).setType(b).setTarget(j).setNodes(e).setPath(f.reverse());return this._m(g);},_m:function(i){var k=this._d[i.getType()];if(!k)return i;var h=i.getPath();var f=h.length;var c={};var g;for(var j=-1;j<f;++j){if(j==-1){g=k[this._g];}else g=k[h[j]];if(!g)continue;for(var d=0;d<g.length;++d)c[g[d]]=(c[g[d]]||0)+1;}var a=[];for(var e in c)if(c[e]==this._f[e]){var b=this._e[e];if(b)a.push(b);}this._i.push({handlers:a,event:i,cursor:0});this.pass();this._i.pop();return i;},pass:function(){var a=this._i[this._i.length-1];while(a.cursor<a.handlers.length){var b=a.cursor;++a.cursor;(a.handlers[b]||JX.bag)(a.event);if(a.event.getStopped())break;}return a.event.getStopped()||a.event.getPrevented();},context:function(){var a=this._i.length;if(!a)return null;return this._i[a-1].event;},mergeData:function(a,b){this._h[a]=b;if(a==0){JX.Stratcom.ready=true;JX.__rawEventQueue({type:'start-queue'});}},hasSigil:function(a,b){var c=a.getAttribute('data-sigil');return c&&(' '+c+' ').indexOf(' '+b+' ')>-1;},addSigil:function(a,b){var c=a.getAttribute('data-sigil');if(c&&!JX.Stratcom.hasSigil(a,b))b=c+' '+b;a.setAttribute('data-sigil',b);},getData:function(e){var d=(e.getAttribute('data-meta')||'').split('_');if(d[0]&&d[1]){var a=this._h[d[0]];var c=d[1];if(a&&(c in a))return a[c];}var b={};if(!this._h[1])this._h[1]={};this._h[1][this._l]=b;e.setAttribute('data-meta','1_'+(this._l++));return b;},addData:function(b,a){return JX.copy(JX.Stratcom.getData(b),a);},allocateMetadataBlock:function(){return this._k++;}}});JX.behavior=function(b,a){JX.behavior._n[b]=a;};JX.initBehaviors=function(c){for(var d in c){var a=c[d];if(!a.length)if(JX.behavior._o.hasOwnProperty(d)){continue;}else a=[null];for(var b=0;b<a.length;b++)JX.behavior._n[d](a[b]);JX.behavior._o[d]=true;}};!function(a){a.behavior._n={};a.behavior._o={};}(JX);JX.install('Request',{construct:function(b,a){this.setURI(b);if(a)this.listen('done',a);},events:['send','done','error','finally'],members:{_p:null,_q:null,_r:false,_s:null,send:function(){var f=null;try{try{f=new XMLHttpRequest();}catch(e){f=new ActiveXObject("Msxml2.XMLHTTP");}}catch(e){f=new ActiveXObject("Microsoft.XMLHTTP");}this._q=f;this._p=JX.Request._t.length;JX.Request._t.push(this);f.onreadystatechange=JX.bind(this,this._u);var a=this.getData()||{};a.__ajax__=true;this._s=JX.Stratcom.allocateMetadataBlock();a.__metablock__=this._s;var c=(this.getDataSerializer()||JX.Request.defaultDataSerializer)(a);var d=this.getURI();var b=this.getMethod().toUpperCase();if(b=='GET')d+=((d.indexOf('?')===-1)?'?':'&')+c;this.invoke('send',this);if(this.getTimeout())this._v=JX.defer(JX.bind(this,this._w,JX.Request.ERROR_TIMEOUT),this.getTimeout());f.open(b,d,true);if(b=='POST'){if(this.getFile()){f.send(this.getFile());}else{f.setRequestHeader('Content-Type','application/x-www-form-urlencoded');f.send(c);}}else f.send(null);},abort:function(){this._x();},_u:function(){var xport=this._q;try{if(this._r)return;if(xport.readyState!=4)return;if(xport.status<200||xport.status>=300){this._w();return;}var text=xport.responseText.substring('for (;;);'.length);var response=eval('('+text+')');}catch(exception){this._w();return;}try{if(response.error){this._w(response.error);}else{JX.Stratcom.mergeData(this._s,response.javelin_metadata||{});this._y(response);JX.initBehaviors(response.javelin_behaviors||{});}}catch(exception){JX.defer(function(){throw exception;});}},_w:function(a){this._x();this.invoke('error',a,this);this.invoke('finally');},_y:function(b){this._x();if(b.onload)for(var a=0;a<b.onload.length;a++)(new Function(b.onload[a]))();this.invoke('done',this.getRaw()?b:b.payload,this);this.invoke('finally');},_x:function(){this._r=true;delete JX.Request._t[this._p];this._v&&this._v.stop();this._q.abort();}},statics:{_t:[],shutdown:function(){for(var a=0;a<JX.Request._t.length;a++)try{JX.Request._t[a]&&JX.Request._t[a].abort();}catch(b){}JX.Request._t=[];},ERROR_TIMEOUT:-9000,defaultDataSerializer:function(a){var c=[];for(var b in a)c.push(encodeURIComponent(b)+'='+encodeURIComponent(a[b]));return c.join('&');}},properties:{URI:null,data:null,dataSerializer:null,method:'POST',file:null,raw:false,timeout:null},initialize:function(){JX.Stratcom.listen('unload',null,JX.Request.shutdown);}});JX.install('$V',{construct:function(a,b){if(this==JX||this==window)return new JX.$V(a,b);if(typeof b=='undefined')return JX.$V.getPos(a);this.x=parseFloat(a);this.y=parseFloat(b);},canCallAsFunction:true,members:{x:null,y:null,setPos:function(a){a.style.left=(this.x===null)?'':(parseInt(this.x,10)+'px');a.style.top=(this.y===null)?'':(parseInt(this.y,10)+'px');return this;},setDim:function(a){a.style.width=(this.x===null)?'':(parseInt(this.x,10)+'px');a.style.height=(this.y===null)?'':(parseInt(this.y,10)+'px');return this;},add:function(a,b){if(a instanceof JX.$V)return this.add(a.x,a.y);return JX.$V(this.x+parseFloat(a),this.y+parseFloat(b));}},statics:{_z:null,getPos:function(b){JX.Event&&(b instanceof JX.Event)&&(b=b.getRawEvent());if(('pageX' in b)||('clientX' in b)){var a=JX.$V._z;return JX.$V(b.pageX||(b.clientX+a.scrollLeft),b.pageY||(b.clientY+a.scrollTop));}var c=b.offsetLeft;var d=b.offsetTop;while(b.offsetParent&&(b.offsetParent!=document.body)){b=b.offsetParent;c+=b.offsetLeft;d+=b.offsetTop;}return JX.$V(c,d);},getDim:function(a){return JX.$V(a.offsetWidth,a.offsetHeight);},getScroll:function(){var a=document.body;var b=document.documentElement;return JX.$V(a.scrollLeft||b.scrollLeft,a.scrollTop||b.scrollTop);},getViewport:function(){var a=JX.$V._z;var b=window;return JX.$V(b.innerWidth||a.clientWidth||0,b.innerHeight||a.clientHeight||0);},getDocument:function(){var a=JX.$V._z;return JX.$V(a.scrollWidth||0,a.scrollHeight||0);}},initialize:function(){var a=((a=document)&&(a=a.documentElement))||((a=document)&&(a=a.body));JX.$V._z=a;}});JX.$=function(a){var b=document.getElementById(a);if(!b||(b.id!=a))throw JX.$.NotFound;return b;};JX.$.NotFound={};JX.install('HTML',{construct:function(a){if(this==JX||this==window)return new JX.HTML(a);this._za=a;},canCallAsFunction:true,members:{_za:null,getFragment:function(){var b=JX.$N('div');b.innerHTML=this._za;var a=document.createDocumentFragment();while(b.firstChild)a.appendChild(b.removeChild(b.firstChild));return a;}}});JX.$N=function(d,a,b){if(typeof b=='undefined'&&(typeof a!='object'||a instanceof JX.HTML)){b=a;a={};}var c=document.createElement(d);if(a.style){JX.copy(c.style,a.style);delete a.style;}if(a.sigil){JX.Stratcom.addSigil(c,a.sigil);delete a.sigil;}if(a.meta){JX.Stratcom.addData(c,a.meta);delete a.meta;}JX.copy(c,a);if(b)JX.DOM.setContent(c,b);return c;};JX.install('DOM',{statics:{_zb:0,_zc:{},setContent:function(b,a){while(b.firstChild)JX.DOM.remove(b.firstChild);JX.DOM.appendContent(b,a);},prependContent:function(b,a){this._zd(b,a,this._ze);},appendContent:function(b,a){this._zd(b,a,this._zf);},_ze:function(b,a){b.insertBefore(a,b.firstChild);},_zf:function(b,a){b.appendChild(a);},_zd:function(e,b,d){if(b===null||typeof b=='undefined')return;if(b instanceof JX.HTML)b=b.getFragment();if(b instanceof Array){for(var c=0;c<b.length;c++){var a=(typeof b[c]=='string')?document.createTextNode(b[c]):b[c];d(e,a);}}else if(b.nodeType){d(e,b);}else d(e,document.createTextNode(b));},remove:function(a){a.parentNode&&JX.DOM.replace(a,null);return a;},replace:function(b,d){var a;if(b.nextSibling){a=JX.bind(b.nextSibling,function(f,e){f.insertBefore(e,this);});}else a=this._zf;var c=b.parentNode;b.parentNode.removeChild(b);this._zd(c,d,a);return b;},nearest:function(a,b){while(a&&a.getAttribute&&!JX.Stratcom.hasSigil(a,b))a=a.parentNode;return a;},serialize:function(c){var b=c.getElementsByTagName('*');var a={};for(var d=0;d<b.length;++d){if(!b[d].name)continue;var f=b[d].type;var e=b[d].tagName;if((f in {radio:1,checkbox:1}&&b[d].checked)||f in {text:1,hidden:1,password:1}||e in {TEXTAREA:1,SELECT:1})a[b[d].name]=b[d].value;}return a;},isNode:function(a){return !!(a&&a.nodeName&&(a!==window));},isType:function(b,c){b=(''+(b.nodeName||'')).toUpperCase();c=JX.$AX(c);for(var a=0;a<c.length;++a)if(c[a].toUpperCase()==b)return true;return false;},listen:function(b,d,c,a){return JX.Stratcom.listen(d,['id:'+JX.DOM.uniqID(b)].concat(JX.$AX(c||[])),a);},uniqID:function(a){if(!a.id)a.id='autoid_'+(++JX.DOM._zb);return a.id;},alterClass:function(d,b,a){var c=((' '+d.className+' ').indexOf(' '+b+' ')>-1);if(a&&!c){d.className+=' '+b;}else if(c&&!a)d.className=d.className.replace(new RegExp('(^|\\s)'+b+'(?:\\s|$)','g'),' ');},htmlize:function(a){return (''+a).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');},show:function(){for(var a=0;a<arguments.length;++a)arguments[a].style.display='';},hide:function(){for(var a=0;a<arguments.length;++a)arguments[a].style.display='none';},textMetrics:function(c,e,f){if(!this._zc[e]){var b=JX.$N('var',{className:e});this._zc[e]=b;}var d=this._zc[e];document.body.appendChild(d);d.style.width=f?(f+'px'):'';JX.DOM.setContent(d,JX.HTML(JX.DOM.htmlize(c.value).replace(/\n/g,'<br />')));var a=JX.$V.getDim(d);document.body.removeChild(d);return a;},scry:function(d,f,e){var b=d.getElementsByTagName(f);if(!e)return JX.$A(b);var c=[];for(var a=0;a<b.length;a++)if(JX.Stratcom.hasSigil(b[a],e))c.push(b[a]);return c;},find:function(b,d,c){var a=JX.DOM.scry(b,d,c);if(!a.length)throw JX.$.NotFound;return a[0];},focus:function(b){try{b.focus();}catch(a){}},scrollTo:function(a){window.scrollTo(0,JX.$V(a).y);}}});JX.install('JSON',{statics:{serialize:function(a){return JX.JSON._zg(a);},_zg:function(d){var c=[];if(d===null){return 'null';}else if(d.push&&d.pop){for(var a=0;a<d.length;a++)if(typeof d[a]!='undefined')c.push(JX.JSON._zg(d[a]));return '['+c.join(',')+']';}else if(d===true){return 'true';}else if(d===false){return 'false';}else if(typeof d=='string'){return JX.JSON._zh(d);}else if(typeof d=='number'){return d;}else{for(var b in d)c.push(JX.JSON._zh(b)+':'+JX.JSON._zg(d[b]));return '{'+c.join(',')+'}';}},_zh:function(a){return '"'+a.replace(/\\/g,'\\\\').replace(/"/g,'\\"')+'"';}}});
-1123
webroot/rsrc/js/javelin/typeahead.dev.js
··· 1 - /** @provides javelin-typeahead-dev */ 2 - 3 - /** 4 - * @requires javelin-install 5 - * javelin-dom 6 - * javelin-vector 7 - * javelin-util 8 - * @provides javelin-typeahead 9 - * @javelin 10 - */ 11 - 12 - /** 13 - * A typeahead is a UI component similar to a text input, except that it 14 - * suggests some set of results (like friends' names, common searches, or 15 - * repository paths) as the user types them. Familiar examples of this UI 16 - * include Google Suggest, the Facebook search box, and OS X's Spotlight 17 - * feature. 18 - * 19 - * To build a @{JX.Typeahead}, you need to do four things: 20 - * 21 - * 1. Construct it, passing some DOM nodes for it to attach to. See the 22 - * constructor for more information. 23 - * 2. Attach a datasource by calling setDatasource() with a valid datasource, 24 - * often a @{JX.TypeaheadPreloadedSource}. 25 - * 3. Configure any special options that you want. 26 - * 4. Call start(). 27 - * 28 - * If you do this correctly, a dropdown menu should appear under the input as 29 - * the user types, suggesting matching results. 30 - * 31 - * @task build Building a Typeahead 32 - * @task datasource Configuring a Datasource 33 - * @task config Configuring Options 34 - * @task start Activating a Typeahead 35 - * @task control Controlling Typeaheads from Javascript 36 - * @task internal Internal Methods 37 - */ 38 - JX.install('Typeahead', { 39 - /** 40 - * Construct a new Typeahead on some "hardpoint". At a minimum, the hardpoint 41 - * should be a ##<div>## with "position: relative;" wrapped around a text 42 - * ##<input>##. The typeahead's dropdown suggestions will be appended to the 43 - * hardpoint in the DOM. Basically, this is the bare minimum requirement: 44 - * 45 - * LANG=HTML 46 - * <div style="position: relative;"> 47 - * <input type="text" /> 48 - * </div> 49 - * 50 - * Then get a reference to the ##<div>## and pass it as 'hardpoint', and pass 51 - * the ##<input>## as 'control'. This will enhance your boring old 52 - * ##<input />## with amazing typeahead powers. 53 - * 54 - * On the Facebook/Tools stack, ##<javelin:typeahead-template />## can build 55 - * this for you. 56 - * 57 - * @param Node "Hardpoint", basically an anchorpoint in the document which 58 - * the typeahead can append its suggestion menu to. 59 - * @param Node? Actual ##<input />## to use; if not provided, the typeahead 60 - * will just look for a (solitary) input inside the hardpoint. 61 - * @task build 62 - */ 63 - construct : function(hardpoint, control) { 64 - this._hardpoint = hardpoint; 65 - this._control = control || JX.DOM.find(hardpoint, 'input'); 66 - 67 - this._root = JX.$N( 68 - 'div', 69 - {className: 'jx-typeahead-results'}); 70 - this._display = []; 71 - 72 - JX.DOM.listen( 73 - this._control, 74 - ['focus', 'blur', 'keypress', 'keydown'], 75 - null, 76 - JX.bind(this, this.handleEvent)); 77 - 78 - JX.DOM.listen( 79 - this._root, 80 - ['mouseover', 'mouseout'], 81 - null, 82 - JX.bind(this, this._onmouse)); 83 - 84 - JX.DOM.listen( 85 - this._root, 86 - 'mousedown', 87 - 'tag:a', 88 - JX.bind(this, function(e) { 89 - this._choose(e.getNode('tag:a')); 90 - e.prevent(); 91 - })); 92 - 93 - }, 94 - 95 - events : ['choose', 'query', 'start', 'change'], 96 - 97 - properties : { 98 - 99 - /** 100 - * Boolean. If true (default), the user is permitted to submit the typeahead 101 - * with a custom or empty selection. This is a good behavior if the 102 - * typeahead is attached to something like a search input, where the user 103 - * might type a freeform query or select from a list of suggestions. 104 - * However, sometimes you require a specific input (e.g., choosing which 105 - * user owns something), in which case you can prevent null selections. 106 - * 107 - * @task config 108 - */ 109 - allowNullSelection : true, 110 - 111 - /** 112 - * Function. Allows you to reconfigure the Typeahead's normalizer, which is 113 - * @{JX.TypeaheadNormalizer} by default. The normalizer is used to convert 114 - * user input into strings suitable for matching, e.g. by lowercasing all 115 - * input and removing punctuation. See @{JX.TypeaheadNormalizer} for more 116 - * details. Any replacement function should accept an arbitrary user-input 117 - * string and emit a normalized string suitable for tokenization and 118 - * matching. 119 - * 120 - * @task config 121 - */ 122 - normalizer : null 123 - }, 124 - 125 - members : { 126 - _root : null, 127 - _control : null, 128 - _hardpoint : null, 129 - _value : null, 130 - _stop : false, 131 - _focus : -1, 132 - _display : null, 133 - 134 - /** 135 - * Activate your properly configured typeahead. It won't do anything until 136 - * you call this method! 137 - * 138 - * @task start 139 - * @return void 140 - */ 141 - start : function() { 142 - this.invoke('start'); 143 - }, 144 - 145 - 146 - /** 147 - * Configure a datasource, which is where the Typeahead gets suggestions 148 - * from. See @{JX.TypeaheadDatasource} for more information. You must 149 - * provide a datasource. 150 - * 151 - * @task datasource 152 - * @param JX.TypeaheadDatasource The datasource which the typeahead will 153 - * draw from. 154 - */ 155 - setDatasource : function(datasource) { 156 - datasource.bindToTypeahead(this); 157 - }, 158 - 159 - 160 - /** 161 - * Override the <input /> selected in the constructor with some other input. 162 - * This is primarily useful when building a control on top of the typeahead, 163 - * like @{JX.Tokenizer}. 164 - * 165 - * @task config 166 - * @param node An <input /> node to use as the primary control. 167 - */ 168 - setInputNode : function(input) { 169 - this._control = input; 170 - return this; 171 - }, 172 - 173 - 174 - /** 175 - * Hide the typeahead's dropdown suggestion menu. 176 - * 177 - * @task control 178 - * @return void 179 - */ 180 - hide : function() { 181 - this._changeFocus(Number.NEGATIVE_INFINITY); 182 - this._display = []; 183 - this._moused = false; 184 - JX.DOM.setContent(this._root, ''); 185 - JX.DOM.remove(this._root); 186 - }, 187 - 188 - 189 - /** 190 - * Show a given result set in the typeahead's dropdown suggestion menu. 191 - * Normally, you only call this method if you are implementing a datasource. 192 - * Otherwise, the datasource you have configured calls it for you in 193 - * response to the user's actions. 194 - * 195 - * @task control 196 - * @param list List of ##<a />## tags to show as suggestions/results. 197 - * @return void 198 - */ 199 - showResults : function(results) { 200 - this._display = results; 201 - if (results.length) { 202 - JX.DOM.setContent(this._root, results); 203 - this._changeFocus(Number.NEGATIVE_INFINITY); 204 - var d = JX.$V.getDim(this._hardpoint); 205 - d.x = 0; 206 - d.setPos(this._root); 207 - this._hardpoint.appendChild(this._root); 208 - } else { 209 - this.hide(); 210 - } 211 - }, 212 - 213 - refresh : function() { 214 - if (this._stop) { 215 - return; 216 - } 217 - 218 - this._value = this._control.value; 219 - if (!this.invoke('change', this._value).getPrevented()) { 220 - if (__DEV__) { 221 - throw new Error( 222 - "JX.Typeahead._update(): " + 223 - "No listener responded to Typeahead 'change' event. Create a " + 224 - "datasource and call setDatasource()."); 225 - } 226 - } 227 - }, 228 - /** 229 - * Show a "waiting for results" UI in place of the typeahead's dropdown 230 - * suggestion menu. NOTE: currently there's no such UI, lolol. 231 - * 232 - * @task control 233 - * @return void 234 - */ 235 - waitForResults : function() { 236 - // TODO: Build some sort of fancy spinner or "..." type UI here to 237 - // visually indicate that we're waiting on the server. 238 - this.hide(); 239 - }, 240 - 241 - 242 - /** 243 - * @task internal 244 - */ 245 - _onmouse : function(event) { 246 - this._moused = (event.getType() == 'mouseover'); 247 - this._drawFocus(); 248 - }, 249 - 250 - 251 - /** 252 - * @task internal 253 - */ 254 - _changeFocus : function(d) { 255 - var n = Math.min(Math.max(-1, this._focus + d), this._display.length - 1); 256 - if (!this.getAllowNullSelection()) { 257 - n = Math.max(0, n); 258 - } 259 - if (this._focus >= 0 && this._focus < this._display.length) { 260 - JX.DOM.alterClass(this._display[this._focus], 'focused', 0); 261 - } 262 - this._focus = n; 263 - this._drawFocus(); 264 - return true; 265 - }, 266 - 267 - 268 - /** 269 - * @task internal 270 - */ 271 - _drawFocus : function() { 272 - var f = this._display[this._focus]; 273 - if (f) { 274 - JX.DOM.alterClass(f, 'focused', !this._moused); 275 - } 276 - }, 277 - 278 - 279 - /** 280 - * @task internal 281 - */ 282 - _choose : function(target) { 283 - var result = this.invoke('choose', target); 284 - if (result.getPrevented()) { 285 - return; 286 - } 287 - 288 - this._control.value = target.name; 289 - this.hide(); 290 - }, 291 - 292 - 293 - /** 294 - * @task control 295 - */ 296 - clear : function() { 297 - this._control.value = ''; 298 - this.hide(); 299 - }, 300 - 301 - 302 - /** 303 - * @task control 304 - */ 305 - disable : function() { 306 - this._control.blur(); 307 - this._control.disabled = true; 308 - this._stop = true; 309 - }, 310 - 311 - 312 - /** 313 - * @task control 314 - */ 315 - submit : function() { 316 - if (this._focus >= 0 && this._display[this._focus]) { 317 - this._choose(this._display[this._focus]); 318 - return true; 319 - } else { 320 - result = this.invoke('query', this._control.value); 321 - if (result.getPrevented()) { 322 - return true; 323 - } 324 - } 325 - return false; 326 - }, 327 - 328 - setValue : function(value) { 329 - this._control.value = value; 330 - }, 331 - 332 - getValue : function() { 333 - return this._control.value; 334 - }, 335 - 336 - /** 337 - * @task internal 338 - */ 339 - _update : function(event) { 340 - var k = event && event.getSpecialKey(); 341 - if (k && event.getType() == 'keydown') { 342 - switch (k) { 343 - case 'up': 344 - if (this._display.length && this._changeFocus(-1)) { 345 - event.prevent(); 346 - } 347 - break; 348 - case 'down': 349 - if (this._display.length && this._changeFocus(1)) { 350 - event.prevent(); 351 - } 352 - break; 353 - case 'return': 354 - if (this.submit()) { 355 - event.prevent(); 356 - return; 357 - } 358 - break; 359 - case 'esc': 360 - if (this._display.length && this.getAllowNullSelection()) { 361 - this.hide(); 362 - event.prevent(); 363 - } 364 - break; 365 - case 'tab': 366 - // If the user tabs out of the field, don't refresh. 367 - return; 368 - } 369 - } 370 - 371 - // We need to defer because the keystroke won't be present in the input's 372 - // value field yet. 373 - JX.defer(JX.bind(this, function() { 374 - if (this._value == this._control.value) { 375 - // The typeahead value hasn't changed. 376 - return; 377 - } 378 - this.refresh(); 379 - })); 380 - }, 381 - 382 - /** 383 - * This method is pretty much internal but @{JX.Tokenizer} needs access to 384 - * it for delegation. You might also need to delegate events here if you 385 - * build some kind of meta-control. 386 - * 387 - * Reacts to user events in accordance to configuration. 388 - * 389 - * @task internal 390 - * @param JX.Event User event, like a click or keypress. 391 - * @return void 392 - */ 393 - handleEvent : function(e) { 394 - if (this._stop || e.getPrevented()) { 395 - return; 396 - } 397 - var type = e.getType(); 398 - if (type == 'blur') { 399 - this.hide(); 400 - } else { 401 - this._update(e); 402 - } 403 - } 404 - } 405 - }); 406 - /** 407 - * @requires javelin-install 408 - * @provides javelin-typeahead-normalizer 409 - * @javelin 410 - */ 411 - 412 - JX.install('TypeaheadNormalizer', { 413 - statics : { 414 - normalize : function(str) { 415 - return ('' + str) 416 - .toLowerCase() 417 - .replace(/[^a-z0-9 ]/g, '') 418 - .replace(/ +/g, ' ') 419 - .replace(/^\s*|\s*$/g, ''); 420 - } 421 - } 422 - }); 423 - /** 424 - * @requires javelin-install 425 - * javelin-util 426 - * javelin-dom 427 - * javelin-typeahead-normalizer 428 - * @provides javelin-typeahead-source 429 - * @javelin 430 - */ 431 - 432 - JX.install('TypeaheadSource', { 433 - construct : function() { 434 - this._raw = {}; 435 - this._lookup = {}; 436 - this.setNormalizer(JX.TypeaheadNormalizer.normalize); 437 - }, 438 - 439 - properties : { 440 - 441 - /** 442 - * Allows you to specify a function which will be used to normalize strings. 443 - * Strings are normalized before being tokenized, and before being sent to 444 - * the server. The purpose of normalization is to strip out irrelevant data, 445 - * like uppercase/lowercase, extra spaces, or punctuation. By default, 446 - * the @{JX.TypeaheadNormalizer} is used to normalize strings, but you may 447 - * want to provide a different normalizer, particiularly if there are 448 - * special characters with semantic meaning in your object names. 449 - * 450 - * @param function 451 - */ 452 - normalizer : null, 453 - 454 - /** 455 - * Transformers convert data from a wire format to a runtime format. The 456 - * transformation mechanism allows you to choose an efficient wire format 457 - * and then expand it on the client side, rather than duplicating data 458 - * over the wire. The transformation is applied to objects passed to 459 - * addResult(). It should accept whatever sort of object you ship over the 460 - * wire, and produce a dictionary with these keys: 461 - * 462 - * - **id**: a unique id for each object. 463 - * - **name**: the string used for matching against user input. 464 - * - **uri**: the URI corresponding with the object (must be present 465 - * but need not be meaningful) 466 - * - **display**: the text or nodes to show in the DOM. Usually just the 467 - * same as ##name##. 468 - * 469 - * The default transformer expects a three element list with elements 470 - * [name, uri, id]. It assigns the first element to both ##name## and 471 - * ##display##. 472 - * 473 - * @param function 474 - */ 475 - transformer : null, 476 - 477 - /** 478 - * Configures the maximum number of suggestions shown in the typeahead 479 - * dropdown. 480 - * 481 - * @param int 482 - */ 483 - maximumResultCount : 5 484 - 485 - }, 486 - 487 - members : { 488 - _raw : null, 489 - _lookup : null, 490 - _typeahead : null, 491 - _normalizer : null, 492 - 493 - bindToTypeahead : function(typeahead) { 494 - this._typeahead = typeahead; 495 - typeahead.listen('change', JX.bind(this, this.didChange)); 496 - typeahead.listen('start', JX.bind(this, this.didStart)); 497 - }, 498 - 499 - didChange : function(value) { 500 - return; 501 - }, 502 - 503 - didStart : function() { 504 - return; 505 - }, 506 - 507 - addResult : function(obj) { 508 - obj = (this.getTransformer() || this._defaultTransformer)(obj); 509 - 510 - if (obj.id in this._raw) { 511 - // We're already aware of this result. This will happen if someone 512 - // searches for "zeb" and then for "zebra" with a 513 - // TypeaheadRequestSource, for example, or the datasource just doesn't 514 - // dedupe things properly. Whatever the case, just ignore it. 515 - return; 516 - } 517 - 518 - if (__DEV__) { 519 - for (var k in {name : 1, id : 1, display : 1, uri : 1}) { 520 - if (!(k in obj)) { 521 - throw new Error( 522 - "JX.TypeaheadSource.addResult(): " + 523 - "result must have properties 'name', 'id', 'uri' and 'display'."); 524 - } 525 - } 526 - } 527 - 528 - this._raw[obj.id] = obj; 529 - var t = this.tokenize(obj.name); 530 - for (var jj = 0; jj < t.length; ++jj) { 531 - this._lookup[t[jj]] = this._lookup[t[jj]] || []; 532 - this._lookup[t[jj]].push(obj.id); 533 - } 534 - }, 535 - 536 - waitForResults : function() { 537 - this._typeahead.waitForResults(); 538 - return this; 539 - }, 540 - 541 - matchResults : function(value) { 542 - 543 - // This table keeps track of the number of tokens each potential match 544 - // has actually matched. When we're done, the real matches are those 545 - // which have matched every token (so the value is equal to the token 546 - // list length). 547 - var match_count = {}; 548 - 549 - // This keeps track of distinct matches. If the user searches for 550 - // something like "Chris C" against "Chris Cox", the "C" will match 551 - // both fragments. We need to make sure we only count distinct matches. 552 - var match_fragments = {}; 553 - 554 - var matched = {}; 555 - var seen = {}; 556 - 557 - var t = this.tokenize(value); 558 - 559 - // Sort tokens by longest-first. We match each name fragment with at 560 - // most one token. 561 - t.sort(function(u, v) { return v.length - u.length; }); 562 - 563 - for (var ii = 0; ii < t.length; ++ii) { 564 - // Do something reasonable if the user types the same token twice; this 565 - // is sort of stupid so maybe kill it? 566 - if (t[ii] in seen) { 567 - t.splice(ii--, 1); 568 - continue; 569 - } 570 - seen[t[ii]] = true; 571 - var fragment = t[ii]; 572 - for (var name_fragment in this._lookup) { 573 - if (name_fragment.substr(0, fragment.length) === fragment) { 574 - if (!(name_fragment in matched)) { 575 - matched[name_fragment] = true; 576 - } else { 577 - continue; 578 - } 579 - var l = this._lookup[name_fragment]; 580 - for (var jj = 0; jj < l.length; ++jj) { 581 - var match_id = l[jj]; 582 - if (!match_fragments[match_id]) { 583 - match_fragments[match_id] = {}; 584 - } 585 - if (!(fragment in match_fragments[match_id])) { 586 - match_fragments[match_id][fragment] = true; 587 - match_count[match_id] = (match_count[match_id] || 0) + 1; 588 - } 589 - } 590 - } 591 - } 592 - } 593 - 594 - var hits = []; 595 - for (var k in match_count) { 596 - if (match_count[k] == t.length) { 597 - hits.push(k); 598 - } 599 - } 600 - 601 - var n = Math.min(this.getMaximumResultCount(), hits.length); 602 - var nodes = []; 603 - for (var kk = 0; kk < n; kk++) { 604 - nodes.push(this.createNode(this._raw[hits[kk]])); 605 - } 606 - 607 - this._typeahead.showResults(nodes); 608 - }, 609 - 610 - createNode : function(data) { 611 - return JX.$N( 612 - 'a', 613 - { 614 - href: data.uri, 615 - name: data.name, 616 - rel: data.id, 617 - className: 'jx-result' 618 - }, 619 - data.display 620 - ); 621 - }, 622 - 623 - normalize : function(str) { 624 - return (this.getNormalizer() || JX.bag())(str); 625 - }, 626 - tokenize : function(str) { 627 - str = this.normalize(str); 628 - if (!str.length) { 629 - return []; 630 - } 631 - return str.split(/ /g); 632 - }, 633 - _defaultTransformer : function(object) { 634 - return { 635 - name : object[0], 636 - display : object[0], 637 - uri : object[1], 638 - id : object[2] 639 - }; 640 - } 641 - } 642 - }); 643 - 644 - 645 - /** 646 - * @requires javelin-install 647 - * javelin-util 648 - * javelin-stratcom 649 - * javelin-request 650 - * javelin-typeahead-source 651 - * @provides javelin-typeahead-preloaded-source 652 - * @javelin 653 - */ 654 - 655 - /** 656 - * Simple datasource that loads all possible results from a single call to a 657 - * URI. This is appropriate if the total data size is small (up to perhaps a 658 - * few thousand items). If you have more items so you can't ship them down to 659 - * the client in one repsonse, use @{JX.TypeaheadOnDemandSource}. 660 - */ 661 - JX.install('TypeaheadPreloadedSource', { 662 - 663 - extend : 'TypeaheadSource', 664 - 665 - construct : function(uri) { 666 - this.__super__.call(this); 667 - this.uri = uri; 668 - }, 669 - 670 - members : { 671 - 672 - ready : false, 673 - uri : null, 674 - lastValue : null, 675 - 676 - didChange : function(value) { 677 - if (this.ready) { 678 - this.matchResults(value); 679 - } else { 680 - this.lastValue = value; 681 - this.waitForResults(); 682 - } 683 - JX.Stratcom.context().kill(); 684 - }, 685 - 686 - didStart : function() { 687 - var r = new JX.Request(this.uri, JX.bind(this, this.ondata)); 688 - r.setMethod('GET'); 689 - r.send(); 690 - }, 691 - 692 - ondata : function(results) { 693 - for (var ii = 0; ii < results.length; ++ii) { 694 - this.addResult(results[ii]); 695 - } 696 - if (this.lastValue !== null) { 697 - this.matchResults(this.lastValue); 698 - } 699 - this.ready = true; 700 - } 701 - } 702 - }); 703 - 704 - 705 - 706 - /** 707 - * @requires javelin-install 708 - * javelin-util 709 - * javelin-stratcom 710 - * javelin-request 711 - * javelin-typeahead-source 712 - * @provides javelin-typeahead-ondemand-source 713 - * @javelin 714 - */ 715 - 716 - JX.install('TypeaheadOnDemandSource', { 717 - 718 - extend : 'TypeaheadSource', 719 - 720 - construct : function(uri) { 721 - this.__super__.call(this); 722 - this.uri = uri; 723 - this.haveData = { 724 - '' : true 725 - }; 726 - }, 727 - 728 - properties : { 729 - /** 730 - * Configures how many milliseconds we wait after the user stops typing to 731 - * send a request to the server. Setting a value of 250 means "wait 250 732 - * milliseconds after the user stops typing to request typeahead data". 733 - * Higher values reduce server load but make the typeahead less responsive. 734 - */ 735 - queryDelay : 125, 736 - /** 737 - * Auxiliary data to pass along when sending the query for server results. 738 - */ 739 - auxiliaryData : {} 740 - }, 741 - 742 - members : { 743 - uri : null, 744 - lastChange : null, 745 - haveData : null, 746 - 747 - didChange : function(value) { 748 - if (JX.Stratcom.pass()) { 749 - return; 750 - } 751 - this.lastChange = new Date().getTime(); 752 - value = this.normalize(value); 753 - 754 - if (this.haveData[value]) { 755 - this.matchResults(value); 756 - } else { 757 - this.waitForResults(); 758 - JX.defer( 759 - JX.bind(this, this.sendRequest, this.lastChange, value), 760 - this.getQueryDelay()); 761 - } 762 - 763 - JX.Stratcom.context().kill(); 764 - }, 765 - 766 - sendRequest : function(when, value) { 767 - if (when != this.lastChange) { 768 - return; 769 - } 770 - var r = new JX.Request( 771 - this.uri, 772 - JX.bind(this, this.ondata, this.lastChange, value)); 773 - r.setMethod('GET'); 774 - r.setData(JX.copy(this.getAuxiliaryData(), {q : value})); 775 - r.send(); 776 - }, 777 - 778 - ondata : function(when, value, results) { 779 - for (var ii = 0; ii < results.length; ii++) { 780 - this.addResult(results[ii]); 781 - } 782 - this.haveData[value] = true; 783 - if (when != this.lastChange) { 784 - return; 785 - } 786 - this.matchResults(value); 787 - } 788 - } 789 - }); 790 - 791 - 792 - /** 793 - * @requires javelin-typeahead javelin-dom javelin-util 794 - * javelin-stratcom javelin-vector javelin-install 795 - * javelin-typeahead-preloaded-source 796 - * @provides javelin-tokenizer 797 - * @javelin 798 - */ 799 - 800 - /** 801 - * A tokenizer is a UI component similar to a text input, except that it 802 - * allows the user to input a list of items ("tokens"), generally from a fixed 803 - * set of results. A familiar example of this UI is the "To:" field of most 804 - * email clients, where the control autocompletes addresses from the user's 805 - * address book. 806 - * 807 - * @{JX.Tokenizer} is built on top of @{JX.Typeahead}, and primarily adds the 808 - * ability to choose multiple items. 809 - * 810 - * To build a @{JX.Tokenizer}, you need to do four things: 811 - * 812 - * 1. Construct it, padding a DOM node for it to attach to. See the constructor 813 - * for more information. 814 - * 2. Build a {@JX.Typeahead} and configure it with setTypeahead(). 815 - * 3. Configure any special options you want. 816 - * 4. Call start(). 817 - * 818 - * If you do this correctly, the input should suggest items and enter them as 819 - * tokens as the user types. 820 - */ 821 - JX.install('Tokenizer', { 822 - construct : function(containerNode) { 823 - this._containerNode = containerNode; 824 - }, 825 - 826 - properties : { 827 - limit : null, 828 - nextInput : null 829 - }, 830 - 831 - members : { 832 - _containerNode : null, 833 - _root : null, 834 - _focus : null, 835 - _orig : null, 836 - _typeahead : null, 837 - _tokenid : 0, 838 - _tokens : null, 839 - _tokenMap : null, 840 - _initialValue : null, 841 - _seq : 0, 842 - _lastvalue : null, 843 - 844 - start : function() { 845 - if (__DEV__) { 846 - if (!this._typeahead) { 847 - throw new Error( 848 - 'JX.Tokenizer.start(): ' + 849 - 'No typeahead configured! Use setTypeahead() to provide a ' + 850 - 'typeahead.'); 851 - } 852 - } 853 - 854 - this._orig = JX.DOM.find(this._containerNode, 'input', 'tokenizer'); 855 - this._tokens = []; 856 - this._tokenMap = {}; 857 - 858 - var focus = this.buildInput(this._orig.value); 859 - this._focus = focus; 860 - 861 - JX.DOM.listen( 862 - focus, 863 - ['click', 'focus', 'blur', 'keydown'], 864 - null, 865 - JX.bind(this, this.handleEvent)); 866 - 867 - JX.DOM.listen( 868 - this._containerNode, 869 - 'click', 870 - null, 871 - JX.bind( 872 - this, 873 - function(e) { 874 - if (e.getNode('remove')) { 875 - this._remove(e.getNodeData('token').key); 876 - } else if (e.getTarget() == this._root) { 877 - this.focus(); 878 - } 879 - })); 880 - 881 - var root = JX.$N('div'); 882 - root.id = this._orig.id; 883 - JX.DOM.alterClass(root, 'jx-tokenizer', true); 884 - root.style.cursor = 'text'; 885 - this._root = root; 886 - 887 - root.appendChild(focus); 888 - 889 - var typeahead = this._typeahead; 890 - typeahead.setInputNode(this._focus); 891 - typeahead.start(); 892 - 893 - JX.defer( 894 - JX.bind( 895 - this, 896 - function() { 897 - JX.DOM.setContent(this._orig.parentNode, root); 898 - var map = this._initialValue || {}; 899 - for (var k in map) { 900 - this.addToken(k, map[k]); 901 - } 902 - this._redraw(); 903 - })); 904 - }, 905 - 906 - setInitialValue : function(map) { 907 - this._initialValue = map; 908 - return this; 909 - }, 910 - 911 - setTypeahead : function(typeahead) { 912 - 913 - typeahead.setAllowNullSelection(false); 914 - 915 - typeahead.listen( 916 - 'choose', 917 - JX.bind( 918 - this, 919 - function(result) { 920 - JX.Stratcom.context().prevent(); 921 - if (this.addToken(result.rel, result.name)) { 922 - this._typeahead.hide(); 923 - this._focus.value = ''; 924 - this._redraw(); 925 - this.focus(); 926 - } 927 - })); 928 - 929 - typeahead.listen( 930 - 'query', 931 - JX.bind( 932 - this, 933 - function(query) { 934 - 935 - // TODO: We should emit a 'query' event here to allow the caller to 936 - // generate tokens on the fly, e.g. email addresses or other freeform 937 - // or algorithmic tokens. 938 - 939 - // Then do this if something handles the event. 940 - // this._focus.value = ''; 941 - // this._redraw(); 942 - // this.focus(); 943 - 944 - if (query.length) { 945 - // Prevent this event if there's any text, so that we don't submit 946 - // the form (either we created a token or we failed to create a 947 - // token; in either case we shouldn't submit). If the query is 948 - // empty, allow the event so that the form submission takes place. 949 - JX.Stratcom.context().prevent(); 950 - } 951 - })); 952 - 953 - this._typeahead = typeahead; 954 - 955 - return this; 956 - }, 957 - 958 - handleEvent : function(e) { 959 - 960 - this._typeahead.handleEvent(e); 961 - if (e.getPrevented()) { 962 - return; 963 - } 964 - 965 - if (e.getType() == 'click') { 966 - if (e.getTarget() == this._root) { 967 - this.focus(); 968 - e.prevent(); 969 - return; 970 - } 971 - } else if (e.getType() == 'keydown') { 972 - this._onkeydown(e); 973 - } else if (e.getType() == 'blur') { 974 - this._redraw(); 975 - } 976 - }, 977 - 978 - refresh : function() { 979 - this._redraw(true); 980 - return this; 981 - }, 982 - 983 - _redraw : function(force) { 984 - var focus = this._focus; 985 - 986 - if (focus.value === this._lastvalue && !force) { 987 - return; 988 - } 989 - this._lastvalue = focus.value; 990 - 991 - var root = this._root; 992 - var metrics = JX.DOM.textMetrics( 993 - this._focus, 994 - 'jx-tokenizer-metrics'); 995 - metrics.y = null; 996 - metrics.x += 24; 997 - metrics.setDim(focus); 998 - 999 - // This is a pretty ugly hack to force a redraw after copy/paste in 1000 - // Firefox. If we don't do this, it doesn't redraw the input so pasting 1001 - // in an email address doesn't give you a very good behavior. 1002 - focus.value = focus.value; 1003 - 1004 - var h = JX.$V(focus).add(JX.$V.getDim(focus)).y - JX.$V(root).y; 1005 - root.style.height = h + 'px'; 1006 - }, 1007 - 1008 - addToken : function(key, value) { 1009 - if (key in this._tokenMap) { 1010 - return false; 1011 - } 1012 - 1013 - var focus = this._focus; 1014 - var root = this._root; 1015 - var token = this.buildToken(key, value); 1016 - 1017 - this._tokenMap[key] = { 1018 - value : value, 1019 - key : key, 1020 - node : token 1021 - }; 1022 - this._tokens.push(key); 1023 - 1024 - root.insertBefore(token, focus); 1025 - 1026 - return true; 1027 - }, 1028 - 1029 - buildInput: function(value) { 1030 - return JX.$N('input', { 1031 - className: 'jx-tokenizer-input', 1032 - type: 'text', 1033 - value: value 1034 - }); 1035 - }, 1036 - 1037 - /** 1038 - * Generate a token based on a key and value. The "token" and "remove" 1039 - * sigils are observed by a listener in start(). 1040 - */ 1041 - buildToken: function(key, value) { 1042 - var input = JX.$N('input', { 1043 - type: 'hidden', 1044 - value: key, 1045 - name: this._orig.name + '[' + (this._seq++) + ']' 1046 - }); 1047 - 1048 - var remove = JX.$N('a', { 1049 - className: 'jx-tokenizer-x', 1050 - sigil: 'remove' 1051 - }, JX.HTML('&times;')); 1052 - 1053 - return JX.$N('a', { 1054 - className: 'jx-tokenizer-token', 1055 - sigil: 'token', 1056 - meta: {key: key} 1057 - }, [value, input, remove]); 1058 - }, 1059 - 1060 - getTokens : function() { 1061 - var result = {}; 1062 - for (var key in this._tokenMap) { 1063 - result[key] = this._tokenMap[key].value; 1064 - } 1065 - return result; 1066 - }, 1067 - 1068 - _onkeydown : function(e) { 1069 - var focus = this._focus; 1070 - var root = this._root; 1071 - switch (e.getSpecialKey()) { 1072 - case 'tab': 1073 - var completed = this._typeahead.submit(); 1074 - if (this.getNextInput()) { 1075 - if (!completed) { 1076 - this._focus.value = ''; 1077 - } 1078 - JX.defer(JX.bind(this, function() { 1079 - this.getNextInput().focus(); 1080 - })); 1081 - } 1082 - break; 1083 - case 'delete': 1084 - if (!this._focus.value.length) { 1085 - var tok; 1086 - while (tok = this._tokens.pop()) { 1087 - if (this._remove(tok)) { 1088 - break; 1089 - } 1090 - } 1091 - } 1092 - break; 1093 - case 'return': 1094 - // Don't subject this to token limits. 1095 - break; 1096 - default: 1097 - if (this.getLimit() && 1098 - JX.keys(this._tokenMap).length == this.getLimit()) { 1099 - e.prevent(); 1100 - } 1101 - JX.defer(JX.bind(this, this._redraw)); 1102 - break; 1103 - } 1104 - }, 1105 - 1106 - _remove : function(index) { 1107 - if (!this._tokenMap[index]) { 1108 - return false; 1109 - } 1110 - JX.DOM.remove(this._tokenMap[index].node); 1111 - delete this._tokenMap[index]; 1112 - this._redraw(true); 1113 - this.focus(); 1114 - return true; 1115 - }, 1116 - 1117 - focus : function() { 1118 - var focus = this._focus; 1119 - JX.DOM.show(focus); 1120 - JX.defer(function() { JX.DOM.focus(focus); }); 1121 - } 1122 - } 1123 - });
-3
webroot/rsrc/js/javelin/typeahead.min.js
··· 1 - /** @provides javelin-typeahead-prod */ 2 - 3 - JX.install('Typeahead',{construct:function(b,a){this._a=b;this._b=a||JX.DOM.find(b,'input');this._c=JX.$N('div',{className:'jx-typeahead-results'});this._d=[];JX.DOM.listen(this._b,['focus','blur','keypress','keydown'],null,JX.bind(this,this.handleEvent));JX.DOM.listen(this._c,['mouseover','mouseout'],null,JX.bind(this,this._e));JX.DOM.listen(this._c,'mousedown','tag:a',JX.bind(this,function(c){this._f(c.getNode('tag:a'));c.prevent();}));},events:['choose','query','start','change'],properties:{allowNullSelection:true,normalizer:null},members:{_c:null,_b:null,_a:null,_g:null,_h:false,_i:-1,_d:null,start:function(){this.invoke('start');},setDatasource:function(a){a.bindToTypeahead(this);},setInputNode:function(a){this._b=a;return this;},hide:function(){this._j(Number.NEGATIVE_INFINITY);this._d=[];this._k=false;JX.DOM.setContent(this._c,'');JX.DOM.remove(this._c);},showResults:function(b){this._d=b;if(b.length){JX.DOM.setContent(this._c,b);this._j(Number.NEGATIVE_INFINITY);var a=JX.$V.getDim(this._a);a.x=0;a.setPos(this._c);this._a.appendChild(this._c);}else this.hide();},refresh:function(){if(this._h)return;this._g=this._b.value;!this.invoke('change',this._g).getPrevented();},waitForResults:function(){this.hide();},_e:function(event){this._k=(event.getType()=='mouseover');this._l();},_j:function(a){var b=Math.min(Math.max(-1,this._i+a),this._d.length-1);if(!this.getAllowNullSelection())b=Math.max(0,b);if(this._i>=0&&this._i<this._d.length)JX.DOM.alterClass(this._d[this._i],'focused',0);this._i=b;this._l();return true;},_l:function(){var a=this._d[this._i];if(a)JX.DOM.alterClass(a,'focused',!this._k);},_f:function(b){var a=this.invoke('choose',b);if(a.getPrevented())return;this._b.value=b.name;this.hide();},clear:function(){this._b.value='';this.hide();},disable:function(){this._b.blur();this._b.disabled=true;this._h=true;},submit:function(){if(this._i>=0&&this._d[this._i]){this._f(this._d[this._i]);return true;}else{result=this.invoke('query',this._b.value);if(result.getPrevented())return true;}return false;},setValue:function(a){this._b.value=a;},getValue:function(){return this._b.value;},_m:function(event){var a=event&&event.getSpecialKey();if(a&&event.getType()=='keydown')switch(a){case 'up':if(this._d.length&&this._j(-1))event.prevent();break;case 'down':if(this._d.length&&this._j(1))event.prevent();break;case 'return':if(this.submit()){event.prevent();return;}break;case 'esc':if(this._d.length&&this.getAllowNullSelection()){this.hide();event.prevent();}break;case 'tab':return;}JX.defer(JX.bind(this,function(){if(this._g==this._b.value)return;this.refresh();}));},handleEvent:function(a){if(this._h||a.getPrevented())return;var b=a.getType();if(b=='blur'){this.hide();}else this._m(a);}}});JX.install('TypeaheadNormalizer',{statics:{normalize:function(a){return (''+a).toLowerCase().replace(/[^a-z0-9 ]/g,'').replace(/ +/g,' ').replace(/^\s*|\s*$/g,'');}}});JX.install('TypeaheadSource',{construct:function(){this._n={};this._o={};this.setNormalizer(JX.TypeaheadNormalizer.normalize);},properties:{normalizer:null,transformer:null,maximumResultCount:5},members:{_n:null,_o:null,_p:null,_q:null,bindToTypeahead:function(a){this._p=a;a.listen('change',JX.bind(this,this.didChange));a.listen('start',JX.bind(this,this.didStart));},didChange:function(a){return;},didStart:function(){return;},addResult:function(b){b=(this.getTransformer()||this._r)(b);if(b.id in this._n)return;this._n[b.id]=b;var c=this.tokenize(b.name);for(var a=0;a<c.length;++a){this._o[c[a]]=this._o[c[a]]||[];this._o[c[a]].push(b.id);}},waitForResults:function(){this._p.waitForResults();return this;},matchResults:function(q){var h={};var i={};var k={};var o={};var p=this.tokenize(q);p.sort(function(r,s){return s.length-r.length;});for(var c=0;c<p.length;++c){if(p[c] in o){p.splice(c--,1);continue;}o[p[c]]=true;var a=p[c];for(var m in this._o)if(m.substr(0,a.length)===a){if(!(m in k)){k[m]=true;}else continue;var g=this._o[m];for(var d=0;d<g.length;++d){var j=g[d];if(!i[j])i[j]={};if(!(a in i[j])){i[j][a]=true;h[j]=(h[j]||0)+1;}}}}var b=[];for(var e in h)if(h[e]==p.length)b.push(e);var l=Math.min(this.getMaximumResultCount(),b.length);var n=[];for(var f=0;f<l;f++)n.push(this.createNode(this._n[b[f]]));this._p.showResults(n);},createNode:function(a){return JX.$N('a',{href:a.uri,name:a.name,rel:a.id,className:'jx-result'},a.display);},normalize:function(a){return (this.getNormalizer()||JX.bag())(a);},tokenize:function(a){a=this.normalize(a);if(!a.length)return [];return a.split(/ /g);},_r:function(a){return {name:a[0],display:a[0],uri:a[1],id:a[2]};}}});JX.install('TypeaheadPreloadedSource',{extend:'TypeaheadSource',construct:function(a){this.__super__.call(this);this.uri=a;},members:{ready:false,uri:null,lastValue:null,didChange:function(a){if(this.ready){this.matchResults(a);}else{this.lastValue=a;this.waitForResults();}JX.Stratcom.context().kill();},didStart:function(){var a=new JX.Request(this.uri,JX.bind(this,this.ondata));a.setMethod('GET');a.send();},ondata:function(b){for(var a=0;a<b.length;++a)this.addResult(b[a]);if(this.lastValue!==null)this.matchResults(this.lastValue);this.ready=true;}}});JX.install('TypeaheadOnDemandSource',{extend:'TypeaheadSource',construct:function(a){this.__super__.call(this);this.uri=a;this.haveData={'':true};},properties:{queryDelay:125,auxiliaryData:{}},members:{uri:null,lastChange:null,haveData:null,didChange:function(a){if(JX.Stratcom.pass())return;this.lastChange=new Date().getTime();a=this.normalize(a);if(this.haveData[a]){this.matchResults(a);}else{this.waitForResults();JX.defer(JX.bind(this,this.sendRequest,this.lastChange,a),this.getQueryDelay());}JX.Stratcom.context().kill();},sendRequest:function(c,b){if(c!=this.lastChange)return;var a=new JX.Request(this.uri,JX.bind(this,this.ondata,this.lastChange,b));a.setMethod('GET');a.setData(JX.copy(this.getAuxiliaryData(),{q:b}));a.send();},ondata:function(d,c,b){for(var a=0;a<b.length;a++)this.addResult(b[a]);this.haveData[c]=true;if(d!=this.lastChange)return;this.matchResults(c);}}});JX.install('Tokenizer',{construct:function(a){this._s=a;},properties:{limit:null,nextInput:null},members:{_s:null,_c:null,_i:null,_t:null,_p:null,_u:0,_v:null,_w:null,_x:null,_y:0,_z:null,start:function(){this._t=JX.DOM.find(this._s,'input','tokenizer');this._v=[];this._w={};var a=this.buildInput(this._t.value);this._i=a;JX.DOM.listen(a,['click','focus','blur','keydown'],null,JX.bind(this,this.handleEvent));JX.DOM.listen(this._s,'click',null,JX.bind(this,function(d){if(d.getNode('remove')){this._za(d.getNodeData('token').key);}else if(d.getTarget()==this._c)this.focus();}));var b=JX.$N('div');b.id=this._t.id;JX.DOM.alterClass(b,'jx-tokenizer',true);b.style.cursor='text';this._c=b;b.appendChild(a);var c=this._p;c.setInputNode(this._i);c.start();JX.defer(JX.bind(this,function(){JX.DOM.setContent(this._t.parentNode,b);var e=this._x||{};for(var d in e)this.addToken(d,e[d]);this._zb();}));},setInitialValue:function(a){this._x=a;return this;},setTypeahead:function(a){a.setAllowNullSelection(false);a.listen('choose',JX.bind(this,function(b){JX.Stratcom.context().prevent();if(this.addToken(b.rel,b.name)){this._p.hide();this._i.value='';this._zb();this.focus();}}));a.listen('query',JX.bind(this,function(b){if(b.length)JX.Stratcom.context().prevent();}));this._p=a;return this;},handleEvent:function(a){this._p.handleEvent(a);if(a.getPrevented())return;if(a.getType()=='click'){if(a.getTarget()==this._c){this.focus();a.prevent();return;}}else if(a.getType()=='keydown'){this._zc(a);}else if(a.getType()=='blur')this._zb();},refresh:function(){this._zb(true);return this;},_zb:function(b){var a=this._i;if(a.value===this._z&&!b)return;this._z=a.value;var e=this._c;var d=JX.DOM.textMetrics(this._i,'jx-tokenizer-metrics');d.y=null;d.x+=24;d.setDim(a);a.value=a.value;var c=JX.$V(a).add(JX.$V.getDim(a)).y-JX.$V(e).y;e.style.height=c+'px';},addToken:function(b,e){if(b in this._w)return false;var a=this._i;var c=this._c;var d=this.buildToken(b,e);this._w[b]={value:e,key:b,node:d};this._v.push(b);c.insertBefore(d,a);return true;},buildInput:function(a){return JX.$N('input',{className:'jx-tokenizer-input',type:'text',value:a});},buildToken:function(b,d){var a=JX.$N('input',{type:'hidden',value:b,name:this._t.name+'['+(this._y++)+']'});var c=JX.$N('a',{className:'jx-tokenizer-x',sigil:'remove'},JX.HTML('&times;'));return JX.$N('a',{className:'jx-tokenizer-token',sigil:'token',meta:{key:b}},[d,a,c]);},getTokens:function(){var b={};for(var a in this._w)b[a]=this._w[a].value;return b;},_zc:function(b){var c=this._i;var d=this._c;switch(b.getSpecialKey()){case 'tab':var a=this._p.submit();if(this.getNextInput()){if(!a)this._i.value='';JX.defer(JX.bind(this,function(){this.getNextInput().focus();}));}break;case 'delete':if(!this._i.value.length){var e;while(e=this._v.pop())if(this._za(e))break;}break;case 'return':break;default:if(this.getLimit()&&JX.keys(this._w).length==this.getLimit())b.prevent();JX.defer(JX.bind(this,this._zb));break;}},_za:function(a){if(!this._w[a])return false;JX.DOM.remove(this._w[a].node);delete this._w[a];this._zb(true);this.focus();return true;},focus:function(){var a=this._i;JX.DOM.show(a);JX.defer(function(){JX.DOM.focus(a);});}}});
-242
webroot/rsrc/js/javelin/workflow.dev.js
··· 1 - /** @provides javelin-workflow-dev */ 2 - 3 - /** 4 - * @requires javelin-install javelin-vector javelin-dom 5 - * @provides javelin-mask 6 - * @javelin 7 - */ 8 - 9 - /** 10 - * Show a transparent "mask" over the page; used by Workflow to draw visual 11 - * attention to modal dialogs. 12 - */ 13 - JX.install('Mask', { 14 - statics : { 15 - _depth : 0, 16 - _mask : null, 17 - show : function() { 18 - if (!JX.Mask._depth) { 19 - JX.Mask._mask = JX.$N('div', {className: 'jx-mask'}); 20 - document.body.appendChild(JX.Mask._mask); 21 - JX.$V.getDocument().setDim(JX.Mask._mask); 22 - } 23 - ++JX.Mask._depth; 24 - }, 25 - hide : function() { 26 - --JX.Mask._depth; 27 - if (!JX.Mask._depth) { 28 - JX.DOM.remove(JX.Mask._mask); 29 - JX.Mask._mask = null; 30 - } 31 - } 32 - } 33 - }); 34 - /** 35 - * @requires javelin-stratcom 36 - * javelin-request 37 - * javelin-dom 38 - * javelin-vector 39 - * javelin-install 40 - * javelin-util 41 - * javelin-mask 42 - * @provides javelin-workflow 43 - * @javelin 44 - */ 45 - 46 - JX.install('Workflow', { 47 - construct : function(uri, data) { 48 - if (__DEV__) { 49 - if (!uri || uri == '#') { 50 - throw new Error( 51 - 'new JX.Workflow(<?>, ...): '+ 52 - 'bogus URI provided when creating workflow.'); 53 - } 54 - } 55 - this.setURI(uri); 56 - this.setData(data || {}); 57 - }, 58 - 59 - events : ['error', 'finally', 'submit'], 60 - 61 - statics : { 62 - _stack : [], 63 - newFromForm : function(form, data) { 64 - var inputs = [].concat( 65 - JX.DOM.scry(form, 'input'), 66 - JX.DOM.scry(form, 'button'), 67 - JX.DOM.scry(form, 'textarea')); 68 - 69 - for (var ii = 0; ii < inputs.length; ii++) { 70 - if (inputs[ii].disabled) { 71 - delete inputs[ii]; 72 - } else { 73 - inputs[ii].disabled = true; 74 - } 75 - } 76 - 77 - var workflow = new JX.Workflow( 78 - form.getAttribute('action'), 79 - JX.copy(data || {}, JX.DOM.serialize(form))); 80 - workflow.setMethod(form.getAttribute('method')); 81 - workflow.listen('finally', function() { 82 - for (var ii = 0; ii < inputs.length; ii++) { 83 - inputs[ii] && (inputs[ii].disabled = false); 84 - } 85 - }); 86 - return workflow; 87 - }, 88 - newFromLink : function(link) { 89 - var workflow = new JX.Workflow(link.href); 90 - return workflow; 91 - }, 92 - _push : function(workflow) { 93 - JX.Mask.show(); 94 - JX.Workflow._stack.push(workflow); 95 - }, 96 - _pop : function() { 97 - var dialog = JX.Workflow._stack.pop(); 98 - (dialog.getCloseHandler() || JX.bag)(); 99 - dialog._destroy(); 100 - JX.Mask.hide(); 101 - }, 102 - disable : function() { 103 - JX.Workflow._disabled = true; 104 - }, 105 - _onbutton : function(event) { 106 - 107 - if (JX.Stratcom.pass()) { 108 - return; 109 - } 110 - 111 - if (JX.Workflow._disabled) { 112 - return; 113 - } 114 - 115 - var t = event.getTarget(); 116 - if (t.name == '__cancel__' || t.name == '__close__') { 117 - JX.Workflow._pop(); 118 - } else { 119 - 120 - var form = event.getNode('jx-dialog'); 121 - var data = JX.DOM.serialize(form); 122 - data[t.name] = true; 123 - data.__wflow__ = true; 124 - 125 - var active = JX.Workflow._stack[JX.Workflow._stack.length - 1]; 126 - var e = active.invoke('submit', {form: form, data: data}); 127 - if (!e.getStopped()) { 128 - active._destroy(); 129 - active 130 - .setURI(form.getAttribute('action') || active.getURI()) 131 - .setData(data) 132 - .start(); 133 - } 134 - } 135 - event.prevent(); 136 - } 137 - }, 138 - 139 - members : { 140 - _root : null, 141 - _pushed : false, 142 - _onload : function(r) { 143 - // It is permissible to send back a falsey redirect to force a page 144 - // reload, so we need to take this branch if the key is present. 145 - if (r && (typeof r.redirect != 'undefined')) { 146 - JX.go(r.redirect, true); 147 - } else if (r && r.dialog) { 148 - this._push(); 149 - this._root = JX.$N( 150 - 'div', 151 - {className: 'jx-client-dialog'}, 152 - JX.HTML(r.dialog)); 153 - JX.DOM.listen( 154 - this._root, 155 - 'click', 156 - [['jx-workflow-button'], ['tag:button']], 157 - JX.Workflow._onbutton); 158 - document.body.appendChild(this._root); 159 - var d = JX.$V.getDim(this._root); 160 - var v = JX.$V.getViewport(); 161 - var s = JX.$V.getScroll(); 162 - JX.$V((v.x - d.x) / 2, s.y + 100).setPos(this._root); 163 - try { 164 - try { 165 - JX.DOM.focus(JX.DOM.find(this._root, 'button', '__default__')); 166 - } catch (_ignored) {} 167 - var inputs = JX.DOM.scry(this._root, 'input') 168 - .concat(JX.DOM.scry(this._root, 'textarea')); 169 - var miny = Number.POSITIVE_INFINITY; 170 - var target = null; 171 - for (var ii = 0; ii < inputs.length; ++ii) { 172 - if (inputs[ii].type != 'hidden') { 173 - // Find the topleft-most displayed element. 174 - var p = JX.$V(inputs[ii]); 175 - if (p.y < miny) { 176 - miny = p.y; 177 - target = inputs[ii]; 178 - } 179 - } 180 - } 181 - target && JX.DOM.focus(target); 182 - } catch (_ignored) {} 183 - } else if (this.getHandler()) { 184 - this.getHandler()(r); 185 - this._pop(); 186 - } else if (r) { 187 - if (__DEV__) { 188 - throw new Error('Response to workflow request went unhandled.'); 189 - } 190 - } 191 - }, 192 - _push : function() { 193 - if (!this._pushed) { 194 - this._pushed = true; 195 - JX.Workflow._push(this); 196 - } 197 - }, 198 - _pop : function() { 199 - if (this._pushed) { 200 - this._pushed = false; 201 - JX.Workflow._pop(); 202 - } 203 - }, 204 - _destroy : function() { 205 - if (this._root) { 206 - JX.DOM.remove(this._root); 207 - this._root = null; 208 - } 209 - }, 210 - start : function() { 211 - var uri = this.getURI(); 212 - var method = this.getMethod(); 213 - var r = new JX.Request(uri, JX.bind(this, this._onload)); 214 - r.setData(this.getData()); 215 - r.setDataSerializer(this.getDataSerializer()); 216 - if (method) { 217 - r.setMethod(method); 218 - } 219 - r.listen('finally', JX.bind(this, this.invoke, 'finally')); 220 - r.listen('error', JX.bind(this, function(error) { 221 - var e = this.invoke('error', error); 222 - if (e.getStopped()) { 223 - return; 224 - } 225 - // TODO: Default error behavior? On Facebook Lite, we just shipped the 226 - // user to "/error/". We could emit a blanket 'workflow-failed' type 227 - // event instead. 228 - })); 229 - r.send(); 230 - } 231 - }, 232 - 233 - properties : { 234 - handler : null, 235 - closeHandler : null, 236 - data : null, 237 - dataSerializer : null, 238 - method : null, 239 - URI : null 240 - } 241 - 242 - });
-3
webroot/rsrc/js/javelin/workflow.min.js
··· 1 - /** @provides javelin-workflow-prod */ 2 - 3 - JX.install('Mask',{statics:{_a:0,_b:null,show:function(){if(!JX.Mask._a){JX.Mask._b=JX.$N('div',{className:'jx-mask'});document.body.appendChild(JX.Mask._b);JX.$V.getDocument().setDim(JX.Mask._b);}++JX.Mask._a;},hide:function(){--JX.Mask._a;if(!JX.Mask._a){JX.DOM.remove(JX.Mask._b);JX.Mask._b=null;}}}});JX.install('Workflow',{construct:function(b,a){this.setURI(b);this.setData(a||{});},events:['error','finally','submit'],statics:{_c:[],newFromForm:function(b,a){var d=[].concat(JX.DOM.scry(b,'input'),JX.DOM.scry(b,'button'),JX.DOM.scry(b,'textarea'));for(var c=0;c<d.length;c++)if(d[c].disabled){delete d[c];}else d[c].disabled=true;var e=new JX.Workflow(b.getAttribute('action'),JX.copy(a||{},JX.DOM.serialize(b)));e.setMethod(b.getAttribute('method'));e.listen('finally',function(){for(var f=0;f<d.length;f++)d[f]&&(d[f].disabled=false);});return e;},newFromLink:function(a){var b=new JX.Workflow(a.href);return b;},_d:function(a){JX.Mask.show();JX.Workflow._c.push(a);},_e:function(){var a=JX.Workflow._c.pop();(a.getCloseHandler()||JX.bag)();a._f();JX.Mask.hide();},disable:function(){JX.Workflow._g=true;},_h:function(event){if(JX.Stratcom.pass())return;if(JX.Workflow._g)return;var e=event.getTarget();if(e.name=='__cancel__'||e.name=='__close__'){JX.Workflow._e();}else{var d=event.getNode('jx-dialog');var b=JX.DOM.serialize(d);b[e.name]=true;b.__wflow__=true;var a=JX.Workflow._c[JX.Workflow._c.length-1];var c=a.invoke('submit',{form:d,data:b});if(!c.getStopped()){a._f();a.setURI(d.getAttribute('action')||a.getURI()).setData(b).start();}}event.prevent();}},members:{_i:null,_j:false,_k:function(c){if(c&&(typeof c.redirect!='undefined')){JX.go(c.redirect,true);}else if(c&&c.dialog){this._d();this._i=JX.$N('div',{className:'jx-client-dialog'},JX.HTML(c.dialog));JX.DOM.listen(this._i,'click','tag:button',JX.Workflow._h);document.body.appendChild(this._i);var b=JX.$V.getDim(this._i);var e=JX.$V.getViewport();var d=JX.$V.getScroll();JX.$V((e.x-b.x)/2,d.y+100).setPos(this._i);try{JX.DOM.focus(JX.DOM.find(this._i,'button','__default__'));var inputs=JX.DOM.scry(this._i,'input').concat(JX.DOM.scry(this._i,'textarea'));var miny=Number.POSITIVE_INFINITY;var target=null;for(var ii=0;ii<inputs.length;++ii)if(inputs[ii].type!='hidden'){var p=JX.$V(inputs[ii]);if(p.y<miny){miny=p.y;target=inputs[ii];}}target&&JX.DOM.focus(target);}catch(a){}}else if(this.getHandler()){this.getHandler()(c);this._e();}},_d:function(){if(!this._j){this._j=true;JX.Workflow._d(this);}},_e:function(){if(this._j){this._j=false;JX.Workflow._e();}},_f:function(){if(this._i){JX.DOM.remove(this._i);this._i=null;}},start:function(){var c=this.getURI();var a=this.getMethod();var b=new JX.Request(c,JX.bind(this,this._k));b.setData(this.getData());b.setDataSerializer(this.getDataSerializer());if(a)b.setMethod(a);b.listen('finally',JX.bind(this,this.invoke,'finally'));b.listen('error',JX.bind(this,function(e){var d=this.invoke('error',e);if(d.getStopped())return;}));b.send();}},properties:{handler:null,closeHandler:null,data:null,dataSerializer:null,method:null,URI:null}});