Create widget classes that encapsulate their own Ajax needs (i.e. autonomous
controls that handle their own html refreshing), and when one redraws
itself, re-attaches its own behaviors as needed. Each widget can be viewed
as its own mini-page.

The pattern on a high level is as follows:

Page level (can be thought of as the main application level in a standard
desktop MVC app)
---contains N controls (or widgets as we like to call them nowadays)

Widget level
---each has it's own Ajax functionality, usually derived from a base class
---can contain N children widgets (i.e. refreshing the outermost widget
causes its children to be redrawn via recursive piggybacking - piggybacking
can be implemented so only 1 request is required for N children rather than
N requests being queued up)
---Widget life-cycle:

   1. Initial request - server renders html out to client, widget's
   container element inserts html
      - Any children included in the widget's definition are also
      rendered recursively
      - After html is rendered on client, a custom event is fired
      (onInit for example), recursively for each widget and its children
      - Each widget class has an internal init event handler which
      applies behaviors to its DOM elements as required (sets up
draggables, click
      handlers, etc..)
      2. Subsequent requests
      - a user interaction or other event triggers a widget to need to
      make a server request to update its HTML
         - A dispose method handles removing all behaviors,
         unregistering draggables (etc..), and cleaning up it's
resources before the
         server request is made. This method is also responsible for
recursively
         calling dispose on all the children widgets, as nested
children will also be
         redrawn via piggybacking.
         - If the widget in question is a child widget, it makes
         its own request and updates its own html, re-attaches
behaviors after render
         just as in step one, with no need for its parent or siblings to redraw
         themselves (autonomous controls)
         - Once the cleanup is done, a new request is made and the
         steps in #1 above are repeated
      3. Garbage collection
      - it is very helpful in larger scale applications to have a
      global garbage collector queue up requests for resource
de-allocation in an
      asynchronous manner to aid overall application performance

