netzke教程

本文介绍如何使用Ext JS创建动态加载、客户端与服务器交互的组件,包括配置组件、定义动作、动态加载子组件及扩展组件的功能。通过实例演示了如何在Rails应用中集成Ext JS组件,并实现实时更新数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Hello world extjs


Let's create a simple "Hello world!" component and embed it into a Rails' view. It'll be an Ext.Panel-based component with a button that triggers client-server communication.

Create YOUR_APP/app/components/hello_world_component.rb, and put in the following content:

class HelloWorldComponent < Netzke::Base
  # Ext.Panel's config option "title"
  js_property :title, "My Hello World Component"

  # Bottom bar with an automatically created action
  js_property :bbar, [:bug_server.action]

  # Action to be placed on the bottom bar
  action :bug_server, :text => 'Greet the World', :icon => :accept

  # Method in the JS class that (by default) processes the action's "click" event
  js_method :on_bug_server, <<-JS
    function(){
      // Remotely calling the server's method greet_the_world (defined below)
      this.greetTheWorld();
    }
  JS

  # Server's method that gets called from the JS
  endpoint :greet_the_world do |params|
    # Tell the client side to call its method showGreeting with "Hello World!" as parameter
    {:show_greeting => "Hello World!"}
  end

  # Another method in the JS class that gets remotely called by the server side
  js_method :show_greeting, <<-JS
    function(greeting){
      this.body.update("Server says: " + greeting);
    }
  JS
end

Embedding the component into Rails views

To embed a Netzke component into your Rails view do the following:

  • Add netzke routes:
# in routes.rb
RailsApp::Application.routes.draw do
  netzke
  ...
end
  • In your layout, within the "head" tag, use the netzke_init helper to include all the necessary JavaScript and styles.
<%= netzke_init %>

# You can optionally specify an Ext theme:
<%= netzke_init :ext_theme => 'gray' %>
  • In your view use the netzke helper to embed the component:
<%= netzke :hello_world_component %>

That's it. While playing with the component, use Firebug or similar tool to check the AJAX requests to understand better what's going on behind the scenes. Also check the source code of the page embedding the component.

Troubleshooting

  • If you're getting the "Netzke: action 'bug_server' not defined" exception in JavaScript, you have possibly the Prototype JS library included in your layout:
<%= javascript_include_tag :defaults %>

It currently conflicts with Netzke and should not be included.


Building and extending components


This page describes API for defining and extending Netzke components. It's especially useful for developers that build their own Netzke components.

JavaScript class of a component

