Building Rails/ExtJS Reusable Components With Netzke, Part 3
This post continues the series of the tutorials on building Netzke widgets – reusable pieces of code for a Rails/ExtJS application. It extends the slightly outdated part 2, and demonstrates the power of the new client-server communication concept introduced in netzke-core v0.4.0. We will build (from scratch) a composite widget that will nest 2 GridPanels (linked by a one-to-many relationship) and a FormPanel that will display some details about the record that is selected in one of the grids. I will show how easy it is to update all nested widgets within one single client-server roundtrip – an approach which gives the developer a new level of freedom in building complex widgets. You will also learn about new configuration options introduced into Netzke::GridPanel. The live demo for this tutorial can be found here.
We will do our work in the context of the netzke-demo application, where we already have two models related to each other via one-to-many relationship: Boss and Clerk. The demo already has the “Bosses and clerks” menu that is demonstrating the result of our previous tutorial – a composite widget that glues 2 GridPanels together, in the sense that the “clerks” grid provides interface to edit the clerks working under a specific boss. It was done by means that, on clicking a boss, the “clerks” grid panel was reconfigured in the browser and forced to reload data with modified filters. This way the logic of filtering the clerks was applied at the client side, which is limiting by itself (and also may be insecure). Another limitation of this approach is discussed in the next paragraph. In this tutorial I’ll show how the same functionality – and quite a bit more – can be achieved with the latest Netzke release in a much more flexible way, where the widget logic is moved onto the server side.
What also makes this tutorial different from the part 2 is that I’m not going to make this widget generic, rather giving an example of designing a widget specific to the application. It will significantly simplify the tutorial and keep it focused without compromising it being educative. And if you worry about the result being thus less reusable (by being less generic), then I would argue that it can still be seen as such – within your applications.
So, clicking a boss in one grid triggers another grid to reload the clerks. But what if we need more – say, another panel that shows some information (say, in simply HTML) about the selected boss? We could create a Panel-based widget that would do just that: given a boss id, it would update itself with the data from the server, the same way as the clerk grid does. This may sound fine, but it would cost an extra request to the server, because the widgets simply don’t know anything about each other, each talking to its server part independently. This way, another panel in our widget would introduce yet another request… One more unpleasant consequence of this approach is that the sub-widgets wouldn’t be getting updated simultaneously (each waiting for the response from its own server part), which is pretty confusing from the end user’s point of view. And let alone the necessity to write a separate widget for a simple task of displaying some HTML.
Wouldn’t it be much better if our composite widget was able to update all its aggregatees itself, just inline with the composite pattern? That’s something that is possible with the latest Netzke release, and soon you’ll see how simple it is.
BorderLayoutPanel – our widget’s layout
First, let’s create our composite widget and call it BossesAndClerks. Because it’s going to host 3 panels (2 grids and the “info panel”), it inherits from Netzke::BorderLayoutPanel:
module Netzke class BossesAndClerks < BorderLayoutPanel end end
Let’s specify the regions and fill them with empty Panels – just to see if we like the layout of the future widget:
def default_config super.merge({ :regions => { :center => { :widget_class_name => "Panel" }, :east => { :widget_class_name => "Panel", :region_config => { :width => 240, :split => true } }, :south => { :widget_class_name => "Panel", :region_config => { :height => 150, :split => true } } } }) end
Let’s load the widget into our application and see how it looks:
How to aggregate a widget into the application and create a menu item for it is slightly outside of the scope of this tutorial, and I’ll soon make a separate post on Netzke::BasicApp, but if you’re eager to know how to do it, please, refer straight to the source code.

The default titles don’t look nice, so let’s correct it. And we also don’t need the top-widget header:
def default_config super.merge({ :regions => { :center => { :widget_class_name => "Panel", :ext_config => { :title => "Bosses" } }, :east => { :widget_class_name => "Panel", :ext_config => { :title => "Info" }, :region_config => { :width => 240, :split => true } }, :south => { :widget_class_name => "Panel", :ext_config => { :title => "Clerks" }, :region_config => { :height => 150, :split => true } } }, :ext_config => { :header => false } }) end
You may remember from the previous tutorials that the ext_config option allows us to specify any configuration options understood by the underlying Ext’s widget, in this case Ext.Panel.

