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 +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
|