motion-kit / motion-kit-events

MotionKit::Events provides event hooks to cleanly separate your views, controllers, and event handling

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MotionKit::Events

In an effort to encourage even MORE separation of concerns in your Controllers and Layouts, the motion-kit-events gem provides a way to listen for custom events (usually triggered by buttons or other UI events) and respond in your controller. This keeps your views from being cluttered with business logic and your controllers from being cluttered with view code.

LoginController

An example of a UIViewController using MotionKit::Events:

class LoginController < UIViewController

  def viewDidLoad
    @layout = LoginLayout.new(root: self.view).build

    @layout.on :login { |username, password| initiate_login(username, password) }
    @layout.on :forgot_password { show_forgot_password }
    @layout.on :help { show_help }
  end

  def initiate_login(username, password)
    @layout.pause_ui
    # send login info to the API
    API::Client.login(username, password) do |user, errors|
      handle_login_response(user, errors)
    end
  end

  def handle_login_response(user, errors)
    # ...
    @layout.resume_ui
  end

  # ...

end

Now we can test just the behavior of the controller. When it receives a :login event, it should send a login request to its API and handle user or errors.

LoginLayout

class LoginLayout < MK::Layout

  def layout
    add UITextField, :username_field do
      delegate self
    end

    add UITextField, :password_field do
      delegate self
    end

    add UIButton, :login_button do
      on :touch do # This is Sugarcube
        trigger_login
      end
    end
  end

  def trigger_login
    # send the username and password to our controller
    trigger :login, get(:username_field).text.to_s, get(:password_field).text.to_s
  end

  def textFieldShouldReturn(field)
    if field == get(:password_field)
      trigger_login
    else
      get(:password_field).becomeFirstResponder
    end
  end

end

Testing

The layout can be tested independently of the controller.

describe LoginLayout do
  before do
    @subject = LoginLayout.new(root: UIView.new).build
  end

  it "triggers :login with username/password when the login button is tapped" do
    @subject.on :login do |user, password|
      user.should == "example"
      password.should == "testing123"
    end
    @subject.get(:username_field).text = "example"
    @subject.get(:password_field).text = "testing123"
    # Simulate tap on button
    @subject.get(:login_button).target.send(@subject.get(:login_button).action)
  end
end

An explanation: State vs. UI/events

The Controller focuses on the movement of the user and the state; the Layout handles displaying the UI state and responding to events.

  1. The user starts out in a "logging in" state. The controller doesn't care how this is represented -- it just cares about when the user is done, and then it pauses the UI.
  2. When the login attempt is complete, the controller tells the UI what state to go in next, either resuming the UI if an error occurred, or just dimissing the controller and passing along the successful login info.

MotionKit::Events is a very lightweight gem. But using it to decouple your UI from your controller can provide a huge long term benefit in terms of keeping your code maintainable!

Sample app

The sample app (most of the code is in app/ios/login/) includes a working version of this example.

Note

The example and specs all require SugarCube to run; this is just because I wanted to have the specs make sure that the on method (used in so many gems) behaves the way you would expect it to.

About

MotionKit::Events provides event hooks to cleanly separate your views, controllers, and event handling


Languages

Language:Ruby 100.0%