Bubbling Events, pt. 1

In this Chapter events, trigger, responds_to_event, selectors
Github code simple-events
Versions 1.1
Next chapter

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.

app/widgets/twitter/form_widget.rb
 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.

app/widgets/twitter/panel_widget.rb
 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?

app/widgets/twitter/panel_widget.rb (snippet)
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.

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.


blog comments powered by Disqus