motion_bindable 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -2
- data/Gemfile.lock +2 -4
- data/README.md +8 -34
- data/lib/motion_bindable/bindable.rb +0 -5
- data/lib/motion_bindable/strategy.rb +64 -12
- data/lib/motion_bindable/version.rb +1 -1
- data/lib/strategies/proc.rb +4 -6
- data/lib/strategies/ui_text_field.rb +24 -18
- data/motion_bindable.gemspec +0 -2
- data/spec/bindable_spec.rb +8 -13
- data/spec/helpers/facon.rb +3 -0
- data/spec/proc_strategy_spec.rb +5 -4
- data/spec/strategy_spec.rb +14 -6
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a89a39aab018219f267f3eff32cde82ac521544
|
4
|
+
data.tar.gz: 1447ebff62c07174943bb0cb3bf30c48794b8267
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bccfc36a62b72c7fe972263f465ea6b14985a215077f51018ffdc1d99e336097f5e0d21b126ccdd276d7862ce154d3c57c1aaeb43e21e86d4e461e27baa4b72
|
7
|
+
data.tar.gz: 256257f81086382161edc6b8462d4129946628cd0d801128be7b26f2802f92c098e926c4aa6e29ef36c65db67e8ba6e47c8a12ea4130dc0b615989777598a59f
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
5
|
-
motion-stump (0.3.0)
|
4
|
+
motion-facon (0.5.0.1)
|
6
5
|
rake (10.1.0)
|
7
6
|
webstub (1.0.1)
|
8
7
|
|
@@ -10,7 +9,6 @@ PLATFORMS
|
|
10
9
|
ruby
|
11
10
|
|
12
11
|
DEPENDENCIES
|
13
|
-
|
14
|
-
motion-stump
|
12
|
+
motion-facon
|
15
13
|
rake
|
16
14
|
webstub
|
data/README.md
CHANGED
@@ -79,48 +79,22 @@ end
|
|
79
79
|
|
80
80
|
When `@name_field.text` or `@address_field.text` changes, so will your model!
|
81
81
|
|
82
|
-
### Refresh
|
83
|
-
|
84
|
-
To refresh the values on your bindable object use this:
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
@bindable.refresh
|
88
|
-
```
|
89
|
-
|
90
|
-
Some strategies only make an update when a `#refresh` is called. See the
|
91
|
-
_Frequency_ column in the table below.
|
92
|
-
|
93
82
|
### Custom Strategies
|
94
83
|
|
95
|
-
The above example uses the `MotionBindable::Strategies::UITextField`.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
class CustomBindableStrategy < MotionBindable::Strategy
|
102
|
-
|
103
|
-
def on_bind
|
104
|
-
# This runs once when the object is bound.
|
105
|
-
end
|
106
|
-
|
107
|
-
def refresh
|
108
|
-
# This runs when the object is bound, and each time `@bindable.refresh`
|
109
|
-
# is called.
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
```
|
84
|
+
The above example uses the `MotionBindable::Strategies::UITextField`. which
|
85
|
+
comes with MotionBindable. Take a look in `lib/motion_bindable/strategies` for
|
86
|
+
the available defaults. You can implement your own strategies by extending
|
87
|
+
`MotionBindable::Strategy`. Please use the existing strategies as a guideline.
|
114
88
|
|
115
89
|
### Defaults Strategies
|
116
90
|
|
117
91
|
The following strategies come with motion-bindable and are setup when
|
118
92
|
`MotionBindable::Strategies.use` is called.
|
119
93
|
|
120
|
-
| Name | Object Candidates | Direction |
|
121
|
-
| ----------------------------------------- | ----------------- | --------- |
|
122
|
-
| `MotionBindable::Strategies::UITextField` | Any `UITextField` | Two-way |
|
123
|
-
| `MotionBindable::Strategies::Proc` | Any `Proc` | One-way |
|
94
|
+
| Name | Object Candidates | Direction |
|
95
|
+
| ----------------------------------------- | ----------------- | --------- |
|
96
|
+
| `MotionBindable::Strategies::UITextField` | Any `UITextField` | Two-way |
|
97
|
+
| `MotionBindable::Strategies::Proc` | Any `Proc` | One-way |
|
124
98
|
|
125
99
|
## Contributing
|
126
100
|
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module MotionBindable
|
2
2
|
|
3
|
-
#
|
4
|
-
# Represents a binding strategy. Designed to be as flexible as possible.
|
5
|
-
#
|
6
3
|
class Strategy
|
7
4
|
|
5
|
+
WATCH_TICK = 0.2
|
6
|
+
|
8
7
|
@strategies_map = [{ class: Strategy, candidates: [Object] }]
|
9
8
|
|
10
9
|
def self.register_strategy(strategy, *objects)
|
@@ -25,24 +24,31 @@ module MotionBindable
|
|
25
24
|
self.object = object
|
26
25
|
end
|
27
26
|
|
27
|
+
public # Methods to override
|
28
|
+
|
29
|
+
# def start_observing_bound; end
|
30
|
+
# def start_observing_object; end
|
31
|
+
# def refresh_bound; end
|
32
|
+
# def on_object_change; end
|
33
|
+
# def on_bound_change; end
|
34
|
+
|
28
35
|
def bind(bound)
|
29
36
|
self.bound = bound
|
30
|
-
|
31
|
-
|
37
|
+
initial_state
|
38
|
+
start_listen
|
32
39
|
self
|
33
40
|
end
|
34
41
|
|
35
|
-
def
|
42
|
+
def refresh_object
|
43
|
+
attribute
|
36
44
|
end
|
37
45
|
|
38
|
-
|
39
|
-
|
40
|
-
# bound with a callback.
|
41
|
-
def refresh; end
|
42
|
-
def on_bind
|
43
|
-
refresh
|
46
|
+
def unbind
|
47
|
+
@watch_bound, @watch_object = nil
|
44
48
|
end
|
45
49
|
|
50
|
+
private # Methods to leave alone
|
51
|
+
|
46
52
|
def attribute
|
47
53
|
object.send(@attr_name)
|
48
54
|
end
|
@@ -51,6 +57,52 @@ module MotionBindable
|
|
51
57
|
object.send(:"#{@attr_name.to_s}=", value)
|
52
58
|
end
|
53
59
|
|
60
|
+
def initial_state
|
61
|
+
if attribute.nil?
|
62
|
+
if respond_to?(:refresh_bound) then on_bound_change(refresh_bound)
|
63
|
+
else on_bound_change
|
64
|
+
end if respond_to?(:on_bound_change)
|
65
|
+
else
|
66
|
+
if respond_to?(:refresh_object) then on_object_change(refresh_object)
|
67
|
+
else on_object_change(attribute)
|
68
|
+
end if respond_to?(:on_object_change)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def start_listen
|
73
|
+
if respond_to?(:start_observing_bound) then start_observing_bound
|
74
|
+
elsif respond_to?(:refresh_bound) && respond_to?(:on_bound_change)
|
75
|
+
watch_bound
|
76
|
+
end
|
77
|
+
|
78
|
+
if respond_to?(:start_observing_object) then start_observing_object
|
79
|
+
elsif respond_to?(:refresh_object) && respond_to?(:on_object_change)
|
80
|
+
watch_object
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def watch_bound
|
85
|
+
@watch_bound = dispatcher.async do
|
86
|
+
result = refresh_bound
|
87
|
+
on_bound_change(result) if result
|
88
|
+
dispatcher.after(WATCH_TICK) { watch_bound } unless @watch_bound
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def watch_object
|
93
|
+
@watch_object = dispatcher.async do
|
94
|
+
result = refresh_object
|
95
|
+
on_object_change(result) if result
|
96
|
+
dispatcher.after(WATCH_TICK) { watch_object } unless @watch_object
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def dispatcher
|
101
|
+
@dispatcher ||= begin
|
102
|
+
Dispatch::Queue.concurrent 'motion.bindable'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
54
106
|
end
|
55
107
|
|
56
108
|
end
|
data/lib/strategies/proc.rb
CHANGED
@@ -2,14 +2,12 @@ module MotionBindable::Strategies
|
|
2
2
|
|
3
3
|
class Proc < ::MotionBindable::Strategy
|
4
4
|
|
5
|
-
def
|
6
|
-
|
5
|
+
def refresh_bound
|
6
|
+
bound.call
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
def update_attribute
|
12
|
-
self.attribute = bound.call
|
9
|
+
def on_bound_change(new = nil)
|
10
|
+
self.attribute = new || bound.call
|
13
11
|
end
|
14
12
|
|
15
13
|
end
|
@@ -2,38 +2,44 @@ module MotionBindable::Strategies
|
|
2
2
|
|
3
3
|
class UITextField < ::MotionBindable::Strategy
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
NSNotificationCenter.defaultCenter.addObserver(
|
12
|
-
self,
|
13
|
-
selector: :on_bound_change,
|
14
|
-
name: UITextFieldTextDidChangeNotification,
|
15
|
-
object: bound
|
5
|
+
def start_observing_bound
|
6
|
+
NSNotificationCenter.defaultCenter.addObserverForName(
|
7
|
+
UITextFieldTextDidChangeNotification,
|
8
|
+
object: bound,
|
9
|
+
queue: nil,
|
10
|
+
usingBlock: proc { |_| on_bound_change }
|
16
11
|
)
|
12
|
+
end
|
17
13
|
|
14
|
+
def start_observing_object
|
18
15
|
# Observe the attribute
|
19
|
-
|
16
|
+
object.addObserver(
|
17
|
+
self,
|
18
|
+
forKeyPath: @attr_name,
|
19
|
+
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,
|
20
|
+
context: nil
|
21
|
+
)
|
20
22
|
end
|
21
23
|
|
22
|
-
def on_bound_change
|
23
|
-
self.attribute =
|
24
|
+
def on_bound_change(new = nil)
|
25
|
+
self.attribute = new || bound.text
|
24
26
|
end
|
25
27
|
|
26
|
-
def on_object_change
|
27
|
-
@bound.text = attribute
|
28
|
+
def on_object_change(new = nil)
|
29
|
+
@bound.text = new || attribute
|
28
30
|
end
|
29
31
|
|
30
32
|
def unbind
|
31
33
|
App.notification_center.unobserve(@bound_observer)
|
32
34
|
unobserve(object, @attr_name)
|
35
|
+
super
|
33
36
|
end
|
34
37
|
|
35
|
-
#
|
36
|
-
|
38
|
+
# NSKeyValueObserving Protocol
|
39
|
+
|
40
|
+
def observeValueForKeyPath(_, ofObject: _, change: _, context: _)
|
41
|
+
on_object_change
|
42
|
+
end
|
37
43
|
|
38
44
|
end
|
39
45
|
|
data/motion_bindable.gemspec
CHANGED
data/spec/bindable_spec.rb
CHANGED
@@ -12,9 +12,10 @@ describe 'MotionBindable::Bindable' do
|
|
12
12
|
before do
|
13
13
|
@object = FakeBindable.new
|
14
14
|
@object.nested = FakeBindable.new
|
15
|
-
@object.stub!(:strategy_for) { |_| FakeStrategy }
|
16
15
|
@bound = Object.new
|
17
|
-
|
16
|
+
@object.should.receive(:strategy_for) do
|
17
|
+
mock('FakeStrategy', new: @strategy)
|
18
|
+
end.times(:any)
|
18
19
|
end
|
19
20
|
|
20
21
|
describe '#bind_attributes' do
|
@@ -25,18 +26,14 @@ describe 'MotionBindable::Bindable' do
|
|
25
26
|
end
|
26
27
|
|
27
28
|
it 'accepts an array of objects' do
|
28
|
-
@attributes = []
|
29
|
-
@strategy.stub!(:bind) { |attribute| @attributes << attribute }
|
30
29
|
@bound2 = Object.new
|
30
|
+
@strategy.should.receive(:bind).times(2)
|
31
31
|
@object.bind_attributes(attribute: [@bound, @bound2])
|
32
|
-
@attributes.length.should.equal 2
|
33
32
|
end
|
34
33
|
|
35
34
|
it 'passes the strategy to bind' do
|
36
|
-
@
|
37
|
-
@object.stub!(:bind) { |_| @called = true }
|
35
|
+
@strategy.should.receive(:bind).once
|
38
36
|
@object.bind_attributes({ attribute: @bound })
|
39
|
-
@called.should.equal true
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
@@ -46,7 +43,7 @@ describe 'MotionBindable::Bindable' do
|
|
46
43
|
end
|
47
44
|
|
48
45
|
it 'accepts nested attributes' do
|
49
|
-
@strategy.
|
46
|
+
@strategy.should.receive(:bind) { |bound| @bounded = bound }.times(:any)
|
50
47
|
@object.bind_attributes nested: { attribute: @bound }
|
51
48
|
@bounded.should.equal @bound
|
52
49
|
end
|
@@ -77,11 +74,9 @@ describe 'MotionBindable::Bindable' do
|
|
77
74
|
end
|
78
75
|
|
79
76
|
it 'should send unbind to all strategies' do
|
80
|
-
@
|
81
|
-
@
|
82
|
-
@strategy2.stub!(:unbind) { @called += 1 }
|
77
|
+
@strategy1.should.receive(:unbind).once
|
78
|
+
@strategy2.should.receive(:unbind).once
|
83
79
|
@object.unbind_all
|
84
|
-
@called.should.equal 2
|
85
80
|
end
|
86
81
|
end
|
87
82
|
|
data/spec/proc_strategy_spec.rb
CHANGED
@@ -42,10 +42,11 @@ describe 'MotionBindable::Strategies::Proc' do
|
|
42
42
|
@object.nested.attribute.should.equal 'Testing.'
|
43
43
|
end
|
44
44
|
|
45
|
-
it '
|
46
|
-
@bound.attribute = '
|
47
|
-
|
48
|
-
|
45
|
+
it 'attribute is updated when the bound object is updated' do
|
46
|
+
@bound.attribute = 'updated'
|
47
|
+
wait(0.5) do
|
48
|
+
@object.attribute.should.equal 'updated'
|
49
|
+
end
|
49
50
|
end
|
50
51
|
|
51
52
|
end
|
data/spec/strategy_spec.rb
CHANGED
@@ -32,6 +32,8 @@ describe 'MotionBindable::Strategy' do
|
|
32
32
|
@object = ObjectOne.new
|
33
33
|
@bound = Object.new
|
34
34
|
@strategy = Strategy.new(@object, :attribute)
|
35
|
+
@strategy.stub!(:watch_bound)
|
36
|
+
@strategy.stub!(:watch_object)
|
35
37
|
end
|
36
38
|
|
37
39
|
describe '#bind' do
|
@@ -47,18 +49,24 @@ describe 'MotionBindable::Strategy' do
|
|
47
49
|
it 'should return self' do
|
48
50
|
@strategy.bind(@bound).should.equal @strategy
|
49
51
|
end
|
50
|
-
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
@strategy.
|
53
|
+
it 'should call on_object_change if the attribute is not nil' do
|
54
|
+
@object.attribute = 'Test'
|
55
|
+
@strategy.should.receive(:on_object_change).and_return(true)
|
56
|
+
@strategy.bind(@bound)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should call on_bound_change if the attribute is nil' do
|
60
|
+
@object.attribute = nil
|
61
|
+
@strategy.should.receive(:on_bound_change).and_return(true)
|
62
|
+
@strategy.bind(@bound)
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
58
66
|
describe '#attribute=' do
|
59
67
|
it 'should be a proxy to set the attribute on the bound object' do
|
60
|
-
@strategy.attribute
|
61
|
-
@object.attribute.should.equal 'test'
|
68
|
+
@strategy.send(:attribute=, 'test')
|
69
|
+
@object.send(:attribute).should.equal 'test'
|
62
70
|
end
|
63
71
|
end
|
64
72
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_bindable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Kot
|
@@ -9,21 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2013-12-23 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bubble-wrap
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ~>
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.4.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ~>
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.4.0
|
12
|
+
dependencies: []
|
27
13
|
description: A simple data binding library for RubyMotion.
|
28
14
|
email:
|
29
15
|
- nk@nathankot.com
|
@@ -50,6 +36,7 @@ files:
|
|
50
36
|
- motion_bindable.gemspec
|
51
37
|
- resources/Default-568h@2x.png
|
52
38
|
- spec/bindable_spec.rb
|
39
|
+
- spec/helpers/facon.rb
|
53
40
|
- spec/proc_strategy_spec.rb
|
54
41
|
- spec/strategy_spec.rb
|
55
42
|
- spec/ui_text_field_strategy_spec.rb
|
@@ -79,6 +66,7 @@ specification_version: 4
|
|
79
66
|
summary: Inspired by RivetsJS
|
80
67
|
test_files:
|
81
68
|
- spec/bindable_spec.rb
|
69
|
+
- spec/helpers/facon.rb
|
82
70
|
- spec/proc_strategy_spec.rb
|
83
71
|
- spec/strategy_spec.rb
|
84
72
|
- spec/ui_text_field_strategy_spec.rb
|