motion-kit-events 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d272150b20b160dfe25edeab5f893ef678a1a2a
4
- data.tar.gz: 6d61019bd8534848a4eca225027ef697532678f8
3
+ metadata.gz: 13f62c76304cbae64f1916709ac22a1ce3555c8b
4
+ data.tar.gz: 0b8af2a3e3749e6f0186f2d3aef818d785e9ce4a
5
5
  SHA512:
6
- metadata.gz: b878d15fec70347b73b7590945db80ee9ba8a538f1c086dfcefee0aeafe6430241e005400779d06dce3671fb5509f72ac7205a60505723267f0146ceeb0a9bf6
7
- data.tar.gz: c5880fa77766864905f33fcaf890bdbbd5e91041432576581ea115a3a49e98b2052ab93395b92df416df4ab10269ba2f9f65839a7a882dd73d49766d0ed094ca
6
+ metadata.gz: 5dcc6e70e96079247e440665f2dd565a9789d75dc93adbb7569e21177d160ac577db7aaf4e9558a4964ddcf143865aebd1eb9fd7ac70a68bf2677a1e3a961f6f
7
+ data.tar.gz: 4ef2b080304f6258bde9f764bec5a145d105870a1fbe3ea71c7a26962e271c8e72080f81d135d221418fc20816950cf8cadcf2f38ed72c6864e8d194105249dd
data/README.md CHANGED
@@ -2,8 +2,10 @@ MotionKit::Events
2
2
  --------
3
3
 
4
4
  In an effort to encourage even MORE separation of concerns in your Controllers
5
- and Layouts, the `motion-kit-events` gem provides a very way to emit generic
6
- events from your layout that you can respond to in your controller.
5
+ and Layouts, the `motion-kit-events` gem provides a way to listen for custom
6
+ events (usually triggered by buttons or other UI events) and respond in your
7
+ controller. This keeps your views from being cluttered with business logic and
8
+ your controllers from being cluttered with view code.
7
9
 
8
10
  ## LoginController
9
11
 
@@ -12,66 +14,39 @@ An example of a UIViewController using MotionKit::Events:
12
14
  ```ruby
13
15
  class LoginController < UIViewController
14
16
 
15
- def loadView
16
- @layout = LoginLayout.new
17
- self.view = @layout.view
17
+ def viewDidLoad
18
+ @layout = LoginLayout.new(root: self.view).build
18
19
 
19
- @layout.on :login do |username, password|
20
- @layout.pause_ui
21
- # send login info to the API (I would recommend using a separate class
22
- # to handle the API conversation, e.g. a LoginStorage class).
23
- storage.login(username, password) do |user, errors|
24
- handle_login(user, errors)
25
- end
26
- end
20
+ @layout.on :login { |username, password| initiate_login(username, password) }
21
+ @layout.on :forgot_password { show_forgot_password }
22
+ @layout.on :help { show_help }
27
23
  end
28
24
 
29
- def storage
30
- @storage ||= LoginStorage.new
25
+ def initiate_login(username, password)
26
+ @layout.pause_ui
27
+ # send login info to the API
28
+ API::Client.login(username, password) do |user, errors|
29
+ handle_login_response(user, errors)
30
+ end
31
31
  end
32
32
 
33
- def handle_login(user, errors)
33
+ def handle_login_response(user, errors)
34
34
  # ...
35
35
  @layout.resume_ui
36
36
  end
37
37
 
38
+ # ...
39
+
38
40
  end
39
41
  ```
40
42
 
41
- Before we go on to show the `LoginLayout`, consider for a second how *easy* it
42
- would be to write specs for a controller like this. We don't need to test the
43
- UI (that will be done in isolation, using the `LoginLayout`) and our controller
44
- doesn't directly send any *requests*, so we can assign it (in our specs) a
45
- `TestLoginLayout` class as the storage, which can imitate the behaviors we are
46
- interested in. We should also use a fake layout class in there, too.
47
-
48
- TL;DR: we can test just the behavior of the controller. When it receives a
49
- `:login` event, it should send a `login` request to its storage, and handle
43
+ Now we can test *just the behavior* of the controller. When it receives a
44
+ `:login` event, it should send a `login` request to its API and handle
50
45
  `user` or `errors`.
51
46
 
52
47
 
53
48
  ## LoginLayout
54
49
 
