Re-instate the floating console - add ElementWrapper.prepend(), .fadeIn(), and .fadeOut() - since core/console depends on core/spi, core/spi cannot depend on core/console - refactor the code in core/spi:ajaxRequest() that uses core/console into core/ajax
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/309253ad Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/309253ad Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/309253ad Branch: refs/heads/5.4-js-rewrite Commit: 309253ad4dd2b86ebdc4fa947037b0c4dbac4117 Parents: febfe01 Author: Howard M. Lewis Ship <[email protected]> Authored: Fri Aug 10 11:24:28 2012 -0700 Committer: Howard M. Lewis Ship <[email protected]> Committed: Fri Aug 10 11:26:54 2012 -0700 ---------------------------------------------------------------------- .../coffeescript/META-INF/modules/core/ajax.coffee | 28 +++-- .../META-INF/modules/core/builder.coffee | 50 +++++--- .../META-INF/modules/core/console.coffee | 49 +++++---- .../coffeescript/META-INF/modules/core/spi.coffee | 92 +++++++++++++-- 4 files changed, 159 insertions(+), 60 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/309253ad/tapestry-core/src/main/coffeescript/META-INF/modules/core/ajax.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/ajax.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/ajax.coffee index d503b68..1e35292 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/ajax.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/ajax.coffee @@ -17,14 +17,25 @@ # Exports a single function, that invokes `core/spi:ajaxRequest()` with the provided `url` and a modified version of the # `options`. # -# It wraps (or provides) `onsuccess` and `onfailure` elements, extended to handle a partial page render reponse (for -# success), or properly log a server-side failure, including using `core/exceptionframe` module to display a server-side -# processing exception. +# It wraps (or provides) `onsuccess`, `onexception`, and `onfailure` handlers, extended to handle a partial page render +# response (for success), or properly log a server-side failure or client-side exception, including using +# `core/exceptionframe` module to display a server-side processing exception. define ["core/pageinit", "core/spi", "core/exceptionframe", "core/console", "_"], (pageinit, spi, exceptionframe, console, _) -> - (url, options = {}) -> + (url, options) -> newOptions = _.extend {}, options, - onfailure: (response) -> + + # Logs the exception to the console before passing it to the + # provided exception handler or throwing the exception. + onexception: (exception) -> + console.error "Request to #{url} failed with #{exception}" + + if options.onexception + options.onexception exception + else + throw exception + + onfailure: (response, failureMessage) -> raw = response.getHeader "X-Tapestry-ErrorMessage" unless _.isEmpty raw message = window.unescape raw @@ -37,12 +48,7 @@ define ["core/pageinit", "core/spi", "core/exceptionframe", "core/console", "_"] if isHTML exceptionframe response.responseText else - message = "Request to #{url} failed with status #{response.getStatus()}" - text = response.getStatusText() - if not _.isEmpty text - message += " -- #{text}" - - console.error message + "." + console.error failureMessage options.onfailure and options.onfailure(response) http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/309253ad/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee index 0bf76d8..2037dfc 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee @@ -12,29 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -# core/builder +# ##core/builder # # A system for constructing DOM element nodes for a particular structure in minimal code. The basic syntax is: -# builder(elementDescription, body...) and the result is a DOM element. The element description is -# primarily the name of the element. +# `builder(elementDescription, body...)` and the result is a `core/spi:ElementWrapper` (a wrapper around the constructed +# DOM elements). The element description is primarily the name of the element. # -# It may contain sequences of ".name"; these appropriate CSS syntax to describe a CSS class name for the element. The -# element name may be omitted when CSS class names are specified, in which case "div" is the default. Multiple CSS class -# names are allowed. +# The element description may contain sequences of "._name_"; these appropriate CSS syntax to describe a CSS class name +# for the element. The element name may be omitted when CSS class names are specified, in which case `div` is the +# default element name. Multiple CSS class names are allowed, e.g., `button.btn.btn-primary`. # -# The description may also include a '>' character; this represents the start of a nested element; in this way -# a structure can quickly be specified. +# The description may also include the `>` character; this represents the start of a nested element; in this way +# a structure can quickly be specified. e.g. `builder "label.checkbox > input", type: "checkbox", " Remember me"` +# would construct: +# +# <label class="checkbox> +# <input type="checkbox"> Remember me</input> +# </label> # # The body may consist of: -# Objects: used to specify attributes and event handles of the element -# Strings: literal text -# Array: a nested element definition -# For an Object, each key and value is simply added as an attribute. However, keys that start with "on" are assumed to -# be event handler functions. The special key "on" consists of nested event handlers for the keys. The following are -# equivlent: -# { onclick: -> ... } and -# { on: { click: -> ... }} +# +# * Objects: used to specify attributes and event handlers of the element +# * Strings: literal text +# * Array: a nested element definition +# +# For an Object, each key and value is simply added as an attribute. However, for keys that start with "on", the value +# is assumed to be an event handler function. The special key "on" consists of nested event handlers for the events +# whose name matches the key. The following are equivalent: +# +# { onclick: -> ... } +# +# and +# +# { on: { click: -> ... }} define ["_", "core/spi"], (_, spi) -> + # _internal_: creates a single DOM element and CSS class attribute createElement = (elementDescription) -> # TODO: Support #id for setting the id of an element, maybe others, such as ?name for the name of an input element. # That will require a regex or more sophisticated parsing. @@ -48,6 +60,7 @@ define ["_", "core/spi"], (_, spi) -> return element + # _internal_: adds attributes and event handlers to a DOM element addAttributes = (element, attributes) -> return unless attributes @@ -64,6 +77,7 @@ define ["_", "core/spi"], (_, spi) -> return null + # _internal_: processes the body, adding attributes and nested nodes to the DOM element addAttributesAndBody = (element, body) -> for nested in body unless nested? @@ -80,6 +94,8 @@ define ["_", "core/spi"], (_, spi) -> return null + # _internal_: builds the tree from the element description, handing nested nodes, and split + # descriptions (containing `>`), returning the topmost DOM element buildTree = (elementDescription, body) -> splitx = elementDescription.indexOf ">" currentDescription = @@ -100,7 +116,7 @@ define ["_", "core/spi"], (_, spi) -> return element # The module exports a single function that builds the tree of elements and returns the top element, wrapped as an - # spi.ElementWrapper. + # `core/spi:ElementWrapper`. (elementDescription, body...) -> element = buildTree elementDescription, body http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/309253ad/tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee index 273b73d..32b0c7b 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/console.coffee @@ -12,42 +12,49 @@ # See the License for the specific language governing permissions and # limitations under the License. -define -> +# ##core/console +# +# A wrapper around the native console, when it exists. +define ["core/spi", "core/builder", "_"], (spi, builder, _) -> nativeConsole = {} floatingConsole = null + FADE_DURATION = 0.25 + # module exports are mutable; someone else could # require this module to change the default DURATION exports = + # Default duration for floating console is 10 seconds. DURATION: 10 - # seconds try + # FireFox will throw an exception if you even access the console object and it does + # not exist. Wow! nativeConsole = console catch e + # _internal_: displays the message inside the floating console, creating the floating + # console as needed. display = (className, message) -> + unless floatingConsole + floatingConsole = builder ".t-console" + spi.body().prepend floatingConsole + + div = builder ".t-console-entry.#{className}", message + + floatingConsole.append div.hide().fadeIn FADE_DURATION + + removed = false + + runFadeout = -> + div.fadeOut FADE_DURATION, -> + div.remove() unless removed - # Disable the floating console temporarily, since we have to resolve the tangle of dependencies - # related to loading vs. the core stack, etc. + window.setTimeout runFadeout, exports.DURATION * 1000 - # unless floatingConsole - # floatingConsole = new Element "div", class: "t-console" - # $(document.body).insert top: floatingConsole - # - # div = new Element "div", class: "t-console-entry #{className}" - # div.update(_.escape(message)).hide() - # floatingConsole.insert top:div - # - # new Effect.Appear div, duration: .25 - # - # fade = new Effect.Fade div, - # delay: exports.DURATION - # afterFinish: -> div.remove() # was T5.dom.remove(div) - # - # div.observe "click", -> - # fade.cancel() - # div.remove() # was T5.dom.remove(div) + div.on "click", -> + div.remove() + removed = true level = (className, consolefn) -> (message) -> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/309253ad/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee index 64e8452..5f9dfdc 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee @@ -18,9 +18,7 @@ # This is the core of the abstraction layer that allows the majority of components to operate without caring whether the # underlying infrastructure framework is Prototype, jQuery, or something else. This is the standard SPI, which wraps # Prototype ... but does it in a way that makes it relatively easy to swap in jQuery instead. -# -# TODO: Define a dependency on "prototype" when that's exposed as a stub module. -define ["_", "core/console"], (_, console) -> +define ["_", "prototype"], (_) -> # _internal_: splits the string into words separated by whitespace split = (str) -> @@ -40,7 +38,36 @@ define ["_", "core/console"], (_, console) -> throw new Error "Provided value <#{content}> is not valid as DOM element content." - # Generic view of an DOM event that is passed to a handler function. + # _internal_: Currently don't want to rely on Scriptaculous, since our needs are pretty minor. + animate = (element, styleName, initial, final, duration, callbacks) -> + styles = {} + range = final - initial + initialTime = Date.now() + first = true + animator = -> + elapsed = Date.now() - initialTime + if elapsed >= duration + styles[styleName] = final + element.setStyle styles + window.clearInterval timeoutID + callbacks.oncomplete and callbacks.oncomplete() + + # TODO: Add an easein/easeout function + + newValue = initial + range * (elapsed / duration) + + element.setStyle styles + + if first + callbacks.onstart and callbacks.onstart() + first = false + + timeoutID = window.setInterval animator + + styles[styleName] = initial + element.setStyle styles + + # Generic view of an DOM event that is passed to a handler function. # # Properties: # @@ -52,7 +79,7 @@ define ["_", "core/console"], (_, console) -> # or a key name for others. class EventWrapper - constructor: (event) -> + constructor: (event) -> @nativeEvent = event @memo = event.memo @type = event.type @@ -118,7 +145,7 @@ define ["_", "core/console"], (_, console) -> # Exposes the original element as property `element`. class ElementWrapper - constructor: (element) -> + constructor: (element) -> @element = $(element) # Hides the wrapped element, setting its display to 'none'. @@ -182,13 +209,48 @@ define ["_", "core/console"], (_, console) -> @element.update (convertContent content) this - # Appends new content (Element or HTML markup string) to the element. + # Appends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. # # Returns this ElementWrapper. append: (content) -> @element.insert bottom: (convertContent content) this + # Prepends new content (Element, ElementWrapper, or HTML markup string) to the body of the element. + # + # Returns this ElementWrapper + prepend: (content) -> + @element.insert top: (convertContent content) + this + + # Runs an animation to fade-in the element over the specified duration. The element may be hidden (via `hide()`) + # initially, and will be made visible (with initial opacity 0, which will increase over time) when the animation + # starts. + # + # * duration - animation duration time, in seconds + # * callback - function invoked after the animation is complete + # + # Returns this ElementWrapper + fadeIn: (duration, callback) -> + animate @element, "opacity", 0, 1, duration * 1000, + onstart: => @element.show() + oncomplete: callback + + this + + # Runs an animation to fade out an element over the specified duration. The element should already + # be visible and fully opaque. + # + # * duration - animation duration time, in seconds + # * callback - function invoked after the animation is complete + # + # Returns this ElementWrapper + fadeOut: (duration, callback) -> + animate @element, "opacity", 1, 0, duration * 1000, + oncomplete: callback + + this + # Finds the first child element that matches the CSS selector. # # Returns the ElementWrapper for the child element, or null if not found. @@ -255,6 +317,10 @@ define ["_", "core/console"], (_, console) -> bodyWrapper = null # Performs an asynchronous Ajax request, invoking callbacks when it completes. + # + # This is very low level; most code will want to go through the `core/ajax` module instead, + # which adds better handling of exceptions and failures, and handles Tapestry's + # # * options.method - "post", "get", etc., default: "post". # Adds a "_method" parameter and uses "post" to handle "delete", etc. # * options.contentType - default "context "application/x-www-form-urlencoded" @@ -262,9 +328,10 @@ define ["_", "core/console"], (_, console) -> # * options.onsuccess - handler to invoke on success. Passed the XMLHttpRequest transport object. # Default does nothing. # * options.onfailure - handler to invoke on failure (server responds with a non-2xx code). - # Passed the response. Default will log and error to the console. + # Passed the response. Default will throw the exception # * options.onexception - handler to invoke when an exception occurs (often means the server is unavailable). - # Passed the exception. Default will log an error to the cnsole and throw the exception. + # Passed the exception. Default will generate an exception message and throw an `Error`. + # # TODO: Clarify what the response object looks like and/or wrap the Prototype Ajax.Response object. # TODO: Define what the return value is, or return exports ajaxRequest = (url, options = {}) -> @@ -276,7 +343,6 @@ define ["_", "core/console"], (_, console) -> if options.onexception options.onexception exception else - console.error "Request to #{url} failed with #{exception}" throw exception onFailure: (response) -> @@ -287,8 +353,12 @@ define ["_", "core/console"], (_, console) -> text = response.getStatusText() if not _.isEmpty text message += " -- #{text}" + message += "." - console.error message + "." + if options.onfailure + options.onfailure response, message + else + throw new Error(message) onSuccess: (response) ->
