Bubbling Events, pt. 1
The simple event system in Apotomo is one of its strengths when being compared to traditional controllers.
It helps you building decoupled systems without having to write manual dispatching code.
Triggering events
When starting with Apotomo the first events are usually fired through triggering requests from the browser.
The term triggering request refers to a request to a #url_for_event-computed address. Apotomo processes this request and triggers the event in the appropriate source widget.
In order to notify the outer world about a new tweet, we need to modify the FormWidget.
1class Twitter::FormWidget < Apotomo::Widget 2 responds_to_event :submit 3 4 def display 5 @tweet = Tweet.new 6 render 7 end 8 9 def submit(evt) 10 @tweet = Tweet.new 11 12 if @tweet.update_attributes(evt[:tweet]) 13 trigger :newTweet 14 replace :state => :display 15 else 16 replace :view => :display 17 end 18 end 19 20end
There’s a #trigger statement, wow (line 13)! This is all we need to fire a bubbling :newTweet event.
You can name the event fired with #trigger whatever you feel like. When needed, you can also append payload data to it. Check the API to learn more.
How do events bubble?
Remember, we have something to say to the outer world: “Hey, there’s a brand-new tweet from the form widget!”
How can the panel catch that event and in response update the tweets list it manages? Well, let’s check the way the bubbling event is traveling.
root |-- panel | |-- form (triggers :newTweet)
Since the triggering form widget is a child of panel, the event will pass the panel on its way to root.
That means, we can simply use #responds_to_event in the PanelWidget.
1class Twitter::PanelWidget < Apotomo::Widget 2 responds_to_event :newTweet, :with => :redraw_list 3 4 has_widgets do 5 self << widget("twitter/form", :tweet_form) 6 end 7 8 9 def display 10 @tweets = Tweet.find(:all) 11 render 12 end 13 14 def list(tweets) 15 render :locals => {:tweets => tweets} 16 end 17 18 def redraw_list(evt) 19 replace "##{widget_id} ul", {:state => :list}, Tweet.find(:all) 20 end 21 22end
Two things got added here.
First, we watch out for the :newTweet event and let it invoke the panel’s #redraw_list state (line 2).
Second, I added a #redraw_list method (line 18-20). This one is pretty interesting. The #replace methods passes the updated tweets list to the #list state and the replaces the list in the page using a custom selector.
Using selectors
The rendered panel roughly looks like this:
<div id="twitter">
<ul>
<li data-id='10'>
Listening to ALL.
</li>
Everything inside the <ul> is from the #list state, so why not simply render that state and then replace it?
19 replace "##{widget_id} ul", {:state => :list}, Tweet.find(:all)
An alternative way to replace content
If that does look weird to you, you can go another way. The replace statement could also be written like this:
html = render({:state => :list}, Tweet.find(:all))
replace "##{widget_id} ul", :text => html
Summary
Clicking “Tweet!” after typing in some text really updates the form and the panel list. Awesome! Our bubbling event worked.
- The
:submitevent is processed in the form widget. - It adds a new tweet to the database, triggers an event and then re-renders itself.
- The panel widget gets alerted by the triggered event and re-renders its list.
- Both page updates are returned to the browser.
Bubbling events is a powerful tool when it comes to inter-widget communication. What a ridiculous word.
Anyway, the list redrawing consumes a lot of cycles as it reloads the entire tweets collection. Let’s learn how we can use the event as a message object in the next excursus.

