Changeset 209

Show
Ignore:
Timestamp:
07/05/10 08:05:34 (2 months ago)
Author:
scott
Message:

Restructured codebase.

  • Consolidated xmvc so it's a single module file.
  • Consolidated joice so it's a simple module and bootstrapper.

Tested — still functioning (also no longer erroring in IE, coincidentally).

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

Legend:

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

    r208 r209  
    3636    <script type="text/javascript" src="src/main/javascript/util/Semaphore.js"></script> 
    3737    <script type="text/javascript" src="src/main/javascript/util/properties.js"></script> 
    38     <script type="text/javascript" src="src/main/javascript/joice/Scope.js"></script> 
    39     <script type="text/javascript" src="src/main/javascript/joice/Context.js"></script> 
    40     <script type="text/javascript" src="src/main/javascript/joice/ContextConfiguration.js"></script> 
    41     <script type="text/javascript" src="src/main/javascript/joice/Loader.js"></script> 
    42     <script type="text/javascript" src="src/main/javascript/joice/Bootstrap.js"></script> 
     38    <script type="text/javascript" src="src/main/javascript/joice/joice.js"></script> 
     39    <script type="text/javascript" src="src/main/javascript/joice/bootstrap.js"></script> 
    4340 
    4441    <link rel="context" type="text/xml" href="example.xml"/> 
  • xmvc/branches/remove-namespaces/example.xml

    r205 r209  
    44  <!-- text/javascript;debug runs the IE-incompatible jit loader which keeps 
    55  track of the actual source location... --> 
    6   <script type="text/javascript" src="src/main/javascript/xmvc/ControllerScope.js"/> 
    7   <script type="text/javascript" src="src/main/javascript/xmvc/Transformer.js"/> 
    8   <script type="text/javascript" src="src/main/javascript/xmvc/Configuration.js"/> 
     6  <script type="text/javascript" src="src/main/javascript/xmvc/controller.js"/> 
    97 
    108  <script type="text/javascript" src="example.js"/> 
  • xmvc/branches/remove-namespaces/src/main/javascript/joice/ContextConfiguration.js

    r208 r209  
    1  
    2 var JOICENS = "http://www.blisted.org/ns/joice/" 
    3 var JOICENS_SCRIPT   = "script" 
    4 var JOICENS_OBJECT   = "object" 
    5 var JOICENS_VALUE    = "value" 
    6 var JOICENS_ARRAY    = "array" 
    7 var JOICENS_XML      = "xml" 
    8 var JOICENS_PROPERTY = "property" 
    9 var JOICENS_ARGUMENT = "argument" 
    10  
    111/* NOTE 20091222T12:52:57 Always use tagName, not localName.  Even though 
    122 * tagName is funky and inappropriate, localName doesn't work *at all* in IE. 
    133 */ 
    144 
    15 /** 
    16  * @constructor Create a new configuration filter. 
    17  * 
    18  * @param {Properties} The given properties object to use for value lookups. 
    19  * 
    20  * @class Interpolates variables with the value of a property. 
    21  * @author <a href="mailto:tag@cpan.org">Scott S. McCoy</a> 
    22  */ 
    23 function XMLConfigurationFilter (configurator) { 
    24     var properties = new Properties() 
    25  
    26     /* The \ isn't needed within a character class, I know, but it stops vim 
    27      * from matching it. */ 
    28     var variable = /\$\{([^\}]*)\}/g 
    29  
    30     var lookupPropertyReplacement = function (match, token, position, string) { 
    31         return properties.getProperty(token) 
    32     } 
    33  
    34     this.addProperties = function (newProperties) { 
    35         properties.mergeProperties(newProperties) 
    36     } 
    37      
    38     /** 
    39      * Configure the given string through interpolation. 
    40      * 
    41      * <p>Given a string which may contain variable references to properties, 
    42      * substitute all variable identifiers with the value of the property they 
    43      * refer to.</p> 
    44      * 
    45      * @return A string which has had all variables references replaced. 
    46      * @throws ConfigurationError If a reference to an undefined property is 
    47      * located. 
    48      */ 
    49     this.configure = function (value) { 
    50         return value.replace(variable, lookupPropertyReplacement) 
    51     } 
    52  
    53     /** 
    54      * Test the given value for tokens which require replacement. 
    55      * 
    56      * <p>Given a string, test it for strings which would typically be 
    57      * interpolated and return the results of the test.</p> 
    58      * 
    59      * @return {Boolean} true if the value has interpolated properties in it, 
    60      * false otherwise. 
    61      */ 
    62     this.hasProperties = function (value) { 
    63         return variable.test(value) 
    64     } 
    65  
    66     /** 
    67      * Interpolate variables in attribute values. 
    68      * 
    69      * <p>Recursively descend the given element and replace all attribute 
    70      * values with an interpolated version of the given attribute value. 
    71      * Interpolation performs a regular expression based replacement for 
    72      * property names enclosed in <code>${</code> and <code>}</code>.  Modifies 
    73      * the attribute nodes in place.</p> 
    74      * 
    75      * @throws ConfigurationError If a reference to an undefined property is 
    76      * located. 
    77      */ 
    78     this.filter = function (element) { 
    79         if (element.hasChildNodes()) { 
    80             var children = element.childNodes 
    81  
    82             for (var i = 0; i < children.length; i++) { 
    83                 this.filter(children[i]) 
    84             } 
    85         } 
    86  
    87         /* There are lots of IE workarounds here... */ 
    88         var attributes = element.attributes 
    89  
    90         if (attributes != null) { 
    91             for (var i = 0; i < attributes.length; i++) { 
    92                 var attribute = attributes[i] 
    93  
    94                 if (typeof attribute != "undefined" && 
    95                         typeof attribute.value != "undefined") { 
    96                     var value = this.configure(attribute.value) 
    97  
    98                     /* At risk of trying to write a read-only attribute, only 
    99                      * modify attributes which had actual modifications... */ 
    100                     if (value != attribute.value) { 
    101                         attribute.value = value 
    102                     } 
    103                 } 
    104             } 
    105         } 
    106     } 
    107  
    108     this.parseConfig = function (element) { 
    109         this.filter(element) 
    110  
    111         configurator.parseConfig(element) 
    112     } 
    113 } 
    114  
    115 /** 
    116  * @class Context Configuration Error. 
    117  * 
    118  * <p>An extension of {@link Error} thrown when an error occurs in a given 
    119  * configuration.</p> 
    120  * @author <a href="mailto:tag@cpan.org">Scott S. McCoy</a> 
    121  */ 
    122 function ContextConfigurationError (message) { 
    123     this.message = message 
    124 } 
    125 ContextConfigurationError.prototype = new Error() 
    126  
    127 /** 
    128  * @constructor Create an XML Context Configuration. 
    129  * @param context The context to configure. 
    130  * 
    131  * @class An XML configuration for a joice context. 
    132  * 
    133  * <p>Configure a joice context using DOM.  This class wraps a context and 
    134  * initializes it using object specifications constructed from the supplied DOM 
    135  * structure.</p> 
    136  * @author <a href="mailto:tag@cpan.org">Scott S. McCoy</a> 
    137  */ 
    138 function XMLContextConfiguration (context, semaphore) { 
    139     var specs       = {} 
    140     var loaders     = { 
    141         "text/javascript": new LABLoader(semaphore), 
    142         "text/javascript;debug": new JITLoader(semaphore) 
    143     } 
    144  
    145     function hasAttribute (node, attribute) { 
    146         return node.attributes && 
    147         node.attributes[attribute] != null && 
    148         typeof node.attributes[attribute] != "undefined"; 
    149     } 
    150  
    151     /** 
    152      * Vivify an object of the given id. 
    153      * 
    154      * @private 
    155      */ 
    156     function vivifyObject (id) { 
    157         if (id == null) { 
    158             return new ObjectSpecification() 
    159         } 
    160         else if (id in specs) { 
    161             return specs[id] 
    162         } 
    163         else { 
    164             /* If it doesn't exist in our local defs (as in it wasn't defined 
    165              * here or previously used), then try pulling it from the Joice 
    166              * context.  If that fails, assume it's a new definition we haven't 
    167              * parsed yet. 
    168              */ 
    169             var jid  = context.getIdForLabel(id) 
    170             var spec = null 
    171  
    172             if (typeof jid != "undefined") { 
    173                 spec = context.getSpecification(jid) 
    174             } 
    175             else { 
    176                 spec = new ObjectSpecification() 
    177             } 
    178  
    179             specs[id] = spec 
    180             spec.name = id 
    181  
    182             return spec 
    183         } 
    184     } 
    185  
    186     /* TODO This is a bit of a quick hack to add array support.  It really 
    187      * shouldn't use each element as a constructor argument since it creates 
    188      * somewhat garbage generated code that's unnecessary.  But it's not an 
    189      * enormous concern right now. 
    190      */ 
    191     function Context_parseArray (element) { 
    192         /* TODO: If you're going to keep this, it might be better done with 
    193          * XSLT.  Same with the anonymous object syntax. 
    194          * 
    195          * Simply transform <array><value/><value/><value/></array> into 
    196          * 
    197          * <object constructor="Array"> 
    198          *  <argument><value/></argument> 
    199          *  <argument><value/></argument> 
    200          *  <argument><value/></argument> 
    201          * </object> 
    202          */ 
    203         var doc = element.ownerDocument 
    204         var arrayObject = doc.createElement(JOICENS_OBJECT) 
    205  
    206         arrayObject.setAttribute("constructor", "Array") 
    207  
    208         var children = element.childNodes 
    209  
    210         for (var i = 0; i < children.length; i++) { 
    211             var child = children[i] 
    212  
    213             /* If we don't filter out non-namespace values we end up with a 
    214              * bunch of elements that only have text nodes in them.  Another 
    215              * way to do this would be to check the node type. ;-) */ 
    216             if (child.nodeType == ELEMENT_NODE) { 
    217                 var argument = doc.createElement(JOICENS_ARGUMENT) 
    218  
    219                 if (hasAttribute(child, "object")) { 
    220                     argument.setAttribute("object", 
    221                             child.getAttribute("object")) 
    222                 } 
    223                 else { 
    224                     /* Append a deeply cloned node.  You can't attach a node to 
    225                      * a document twice. */ 
    226                     argument.appendChild(child.cloneNode(true)) 
    227                 } 
    228  
    229                 arrayObject.appendChild(argument) 
    230             } 
    231         } 
    232  
    233         return Context_parseObject(arrayObject) 
    234     } 
    235  
    236     function Context_parseProperty (element) { 
    237         var value  = element.getAttribute("value") 
    238         var object = element.getAttribute("object") 
    239  
    240         if (value != undefined) { 
    241             return new PropertySpecification("value", value) 
    242         } 
    243         else if (object != undefined) { 
    244             return new PropertySpecification("object", vivifyObject(object)) 
    245         } 
    246         else if (element.hasChildNodes()) { 
    247             var children = element.childNodes 
    248             var matches  = 0 
    249             var result   = null 
    250              
    251             for (var i = 0; i < children.length; i++) { 
    252                 var child = children[i] 
    253  
    254                 if (child.nodeType == ELEMENT_NODE) switch (child.tagName) { 
    255                     case JOICENS_OBJECT: 
    256                         /* {} is just new Object(), I'm assuming that's true on 
    257                          * all implementations it very well should be.  This 
    258                          * means <object><property name="foo"/></object> would 
    259                          * always create a new empty object. 
    260                          */ 
    261                         child.setAttribute("constructor", "Object") 
    262  
    263                         matches++ 
    264                         result = new PropertySpecification("object",  
    265                                 Context_parseObject(child)) 
    266  
    267                         break 
    268  
    269                     case JOICENS_VALUE: 
    270                         var text = null 
    271  
    272                         if (child.hasChildNodes()) { 
    273                             text = child.firstChild.nodeValue 
    274                         } 
    275  
    276                         matches++ 
    277                         result = new PropertySpecification("value", text) 
    278  
    279                         break 
    280  
    281                     case JOICENS_XML: 
    282                         matches++ 
    283  
    284                         /* Select the first child element node as the root. */ 
    285                         if (child.hasChildNodes()) { 
    286 FIRST_CHILD:                for (var node = child.firstChild; 
    287                                      node != null; 
    288                                      node = node.nextSibling) 
    289                             { 
    290                                 if (node.nodeValue == ELEMENT_NODE) { 
    291                                     child = node 
    292                                     break FIRST_CHILD 
    293                                 } 
    294                             } 
    295                         } 
    296  
    297                         result = new PropertySpecification("value", 
    298                                 child) 
    299  
    300                         break 
    301  
    302                     case JOICENS_ARRAY: 
    303                         matches++ 
    304                         result = new PropertySpecification("object", 
    305                                 Context_parseArray(child)) 
    306                         break 
    307                 } 
    308             } 
    309  
    310             if (matches != 1) { 
    311                 throw new ContextConfigurationError("Properties may only " + 
    312                     "have a single child.  If multiple values are desired, " + 
    313                     "try the <array/> element instead.") 
    314             } 
    315  
    316             return result 
    317         } 
    318         else { 
    319             /* TODO Throw a configuration error... */ 
    320             throw new ContextConfigurationError("Properties must either have" + 
    321                 "a value attribute, an object attribute, or children") 
    322         } 
    323     } 
    324  
    325     function Context_parseObject (element) { 
    326         var id    = element.getAttribute("id") 
    327         var spec  = vivifyObject(id) 
    328  
    329         var children = element.childNodes 
    330  
    331         for (var i = 0; i < children.length; i++) { 
    332             var property = children[i] 
    333  
    334             if (property.tagName == JOICENS_PROPERTY) { 
    335                 var name = property.getAttribute("name") 
    336  
    337                 if (name == null) { 
    338                     throw new ContextConfigurationError("All properties must " + 
    339                             " have a name attribute") 
    340                 } 
    341  
    342                 spec.setProperty(name, Context_parseProperty(property)) 
    343             } 
    344             else if (property.tagName == JOICENS_ARGUMENT) { 
    345                 spec.addArgument(Context_parseProperty(property)) 
    346             } 
    347             else { 
    348                 /* TODO Throw a configuration error... */ 
    349             } 
    350         } 
    351  
    352         spec.init  = element.getAttribute("initialization") 
    353         spec.ctor  = element.getAttribute("constructor") 
    354         spec.scope = element.getAttribute("scope") || "singleton" 
    355  
    356         /* An internal unique identifier for the object.  This is used in 
    357          * scopes, scopes have no reference to the bean name.  This is a big 
    358          * deviation from the spring container, but it prevents bean name 
    359          * generation and thus prevents the possibility of making references to 
    360          * beans with generated names. */ 
    361         spec.name  = id 
    362  
    363         return spec 
    364     } 
    365  
    366     this.parseScript = function Context_parseScript (script) { 
    367         var type = script.getAttribute("type") 
    368         var src  = script.getAttribute("src") 
    369  
    370         /* 
    371            window.alert("[configuration] loading script %s (%s)".sprintf( 
    372            src, type)) 
    373            */ 
    374  
    375         var loader = loaders[type] 
    376  
    377         if (typeof loader == "undefined") { 
    378             throw new ContextConfigurationError( 
    379                     "Unable to load \f type \f: \f".format( 
    380                         src, type, "No appropriate script loader for type")) 
    381         } 
    382  
    383         loader.load(src) 
    384     } 
    385  
    386     /** 
    387      * Parse a configuration. 
    388      * 
    389      * <p>Given a top level joice configuration element, parse the 
    390      * the element as a joice configuration and create a series of {@link 
    391      * ObjectSpecification} objects.  When all {@link ObjectSpecification} 
    392      * elements are created and their interdependancies resolved, register them 
    393      * with the {@link Context}.</p> 
    394      * 
    395      * @param {Element} config The top level configuration element. 
    396      */ 
    397     this.parseConfig = function (config) { 
    398         var possibleObjects = config.childNodes 
    399  
    400         for (var i = 0; i < possibleObjects.length; i++) { 
    401             var possibleObject = possibleObjects[i] 
    402  
    403             if (possibleObject.nodeType == ELEMENT_NODE && 
    404                     possibleObject.tagName == JOICENS_SCRIPT) { 
    405                 Context_parseScript(possibleObject) 
    406             } 
    407  
    408             if (possibleObject.nodeType == ELEMENT_NODE && 
    409                     possibleObject.tagName == JOICENS_OBJECT) { 
    410                 Context_parseObject(possibleObject) 
    411             } 
    412         } 
    413  
    414         for (var key in specs) { 
    415             var ctor = specs[key].ctor 
    416  
    417             if (typeof ctor == "undefined") { 
    418                 throw new ContextConfigurationError( 
    419                     "Unable to find object definition with id \"\f\"".format( 
    420                       key)) 
    421             } 
    422  
    423             context.addSpecification(specs[key]) 
    424         } 
    425     } 
    426 } 
  • xmvc/branches/remove-namespaces/src/main/javascript/xmvc/Configuration.js

    r208 r209  
    11 
    2 function XMLController (configuration) { 
    3     var controller   = new Controller(window.document) 
    4     var configparser = new XMVCConfigParser(controller) 
    5  
    6     configparser.parse(configuration) 
    7 } 
    8  
    9 function XMVCConfigParser (controller) { 
    10     this._buildTransformer = function (transform) { 
    11         var source   = transform.getAttribute("src") 
    12         var type     = transform.getAttribute("type") 
    13  
    14         var transformer = controller.getTransformer(type, source) 
    15     } 
    16  
    17     this._nextTransformNode = function (transform) { 
    18         for (var node = transform.firstChild; 
    19                  node != null; 
    20                  node = node.nextSibling) { 
    21             if (node.tagName == "transform") { 
    22                 return node 
    23             } 
    24         } 
    25  
    26         return null 
    27     } 
    28  
    29     this._buildTransformObserver = function (transform) { 
    30         var mutation = "replace" 
    31  
    32         if (transform.hasAttribute("behavior")) { 
    33             mutation = transform.getAttribute("behavior") 
    34         } 
    35  
    36         var transformers = [] 
    37  
    38         for (var node = transform; 
    39                  node != null; 
    40                  node = this._nextTransformNode(node))  
    41         { 
    42             var transformer = this._buildTransformer(transform) 
    43  
    44             /* Prepend to the list, they need to be processed from lowest 
    45              * descendant to parent */ 
    46             transformers.unshift(transformer) 
    47         } 
    48  
    49         var locator     = this._buildLocator(transform) 
    50         var mutator     = controller.getMutator(mutation) 
    51  
    52         var transformObserver = new TransformObserver( 
    53             transformers, locator, mutator) 
    54  
    55         return transformObserver 
    56     } 
    57  
    58     this._buildAction = function (action, observer) { 
    59         return controller.createAction( 
    60             action.getAttribute("object"), 
    61             action.getAttribute("method"), 
    62             observer) 
    63     } 
    64  
    65     this._buildLocator = function (node) { 
    66         var locateType = null 
    67         var locatePath = null  
    68  
    69         /* TODO: I think this is all out of date... */ 
    70         if (node.getAttribute("id")) { 
    71             locateType = "id" 
    72             locatePath = node.getAttribute("id") 
    73         } 
    74         else if (node.getAttribute("path")) { 
    75             locateType = "xpath" 
    76             locatePath = node.getAttribute("path") 
    77         } 
    78         else if (node.getAttribute("type")) { 
    79             locateType = node.getAttribute("type") 
    80             locatePath = node.getAttribute("expression") 
    81         } 
    82         else { 
    83             throw new Error("Invalid node expression, must have both " + 
    84                     "type and expression attributes") 
    85         } 
    86  
    87         return controller.createLocator(locateType, locatePath) 
    88     } 
    89  
    90     this._buildSelection = function (node) { 
    91         var locator   = this._buildLocator(node) 
    92  
    93         var selection = controller.createSelection(locator) 
    94  
    95         for (var child = node.firstChild; 
    96                  child != null; 
    97                  child = child.nextSibling) 
    98         { 
    99             if (child.tagName == "action") { 
    100                 /* TODO: Support multiple transform observers through the 
    101                  * listable transform interface. */ 
    102                 var transform = this._nextTransformNode(child) 
    103                 var observer  = null 
    104  
    105                 if (transform != null) { 
    106                     observer = this._buildTransformObserver(transform) 
    107                 } 
    108                 else { 
    109                     observer = new NoopObserver() 
    110                 } 
    111  
    112                 selection.addAction( 
    113                         child.getAttribute("type"), 
    114                         this._buildAction(child, observer) 
    115                         ) 
    116             } 
    117         } 
    118  
    119         return selection 
    120     } 
    121  
    122     this._parseNodes = function (element) { 
    123         var selections = [] 
    124  
    125         for (var node = element.firstChild;  
    126              node != null;  
    127              node = node.nextSibling) 
    128         { 
    129             if (node.nodeType == ELEMENT_NODE) { 
    130                 if (node.tagName == "node") { 
    131                     selections.push(this._buildSelection(node)) 
    132                 } 
    133             } 
    134         } 
    135  
    136         return selections 
    137     } 
    138  
    139     /** 
    140      * Parse a document (or fragment) 
    141      * 
    142      * <p>This expects a DOM node which is either the <em>document</em> element 
    143      * of a controller specification, or an element which contains a controller 
    144      * specification.  It configures the controller this XMVCConfigParser 
    145      * wraps.</p> 
    146      */ 
    147     this.parse = function (node) { 
    148         var selections = [] 
    149  
    150         for (var node = node.firstChild; 
    151                  node != null; 
    152                  node = node.nextSibling) 
    153         { 
    154             if (node.nodeType == ELEMENT_NODE) { 
    155                 if (node.tagName == "controller") { 
    156                     selections = this._parseNodes(node) 
    157                 } 
    158             } 
    159         } 
    160  
    161         for (var i = 0; i < selections.length; i++) { 
    162             controller.addSelection(selections[i]) 
    163         } 
    164     } 
    165 } 
  • xmvc/branches/remove-namespaces/src/main/javascript/xmvc/controller.js

    r208 r209  
     1var globalDocument = document 
     2 
     3/** 
     4 * @class Lazily vivified scope which matches lexical document area. 
     5 * 
     6 * <p>Creates a "scope" which can hold name value pairs relative to a given 
     7 * lexical area of a document.  The <em>active</em> lexical area must be 
     8 * selected using the {@link select(element)} method.  Once an area is 
     9 * selected, {@link get(id, factory)} can be called to fetch objects from 
     10 * it.</p> 
     11 * 
     12 * <p>This object implements the Scope API from the Joice Dependency Injection 
     13 * Container.</p> 
     14 * 
     15 * @author <a href="mailto:tag@cpan.org">Scott S. McCoy</a> 
     16 */ 
     17function DocumentScope (document) { 
     18    this.root   = new ControllerScope() 
     19    this.active = null 
     20    this.live   = false 
     21 
     22    document.documentElement.scope = this.root 
     23 
     24    /** 
     25     * Select the scope for the supplied element. 
     26     * 
     27     * <p>Makes the scope for the supplied element the active scope.  If no 
     28     * scope is present, all parents are descended until a scope is located and 
     29     * the scope chain is vivified for the given element.</p> 
     30     * 
     31     * @return true if the element had a scope, false if it had to locate a 
     32     * parent scope. 
     33     * 
     34     * @throws DocumentScopeError if the element is not a child of the document 
     35     * this scope is intended for. 
     36     */ 
     37    this.select = function (element) { 
     38        this.active = this.forNode(element) 
     39 
     40        this.live = this.active != null 
     41 
     42        if (!this.live) { 
     43            this.active = this.findParent(element) 
     44        } 
     45 
     46        return this.live 
     47    } 
     48 
     49    /** 
     50     * Fetch the value of the current object in the scope for the active 
     51     * element. 
     52     * 
     53     * <p>Starting at whichever element was most recently selected with {@link 
     54     * select(element)}, starts descending upward the scope chain looking for a 
     55     * value associated with the given id.  If one is found one is returned, 
     56     * otherwise an object is created by the given factory and placed in the 
     57     * active scope.  In all cases, and object is returned.</p> 
     58     * 
     59     * @param id The id of the object to locate. 
     60     * @param factory The object factory to use to create an object if 
     61     * necessary. 
     62     * 
     63     * @return The intended object. 
     64     */ 
     65    this.get = function (id, factory) { 
     66        var value = this.active.valueOf(id) 
     67 
     68        if (typeof value == "undefined") { 
     69            if (!this.live) { 
     70                this.active = this.vivify(element) 
     71                this.live   = true 
     72            } 
     73             
     74            value = factory.getObject() 
     75 
     76            this.active.set(id, value) 
     77        } 
     78 
     79        return value 
     80    } 
     81 
     82    /** 
     83     * Find the nearest parent scope. 
     84     * 
     85     * <p>Descends all parent nodes until a scope is found, and returns the 
     86     * scope found.</p> 
     87     * 
     88     * @return {DocumentScope} A scope if one is found, otherwise null. 
     89     */ 
     90    this.findParent = function DocumentScope_findParent (element) { 
     91        for (var node = element; node != null; node = node.parentNode) { 
     92            if (node.scope) { 
     93                return scope 
     94            } 
     95        } 
     96 
     97        return null 
     98    } 
     99 
     100    /** 
     101     * Vivify scope for a given element of a document. 
     102     * 
     103     * <p>This method ascends the document from the supplied element until a 
     104     * node with scope associated with it is found.  It then attaches scope to 
     105     * all elements in the ascent of the tree.  If the given element has scope 
     106     * already, no new scopes are created.</p> 
     107     * 
     108     * @return The scope for the supplied element. 
     109     * @throws DocumentScopeError If the supplied element does not belong 
     110     * to the document this document scope is attached to. 
     111     * @throws DocumentScopeError If the supplied element is not actually 
     112     * <b>attached</b> to the document. 
     113     */ 
     114    /* TODO There is some way to optimize this 
     115     *  * Make select find the nearest releative scope (maybe not the current 
     116     *  node) and save the distance from the selected node to the scope. 
     117     *  * Cause set to be aware of whether or not the currently selected scope 
     118     *  is the scope of the current element, and if it isn't add a new scope to 
     119     *  the chain. 
     120     *  * Make the scope nodes themselves dually linked and keep a depth. 
     121     *  Compare the depth to the distance from the currently selected node, and 
     122     *  when a new scope is node is required insert it before the parent if the 
     123     *  current node is a child. 
     124     * 
     125     *    This is half baked because it may cause mis-matches.  There is 
     126     *    something there though.  Lookups would be faster if they didn't 
     127     *    require descending the entire node chain worth of scope. 
     128     * 
     129     *  It might also just be faster to actually make the parent-scope relation 
     130     *  descend the node graph from the current selection looking for a "scope" 
     131     *  node and keep the parent-child relationship out of the actual scope 
     132     *  nodes. 
     133     */ 
     134    this.vivify = function DocumentScope_vivify (element) { 
     135        if (element.ownerDocument != document) { 
     136            throw new DocumentScopeError( 
     137                "Attempt to vifify scope for element {" +  
     138                element.namespaceURI + "}#" + element.localName +  
     139                " failed!  Element is not a part of this scope's document" 
     140                ) 
     141        } 
     142 
     143        var lastNode  = null 
     144        var nodeStack = [] 
     145 
     146        /* Wheel backward up the document until we find a node that has 
     147         * scope... */ 
     148        for (var cursor = element; cursor != null; cursor = cursor.parentNode) 
     149        { 
     150            if (typeof cursor.scope != "undefined") { 
     151                var lastNode = cursor 
     152 
     153                break 
     154            } 
     155 
     156            nodeStack.unshift(cursor) 
     157        } 
     158 
     159        if (lastNode == null) { 
     160            throw new DocumentScopeError( 
     161                "Unable to find a node in the parent tree which has scope " + 
     162                "attached.  Has this element been added to the document?" 
     163                ) 
     164        } 
     165         
     166        var scope = lastNode.scope 
     167 
     168        for (var i = 0; i < nodeStack.length; i++) { 
     169            var cursor = nodeStack[i] 
     170 
     171            /* I *really* do mean this */ 
     172            cursor.scope = scope = new ControllerScope(scope) 
     173        } 
     174 
     175        return scope 
     176    } 
     177 
     178    /** 
     179     * Return the scope for the given node. 
     180     * 
     181     * <p>Given a node which belongs to the document this scope is attached to, 
     182     * returns the scope associated with that node, if any.</p> 
     183     * 
     184     * @return {ControllerScope} The scope associated with this node, may 
     185     * be null. 
     186     * @throws DocumentScopeError If the supplied object is not a node of 
     187     * this document. 
     188     */ 
     189    this.forNode = function DocumentScope_forNode (node) { 
     190        if (element.ownerDocument != document) { 
     191            throw new DocumentScopeError( 
     192                "Attempt to vifify scope for element {" +  
     193                element.namespaceURI + "}#" + element.localName +  
     194                " failed!  Element is not a part of this scope's document" 
     195                ) 
     196        } 
     197 
     198        return node.scope || null 
     199    } 
     200} 
     201 
     202/** 
     203 * @constructor. 
     204 * @class An object to represent a level of scope. 
     205 * 
     206 * <p><b><u>Note</u></b> <i>This is typically not used as a part of the XMVC 
     207 * API.</i></p> 
     208 * 
     209 * <p>This object represents a level of scope in a document fragment being 
     210 * parsed, this scope chain is not stored directly.  References to the scope 
     211 * chain are stored implicitly by event handler attachment, meaning any scope 
     212 * levels will be garbage collected.</p> 
     213 * 
     214 * <p>Each level of scope represented by this object is simple and hierarchial 
     215 * in nature.  A level of scope has access to it's parent, which may have a 
     216 * parent, and so on until the top level (the scope with an undefined parent) 
     217 * is reached.</p> 
     218 * 
     219 * <p>Scope is used constructor handlers, which instantiate objects of a given 
     220 * name.  The scope follows the document structure, so elements contained 
     221 * within the instantiating element can call methods on this object when events 
     222 * of a given type happens.</p> 
     223 * 
     224 * @author <a href="tag@cpan.org">Scott S. McCoy</a> 
     225 */ 
     226function ControllerScope (parent) { 
     227    var table = {} 
     228 
     229    /** 
     230     * Return the scope that owns a given key. 
     231     * <p>This method will search the scope chain and find the scope which owns 
     232     * a given key.  When the scope is located, a reference to it is returned. 
     233     * </p> 
     234     * 
     235     * @return {ControllerScope} owner if found. 
     236     * @return undefined when unavailable. 
     237     */ 
     238    this.contains = function (key) { 
     239        if (table[key] != undefined) { 
     240            return this 
     241        } 
     242 
     243        //return parent != undefined ? parent.contains(key) : undefined 
     244        return parent && parent.contains(key) 
     245    } 
     246 
     247    /** 
     248     * Return the value of a given key. 
     249     * <p>Searches scope chain for the key and returns it's value.</p> 
     250     * @return {Object} the value if located in this or a parent scope. 
     251     * @return undefined if the given key is not found in the chain. 
     252     */ 
     253    this.valueOf = function (key) { 
     254        if (table[key] != undefined) { 
     255            return table[key] 
     256        } 
     257         
     258        return typeof parent != "undefined" ? parent.valueOf(key) : undefined 
     259    } 
     260 
     261    /** 
     262     * Give a key a value within the appropriate level of scope. 
     263     * 
     264     * <p>If the key is defined in this scope, then a value is set.  Otherwise, 
     265     * the parent scope is searched.  If an owning parent scope is found, the 
     266     * value is set there, otherwise the value will again be set in this 
     267     * scope.</p> 
     268     * 
     269     * @see allocate to allocate a key in this scope intentionally. 
     270     */ 
     271    this.set = function (key, value) { 
     272        if (table[key] != undefined) { 
     273            table[key] = value 
     274 
     275            return true 
     276        } 
     277 
     278        if (!this.top()) { 
     279            var scope = parent.contains(key) 
     280 
     281            if (scope != undefined)  
     282                return scope.set(key, value) 
     283        } 
     284 
     285        table[key] = value 
     286        return false 
     287    } 
     288 
     289    /** 
     290     * Allocate a key in this scope directly. 
     291     * 
     292     * <p>Some times, the semantics of {@link set(key, value)} are unfavorable 
     293     * and this method will allow you to ensure, before calling {@link set} 
     294     * a key will actually belong to the scope.  This would be used for actions 
     295     * which instantiate new instances of an object.</p> 
     296     */ 
     297    this.allocate = function (key) { 
     298        /* This cannot be undefined or null.  We use a completely empty object 
     299         * in it's place */ 
     300        table[key] = new Object() 
     301    } 
     302 
     303    /** 
     304     * Return a reference to the parent scope. 
     305     * @return {ControllerScope} parent scope. 
     306     * @return undefined if this is the top level scope. 
     307     */ 
     308    this.parent = function () { return parent } 
     309 
     310    /** 
     311     * Test for the top level of scope. 
     312     * @return true if this is the top level of scope. 
     313     * @return false otherwise. 
     314     */ 
     315    this.top = function () { return parent == undefined } 
     316} 
    1317 
    2318/** 
     
    19335 * @param document The document to control events for, may be null. 
    20336 */ 
    21 var globalDocument = document 
    22  
    23337function Controller (document) { 
    24338    if (typeof document == "undefined") { 
     
    565879    } 
    566880} 
     881 
     882/** 
     883 * Controller for XML Documents. 
     884 */ 
     885function XMLController (configuration) { 
     886    var controller   = new Controller(window.document) 
     887    var configparser = new XMVCConfigParser(controller) 
     888 
     889    configparser.parse(configuration) 
     890} 
     891 
     892/** 
     893 * Configuration parser for XMVC. 
     894 */ 
     895function XMVCConfigParser (controller) { 
     896    this._buildTransformer = function (transform) { 
     897        var source   = transform.getAttribute("src") 
     898        var type     = transform.getAttribute("type") 
     899 
     900        var transformer = controller.getTransformer(type, source) 
     901    } 
     902 
     903    this._nextTransformNode = function (transform) { 
     904        for (var node = transform.firstChild; 
     905                 node != null; 
     906                 node = node.nextSibling) { 
     907            if (node.tagName == "transform") { 
     908                return node 
     909            } 
     910        } 
     911 
     912        return null 
     913    } 
     914 
     915    this._buildTransformObserver = function (transform) { 
     916        var mutation = "replace" 
     917 
     918        if (transform.hasAttribute("behavior")) { 
     919            mutation = transform.getAttribute("behavior") 
     920        } 
     921 
     922        var transformers = [] 
     923 
     924        for (var node = transform; 
     925                 node != null; 
     926                 node = this._nextTransformNode(node))  
     927        { 
     928            var transformer = this._buildTransformer(transform) 
     929 
     930            /* Prepend to the list, they need to be processed from lowest 
     931             * descendant to parent */ 
     932            transformers.unshift(transformer) 
     933        } 
     934 
     935        var locator     = this._buildLocator(transform) 
     936        var mutator     = controller.getMutator(mutation) 
     937 
     938        var transformObserver = new TransformObserver( 
     939            transformers, locator, mutator) 
     940 
     941        return transformObserver 
     942    } 
     943 
     944    this._buildAction = function (action, observer) { 
     945        return controller.createAction( 
     946            action.getAttribute("object"), 
     947            action.getAttribute("method"), 
     948            observer) 
     949    } 
     950 
     951    this._buildLocator = function (node) { 
     952        var locateType = null 
     953        var locatePath = null  
     954 
     955        /* TODO: I think this is all out of date... */ 
     956        if (node.getAttribute("id")) { 
     957            locateType = "id" 
     958            locatePath = node.getAttribute("id") 
     959        } 
     960        else if (node.getAttribute("path")) { 
     961            locateType = "xpath" 
     962            locatePath = node.getAttribute("path") 
     963        } 
     964        else if (node.getAttribute("type")) { 
     965            locateType = node.getAttribute("type") 
     966            locatePath = node.getAttribute("expression") 
     967        } 
     968        else { 
     969            throw new Error("Invalid node expression, must have both " + 
     970                    "type and expression attributes") 
     971        } 
     972 
     973        return controller.createLocator(locateType, locatePath) 
     974    } 
     975 
     976    this._buildSelection = function (node) { 
     977        var locator   = this._buildLocator(node) 
     978 
     979        var selection = controller.createSelection(locator) 
     980 
     981        for (var child = node.firstChild; 
     982                 child != null; 
     983                 child = child.nextSibling) 
     984        { 
     985            if (child.tagName == "action") { 
     986                /* TODO: Support multiple transform observers through the 
     987                 * listable transform interface. */ 
     988                var transform = this._nextTransformNode(child) 
     989                var observer  = null 
     990 
     991                if (transform != null) { 
     992                    observer = this._buildTransformObserver(transform) 
     993                } 
     994                else { 
     995                    observer = new NoopObserver() 
     996                } 
     997 
     998                selection.addAction( 
     999                        child.getAttribute("type"), 
     1000                        this._buildAction(child, observer) 
     1001                        ) 
     1002            } 
     1003        } 
     1004 
     1005        return selection 
     1006    } 
     1007 
     1008    this._parseNodes = function (element) { 
     1009        var selections = [] 
     1010 
     1011        for (var node = element.firstChild;  
     1012             node != null;  
     1013             node = node.nextSibling) 
     1014        { 
     1015            if (node.nodeType == ELEMENT_NODE) { 
     1016                if (node.tagName == "node") { 
     1017                    selections.push(this._buildSelection(node)) 
     1018                } 
     1019            } 
     1020        } 
     1021 
     1022        return selections 
     1023    } 
     1024 
     1025    /** 
     1026     * Parse a document (or fragment) 
     1027     * 
     1028     * <p>This expects a DOM node which is either the <em>document</em> element 
     1029     * of a controller specification, or an element which contains a controller 
     1030     * specification.  It configures the controller this XMVCConfigParser 
     1031     * wraps.</p> 
     1032     */ 
     1033    this.parse = function (node) { 
     1034        var selections = [] 
     1035 
     1036        for (var node = node.firstChild; 
     1037                 node != null; 
     1038                 node = node.nextSibling) 
     1039        { 
     1040            if (node.nodeType == ELEMENT_NODE) { 
     1041                if (node.tagName == "controller") { 
     1042                    selections = this._parseNodes(node) 
     1043                } 
     1044            } 
     1045        } 
     1046 
     1047        for (var i = 0; i < selections.length; i++) { 
     1048            controller.addSelection(selections[i]) 
     1049        } 
     1050    } 
     1051}