PDA

View Full Version : How to implement Events which are not bound to Elements



BorisNikolaevich
23 May 2007, 1:31 PM
There is just one thing that keeps me from being able to wipe out all my references to YAHOO.util.CustomEvent, and that is that I can't figure out from the Ext reference how to create & fire events that are not tied to Elements. Everything I can find on EventObject and EventManager and Observable seems to be focused on "user-object interaction events" (e.g. "click" an object) and not "application-UI interaction."

Let me give a description of what I mean, and then hopefully someone can say, "Boris, you дурак, it's simple to implement that using Ext as ________________, which you would know if you had read the documentation more carefully."

My application uses "message queues" based on YAHOO.util.CustomEvent to keep application components notified of state changes. For example, when a response from the server indicates that the "Current Organization" has changed, the response handler adds a "Current Organization Changed" message (i.e. event) to the application message queue. It does not keep track of which components need to know when the current organization has changed. Instead, any component which needs to perform some action when the current organization changes--maybe change the text in a panel, or enable certain buttons, whatever--subscribes to the application message queue and listens for "Current Organization Changed" messages.

Currently, the MessageQueue has two methods, sendMessage(queueName, message) and subscribe(queueName, messageHandler). A snippet from the sendMessage method:
if (!queueName) return false;
if(!queues[queueName])
{
queues[queueName] = new YAHOO.util.CustomEvent(queueName, this);
}
message.queueName = queueName;
queues[queueName].fire(message);

("this" being the MessageQueue object which exposes the sendMessage method)
... and then, in the subscribe method, since queues[queueName] is a YAHOO.util.CustomEvent object, I can simply pass the subscription request on as
queues[queueName].subscribe(messageHandler)

For the "Current Organization Changed" example above, then, the method handling server responses might detect that the current organization has changed, and call
MessageQueue.sendMessage("Application Events", "Current Organization Changed") and the component which needs to know about the change might add a line to its init() method like
MessageQueue.subscribe("Application Events", currentOrganizationChanged)
Note that all of this code is simplified for the purpose of illustration, and clearly there are some pieces I left out, but I wanted to make sure that the general concept was clear instead of focusing on the actual code. Can anyone tell me how to accomplish this by replacing YAHOO.util.CustomEvent(queueName, this) with Ext event objects? Thanks a million!

tryanDLS
23 May 2007, 1:36 PM
Just thinking off the top of my head, but this might give you some direction. You can derive your object from Observable and add any events you want - they don't have to be UI related. You could look at Store as an example - it defines load and beforeload events that can be fired by code.

BorisNikolaevich
23 May 2007, 1:56 PM
That's a great place to start, thanks for pointing me there, Tim. And just to be clear, it sounds like you're suggesting that the internal queues[queueName] object is the one that would derive from Observable, right? Something like the air code
queues[queueName] = new function {...}

Ext.extend(queues[queueName], Ext.util.Observable);

Of course, this also begs the question, "If I am using YUI as the base library for Ext, does it really matter if I replace CustomEvent?" Mostly I was doing this because Jack mentioned the performance improvement from removing CustomEvent from legacy code... and I am your run-of-the-mill follower.

tryanDLS
23 May 2007, 2:42 PM
You'd probably want to kick it around a little as to whether individual queue elements are derived from Observable, or the queue itself does. In that case a queue might just contain a MixedCollection of it's objects. Kind of depends on whether the queue can delegate the events to the right object, or the objects have to do things themselves. The less events you have to create the better of you'll be performance wise

Going the Observable route means you get to use same syntax as the rest of Ext and don't have to code to the YAHOO API, and if you ever need to switch adapters, it's transparent.

BorisNikolaevich
26 May 2007, 12:40 AM
Thanks again, Tim. I did play with both an Observable MessageQueue manager object and Observable queues. With the Observable MessageQueue, the queue name becomes the event name, and so broadcasting a message means calling this.fireEvent(queueName, message) to notify all subscribers. In this case, the global MessageQueue can be defined like this:

