- Break the hard linkage of Tapestry to Prototype and Scriptaculous, by introducing an abstraction layer
- Remove the clumsy
- Make it easier to override behavior associated with client elements
- Make pages load faster
- Integrate Bootstrap
- Make it easier for rich client libraries such as Backbone or AngularJS to operate within a page
- Properly document Tapestry's client support
The Overall Vision
The overall vision for the client-side in Tapestry is encapsulation, at several different levels.
Where possible, all of this behavior is driven by
data- attributes on the elements, combined with top-level event handlers. On the client side, events are used not only to respond directly to user actions (with "click", "mouseOver", "submit", or other event listeners) but also to allow elements to collaborate in various ways. For example, input validation is based on triggering a specific custom event on each form control element, and top-level event handlers can then manage the validation for any number of fields.
Prototype vs. jQuery
For several years, it has been obvious that Tapestry "backed the wrong horse" with respect to Prototype and jQuery. When the first code was being laid down in 2007 or 2008, it wasn't so clear that jQuery with its odd abstractions and unfamiliar approach, would go on to conquer the world. Meanwhile, Prototype was very strongly integrated into Ruby on Rails and had first class documentation and books.
That being said, jQuery is not the be-all and end-all either. Tapestry 5.4 introduces an abstraction layer, that allows many components to write code that doesn't care whether the foundation framework is Prototype or jQuery or something else. If you like jQuery then there's no problem: write your application using just jQuery and you can ignore a lot of the features in the abstraction layer. Your code will likely be just a bit more efficient.
If you are building a reusable component or library, writing to the abstraction layer may be worth the effort; it is entirely possible that someone may write a replacement for the abstraction layer that targets your favorite foundation framework, such as ExtJS, MooTools, or something not even known of today.
Heavy vs. Light
The use of individual event handlers meant that Tapestry applications would use more client-side objects that a bespoke jQuery solution ... because the normal approach in jQuery is to attach a single event handler to the document or body that handles any events that bubble up to the top and match a CSS selector.
This is more of a full lifecycle approach; adding or removing page content (such as with a Zone component) is both cheaper and less error prone using top-level event handlers than per-element event handlers; there's also less of a chance of memory leaks under Internet Explorer.
A specific example of this approach is how client-side validation now works; in the past, there was a complex system of classes and event listeners that were specific to each individual field. Field controllers had to register with Form controllers. Validators had to register with Field controllers.
Under 5.4, there are a number of
data- attributes that can be attached to any DOM element. A form searches for elements with a non-blank value for
data-validation; each such element has a series of custom events triggered on it. The top-level handlers for those events receive notifications for elements throughout the document.
define ["underscore", "./dom", "./events", "./utils", "./messages", "./fields"], (_, dom, events, utils, messages) -> ... dom.onDocument events.field.optional, "[data-optionality=required]", (event, memo) -> if utils.isBlank memo.value memo.error = (@attr "data-required-message") or "REQUIRED" ... dom.onDocument events.field.validate, "[data-validate-min-length]", (event, memo) -> min = parseInt @attr "data-validate-min-length" if memo.translated.length < min memo.error = (@attr "data-min-length-message") or "TOO SHORT" return false
t5/core/events module defines constants for different custom event name, it's also a handy place to hang documentation about those events.
t5/core/dom namespace is the abstraction layer.
onDocument is a handy way to attach a top-level event handler.
Fields that are required have the attribute
data-optionality=required; the event handler is passed a memo object that includes a
value property, the value from the field. This makes it easier to generate an error if the value is blank. Because the exact error message may be customized or localized, it is provided in the element as well, as the
data-required-message attribute. Setting
memo.error to a validation error string causes the field to be decorated with the error message and indicates that the form itself is in error and not ready for submission.
A different event is triggered after the optionality check; The
memo.translated property is the value translated before validation (for a numeric field, it would be translated from a string to a number, for example). Again, the
error property is set, and the
return false ensures that the event will stop bubbling to containing elements or event handlers.
What's very useful in this overall approach is that it no longer matters whether the fields were rendered by Tapestry on the server, or rendered locally (perhaps using Backbone or AngularJS) on the client. As long as they have the correct
data- attributes, then they can participate in Tapestry's overall form validation and submission cycle, and even leverage the default validation decoration behavior.
The Abstraction Layer
The abstraction layer is defined by the
t5/core/dom module. This module currently has two different implementations - one is a wrapper around Prototype, and the other is a wrapper around jQuery.
The resulting abstraction layer is a bit of a hybrid; it mostly looks like jQuery, but events look a bit more like Prototype. It also doesn't have jQuery's concept of operating on a matched set of elements.
The abstraction is both transitional and permanent. It is transitional in that it is about allowing existing sites with a heavy investment in Prototype to continue to operate with Prototype in the mix. It is permanent in that it is desirable for third party library developers to keep an abstraction layer between Tapestry's client-side code and any underlying framework, so that particular applications can provide their own abstraction layer and operate without breaking built-in components.
Most applications should transition to jQuery and feel free to use jQuery directly. It is still best to inject module jquery into your own modules (usually as parameter
If you are writing a third-party application and want to maximize re-use, then use the abstraction layer.
It is often easier to use the abstraction layer to respond correctly to custom Tapestry events.