| | 319 | * @class A passthrough object transformer. |
| | 320 | * |
| | 321 | * <p>Simple transformer that loads an object (by name) from the global joice |
| | 322 | * context and calls {@link #transform()} on it. The {@link transform()} |
| | 323 | * method is given the parameters of the input object, and the map of name |
| | 324 | * value pairs this object encapsulates.</p> |
| | 325 | * |
| | 326 | * @constructor Create a new object transformer. |
| | 327 | * |
| | 328 | * @param objectName The label of the object to load. |
| | 329 | * @param parameters A map of name value pairs to pass to the transform method. |
| | 330 | */ |
| | 331 | function ObjectTransformer (objectName, parameters) { |
| | 332 | /** |
| | 333 | * Load the given object and invoke transform. |
| | 334 | * |
| | 335 | * @param input The object to be transformed. |
| | 336 | * @return An XML Document fragment, or an object for another transformer. |
| | 337 | */ |
| | 338 | this.transform = function (input) { |
| | 339 | var object = context.load(objectName) |
| | 340 | |
| | 341 | return object.transform(input, parameters) |
| | 342 | } |
| | 343 | } |
| | 344 | |
| | 345 | /** |
| | 346 | * @class An XSLT Transformer. |
| | 347 | * |
| | 348 | * <p>This simple XSLT Transformer asynchornously fetches a stylesheet, loads |
| | 349 | * it into the XSLTProcessor and configures it with the given set of |
| | 350 | * parameters. It then uses this stylesheet for any and all |
| | 351 | * transformations.</p> |
| | 352 | * |
| | 353 | * @constructor |
| | 354 | * Create a new XSLT Transformer. |
| | 355 | * |
| | 356 | * @param stylesrc The URI of the stylesheet to load. |
| | 357 | * @param parameters A map of parameters. |
| | 358 | */ |
| | 359 | function XSLTTransformer (stylesrc, parameters) { |
| | 360 | /* This should use document.load("..."), but that doesn't seem to work in |
| | 361 | * safari. Goddamn browser portability nonsense. */ |
| | 362 | var request = new HttpRequest(stylesrc) |
| | 363 | var processor = new XSLTProcessor() |
| | 364 | |
| | 365 | request.async = true |
| | 366 | request.callback = function (request) { |
| | 367 | processor.importStylesheet(request.responseXML) |
| | 368 | |
| | 369 | for (var key in parameters) { |
| | 370 | processor.setParameter(null, key, parameters[key]) |
| | 371 | } |
| | 372 | } |
| | 373 | |
| | 374 | request.get() |
| | 375 | |
| | 376 | /** |
| | 377 | * Transform the given input. |
| | 378 | * |
| | 379 | * @param input An XML document fragment. |
| | 380 | * @return An XML Document fragment for the current document. |
| | 381 | */ |
| | 382 | this.transform = function (input) { |
| | 383 | return processor.transformToFragment(input, document) |
| | 384 | } |
| | 385 | } |
| | 386 | |
| | 387 | /** |
| | 388 | * A mutator which processes the incoming modification. |
| | 389 | */ |
| | 390 | function MutatorWrapper (controller, mutator) { |
| | 391 | this.apply = function MutatorWrapper_apply (context, modification) { |
| | 392 | if (typeof modification != "undefined") { |
| | 393 | controller.process(modification) |
| | 394 | } |
| | 395 | |
| | 396 | mutator.apply(context, modification) |
| | 397 | } |
| | 398 | } |
| | 399 | |
| | 400 | /** |
| | 401 | * A mutator which appends the transform result to the context. |
| | 402 | */ |
| | 403 | function AppendMutator () { |
| | 404 | this.apply = function AppendMutator_apply (context, modification) { |
| | 405 | context.appendChild(modification) |
| | 406 | } |
| | 407 | } |
| | 408 | |
| | 409 | /** |
| | 410 | * A mutator which replaces the context element with the transform result. |
| | 411 | */ |
| | 412 | function ReplaceMutator () { |
| | 413 | this.apply = function ReplaceMutator_apply (context, modification) { |
| | 414 | context.parentNode.replaceChild(modification, context) |
| | 415 | } |
| | 416 | } |
| | 417 | |
| | 418 | /** |
| | 419 | * A mutator which simply removes elements. |
| | 420 | */ |
| | 421 | function RemoveMutator () { |
| | 422 | this.apply = function RemoveMutator_apply (context, modification) { |
| | 423 | context.parentNode.removeChild(context) |
| | 424 | |
| | 425 | return null |
| | 426 | } |
| | 427 | } |
| | 428 | |
| | 429 | /** |
| | 430 | * An XPath selector. |
| | 431 | * |
| | 432 | * <p>Relies on there being element and document level <code>selectNodes</code> |
| | 433 | * and <code>selectSingleNode</code> methods as provided by Internet Explorer |
| | 434 | * and Sarissa.</p> |
| | 435 | * |
| | 436 | * @constructor Create a new XPath based Locator. |
| | 437 | * @param controller The controller this locator is to be bound with. |
| | 438 | * @param expression The xpath expression. |
| | 439 | * @param source The element where this expression was defined in |
| | 440 | * configuration. |
| | 441 | */ |
| | 442 | function XPathLocator (controller, expression, source) |
| | 443 | { |
| | 444 | var log = new Log(Log.DEBUG, Log.consoleLogger) |
| | 445 | var resolver = null |
| | 446 | |
| | 447 | /* If this isn't an HTML document, we will create an NSResolver appropriate |
| | 448 | * to the context (configuration uses the config document, other |
| | 449 | * programatic usage leverages the definitions of the document being |
| | 450 | * controlled). If it is an HTML document, then just leave the resolver |
| | 451 | * null since it won't support namespaces anyway. |
| | 452 | */ |
| | 453 | if (!document.body) { |
| | 454 | if (typeof source != "undefined") { |
| | 455 | resolver = source.ownerDocument.createNSResolver(source) |
| | 456 | } |
| | 457 | else { |
| | 458 | resolver = controller.document.createNSResolver( |
| | 459 | controller.document.documentElement |
| | 460 | ) |
| | 461 | } |
| | 462 | } |
| | 463 | |
| | 464 | if (typeof XPathResult == "undefined") { |
| | 465 | log.warn("Current environment does not support xpath") |
| | 466 | } |
| | 467 | |
| | 468 | this.locate = function XPathLocator_locate (context) { |
| | 469 | if (typeof XPathResult != "undefined") { |
| | 470 | var matches = controller.document.evaluate( |
| | 471 | expression, |
| | 472 | context, |
| | 473 | resolver, |
| | 474 | XPathResult.FIRST_ORDERED_NODE_TYPE, |
| | 475 | null |
| | 476 | ) |
| | 477 | |
| | 478 | return matches.singleNodeValue |
| | 479 | } |
| | 480 | } |
| | 481 | |
| | 482 | this.list = function XPathLocator_list (context) { |
| | 483 | var results = [] |
| | 484 | |
| | 485 | /* If we're in an environment that doesn't support xpath... |
| | 486 | * so be it. |
| | 487 | */ |
| | 488 | if (typeof XPathResult != "undefined") { |
| | 489 | var matches = controller.document.evaluate( |
| | 490 | expression, |
| | 491 | context, |
| | 492 | resolver, |
| | 493 | XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, |
| | 494 | null |
| | 495 | ) |
| | 496 | |
| | 497 | for (var i = 0; i < matches.snapshotLength; i++) { |
| | 498 | results.push(matches.snapshotItem(i)) |
| | 499 | } |
| | 500 | } |
| | 501 | |
| | 502 | return results |
| | 503 | } |
| | 504 | |
| | 505 | this.toString = function XPathLocator_toString () { |
| | 506 | return "xpath://%s/".sprintf(expression) |
| | 507 | } |
| | 508 | } |
| | 509 | |
| | 510 | /** |
| | 511 | * A simple element-id locator. |
| | 512 | * |
| | 513 | * <p>This locator simply uses <code>getElementById(String)</code> on the root |
| | 514 | * document. If the location is contextual, it traverses the acenstors of the |
| | 515 | * discovered searching for a node which matches the given context. If the |
| | 516 | * node found was discovered to be within the approprate context, then it is |
| | 517 | * used. Otherwise it is discarded.</p> |
| | 518 | * |
| | 519 | * @constructor |
| | 520 | * @param controller The controller this locator is to be bound with. |
| | 521 | * @param id The id expression. |
| | 522 | */ |
| | 523 | function FastIdLocator (controller, id) { |
| | 524 | this.locate = function FastIdLocator_locate (context) { |
| | 525 | /* fast id locator is based on id's in the document being controlled, |
| | 526 | * ignore the source document.. |
| | 527 | */ |
| | 528 | var element = controller.document.getElementById(id) |
| | 529 | |
| | 530 | if (context == null || typeof context == "undefined") { |
| | 531 | return element |
| | 532 | } |
| | 533 | |
| | 534 | |
| | 535 | for (var cursor = element; cursor != null; cursor = cursor.parentNode) |
| | 536 | { |
| | 537 | if (cursor == context) { |
| | 538 | return element; |
| | 539 | } |
| | 540 | } |
| | 541 | |
| | 542 | return null; |
| | 543 | } |
| | 544 | |
| | 545 | this.list = function FastIdLocator_list (context) { |
| | 546 | var element = this.locate(context) |
| | 547 | |
| | 548 | return element != null ? [ element ] : [] |
| | 549 | } |
| | 550 | |
| | 551 | this.toString = function FastIdLocator_toString () { |
| | 552 | return "node://#%s".sprintf(id) |
| | 553 | } |
| | 554 | } |
| | 555 | |
| | 556 | |
| | 557 | /** |
| 591 | | function Action (scope, objectName, methodName, observer) { |
| 592 | | var listable = new ListableObserver([]) |
| 593 | | |
| 594 | | if (typeof observer != "undefined") |
| 595 | | listable.addObserver(observer) |
| 596 | | |
| 597 | | this.addObserver = function Action_addObserver (observer) { |
| 598 | | listable.addObserver(observer) |
| 599 | | |
| 600 | | return this |
| 601 | | } |
| 602 | | |
| 603 | | /** |
| 604 | | * Handle an event. |
| 605 | | * |
| 606 | | * <p>Handle an actual DOM event and perform the appropriate action for the |
| 607 | | * event, execute the appropriate method on the appropriate object and |
| 608 | | * provide it the appropriate observer. This method is called directly |
| 609 | | * from the DOM Event API.</p> |
| 610 | | * |
| 611 | | * @param event The event object (implementation specific) |
| 612 | | * @param e (optional) Event Target or similar element (implementation |
| 613 | | * specific) |
| 614 | | * |
| 615 | | * @result Implementation specific, comes from action result. |
| 616 | | */ |
| 617 | | /* XXX This method is called from the context of a DOM element. It *must* |
| 618 | | * not rely on local "this" references that do not point to the current |
| 619 | | * target element for the given event. |
| 620 | | */ |
| 621 | | this.handle = function Action_handle (event, e) { |
| 622 | | //scope.select(this) |
| 623 | | |
| 624 | | var object = context.load(objectName) |
| 625 | | |
| 626 | | /* If the method returned a value, simulate a notification with it. */ |
| 627 | | try { |
| 628 | | var result = object[methodName](observer, |
| 629 | | { element: this, event: event, extra: e, scope: scope }) |
| 630 | | } |
| 631 | | catch (error) { |
| 632 | | observer.error(error) |
| 633 | | } |
| 634 | | |
| 635 | | /* XXX This doesn't seem right, we might want to stop events by |
| 636 | | * returning "false" etc... |
| 637 | | if (typeof result != "undefined" && result != null) { |
| 638 | | observer.notify(result) |
| 639 | | } |
| 640 | | */ |
| 641 | | |
| 642 | | return result |
| 643 | | } |
| 644 | | |
| 645 | | this.toString = function Action_toString () { |
| 646 | | return "[action (%s.%s), observer: %s]".sprintf( |
| 647 | | objectName, methodName, observer |
| 648 | | ); |
| 649 | | } |
| 650 | | } |
| 651 | | |
| 652 | | function MutatorWrapper (controller, mutator) { |
| 653 | | this.apply = function MutatorWrapper_apply (context, modification) { |
| 654 | | mutator.apply(context, modification) |
| 655 | | |
| 656 | | controller.process(modification) |
| 657 | | } |
| 658 | | } |
| 659 | | |
| 660 | | function AppendMutator () { |
| 661 | | this.controller = null |
| 662 | | |
| 663 | | this.apply = function AppendMutator_apply (context, modification) { |
| 664 | | context.appendChild(modification) |
| 665 | | } |
| 666 | | } |
| 667 | | |
| 668 | | function ReplaceMutator () { |
| 669 | | this.controller = null |
| 670 | | |
| 671 | | this.apply = function ReplaceMutator_apply (context, modification) { |
| 672 | | context.parentNode.replaceChild(modification, context) |
| 673 | | } |
| 674 | | } |
| 675 | | |
| 676 | | /** |
| 677 | | * An XPath selector. |
| 678 | | * |
| 679 | | * <p>Relies on there being element and document level <code>selectNodes</code> |
| 680 | | * and <code>selectSingleNode</code> methods as provided by Internet Explorer |
| 681 | | * and Sarissa.</p> |
| 682 | | * |
| 683 | | * @constructor Create a new XPath based Locator. |
| 684 | | * @param expression The xpath expression. |
| 685 | | */ |
| 686 | | function XPathLocator (controller, expression) { |
| 687 | | this.locate = function XPathLocator_locate (context) { |
| 688 | | if (context == null || typeof context == "undefined") { |
| 689 | | context = controller.document |
| 690 | | } |
| 691 | | |
| 692 | | return context.selectSingleNode(expression) |
| 693 | | } |
| 694 | | |
| 695 | | this.list = function XPathLocator_list (context) { |
| 696 | | if (context == null) { |
| 697 | | context = controller.document |
| 698 | | } |
| 699 | | |
| 700 | | return context.selectNodes(expression) |
| 701 | | } |
| 702 | | |
| 703 | | this.toString = function XPathLocator_toString () { |
| 704 | | return "xpath://%s/".sprintf(expression) |
| 705 | | } |
| 706 | | } |
| 707 | | |
| 708 | | function FastIdLocator (controller, id) { |
| 709 | | this.locate = function FastIdLocator_locate (context) { |
| 710 | | var element = controller.document.getElementById(id) |
| 711 | | |
| 712 | | if (context == null || typeof context == "undefined") { |
| 713 | | return element |
| 714 | | } |
| 715 | | |
| 716 | | |
| 717 | | for (var cursor = element; cursor != null; cursor = cursor.parentNode) |
| 718 | | { |
| 719 | | if (cursor == context) { |
| 720 | | return element; |
| 721 | | } |
| 722 | | } |
| 723 | | |
| 724 | | return null; |
| 725 | | } |
| 726 | | |
| 727 | | this.list = function FastIdLocator_list (context) { |
| 728 | | var element = this.locate(context) |
| 729 | | |
| 730 | | return element != null ? [ element ] : [] |
| 731 | | } |
| 732 | | |
| 733 | | this.toString = function FastIdLocator_toString () { |
| 734 | | return "node://#%s".sprintf(id) |
| | 867 | /** |
| | 868 | * An observer which is aware of the event context. |
| | 869 | * |
| | 870 | * <p>This observer is a wrapper for other observers (this is coming to be |
| | 871 | * quite a chain). The observer simply keeps track of the node in which the |
| | 872 | * event took place, and passes the node along as a second argument to the |
| | 873 | * static observers.</p> |
| | 874 | * |
| | 875 | * <p>This is required due to the current structure of {@link |
| | 876 | * TransformObserver} as a static object.</p> |
| | 877 | * |
| | 878 | * @param observer Observer to wrap, typically {@link ListableObserver}. |
| | 879 | * @param context The element where the event took place. |
| | 880 | */ |
| | 881 | function ContextAwareObserver (observer, context) { |
| | 882 | /** |
| | 883 | * Pass along the notification. |
| | 884 | */ |
| | 885 | this.notify = function (input) { |
| | 886 | observer.notify(input, context) |
| 813 | | /** |
| 814 | | * @class An XSLT Transformer. |
| 815 | | * |
| 816 | | * <p>This simple XSLT Transformer asynchornously fetches a stylesheet, loads |
| 817 | | * it into the XSLTProcessor and configures it with the given set of |
| 818 | | * parameters. It then uses this stylesheet for any and all |
| 819 | | * transformations.</p> |
| 820 | | * |
| 821 | | * @constructor |
| 822 | | * Create a new XSLT Transformer. |
| 823 | | * |
| 824 | | * @param stylesrc The URI of the stylesheet to load. |
| 825 | | * @param parameters A map of parameters. |
| 826 | | */ |
| 827 | | function XSLTTransformer (stylesrc, parameters) { |
| 828 | | /* This should use document.load("..."), but that doesn't seem to work in |
| 829 | | * safari. Goddamn browser portability nonsense. */ |
| 830 | | var request = new HttpRequest(stylesrc) |
| 831 | | var processor = new XSLTProcessor() |
| 832 | | |
| 833 | | request.async = true |
| 834 | | request.callback = function (stylesheet) { |
| 835 | | processor.importStylesheet(stylesheet) |
| 836 | | |
| 837 | | for (var key in parameters) { |
| 838 | | processor.setParameter(null, key, parameters[key]) |
| 839 | | } |
| 840 | | } |
| 841 | | |
| 842 | | request.get() |
| 843 | | |
| 844 | | /** |
| 845 | | * Transform the given input. |
| 846 | | * |
| 847 | | * @param input An XML document fragment. |
| 848 | | * @return An XML Document fragment for the current document. |
| 849 | | */ |
| 850 | | this.transform = function (input) { |
| 851 | | return processor.transformToFragment(input, document) |
| 852 | | } |
| 853 | | } |
| 854 | | |
| 855 | | /** |
| 856 | | * @class A passthrough object transformer. |
| 857 | | * |
| 858 | | * <p>Simple transformer that loads an object (by name) from the global joice |
| 859 | | * context and calls {@link #transform()} on it. The {@link transform()} |
| 860 | | * method is given the parameters of the input object, and the map of name |
| 861 | | * value pairs this object encapsulates.</p> |
| 862 | | * |
| 863 | | * @constructor Create a new object transformer. |
| 864 | | * |
| 865 | | * @param objectName The label of the object to load. |
| 866 | | * @param parameters A map of name value pairs to pass to the transform method. |
| 867 | | */ |
| 868 | | function ObjectTransformer (objectName, parameters) { |
| 869 | | /** |
| 870 | | * Load the given object and invoke transform. |
| 871 | | * |
| 872 | | * @param input The object to be transformed. |
| 873 | | * @return An XML Document fragment, or an object for another transformer. |
| 874 | | */ |
| 875 | | this.transform = function (input) { |
| | 971 | function Action (scope, objectName, methodName, observer) { |
| | 972 | var listable = new ListableObserver([]) |
| | 973 | |
| | 974 | if (!methodName) { |
| | 975 | var error = new Error("methodName is null") |
| | 976 | window.alert(error.stack) |
| | 977 | } |
| | 978 | |
| | 979 | if (typeof observer != "undefined") { |
| | 980 | listable.addObserver(observer) |
| | 981 | } |
| | 982 | |
| | 983 | this.addObserver = function Action_addObserver (observer) { |
| | 984 | listable.addObserver(observer) |
| | 985 | |
| | 986 | return this |
| | 987 | } |
| | 988 | |
| | 989 | /** |
| | 990 | * Handle an event. |
| | 991 | * |
| | 992 | * <p>Handle an actual DOM event and perform the appropriate action for the |
| | 993 | * event, execute the appropriate method on the appropriate object and |
| | 994 | * provide it the appropriate observer. This method is called directly |
| | 995 | * from the DOM Event API.</p> |
| | 996 | * |
| | 997 | * @param event The event object (implementation specific) |
| | 998 | * @param e (optional) Event Target or similar element (implementation |
| | 999 | * specific) |
| | 1000 | * |
| | 1001 | * @result Implementation specific, comes from action result. |
| | 1002 | */ |
| | 1003 | /* XXX This method is called from the context of a DOM element. It *must* |
| | 1004 | * not rely on local "this" references that do not point to the current |
| | 1005 | * target element for the given event. |
| | 1006 | */ |
| | 1007 | this.handle = function Action_handle (event, e) { |
| | 1008 | //scope.select(this) |
| | 1009 | |