examplenamespace.MessageQueue = function()
{
var _messageQueue = function()
{
this.SendMessage = function(message, queueName)
{
// optional queueName parameter allows easy re-sending
// of a message on a different queue, overriding the
// original queue.
if (queueName)
{
message.queueName = queueName;
}
else
{
queueName = message.queueName;
}
if (!queueName) return false;
var queue = {events: {}};
queue.events[queueName] = true;
this.addEvents(queue);

this.fireEvent(queueName, message);

return true;
}

this.Subscribe = function(subscription, callback, subscriberArgs)
{
var subscriptionInfo = {};

// The subscription can be created by passing an object with
// subscription properties, or can be created with a shortcut
// in the form of "Query Name:Message Name:Message Content"
// (where Message Name and Message Content are both optional)
if (typeof(subscription)=='string')
{
subscription = subscription.split(':');
subscriptionInfo.queueName = subscription[0];
subscriptionInfo.name = subscription[1];
subscriptionInfo.content = subscription[2];
}
else
{
subscriptionInfo = subscription;
}
subscriptionInfo.subscriberArgs = subscriberArgs;

var queueName = subscriptionInfo.queueName;

if (!queueName || !callback) return false;
var queue = {events: {}};
queue.events[queueName] = true;
this.addEvents(queue);

this.on
(
queueName,
function(message, subscriptionInfo)
{

// A subscriber can listen to the whole message queue, or
// to messages on the queue with specific properties.
// Here we look at the subscription properties to see
// whether the message matches the subscription.
var subscriptionMatched = true;
for (var filterItem in subscriptionInfo)
{
if(subscriptionInfo[filterItem] && (filterItem != 'subscriberArgs'))
{
subscriptionMatched = (subscriptionMatched && (subscriptionInfo[filterItem] == message[filterItem]));
if (!subscriptionMatched) break;
}
}
if (subscriptionMatched)
{
callback(message, subscriptionInfo.subscriberArgs);
}
}.createDelegate(this, subscriptionInfo, true)
)
}
}

Ext.extend(_messageQueue, Ext.util.Observable);

return new _messageQueue();
}();On the other hand, if it is not the MessageQueue but the internal queues themselves which are Observable, only the guts of the MessageQueue object need to change:
examplenamespace.MessageQueue = function()
{
var queues = {};

messageQueue = function(queueName)
{
this.name = queueName;
this.events = {"MessageSent": true};
}

Ext.extend(messageQueue, Ext.util.Observable);

return {
SendMessage: function(message, queueName)
{
// optional queueName parameter allows easy re-sending
// of a message on a different queue, overriding the
// original queue.
if (queueName)
{
message.queueName = queueName;
}
else
{
queueName = message.queueName;
}
if (!queueName) return false;
if(!queues[queueName])
{
queues[queueName] = new messageQueue(queueName);
}

queues[queueName].fireEvent("MessageSent", message);

return true;
}
,
Subscribe: function(subscription, callback, subscriberArgs)
{
var subscriptionInfo = {};

// The subscription can be created by passing an object with
// subscription properties, or can be created with a shortcut
// in the form of "Query Name:Message Name:Message Content"
// (where Message Name and Message Content are both optional)
if (typeof(subscription)=='string')
{
subscription = subscription.split(':');
subscriptionInfo.queueName = subscription[0];
subscriptionInfo.name = subscription[1];
subscriptionInfo.content = subscription[2];
}
else
{
subscriptionInfo = subscription;
}
subscriptionInfo.subscriberArgs = subscriberArgs;

var queueName = subscriptionInfo.queueName;

if (!queueName || !callback) return false;

if(!queues[queueName])
{
queues[queueName] = new messageQueue(queueName);
}

queues[queueName].on
(
"MessageSent",
function(message, subscriptionInfo)
{

// A subscriber can listen to the whole message queue, or
// to messages on the queue with specific properties.
// Here we look at the subscription properties to see
// whether the message matches the subscription.
var subscriptionMatched = true;
for (var filterItem in subscriptionInfo)
{
if(subscriptionInfo[filterItem] && (filterItem != 'subscriberArgs'))
{
subscriptionMatched = (subscriptionMatched && (subscriptionInfo[filterItem] == message[filterItem]));
if (!subscriptionMatched) break;
}
}
if (subscriptionMatched)
{
callback(message, subscriptionInfo.subscriberArgs);
}
}.createDelegate(this, subscriptionInfo, true)
)
}
}
}(); I actually went with the second technique, both because I found it easier to follow the concept when trying to look at it as "what if I weren't the guy who wrote this?" and because I couldn't find a way I liked for adding a dynamic event property to pass to addEvents. The statement
| var queue = {events: {}};
| queue.events[queueName] = true;
looks too... um, hacked, which it is.

