motion_bindable 0.2.5 → 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/Gemfile +1 -0
- data/Gemfile.lock +8 -0
- data/README.md +17 -4
- data/lib/motion_bindable/bindable.rb +3 -0
- data/lib/motion_bindable/strategy.rb +18 -46
- data/lib/motion_bindable/strategy_helpers.rb +72 -0
- data/lib/motion_bindable/version.rb +1 -1
- data/lib/strategies/proc.rb +26 -4
- data/lib/strategies/ui_label.rb +3 -11
- data/lib/strategies/ui_text_field.rb +3 -17
- data/spec/strategy_helpers_spec.rb +57 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28fbdbec6c2d3b77ecad6a2ca7c24b8ea3f87934
|
4
|
+
data.tar.gz: 30d6ce55f370a6a6c39b3cc40ba23d8f2d17480c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4acbbb6b7e780bf08f12cd655513dc289990b81ac7056e492dca53e16175621b0dcbf67510e3ff8f1d8dc465d0d901c0aa85d1dd81f66b79820b308bcab86bd9
|
7
|
+
data.tar.gz: f5bb69ca4a6aebbaad375b4cb2f2ef5ddd81eb3119887dbb7986846322c863e17318c75aa3564a9e86389e971c2602127885d5ca920679df8d5bba4c3ce47f4d
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
+
bubble-wrap (1.5.0)
|
4
5
|
motion-facon (0.5.0.1)
|
6
|
+
motion-require (0.2.0)
|
7
|
+
motion-support (0.2.6)
|
8
|
+
motion-require (>= 0.0.6)
|
9
|
+
motion_model (0.5.0)
|
10
|
+
bubble-wrap (>= 1.3.0)
|
11
|
+
motion-support (>= 0.1.0)
|
5
12
|
rake (10.1.0)
|
6
13
|
webstub (1.0.1)
|
7
14
|
|
@@ -10,5 +17,6 @@ PLATFORMS
|
|
10
17
|
|
11
18
|
DEPENDENCIES
|
12
19
|
motion-facon
|
20
|
+
motion_model
|
13
21
|
rake
|
14
22
|
webstub
|
data/README.md
CHANGED
@@ -5,6 +5,11 @@
|
|
5
5
|
|
6
6
|
A simple two-way data binding library for RubyMotion.
|
7
7
|
|
8
|
+
## NOTICE
|
9
|
+
|
10
|
+
Version `0.3.0` introduces breaking changes and deprecations. If you're upgrading, please check the [release
|
11
|
+
notes](nathankot/motion-bindable/releases/tag/v0.3.0).
|
12
|
+
|
8
13
|
## Installation
|
9
14
|
|
10
15
|
Add this line to your application's Gemfile:
|
@@ -24,7 +29,7 @@ this to your `app_delegate.rb`:
|
|
24
29
|
|
25
30
|
``` ruby
|
26
31
|
def application(application, didFinishLaunchingWithOptions: launch_options)
|
27
|
-
MotionBindable::Strategies.
|
32
|
+
MotionBindable::Strategies.apply
|
28
33
|
true
|
29
34
|
end
|
30
35
|
```
|
@@ -75,6 +80,9 @@ class ItemListViewController
|
|
75
80
|
view.addSubview @address_field
|
76
81
|
|
77
82
|
@item = Item.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def viewWillAppear(animated)
|
78
86
|
@item.bind_attributes({
|
79
87
|
name: @name_field,
|
80
88
|
location: {
|
@@ -82,6 +90,11 @@ class ItemListViewController
|
|
82
90
|
}
|
83
91
|
})
|
84
92
|
end
|
93
|
+
|
94
|
+
# Recommended: Clean everything up when the view leaves
|
95
|
+
def viewWillDisappear(animated)
|
96
|
+
@item.unbind_all
|
97
|
+
end
|
85
98
|
end
|
86
99
|
```
|
87
100
|
|
@@ -101,9 +114,9 @@ The following strategies come with motion-bindable and are setup when
|
|
101
114
|
|
102
115
|
| Name | Object Candidates | Direction |
|
103
116
|
| ----------------------------------------- | ----------------- | ------------------- |
|
104
|
-
| `MotionBindable::Strategies::UITextField` | Any `UITextField` | Bound
|
105
|
-
| `MotionBindable::Strategies::Proc` | Any `Proc` | Bound
|
106
|
-
| `MotionBindable::Strategies::UILabel` | Any `UILabel` | Bound
|
117
|
+
| `MotionBindable::Strategies::UITextField` | Any `UITextField` | Bound <-> Attribute |
|
118
|
+
| `MotionBindable::Strategies::Proc` | Any `Proc` | Bound --> Attribute |
|
119
|
+
| `MotionBindable::Strategies::UILabel` | Any `UILabel` | Bound <-- Attribute |
|
107
120
|
|
108
121
|
## Contributing
|
109
122
|
|
@@ -11,8 +11,11 @@ module MotionBindable
|
|
11
11
|
def bind_attributes(attrs, object = self)
|
12
12
|
attrs.each_pair do |k, v|
|
13
13
|
case v
|
14
|
+
# Recurse if another hash
|
14
15
|
when Hash then bind_attributes(v, object.send(k))
|
16
|
+
# Allow binding multiple bound if an array
|
15
17
|
when Array then v.each { |v| bind strategy_for(v).new(object, k).bind(v) }
|
18
|
+
# Otherwise bind
|
16
19
|
else bind strategy_for(v).new(object, k).bind(v)
|
17
20
|
end
|
18
21
|
end
|
@@ -1,9 +1,5 @@
|
|
1
1
|
module MotionBindable
|
2
|
-
|
3
2
|
class Strategy
|
4
|
-
|
5
|
-
WATCH_TICK = 0.2
|
6
|
-
|
7
3
|
@strategies_map = [{ class: Strategy, candidates: [Object] }]
|
8
4
|
|
9
5
|
def self.register_strategy(strategy, *objects)
|
@@ -25,14 +21,20 @@ module MotionBindable
|
|
25
21
|
self.object = object
|
26
22
|
end
|
27
23
|
|
28
|
-
public # Methods to override
|
24
|
+
public # (Optional) Methods to override
|
29
25
|
|
26
|
+
# def start_observing; end
|
30
27
|
# def start_observing_bound; end
|
31
28
|
# def start_observing_object; end
|
32
|
-
# def refresh_bound; end
|
33
29
|
# def on_object_change; end
|
34
30
|
# def on_bound_change; end
|
35
31
|
|
32
|
+
def bound_value
|
33
|
+
end
|
34
|
+
|
35
|
+
def object_value
|
36
|
+
end
|
37
|
+
|
36
38
|
def bind(bound)
|
37
39
|
self.bound = bound
|
38
40
|
initial_state
|
@@ -41,9 +43,12 @@ module MotionBindable
|
|
41
43
|
end
|
42
44
|
|
43
45
|
def unbind
|
44
|
-
@watching = false
|
45
46
|
end
|
46
47
|
|
48
|
+
# Deprecation support purposes
|
49
|
+
alias_method :refresh_bound, :bound_value
|
50
|
+
alias_method :refresh_object, :object_value
|
51
|
+
|
47
52
|
private # Methods to leave alone
|
48
53
|
|
49
54
|
def attribute
|
@@ -55,51 +60,18 @@ module MotionBindable
|
|
55
60
|
end
|
56
61
|
|
57
62
|
def initial_state
|
63
|
+
# We try to find an existing value and fill it up
|
58
64
|
if attribute.nil? && respond_to?(:on_bound_change)
|
59
|
-
|
60
|
-
else on_bound_change
|
61
|
-
end
|
65
|
+
on_bound_change(bound_value)
|
62
66
|
elsif respond_to?(:on_object_change)
|
63
|
-
|
64
|
-
else on_object_change(attribute)
|
65
|
-
end
|
67
|
+
on_object_change(object_value)
|
66
68
|
end
|
67
69
|
end
|
68
70
|
|
69
71
|
def start_listen
|
70
|
-
|
71
|
-
|
72
|
-
if respond_to?(:
|
73
|
-
elsif respond_to?(:refresh_bound) && respond_to?(:on_bound_change)
|
74
|
-
sides << :bound
|
75
|
-
end
|
76
|
-
if respond_to?(:start_observing_object) then start_observing_object
|
77
|
-
elsif respond_to?(:refresh_object) && respond_to?(:on_object_change)
|
78
|
-
sides << :object
|
79
|
-
end
|
80
|
-
|
81
|
-
@watching = true
|
82
|
-
watch(sides)
|
83
|
-
end
|
84
|
-
|
85
|
-
def watch(sides)
|
86
|
-
dispatcher.async do
|
87
|
-
if @watching
|
88
|
-
bound_result = refresh_bound if sides.include?(:bound)
|
89
|
-
object_result = refresh_object if sides.include?(:object)
|
90
|
-
on_bound_change(bound_result) if bound_result
|
91
|
-
on_object_change(object_result) if object_result
|
92
|
-
dispatcher.after(WATCH_TICK) { watch(sides) }
|
93
|
-
end
|
94
|
-
end
|
72
|
+
start_observing if respond_to?(:start_observing)
|
73
|
+
start_observing_bound if respond_to?(:start_observing_bound)
|
74
|
+
start_observing_object if respond_to?(:start_observing_object)
|
95
75
|
end
|
96
|
-
|
97
|
-
def dispatcher
|
98
|
-
@dispatcher ||= begin
|
99
|
-
Dispatch::Queue.concurrent 'motion.bindable'
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
76
|
end
|
104
|
-
|
105
77
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module MotionBindable::StrategyHelpers
|
2
|
+
# A generic method for observing an object's attribute that is
|
3
|
+
# being bound to.
|
4
|
+
#
|
5
|
+
# Where strategies deal with observing a large variety of possible bound
|
6
|
+
# objects, this method will only have to worry about a small set of possible
|
7
|
+
# objects being binded to, KVO should actually cover most cases.
|
8
|
+
def observe_object(&block)
|
9
|
+
@_observe_object_cb = block
|
10
|
+
|
11
|
+
if defined?(MotionModel::Model) && object.is_a?(MotionModel::Model)
|
12
|
+
@_observe_object_mode = :motion_model
|
13
|
+
start_observing_motion_model
|
14
|
+
else
|
15
|
+
@_observe_object_mode = :kvo
|
16
|
+
start_observing_kvo
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def stop_observe_object
|
21
|
+
case @_observe_object_mode
|
22
|
+
when :kvo then stop_observing_kvo
|
23
|
+
end
|
24
|
+
|
25
|
+
@_observe_object_mode,
|
26
|
+
@_observe_object_block = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# NSKeyValueObserving Protocol
|
30
|
+
|
31
|
+
def observeValueForKeyPath(_, ofObject: _, change: change, context: _)
|
32
|
+
@_observe_object_cb.call(change[:old], change[:new])
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def start_observing_motion_model
|
38
|
+
@_observe_object_old_value = object.send(attr_name)
|
39
|
+
NSNotificationCenter.defaultCenter.addObserverForName(
|
40
|
+
'MotionModelDataDidChangeNotification',
|
41
|
+
object: object,
|
42
|
+
queue: nil,
|
43
|
+
usingBlock: proc do |notification|
|
44
|
+
new = notification.object.send(attr_name)
|
45
|
+
@_observe_object_cb.call(
|
46
|
+
@_observe_object_old_value,
|
47
|
+
new
|
48
|
+
) if @_observe_object_cb
|
49
|
+
@_observe_object_old_value = new
|
50
|
+
end
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_observing_kvo
|
55
|
+
object.addObserver(
|
56
|
+
self,
|
57
|
+
forKeyPath: attr_name,
|
58
|
+
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,
|
59
|
+
context: nil
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop_observing_kvo
|
64
|
+
object.removeObserver(
|
65
|
+
self,
|
66
|
+
forKeyPath: attr_name
|
67
|
+
)
|
68
|
+
rescue
|
69
|
+
# See: http://nshipster.com/key-value-observing/
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
data/lib/strategies/proc.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module MotionBindable::Strategies
|
2
|
-
|
3
2
|
class Proc < ::MotionBindable::Strategy
|
3
|
+
WATCH_TICK = 0.2
|
4
4
|
|
5
|
-
def
|
5
|
+
def bound_value
|
6
6
|
bound.call
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def object_value
|
10
10
|
attribute
|
11
11
|
end
|
12
12
|
|
@@ -19,6 +19,28 @@ module MotionBindable::Strategies
|
|
19
19
|
super
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
def start_observing
|
23
|
+
@watching = true
|
24
|
+
watch
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
23
28
|
|
29
|
+
def watch
|
30
|
+
dispatcher.async do
|
31
|
+
if @watching
|
32
|
+
new = bound_value
|
33
|
+
on_bound_change(new) if new != @old_bound_value
|
34
|
+
@old_bound_value = nil
|
35
|
+
dispatcher.after(WATCH_TICK) { watch }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def dispatcher
|
41
|
+
@dispatcher ||= begin
|
42
|
+
Dispatch::Queue.concurrent 'org.motion.bindable'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
24
46
|
end
|
data/lib/strategies/ui_label.rb
CHANGED
@@ -1,15 +1,9 @@
|
|
1
1
|
module MotionBindable::Strategies
|
2
|
-
|
3
2
|
class UILabel < ::MotionBindable::Strategy
|
3
|
+
include MotionBindable::StrategyHelpers
|
4
4
|
|
5
5
|
def start_observing_object
|
6
|
-
|
7
|
-
object.addObserver(
|
8
|
-
self,
|
9
|
-
forKeyPath: attr_name,
|
10
|
-
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,
|
11
|
-
context: nil
|
12
|
-
)
|
6
|
+
observe_object { |_, new| on_object_change(new) }
|
13
7
|
end
|
14
8
|
|
15
9
|
def on_object_change(new = nil)
|
@@ -22,10 +16,8 @@ module MotionBindable::Strategies
|
|
22
16
|
end
|
23
17
|
|
24
18
|
def unbind
|
25
|
-
|
19
|
+
stop_observe_object
|
26
20
|
super
|
27
21
|
end
|
28
|
-
|
29
22
|
end
|
30
|
-
|
31
23
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module MotionBindable::Strategies
|
2
|
-
|
3
2
|
class UITextField < ::MotionBindable::Strategy
|
3
|
+
include MotionBindable::StrategyHelpers
|
4
4
|
|
5
5
|
def start_observing_bound
|
6
6
|
@bound_observer = NSNotificationCenter.defaultCenter.addObserverForName(
|
@@ -12,13 +12,7 @@ module MotionBindable::Strategies
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def start_observing_object
|
15
|
-
|
16
|
-
object.addObserver(
|
17
|
-
self,
|
18
|
-
forKeyPath: @attr_name,
|
19
|
-
options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld,
|
20
|
-
context: nil
|
21
|
-
)
|
15
|
+
observe_object { |_, new| on_object_change(new) }
|
22
16
|
end
|
23
17
|
|
24
18
|
def on_bound_change(new = nil)
|
@@ -31,16 +25,8 @@ module MotionBindable::Strategies
|
|
31
25
|
|
32
26
|
def unbind
|
33
27
|
NSNotificationCenter.defaultCenter.removeObserver(@bound_observer)
|
34
|
-
|
28
|
+
stop_observe_object
|
35
29
|
super
|
36
30
|
end
|
37
|
-
|
38
|
-
# NSKeyValueObserving Protocol
|
39
|
-
|
40
|
-
def observeValueForKeyPath(_, ofObject: _, change: _, context: _)
|
41
|
-
on_object_change
|
42
|
-
end
|
43
|
-
|
44
31
|
end
|
45
|
-
|
46
32
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
describe 'MotionBindable::StrategyHelpers' do
|
2
|
+
class TestObject
|
3
|
+
attr_accessor :prop
|
4
|
+
end
|
5
|
+
|
6
|
+
class TestModel
|
7
|
+
include MotionModel::Model
|
8
|
+
include MotionModel::ArrayModelAdapter
|
9
|
+
columns :prop
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestStrategy < MotionBindable::Strategy
|
13
|
+
include MotionBindable::StrategyHelpers
|
14
|
+
|
15
|
+
def observe(&block)
|
16
|
+
observe_object(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#observe_object' do
|
21
|
+
describe 'normal object' do
|
22
|
+
before do
|
23
|
+
@object = TestObject.new
|
24
|
+
@response = nil
|
25
|
+
@strategy = TestStrategy.new(@object, :prop)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should call the cb for changes' do
|
29
|
+
@strategy.observe { |old, new| @response = new }
|
30
|
+
@object.prop = 'updated'
|
31
|
+
wait(0.5) do
|
32
|
+
@response.should.equal 'updated'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'MotionModel' do
|
38
|
+
before do
|
39
|
+
@object = TestModel.new
|
40
|
+
@response = nil
|
41
|
+
@strategy = TestStrategy.new(@object, :prop)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should call the cb for changes' do
|
45
|
+
@strategy.observe { |old, new| @response = new }
|
46
|
+
@object.prop = 'updated'
|
47
|
+
@object.save
|
48
|
+
wait(0.5) { @response.should.equal 'updated' }
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not turn the model.class into a KVO thingy' do
|
52
|
+
@strategy.observe { |old, new| @response = new }
|
53
|
+
@object.is_a?(MotionModel::Model).should.equal true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_bindable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Kot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple data binding library for RubyMotion.
|
14
14
|
email:
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/motion_bindable.rb
|
30
30
|
- lib/motion_bindable/bindable.rb
|
31
31
|
- lib/motion_bindable/strategy.rb
|
32
|
+
- lib/motion_bindable/strategy_helpers.rb
|
32
33
|
- lib/motion_bindable/version.rb
|
33
34
|
- lib/strategies/proc.rb
|
34
35
|
- lib/strategies/strategies.rb
|
@@ -45,6 +46,7 @@ files:
|
|
45
46
|
- spec/strategies/ui_label_spec.rb
|
46
47
|
- spec/strategies/ui_label_x_ui_text_field_spec.rb
|
47
48
|
- spec/strategies/ui_text_field_spec.rb
|
49
|
+
- spec/strategy_helpers_spec.rb
|
48
50
|
homepage: https://github.com/nathankot/motion-bindable
|
49
51
|
licenses:
|
50
52
|
- MIT
|
@@ -79,3 +81,4 @@ test_files:
|
|
79
81
|
- spec/strategies/ui_label_spec.rb
|
80
82
|
- spec/strategies/ui_label_x_ui_text_field_spec.rb
|
81
83
|
- spec/strategies/ui_text_field_spec.rb
|
84
|
+
- spec/strategy_helpers_spec.rb
|