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 +4 -4
- data/README.md +62 -46
- data/lib/motion-kit-events/layout.rb +13 -0
- data/lib/motion-kit-events/version.rb +1 -1
- data/spec/ios/events_spec.rb +11 -0
- data/spec/osx/events_spec.rb +13 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13f62c76304cbae64f1916709ac22a1ce3555c8b
|
4
|
+
data.tar.gz: 0b8af2a3e3749e6f0186f2d3aef818d785e9ce4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
6
|
-
events
|
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
|
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
|
20
|
-
|
21
|
-
|
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
|
30
|
-
@
|
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
|
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
|
-
|
42
|
-
|
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)
|
data/spec/ios/events_spec.rb
CHANGED
@@ -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
|
data/spec/osx/events_spec.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2014-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: motion-kit
|