Changeset 212

Show
Ignore:
Timestamp:
07/07/10 06:47:22 (2 months ago)
Author:
scott
Message:

Fixes in deferred document processing, mostly working implementation.

  • Made several fixes in the handling of deferred document processing.
    • Fixed bug where nodes would get removed before they were updated.
      • Simple update use case for example is now working in mozilla.
    • Added namespace support to XML documents based on context.
      • XML Configuration on XML documents uses namespaces defined in configuration document for xpath expressions.
      • Programmatic configuration on XML documents uses namespaces defined in document based on the currently selected node.
      • Identifies HTML documents by looking for document.body and if present, ignores namespaces to avoid errors in mozilla.
  • Created context aware observer.
    • Created new observer-wrapper which is aware of the event context. This observer-wrapper is returned as the core observer for each event, and wraps the static transform observers to provide a context argument indiciating the node which recieved the event. This is subsequently used in the locators.

Chrome seems to now be non-functional, for reasons currently unknown to me.

Location:
xmvc/branches/remove-namespaces
Files:
1 added
4 modified

Legend:

Unmodified
Added
Removed
  • xmvc/branches/remove-namespaces/example.html

    r209 r212  
    2828    <script type="text/javascript" src="src/main/javascript/string/sprintf.js"></script> 
    2929    <script type="text/javascript" src="src/main/javascript/array/each.js"></script> 
     30    <script type="text/javascript" src="src/main/javascript/array/grep.js"></script> 
    3031    <script type="text/javascript" src="src/main/javascript/util/HttpClient.js"></script> 
    3132    <!-- For the run script loader --> 
     
    4748    <pre id="content"/> 
    4849 
    49     <a id="ping">Ping</a> 
     50    <a id="ping" href="#">Ping</a> 
    5051  </body> 
    5152</html> 
  • xmvc/branches/remove-namespaces/example.xml

    r209 r212  
    2828  </object> 
    2929 
     30  <object id="greeter" constructor="Greeter"/> 
     31 
    3032  <object id="foo" constructor="Foo"> 
    3133    <argument object="bar"/> 
     
    4547          </node> 
    4648 
    47           <!-- 
    4849          <node id="ping"> 
    49             <action type="click" object="greeter" handler="hello"> 
     50            <action type="click" object="greeter" method="hello"> 
    5051              <transform type="xslt" path="//body" behavior="append" 
    5152                  src="example.xsl"> 
    5253                <param name="message" value="Hello, world"/> 
    53               </transformer> 
     54              </transform> 
    5455            </action> 
    5556          </node> 
    5657 
    57           <node path="//div[contains(@class, 'dialog')]/input[@type = 'submit']"> 
    58             <action type="click" object="greeter" handler="goodbye"/> 
     58          <!-- This is the guy that's erroring? --> 
     59          <node path="//div[@class = 'dialog']/input[@type = 'submit']"> 
     60            <action type="click" object="greeter" method="goodbye"/> 
    5961          </node> 
    60           --> 
    6162        </controller> 
    6263      </xml> 
  • xmvc/branches/remove-namespaces/src/main/javascript/joice/joice.js

    r209 r212  
    745745        var src  = script.getAttribute("src") 
    746746 
    747         /* 
    748            window.alert("[configuration] loading script %s (%s)".sprintf( 
    749            src, type)) 
    750            */ 
    751  
    752747        var loader = loaders[type] 
    753748 
     
    825820    /* Default scopes */ 
    826821    var scopes         = { 
    827  // TODO: IE is not finding these constructors for whatever reason.. */ 
    828822        "singleton": new SingletonScope(), 
    829823        "prototype": new PrototypeScope() 
  • xmvc/branches/remove-namespaces/src/main/javascript/xmvc/controller.js

    r209 r212  
    317317 
    318318/** 
     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 */ 
     331function 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 */ 
     359function 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 */ 
     390function 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 */ 
     403function 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 */ 
     412function 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 */ 
     421function 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 */ 
     442function 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 */ 
     523function 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/** 
    319558 * @class The eXtensible Markup View Controller. 
    320559 * 
     
    352591    } 
    353592 
     593    var transformers = { 
     594        "xslt": XSLTTransformer, 
     595        "object": ObjectTransformer 
     596    } 
     597 
     598    var mutators = { 
     599        "append": new AppendMutator(), 
     600        "replace": new ReplaceMutator() 
     601    } 
     602 
     603    this.getMutator = function (mutation) { 
     604//      window.alert(mutators[mutation]) 
     605        return new MutatorWrapper(this, mutators[mutation]) 
     606    } 
     607 
     608    this.getTransformer = function (type, source) { 
     609        return new transformers[type](source) 
     610    } 
     611 
    354612    /* TODO: Debug document scope... 
    355613     * 
     
    385643    } 
    386644 
    387     var handleProcessing = function (node) { 
     645    var handleProcessing = function (controller, node) { 
    388646        /* Walk the node if we have stuff to do... */ 
    389647        if (walkObservers.length) { 
    390             this.walk(node) 
     648            controller.walk(node) 
    391649        } 
    392650 
     
    401659    this.process = function (node) { 
    402660        var controller = this 
    403          
     661 
    404662        /* Cycle in and out of the browser context to allow for rendering. 
    405663         * This sets the timeout to 1ms so it won't contend with the "ready" 
    406664         * state handlers used to initialize some other frameworks (including 
    407665         * joice).  We want those to go first, just incase they create objects 
    408          * or something (like joice). */ 
    409         window.setTimeout(function () { 
    410             handleProcessing.call(controller, node) 
    411         }, 1) 
     666         * or something (like joice).    
     667         * 
     668         * Unwind any document fragement *first*.  This is because once the 
     669         * actual modification takes place, the nodes will no longer be a child 
     670         * of this fragment. 
     671         * 
     672         * TODO Determine if this causes any problems in IE, which is known to 
     673         * actually pause the javascript runtime to process manipulations to 
     674         * the document — it's unknown if it will pause the javascript runtime 
     675         * to process timeouts. 
     676         */ 
     677        if (node.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { 
     678            for (var node = node.firstChild;  
     679                    node != null;  
     680                    node = node.nextSibling) 
     681            { 
     682                window.setTimeout(handleProcessing, 1, this, node) 
     683            } 
     684        } 
     685        else { 
     686            window.setTimeout(handleProcessing, 1, this, node) 
     687        } 
    412688    } 
    413689 
     
    436712    } 
    437713 
    438     this.createLocator = function (type, expression) { 
     714    this.createLocator = function (type, expression, node) { 
    439715        if (locators[type] != undefined) { 
    440             return new locators[type](this, expression) 
     716            return new locators[type](this, expression, node) 
    441717        } 
    442718        else { 
     
    589865} 
    590866 
    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 */ 
     881function ContextAwareObserver (observer, context) { 
     882    /** 
     883     * Pass along the notification. 
     884     */ 
     885    this.notify = function (input) { 
     886        observer.notify(input, context) 
    735887    } 
    736888} 
     
    762914     * @param input The input object to pass along. 
    763915     */ 
    764     this.notify = function (input) { 
     916    this.notify = function (input, context) { 
    765917        observers.each(function (observer) { 
    766             observer.notify(input) 
     918            observer.notify(input, context) 
    767919        }) 
    768920    } 
     
    771923     * Notify all encapsulated observers of the exception. 
    772924     */ 
    773     this.error = function (exception) { 
     925    this.error = function (exception, context) { 
    774926        observers.each(function (observer) { 
    775             observer.error(exception) 
     927            observer.error(exception, context) 
    776928        }) 
    777929    } 
     
    779931 
    780932function TransformObserver (transformers, locator, mutator) { 
     933    if (transformers.grep(undefined).length) { 
     934        var error = new Error("transfomers argument is null") 
     935        window.alert("%s\n%s".sprintf(error.message, error.stack)) 
     936    } 
     937 
    781938    this.error  = function (error) { 
    782939        /* I don't feel like this is the right thing for this observer to do. 
     
    785942    } 
    786943 
    787     this.notify = function (input) { 
     944    this.notify = function (input, context) { 
    788945        var modification = this.transform(input) 
    789946        /* XXX Shouldn't locate() be taking a context here? */ 
    790         var element      = locator.locate() 
     947 
     948        var element      = locator.locate(context) 
    791949 
    792950        if (element == null) { 
     
    811969} 
    812970 
    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) { 
     971function 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 
    8761010        var object = context.load(objectName) 
    8771011 
    878         return object.transform(input, parameters) 
     1012        if (object == null) { 
     1013            throw new Error("Unable to load \"%s\"".sprintf(objectName)) 
     1014        } 
     1015 
     1016        /* If the method returned a value, simulate a notification with it. */ 
     1017//      try { 
     1018         
     1019        var contextAware = new ContextAwareObserver(observer, this) 
     1020 
     1021        var result = object[methodName](contextAware,  
     1022                { element: this, event: event, extra: e, scope: scope }) 
     1023//      } 
     1024//      catch (error) { 
     1025//          observer.error(error) 
     1026//      } 
     1027 
     1028        /* XXX This doesn't seem right, we might want to stop events by 
     1029         * returning "false" etc...  
     1030        if (typeof result != "undefined" && result != null) { 
     1031            observer.notify(result) 
     1032        } 
     1033         */ 
     1034 
     1035        return result 
     1036    } 
     1037 
     1038    this.toString = function Action_toString () { 
     1039        return "[action (%s.%s), observer: %s]".sprintf( 
     1040                objectName, methodName, observer 
     1041                ); 
    8791042    } 
    8801043} 
     
    8981061        var type     = transform.getAttribute("type") 
    8991062 
     1063        /* TODO: Build parameters...sheesh */ 
     1064        /* TODO: Report errors...! */ 
    9001065        var transformer = controller.getTransformer(type, source) 
     1066 
     1067        return transformer 
    9011068    } 
    9021069 
     
    9161083        var mutation = "replace" 
    9171084 
    918         if (transform.hasAttribute("behavior")) { 
     1085        if (transform.getAttribute("behavior")) { 
    9191086            mutation = transform.getAttribute("behavior") 
    9201087        } 
     
    9261093                 node = this._nextTransformNode(node))  
    9271094        { 
    928             var transformer = this._buildTransformer(transform) 
    929  
    9301095            /* Prepend to the list, they need to be processed from lowest 
    9311096             * descendant to parent */ 
    932             transformers.unshift(transformer) 
     1097            transformers.unshift(this._buildTransformer(transform)) 
    9331098        } 
    9341099 
     
    9711136        } 
    9721137 
    973         return controller.createLocator(locateType, locatePath) 
     1138        return controller.createLocator(locateType, locatePath, node) 
    9741139    } 
    9751140