Archive for the ‘Your 12 Minutes of Apotomo’ Category

Writing an interactive form (Part 4)

Saturday, April 17th, 2010

I’m really considering shooting screencasts, blogging is tedious. I could talk stupid things and upload. How’d you like that?

app-with-form

Ok, last week we had a working app with a list widget showing the items. Today we’re gonna write a form widget to add new items. The code for this lesson is at tag 0.7.

1First thing to do is generating stubs.

$ script/generate widget item_form display create --haml
      create  app/cells/item_form
      create  app/cells/item_form.rb
      create  app/cells/item_form/display.html.haml
      create  app/cells/item_form/create.html.haml

The app/cells/item_form.rb file looks like this, after some editing.

1
2
3
4
5
6
class ItemForm < Apotomo::StatefulWidget
  def display
    @todo = Todo.new
    render
  end
end

I create a new Todo so I can use it in the view.

2The view needs to be implemented, too!

app/cells/item_form/display.html.haml

1
2
3
4
5
6
7
%h3 Create new item
 
= error_messages_for :todo
 
- form_to_event(:submit) do
  = text_area :todo, :text, :rows => 2
  = submit_tag "Create Todo!"

Easy. The only interesting here is the call to #form_to_event
which basically triggers a :submit event when the form is, nah, submitted (line 5).

3
Let’s append the form widget to our widget tree in app/controllers/dashboard_controller.rb.

1
2
3
4
5
6
7
8
9
10
class DashboardController < ApplicationController
  include Apotomo::ControllerMethods
 
  def index
    use_widgets do |root|
      root << widget(:item_list, 'dashboard_list')
      root << widget(:item_form, 'new_item')
    end
  end
end

I call the widget new_item (line 7).

4Pluggin’ the widget into the controller again happens in app/views/dashboard/index.html.erb (line 10 and 12).

<h2>Dashboard</h2>
 
<div class="slot">
  <%= render_widget 'dashboard_list' %>
</div>
 
<div class="slot">
  <%= render_widget 'new_item' %>
</div>

5In order to process the form, we need to observe that :submit event, so I will extend the widget class a bit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ItemForm < Apotomo::StatefulWidget
  def display
    respond_to_event :submit, :with => :create
 
    @todo = Todo.new
    render
  end
 
  def create
    @todo.update_attributes(param(:todo))
 
    render :view => :display
  end
end

The event handler (line 3) will execute the #create method when the form is submitted.

Nothing unusual happens there, I update the @todo and instruct the widget to render the former view display.html.haml.

6Entering some text into the form and pressing Submit yields the expected result! The new item gets saved.

app-with-form

However, the list doesn’t update so we can not see what we did. We should implement some notification so the list updates when there was a new item created.

That’s what we’re gonna do in the next lesson, the 12 minutes are over!

Making unobstrusive pagination (Part 3)

Friday, April 2nd, 2010

In the last lesson I demonstrated how we could quickly write our own pagination using #link_to_event.

A couple of guys (including mislav himself) told me to make the pagination unobstrusive, so here’s how i did this.

simple_paging

You’re gonna learn how to

  • pack unobstrusive JavaScript in Apotomo widgets
  • use Helpers in widgets
  • configure your widget view with #render

1I didn’t have any clue what unobstrusive JavaScript refers to. It means “separating JavaScript and markup”. After lots of tries, I figured out how to let will_paginate handle the markup and do the JS part myself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%h3 Things
%p
  = page_entries_info @items
 
%ul
  - @items.each do |item|
    %li
      = item.text
 
= will_paginate @items
 
- javascript_tag do
  $(document.body).observe('click', function(e) {
  var el = e.element()
 
  if (el.match('.pagination a')) {
  page = el.href.split("page=").last(); // I don't know how to do it better.
  = "new Ajax.Request('#{url_for_event(:paginate)}'+'&page='+page, { method: 'get' })"
 
  e.stop()
  }})