Each component operates on both client and server side. At the server side it's represented by a Ruby class, at the client side it's represented by a corresponding JavaScript class (extending one of the Ext JS classes, or, in case of extending an existing Netzke component, that component's JavaScript class). This section describes what it takes to specify the client side of a component.

Below is the list of DSL methods used for defining the JavaScript class of a component. For details on each method refer to the correspondingAPI.

  • js_base_class - base JS class for the component
  • js_propertiesjs_property - default class properties
  • js_method - class methods
  • js_mixin - external class methods and properties (from a .js file)
  • js_include - external dependencies

Defining actions

Use the action class method to specify actions that get mapped to clickable elements such as menu items or buttons (more on actions in theExt JS docs). For example:

action :open_window, :text => "Show window", :icon => :window, :disabled => true, :handler => :some_method

In this case, the passed config will be merged with the corresponding action configuration from the superclass.

If you want an action to be configured on the instance level, pass it a block:

action :do_stuff do
  {:disabled => config[:with_my_action_disabled]}
end

If you want to override an existing action and need access to its superclass's definition, override the "[action_name]_action" method:

def do_stuff_action
  super.merge(:text => super[:text] + " extra")
end

Client-server communication

Defining an endpoint

For a component to establish communication between its client and server part, an endpoint must be defined:

endpoint :do_something do |params|
  # ...
end

Calling an endpoint

By defining the endpoint on the server, the client side automatically gets a method that is used to call the server, in this case doSomething (note conversion from underscore to camelcase). It can be called like this:

this.doSomething(argsObject, callbackFunction, scope);
  • argsObject is what the server side will receive as the params argument (as hash)
  • callbackFunction, when provided, will be called after the server successfully processes the request
  • scope is the scope in which callbackFunction will be called

The callback function may receive an argument which will be set to the value that the server passes to the special set_result key in the resulting hash:

endpoint :do_something do |params|
  # ...
  {:set_result => 42}
end

This way, when we do:

this.doSomething({}, function(res){ alert(res); });

the alert dialog will display "42".

Overriding an endpoint

If you wish to override an endpoint defined in the superclass, override the "{endpoint name}_endpoint" method:

def do_something_endpoint(params)
  super.merge(...)
end

Composition

A component can aggregate other components, thus becoming a "composite".

Defining child components

Use the component class method (aliased add_component) to declare a child component. In the simplest form it accepts the name of the component and a configuration hash:

component :clerks_grid, :class_name => "Basepack::GridPanel", :model => "Grid"

If you want a component to be configured on the instance level (e.g. you need access to the instance methods), pass it a block:

component :some_form do
  { :class_name => "Basepack::FormPanel", :model => config[:model] }
end

If you want to override a component defined in the superclass, override the "{component name}_component" method. It should return the configuration hash. For example:

def clerks_grid_component
  super.merge(
    :class_name => "MyCustomizedGrid",
    :title => super[:title] + " (customized)"
  )
end

Overriding children components' endpoints

In order for a composite class to interact with the client-server communication that takes place inside its aggregates, a component may define a method with a name following the convention, where double underscore serves as a path delimiter to specify which component and which method must be addressed. The easiest is to give an example:

def child__grand_child__do_something_endpoint(params)
  # ...
end

This will override the do_something endpoint of a component called "grand_child" which is a child of the our "child" component.

What this method will return, will be seen in the scope of the "grand_child" component itself. This way, to call a JavaScript method in our current component, we'd need to do something like this:

def child__grand_child__do_something_endpoint(params)
  # ...
  {:parent => { # going to the scope of "child"
    :parent => { # now we are in the scope of our current component
      :some_method => [*some_params]
    }
  }}
end

Instance-level configuration

When the JavaScript instance of a component gets instantiated, it receives a config object, which is built by the Ruby instance of the component. This config object will override any default properties defined by js_properties. Also, some configuration options, such as items, can only be specified in the configuration object, not as a class's default property.

Besides using this configuration object to instantiate the JavaScript class, a component may understand some configuration options that are only meant for the Ruby part of it, without being passed to the JavaScript instance (a component may decide which options should not go to the JavaScript side by overriding the server_side_config_options method, which returns an array of option names as symbols, e.g.:[:server_option_one, :server_option_two]). These options are internally represented in a component as a hash. What goes into that hash, is defined in different layers, and we may want to be in control of all of them when extending an existing component.

Overriding instance configuration

You can override instance level configuration by defining a method called configuration. For example:

def configuration
  super.merge(:title => super[:mode] == :config ? config_mode_title(super[:title]) : super[:title])
end

def config_mode_title(original_title)
  "#{original_title} (config mode)"
end

Here we set the title of a component depending on other configuration option, mode. So, if the user configures our component like this:

netzke :the_component, :title => "The Component"

this component, being instantiated in "config mode", will have the title "The Component (config mode)".

Overriding default component configuration

Sometimes it's handy, extending an existing component, to provide it with default configuration. You can do it by overriding the default_configmethod:

def default_config
  super.merge(:mode => :config) # by default, this component will be in "config mode"
end

Accessing the resulting config

You can access the resulting configuration hash anywhere inside the component by calling the config method, e.g.:

do_something if config[:some_feature_enabled]

Dynamic loading of components



This guide assumes the extjs4 branch of Netzke Core

Let's say our component has a child component declared like this:

component :simple_component, :class_name => "MySimpleComponent", :lazy_loading => true

We can load this component dynamically from the client side of our component by calling the method loadNetzkeComponent. It accepts the following configuration options:

  • name - the name of the component (in our case - 'simple_component')
  • container - (optional) instance or id of a panel with the 'fit' layout that the loaded component will automatically be rendered to
  • callback - (optional) function that will be called after the component is loaded; as the only parameter, it will receive the loaded component's instance
  • scope - (optional) scope for the callback

For several examples of dynamic component loading, see the ComponentLoader class in the test app of Netzke Core.



Adding custom actions to a component



This tutorial which will show how to extend a Netzke component in order to add a custom Ext.Action into it, and then bind that to a new button in a toolbar, as well as to a new context menu item. Then you'll see how we can conditionally disable this action for more consistent end-user's experience. If Ext JS actions are still something new to you, you'll find this tutorial especially useful, as I'll provide a brief introduction unto them. Otherwise you'll see how easy it is to use Ext.Action with Netzke. As usual, there's an online demo of this tutorial's results, and the source code is on GitHub.

We'll be extending Netzke::Basepack::GridPanel, as it already provides both the bottom toolbar and the context menu, which will leave us little work adding our action into them. Let's call our component CustomActionGrid. Add a file named custom_action_grid.rb into the "app/components" directory in your Netzke-enabled project (as always, you can use netzke-demo project as your playground), and start with defining our widget's class:

class CustomActionGrid < GridPanel
end

What is Ext.Action

Let's take a moment to understand what Ext.Action actually is. Here's an excerpt from the Sencha Ext JS documentation:

An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI updates across any components that support the Action interface (primarily Ext.Toolbar, Ext.Button and Ext.menu.Menu components).

It may be easier to understand the concept by an example. Imagine, your component needs to do something on a request from the end-user. What are the ways for the user to trigger the action? She may click a button on the component's toolbar for example, or use the context menu, or even turn to the application-wide menu that links to your widget. Those possibilities are represented by different Ext JS components (hence the reference to "multiple components"): your component's toolbar, the context menu, or the application's menu. If you were to configure all the 3 occurrences by hand, you would end up with not-so-DRY code, defining at least the same handler function everywhere. Now add that a button or a menu item can also have an icon and a state (enabled/disabled, hidden/shown), both of which can dynamically change according to some conditions, and you'll realize that maintaining consistency among your component's actions would be a tedious task. That's where Ext.Action comes to rescue. Create an action with an initial handler, icon, and state, and then link to it from any of those "components that support the Action interface". Later, if you dynamically change any of those properties (in this tutorial we'll be playing with the state), Ext JS will automatically update all controls that use this action.

Let's add our custom action by overriding the Netzke::Base.action method:

action :show_details, :text => "Show details"

In this case the action will be called "showDetails", and, by being triggered, it'll call the component's method named onShowDetails by default (you can overwrite it by providing the :handler option like this: :handler => :my_handler). Additionally, you can specify any extra configuration options that are accepted by Ext.Action.

Implementing the action

We may want our action to do something as complex or as simple as we wish. In this tutorial let's open a little window which will display a formatted HTML text showing the summary of the currently selected record. So, let's define the handler:

js_method :on_show_details, <<-JS
  function(){
    var tmpl = new Ext.Template("<b>{0}</b>: {1}<br/>"), html = "";
    
    Ext.iterate(this.getSelectionModel().selected.first().data, function(key, value){
      html += tmpl.apply([key.humanize(), value]);
    }, this);

    Ext.Msg.show({
      title: "Details",
      width: 300,
      msg: html
    });
  }
JS

Adding the action to the toolbar and context menu

After this we want to add the action both to the bottom toolbar and context menu (here I'm adding them, followed by a separator, before the other items):

def default_bbar
  [:show_details.action, "-", *super]
end

def default_context_menu
  [:show_details.action, "-", *super]
end

Here's how the resulting bottom bar and context menu should look like: Action in context menu and bottom bar

And here's what we should see after we click either the button, or the context menu item:

The result of the action

Dynamically enabling/disabling the action

Now let's disable the action if the amount of selected rows in the grid is different from 1. In order to do it, override theExt.Component#initComponent method like this:

js_method :init_component, <<-JS
  function(){
    #{js_full_class_name}.superclass.initComponent.call(this);

    this.getSelectionModel().on('selectionchange', function(selModel){
      this.actions.showDetails.setDisabled(selModel.getCount() != 1);
    }, this);
  }
JS

Note, that the generated by Netzke JavaScript class is provided with the actions property to access the actions that we define in the actionsmethod in Ruby.

Now, if we select more than one row, our action gets disabled:

Action disabled

And here's the last little touch. As the GridPanel initially loads without any rows selected, we want our action to be disabled by default. Update the actions method like this:

action :show_details, :text => "Show details", :disabled => true

Conclusion

This concludes the tutorial. Hopefully, you have learnt how to create and implement custom actions in a Netzke component, and how to dynamically change the action's state.

Please, leave your remarks, questions or suggestions in the comments, or post them straight on the Netzke Google Groups.

Please, help me improve the quality of this tutorial and keep it up-to-date by editing this page.

Building a composite component



In this tutorial we will build (from scratch) a composite component that will nest 2 grids (linked by a one-to-many relationship) and a form 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 components within a single client-server roundtrip, using an approach which gives the developer a great level of freedom in building complex components. Live demo for this tutorial can be found here, while the code can be found in the netzke-demo project (the rails3 branch) on GitHub.

For this tutorial, I was using: Rails 3.0.1, netzke-core 0.6.2, netzke-basepack 0.6.1, Ruby 1.9.2, Ext 3.3.0. Live demo mentioned before still uses Rails 2.3, Ruby 1.8.7 and older versions of Netzke.

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 component 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 Netzke v0.5.0 release in a much more flexible way, where the component 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 component generic, rather giving an example of designing a component specific to the application. It will significantly simplify the tutorial and keep it focused without compromising it being educative.

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 plain HTML) about the selected boss? We could create a Panel-based component 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 components simply don't know anything about each other, each talking to its server part independently. This way, another panel in our component would introduce yet another request, etc. One more unpleasant consequence of this approach is that the sub-components 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 component for a simple task of displaying some HTML.

Wouldn't it be much better if our composite component was able to update all its children components itself, inline with the composite pattern? That's something that is possible with Netzke, and soon you'll see how simple it is.

BorderLayoutPanel - our component's layout

Let's call our future component BossesAndClerks. Because it's going to host 3 panels (2 grids and the "info" panel), it inherits fromNetzke::Basepack::BorderLayoutPanel:

# in app/components/bosses_and_clerks.rb
class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
end

Let's specify the regions as empty panels - just to see if we like the layout of the future component:

class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
  def configuration
    super.merge(
      :items => [{
        :region => :center
      },{
        :region => :east,
        :width => 240,
        :split => true
      },{
        :region => :south,
        :height => 150,
        :split => true
      }]
    )
  end
end

To see how our component looks in a view, let's start the server, and then point our browser to http://localhost:3000/components/BossesAndClerks:

Layout of the component

Doesn't look too bad, but let's add headers to the nested panels and remove the header from the top-level panel:

class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
  def configuration
    super.merge(
      :header => false,
      :items => [{
          :region => :center,
          :title => "Bosses"
        },{
          :region => :east,
          :title => "Info",
          :width => 240,
          :split => true
        },{
          :region => :south,
          :title => "Clerks",
          :height => 150,
          :split => true
      }]
    )
  end
end

Improved titles

GridPanels

Now that we like the layout of our future component, let's introduce 2 GridPanels into it (GridPanel is a full-featured component from netzke-basepack), and a Panel in the "east" region. One of the grid we'll configure to show bosses, the other - clerks:

class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
  def configuration
    super.merge(
      :header => false,
      :items => [{
          :name => "bosses",
          :class_name => "Netzke::Basepack::GridPanel",
          :model => "Boss",
          :region => :center,
          :title => "Bosses"
        },{
          :name => "info",
          :class_name => "Netzke::Basepack::Panel",
          :region => :east,
          :title => "Info",
          :width => 240,
          :split => true
        },{
          :name => "clerks",
          :class_name => "Netzke::Basepack::GridPanel",
          :model => "Clerk",
          :region => :south,
          :title => "Clerks",
          :height => 150,
          :split => true
      }]
    )
  end
end

Grid panels added

The Panel in the "east" region didn't change anything visually, but will provide us with some extra functionality we'll be using later.

We haven't done much programming at this point, as you would probably agree, it rather feels more like configuring. And although our grids are fully functional (you can add/edit/remove records, even several at a time, you have Rails' validations, pagination, sorting, etc), there's no relation set up between them. So, let's write some code now.

Setting the row click event

Along with the Ext guidelines, we'll set the row click event on the bosses grid inside the Ext.Component#initComponent method:

class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
  def configuration
    super.merge(
      :header => false,
      :items => [{
          :name => "bosses",
          :class_name => "Netzke::Basepack::GridPanel",
          :region => :center,
          :title => "Bosses",
          :model => "Boss"
        },{
          :name => "info",
          :class_name => "Netzke::Basepack::Panel",
          :region => :east,
          :title => "Info",
          :width => 240,
          :split => true
        },{
          :name => "clerks",
          :class_name => "Netzke::Basepack::GridPanel",
          :model => "Clerk",
          :region => :south,
          :title => "Clerks",
          :height => 150,
          :split => true
      }]
    )
  end

  # Overriding initComponent
  js_method :init_component, <<-JS
    function(){
      // calling superclass's initComponent
      #{js_full_class_name}.superclass.initComponent.call(this);

      // setting the 'rowclick' event
      this.getChildComponent('bosses').on('rowclick', this.onBossSelectionChanged, this);
    }
  JS

  # Event handler
  js_method :on_boss_selection_changed, <<-JS
    function(self, rowIndex){
      alert("Boss id: " + self.store.getAt(rowIndex).get('id'));
    }
  JS

end

The Netzke::Base.js_method method allows defining public methods of the JavaScript class of the component. Netzke converts method names from underscore in Ruby into lower camel case in JavaScript, just for the sake of common conventions.

The getChildComponent method is provided by any Netzke component to access its (instantiated) children.

By the way, you may appreciate some subtle magic taking place here. We're overriding initComponent in JavaScript, right? It means that our generated JavaScript class is inheriting from some other class, but from which? Well, it's the JavaScript class thatBasepack::BorderLayoutPanel is generating as its own client-side code! Because we're extending our component class fromBasepack::BorderLayoutPanel, we're getting JavaScript level extension for free.

Now, here we're approaching the key moment. What is going to happen next, is that the event handler will send the selected boss's id to the server side, which will be in full control of how to react on that. It will command the client side to update the data in the second grid, as well as the HTML in the "east" panel, and even change some other little things - all in one roundtrip!

"Directly" calling Ruby methods from JavaScript

In order for the JS instance to remotely call a method on the server, we need to define an endpoint in our class:

endpoint :select_boss do |params|
end

This automatically gives us a dynamically defined 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 an asynchronous AJAX 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]}\n"
end

