RichFaces 4.0 Core: AJAX - JavaScript components
nbelaevski Jul 21, 2009 3:42 PMWe have a lot of JavaScript code in RichFaces components, because of their rich client-side functionality. Also we're providing public JavaScript API to let developers control components on the client side; there are #{rich:component()} function and rich:componentControl component that both utilize this feature. So, let's start with the information on how RichFaces JS API call is being done using #{rich:component()} function as an example.
When JS component instance is first created, it is attached to the main DOM element of this component, using "component" property. Imagine
<rich:calendar id="calendar" />. This is rendered as (code adopted for ease of understanding)
<span id="form:calendar"> <script>new Calendar('form:calendar')</script> </span>
JS object Calendar has the following code in constructor:
Calendar = function(mainElementId) { this.calendarElement = document.getElementById(mainElementId); this.calendarElement.component = this; ... Event.observe('mousedown', this.handler.bind(this)); }
On this step we already have constructed instance of calendar initialized and attached to DOM elements. Now we can call some function using JS API:
#{rich:component('calendar')}.today()
and this is rendered as the following JS:
document.getElementById('form:calendar').component.today()Let's call this approach "current" or Curr.
Alternative approach to this one is recreation of server-side components tree on the client using some special API. Let's call this approach "alternative" or Alt.
Pros and cons of both approaches:
Alt:
- Pros:
-- Reflects components hierarchy
-- Doesn't pollute DOM nodes
-- Doesn't create circular references (this MSDN article describes what's wrong with it)
- Cons:
-- Very hard to maintain - children components are created before parent
Cur:
- Pros:
-- Very leightweight and simple to support
- Cons:
-- Have no pros of Alt
In order to get rid of memory leaks, Cur includes a special code that traverses DOM tree for DOM elements being replaced and calls special "destroy" method on components. The same traversal is done for page "unload" event. Another important thing that this method does is clearing JS libraries data store for updated elements.
Data store was developed in order to break circular references and keep JS data separated from DOM objects. Concept behind data store is very simple: it's just a global array or hash where data for DOM objects is stored using API provided by the JS library. When data for some DOM object is stored, it gets unique identifier - key of the cell in data store. Both Prototype.js and jQuery use this:
//jQuery var id = elem[ expando ]; // Compute a unique ID for the element if ( !id ) id = elem[ expando ] = ++uuid; // Only generate the data cache if we're // trying to access or manipulate it if ( name && !jQuery.cache[ id ] ) jQuery.cache[ id ] = {}; // Prevent overriding the named cache with undefined values if ( data !== undefined ) jQuery.cache[ id ][ name ] = data;
//Prototype.js var cache = Event.cache; function getEventID(element) { if (element._prototypeEventID) return element._prototypeEventID[0]; arguments.callee.id = arguments.callee.id || 1; return element._prototypeEventID = [++arguments.callee.id]; } function getCacheForID(id) { return cache[id] = cache[id] || { }; } function getWrappersForEventName(id, eventName) { var c = getCacheForID(id); return c[eventName] = c[eventName] || []; }
What's happening when we update DOM elements that use data store? That's right, data for removed elements is waiting for page reload, when global JS objects are recreated. For one-page RIA applications this means "never". Another facet of the problem is removal of elements that attached event handlers to global objects by AJAX (e.g. D'n'D updated by poll component). So, in RichFaces we've implemented special "memory.js" scripts, that are attaching as AJAX events and window "unload" listener and do the actual clean (Fisheye, lines 65-89) for bundled JS libraries (BTW, that's one of the reasons to use bundled JS libraries instead of external).
Is there something in Mojarra AJAX? Out of the box, no:
var doDelete = function doDelete(element) { var id = element.getAttribute('id'); var target = $(id); var parent = target.parentNode; parent.removeChild(target); };(here "element" method parameter is response XML node).
Possible implementation variants:
1. Use existing in Mojarra "complete" event and clear elements exploiting knowledge about JSF AJAX response structure. Disadvantages - low performance; not compatible with the different kind of extensions provided by 3rd party libraries; risk of response structure modification in the future JSF versions.
2. Track all elements in the global JS array, find the ones that were removed from DOM and do the cleanup after AJAX update completion. Disadvantages - lot of.
3. Request addition of such event to the specification/implementation and utilize it.