Looking at the internals of EventManager, I think I could also have made that work for me instead of a MessageQueue; however, updating the MessageQueue meant fewer changes to the rest of the application's code. And since I very much believe in sharing the pieces I can, I wanted to post these two examples in case someone else is using a similar design pattern. To make the code complete, for either version of the MessageQueue I've got a Message class

examplenamespace.MessageQueue.Message = function(queueName, messageName, messageContent, messageSource)
{
this.queueName = queueName;
this.name = messageName;
this.content = messageContent;
this.source = messageSource;
};

... two very simple examples of "subscriptions" which might be defined in some components' init() method

examplenamespace.MessageQueue.Subscribe
(
"Application Events",
function(message)
{
alert("An application event occured")
}
);

examplenamespace.MessageQueue.Subscribe
(
"Application Events:Organization Changed",
function(message, args)
{
alert(message.name + " changed to " + message.content + " by " + args)
},
"Joe Schmoe"
);... and an example of sending a message, which might happen in a data processing routine or even in another UI component:
MessageQueue.SendMessage
(
new MessageQueue.Message("Application Events", "Organization Changed", 1097, this)
)I'd love to hear any feedback from anyone who is using something similar, or who learns something useful from my post. Otherwise, Enjoy!

P.S. I know that there's no Unsubscribe method... so far, there hasn't been a need for one in this project. You may find that you need to write one.

rushedlogic
4 Jul 2007, 1:57 PM
This is just the topic i needed to discuss...

I'm looking to create a message queue so i can produce a UI of loosely coupled components. These eventing topics are pretty standard in a Swing type application and I'd like to apply the same good practices to my apps in Ext.

My key obective is to have a central global event loop that components can attach themselves to and broker all global activity.

BorisNikolaevich - Are my objectives anything like yours with this thread? If so, have you found a neat way to get IFrames to communicate neatly?


I wish i had found this thread before i created one just yesterday (http://extjs.com/forum/showthread.php?t=8704) :)

vmorale4
13 Apr 2008, 6:46 AM
I'm so glad I found this post! :)

Just my 2 cents, but it would be nice if this pattern of element-agnostic message queues was built-in in Ext 2.x core, it helps avoid unnecessary coupling. It is specially useful for large, dynamic apps where the components that are going to be load is not necessary known,or new components are deleted/updated frequently.

hendricd
13 Apr 2008, 3:04 PM
If so, have you found a neat way to get IFrames to communicate neatly?Have a look at ManagedIFrame (sig below), it has a x-frame messaging system (messaging for same-origin domains only) which may be of interest.

It exposes frame messaging via standard Ext Events.

anshubansal2000
15 Jun 2009, 5:05 AM
Hi Guys,
I saw the discussion is around implementing custom events. I am also having hard time in implementing some custom event on GridPanel. I noticed that there is NO OnRowOver and OnRowOut functionality attached to the Grid Panel so I thought to implement on my own but am without luck.

Here is the things I implemented.
I declared a GridPanel just a basic one.

Now I am initializing yahoo's custom events just like this. and attaching it to the grid.


//EVent Declaration:

onRowChange = new YAHOO.util.CustomEvent("onRowChange");
onRowChange.subscribe(testing);

//Grid Attachment.

grid.on('onRowChange',this);

//Testing Function:
function testing()
{
alert('Hi I am in Row Select');
}


But this doesn't seem to be working. Am I doing something wrong? Can we attach any custom event to ext's component like this??

Help and replies are appreciated.

Thanks
Anshu