JRBuilder: Binding Demo 2
Home «  Prev    Contents    Next  » Download
JRBuilder: Binding Demo 2
Still more on Binding: Using prototypes. Binding contexts and inheritance.             
This example builds on the the previous example, and introduces the concepts of binding contexts and prototypes.

A binding context is a collection of zero or more each of bindings, prototypes, translators, and converters, and potentially other types of things in the future. (Translators and converters are discussed in a later example.) Each Bindery instance contains a single default context, which is essentially unnamed; any Bindery operation that does not specify a binding context name operates on the default context. All other binding contexts are named.

A named binding context is created the first time it is specified in a binding_context statement. At that time (and only at that time, though this is likely to change), a parent binding context may be specified, from which the new context will inherit all prototypes, translators, and converters defined in the parent context. Any bindings defined in the parent are not inherited. The new context also inherits everything its parent inherited, and so on.

(I will probably add multiple inheritance in the near future. It seems like it could be quite useful, at least to the extent that any of this is.)

A new binding context is created in a bound state; that is, any bindings specified immediately go into effect. (This will change soon; I plan to add an unbound_context statement in the next version that will create a context in an unbound state.) A bound context may be unbound, then later rebound or removed. The unbind_context, rebind_context, and remove_context statements are discussed in the next example.

In this example, I first create a new Bindery, and then create a new named binding context, 'my_swing_lib'. I don't plan to create any bindings in this context; as its name suggests, it is meant to act as a library of useful prototypes (and later other types of things). (Note: you can just follow the code in blue; the rest is taken directly from the previous example.)

The new binding context defines two prototypes. A prototype definition usually looks something like a correspondent definition, except that a class (or nil) is specified instead of a particular instance of a class; and a correspondent is specified in a bind statement, while a prototype is specified outside of any binding. A prototype acts as a template of sorts (in fact, that might be a better name). New correspondent definitions are matched against all prototypes, from which they may inherit any of: event method, getter, setter, predicate, attributes (discussed below), unit, or type (unit and type are discussed in a later example.)

Prototypes are searched from nearest to farthest defined, matching on class, event method, getter, setter, unit, and type, where a nil value in the prototype acts as a wildcard, matching any value in the correspondent. All six elements above must match, by equality or wildcard, for the prototype to be used. If a prototype matches, any element that it defines that is not yet present in the correspondent will be set in the correspondent. Prototypes that are specified after a given binding will not be searched for that binding.

Prototypes use the method name any (or any_method) to signify a nil event method for matching purposes. Correspondents use the method name proto (or proto_method) to indicate no event method is yet specified.

So, to get back to the example (you're undoubtedly relieved), I've created a named context with two prototypes, in a Bindery created outside of any Swing context. Next, I create two Swing contexts, each using the new Bindery. Then, in each Swing context I create a new binding context that inherits the prototypes from the 'lib' context. I then declare my bindings, omitting those elements that I expect to inherit from a prototype.

You'll notice that in the specification of the prototype for undoable_edit_happened, an attribs parameter is passed to the getter and setter. Attributes may be specified in the blocks of both correspondents and prototypes; anything specified there that isn't a getter, setter, or predicate is an attribute. An attribute is a name:value pair, where the name must be a String (which will be converted to a Symbol), while the value can be pretty much anything the user deems useful. Two attributes, unit and type, have special significance and, as I mentioned, are discussed in a later example.

In this example, the attribute obj is used to pass the text field to the getter and setter defined in the prototype.

The next example, (which I promise will be brief) will demonstrate binding objects in separate applications.
require 'jrbuilder'

bindery = Bindery::Bindery.new

# create a named binding context with some
# useful prototypes
bindery.binding_context('my_swing_lib') {

    undoable_edit_happened(javax.swing.text.Document) {
      getter { |obj,attribs| attribs[:obj].text }
      setter { |obj,value,attribs| attribs[:obj].text = value }
    }
    
    state_changed(javax.swing.JSlider,'value')
}
# create two Swing contexts that share the same bindery
ctx1 = JRBuilder.new_swing_context(bindery)
ctx2 = JRBuilder.new_swing_context(bindery)

ctx1.enter {
  attr_reader :my_frame

  sliders = []
  field1 = nil
  field2 = nil
  
  @my_frame = frame('Binding Demo 2A') { size 200, 400; location 200, 100
    layout :grid,2,1; border :bevel, :RAISED
    on_window_closing { my_frame.dispose }
    menu_bar { 
      menu('File') {  mnemonic :VK_F; background :WHITE
        menu_item('Exit') { mnemonic :VK_X; background :WHITE
          on_click { my_frame.dispose }
        }
      }
    } 
    y_box { border(:titled, 'Horizontal Sliders') { border :etched, :LOWERED }
      y_glue
      4.times {
        sliders << slider(:HORIZONTAL) { value 0 }
        y_glue
      }
    }
    y_box { border(:titled, 'Text Fields') { border :etched, :LOWERED }
      y_glue
      text_area('Type text in one box, and watch it appear in the other') {
        line_wrap true; wrap_style_word true; editable false;
        border :etched, :RAISED; fixed_size 175,40
      }
      y_glue
      field1 = text_field('Enter text here',15) {
        border :bevel, :LOWERED; fixed_size 175,24
        on_focus_gained { field1.select_all }
      }
      y_glue
      field2 = text_field('Enter text here',15) {
        border :bevel, :LOWERED; fixed_size 175,24
        on_focus_gained { |event,obj| obj.select_all }
      }
      y_glue
    }
  }
  # create a binding context that inherits from
  # our lib
  binding_context('my_swing_lib', 'my_ctx_1') {
    bind { sliders.each do |slider| proto(slider); end }
    bind {
      proto(field1.document) { obj field1 }
      proto(field2.document) { obj field2 }
    }
  }
}

ctx2.enter {
  attr_reader :my_frame

  sliders = []
  field1 = nil
  field2 = nil
  
  @my_frame = frame('Binding Demo 2B') { size 200, 400; location 500, 100
    layout :grid,2,1; border :bevel, :RAISED
    on_window_closing { my_frame.dispose }
    menu_bar { 
      menu('File') {  mnemonic :VK_F; background :WHITE
        menu_item('Exit') { mnemonic :VK_X; background :WHITE
          on_click { my_frame.dispose }
        }
      }
    } 
    y_box { border(:titled, 'Horizontal Sliders') { border :etched, :LOWERED }
      y_glue
      4.times {
        sliders << slider(:HORIZONTAL) { value 0 }
        y_glue
      }
    }
    y_box { border(:titled, 'Text Fields') { border :etched, :LOWERED }
      y_glue
      text_area('Type text in one box, and watch it appear in the other') {
        line_wrap true; wrap_style_word true; editable false;
        border :etched, :RAISED; fixed_size 175,40
      }
      y_glue
      field1 = text_field('Enter text here',15) {
        border :bevel, :LOWERED; fixed_size 175,24
        on_focus_gained { field1.select_all }
      }
      y_glue
      field2 = text_field('Enter text here',15) {
        border :bevel, :LOWERED; fixed_size 175,24
        on_focus_gained { |event,obj| obj.select_all }
      }
      y_glue
    }
  }
  # create a binding context that inherits from
  # our lib
  binding_context('my_swing_lib', 'my_ctx_2') {
    bind { sliders.each do |slider| proto(slider); end }
    bind {
      proto(field1.document) { obj field1 }
      proto(field2.document) { obj field2 }
    }
  }
}
ctx1.my_frame.show
ctx2.my_frame.show
Home «  Prev    Contents    Next  » Download