55
- We send the `:login` event along with the username and password when the user
56
- presses the login button or presses "Return" from the password field. This is
57
- all code that would normally be in the UIViewController. The tight coupling of
58
- the UIViewController and the UI is very common in iOS apps, but there is much to
59
- be gained be decoupling these roles. The Controller can focus on the *movement*
60
- of the user.
61
-
62
- The user starts out in a "logging in" state. Until they submit credentials, the
63
- controller need not be interested in what the UI is doing to accomodate this
64
- procedure. It just cares about when the user is *done*, and it pauses the UI.
65
-
66
- When the login attempt is complete, the controller tells the UI what state to go
67
- in next, either resuming the UI if an error occurred, or just dimissing the
68
- controller and passing along the successful login info.
69
-
70
- If you look at the code provided by MotionKit::Events you'll be happy to see
71
- that it is a *tiny* little gem. But using it to decouple your UI from your
72
- controller can provide a huge long term benefit in terms of keeping your code
73
- maintainable!
74
-
75
50
  ```ruby
76
51
  class LoginLayout < MK::Layout
77
52
 
@@ -85,7 +60,7 @@ class LoginLayout < MK::Layout
85
60
  end
86
61
 
87
62
  add UIButton, :login_button do
88
- on :touch do
63
+ on :touch do # This is Sugarcube
89
64
  trigger_login
90
65
  end
91
66
  end
@@ -107,6 +82,47 @@ class LoginLayout < MK::Layout
107
82
  end
108
83
  ```
109
84
 
85
+ ### Testing
86
+
87
+ The layout can be tested independently of the controller.
88
+
89
+ ```ruby
90
+ describe LoginLayout do
91
+ before do
92
+ @subject = LoginLayout.new(root: UIView.new).build
93
+ end
94
+
95
+ it "triggers :login with username/password when the login button is tapped" do
96
+ @subject.on :login do |user, password|
97
+ user.should == "example"
98
+ password.should == "testing123"
99
+ end
100
+ @subject.get(:username_field).text = "example"
101
+ @subject.get(:password_field).text = "testing123"
102
+ # Simulate tap on button
103
+ @subject.get(:login_button).target.send(@subject.get(:login_button).action)
104
+ end
105
+ end
106
+ ```
107
+
108
+ ### An explanation: State vs. UI/events
109
+
110
+ The Controller focuses on the *movement* of the user and the state;
111
+ the Layout handles displaying the UI state and responding to events.
112
+
113
+ 1. The user starts out in a "logging in" state. The controller doesn't care
114
+ how this is represented -- it just cares about when the user is *done*, and
115
+ then it pauses the UI.
116
+ 2. When the login attempt is complete, the controller tells the UI what state
117
+ to go in next, either resuming the UI if an error occurred, or just dimissing
118
+ the controller and passing along the successful login info.
119
+
120
+ MotionKit::Events is a very lightweight gem. But using it to decouple your UI
121
+ from your controller can provide a huge long term benefit in terms of keeping
122
+ your code maintainable!
123
+
124
+ ### Sample app
125
+
110
126
  The sample app (most of the code is in [app/ios/login/][login]) includes a working
111
127
  version of this example.
112
128
 
@@ -21,6 +21,19 @@ module MotionKit
21
21
  end
22
22
  end
23
23
 
24
+ # Removes *all* event handlers for the specified event
25
+ def off(*args)
26
+ if @context || @assign_root
27
+ apply_with_target(:off, *args)
28
+ else
29
+ event = args.first
30
+ unless event
31
+ raise ArgumentError.new('`event` is a required argument to Layout#off')
32
+ end
33
+ motion_kit_event_handlers[event] = nil
34
+ end
35
+ end
36
+
24
37
  def trigger(*args, &handler)
25
38
  if @context
26
39
  apply(:trigger, *args, &handler)
@@ -1,5 +1,5 @@
1
1
  module MotionKit
2
2
  module Events
3
- VERSION = '0.2.2'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -13,4 +13,15 @@ describe 'MotionKit::Events' do
13
13
  controller.success.should == true
14
14
  end
15
15
 
16
+ context 'when the event is removed' do
17
+
18
+ it 'should not respond to the test button' do
19
+ controller.remove_events
20
+ controller.success.should.not == true
21
+ controller.test_button.trigger(:touch)
22
+ controller.success.should.not == true
23
+ end
24
+
25
+ end
26
+
16
27
  end
@@ -20,4 +20,17 @@ describe 'MotionKit::Events' do
20
20
  @controller.success.should == true
21
21
  end
22
22
 
23
+ context 'when the event is removed' do
24
+
25
+ it 'should not respond to the test button' do
26
+ @controller.remove_events
27
+ @controller.success.should.not == true
28
+ target = @controller.test_button.target
29
+ action = @controller.test_button.action
30
+ target.send(action)
31
+ @controller.success.should.not == true
32
+ end
33
+
34
+ end
35
+
23
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-kit-events
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Colin T.A. Gray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-31 00:00:00.000000000 Z
11
+ date: 2014-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: motion-kit