glimmer 0.9.1 → 0.9.2

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
  SHA256:
3
- metadata.gz: 9a0e5867d64de61a6733bb4946eb3b366ae854a72bf99cdc048707c00cd9c276
4
- data.tar.gz: b3735c7bff7ee61219f822378b59077e00f9957ed1031b09fd1e3f461eb90868
3
+ metadata.gz: 2d387850f085c102bcaf51d9f9f1250cf02124372beed08612bc2dde2766f387
4
+ data.tar.gz: 828032e8a3d6779e47d63c054cf15211ec8b989a58af47ba1aedfed36e696b67
5
5
  SHA512:
6
- metadata.gz: b5e181bff40082c4620fc6ad1a306af61c7d59e24a30c13c6038606f008d9480da5532d07a704d0c39563318c23935efb909f958f596b7c49c5f2a6843ace165
7
- data.tar.gz: 0562d569471bb792943cac85d0c3a79462573074e1ad634fb0aac1f1e9b53bbe9121d8534f9ea1ce94f5b30b0c47947618e73938e38d72695cecbfba48684210
6
+ metadata.gz: 5e7d349b48f19af94bab354eceef736e6c58e9d4e608582fb12c1677c453dfff5ee02c74a968133e985239a18f0058f66b35a4199fc4c3ac63b29cb0d80a7f91
7
+ data.tar.gz: 2d4f7f4600517ca0f6e9f08946b37f7aaff9740dc3f49b1844147d6635cd4cce053af200e1c58c0a5eac8523d4cce2f9601ead454665049919954498d697c32e
data/README.md CHANGED
@@ -1,12 +1,19 @@
1
- # Glimmer - Ruby Desktop Development GUI Library
1
+ # <img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=105 /> Glimmer - Ruby Desktop Development GUI Library
2
2
  [![Gem Version](https://badge.fury.io/rb/glimmer.svg)](http://badge.fury.io/rb/glimmer)
3
- [![Travis CI](https://travis-ci.com/AndyObtiva/glimmer.svg?branch=master)](https://travis-ci.com/github/AndyObtiva/glimmer)
3
+ [![Travis CI](https://travis-ci.com/AndyObtiva/glimmer.svg?branch=master)](https://travis-ci.com/github/AndyObtiva/glimmer)
4
+ [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
5
 
5
6
  Glimmer is a native-GUI cross-platform desktop development library written in Ruby. Glimmer's main innovation is a JRuby DSL that enables productive and efficient authoring of desktop application user-interfaces while relying on the robust Eclipse SWT library. Glimmer additionally innovates by having built-in data-binding support to greatly facilitate synchronizing the GUI with domain models. As a result, that achieves true decoupling of object oriented components, enabling developers to solve business problems without worrying about GUI concerns, or alternatively drive development GUI-first, and then write clean business models test-first afterwards.
6
7
 
7
8
  [<img src="https://covers.oreillystatic.com/images/9780596519650/lrg.jpg" width=105 /><br />
8
9
  Featured in<br />JRuby Cookbook](http://shop.oreilly.com/product/9780596519650.do)
9
10
 
11
+ Glimmer DSL gems:
12
+ - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (Desktop GUI)
13
+ - [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Web GUI Adapter for Desktop Apps)
14
+ - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
15
+ - [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS (Cascading Style Sheets)
16
+
10
17
  ## Examples
11
18
 
12
19
  ### Hello, World!
@@ -199,13 +206,13 @@ Glimmer might still work on lower versions of Java, JRuby and SWT, but there are
199
206
 
200
207
  ## Setup
201
208
 
202
- Please follow these instructions to make the `glimmer` command available on your system.
209
+ Please follow these instructions to make the `glimmer` command available on your system via the [`glimmer-dsl-swt`](https://github.com/AndyObtiva/glimmer-dsl-swt) gem.
203
210
 
204
211
  ### Option 1: Direct Install
205
212
 
206
213
  Run this command to install directly:
207
214
  ```
208
- jgem install glimmer-dsl-swt -v 0.1.0
215
+ jgem install glimmer-dsl-swt -v 0.1.2
209
216
  ```
210
217
 
211
218
  `jgem` is JRuby's version of `gem` command.
@@ -216,7 +223,7 @@ Otherwise, you may also run `jruby -S gem install ...`
216
223
 
217
224
  Add the following to `Gemfile`:
218
225
  ```
219
- gem 'glimmer-dsl-swt', '~> 0.1.0'
226
+ gem 'glimmer-dsl-swt', '~> 0.1.2'
220
227
  ```
221
228
 
222
229
  And, then run:
@@ -224,7 +231,7 @@ And, then run:
224
231
  jruby -S bundle install
225
232
  ```
226
233
 
227
- You may learn more about other Glimmer related gems at [Multi-DSL Support](#multi-dsl-support)
234
+ You may learn more about other Glimmer related gems ([`glimmer-dsl-opal`](https://github.com/AndyObtiva/glimmer-dsl-opal), [`glimmer-dsl-xml`](https://github.com/AndyObtiva/glimmer-dsl-xml), and [`glimmer-dsl-css`](https://github.com/AndyObtiva/glimmer-dsl-css)) at [Multi-DSL Support](#multi-dsl-support)
228
235
 
229
236
  ## Glimmer Command
230
237
 
@@ -2391,10 +2398,6 @@ By the way, keep in mind that during normal operation, it does also indicate a f
2391
2398
  Exec failed with code 2 command [[/usr/bin/SetFile, -c, icnC, /var/folders/4_/g1sw__tx6mjdgyh3mky7vydc0000gp/T/fxbundler4076750801763032201/images/MathBowling/.VolumeIcon.icns] in unspecified directory
2392
2399
  ```
2393
2400
 
2394
- ## Limitations
2395
-
2396
- - Glimmer apps have a long startup up time and can take anywhere between 12 and 30 seconds to start depending on the app and the machine. Once started, Glimmer apps run very fast and perform very responsively since Ruby is more than fast and capable for handling desktop GUIs. In any case, one idea to address this limitation is to include a background service (daemon) that can launch Glimmer apps instantly or near instantly by keeping a JRuby runtime started and ready at all times.
2397
-
2398
2401
  ## Resources
2399
2402
 
2400
2403
  * [Code Master Blog](http://andymaleh.blogspot.com/search/label/Glimmer)
@@ -2412,11 +2415,9 @@ You may submit [issues](https://github.com/AndyObtiva/glimmer/issues) on [GitHub
2412
2415
 
2413
2416
  [Click here to submit an issue.](https://github.com/AndyObtiva/glimmer/issues)
2414
2417
 
2415
- ### IRC Channel
2416
-
2417
- If you need live help, try the [#glimmer](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer) IRC channel on [irc.mibbit.net](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer). If no one was available, you may [leave a GitHub issue](https://github.com/AndyObtiva/glimmer/issues) to schedule a meetup on IRC.
2418
+ ### Chat
2418
2419
 
2419
- [Click here to connect to #glimmer IRC channel immediately via a web interface.](http://widget.mibbit.com/?settings=7514b8a196f8f1de939a351245db7aa8&server=irc.mibbit.net&channel=%23glimmer)
2420
+ If you need live help, try to [![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2420
2421
 
2421
2422
  ## Feature Suggestions
2422
2423
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.1
1
+ 0.9.2
@@ -0,0 +1,248 @@
1
+ require 'glimmer/data_binding/observable'
2
+ require 'glimmer/data_binding/observer'
3
+
4
+ module Glimmer
5
+ module DataBinding
6
+ class ModelBinding
7
+ include Observable
8
+ include Observer
9
+
10
+ attr_reader :binding_options
11
+
12
+ def initialize(base_model, property_name_expression, binding_options = nil)
13
+ @base_model = base_model
14
+ @property_name_expression = property_name_expression
15
+ @binding_options = binding_options || {}
16
+ if computed?
17
+ @computed_model_bindings = computed_by.map do |computed_by_property_expression|
18
+ self.class.new(base_model, computed_by_property_expression)
19
+ end
20
+ end
21
+ end
22
+
23
+ def model
24
+ nested_property? ? nested_model : base_model
25
+ end
26
+
27
+ # e.g. person.address.state returns [person, person.address]
28
+ def nested_models
29
+ @nested_models = [base_model]
30
+ model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name|
31
+ if reduced_model.nil?
32
+ nil
33
+ else
34
+ invoke_property_reader(reduced_model, nested_model_property_name).tap do |new_reduced_model|
35
+ @nested_models << new_reduced_model
36
+ end
37
+ end
38
+ end
39
+ @nested_models
40
+ end
41
+
42
+ def nested_model
43
+ nested_models.last
44
+ end
45
+
46
+ def base_model
47
+ @base_model
48
+ end
49
+
50
+ def property_name
51
+ nested_property? ? nested_property_name : property_name_expression
52
+ end
53
+
54
+ def convert_on_read(value)
55
+ apply_converter(@binding_options[:on_read], value)
56
+ end
57
+
58
+ def convert_on_write(value)
59
+ apply_converter(@binding_options[:on_write], value)
60
+ end
61
+
62
+ def apply_converter(converter, value)
63
+ if converter.nil?
64
+ value
65
+ elsif converter.is_a?(String) || converter.is_a?(Symbol)
66
+ if value.respond_to?(converter)
67
+ value.send(converter)
68
+ else
69
+ raise Glimmer::Error, "Unsupported bind converter: #{converter.inspect}"
70
+ end
71
+ elsif converter.respond_to?(:call, value)
72
+ converter.call(value)
73
+ else
74
+ raise Glimmer::Error, "Unsupported bind converter: #{converter.inspect}"
75
+ end
76
+ end
77
+
78
+ # All nested property names
79
+ # e.g. property name expression "address.state" gives ['address', 'state']
80
+ # If there are any indexed property names, this returns indexes as properties.
81
+ # e.g. property name expression "addresses[1].state" gives ['addresses', '[1]', 'state']
82
+ def nested_property_names
83
+ @nested_property_names ||= property_name_expression.split(".").map {|pne| pne.match(/([^\[]+)(\[[^\]]+\])?/).to_a.drop(1)}.flatten.compact
84
+ end
85
+
86
+ # Final nested property name
87
+ # e.g. property name expression "address.state" gives :state
88
+ def nested_property_name
89
+ nested_property_names.last
90
+ end
91
+
92
+ # Model representing nested property names
93
+ # e.g. property name expression "address.state" gives [:address]
94
+ def model_property_names
95
+ nested_property_names[0...-1]
96
+ end
97
+
98
+ def nested_property?
99
+ property_name_expression.match(/[.\[]/)
100
+ end
101
+
102
+ def property_name_expression
103
+ @property_name_expression
104
+ end
105
+
106
+ def computed?
107
+ !computed_by.empty?
108
+ end
109
+
110
+ def computed_by
111
+ [@binding_options[:computed_by]].flatten.compact
112
+ end
113
+
114
+ def nested_property_observers_for(observer)
115
+ @nested_property_observers_collection ||= {}
116
+ unless @nested_property_observers_collection.has_key?(observer)
117
+ @nested_property_observers_collection[observer] = nested_property_names.reduce({}) do |output, property_name|
118
+ output.merge(
119
+ property_name => Observer.proc do |new_value|
120
+ # Ensure reattaching observers when a higher level nested property is updated (e.g. person.address changes reattaches person.address.street observer)
121
+ add_observer(observer)
122
+ observer.call(evaluate_property)
123
+ end
124
+ )
125
+ end
126
+ end
127
+ @nested_property_observers_collection[observer]
128
+ end
129
+
130
+ def add_observer(observer)
131
+ if computed?
132
+ add_computed_observers(observer)
133
+ elsif nested_property?
134
+ add_nested_observers(observer)
135
+ else
136
+ model_binding_observer = Observer.proc do |new_value|
137
+ observer.call(evaluate_property)
138
+ end
139
+ observer_registration = model_binding_observer.observe(model, property_name)
140
+ my_registration = observer.registration_for(self)
141
+ observer.add_dependent(my_registration => observer_registration)
142
+ end
143
+ end
144
+
145
+ def remove_observer(observer)
146
+ if computed?
147
+ @computed_model_bindings.each do |computed_model_binding|
148
+ computed_observer_for(observer).unobserve(computed_model_binding)
149
+ end
150
+ @computed_observer_collection.delete(observer)
151
+ elsif nested_property?
152
+ nested_property_observers_for(observer).clear
153
+ else
154
+ observer.unobserve(model, property_name)
155
+ end
156
+ end
157
+
158
+ def computed_observer_for(observer)
159
+ @computed_observer_collection ||= {}
160
+ unless @computed_observer_collection.has_key?(observer)
161
+ @computed_observer_collection[observer] = Observer.proc do |new_value|
162
+ observer.call(evaluate_property)
163
+ end
164
+ end
165
+ @computed_observer_collection[observer]
166
+ end
167
+
168
+ def add_computed_observers(observer)
169
+ @computed_model_bindings.each do |computed_model_binding|
170
+ observer_registration = computed_observer_for(observer).observe(computed_model_binding)
171
+ my_registration = observer.registration_for(self)
172
+ observer.add_dependent(my_registration => observer_registration)
173
+ end
174
+ end
175
+
176
+ def add_nested_observers(observer)
177
+ nested_property_observers = nested_property_observers_for(observer)
178
+ nested_models.zip(nested_property_names).each_with_index do |zip, i|
179
+ model, property_name = zip
180
+ nested_property_observer = nested_property_observers[property_name]
181
+ previous_index = i - 1
182
+ parent_model = previous_index.negative? ? self : nested_models[previous_index]
183
+ parent_property_name = previous_index.negative? ? nil : nested_property_names[previous_index]
184
+ parent_observer = previous_index.negative? ? observer : nested_property_observers[parent_property_name]
185
+ parent_property_name = nil if parent_property_name.to_s.start_with?('[')
186
+ unless model.nil?
187
+ if property_indexed?(property_name)
188
+ # TODO figure out a way to deal with this more uniformly
189
+ observer_registration = nested_property_observer.observe(model)
190
+ else
191
+ observer_registration = nested_property_observer.observe(model, property_name)
192
+ end
193
+ parent_registration = parent_observer.registration_for(parent_model, parent_property_name)
194
+ parent_observer.add_dependent(parent_registration => observer_registration)
195
+ end
196
+ end
197
+ end
198
+
199
+ def call(value)
200
+ return if model.nil?
201
+ converted_value = value
202
+ invoke_property_writer(model, "#{property_name}=", converted_value) unless evaluate_property == converted_value
203
+ end
204
+
205
+ def evaluate_property
206
+ value = nil
207
+ value = invoke_property_reader(model, property_name) unless model.nil?
208
+ convert_on_read(value)
209
+ end
210
+
211
+ def evaluate_options_property
212
+ model.send(options_property_name) unless model.nil?
213
+ end
214
+
215
+ def options_property_name
216
+ self.property_name + "_options"
217
+ end
218
+
219
+ def property_indexed?(property_expression)
220
+ property_expression.to_s.start_with?('[')
221
+ end
222
+
223
+ def invoke_property_reader(object, property_expression)
224
+ if property_indexed?(property_expression)
225
+ property_method = '[]'
226
+ property_argument = property_expression[1...-1]
227
+ property_argument = property_argument.to_i if property_argument.match(/\d+/)
228
+ object.send(property_method, property_argument)
229
+ else
230
+ object.send(property_expression)
231
+ end
232
+ end
233
+
234
+ def invoke_property_writer(object, property_expression, value)
235
+ return if @binding_options[:read_only]
236
+ value = convert_on_write(value)
237
+ if property_indexed?(property_expression)
238
+ property_method = '[]='
239
+ property_argument = property_expression[1...-2]
240
+ property_argument = property_argument.to_i if property_argument.match(/\d+/)
241
+ object.send(property_method, property_argument, value)
242
+ else
243
+ object.send(property_expression, value)
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,21 @@
1
+ require 'glimmer/error'
2
+
3
+ module Glimmer
4
+ module DataBinding
5
+ module Observable
6
+ # TODO rename methods to observe/unobserve
7
+ def add_observer(observer, property_or_properties=nil)
8
+ raise Error, 'Not implemented!'
9
+ end
10
+
11
+ def remove_observer(observer, property_or_properties=nil)
12
+ raise Error, 'Not implemented!'
13
+ end
14
+
15
+ # Overriding inspect to avoid printing very long observer hierarchies
16
+ def inspect
17
+ "#<#{self.class.name}:0x#{self.hash.to_s(16)}>"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,86 @@
1
+ require 'set'
2
+
3
+ require 'glimmer/data_binding/observable'
4
+
5
+ module Glimmer
6
+ module DataBinding
7
+ # TODO prefix utility methods with double-underscore
8
+ module ObservableArray
9
+ include Observable
10
+
11
+ def add_observer(observer, element_properties=nil)
12
+ return observer if has_observer?(observer) && element_properties.nil?
13
+ property_observer_list << observer
14
+ [element_properties].flatten.compact.each do |property|
15
+ each do |element|
16
+ observer.observe(element, property)
17
+ end
18
+ end
19
+ observer
20
+ end
21
+
22
+ def remove_observer(observer, element_properties=nil)
23
+ property_observer_list.delete(observer)
24
+ [element_properties].flatten.compact.each do |property|
25
+ each do |element|
26
+ observer.unobserve(element, property)
27
+ end
28
+ end
29
+ observer
30
+ end
31
+
32
+ def has_observer?(observer)
33
+ property_observer_list.include?(observer)
34
+ end
35
+
36
+ def property_observer_list
37
+ @property_observer_list ||= Set.new
38
+ end
39
+
40
+ def notify_observers
41
+ property_observer_list.each {|observer| observer.call}
42
+ end
43
+
44
+ def <<(element)
45
+ super(element)
46
+ notify_observers
47
+ end
48
+
49
+ def []=(index, value)
50
+ old_value = self[index]
51
+ unregister_dependent_observers(old_value)
52
+ super(index, value)
53
+ notify_observers
54
+ end
55
+
56
+ def delete(element)
57
+ unregister_dependent_observers(element)
58
+ super(element)
59
+ notify_observers
60
+ end
61
+
62
+ def delete_at(index)
63
+ old_value = self[index]
64
+ unregister_dependent_observers(old_value)
65
+ super(index)
66
+ notify_observers
67
+ end
68
+
69
+ def clear
70
+ each do |old_value|
71
+ unregister_dependent_observers(old_value)
72
+ end
73
+ super()
74
+ notify_observers
75
+ end
76
+
77
+ def unregister_dependent_observers(old_value)
78
+ # TODO look into optimizing this
79
+ return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
80
+ property_observer_list.each do |observer|
81
+ observer.unregister_dependents_with_observable(observer.registration_for(self), old_value)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,104 @@
1
+ require 'glimmer/data_binding/observable'
2
+ require 'glimmer/data_binding/observer'
3
+
4
+ module Glimmer
5
+ module DataBinding
6
+ # TODO prefix utility methods with double-underscore
7
+ module ObservableModel
8
+ include Observable
9
+
10
+ class Notifier
11
+ include Observer
12
+ def initialize(observable_model, property_name)
13
+ @observable_model = observable_model
14
+ @property_name = property_name
15
+ end
16
+ def call(new_value=nil)
17
+ @observable_model.notify_observers(@property_name)
18
+ end
19
+ end
20
+
21
+ def add_observer(observer, property_name)
22
+ return observer if has_observer?(observer, property_name)
23
+ property_observer_list(property_name) << observer
24
+ add_property_writer_observers(property_name)
25
+ observer
26
+ end
27
+
28
+ def remove_observer(observer, property_name)
29
+ property_observer_list(property_name).delete(observer)
30
+ end
31
+
32
+ def has_observer?(observer, property_name)
33
+ property_observer_list(property_name).include?(observer)
34
+ end
35
+
36
+ def has_observer_for_any_property?(observer)
37
+ property_observer_hash.values.map(&:to_a).sum.include?(observer)
38
+ end
39
+
40
+ def property_observer_hash
41
+ @property_observers ||= Hash.new
42
+ end
43
+
44
+ def property_observer_list(property_name)
45
+ property_observer_hash[property_name.to_sym] = Set.new unless property_observer_hash[property_name.to_sym]
46
+ property_observer_hash[property_name.to_sym]
47
+ end
48
+
49
+ def notify_observers(property_name)
50
+ property_observer_list(property_name).each {|observer| observer.call(send(property_name))}
51
+ end
52
+ #TODO upon updating values, make sure dependent observers are cleared (not added as dependents here)
53
+
54
+ def add_property_writer_observers(property_name)
55
+ property_writer_name = "#{property_name}="
56
+ method(property_writer_name)
57
+ ensure_array_object_observer(property_name, send(property_name))
58
+ begin
59
+ method("__original_#{property_writer_name}")
60
+ rescue
61
+ old_method = self.class.instance_method(property_writer_name)
62
+ define_singleton_method("__original_#{property_writer_name}", old_method)
63
+ define_singleton_method(property_writer_name) do |value|
64
+ old_value = self.send(property_name)
65
+ unregister_dependent_observers(property_name, old_value)
66
+ self.send("__original_#{property_writer_name}", value)
67
+ notify_observers(property_name)
68
+ ensure_array_object_observer(property_name, value, old_value)
69
+ end
70
+ end
71
+ rescue => e
72
+ # ignore writing if no property writer exists
73
+ Glimmer::Config.logger&.debug "No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"
74
+ end
75
+
76
+ def unregister_dependent_observers(property_name, old_value)
77
+ # TODO look into optimizing this
78
+ return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
79
+ property_observer_list(property_name).each do |observer|
80
+ observer.unregister_dependents_with_observable(observer.registration_for(self, property_name), old_value)
81
+ end
82
+ end
83
+
84
+ def ensure_array_object_observer(property_name, object, old_object = nil)
85
+ return unless object.is_a?(Array)
86
+ array_object_observer = array_object_observer_for(property_name)
87
+ array_observer_registration = array_object_observer.observe(object)
88
+ property_observer_list(property_name).each do |observer|
89
+ my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
90
+ observer.add_dependent(my_registration => array_observer_registration)
91
+ end
92
+ array_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableArray)
93
+ end
94
+
95
+ def array_object_observer_for(property_name)
96
+ @array_object_observers ||= {}
97
+ unless @array_object_observers.has_key?(property_name)
98
+ @array_object_observers[property_name] = ObservableModel::Notifier.new(self, property_name)
99
+ end
100
+ @array_object_observers[property_name]
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,127 @@
1
+ require 'glimmer/error'
2
+
3
+ module Glimmer
4
+ module DataBinding
5
+ # Mixin representing Observer trait from Observer Design Pattern
6
+ # Allows classes to include without interfering with their
7
+ # inheritance hierarchy.
8
+ #
9
+ # Includes a default implementation that can receive an observer block
10
+ # Example: Observer.proc {|new_value| puts new_value}
11
+ # Subclasses may override
12
+ module Observer
13
+ # Observer Proc default implementation that takes an observer block to process updates
14
+ # via call method
15
+ class Proc
16
+ include Observer
17
+
18
+ def initialize(&observer_block)
19
+ @observer_block = observer_block
20
+ end
21
+
22
+ # Called by observables once updates occur sending in the new_value if any
23
+ def call(new_value=nil)
24
+ @observer_block.call(new_value)
25
+ end
26
+ end
27
+
28
+ class Registration < Struct.new(:observer, :observable, :property, keyword_init: true)
29
+ def unregister
30
+ observer.unobserve(observable, property)
31
+ end
32
+ alias unobserve unregister
33
+ end
34
+
35
+ class << self
36
+ def proc(&observer_block)
37
+ Proc.new(&observer_block)
38
+ end
39
+ end
40
+
41
+ def registrations
42
+ @registrations ||= Set.new
43
+ end
44
+
45
+ def registration_for(observable, property = nil)
46
+ Registration.new(observer: self, observable: observable, property: property)
47
+ end
48
+
49
+ # mapping of registrations to dependents
50
+ # {[observable, property] => [[dependent, dependent_observable, dependent_property], ...]}
51
+ def dependents
52
+ @dependents ||= Hash.new
53
+ end
54
+
55
+ def dependents_for(registration)
56
+ dependents[registration] ||= Set.new
57
+ end
58
+
59
+ # registers observer in an observable on a property (optional)
60
+ # observer maintains registration list to unregister later
61
+ def register(observable, property = nil)
62
+ unless observable.is_a?(Observable)
63
+ # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
64
+ if observable.is_a?(Array)
65
+ observable.extend(ObservableArray)
66
+ else
67
+ observable.extend(ObservableModel)
68
+ end
69
+ end
70
+ observable.add_observer(*[self, property].compact)
71
+ registration_for(observable, property).tap do |registration|
72
+ self.registrations << registration
73
+ end
74
+ end
75
+ alias observe register
76
+
77
+ def unregister(observable, property = nil)
78
+ # TODO optimize performance in the future via indexing and/or making a registration official object/class
79
+ observable.remove_observer(*[self, property].compact)
80
+ registration = registration_for(observable, property)
81
+ dependents_for(registration).each do |dependent|
82
+ dependent.unregister
83
+ remove_dependent(registration => dependent)
84
+ end
85
+ registrations.delete(registration)
86
+ end
87
+ alias unobserve unregister
88
+
89
+ def unregister_dependents_with_observable(registration, dependent_observable)
90
+ thedependents = dependents_for(registration).select do |thedependent|
91
+ thedependent.observable == dependent_observable
92
+ end
93
+ thedependents.each do |thedependent|
94
+ thedependent.unregister
95
+ end
96
+ end
97
+
98
+ # cleans up all registrations in observables
99
+ def unregister_all_observables
100
+ registrations.each do |registration|
101
+ registration.unregister
102
+ end
103
+ end
104
+ alias unobserve_all_observables unregister_all_observables
105
+
106
+ # add dependent observer to unregister when unregistering observer
107
+ def add_dependent(parent_to_dependent_hash)
108
+ registration = parent_to_dependent_hash.keys.first
109
+ dependent = parent_to_dependent_hash.values.first
110
+ dependents_for(registration) << dependent
111
+ end
112
+
113
+ def remove_dependent(parent_to_dependent_hash)
114
+ registration = parent_to_dependent_hash.keys.first
115
+ dependent = parent_to_dependent_hash.values.first
116
+ dependents_for(registration).delete(dependent)
117
+ end
118
+
119
+ def call(new_value)
120
+ raise Error, 'Not implemented!'
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ require 'glimmer/data_binding/observable_model'
127
+ require 'glimmer/data_binding/observable_array'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-14 00:00:00.000000000 Z
11
+ date: 2020-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -145,6 +145,11 @@ files:
145
145
  - VERSION
146
146
  - lib/glimmer.rb
147
147
  - lib/glimmer/config.rb
148
+ - lib/glimmer/data_binding/model_binding.rb
149
+ - lib/glimmer/data_binding/observable.rb
150
+ - lib/glimmer/data_binding/observable_array.rb
151
+ - lib/glimmer/data_binding/observable_model.rb
152
+ - lib/glimmer/data_binding/observer.rb
148
153
  - lib/glimmer/dsl/engine.rb
149
154
  - lib/glimmer/dsl/expression.rb
150
155
  - lib/glimmer/dsl/expression_handler.rb