diff --git a/lib/monkeybars/controller.rb b/lib/monkeybars/controller.rb index bb1a72e..85b47b3 100644 --- a/lib/monkeybars/controller.rb +++ b/lib/monkeybars/controller.rb @@ -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 # ========== # @@ -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 # @@ -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? @@ -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 @@ -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 @@ -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)) @@ -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 diff --git a/spec/unit/controller_spec.rb b/spec/unit/controller_spec.rb index ae28aba..faac69b 100644 --- a/spec/unit/controller_spec.rb +++ b/spec/unit/controller_spec.rb @@ -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 @@ -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 @@ -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"