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