Peter's Guide

In this Chapter basic widget, generator, #render_widget
Github code introduction
Versions 0.1 (Rails 2.3), 1.0, 1.1
Next chapter

Apotomo is a generic widget framework for Rails. While you write the actual widgets – like a twitter box, tab panels or a full-blown shopping cart – Apotomo provides you with all you need for a component-oriented GUI application.

Here’s a quick overview:

What are we gonna do?

This is a brief tutorial for setting up a small Rails 3 app with Apotomo widgets. It’s meant as a starting point for learning Apotomo, so grab a beer and read on!

The code is available in a git repo, feel free to clone.

During the course we are developing a rich dashboard application for twittering. The first widget looks like this. Pretty straight-forward.

Tweeter

Installation

You've already got Rails? Ok.

$ rails new bar
    create  
    create  app/controllers
    create  app/helpers
    create  app/models
    create  app/views/layouts
    ...

Now, tweak your Gemfile, we need the Apotomo gem:

Gemfile
1source 'http://rubygems.org'
2
3gem 'rails', '~>3.0'
4gem 'apotomo', "~>1.1.0"
5gem 'haml'
6gem 'sqlite3-ruby', :require => 'sqlite3'
7gem 'jquery-rails'

Install the gems with bundler.

$ bundle install

Layout

Next, we need a layout.

app/views/layouts/application.html.haml
1%html
2%head
3  = javascript_include_tag :defaults
4  = stylesheet_link_tag "app"
5
6%body
7  = yield

Notice that we already pull JQuery and Rails UJS support into the page (line 3).

Installing jquery-ujs needs another step.

$ rails generate jquery:install

The Tweet model

Say we’re writing a small widget that let’s you post and displays your recent posts. We need a Tweet model for that.

$ rails generate model tweet text:string
$ rake db:migrate

Controller

Widgets are usually embedded into controllers, so let’s generate a dashboard controller.

$ rails generate controller dashboard index
    exists  app/controllers/
    exists  app/helpers/
    ...

The first Widget!

Writing a tweet widget is as easy as creating a new controller.

$ rails generate apotomo:widget twitter display -e haml
    exists  app/widgets/
    create  app/widgets/twitter
    exists  test/widgets
    create  app/widgets/twitter_widget.rb
    create  app/widgets/twitter/display.html.haml
    create  test/widgets/twitter_widget_test.rb

    

The widget class should look as follows. Notice that widgets reside in app/widgets/.

app/widgets/twitter_widget.rb
 1class TwitterWidget < Apotomo::Widget
 2  responds_to_event :submit, :with => :process_tweet
 3
 4  def display
 5    @tweets = Tweet.find(:all)
 6    render
 7  end
 8  
 9  def process_tweet(evt)
10    Tweet.new(:text => evt[:text]).save
11
12    @tweets = Tweet.find(:all) # this is wet!
13    replace :view => :display
14  end
15end

Feeling knocked down? No problem, let’s go through this step-by-step.

How do I render widgets in the dashboard controller?

Ok, I will start the other way round, with the dashboard controller which uses the twitter widget.

app/controllers/dashboard_controller.rb
 1class DashboardController < ApplicationController
 2
 3  has_widgets do |root|
 4    root << widget(:twitter)
 5  end
 6
 7  def index
 8  end
 9end

Apotomo requires you to “declare” which widgets you’re gonna use in an action. And this is done in the has_widgets block (line 3-5). We simply append our brand-new twitter widget to the root widget.

The root widget caused some headache for new users. Apotomo simply provides a default root widget for your widget tree. Check the API to learn more.

Render it!

Nothing will happen until we actually render the widget in the action view.

app/views/dashboard/index.html.haml
1%h1 My Dashboard
2
3#dashboard
4  .column
5    = render_widget :twitter

This will invoke our twitter widget’s #display method and simply return the markup (or whatever else you do).

#render_widget invokes the default state #display on the widget if no other options are given. Check the API to learn more.

The widget got a view

Let’s peek at the widget’s source, again.

app/widgets/twitter_widget.rb (snippet)
 1class TwitterWidget < Apotomo::Widget
 2  responds_to_event :submit, :with => :process_tweet
 3
 4  def display
 5    @tweets = Tweet.find(:all)
 6    render
 7  end

First, we grab all available tweets (line 5). Next, we call #render which will render #display’s view. This is roughly similar to ordinary controllers.

app/widgets/twitter/display.html.haml
 1- widget_div do
 2  %ul
 3    - for tweet in @tweets
 4      %li
 5        = tweet.text
 6 
 7  %p
 8    What are you up to?
 9   
10  - form_tag url_for_event(:submit), :remote => true do
11    = text_field_tag 'text'
12   
13    = submit_tag "Tweet!"

The entire view is wrapped by #widget_div (line 1) – which is just a helper doing something like

<div id="twitter">
  ...
</div>

#widget_div per default uses the widget’s id for the div. Check the API to learn more.

Oh no, JavaScript!

Most of the view is boring stuff. We list all twitter items (line 3-5) and then create an AJAX form. Notice how we delegate computation of the form’s action url to Apotomo by calling #url_for_event (line 10).

#url_for_event returns an apotomo-generated url for triggering an event in your widget tree. Check the API to learn more.

Triggering an event

Now that you click “Submit”, what happens?

Responding to events

Remember, our widget started like this:

app/widgets/twitter_widget.rb (snippet)
 1class TwitterWidget < Apotomo::Widget
 2  responds_to_event :submit, :with => :process_tweet

That’s where we set our observer, saying “If I see a :submit event, I run my #process_tweet state!”.

The term state refers to a rendering method in a widget and is similar to an action in plain old controllers.

Updating the widget in the browser

What does the #process_tweet method do in order to respond to the event?

app/widgets/twitter_widget.rb (snippet)
 9  def process_tweet(evt)
10    Tweet.new(:text => evt[:text]).save
11
12    @tweets = Tweet.find(:all) # this is wet!
13    replace :view => :display
14  end

Please note how the method receives the event object as argument (line 9) – how cool’s that?

The event may be queried for parameters sent with the event – this is how we retrieve the entered text from the form (line 10).

We then reload the tweets list (line 12) and call #replace to, well, replace the widget on the page (line 13). In other words, we re-render the display view and replace it in the browser.

#replace and #update are little helper methods and wrap the content into JavaScript. They accept the same options as #render plus an optional selector. Check the API to learn more.

You don’t have to use these helpers – they’re here for your convenience. However, you may return any content, JS or whatever using render :text => "...".

Finally, let’s run it!

Now that we figured all the important parts, start the engine! I can hear the theme of Rocky I in the background…

$ rails s

…and browse to http://localhost:3000/dashboard

Happy “tweeting”.

Now, let’s learn how to improve our view architecture by using partials and states.


blog comments powered by Disqus