Skip to content

Commit

Permalink
Fixing issue #21, memoization of view_state
Browse files Browse the repository at this point in the history
  • Loading branch information
dkoontz committed Jun 11, 2008
1 parent 0fa03e1 commit 3fbf5a4
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 44 deletions.
100 changes: 70 additions & 30 deletions lib/monkeybars/controller.rb
Expand Up @@ -52,8 +52,9 @@ module Monkeybars
# def button_action_performed
# repaint_while do
# sleep(20) # the gui is still responsive while we're in here sleeping
# some_component.text = "done sleeping!"
# end
# model.text = "done sleeping!"
# update_view
# end
# ==========
#
Expand All @@ -71,7 +72,7 @@ module Monkeybars
# close_action :exit
#
# def ok_button_mouse_released
# puts "The user's name is: #{view_state.user_name}"
# puts "The user's name is: #{view_state.model.user_name}"
# end
# end
#
Expand Down Expand Up @@ -297,6 +298,7 @@ def initialize
@__registered_handlers = Hash.new{|h,k| h[k] = []}
@__event_handler_procs = Hash.new{|h,k| h[k] = []}
@__nested_controllers = {}
@__view_state = nil

unless self.class.handlers.empty?
if @__view.nil?
Expand Down Expand Up @@ -488,6 +490,7 @@ def handle_event(component_name, event_name, event) #:nodoc:
end

callbacks.each{ |proc| 0 == proc.arity ? proc.call : proc.call(event) }
clear_view_state
end

private
Expand All @@ -505,49 +508,60 @@ def transfer # :doc:
@__transfer
end

# Returns an array containing a model and a transfer hash of the view's current
# contents as defined by the view's mappings. This is for use in event handlers.
# The contents of the model and transfer are *not* the same as the contents of
# the model and transfer in the controller, if you wish to propogate the values
# from the temporary model into the actual model, you must do this yourself. A
# helper method #update_model is provided to make this easier. In an
# event handler this method is thread safe as Swing is single threaded and blocks
# any modification to the GUI while the handler is being proccessed. Each time
# this method is called the view mappings are called, so if you want to use
# several model or transfer properties you should save off the return values
# into a local variable.
#
# Returns a ViewState object which contains a model and a transfer hash of the
# view's current contents as defined by the view's mappings. This is for use in
# event handlers. The contents of the model and transfer are *not* the same as
# the contents of the model and transfer in the controller, if you wish to
# propogate the values from the view state's model into the actual model, you
# must do this yourself. A helper method #update_model is provided to make this
# easier. In an event handler this method is thread safe as Swing is single threaded
# and blocks any modification to the GUI while the handler is being proccessed.
#
# The view state object has two properties, model and transfer.
#
# def ok_button_action_performed
# view_model, view_transfer = view_state
# # do various things with view_model or view_transfer here
# if view_state.transfer[:foo] == :bar
# model.baz = view_state.model.baz
# end
# end
#
#
# Any subsequent call to view_state will return the same object, that is, this
# method is memoized internally. At the end of each event (after all handlers
# have been called) the memoized view state is cleared. If you call view_state
# outside of an event handler it is important that you clear the view state
# yourself by calling clear_view_state.
def view_state # :doc:
return @__view_state unless @__view_state.nil?
unless self.class.model_class.nil?
model = create_new_model
transfer = {}
@__view.write_state(model, transfer)
return model, transfer
@__view_state = ViewState.new(model, transfer)
else
nil
end
end

# Resets memoized view_state value. This is called automatically after each
# event so it would only need to be called if view_state is used outside
# of an event handler.
def clear_view_state # :doc:
@__view_state = nil
end

