Beta Release
Current version 0.5.0
Swing, Html, Xml    Cheri::JRuby::Explorer
A Builder Framework for (J)Ruby and Java

Cheri is a framework for creating builder applications; that is, applications that build or represent hierarchical, tree-like, structures, using a declarative syntax. Cheri includes a number of builders based on the framework, as well as a builder–builder for easily creating new builders or extending existing builders. Cheri also comes with a demo application, Cheri::JRuby::Explorer (CJX), that is built using two of the supplied builders (Cheri::Swing and Cheri::Html).

Cheri may be installed as a gem (gem install cheri), or downloaded as a zip file from the Cheri project page. The Cheri::Swing and CJX components require JRuby version 1.0.3 or later; the other components run on C (MRI) Ruby as well.

Background

Cheri grew out of the JRBuilder project, a Groovy-SwingBuilder-style builder for JRuby. JRBuilder worked well enough, but was difficult to extend, or integrate with other builder components. Cheri is designed to allow multiple builder components to be used together. It also provides a means to extend builders without subclassing them, so multiple extensions may be used concurrently. (The Cheri::Swing and Cheri::Html example on the right demonstrates two builders being used seamlessly.)

Cheri::Swing is the direct descendant of JRBuilder, though it retains little of the original code. (There is also a Cheri::AWT component, but it exists mostly to support Cheri::Swing.) Cheri::Html originated as a sort of proof-of-concept trial of the generality of Cheri's architecture. In fact, it took only about 30 minutes to write a simple Cheri::Html, but the exercise lead to a number of improvements to the Cheri model (and lead me to embark on a more complete Cheri::Html). Cheri::Xml came about because, well, the code was pretty much there in Cheri::Html, so it would have been silly not to include it.

Though it's fairly easy to create new builders using the Cheri framework, I felt an even simpler mechanism would be useful. The Cheri builder-builder provides a means to create relatively simple builders without delving into the workings of the Cheri framework. It also provides a means to extend existing builders. (The extending Cheri::Swing example on the right demonstrates one way of extending Cheri::Swing with additional classes.)

Common features

Cheri builders are mixin modules; to use one, you include it in a class. The builder's functionality is available to instances of that class, and any subclasses (unless the including class is Object — inclusion in Object / at the top level is supported, but discouraged; inheritance is disabled in that case).

Cheri builders implement method_missing in the classes in which they're included; in normal operation, that is how Cheri receives the symbols that are mapped to class (or method) names for objects to be constructed. (As you will see, however, it is possible to use Cheri builders without relying on method_missing.) If no included builder can resolve a symbol received by method_missing, Cheri calls super to pass the call along, in case other code inherited by the class relies on it; likewise, if your class must implement method_missing, it can still work smoothly with Cheri builders as long as it passes along methods it doesn't consume:

class MyClass
  include Cheri::Xml
  #...
  def method_missing(sym,*args)
    if my_sym?(sym)
      #...
    else
      super
    end
  end
end

All Cheri builders also implement a cheri method, which plays two roles, depending on how it's called. When called with a block, the cheri method enables Cheri builder syntax within its scope for all included builders. When called without a block, it returns a CheriProxy object that can act as a receiver for builder methods, for any included builder. (For this reason, it's often referred to in Cheri code as a proxy method, though that isn't completely accurate.)

include Cheri::Swing
#...
@f = frame 'My Frame' #=> NoMethodError
                      #=> (no builder in scope)
  
@f = cheri.frame 'My Frame' #=> JFrame instance

cheri {
  @f = frame 'My Frame' #=> JFrame instance
}

Each built-in Cheri builder also supplies its own proxy method (in addition to the cheri method): swing for Cheri::Swing (and also awt, since Cheri::Swing includes the little-renowned Cheri::AWT), html for Cheri::Html, and xml for Cheri::Xml. These methods play the same dual scoping/proxy roles as the cheri method, but apply only to their respective builders. (Each also provides additional functionality; see the sections on individual builders for details.) The builder-specific proxy methods also serve to disambiguate overloaded builder method names. Consider:

include Cheri::AWT #=> uncommon, but legal
include Cheri::Swing
include Cheri::Html

# HTML frame? Swing frame? AWT frame?
@f = cheri.frame

# (answer: whichever was loaded last)

@af = awt.frame   #=> java.awt.Frame
@sf = swing.frame #=> javax.swing.JFrame
@hf = html.frame  #=> HTML frame
                  #=> (Cheri::Html::EmptyElem)

The proxy methods can also be used in any case where a conflict exists with a defined method with the same name (since method_missing only works if the method is, um, missing). A common conflict arises with the HTML p element; I normally undef p when using Cheri::Html, but could just use html.p instead. In fact, as noted earlier, it's possible to avoid method_missing altogether, if you have reason to do so (and are willing to forgo the aesthetic pleasure of unencumbered builder syntax):

$stdout html{
  html.body{
    html.table{
      html.tr{html.td 'Hi Mom'}
}}}

Stay tuned…

This is a beta release of Cheri — more features are in the works, and more documentation is on the way, so be sure to check back from time-to-time. Feel free to post bugs or questions on the forums or mailing list at the Cheri project/download page.

Bill Dortch
22 June 2007

Cheri::JRuby::Explorer (CJX) built using Cheri::Swing and Cheri::Html
Cheri::Swing hello world
require 'rubygems'
require 'cheri/swing'
include Cheri::Swing

f = swing.frame('Hello World') { label 'Hello!' }
f.pack
f.visible = true
Cheri::Swing fancy hello world
@frame = swing.frame('Hello World') {|frm|
  grid_table_layout frm, :wx=>0.1, :wy=>0.1
  empty_border 10,10,10,10
  content_pane { background :YELLOW }
  size 200,150
  on_window_closing { puts 'bye!'; frm.dispose }
  grid_row {
    label 'Hello', :align=>:northwest
    label('Beautiful', :a=>:center, :p=>10) {
      opaque true
      background :WHITE
      foreground :BLUE
      horizontal_alignment :CENTER
    }
    label 'World!', :a=>:se
  }
}
@frame.visible = true
Cheri::Swing and Cheri::Html
require 'cheri/swing'
require 'cheri/html'
include Cheri::Swing
include Cheri::Html

folks = {
  "Susan" =>"Wife", "Larry" =>"Son",
  "Bob" => "Friend","MaryAnne" => "Friend",
}
@frame = swing.frame('Swing and Html') { |frm|
  size 240,180; box_layout frm,:Y_AXIS
  content_pane { background :WHITE }
  default_close_operation :EXIT_ON_CLOSE
  menu_bar { 
    menu('File') {  mnemonic :VK_F
      menu_item('Exit') { mnemonic :VK_X
        on_click { @frame.dispose }
  }}}
  scroll_pane {
    align :LEFT
    editor_pane {
      content_type 'text/html'
      editable false
      background color(255,255,240)
      html {head {
         style "body { font-family: sans-serif; }" }
        body {div(:align=>:center) {
        table(:width=>'90%',:border=>1) {
          tr th('Name'), th('Relationship'),
            :bgcolor=>:yellow
          folks.each do |name,rel|
            tr(:bgcolor=>'#e0ffff') {
              td {name}; td {rel} }
          end      
   }}}}}}
}
@frame.visible = true
Cheri's builder-builder: extending Cheri::Swing
require 'cheri/swing'
class MyFrame < javax.swing.JFrame
  #...
end
class MyButton < javax.swing.JButton
  #...
end
MyBuilder = Cheri::Builder.new_builder do
  extend_builder Cheri::Swing
  build MyFrame
  build MyButton, :cool_button
end
include Cheri::Swing
include MyBuilder
@frame = swing.my_frame 'My frame!' do |frm|
  on_window_closing { @frame.dispose }
  size 240,180; box_layout frm,:Y_AXIS
  y_glue
  cool_button 'My cool button!' do
   on_click { puts 'Cool!' }
  end
  y_glue  
end
Copyright © 2007-2009 Bill Dortch