Instead of modifying will_paginate's output (line 10) I add an observer that triggers the :pagination event (line 13).

I have to extract the page from the element href, that’s a little bit fiddling. Maybe someone can tell me how to do it better (line 17)?

The interesting part here is the call to #url_for_event (line 18). It computes the url to trigger the paginate event, which happens in Ruby!

That works. After clicking a page, the widgets updates itself via AJAX.

2Anyway, I keeps adding observers as the widgets updates and re-injects the JS into the page over and over again. There are multiple solutions to the problem.

  • Fix the JS to add the observer only once
  • Use two different views in your widget, the initial one with, the following without the JS
  • Write the JS to a global variable using #capture that’s evaluated only once when the page is rendered completely

Personally I hate using global variables (that’s what #capture does). I will use it anyway cause it shows a few interesting concepts.

3We need to render the JavaScript to the page footer.

Cells provide a view method #global_capture for sending local content to the global controller view. That’s not nice. Widgets shouldn’t know anything about the global page.

Apotomo widgets are based on cells, so they may use that technique as well.

In the display.html.erb view

...
- global_capture :ujs do
  - javascript_tag do
    $(document.body).observe('click', function(e) {
...

I embed the #javascript_tag into #global_capture that saves the whole block to the global view’s instance variable @ujs, which is a brutal interface violation.

In the app’s views/layouts/application.html.erb I have to render @ujs in order to embed the JavaScript into the page only once. I still hate myself for doing that.

  </body>
  <%= @ujs %>
</html>

That’s all for the markup.

4 The #global_capture method is not automatically available in the widget view, it’s provided by a helper, so let’s introduce it to our view.

class ItemList < Apotomo::StatefulWidget
  helper Cells::Helpers::CaptureHelper
 
  def display
  #...

That looks like in a controller. Note that you can use #helper to pull any helper in your widget view.

5Ok, the paging is working in some unobstrusive way. I’m still not sure if unobstrusive JavaScript is something I like.

What I like is CSS and how it can help us to beautify elements without adding obstrusive CSS.

To add a background to our item list I add a css class to it’s surrounding div.

1
2
3
4
5
6
7
8
9
10
11
class ItemList < Apotomo::StatefulWidget
  helper Cells::Helpers::CaptureHelper
 
  def display
    respond_to_event :paginate, :with => :display
 
    @items = Todo.paginate :page => param(:page), :per_page => 10, :order => 'created_at DESC'
 
    render :html_options => {:class => 'lined'}
  end
end

All I need is adding :html_options to the #render invocation (line 9), this will add the attributes to our list. Obviously I also added some class to the CSS document, but that’s not part of this course. My CSS-skills suck.

6Let’s view our page again.

lined_list

Looks cooooool, doesn’t it?

I’d love to see some JS fix to prevent the usage of #global_capture. Or do you like it? It’s a common technique in Rails, though. Doesn’t mean that this is a good thing.

In the next lesson we’ll add an interactive form to add Todo items which processes and updates like the list widget, via AJAX and superfast.

Go to: Writing forms

What about pagination? (Part 2)

Saturday, March 27th, 2010

The list we wrote yesterday was just a basic example (we finished with tag 0.3). Today we’re gonna paginate the list using the plugin will_paginate.
That serves a couple of purposes:

  • limiting the amount of items will help keeping the UI clean
  • using will_paginate proofs that helpers in widgets are just like helpers in controllers
  • switching to AJAX pagination will touch Apotomo’s simple event handling

1Limiting the item list means limiting the #find operation in our list widget.
app/cells/item_list.rb

class ItemList < Apotomo::StatefulWidget
  def display
    @items = Todo.paginate :page => param(:page), :per_page => 10, :order => 'created_at DESC'
 
    render
  end
end

That’s dead simple, we just exchanged #find with #paginate. Accessing request parameters can happen with params[], however, in widgets it’s a good practice to stick to #param.
The later prevents you from accessing parameters not bound to your widget.

2 Obviously we need to fix our view as well, as pagination only makes sense when we got buttons to switch pages.
app/cells/item_list/display.html.haml

%h3 Things
%p
  = page_entries_info @items
 
%ul
  - @items.each do |item|
    %li
      = item.text
 
= will_paginate @items

simple_paging
When viewing our page, we got a basic paging list, clicking a page button issues a new request.

3That works (tag 0.4), but it’s not the concept behind widgets. Widgets usually want to update via AJAX to save load time and to assure proper encapsulation.

Why should we reload the whole page for updating a single list? That’s overhead!

Keeping that in our collective mind we should add ajaxed buttons.

4Unfortunately will_paginate doesn’t provide a simple way to change the rendering of links (what about some block that’d define the link markup, mislav?) so we’re goin’ that way by foot.

app/cells/item_list/display.html.haml

1
2
3
4
5
6
7
8
9
10
11
%h3 Things
%p
  = page_entries_info @items
 
%ul
  - @items.each do |item|
    %li
      = item.text
 
- (1..@items.total_pages).each do |page|
  = link_to_event "|#{page}| ", :paginate, :page => page

We still use the handy #page_entries_info in line 3. To generate the page list we iterate through all available pages (line 10) calling a yet fameless method #link_to_event.

ajax_paging
5 The small helper method creates linked page numbers (|1| |2|). When clicking, nothing happens.

Well, you just fired an event! Looking at firebug’s XHTTP tracking, a :paginate event is triggered in the 'dashboard_list'. That’s our baby!

6Alright, so let’s handle this event. In Apotomo, triggered events can be observed, maybe we should try that.

1
2
3
4
5
6
7
8
9
class ItemList < Apotomo::StatefulWidget
  def display
    respond_to_event :paginate, :with => :display
 
    @items = Todo.paginate :page => param(:page), :per_page => 10, :order => 'created_at DESC'
 
    render
  end
end

Another apotomo’ed method, #respond_to_event, instructs our list to run the #display state again when it encounters the :paginate event (line 3).

After clicking a page link the updated list appears instantaneously. At the right place. Suuuperfast. Via AJAX. That means:

  • the :paginate event is triggered
  • it’s catched by our list widget using #respond_to_event
  • the list invokes the #display method again
  • the new content replaces the old content, automatically

Cool.

But there’s more.

In the next lessons we’ll polish our list view and add another widget for posting new items. Hope to see you, guys! COMMENT.

Go to: Unobstrusive JavaScript

Meet the StatefulWidget! (Part 1)

Saturday, March 27th, 2010

Hey, great, you decided to dive into Apotomo. Most people didn’t regret that, so far. For Will, Apotomo is “what was missing in my two years of study of Ruby on Rails”, for Andy “it’s like a breath of fresh air for rails”, and for Markus, it’s just “sexy”.

A very simple item list.
In this tutorial we’re going to model and implement a small todo application, namely dumdidoo, which saves and displays things you have to do.

1 It’s on github, so go and get it. Be sure to change to the 0.1 tag, so you’re at the very beginning of our exciting way.

$ git clone git://github.com/apotonick/dumdidoo.git
$ cd dumdidoo/
$ git submodule init
$ git submodule update
 
$ script/server

When browsing to http://localhost:3000/dashboard you see the raw view of the #index action in the DashboardController.

2Luckily, I already provide you with some Todo model and random items in the database. What about some list widget to show the items?

$ script/generate widget item_list display --haml
      create  app/cells/
      create  app/cells/item_list
      create  test/widgets
      create  app/cells/item_list.rb
      create  app/cells/item_list/display.html.haml
      create  test/widgets/item_list_test.rb

Just telling the widget generator to create things for me. It spits out the widget class in app/cells/item_list.rb and a bare view for the display state.

In Apotomo a widget is like a controller in Rails, and a state is similar to an action. The standard state of a widget is usually #display, that’s somehow matching to the index action in a controller.

So if you look into the widget class and view files you’ll see they look like good old controllers!

3To plug the widget into a page we have to work in the controller.

1
2
3
4
5
6
7
8
9
class DashboardController < ApplicationController
  include Apotomo::ControllerMethods
 
  def index
    use_widgets do |root|
      root << widget(:item_list, 'dashboard_list')
    end
  end
end

In Apotomo, widgets are organized in a tree. The #use_widgets method yields this ominous root node, and we attach a widget of type item_list to it, identified by ‘dashboard_list‘.

We need the id to render that widget.

app/views/dashboard/index.html.erb

<h2>Dashboard</h2>
 
<%= render_widget 'dashboard_list' %>

Ok, so we can call #render_widget in controller views to render widgets that we defined earlier with #use_widgets. Widgets, widgets, widgets…

blank_cell

When viewing the page, the widget’s default view appears within the controller’s view. That looks trivial, but, hey, you implemented your first widget! Congratulations!

4Now it’s time to extend this item_list widget and add functionality to list the items.

1
2
3
4
5
6
class ItemList < Apotomo::StatefulWidget
  def display
    @items = Todo.find :all
    render
  end
end

app/cells/item_list.rb

This will collect all the Todo items and push ‘em to an instance variable of our widget.

The render call without any arguments just looks for the corresponding view in app/cells/item_list/ (which is named after the state). In our case, it will find app/cells/item_list/display.html.haml.

Note that you could also have erb views, or builder, or whatever you prefer.

%h3 Things
 
%ul
  - @items.each do |item|
    %li
      = item.text

app/cells/item_list/display.html.haml

Widget views can access instance variables from its widget instance, just as you know that from ordinary controllers.

simple_listWhen browsing back to the page, we see the list of all Todo items!
Wow! You just learned how to implement encapsulated, reuseable components, and we’re at tag 0.3, yet.

5I was drunk yesterday. Today, I’m tired. I will stop here. Did it take you 12 minutes? No? Do you have questions already? Feel free to use the comments below.

Well, tomorrow we’re gonna extend the list, so it is pageable. We’re gonna use will_paginate for that and on top of that we’ll get AJAX paging. Exciting!

Go to: AJAX Paging

Your 12 Minutes of Apotomo - new weekly tutorial series launched!

Saturday, March 27th, 2010

I’m happy to announce a new tutorial series “Your 12 Minutes of Apotomo” which will appear at least once per week, stealing up to 12 minutes of your worthy time per lesson in exchange for learning Apotomo.

dumdidoo

Apotomo is stateful widgets for Ruby and Rails that help you keep your code clean and enrich your user interfaces with components.

And what do we do?

We’re gonna develop a small Todo app dumdidoo with Rails that utilizes a bunch of nifty Web 2.0 features, including

  • dashboards with slots for widgets
  • tab panel and more popular elements for a user-friendly GUI
  • drag&drop everywhere
  • AJAX pagination, form processing and file uploads

Sounds cool, what else?

This is not a JavaScript tutorial, it is all Ruby! From a software developer’s point-of-view you’re gonna learn about

  • UI design with widgets
  • component-oriented architecture
  • event triggering in the browser and in your Ruby code
  • event mapping from JS to Ruby
  • statefulness and persistence vs. traditional Rails controllers
  • unit- and functional tests for components
  • YUI controls integration in your Rails app with Apotomo’s YUI widgets

Can we go?

The first lesson is right here.

And, please…

Open your mouth and post questions so we can discuss obscure things in the next lection. Also, don’t be shy and post “feature requests” (what you’d like the app to do) cause I need to know what you guys want.

We can implement proposed features in one of the following lessons.

Looking forward to the trip!

Go to: Basic widgets