Let's update the onBossSelectionChanged method to do that remote call:

# Event handler
js_method :on_boss_selection_changed, <<-JS
  function(self, rowIndex){
    this.selectBoss({boss_id: 100});
  }
JS

Reload the component 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 component 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 defined in the hash that is returned by the select_bossendpoint):

endpoint :select_boss do |params|
  {
    :clerks => {:load_store_data => new_data}
  }
end

What comes into new_data I'm explaining below.

The :clerks key in this hash defines the scope of the call - e.g. the child component's name to which this call is addressed. 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:

endpoint :select_boss do |params|
  {
    :clerks => {:load_store_data => new_data, :set_title => "Some new title"}
  }
end

You may see that :set_title refers to the Ext.Panel#setTitle method, which, well, updates the title of the grid.

Similarly, you can call methods in other child components as well:

endpoint :select_boss do |params|
  {
    :clerks => {:load_store_data => new_data, :set_title => "Some new title"},
    :info => {:update_body_html => statistics_html, :set_title => "Updated title here, too"}
  }
end

... where :update_body_html refers to the the self-explanatory updateBodyHtml method defined on the JavaScript side ofNetzke::Basepack::Panel (that's where it comes in handy!).

I hope, you're getting the idea by now. 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 determined by the server.

If no scope is specified, the calling component 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 endpoint block we can instantiate the clerks-related child component and call its method get_data:

clerks_grid = component_instance(:clerks)
clerks_data = clerks_grid.get_data

Done like this, it will return all the same unfiltered clerks as before. Now let's use the following configuration options of Basepack::GridPanel:scope and strong_default_attrs. The first one allows us to specify the scope 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.

And at the same time let's reorganize our code slightly, extracting the clerks grid configuration out of the config call into a child component declaration, using the component call:

class BossesAndClerks < Netzke::Basepack::BorderLayoutPanel
  def configuration
    super.merge(
      :header => false,
      :items => [
        {
          :region => :center,
          :title => "Bosses",
          :name => "bosses",
          :class_name => "Netzke::Basepack::GridPanel",
          :model => "Boss"
        },{
          :region => :east,
          :title => "Info",
          :name => "info",
          :width => 240,
          :split => true
        },
        :clerks.component(
          :region => :south,
          :title => "Clerks",
          :height => 150,
          :split => true
        )
      ]
    )
  end

  component :clerks do
    {
      :class_name => "Netzke::Basepack::GridPanel",
      :model => "Clerk",

      # do not load data initially
      :load_inline_data => false,

      # limit the scope to the selected boss (see below)
      :scope => {:boss_id => component_session[:selected_boss_id]},

      # always set boss_id
      :strong_default_attrs => {:boss_id => component_session[:selected_boss_id]}
    }
  end

  # ...
end

You may notice that in the config we reference to the clerks component by calling the component method on the :clerks symbol, also passing additional configuration (which is more related to the layout, than to the component itself)

The component_session method works just as normal Rails' session, besides that the data in component_session is scoped by the current component. We'll use it to store the id of the currently selected boss, which makes the final version ofthe select_boss endpoint look like this:

endpoint :select_boss do |params|
  # store selected boss id in the session for this component's instance
  component_session[:selected_boss_id] = params[:boss_id]

  # selected boss
  boss = Boss.find(params[:boss_id])

  clerks_grid = component_instance(:clerks)
  clerks_data = clerks_grid.get_data

  {
    :clerks => {:load_store_data => clerks_data, :set_title => "Clerks for #{boss.name}"},
    :info => {:update_body_html => boss_info_html(boss), :set_title => "#{boss.name}"}
  }
end

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.where(:salary.gt => 5000).count}<br/>"
  res << "To lay off: #{boss.clerks.where(:subject_to_lay_off => true).count}<br/>"
  res
end

Conclusion

This concludes the tutorial. Please, leave your remarks, questions or suggestions in the comments, or post them straight on the Netzke Google Groups.

Final result




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值