Archive for June, 2008

Using an ExtJS FormPanel in the Apotomo/Rails world

Tuesday, June 17th, 2008

The FormPanel widget in ExtJS does a real good job. It can be configured to validate complicated wired fields directly in the browser and has a clean, easy, and generic way to handle loading, checking and saving data through Ajax and JSON. Of course we want this widget in our Rails application. Luckily, Apotomo’s Extjs::FormPanel does all this.

Here’s a very simple example how to insert and customize such a FormPanel.

app/apotomo/apotomo_widget_tree.rb

1
2
  w << my_form = cell(:my_first_form, :render_as_function, 'my_form',
    :object_id => 9)

We plug a cell widget into our application, and set it’s start state. I also explicitly set a static parameter (line 2) for demonstration purposes.

app/cells/my_first_form_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MyFirstFormCell < Extjs::FormPanel
 
  def init_config
    config = super
    config[:title] = "My first cheap Form"
    config[:width] = 300
    config[:frame] = true
 
    config[:items] = [
      { :xtype      => :textfield, 
        :fieldLabel => "Some text", 
        :name       => "text_field"}]
  end
 
  def load_data
    id = param(:object_id)   # find out business object to edit.
    @obj = get_object_for(id)
    return @obj
  end
 
  # note that we already have the form business object in @obj.
  def process_data(data)
    if @obj.valid?(data[:text_field])
      # update and save @obj here, send an email, ...
 
      return valid_answer
    end
 
    return invalid_answer({:text_field => "not ok!"})
  end  
end

To customize the generic FormPanel we derive MyFirstFormCell (line 1). The easiest way to add some fields to the form is to provide their config in #init_config (line 9-12). If you think this is to coarse-grained (or static) and would like to add real Apotomo widgets like TextField, you can also go this way. But- more on that in another article.

Loading initial data
By overwriting the #load_data method, we can implement the way our form finds its data to edit (line 15-19). In this example, this method is called whenever the “Load” button in the form is clicked.

Processing the input
When overwriting the #process_data method we can hook into the form processing workflow and implement our own. It’s called after clicking “Save”. We already get the form input as a hash in the data parameter. It’s our time to check it here. We could e.g. pass it to ActiveRecord::update_attributes.
If we encounter validation errors on the server-side we can send those back to the ExtJS form with #invalid_answer (line 29).

Summary
The Extjs::FormPanel widget provides a quick but flexible way to integrate FormPanels in your applications, using pure Ruby. By overwriting methods you can easily build quite complex forms without having to worry about JavaScript and ExtJS events, since they’re mapped to Ruby via Apotomo.

This example was very generic and simple. It was meant to show the basic idea behind this cool widget.

A typical application: a list-and-edit widget

Saturday, June 14th, 2008

In almost any application you need listings of business objects and forms to edit these. Here’s a small example to illustrate how to implement such a …thing in Apotomo. This example is a bit more complicated, since it involves 3 nested widgets, and events. Don’t be scared anyway, it’s simple.

There’s a huge widget containing two smaller, the listing and the edit form, which are widgets itself.

Clicking on “edit” loads the respective edit form. After saving the form, the listing is updated as well.

This is awesome. How is it done? Let’s look at the widget tree first.

app/apotomo/apotomo_widget_tree.rb

1
2
3
4
5
6
root << manager = cell(:manager, :manage, 'manager_example')
  manager << manager_list = cell(:manager, :list_employees, 'manager_list')
  manager << manager_edit = cell(:manager, :edit_empty_employee, 'manager_edit')
 
  manager_list.watch(:editClick, 'manager_edit', :_edit_employee)
  manager_edit.watch(:employeeChanged, 'manager_list', :list_employees)

We attach the two widgets to its parent, called manager_example (line 2-3). The listing widget goes to its start state :list_employees, whereas the edit form widget goes to :edit_empty_employee and shows an empty form first.
Another exciting thing are the two observers (line 5-6), but more on that later.

When clicking on “edit” something happens, the edit form for the respective employee shows. We should investigate on that.

First we should look how the listing is created.

The employee list

app/cells/manager_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class ManagerCell < Apotomo::StatefulWidget
 
  def transition_map
    { :edit_empty_employee => [:_edit_employee, :edit_empty_employee, ],
      :_edit_employee => [:_edit_employee, :_check_employee],
      :_check_employee => [ :_edit_employee, :_check_employee,]
    }
  end
 
  def manage
    nil
  end
 
  def list_employees
    @employees = my_employees
 
    jump_to_state :_list_employees
  end
 
  def _list_employees
  end
 
  def edit_empty_employee
    state_view :_edit_employee
  end
 
  def _edit_employee
    @employee = param(:employee)
    nil
  end
 
  def _check_employee
    name = param(:name)
 
    if @employee == name
      @msg = "You didn't change anything, idiot!"
    else
      @msg = "Changes saved."
      save_employee_name(@employee, name)
      trigger(:employeeChanged)
      @employee = name
    end
 
    state_view :_edit_employee
  end
 
  # helper methods go here...
end

The listing widget’s start_state is :list_employees, so the respective state method is executed. It loads the initial employees list and jumps to the state :_list_employees (line 14-18). Since nothing special happens here, the view for :_list_employees is rendered.

app/cells/manager_cell/_list_employees.rhtml

1
2
3
4
5
6
7
8
<ul>
  <% @employees.each do |emp| %>
    <li>
      <%= emp %>
      <%= link_to_event("edit", :type => :editClick, :employee => emp) %>
    </li>
  <% end %> 
</ul>

Common stuff. A list is rendered with links. The links are created from the Apotomo method link_to_event.

The “click-edit” event

