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();