motion_bindable 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52e8be3ba802d2d3a4a0f1bdddbac59a32eea455
4
- data.tar.gz: 6c14152e5fee498b339fe107141b0cf86aa778a3
3
+ metadata.gz: 28fbdbec6c2d3b77ecad6a2ca7c24b8ea3f87934
4
+ data.tar.gz: 30d6ce55f370a6a6c39b3cc40ba23d8f2d17480c
5
5
  SHA512:
6
- metadata.gz: 089f88fb16423348824e9e04d3f5384e7f1cf9ced50c82779cd17cb3d3433783de8a8ec060abe40b4524375e6df3e84aff77da6b2d01db62393ae4f8ddccad89
7
- data.tar.gz: ea79259bdcc11b2d20b75f53a4a11b95733b1691b1aab7ab2e77fb18141fd1b7b576655094befe3c3550f4f1faf6463467676f6270bb2f95e65396b042a3773c
6
+ metadata.gz: 4acbbb6b7e780bf08f12cd655513dc289990b81ac7056e492dca53e16175621b0dcbf67510e3ff8f1d8dc465d0d901c0aa85d1dd81f66b79820b308bcab86bd9
7
+ data.tar.gz: f5bb69ca4a6aebbaad375b4cb2f2ef5ddd81eb3119887dbb7986846322c863e17318c75aa3564a9e86389e971c2602127885d5ca920679df8d5bba4c3ce47f4d
data/Gemfile CHANGED
@@ -2,5 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gem 'rake'
4
4
  # Add your dependencies here:
5
+ gem 'motion_model'
5
6
  gem 'webstub'
6
7
  gem 'motion-facon'
@@ -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.use
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 >-< Attribute |
105
- | `MotionBindable::Strategies::Proc` | Any `Proc` | Bound >-- Attribute |
106
- | `MotionBindable::Strategies::UILabel` | Any `UILabel` | Bound --< Attribute |
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
- if respond_to?(:refresh_bound) then on_bound_change(refresh_bound)
60
- else on_bound_change
61
- end
65
+ on_bound_change(bound_value)
62
66
  elsif respond_to?(:on_object_change)
63
- if respond_to?(:refresh_object) then on_object_change(refresh_object)
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
- sides = []
71
-
72
- if respond_to?(:start_observing_bound) then start_observing_bound
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
@@ -1,3 +1,3 @@
1
1
  module MotionBindable
2
- VERSION = "0.2.5"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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 refresh_bound
5
+ def bound_value
6
6
  bound.call
7
7
  end
8
8
 
9
- def refresh_object
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
- end
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
@@ -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
- # Observe the attribute
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
- object.removeObserver(self, forKeyPath: attr_name)
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
- # Observe the attribute
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
- object.removeObserver(self, forKeyPath: @attr_name)
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.2.5
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-01-19 00:00:00.000000000 Z
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