Composed widgets 2.3 3.0

Being real web components, Apotomo widgets can be nested. This allows building more complex widget trees where widgets are put into containers.
We need a new feature: Tweets should be markable as “loved” if we like them.
Clicking on the heart icon flags the respective Tweet row as loved and redraws the list item. Let’s make the TwitterWidget a container where each loveable tweet item is a separate child widget.
Making tweets loveable
First add the new field to the tweets table.
script/generate migration add_loved_flag_to_tweet loved:boolean
And execute the migration
rake db:migrate
Now it’s time for another widget, yeah!
Creating a new widget
The generator strikes back – we call it TweetWidget as it displays one item, only.
script/generate widget tweet_widget display --haml
exists app/cells/
create app/cells/tweet_widget
exists test/widgets
create app/cells/tweet_widget.rb
create app/cells/tweet_widget/display.html.haml
create test/widgets/tweet_widget_test.rb
The new class is pretty straight-forward.
Somebody has to pass in the Tweet instance we have to display – that’s usually the container’s job.
Containing some JavaScript capable of improvement, the view is still simple. I never said I’m good at JavaScript.
1%li{:id => widget_id, 'data-id' => @tweet.id, 'data-event-url' => url_for_event(:heart)} 2 = @tweet.text 3 4 - if @tweet.loved? 5 = image_tag "heart.png" 6 - else 7 = image_tag "heart-faded.png" 8 9:javascript 10 $("##{widget_id} img").click(function() { 11 $.ajax({url: $("##{widget_id}").attr("data-event-url") + "&id=" + $("##{widget_id}").attr("data-id")}) 12 });
Basically, if the heart is clicked, an AJAX request is issued (line 10-11) to the url provided by #url_for_event (line 1). Also, we append the id of the loved Tweet object. I never said I’m good at JavaScript.
Embedding widgets into containers
A container widget is no special thing, it’s just adding children to itself and rendering ’em. The old TwitterWidget will be the container.
So far, only #display_form changed. For each tweet it appends a new TweetWidget instance passing in the :tweet as parameter (line 10).
It is important to understand that you can use the composing methods like << or remove! not in controllers has_widget blocks only, but in widgets, too.
Rendering children
When calling render without the :render_children => false option, children will be rendered automatically.
Nothing really changed here as well – except that we simply access the rendered_children hash which contains the rendered child views already (line 2). Pretty handy.
This looks good. If we click a heart icon… an AJAX request is triggered but brings us back an exception.
Source "tweet-26" non-existent.
Well, we are in a stateless setup, so naturally in the triggered state the container widget forgets all the children we added earlier. The problem: when we trigger an event from a tweet widget (by clicking the heart), Apotomo tries to find this widget instance to start bubbling the event. But it’s already gone as it didn’t survive the request.
Lucky us we already read about that.
Statefulness in a stateless world
The has_widgets block in widgets is our substitution for the lack of statefulness here (hey, you chose to derive from Widget instead of StatefulWidget!) – it is run every time the widget is instantiated.
1class TwitterWidget < Apotomo::Widget 2 responds_to_event :submit, :with => :process_tweet 3 4 after_add do |me, parent| 5 me.root.respond_to_event :tweetDeleted, :with => :redraw, :on => me.name 6 end 7 8 has_widgets do |me| 9 for t in Tweet.find(:all) 10 me << widget(:tweet_widget, "tweet-#{t.id}", :display, :tweet => t) 11 end 12 end 13 14 def display_form 15 render :layout => 'portlet' 16 end
Instead of adding the kids in an explicit state (that was in #display_form before) we put the setup on the class layer (line 8-12).
Clicking the heart now works… nothing happens, though, cause we didn’t setup an event handler.
Catching the heart beat
The click should be processed in the TweetWidget which fired it as well.

We already know that shit. First create an observer (line 1). In #toggle we, well, toggle the loved state of the desired tweet and replace the content with a re-rendered display view (line 9-14).
And voilà – clicking the heart icon updates the tweet.