GridPanels
Now that we like the layout of the future widget, let’s introduce 2 GridPanels into it (GridPanel is a full-featured widget from netzke-basepack). One of them we configure to show bosses, the other – clerks:
def default_config super.merge({ :regions => { :center => { :widget_class_name => "GridPanel", :data_class_name => "Boss", :ext_config => { :title => "Bosses" } }, :east => { :widget_class_name => "Panel", :ext_config => { :title => "Info" }, :region_config => { :width => 240, :split => true } }, :south => { :widget_class_name => "GridPanel", :data_class_name => "Clerk", :ext_config => { :title => "Clerks" }, :region_config => { :height => 150, :split => true } } }, :ext_config => { :header => false } }) end

We haven’t done much programming at this point, you would probably agree, as it feels more like configuring. And although our grids are fully functional (you can add/edit/remove records, even several at a time, move/hide/resize columns, you have Rails' validations and on-the-fly configuration, etc), there’s no relationship set up between them. So, let’s write some code now.
Setting the row click event
Along with the Ext guidelines, we’ll do it inside Ext.Component#initComponent (also note how BorderLayoutPanel provides convenient methods to access its regions widgets, like getCenterWidget, getWestWidget, etc):
def self.js_extend_properties super.merge({ :init_component => <<-END_OF_JAVASCRIPT.l, function(){ #{js_full_class_name}.superclass.initComponent.call(this); // Set the selection changed event this.getCenterWidget().on('rowclick', this.onBossSelectionChanged, this); } END_OF_JAVASCRIPT # Event handler :on_boss_selection_changed => <<-END_OF_JAVASCRIPT.l, function(self, rowIndex){ alert("Boss id: " + self.store.getAt(rowIndex).get('id')); } END_OF_JAVASCRIPT }) end
Netzke::Base.class#js_extend_properties method allows defining the properties (including functions) of the JavaScript class of the widget. It automatically converts property names from underscore in Ruby into lower camel case in JavaScript.
Now, here comes the key point. What is going to happen next, is that the event handler will send the selected boss' id to the server side, which will be in full control on how to react on that. It will decide to update the data in the second grid, as well as the HTML in the third panel, as well as some other little things that were not so easy to do before.
“Directly call” Ruby methods from JavaScript
Something that was introduced in the latest Netzke release, is that now there is a way for any widget to call its Ruby methods “straight” from the JavaScript side. The call is asynchronous, of course, and is done via AJAX. To do this, we first need to define the API point in our widget’s class:
api :select_boss
This provides a method called selectBoss in our JavaScript class, which we now can call just like this:
this.selectBoss({param1:10, param2:20, ...})
… which will magically result in the call to the ruby method select_boss with a hash {:param1 => 10, :param2 => 20, …} passed to it:
def select_boss(params) logger.debug "!!! params[:boss_id]: #{params[:boss_id].inspect}\n" {} end
In a moment I’ll explain why this method returns a hash
Let’s update the onBossSelectionChanged method to do that remote call:
:on_boss_selection_changed => <<-END_OF_JAVASCRIPT.l, function(self, rowIndex){ this.selectBoss({boss_id: self.store.getAt(rowIndex).get('id')}); } END_OF_JAVASCRIPT
Reload the widget and watch the Rails' log as we click a boss:
!!! params[:boss_id]: "4"
That same magic in reverse: calling JavaScript methods from Ruby
Now, in response to the call, we need to update the “clerks” grid. What exactly has to be done for that? We need to command the client side of our widget to update the “clerks” grid panel with new data. Well, similarly to how we could “call” the Ruby method from JavaScript, we can call any method on the JavaScript side “from” our select_boss Ruby method. This call is encoded in the hash that is returned by select_boss):
{
:south => {:load_store_data => new_data}
}
The :south key in this hash defines the scope of the call – e.g. the aggregatee’s id to which this call is addressed (the double-underscore notation can be used to address nested widgets down the hierarchy, e.g. :southtab0west). Then there comes a hash where the keys define the methods, and the values – the parameters that should be passed to them. Thus, multiple methods can be called, for example:
{
:south => {:load_store_data => new_data, :set_title => "Clerks for #{boss.name}"}
}
You may see that :set_title refers to the Ext.Panel#setTitle method, which, well, updates the title of the Ext.Panel.
Similarly, you can simultaneously request calls to some methods in other aggregatees as well:
{
:south => {:load_store_data => new_data, :set_title => "Clerks for #{boss.name}"},
:east => {:update_body_html => statistics_html, :set_title => "#{boss.name}"}
}
… where :update_body_html refers to the the self-explanatory updateBodyHtml method defined on the JavaScript side of Netzke::Panel.
I hope, you get the idea. In one round-trip request to the server we are able to do anything that we could do right from JavaScript side, with a big difference that what has to be done is defined by the server.
Going a bit advanced, I will mention that wherever the order of the calls is important, you can use arrays instead of hashes, and it’ll work just as fine:
[ {:east => [ {:set_title => "#{boss.name}"}, {:update_body_html => statistics_html} ],{ :south => [ {:load_store_data => new_data}, {:set_title => "Clerks for #{boss.name}"} ]} ]And, by the way, if no scope is specified, the calling widget itself is the target (similarly to “self” in Ruby, or “this” in JavaScript), e.g.:
{ :set_title => "New title for ourselves!" }
Getting the “clerks” grid data
Now, how do we get the data for the “clerks” grid? In the select_boss method we can instantiate the clerks-related aggregatee and call its API method get_data:
clerks_grid = aggregatee_instance(:south)
clerks_data = clerks_data.get_data
Done like this, it will return all the same unfiltered clerks as before. Now let’s use the new configuration options introduced into Netzke::GridPanel, namely :scopes and :strong_default_attrs. The first one allows us to specify the named scopes that will be applied to the grid’s data, the second – the extra attributes hash that will be applied to each newly created record. We’ll use these to limit the scope of our “clerks” grid to the clerks under the currently selected boss. Let’s apply these options right into the definition of the “clerks” (south) region:
:south => { :widget_class_name => "GridPanel", :data_class_name => "Clerk", :ext_config => { :title => "Clerks" }, :region_config => { :height => 150, :split => true }, :scopes => [[:boss_id, widget_session[:selected_boss_id]]], :strong_default_attrs => {:boss_id => widget_session[:selected_boss_id]} }
It may not seem as an obvious place to specify the scope that is going to change during what seems to be the widget’s lifetime. After all, isn’t it the place where we define our widget? Especially for a desktop apps developer that may be confusing, but remember, that in our case the whole widget instances hierarchy is recreated at each and every request to the server.
The widget_session method works just as normal Rails' session, besides that the data in widget_session is scoped by the current widget. We’ll use it to store the id of the currently selected boss, which makes the final version of select_boss look like this:
def select_boss(params) # store selected boss id in the session for this widget's instance widget_session[:selected_boss_id] = params[:boss_id] # selected boss boss = Boss.find(params[:boss_id]) # instantiate the "clerks" widget and get its data clerks_grid = aggregatee_instance(:south) clerks_data = clerks_grid.get_data # pass { :south => {:load_store_data => clerks_data, :set_title => "Clerks for #{boss.name}"}, :east => {:update_body_html => boss_info_html(boss), :set_title => "#{boss.name}"}, } end
Yet another in-depth remark. We can effectively set the new widget_session[:selected_boss_id] before calling aggregatee_instance because aggregatee_instance is exactly where the “clerks” widget gets instantiated for the first time – using that lazy configuration hash specified in default_config.
And the boss_info_html method, which will provide the HTML for the third panel, can be defined like this:
def boss_info_html(boss) res = "<h1>Statistics on clerks</h1>" res << "Number: #{boss.clerks.count}<br/>" res << "With salary > $5,000: #{boss.clerks.salary_gt(5000).count}<br/>" res << "To lay off: #{boss.clerks.subject_to_lay_off_eq(true).count}<br/>" res end
Conclusion
This concludes the tutorial. Please, leave your remarks, questions or suggestions in the comments, or post them straight to the Netzke Google Groups.