Event Bus
---An application wide event (pub/sub model) mechanism is usually a
requirement to allow widget-to-widget communication and for the app-level to
dispatch global events which the widgets can respond to
---For a very easy to use custom event publisher, see my EventPublisher
class as posted earlier in the year (file attached as .txt, and link to post
on this list explaining usage:
http://threebit.net/mail-archive/rails-spinoffs/msg00538.html) -- the code
in this attached file is slightly updated from that post, use the file, not
the code at that link.


On 12/16/06, Michael Schuerig <[EMAIL PROTECTED]> wrote:
>
>
>
> Ajax request may well replace parts of a page to which observers had
> been attached previously. The original observers may have been attached
> completely explicitly or more implicitly through behavio(u)r-like
> constructs. It is rather unlikely that knowledge about all such
> observers is localized in one place in the application code.
>
> Given these constraints, has someone devised an elegant way to re-attach
> equivalent observers after an update?
>
> Michael
>
> --
> Michael Schuerig
> mailto:[EMAIL PROTECTED]
> http://www.schuerig.de/michael/
>
> >
>


-- 
Ryan Gahl
Application Development Consultant
Athena Group, Inc.
Inquire: 1-920-955-1457
Blog: http://www.someElement.com


--~--~---------~--~----~------------~-------~--~----~
 You received this message because you are subscribed to the Google Groups 
"Ruby on Rails: Spinoffs" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-spinoffs?hl=en
-~----------~----~----~----~------~----~------~--~---
/**
 * The EventPublisher class allows objects to fire events (and other objects to
 * subscribe handlers to those events). The events can be fired either 
 * synchronously or asynchronously (depending on how the handlers register 
themselves),
 * and may pass optional arguments to the handlers.
 */ 
EventPublisher = Class.create();
EventPublisher.prototype = 
{       
        /**
         * @constructor
         */
        initialize: function() 
        {
        },
        
        /**
         * Attaches a {handler} function to the publisher's {eventName} event 
for execution upon the event firing
         * @param {String} eventName
         * @param {Function} handler
         * @param {Boolean} asynchFlag [optional] Defaults to false if omitted. 
Indicates whether to execute {handler} asynchronously (true) or not (false).
         */ 
        attachEventHandler: function(eventName, handler) 
        {
                // using an event cache array to track all handlers for proper 
cleanup
                if (this.allEvents == null)
                        this.allEvents = new Array();
                // loop through the event cache to prevent adding duplicates
                var len = this.allEvents.length;
                var foundEvent = false;
                for (var i = 0; i < len; i++) 
                {
                        if (this.allEvents[i] == eventName) 
                        {
                                foundEvent = true;
                                break;
                        }
                }
                if (!foundEvent)
                        this.allEvents.push(eventName);
                        
                eventName = eventName + "_evt"; // appending _evt to event name 
to avoid collisions
                if (this[eventName] == null)
                        this[eventName] = new Array();
                        
                //create a custom object containing the handler method and the 
asynch flag
                var asynchVar = arguments.length > 2 ? arguments[2] : false;
                var handlerObj = 
                {
                        method: handler,
                        asynch: asynchVar
                };
                
                this[eventName].push(handlerObj);
        },
        
        /**
         * Removes a single handler from a specific event
         * @param {String} eventName The event name to clear the handler from
         * @param {Function} handler A reference to the handler function to 
un-register from the event
         */ 
        removeEventHandler: function(eventName, handler) 
        {
                eventName = eventName + "_evt"; // appending _evt to event name 
to avoid collisions
                if (this[eventName] != null)
                        this[eventName] = this[eventName].reject(function(obj) 
{ return obj.method == handler; });
        },
        
        /**
         * Removes all handlers from a single event
         * @param {String} eventName The event name to clear handlers from
         */ 
        clearEventHandlers: function(eventName) 
        {
                eventName = eventName + "_evt"; // appending _evt to event name 
to avoid collisions
                this[eventName] = null;
        },
        
        /**
         * Removes all handlers from ALL events
         */ 
        clearAllEventHandlers: function() 
        {
                if (this.allEvents) 
                {
                        var len = this.allEvents.length;
                        for (var i = 0; i < len; i++) 
                        {
                                this.clearEventHandlers(this.allEvents[i]);
                        }
                }
        },    
        
        /**
         * Fires the event {eventName}, resulting in all registered handlers to 
be executed.
         * @param {String} eventName The name of the event to fire
         * @params {Object} args [optional] Any object, will be passed into the 
handler function as the only argument
         */
        fireEvent: function(eventName) 
        {
                var evtName = eventName + "_evt"; // appending _evt to event 
name to avoid collisions
                if (this[evtName] != null) 
                {
                        var len = this[evtName].length; //optimization
                                                        
                        for (var i = 0; i < len; i++)
                        {
                                try
                                {
                                        if (arguments.length > 1)
                                        {
                                                if (this[evtName][i].asynch)
                                                {
                                                        var eventArgs = 
arguments[1];
                                                        var method = 
this[evtName][i].method.bind(this);
                                                        setTimeout(function() { 
method(eventArgs) }.bind(this), 10);
                                                }
                                                else
                                                        
this[evtName][i].method(arguments[1]);                        
                                        } else 
                                        {
                                                if (this[evtName][i].asynch)
                                                {
                                                        var eventHandler = 
this[evtName][i].method;
                                                        
setTimeout(eventHandler, 1);
                                                }
                                                else
                                                        if (this && 
this[evtName] && this[evtName][i] && this[evtName][i].method)
                                                                
this[evtName][i].method();
                                        }
                                }
                                catch (e) 
                                {
                                        if (this.id) 
                                        {
                                                alert("error: error in " + 
this.id + ".fireevent():\n\nevent name: " + eventName + "\n\nerror message: " + 
e.message);
                                        } 
                                        else 
                                        {
                                                alert("error: error in [unknown 
object].fireevent():\n\nevent name: " + eventName + "\n\nerror message: " + 
e.message);
                                        }
                                }
                        }
                }
        }
};

/**
 * Global event dispatcher object for wide spread broadcasting and generic 
subscriptions.
 * This facilitates greater de-coupling where publishers and subscribers need 
not know about each other.
 */
var EventDispatcher = new WW.Event.EventPublisher();

Reply via email to