motion-kit-events 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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