# This method is almost always used from within an event handler to propogate
# the view_state to the model. Updates the model from the source provided
# (typically from view_state). The list of properties defines what is modified
# on the model.
#
# def ok_button_action_perfomed(event)
# view_model, view_transfer = view_state
# update_model(view_model, :user_name, :password)
# def ok_button_action_perfomed
# update_model(view_state.model, :user_name, :password)
# end
#
# This would have the same effect as:
#
# view_model, view_transfer = view_state
# model.user_name = view_model.user_name
# model.password = view_model.password
# model.user_name = view_state.model.user_name
# model.password = view_state.model.password
def update_model(source, *properties) # :doc:
update_provided_model(source, @__model, *properties)
end
Expand All @@ -557,16 +571,14 @@ def update_model(source, *properties) # :doc:
# the properties to be propogated to. This is useful if you have a composite
# model or need to updated other controllers.
#
# def ok_button_action_perfomed(event)
# view_model, view_transfer = view_state
# update_provided_model(view_model, model.user, :user_name, :password)
# def ok_button_action_perfomed
# update_provided_model(view_state.model, model.user, :user_name, :password)
# end
#
# This would have the same effect as:
#
# view_model, view_transfer = view_state
# model.user.user_name = view_model.user_name
# model.user.password = view_model.password
# model.user.user_name = view_state.model.user_name
# model.user.password = view_state.model.password
def update_provided_model(source, destination, *properties) # :doc:
properties.each do |property|
destination.send("#{property}=", source.send(property))
Expand Down Expand Up @@ -687,6 +699,34 @@ def built_in_close_method(event)

class InvalidCloseAction < Exception; end

# A formal object representing the view's model and transfer state. This used to be
# an array so we emulate the array methods that are in common usage.
class ViewState
attr_reader :model, :transfer

def initialize(model, transfer)
@model, @transfer = model, transfer
end

def [](index)
case index
when 0
@model
when 1
@transfer
else
nil
end
end

def first
@model
end

def last
@transfer
end
end
end


56 changes: 42 additions & 14 deletions spec/unit/controller_spec.rb
Expand Up @@ -145,13 +145,13 @@ def method_missing(method, *args, &block)

describe Monkeybars::Controller, "#view_state" do

it "returns the view's state as a model and a transfer hash" do
class ViewStateModel
it "returns the view's state as a ViewState object" do
class ReturnsViewStateModel
attr_accessor :text
def initialize; @text= ""; end
end

class ViewStateView < Monkeybars::View
class ReturnsViewStateView < Monkeybars::View
set_java_class "org.monkeybars.TestView"
map :view => "testTextField.text", :model => :text
map :view => "testTextField.columns", :transfer => :text_columns
Expand All @@ -161,23 +161,50 @@ def load
end
end

class ViewStateController < Monkeybars::Controller
set_view "ViewStateView"
set_model "ViewStateModel"
class ReturnsViewStateController < Monkeybars::Controller
set_view "ReturnsViewStateView"
set_model "ReturnsViewStateModel"
end

t = ViewStateController.instance
t = ReturnsViewStateController.instance

view_state = t.send(:view_state)
view_state.class.should == Monkeybars::ViewState
view_state.model.text.should == "A text field"
view_state.transfer[:text_columns].should == 10

t.close
end

it "memoizes the value of the view state" do
class MemoizesViewStateModel
attr_accessor :text
def initialize; @text= ""; end
end

class MemoizesViewStateView < Monkeybars::View
set_java_class "org.monkeybars.TestView"
map :view => "testTextField.text", :model => :text
map :view => "testTextField.columns", :transfer => :text_columns

def load
testTextField.columns = 10
end
end

class MemoizesViewStateController < Monkeybars::Controller
set_view "MemoizesViewStateView"
set_model "MemoizesViewStateModel"
end

t = MemoizesViewStateController.instance
view_state = t.send(:view_state)
view_state.should be_equal(t.send(:view_state))

view_state, transfer = t.send(:view_state)
view_state.text.should == "A text field"
transfer[:text_columns].should == 10
t.instance_variable_get("@__view").testTextField.text = "test data"
t.instance_variable_get("@__view").testTextField.columns = 20
view_state, transfer = t.send(:view_state)
view_state.text.should == "test data"
transfer[:text_columns].should == 20

t.close
view_state.should be_equal(t.send(:view_state))
end
end

Expand Down Expand Up @@ -266,6 +293,7 @@ def initialize
end

c = EmptyController.instance
raise "This spec is not implemented properly"
end

it "does not add an implicit handler if an explict handler of that type was already added for that component"
Expand Down

0 comments on commit 3fbf5a4

Please sign in to comment.