Changeset 209
- Timestamp:
- 07/05/10 08:05:34 (2 months ago)
- Location:
- xmvc/branches/remove-namespaces
- Files:
-
- 1 added
- 2 removed
- 4 modified
- 2 moved
-
example.html (modified) (1 diff)
-
example.xml (modified) (1 diff)
-
src/main/javascript/joice/Context.js (deleted)
-
src/main/javascript/joice/ContextConfiguration.js (modified) (1 diff)
-
src/main/javascript/joice/bootstrap.js (moved) (moved from xmvc/branches/remove-namespaces/src/main/javascript/joice/Bootstrap.js)
-
src/main/javascript/joice/joice.js (added)
-
src/main/javascript/xmvc/Configuration.js (modified) (1 diff)
-
src/main/javascript/xmvc/ControllerScope.js (deleted)
-
src/main/javascript/xmvc/controller.js (moved) (moved from xmvc/branches/remove-namespaces/src/main/javascript/xmvc/Transformer.js) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
xmvc/branches/remove-namespaces/example.html
r208 r209 36 36 <script type="text/javascript" src="src/main/javascript/util/Semaphore.js"></script> 37 37 <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> 43 40 44 41 <link rel="context" type="text/xml" href="example.xml"/> -
xmvc/branches/remove-namespaces/example.xml
r205 r209 4 4 <!-- text/javascript;debug runs the IE-incompatible jit loader which keeps 5 5 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"/> 9 7 10 8 <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 11 1 /* NOTE 20091222T12:52:57 Always use tagName, not localName. Even though 12 2 * tagName is funky and inappropriate, localName doesn't work *at all* in IE. 13 3 */ 14 4 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 vim27 * from matching it. */28 var variable = /\$\{([^\}]*)\}/g29 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 they43 * 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 is47 * 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 be57 * 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 attribute70 * values with an interpolated version of the given attribute value.71 * Interpolation performs a regular expression based replacement for72 * property names enclosed in <code>${</code> and <code>}</code>. Modifies73 * the attribute nodes in place.</p>74 *75 * @throws ConfigurationError If a reference to an undefined property is76 * located.77 */78 this.filter = function (element) {79 if (element.hasChildNodes()) {80 var children = element.childNodes81 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.attributes89 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, only99 * modify attributes which had actual modifications... */100 if (value != attribute.value) {101 attribute.value = value102 }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 given119 * configuration.</p>120 * @author <a href="mailto:tag@cpan.org">Scott S. McCoy</a>121 */122 function ContextConfigurationError (message) {123 this.message = message124 }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 and134 * initializes it using object specifications constructed from the supplied DOM135 * 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 * @private155 */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 defined165 * here or previously used), then try pulling it from the Joice166 * context. If that fails, assume it's a new definition we haven't167 * parsed yet.168 */169 var jid = context.getIdForLabel(id)170 var spec = null171 172 if (typeof jid != "undefined") {173 spec = context.getSpecification(jid)174 }175 else {176 spec = new ObjectSpecification()177 }178 179 specs[id] = spec180 spec.name = id181 182 return spec183 }184 }185 186 /* TODO This is a bit of a quick hack to add array support. It really187 * shouldn't use each element as a constructor argument since it creates188 * somewhat garbage generated code that's unnecessary. But it's not an189 * enormous concern right now.190 */191 function Context_parseArray (element) {192 /* TODO: If you're going to keep this, it might be better done with193 * XSLT. Same with the anonymous object syntax.194 *195 * Simply transform <array><value/><value/><value/></array> into196 *197 * <object constructor="Array">198 * <argument><value/></argument>199 * <argument><value/></argument>200 * <argument><value/></argument>201 * </object>202 */203 var doc = element.ownerDocument204 var arrayObject = doc.createElement(JOICENS_OBJECT)205 206 arrayObject.setAttribute("constructor", "Array")207 208 var children = element.childNodes209 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 a214 * bunch of elements that only have text nodes in them. Another215 * 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 to225 * 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.childNodes248 var matches = 0249 var result = null250 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 on257 * all implementations it very well should be. This258 * means <object><property name="foo"/></object> would259 * 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 break268 269 case JOICENS_VALUE:270 var text = null271 272 if (child.hasChildNodes()) {273 text = child.firstChild.nodeValue274 }275 276 matches++277 result = new PropertySpecification("value", text)278 279 break280 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 = node292 break FIRST_CHILD293 }294 }295 }296 297 result = new PropertySpecification("value",298 child)299 300 break301 302 case JOICENS_ARRAY:303 matches++304 result = new PropertySpecification("object",305 Context_parseArray(child))306 break307 }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 result317 }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.childNodes330 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 in357 * scopes, scopes have no reference to the bean name. This is a big358 * deviation from the spring container, but it prevents bean name359 * generation and thus prevents the possibility of making references to360 * beans with generated names. */361 spec.name = id362 363 return spec364 }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 the390 * the element as a joice configuration and create a series of {@link391 * ObjectSpecification} objects. When all {@link ObjectSpecification}392 * elements are created and their interdependancies resolved, register them393 * 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.childNodes399 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].ctor416 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 1 1 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 node23 }24 }25 26 return null27 }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 lowest45 * 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 transformObserver56 }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 = null67 var locatePath = null68 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 the101 * listable transform interface. */102 var transform = this._nextTransformNode(child)103 var observer = null104 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 selection120 }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 selections137 }138 139 /**140 * Parse a document (or fragment)141 *142 * <p>This expects a DOM node which is either the <em>document</em> element143 * of a controller specification, or an element which contains a controller144 * specification. It configures the controller this XMVCConfigParser145 * 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 1 var 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 */ 17 function 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 */ 226 function 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 } 1 317 2 318 /** … … 19 335 * @param document The document to control events for, may be null. 20 336 */ 21 var globalDocument = document22 23 337 function Controller (document) { 24 338 if (typeof document == "undefined") { … … 565 879 } 566 880 } 881 882 /** 883 * Controller for XML Documents. 884 */ 885 function 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 */ 895 function 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 }