motion_bindable 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8fecf48e97819f07047c4d906ac09db46aa92da
4
- data.tar.gz: 3ab8cce360df5457b93e6d1e4d7a41da5d86b901
3
+ metadata.gz: 4a89a39aab018219f267f3eff32cde82ac521544
4
+ data.tar.gz: 1447ebff62c07174943bb0cb3bf30c48794b8267
5
5
  SHA512:
6
- metadata.gz: 2b929c021c2f8b99a00867c8da1b9e976dd4d29e7d6b5b185d30e3ebe1d2da72b25c5d28e55c585f1e7ea6d49f9de1248f855d0b91d9b36cbd4a71a04a614af5
7
- data.tar.gz: ffdfe46d6554d279a2f7e79002e1e45384a7f6077286192fa4ed8e1bec30bcba688edf93f827c2bf4cad300fa76002b29bb55b312aef1103a0e53048a232ad73
6
+ metadata.gz: 3bccfc36a62b72c7fe972263f465ea6b14985a215077f51018ffdc1d99e336097f5e0d21b126ccdd276d7862ce154d3c57c1aaeb43e21e86d4e461e27baa4b72
7
+ data.tar.gz: 256257f81086382161edc6b8462d4129946628cd0d801128be7b26f2802f92c098e926c4aa6e29ef36c65db67e8ba6e47c8a12ea4130dc0b615989777598a59f
data/Gemfile CHANGED
@@ -3,5 +3,4 @@ source 'https://rubygems.org'
3
3
  gem 'rake'
4
4
  # Add your dependencies here:
5
5
  gem 'webstub'
6
- gem 'motion-stump'
7
- gem 'bubble-wrap', '~> 1.4.0'
6
+ gem 'motion-facon'
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- bubble-wrap (1.4.0)
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
- bubble-wrap (~> 1.4.0)
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
- which comes with MotionBindable. Take a look in
97
- `lib/motion_bindable/strategies` for the available defaults. You can implement
98
- your own strategies by extending `MotionBindable::Strategy` like so:
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 | Frequency |
121
- | ----------------------------------------- | ----------------- | --------- | ---------- |
122
- | `MotionBindable::Strategies::UITextField` | Any `UITextField` | Two-way | On Change |
123
- | `MotionBindable::Strategies::Proc` | Any `Proc` | One-way | On Refresh |
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
 
@@ -34,11 +34,6 @@ module MotionBindable
34
34
  @bindings = []
35
35
  end
36
36
 
37
- def refresh
38
- @bindings.each { |b| b.refresh }
39
- self
40
- end
41
-
42
37
  def strategy_for(reference)
43
38
  Strategy.find_by_reference(reference)
44
39
  end
@@ -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
- on_bind
31
-
37
+ initial_state
38
+ start_listen
32
39
  self
33
40
  end
34
41
 
35
- def unbind
42
+ def refresh_object
43
+ attribute
36
44
  end
37
45
 
38
- # You can either choose to just override `#refresh` for objects that can't
39
- # be bound with callbacks. Or override `#on_bind` for objects that can be
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
@@ -1,3 +1,3 @@
1
1
  module MotionBindable
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -2,14 +2,12 @@ module MotionBindable::Strategies
2
2
 
3
3
  class Proc < ::MotionBindable::Strategy
4
4
 
5
- def refresh
6
- update_attribute
5
+ def refresh_bound
6
+ bound.call
7
7
  end
8
8
 
9
- private
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
- include BW::KVO
6
-
7
- def on_bind
8
- refresh
9
-
10
- # Observe the bound object
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
- observe(object, @attr_name) { |_, _| on_object_change }
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 = bound.text unless bound.text.empty?
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
- # Text field takes precedence
36
- alias_method :refresh, :on_bound_change
38
+ # NSKeyValueObserving Protocol
39
+
40
+ def observeValueForKeyPath(_, ofObject: _, change: _, context: _)
41
+ on_object_change
42
+ end
37
43
 
38
44
  end
39
45
 
@@ -17,6 +17,4 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
-
21
- spec.add_dependency 'bubble-wrap', '~> 1.4.0'
22
20
  end
@@ -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
- FakeStrategy.stub!(:new) { |_, _| @strategy }
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
- @called = false
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.stub!(:bind) { |bound| @bounded = bound }
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
- @called = 0
81
- @strategy1.stub!(:unbind) { @called += 1 }
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
 
@@ -0,0 +1,3 @@
1
+ describe do
2
+ extend Facon::SpecHelpers
3
+ end
@@ -42,10 +42,11 @@ describe 'MotionBindable::Strategies::Proc' do
42
42
  @object.nested.attribute.should.equal 'Testing.'
43
43
  end
44
44
 
45
- it 'can refresh manually' do
46
- @bound.attribute = 'Updated.'
47
- @object.refresh.attribute.should.equal 'Updated.'
48
- @object.refresh.nested.attribute.should.equal 'Updated.'
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
@@ -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
- describe '#refresh' do
53
- it 'should respond' do
54
- @strategy.respond_to?(:refresh).should.equal true
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 = 'test'
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.1.1
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