"Fossies" - the Fresh Open Source Software Archive

Member "tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee" (8 Apr 2021, 33005 Bytes) of package /linux/www/apache-tapestry-5.7.2-sources.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Coffeescript source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "t5-core-dom.coffee": 5.7.1_vs_5.7.2.

    1 # Licensed under the Apache License, Version 2.0 (the "License");
    2 # you may not use this file except in compliance with the License.
    3 # You may obtain a copy of the License at
    4 #
    5 #     http:#www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS,
    9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   10 # See the License for the specific language governing permissions and
   11 # limitations under the License.
   12 
   13 
   14 # ## t5/core/dom
   15 #
   16 # This is the abstraction layer that allows the majority of components to operate without caring whether the
   17 # underlying infrastructure framework is Prototype, jQuery, or something else.
   18 #
   19 # The abstraction layer has a number of disadvantages:
   20 #
   21 # * It adds a number of layers of wrapper around the infrastructure framework objects
   22 # * It is leaky; some behaviors will vary slightly based on the active infrastructure framework
   23 # * The abstraction is alien to both Prototype and jQuery developers; it mixes some ideas from both
   24 # * It is much less powerful or expressive than either infrastructure framework used directly
   25 #
   26 # It is quite concievable that some components will require direct access to the infrastructure framework, especially
   27 # those that are wrappers around third party libraries or plugins; however many simple components may need no more than
   28 # the abstract layer and gain the valuable benefit of not caring about the infrastructure framework.
   29 
   30 #if prototype
   31 define ["underscore", "./utils", "./events", "jquery"],
   32 (_, utils, events) ->
   33 #elseif jquery
   34 define ["underscore", "./utils", "./events", "jquery"],
   35 (_, utils, events, $) ->
   36 #endif
   37 
   38 #if prototype
   39   # Save a local reference to Prototype.$ ... see notes about some challenges using Prototype, jQuery,
   40   # and RequireJS together, here: https://github.com/jrburke/requirejs/issues/534
   41   $ = window.$
   42 
   43   # Fires a native event; something that Prototype does not normally do.
   44   # Returns true if the event completed normally, false if it was canceled.
   45   fireNativeEvent = (element, eventName) ->
   46     if document.createEventObject
   47       # IE support:
   48       event = document.createEventObject()
   49       return element.fireEvent "on#{eventName}", event
   50 
   51     # Everyone else:
   52     event = document.createEvent "HTMLEvents"
   53     event.initEvent eventName, true, true
   54     element.dispatchEvent event
   55     return not event.defaultPrevented
   56 
   57   # converts a selector to an array of DOM elements
   58   parseSelectorToElements = (selector) ->
   59     if _.isString selector
   60       return $$ selector
   61 
   62     # Array is assumed to be array of DOM elements
   63     if _.isArray selector
   64       return selector
   65 
   66     # Assume its a single DOM element
   67 
   68     [selector]
   69 #endif
   70 
   71   # Converts content (provided to `ElementWrapper.update()` or `append()`) into an appropriate type. This
   72   # primarily exists to validate the value, and to "unpack" an ElementWrapper into a DOM element.
   73   convertContent = (content) ->
   74     if _.isString content
   75       return content
   76 
   77     if _.isElement content
   78       return content
   79 
   80     if content instanceof ElementWrapper
   81 #if jquery
   82       return content.$
   83 #elseif prototype
   84       return content.element
   85 #endif
   86 
   87     throw new Error "Provided value <#{content}> is not valid as DOM element content."
   88 
   89   # Generic view of an DOM event that is passed to a handler function.
   90   #
   91   # Properties:
   92   #
   93   # * nativeEvent - the native Event object, which may provide additional information.
   94   # * memo - the object passed to `ElementWrapper.trigger()`.
   95   # * type - the name of the event that was triggered.
   96   # * char - the character value of the pressed key, if a printable character, as a string.
   97   # * key -The key value of the pressed key. This is the same as the `char` property for printable keys,
   98   #  or a key name for others.
   99   class EventWrapper
  100 
  101 #if jquery
  102     constructor: (event, memo) ->
  103       @nativeEvent = event
  104       @memo = memo
  105 #elseif prototype
  106     constructor: (event) ->
  107       @nativeEvent = event
  108       @memo = event.memo
  109 #endif
  110       # This is to satisfy YUICompressor which doesn't seem to like 'char', even
  111       # though it doesn't appear to be a reserved word.
  112       this[name] = event[name] for name in ["type", "char", "key"]
  113 
  114     # Stops the event which prevents further propagation of the DOM event,
  115     # as well as DOM event bubbling.
  116     stop: ->
  117 #if jquery
  118       @nativeEvent.preventDefault()
  119       @nativeEvent.stopImmediatePropagation()
  120 #elseif prototype
  121       # There's no equivalent to stopImmediatePropagation() unfortunately.
  122       @nativeEvent.stop()
  123 #endif
  124 
  125 #if jquery
  126   # Interface between the dom's event model, and jQuery's.
  127   #
  128   # * jqueryObject - jQuery wrapper around one or more DOM elements
  129   # * eventNames - space-separated list of event names
  130   # * match - selector to match bubbled elements, or null
  131   # * handler - event handler function to invoke; it will be passed an `EventWrapper` instance as the first parameter,
  132   #   and the memo as the second parameter. `this` will be the `ElementWrapper` for the matched element.
  133   #
  134   # Event handlers may return false to stop event propogation; this prevents an event from bubbling up, and
  135   # prevents any browser default behavior from triggering.  This is often easier than accepting the `EventWrapper`
  136   # object as the first parameter and invoking `stop()`.
  137   #
  138   # Returns a function of no parameters that removes any added handlers.
  139 
  140   onevent = (jqueryObject, eventNames, match, handler) ->
  141     throw new Error "No event handler was provided." unless handler?
  142 
  143     wrapped = (jqueryEvent, memo) ->
  144       # Set `this` to be the matched ElementWrapper, rather than the element on which the event is observed
  145       # (which is often further up the hierarchy).
  146       elementWrapper = new ElementWrapper $(jqueryEvent.target)
  147       eventWrapper = new EventWrapper jqueryEvent, memo
  148 
  149       result = handler.call elementWrapper, eventWrapper, memo
  150 
  151       # If an event handler returns exactly false, then stop the event.
  152       if result is false
  153         eventWrapper.stop()
  154 
  155       return
  156 
  157     jqueryObject.on eventNames, match, wrapped
  158 
  159     # Return a function to stop listening
  160     -> jqueryObject.off eventNames, match, wrapped
  161 #elseif prototype
  162   # Interface between the dom's event model, and Prototype's.
  163   #
  164   # * elements - array of DOM elements (or the document object)
  165   # * eventNames - array of event names
  166   # * match - selector to match bubbled elements, or null
  167   # * handler - event handler function to invoke; it will be passed an `EventWrapper` instance as the first parameter,
  168   #   and the memo as the second parameter. `this` will be the `ElementWrapper` for the matched element.
  169   #
  170   # Event handlers may return false to stop event propagation; this prevents an event from bubbling up, and
  171   # prevents any browser default behavior from triggering.  This is often easier than accepting the `EventWrapper`
  172   # object as the first parameter and invoking `stop()`.
  173   #
  174   # Returns a function of no parameters that removes any added handlers.
  175 
  176   onevent = (elements, eventNames, match, handler) ->
  177       throw new Error "No event handler was provided." unless handler?
  178 
  179       wrapped = (prototypeEvent) ->
  180         # Set `this` to be the matched ElementWrapper, rather than the element on which the event is observed
  181         # (which is often further up the hierarchy).
  182         elementWrapper = new ElementWrapper prototypeEvent.findElement()
  183         eventWrapper = new EventWrapper prototypeEvent
  184 
  185         # Because there's no stopImmediatePropogation() as with jQuery, we detect if the
  186         # event was stopped and simply stop calling the handler.
  187         result = if prototypeEvent.stopped
  188                   false
  189                 else handler.call elementWrapper, eventWrapper, eventWrapper.memo
  190 
  191         # If an event handler returns exactly false, then stop the event.
  192         if result is false
  193           prototypeEvent.stop()
  194 
  195         return
  196 
  197       eventHandlers = []
  198 
  199       for element in elements
  200         for eventName in eventNames
  201           eventHandlers.push (Event.on element, eventName, match, wrapped)
  202 
  203       # Return a function to remove the handler(s)
  204       ->
  205         for eventHandler in eventHandlers
  206           eventHandler.stop()
  207 #endif
  208 
  209   # Wraps a DOM element, providing some common behaviors.
  210   # Exposes the DOM element as property `element`.
  211   class ElementWrapper
  212 
  213 #if jquery
  214     # Passed the jQuery object
  215     constructor: (query) ->
  216       @$ = query
  217       @element = query[0]
  218 #elseif prototype
  219     constructor: (@element) ->
  220 #endif
  221 
  222     # Some coders would use some JavaScript cleverness to automate more of the mapping from the ElementWrapper API
  223     # to the jQuery API, but that eliminates a chance to write some very necessary documentation.
  224 
  225     toString: ->
  226       markup = @element.outerHTML
  227 
  228       "ElementWrapper[#{markup.substring 0, (markup.indexOf ">") + 1}]"
  229 
  230     # Hides the wrapped element, setting its display to 'none'.
  231     hide: ->
  232 #if jquery
  233       @$.hide()
  234 #elseif prototype
  235       @element.hide()
  236 #endif
  237 
  238       return this
  239 
  240     # Displays the wrapped element if hidden.
  241     show: ->
  242 #if jquery
  243       @$.show()
  244 #elseif prototype
  245       @element.show()
  246 #endif
  247 
  248       return this
  249 
  250     # Gets or sets a CSS property. jQuery provides a lot of mapping of names to canonical names.
  251     css: (name, value) ->
  252 
  253       if arguments.length is 1
  254 #if jquery
  255         return @$.css name
  256 #elseif prototype
  257         return @element.getStyle name
  258 #endif
  259 
  260 #if jquery
  261       @$.css name, value
  262 #elseif prototype
  263       @element.setStyle name: value
  264 #endif
  265 
  266       return this
  267 
  268     # Returns the offset of the object relative to the document. The returned object has
  269     # keys `top`' and `left`'.
  270     offset: ->
  271 #if jquery
  272       @$.offset()
  273 #elseif prototype
  274       @element.viewportOffset()
  275 #endif
  276 
  277     # Removes the wrapped element from the DOM.  It can later be re-attached.
  278     remove: ->
  279 #if jquery
  280       @$.detach()
  281 #elseif prototype
  282       @element.remove()
  283 #endif
  284 
  285       return this
  286 
  287     # Reads or updates an attribute. With one argument, returns the current value
  288     # of the attribute. With two arguments, updates the attribute's value, and returns
  289     # the previous value. Setting an attribute to null is the same as removing it.
  290     #
  291     # Alternately, the first attribute can be an object in which case all the keys
  292     # and values of the object are applied as attributes, and this `ElementWrapper` is returned.
  293     #
  294     # * name - the attribute to read or update, or an object of keys and values
  295     # * value - (optional) the new value for the attribute
  296     attr: (name, value) ->
  297 
  298       if _.isObject name
  299         for attributeName, value of name
  300           @attr attributeName, value
  301 
  302         return this
  303 
  304 #if jquery
  305       current = @$.attr name
  306       if arguments.length > 1
  307         if value is null
  308           @$.removeAttr name
  309         else
  310           @$.attr name, value
  311       if _.isUndefined current
  312         current = null
  313 #elseif prototype
  314       current = @element.readAttribute name
  315       if arguments.length > 1
  316         # Treat undefined and null the same; Prototype does something slightly odd,
  317         # treating undefined as a special case where the attribute value matches
  318         # the attribute name.
  319         @element.writeAttribute name, if value is undefined then null else value
  320 #endif
  321 
  322       return current
  323 
  324     # Moves the cursor to the field.
  325     focus: ->
  326 #if jquery
  327       @$.focus()
  328 #elseif prototype
  329       @element.focus()
  330 #endif
  331 
  332       return this
  333 
  334     # Returns true if the element has the indicated class name, false otherwise.
  335     hasClass: (name) ->
  336 #if jquery
  337       @$.hasClass name
  338 #elseif prototype
  339       @element.hasClassName name
  340 #endif
  341 
  342     # Removes the class name from the element.
  343     removeClass: (name) ->
  344 #if jquery
  345       @$.removeClass name
  346 #elseif prototype
  347       @element.removeClassName name
  348 #endif
  349 
  350       return this
  351 
  352     # Adds the class name to the element.
  353     addClass: (name) ->
  354 #if jquery
  355       @$.addClass name
  356 #elseif prototype
  357       @element.addClassName name
  358 #endif
  359 
  360       return this
  361 
  362     # Updates this element with new content, replacing any old content. The new content may be HTML text, or a DOM
  363     # element, or an ElementWrapper, or null (to remove the body of the element).
  364     update: (content) ->
  365 #if jquery
  366       @$.empty()
  367 
  368       if content
  369         @$.append (convertContent content)
  370 #elseif prototype
  371       @element.update (content and convertContent content)
  372 #endif
  373 
  374       return this
  375 
  376     # Appends new content (Element, ElementWrapper, or HTML markup string) to the body of the element.
  377     append: (content) ->
  378 #if jquery
  379       @$.append (convertContent content)
  380 #elseif prototype
  381       @element.insert bottom: (convertContent content)
  382 #endif
  383 
  384       return this
  385 
  386     # Prepends new content (Element, ElementWrapper, or HTML markup string) to the body of the element.
  387     prepend: (content) ->
  388 #if jquery
  389       @$.prepend (convertContent content)
  390 #elseif prototype
  391       @element.insert top: (convertContent content)
  392 #endif
  393 
  394       return this
  395 
  396     # Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately before
  397     # this ElementWrapper's element.
  398     insertBefore: (content) ->
  399 #if jquery
  400       @$.before (convertContent content)
  401 #elseif prototype
  402       @element.insert before: (convertContent content)
  403 #endif
  404 
  405       return this
  406 
  407     # Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately after
  408     # this ElementWrapper's element.
  409     insertAfter: (content) ->
  410 #if jquery
  411       @$.after (convertContent content)
  412 #elseif prototype
  413       @element.insert after: (convertContent content)
  414 #endif
  415 
  416       return this
  417 
  418     # Finds the first child element that matches the CSS selector, wrapped as an ElementWrapper.
  419     # Returns null if not found.
  420     findFirst: (selector) ->
  421 #if jquery
  422       match = @$.find selector
  423 
  424       if match.length
  425         # At least one element was matched, just keep the first
  426         new ElementWrapper match.first()
  427 #elseif prototype
  428       match = @element.down selector
  429 
  430       # Prototype returns undefined if not found, we want to return null.
  431       if match
  432         new ElementWrapper match
  433 #endif
  434       else
  435         return null
  436 
  437     # Finds _all_ child elements matching the CSS selector, returning them
  438     # as an array of ElementWrappers.
  439     find: (selector) ->
  440 #if jquery
  441       matches = @$.find selector
  442 
  443       for i in [0...matches.length]
  444         new ElementWrapper matches.eq i
  445 #elseif prototype
  446       matches = @element.select selector
  447 
  448       new ElementWrapper(e) for e in matches
  449 #endif
  450 
  451     # Find the first container element that matches the selector (wrapped as an ElementWrapper),
  452     # or returns null.
  453     findParent: (selector) ->
  454 #if jquery
  455       parents = @$.parents selector
  456 
  457       return null unless parents.length
  458 
  459       new ElementWrapper parents.eq(0)
  460 #elseif prototype
  461       parent = @element.up selector
  462 
  463       return null unless parent
  464 
  465       new ElementWrapper parent
  466 #endif
  467 
  468     # Returns this ElementWrapper if it matches the selector; otherwise, returns the first container element (as an ElementWrapper)
  469     # that matches the selector. Returns null if no container element matches.
  470     closest: (selector) ->
  471 #if jquery
  472       match = @$.closest selector
  473 
  474       switch
  475         when match.length is 0 then return null
  476         when match[0] is @element then return this
  477         else return new ElementWrapper match
  478 #elseif prototype
  479       if @element.match selector
  480         return this
  481 
  482       return @findParent selector
  483 #endif
  484 
  485     # Returns an ElementWrapper for this element's containing element.
  486     # Returns null if this element has no parent (either because this element is the document object, or
  487     # because this element is not yet attached to the DOM).
  488     parent: ->
  489 #if jquery
  490       parent = @$.parent()
  491 
  492       return null unless parent.length
  493 #elseif prototype
  494       parent = @element.parentNode
  495 
  496       return null unless parent
  497 #endif
  498       new ElementWrapper parent
  499 
  500     # Returns an array of all the immediate child elements of this element, as ElementWrappers.
  501     children: ->
  502 #if jquery
  503       children = @$.children()
  504 
  505       for i in [0...children.length]
  506         new ElementWrapper children.eq i
  507 
  508 #elseif prototype
  509       new ElementWrapper(e) for e in @element.childElements()
  510 #endif
  511 
  512     # Returns true if this element is visible, false otherwise. This does not check to see if all containers of the
  513     # element are visible.
  514     visible: ->
  515 #if jquery
  516       @$.css("display") isnt "none"
  517 #elseif prototype
  518       @element.visible()
  519 #endif
  520 
  521     # Returns true if this element is visible, and all parent elements are also visible, up to the document body.
  522     deepVisible: ->
  523       element = this.element
  524       element.offsetWidth > 0 && element.offsetHeight > 0
  525 
  526     # Fires a named event, passing an optional _memo_ object to event handler functions. This must support
  527     # common native events (exact list TBD), as well as custom events (in Prototype, custom events must have
  528     # a prefix that ends with a colon).
  529     #
  530     # * eventName - name of event to trigger on the wrapped Element
  531     # * memo - optional value assocated with the event; available as WrappedeEvent.memo in event handler functions (must
  532     #   be null for native events). The memo, when provided, should be an object; it is an error if it is a string or other
  533     #  non-object type..
  534     #
  535     # Returns true if the event fully executed, or false if the event was canceled.
  536     trigger: (eventName, memo) ->
  537       throw new Error "Attempt to trigger event with null event name" unless eventName?
  538 
  539       unless (_.isNull memo) or (_.isObject memo) or (_.isUndefined memo)
  540         throw new Error "Event memo may be null or an object, but not a simple type."
  541 
  542 #if jquery
  543       jqEvent = $.Event eventName
  544 
  545       @$.trigger jqEvent, memo
  546 
  547       return not jqEvent.isImmediatePropagationStopped()
  548 #elseif prototype
  549       if (eventName.indexOf ':') > 0
  550         # Custom event is supported directly by Prototype:
  551         event = @element.fire eventName, memo
  552         return not event.defaultPrevented
  553 
  554       # Native events take some extra work:
  555       if memo
  556         throw new Error "Memo must be null when triggering a native event"
  557 
  558       # Hacky solution for TAP5-2602 (5.4 LinkSubmit does not work with Prototype JS)
  559       unless Prototype.Browser.WebKit and eventName == 'submit' and @element instanceof HTMLFormElement
  560         fireNativeEvent @element, eventName
  561       else
  562         @element.requestSubmit()
  563               
  564 #endif
  565 
  566     # With no parameters, returns the current value of the element (which must be a form control element, such as `<input>` or
  567     # `<textarea>`). With one parameter, updates the field's value, and returns the previous value. The underlying
  568     # foundation is responsible for mapping this correctly based on the type of control element.
  569     # TODO: Define behavior for multi-named elements, such as `<select>`.
  570     #
  571     # * newValue - (optional) new value for field
  572     value: (newValue) ->
  573 #if jquery
  574       current = @$.val()
  575 
  576       if arguments.length > 0
  577         @$.val newValue
  578 #elseif prototype
  579       current = @element.getValue()
  580 
  581       if arguments.length > 0
  582         @element.setValue newValue
  583 #endif
  584       return current
  585 
  586     # Returns true if element is a checkbox and is checked
  587     checked: ->
  588       @element.checked
  589 
  590     # Stores or retrieves meta-data on the element. With one parameter, the current value for the name
  591     # is returned (or undefined). With two parameters, the meta-data is updated and the previous value returned.
  592     # For Prototype, the meta data is essentially empty (except, perhaps, for some internal keys used to store
  593     # event handling information).  For jQuery, the meta data may be initialized from data- attributes.
  594     #
  595     # * name - name of meta-data value to store or retrieve
  596     # * value - (optional) new value for meta-data
  597     meta: (name, value) ->
  598 #if jquery
  599       current = @$.data name
  600 
  601       if arguments.length > 1
  602         @$.data name, value
  603 #elseif prototype
  604       current = @element.retrieve name
  605 
  606       if arguments.length > 1
  607         @element.store name, value
  608 #endif
  609       return current
  610 
  611     # Adds an event handler for one or more events.
  612     #
  613     # * events - one or more event names, separated by spaces
  614     # * match - optional: CSS expression used as a filter; only events that bubble
  615     #   up to the wrapped element from an originating element that matches the CSS expression
  616     #   will invoke the handler.
  617     # * handler - function invoked; the function is passed an `EventWrapper` object, and the
  618     #   context (`this`) is the `ElementWrapper` for the matched element.
  619     #
  620     # Returns a function of no parameters that removes any added handlers.
  621     on: (events, match, handler) ->
  622       exports.on @element, events, match, handler
  623       return this
  624 
  625     # Returns the text of the element (and its children).
  626     text: ->
  627 #if jquery
  628       @$.text()
  629 #elseif prototype
  630       @element.textContent or @element.innerText
  631 #endif
  632 
  633 #if jquery
  634   # Wrapper around the `jqXHR` object
  635   class RequestWrapper
  636 
  637     constructor: (@jqxhr) ->
  638 
  639     # Abort a running ajax request
  640     abort: -> @jqxhr.abort()
  641 #elseif prototype
  642   # Wrapper around the Prototype `Ajax.Request` object
  643   class RequestWrapper
  644 
  645     constructor: (@req) ->
  646 
  647     # Abort a running ajax request
  648     abort: -> throw "Cannot abort Ajax request when using Prototype."
  649 #endif
  650 
  651 
  652 #if jquery
  653   # Wrapper around the `jqXHR` object
  654   class ResponseWrapper
  655 
  656     constructor: (@jqxhr, data) ->
  657 
  658       @status = @jqxhr.status
  659       @statusText = @jqxhr.statusText
  660       @json = data # Mostly right?  Need a content type check?
  661       @text = @jqxhr.responseText
  662 
  663     # Retrieves a response header by name
  664     header: (name) ->
  665       @jqxhr.getResponseHeader name
  666 #elseif prototype
  667   # Wrapper around the Prototype `Ajax.Response` object
  668   class ResponseWrapper
  669 
  670     constructor: (@res) ->
  671 
  672       @status = @res.status
  673       @statusText = @res.statusText
  674       @json = @res.responseJSON
  675       @text = @res.responseText
  676 
  677     # Retrieves a response header by name
  678     header: (name) ->
  679       @res.getHeader name
  680 #endif
  681 
  682   # Used to track how many active Ajax requests are currently in-process. This is incremented
  683   # when an Ajax request is started, and decremented when an Ajax request completes or fails.
  684   # The body attribute `data-ajax-active` is set to the number of active Ajax requests, whenever the
  685   # count changes. This only applies to Ajax requests that are filtered through the t5/core/dom API;
  686   # other libraries (including RequireJS) which bypass this API are not counted.
  687 
  688   activeAjaxCount = 0
  689 
  690   adjustAjaxCount = (delta) ->
  691     activeAjaxCount += delta
  692 
  693     exports.body.attr "data-ajax-active", activeAjaxCount
  694 
  695   # Performs an asynchronous Ajax request, invoking callbacks when it completes.
  696   #
  697   # This is very low level; most code will want to go through the `t5/core/ajax` module instead,
  698   # which adds better handling of exceptions and failures, and handles Tapestry's partial page
  699   # render reponse keys.
  700   #
  701   # * options.method - "post", "get", etc., default: "post".
  702   # * options.contentType - request content, defaults to "application/x-www-form-urlencoded"
  703   # * options.data - optional, additional key/value pairs (for the default content type)
  704   # * options.success - handler to invoke on success. Passed the ResponseWrapper object.
  705   #   Default does nothing.
  706   # * options.failure - handler to invoke on failure (server responds with a non-2xx code).
  707   #   Passed the response. Default will throw the exception
  708   # * options.exception - handler to invoke when an exception occurs (often means the server is unavailable).
  709   #   Passed the exception. Default will generate an exception message and throw an `Error`.
  710   #   Note: not really supported under jQuery, a hold-over from Prototype.
  711   # Returns the module's exports
  712   ajaxRequest = (url, options = {}) ->
  713 #if jquery
  714     jqxhr = $.ajax
  715       url: url
  716       type: options.method?.toUpperCase() or "POST"
  717       contentType: options.contentType
  718       traditional: true
  719       data: options.data
  720       # jQuery doesn't have the equivalent of Protoype's onException
  721       error: (jqXHR, textStatus, errorThrown) ->
  722         adjustAjaxCount -1
  723 
  724         return if textStatus is "abort"
  725         message = "Request to #{url} failed with status #{textStatus}"
  726         text = jqXHR.statusText
  727         if not _.isEmpty text
  728           message += " -- #{text}"
  729         message += "."
  730 
  731         if options.failure
  732           options.failure (new ResponseWrapper jqXHR), message
  733         else
  734           throw new Error message
  735 
  736         return
  737 
  738       success: (data, textStatus, jqXHR) ->
  739 
  740         adjustAjaxCount -1
  741 
  742         options.success and options.success(new ResponseWrapper jqXHR, data)
  743         return
  744 
  745     adjustAjaxCount +1
  746 
  747     new RequestWrapper jqxhr
  748 #elseif prototype
  749     finalOptions =
  750       method: options.method or "post"
  751       contentType: options.contentType or "application/x-www-form-urlencoded"
  752       parameters: options.data
  753       onException: (ajaxRequest, exception) ->
  754 
  755         adjustAjaxCount -1
  756 
  757         if options.exception
  758           options.exception exception
  759         else
  760           throw exception
  761 
  762         return
  763 
  764       onFailure: (response) ->
  765         adjustAjaxCount -1
  766 
  767         message = "Request to #{url} failed with status #{response.getStatus()}"
  768         text = response.getStatusText()
  769         if not _.isEmpty text
  770           message += " -- #{text}"
  771         message += "."
  772 
  773         if options.failure
  774           options.failure (new ResponseWrapper response), message
  775         else
  776           throw new Error message
  777 
  778         return
  779 
  780       onSuccess: (response) ->
  781 
  782         adjustAjaxCount -1
  783 
  784         # Prototype treats status == 0 as success, even though it may
  785         # indicate that the server didn't respond.
  786         if (not response.getStatus()) or (not response.request.success())
  787           finalOptions.onFailure(new ResponseWrapper response)
  788           return
  789 
  790         # Tapestry 5.3 includes lots more exception catching ... that just got in the way
  791         # of identifying the source of problems.  That's been stripped out.
  792         options.success and options.success(new ResponseWrapper response)
  793         return
  794 
  795     adjustAjaxCount +1
  796 
  797     new RequestWrapper (new Ajax.Request url, finalOptions)
  798 #endif
  799 
  800   scanners = null
  801 
  802   # Sets up a scanner callback; this is used to perfom one-time setup of elements
  803   # that match a particular CSS selector. The callback is passed each element that
  804   # matches the selector. The callback is expected to modify the element so that it does not
  805   # match future selections caused by zone updates, typically by removing the CSS class or data- attribute
  806   # referenced by the selector.
  807   scanner = (selector, callback) ->
  808     # Define a function that scans some root element (the body initially; later an updated Zone)
  809     scan = (root) ->
  810       callback el for el in root.find selector
  811       return
  812 
  813     # Do it once immediately:
  814 
  815     scan exports.body
  816 
  817     # Lazily set up a single event handler for running any added scanners.
  818 
  819     if scanners is null
  820       scanners = []
  821       exports.body.on events.initializeComponents, ->
  822         f this for f in scanners
  823         return
  824 
  825     scanners.push scan
  826 
  827     return
  828 
  829   # The main export is a function that wraps a DOM element as an ElementWrapper; additional functions are attached as
  830   # properties.
  831   #
  832   # * element - a DOM element, or a string id of a DOM element
  833   #
  834   # Returns the ElementWrapper, or null if no element with the id exists
  835   exports = wrapElement = (element) ->
  836     if _.isString element
  837 #if jquery
  838       element = document.getElementById element
  839 #elseif prototype
  840       element = $ element
  841 #endif
  842       return null unless element
  843     else
  844       throw new Error "Attempt to wrap a null DOM element" unless element
  845 
  846 #if jquery
  847     # Assume the object is a DOM element, document or window; something that is compatible with the
  848     # jQuery API (especially with respect to events).
  849     new ElementWrapper ($ element)
  850 #elseif prototype
  851     # Assume the object is a DOM element, document or window; something that is compatible with the
  852     # Prototype API (especially with respect to events).
  853     new ElementWrapper element
  854 #endif
  855 
  856   # Creates a new element, detached from the DOM.
  857   #
  858   # * elementName - (string) name of element to create, if ommitted, then "div"
  859   # * attributes - (object) attributes to apply to the created element (may be omitted)
  860   # * body - (string) content for the new element, may be omitted for no body
  861   createElement = (elementName, attributes, body) ->
  862 
  863     if _.isObject elementName
  864       body = attributes
  865       attributes = elementName
  866       elementName = null
  867 
  868     if _.isString attributes
  869       body = attributes
  870       attributes = null
  871 
  872     element = wrapElement document.createElement (elementName or "div")
  873 
  874     if attributes
  875       element.attr attributes
  876 
  877     if body
  878       element.update body
  879 
  880     return element
  881 
  882   # Returns the value of a given data attribute as an object.
  883   # The "data-" prefix is added automatically.
  884   # element - (object) HTML dom element
  885   # attribute - (string) name of the data attribute without the "data-" prefix.
  886   getDataAttributeAsObject = (element, attribute) ->
  887 
  888 #if jquery
  889     value = $(element).data(attribute)
  890 #elseif prototype
  891     value = $(element).readAttribute('data-' + attribute)
  892     if value isnt null
  893       value = JSON.parse(value)
  894     else
  895       value = {}
  896 #endif
  897 
  898   # Returns the URL of a component event based on its name and an optional element
  899   # or null if the event information is not found. When the element isn't passed
  900   # or it's null, the event data is taken from the <body> element.
  901   #
  902   # * eventName - (string) name of the component event
  903   # * element - (object) HTML DOM element to be used as the beginning of the event data search. Optional.
  904   getEventUrl = (eventName, element) ->
  905 
  906     if not (eventName?)
  907       throw 'dom.getEventUrl: the eventName parameter cannot be null'
  908 
  909     if not _.isString eventName
  910       throw 'dom.getEventUrl: the eventName parameter should be a string'
  911 
  912     eventName = eventName.toLowerCase()
  913 
  914     if element is null
  915       element = document.body
  916     else if element instanceof ElementWrapper
  917       element = element.element;
  918     else if element.jquery?
  919       element = element[0];
  920 
  921 
  922     # Look for event data in itself first, then in the preceding siblings
  923     # if not found
  924     url = null
  925 
  926     while not url? and element.previousElementSibling?
  927       data = getDataAttributeAsObject(element, 'component-events')
  928       url = data?[eventName]?.url
  929       element = element.previousElementSibling
  930 
  931     if not url?
  932 
  933       # Look at parent elements recursively
  934       while not url? and element.parentElement?
  935         data = getDataAttributeAsObject(element, 'component-events')
  936         url = data?[eventName]?.url
  937         element = element.parentElement;
  938     
  939     return url;
  940 
  941   _.extend exports,
  942   
  943     getEventUrl: getEventUrl
  944     
  945     wrap: wrapElement
  946 
  947     create: createElement
  948 
  949     ajaxRequest: ajaxRequest
  950 
  951     # Used to add an event handler to an element (possibly from elements below it in the hierarchy).
  952     #
  953     # * selector - CSS selector used to select elements to attach handler to; alternately,
  954     #   a single DOM element, or an array of DOM elements. The document is considered an element
  955     #   for these purposes.
  956     # * events - one or more event names, separated by spaces
  957     # * match - optional: CSS expression used as a filter; only events that bubble
  958     # * up to a selected element from an originating element that matches the CSS expression
  959     #   will invoke the handler.
  960     # * handler - function invoked; the function is passed an `EventWrapper` object, and the context (`this`)
  961     #   is the `ElementWrapper` for the matched element
  962     #
  963     # Returns a function of no parameters that removes any added handlers.
  964     on: (selector, events, match, handler) ->
  965       unless handler?
  966         handler = match
  967         match = null
  968 
  969 #if jquery
  970       elements = $ selector
  971 #elseif prototype
  972       elements = parseSelectorToElements selector
  973       events = utils.split events
  974 #endif
  975       onevent elements, events, match, handler
  976 
  977     # onDocument() is used to add an event handler to the document object; this is used
  978     # for global (or default) handlers.
  979     # Returns a function of no parameters that removes any added handlers.
  980     onDocument: (events, match, handler) ->
  981       exports.on document, events, match, handler
  982 
  983     # Returns a wrapped version of the document.body element. Because all Tapestry JavaScript occurs
  984     # inside a block at the end of the document, inside the `<body`> element, it is assumed that
  985     # it is always safe to get the body.
  986     body: wrapElement document.body
  987 
  988     scanner: scanner
  989 
  990   return exports