When clicking on “edit”, an event is triggered, the source is the current widget with the id manager_list. The type of the event is :editClick.
Fine, and who cares about that? Remember the listeners? Here we go.

The “click-edit” listener

When looking at the widget tree we see that an event handler is attached to act on exactly that kind of event (line 5).

app/apotomo/apotomo_widget_tree.rb

5
manager_list.watch(:editClick, 'manager_edit', :_edit_employee)

In english, this means “if you see an editClick event fired by the manager_list widget, invoke the state :_edit_employee on the manager_edit widget!”. This is equivalent to a callback in GUI environments you’re used to. You are used to GUI development, aren’t you?

If you have a look at the respective state method :_edit_employee, you can see that the method loads the edited employee and renders its view (line 27-30).
Please note that whenever a widget changes its state also its content on the page is updated automatically. That’s why you instantly see an editable form after clicking the link.

Summary: A list widget fires an event, which leads another widget to change an empty form into a real edit form.

The edit form

After editing and clicking the submit button, the form input is sent to the manager_edit widget and asks it to go to the state :_check_employee (line 2 below).

app/cells/manager_cell/_edit_employee.rhtml

1
2
3
4
5
6
7
<%= @msg %>
<%= form_to_event :state => :_check_employee %>
 
  Employee name:
  <%= text_field_tag 'name', @employee, :size => 9 %>
  <%= submit_tag "Save changes" %>
</form>

Looking at the _check_employee state method (line 32-45) we see a form workflow as usual: check the input, and render the updated form. If the new input was valid, another event is triggered (line 40), this time it’s a :employeeChanged event.

The “employee updated” event

Now that we know how to handle events, we instantly see what happens now (line 6 below).

app/apotomo/apotomo_widget_tree.rb

6
  manager_edit.watch(:employeeChanged, 'manager_list', :list_employees)

The manager_list widget indicates interest in this event and wants itself to be pushed into the state :list_employees. The state method (line 14-18) reloads the employee list and renders the updated list.

Whoo, this was quite a lot! Three widgets, two events and a lot of states. Time to lean back and grab yourself a beer. Cheers, Peter!

ExtJS TabPanels going Apotomo

Thursday, June 12th, 2008

When I want to separate similar parts of my application, I love to use “notebooks”, or tab panels. The users instantly notices that all of the tabs belong to one section of the application, but represent different areas of functionality. TabPanels in ExtJS additionaly have the nice behaviour to load their tab content on demand. Today I integrated this widget into Apotomo.

The setup is a Panel, containing a TabPanel. The first tab contains a TreePanel itself.

app/apotomo/apotomo_widget_tree.rb

1
2
3
4
5
6
7
8
w << apnl = ext_panel('apotomo_panel', :title => "Apotomo rocks!", :width => 320)
  apnl << tbpanel = ext_tab_panel('my_tabpanel', :height => 90)
 
    tbpanel << tab1 = ext_tab('my_tab1', :title => "Wow!")
    tbpanel << tab2 = ext_tab('my_tab2', :title => "Give me some Panel")
 
      tab1 << tpanel = ext_tree_panel('an_ext_tree', :width => 200)
      tab2 << ext_panel('small_panel', :title => "Panel in Tab", :width => 180)

We just add one child, a Panel, to tab2 (line 8). Any number of widgets could be added here, e.g. a form widget, a news feed widget, or whatever you like.

When clicking on the second tab, ExtJS loads the content via AJAX by asking Apotomo for it.

The Tab widget isn’t directly mapped to an ExtJS widget, it was introduced for a better modularity and abstraction in Apotomo.

Acting upon clicks in an ExtJS TreePanel

Tuesday, June 10th, 2008

ExtJS’ TreePanel absolutely rock. You can display complicated data structures without worrying about the rendering process at all. However, what if you want to catch a click on a node and somehow process it in the Apotomo/Ruby environment?

In this example I’ll attach an event handler to a TreePanel which updates another widget whenever a node is clicked.

First we’ll have a look at the WidgetTree setup.

app/apotomo/apotomo_widget_tree.rb

1
2
3
4
5
6
7
8
9
10
ts = Apotomo::TreeStore.new
ts[:root] = {:two=>{:two_one=>{}}, :three=>{}}
 
w << pnl = ext_panel('big_panel', :title => "Big Panel")
  pnl << tpanel = ext_tree_panel('an_ext_tree', :width => 200)    
    tpanel.set_store(ts)
 
  pnl << peer_panel = ext_panel('peer_panel', :title => "Tree peer", :width => 300)
    peer_panel << cell(:tree_peer, :init, 'my_tree_peer')
      tpanel.watch(:click, 'my_tree_peer', :_show_clicked)

When rendered, this leads to the following app state.

We can see the TreePanel widget and the “Peer Panel” widget being pushed into the “Big Panel” (line 5 and 8).

The my_tree_peer widget is in state :init, displaying a message that nothing special happened (line 9). If a click in the tree is encountered, my_tree_code is asked to go into the state :_show_clicked. (line 10).

Let’s click on a node.

In our new application state, the my_tree_peer widget reports which node was clicked. How spectacular! The Apotomo event dispatcher sent it to another state. We can find out how this new state works by looking at the widget code.

app/cells/tree_peer_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TreePeerCell < Apotomo::StatefulWidget
 
  def transition_map
    { :init => [:_show_clicked],
      :_show_clicked => [:_show_clicked],
    }
  end
 
  def init
    "No item has been clicked."
  end
 
  def _show_clicked
    node_id = param(:node_id)
    "You clicked node #{node_id}."
  end
 
end

The two state methods just send static html to the screen, which is ok for demonstration purposes.

Note how we mapped a real ExtJS client-side event into a server-side Ruby event and managed all the event-handling in pure ruby. That’s Apotomo!