glimmer 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +5 -4
- data/VERSION +1 -1
- data/glimmer.gemspec +8 -7
- data/lib/glimmer/data_binding/model_binding.rb +19 -8
- data/lib/glimmer/data_binding/observable_array.rb +12 -12
- data/lib/glimmer/data_binding/observable_hash.rb +2 -60
- data/lib/glimmer/data_binding/observable_hashable.rb +75 -0
- data/lib/glimmer/data_binding/observable_model.rb +25 -34
- data/lib/glimmer/data_binding/observer.rb +14 -14
- metadata +11 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4819523f794404e37e2dc3c9c673b4e33f80e20825a077a775d9d14b7bb2e437
|
4
|
+
data.tar.gz: ce87b095f130bd8bde0b076bd8499dfa5ce2e3ccf5d8d956b8452537e181d314
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41362408327087fd238ac81ecf3a449505c43eb57a31b0c69253abd2a8c523ca64c48da31ab47030dbba1a756b90435908e19d67cde33fd939bc4b27c7480527
|
7
|
+
data.tar.gz: d37570e378e876331282db59bb5276d280d8cfbd8b3d70694a654733fd7cd5d5db9ff08dc7cec1f1fe24a92e413f2b00217d210204bc0d7559130f7195085216
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,15 @@
|
|
3
3
|
Related Change Logs:
|
4
4
|
- [glimmer-dsl-swt/CHANGELOG.md](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/CHANGELOG.md)
|
5
5
|
|
6
|
+
### 2.5.0
|
7
|
+
|
8
|
+
- Support observing `Struct`/`OpenStruct` changes via `:[]=` method in addition to attribute writers.
|
9
|
+
- Support read-only direct observation of `Hash` object without key via `ModelBinding` (e.g. `ModelBinding.new(some_hash)`)
|
10
|
+
- Support read-only direct observation of `Array` object without index via `ModelBinding` (e.g. `ModelBinding.new(some_array)`)
|
11
|
+
- Support observing `Hash` attribute with `ModelBinding` (all keys or a single key)
|
12
|
+
- Disable `#ensure_hash_object_observer` in ObservableModel/ObservableHash/ObservableArray since it has performance implications and is not necessary
|
13
|
+
- Fix issue with `#ensure_array_object_observer` not receiving `recursive: true` option when updating value of an attribute in `ObservableArray`, `ObservableModel`, and `ObservableHash`
|
14
|
+
|
6
15
|
### 2.4.1
|
7
16
|
|
8
17
|
- Support `recursive: [integer]` option for ObservableArray#add_observer for finite recursion
|
data/README.md
CHANGED
@@ -34,6 +34,7 @@ Start by checking out Glimmer's original GUI DSL, which got extracted into its o
|
|
34
34
|
- [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
|
35
35
|
- [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
|
36
36
|
- [glimmer-dsl-libui](https://github.com/AndyObtiva/glimmer-dsl-libui): Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)
|
37
|
+
- [glimmer-dsl-gtk](https://github.com/AndyObtiva/glimmer-dsl-gtk): Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)
|
37
38
|
- [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
|
38
39
|
- [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
|
39
40
|
|
@@ -229,7 +230,7 @@ end
|
|
229
230
|
### Setup
|
230
231
|
|
231
232
|
Follow these steps to author a [Glimmer](https://rubygems.org/gems/glimmer) DSL:
|
232
|
-
- Add `gem 'glimmer', '~> 2.
|
233
|
+
- Add `gem 'glimmer', '~> 2.5.0'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.5.0` and add `require 'glimmer'`
|
233
234
|
- Create `glimmer/dsl/[dsl_name]/dsl.rb`, which requires and adds all dynamic expressions for the [dsl_name] Glimmer DSL module as per the code shown in the previous section (or [Official DSLs](#official-dsls) as examples)
|
234
235
|
- Create `glimmer/dsl/[dsl_name]/[expresion_name]_expresion.rb` for every [expresion_name] expression needed, whether dynamic or static
|
235
236
|
|
@@ -1186,9 +1187,9 @@ This relies mainly on the Observer Design Pattern and the MVP (Model-View-Presen
|
|
1186
1187
|
These are the main classes concerning data-binding:
|
1187
1188
|
- `Glimmer::DataBinding::Observer`: Provides general observer support including unique registration and deregistration for cleanup and prevention of memory leaks. Main methods concerned are: `call`, `register` (alias: `observe`), and `unregister` (alias: `unobserve` or `deregister`)
|
1188
1189
|
- `Glimmer::DataBinding::Observable`: General super-module for all observables. Main methods concerned are: `add_observer` and `remove_observer`
|
1189
|
-
- `Glimmer::DataBinding::ObservableModel`: Mixin module for any observable model with observable attributes. In addition to `Observable` methods, it has a `notify_observers` method to be called when changes occur. It automatically enhances all attribute setters (ending with `=`) to notify observers on changes. Also, it automatically handles observing array attributes using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
|
1190
|
+
- `Glimmer::DataBinding::ObservableModel`: Mixin module for any observable model (`Object`, `Struct` or `OpenStruct`) with observable attributes (observes attribute writers and `Struct`/`OpenStruct` `:[]=` method). In addition to `Observable` methods, it has a `notify_observers` method to be called when changes occur. It automatically enhances all attribute setters (ending with `=`) to notify observers on changes. Also, it automatically handles observing array attributes using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
|
1190
1191
|
- `Glimmer::DataBinding::ObservableArray`: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. `push`, `select!`, or `delete`) recursively (meaning if an array contained arrays and they changed, observers are notified). Accepts `recursive: true` option in `add_observer` method to recursively observe nested arrays all the way down. Alternatively, pass `recursive: [integer]` to limit recursion in `Array` observation to a specific number of levels beyond the first level (which is always included).
|
1191
|
-
- `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing hash mutation operations (e.g. `hash[key]=value`, `select!`, `merge!`)
|
1192
|
+
- `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing hash mutation operations (e.g. `hash[key]=value`, `select!`, `merge!`). Also, it automatically handles observing array values using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
|
1192
1193
|
- `Glimmer::DataBinding::ModelBinding`: a higher-level abstraction that relies on all the other observer/observable classes to support basic data-binding, nested data-binding, and computed data-binding
|
1193
1194
|
- `Glimmer::DataBinding::Shine`: enables highly intuitive and visually expressive syntax to perform bidirectional (two-way) data-binding with `<=>` and unidirectional (one-way) data-binding with `<=`
|
1194
1195
|
|
@@ -1254,7 +1255,7 @@ end
|
|
1254
1255
|
|
1255
1256
|
Note that if an observed model attribute or hash key is an array, it is automatically observed for array changes, not just attribute/key-value changes.
|
1256
1257
|
|
1257
|
-
All of the features above make Glimmer's data-binding library one of the most sophisticated and advanced in the industry since
|
1258
|
+
All of the features above make Glimmer's data-binding library one of the most sophisticated and advanced in the industry since it automates everything instead of requiring endless manual configuration, thus resulting in some of the tersest most declarative syntax for using observers and data-binding.
|
1258
1259
|
|
1259
1260
|
You may learn more by looking into [data-binding specs](/Users/andy/code/glimmer/spec/lib/glimmer/data_binding) as well as [Data-Binding](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#data-binding) and [Observer](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#observer) usage in [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt)
|
1260
1261
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.5.0
|
data/glimmer.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: glimmer 2.
|
5
|
+
# stub: glimmer 2.5.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "glimmer".freeze
|
9
|
-
s.version = "2.
|
9
|
+
s.version = "2.5.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["AndyMaleh".freeze]
|
14
|
-
s.date = "2021-11-
|
15
|
-
s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Tk (Ruby Desktop Development GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for
|
14
|
+
s.date = "2021-11-19"
|
15
|
+
s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps), Glimmer DSL for Tk (Ruby Desktop Development GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
18
18
|
"CHANGELOG.md",
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/glimmer/data_binding/observable.rb",
|
34
34
|
"lib/glimmer/data_binding/observable_array.rb",
|
35
35
|
"lib/glimmer/data_binding/observable_hash.rb",
|
36
|
+
"lib/glimmer/data_binding/observable_hashable.rb",
|
36
37
|
"lib/glimmer/data_binding/observable_model.rb",
|
37
38
|
"lib/glimmer/data_binding/observer.rb",
|
38
39
|
"lib/glimmer/data_binding/shine.rb",
|
@@ -51,7 +52,7 @@ Gem::Specification.new do |s|
|
|
51
52
|
]
|
52
53
|
s.homepage = "http://github.com/AndyObtiva/glimmer".freeze
|
53
54
|
s.licenses = ["MIT".freeze]
|
54
|
-
s.rubygems_version = "3.2.
|
55
|
+
s.rubygems_version = "3.2.31".freeze
|
55
56
|
s.summary = "Glimmer - DSL Engine for Ruby GUI and More".freeze
|
56
57
|
|
57
58
|
if s.respond_to? :specification_version then
|
@@ -70,7 +71,7 @@ Gem::Specification.new do |s|
|
|
70
71
|
s.add_development_dependency(%q<coveralls>.freeze, [">= 0"])
|
71
72
|
s.add_development_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
|
72
73
|
s.add_development_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
|
73
|
-
s.add_development_dependency(%q<rake-tui>.freeze, ["
|
74
|
+
s.add_development_dependency(%q<rake-tui>.freeze, ["> 0"])
|
74
75
|
else
|
75
76
|
s.add_dependency(%q<array_include_methods>.freeze, ["~> 1.4.0"])
|
76
77
|
s.add_dependency(%q<facets>.freeze, [">= 3.1.0", "< 4.0.0"])
|
@@ -83,7 +84,7 @@ Gem::Specification.new do |s|
|
|
83
84
|
s.add_dependency(%q<coveralls>.freeze, [">= 0"])
|
84
85
|
s.add_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
|
85
86
|
s.add_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
|
86
|
-
s.add_dependency(%q<rake-tui>.freeze, ["
|
87
|
+
s.add_dependency(%q<rake-tui>.freeze, ["> 0"])
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
@@ -30,9 +30,9 @@ module Glimmer
|
|
30
30
|
|
31
31
|
attr_reader :binding_options, :property_name_expression
|
32
32
|
|
33
|
-
def initialize(
|
34
|
-
|
35
|
-
@property_name_expression =
|
33
|
+
def initialize(*args)
|
34
|
+
binding_options = args.pop if args.size > 1 && args.last.is_a?(Hash)
|
35
|
+
@base_model, @property_name_expression = args
|
36
36
|
@binding_options = binding_options || Concurrent::Hash.new
|
37
37
|
if computed?
|
38
38
|
@computed_model_bindings = Concurrent::Array.new(computed_by.map do |computed_by_property_expression|
|
@@ -91,7 +91,7 @@ module Glimmer
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def nested_property?
|
94
|
-
property_name_expression.match(/[.\[]/)
|
94
|
+
property_name_expression.to_s.match(/[.\[]/)
|
95
95
|
end
|
96
96
|
|
97
97
|
def computed?
|
@@ -137,7 +137,7 @@ module Glimmer
|
|
137
137
|
apply_processor(@binding_options[:after_read], converted_value)
|
138
138
|
end
|
139
139
|
end
|
140
|
-
observer_registration = model_binding_observer.observe(model, property_name, observation_options)
|
140
|
+
observer_registration = model_binding_observer.observe(*[model, property_name, observation_options].compact)
|
141
141
|
my_registration = observer.registration_for(self)
|
142
142
|
observer.add_dependent(my_registration => observer_registration)
|
143
143
|
end
|
@@ -206,7 +206,7 @@ module Glimmer
|
|
206
206
|
def call(value, *extra_args)
|
207
207
|
return if model.nil?
|
208
208
|
converted_value = value
|
209
|
-
invoke_property_writer(model, "#{property_name}=", converted_value) unless converted_value == evaluate_property
|
209
|
+
invoke_property_writer(model, model.is_a?(Hash) ? property_name : "#{property_name}=", converted_value) unless converted_value == evaluate_property || property_name.nil?
|
210
210
|
end
|
211
211
|
|
212
212
|
def evaluate_property
|
@@ -263,11 +263,18 @@ module Glimmer
|
|
263
263
|
property_argument = property_argument.to_i if property_argument.match(/\d+/)
|
264
264
|
object.send(property_method, property_argument)
|
265
265
|
else
|
266
|
-
|
266
|
+
if property_expression.nil?
|
267
|
+
object
|
268
|
+
elsif object.is_a?(Hash)
|
269
|
+
object[property_expression]
|
270
|
+
else
|
271
|
+
object.send(property_expression)
|
272
|
+
end
|
267
273
|
end
|
268
274
|
end
|
269
275
|
|
270
276
|
def invoke_property_writer(object, property_expression, value)
|
277
|
+
return if property_expression.nil?
|
271
278
|
raise "Cannot invoke `#{property_expression}` because ModelBinding#binding_options[:read_only]=true" if @binding_options[:read_only]
|
272
279
|
apply_processor(@binding_options[:before_write], value)
|
273
280
|
converted_value = convert_on_write(value)
|
@@ -277,7 +284,11 @@ module Glimmer
|
|
277
284
|
property_argument = property_argument.to_i if property_argument.match(/\d+/)
|
278
285
|
object.send(property_method, property_argument, converted_value)
|
279
286
|
else
|
280
|
-
object.
|
287
|
+
if object.is_a?(Hash)
|
288
|
+
object[property_expression] = converted_value
|
289
|
+
else
|
290
|
+
object.send(property_expression, converted_value)
|
291
|
+
end
|
281
292
|
end
|
282
293
|
apply_processor(@binding_options[:after_write], converted_value)
|
283
294
|
end
|
@@ -50,7 +50,7 @@ module Glimmer
|
|
50
50
|
element_properties = args
|
51
51
|
element_properties = element_properties.flatten.compact.uniq
|
52
52
|
return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
|
53
|
-
property_observer_list
|
53
|
+
property_observer_list[observer] = options
|
54
54
|
observer_element_properties[observer] = element_properties_for(observer) + Concurrent::Set.new(element_properties)
|
55
55
|
if !options.empty? && options[:recursive].is_a?(Integer)
|
56
56
|
options = options.clone
|
@@ -60,9 +60,9 @@ module Glimmer
|
|
60
60
|
observer
|
61
61
|
end
|
62
62
|
|
63
|
-
def add_element_observers(element,
|
64
|
-
property_observer_list.each do |observer|
|
65
|
-
add_element_observer(element, observer, options)
|
63
|
+
def add_element_observers(element, general_options = {})
|
64
|
+
property_observer_list.each do |observer, options|
|
65
|
+
add_element_observer(element, observer, options.merge(general_options))
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
@@ -79,7 +79,7 @@ module Glimmer
|
|
79
79
|
return unless object&.is_a?(Array)
|
80
80
|
array_object_observer = array_object_observer_for(object)
|
81
81
|
array_observer_registration = array_object_observer.observe(object, options)
|
82
|
-
property_observer_list.each do |observer|
|
82
|
+
property_observer_list.each do |observer, options|
|
83
83
|
my_registration = observer.registration_for(self)
|
84
84
|
observer.add_dependent(my_registration => array_observer_registration)
|
85
85
|
end
|
@@ -109,7 +109,7 @@ module Glimmer
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def remove_element_observers(element)
|
112
|
-
property_observer_list.each do |observer|
|
112
|
+
property_observer_list.each do |observer, options|
|
113
113
|
remove_element_observer(element, observer)
|
114
114
|
end
|
115
115
|
end
|
@@ -120,15 +120,15 @@ module Glimmer
|
|
120
120
|
end
|
121
121
|
if element.is_a?(ObservableArray)
|
122
122
|
array_object_observer_for(element).unobserve(element)
|
123
|
-
element.property_observer_list.select {|
|
124
|
-
o.deregister_all_observables
|
123
|
+
element.property_observer_list.select {|obs, opt| obs.respond_to?(:observable_array) && obs.observable_array == self}.each do |o|
|
124
|
+
o.deregister_all_observables if o.respond_to?(:deregister_all_observables)
|
125
125
|
@array_object_observers.reject! {|k, v| v == o}
|
126
126
|
end
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
130
|
def has_observer?(observer)
|
131
|
-
property_observer_list.include?(observer)
|
131
|
+
property_observer_list.keys.include?(observer)
|
132
132
|
end
|
133
133
|
|
134
134
|
def has_observer_element_properties?(observer, element_properties)
|
@@ -136,7 +136,7 @@ module Glimmer
|
|
136
136
|
end
|
137
137
|
|
138
138
|
def property_observer_list
|
139
|
-
@property_observer_list ||= Concurrent::
|
139
|
+
@property_observer_list ||= Concurrent::Hash.new
|
140
140
|
end
|
141
141
|
|
142
142
|
def observer_element_properties
|
@@ -148,7 +148,7 @@ module Glimmer
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def notify_observers
|
151
|
-
property_observer_list.to_a.each { |
|
151
|
+
property_observer_list.to_a.each { |obs, opt| obs.call(self) }
|
152
152
|
end
|
153
153
|
|
154
154
|
def <<(element)
|
@@ -372,7 +372,7 @@ module Glimmer
|
|
372
372
|
|
373
373
|
def unregister_dependent_observers(old_value)
|
374
374
|
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
|
375
|
-
property_observer_list.each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
|
375
|
+
property_observer_list.each { |observer, options| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
|
376
376
|
end
|
377
377
|
alias deregister_dependent_observers unregister_dependent_observers
|
378
378
|
end
|
@@ -19,13 +19,13 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
-
require 'glimmer/data_binding/
|
22
|
+
require 'glimmer/data_binding/observable_hashable'
|
23
23
|
require 'glimmer/data_binding/observer'
|
24
24
|
|
25
25
|
module Glimmer
|
26
26
|
module DataBinding
|
27
27
|
module ObservableHash
|
28
|
-
include
|
28
|
+
include ObservableHashable
|
29
29
|
|
30
30
|
class Notifier
|
31
31
|
include Observer
|
@@ -40,28 +40,6 @@ module Glimmer
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
OBSERVED_STORE_METHOD = lambda do |key, value|
|
44
|
-
if key_observer_list(key).empty?
|
45
|
-
if all_key_observer_list.empty?
|
46
|
-
self.send('__original__store', key, value)
|
47
|
-
else
|
48
|
-
old_value = self[key]
|
49
|
-
unregister_dependent_observers(nil, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
|
50
|
-
self.send('__original__store', key, value)
|
51
|
-
notify_observers(key)
|
52
|
-
ensure_array_object_observer(nil, value, old_value)
|
53
|
-
ensure_hash_object_observer(nil, value, old_value)
|
54
|
-
end
|
55
|
-
else
|
56
|
-
old_value = self[key]
|
57
|
-
unregister_dependent_observers(key, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
|
58
|
-
self.send('__original__store', key, value)
|
59
|
-
notify_observers(key)
|
60
|
-
ensure_array_object_observer(key, value, old_value)
|
61
|
-
ensure_hash_object_observer(key, value, old_value)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
43
|
def add_observer(observer, key = nil, options = {})
|
66
44
|
if key.is_a?(Hash)
|
67
45
|
options = key
|
@@ -123,24 +101,6 @@ module Glimmer
|
|
123
101
|
(key_observer_list(key).to_a - all_key_observer_list.to_a).each { |observer| observer.call(self[key], key) }
|
124
102
|
end
|
125
103
|
|
126
|
-
def add_key_writer_observer(key = nil, options)
|
127
|
-
ensure_array_object_observer(key, self[key], nil, options)
|
128
|
-
ensure_hash_object_observer(key, self[key], nil, options)
|
129
|
-
begin
|
130
|
-
method('__original__store')
|
131
|
-
rescue
|
132
|
-
define_singleton_method('__original__store', store_method)
|
133
|
-
define_singleton_method('[]=', &OBSERVED_STORE_METHOD)
|
134
|
-
end
|
135
|
-
rescue => e
|
136
|
-
#ignore writing if no key writer exists
|
137
|
-
Glimmer::Config.logger.debug {"No need to observe store method: '[]='\n#{e.message}\n#{e.backtrace.join("\n")}"}
|
138
|
-
end
|
139
|
-
|
140
|
-
def store_method
|
141
|
-
self.class.instance_method('[]=') rescue self.method('[]=')
|
142
|
-
end
|
143
|
-
|
144
104
|
def unregister_dependent_observers(key, old_value)
|
145
105
|
# TODO look into optimizing this
|
146
106
|
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray) || old_value.is_a?(ObservableHash)
|
@@ -166,24 +126,6 @@ module Glimmer
|
|
166
126
|
@array_object_observers[key]
|
167
127
|
end
|
168
128
|
|
169
|
-
def ensure_hash_object_observer(key, object, old_object = nil, options = {})
|
170
|
-
options ||= {}
|
171
|
-
return unless object&.is_a?(Hash)
|
172
|
-
hash_object_observer = hash_object_observer_for(key)
|
173
|
-
hash_observer_registration = hash_object_observer.observe(object, options)
|
174
|
-
key_observer_list(key).each do |observer|
|
175
|
-
my_registration = observer.registration_for(self, key) # TODO eliminate repetition
|
176
|
-
observer.add_dependent(my_registration => hash_observer_registration)
|
177
|
-
end
|
178
|
-
hash_object_observer_for(key).unregister(old_object) if old_object.is_a?(ObservableHash)
|
179
|
-
end
|
180
|
-
|
181
|
-
def hash_object_observer_for(key)
|
182
|
-
@hash_object_observers ||= Concurrent::Hash.new
|
183
|
-
@hash_object_observers[key] = ObservableModel::Notifier.new(self, key) unless @hash_object_observers.has_key?(key)
|
184
|
-
@hash_object_observers[key]
|
185
|
-
end
|
186
|
-
|
187
129
|
def delete(key, &block)
|
188
130
|
old_value = self[key]
|
189
131
|
unless old_value.nil?
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'glimmer/data_binding/observable'
|
23
|
+
|
24
|
+
module Glimmer
|
25
|
+
module DataBinding
|
26
|
+
# Represents a Hash-like object with attributes writable via :[]= store method like Hash, Struct, and OpenStruct
|
27
|
+
# Expects including class to have the following methods:
|
28
|
+
# - key_observer_list
|
29
|
+
# - all_key_observer_list
|
30
|
+
# - unregister_dependent_observer
|
31
|
+
# - ensure_array_object_observer
|
32
|
+
module ObservableHashable
|
33
|
+
include Observable
|
34
|
+
|
35
|
+
OBSERVED_STORE_METHOD = lambda do |options|
|
36
|
+
lambda do |key, value|
|
37
|
+
if key_observer_list(key).empty?
|
38
|
+
if all_key_observer_list.empty?
|
39
|
+
self.send('__original__store', key, value)
|
40
|
+
else
|
41
|
+
old_value = self[key]
|
42
|
+
unregister_dependent_observers(nil, old_value) # remove dependent observers previously installed in ensure_array_object_observer
|
43
|
+
self.send('__original__store', key, value)
|
44
|
+
notify_observers(key)
|
45
|
+
ensure_array_object_observer(nil, value, old_value, options)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
old_value = self[key]
|
49
|
+
unregister_dependent_observers(key, old_value) # remove dependent observers previously installed in ensure_array_object_observer
|
50
|
+
self.send('__original__store', key, value)
|
51
|
+
notify_observers(key)
|
52
|
+
ensure_array_object_observer(key, value, old_value, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_key_writer_observer(key = nil, options)
|
58
|
+
ensure_array_object_observer(key, self[key], nil, options)
|
59
|
+
begin
|
60
|
+
method('__original__store')
|
61
|
+
rescue
|
62
|
+
define_singleton_method('__original__store', store_method)
|
63
|
+
define_singleton_method('[]=', &OBSERVED_STORE_METHOD.call(options))
|
64
|
+
end
|
65
|
+
rescue => e
|
66
|
+
#ignore writing if no key writer exists
|
67
|
+
Glimmer::Config.logger.debug {"No need to observe store method: '[]='\n#{e.message}\n#{e.backtrace.join("\n")}"}
|
68
|
+
end
|
69
|
+
|
70
|
+
def store_method
|
71
|
+
self.class.instance_method('[]=') rescue self.method('[]=')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -19,13 +19,13 @@
|
|
19
19
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
|
-
require 'glimmer/data_binding/
|
22
|
+
require 'glimmer/data_binding/observable_hashable'
|
23
23
|
require 'glimmer/data_binding/observer'
|
24
24
|
|
25
25
|
module Glimmer
|
26
26
|
module DataBinding
|
27
27
|
module ObservableModel
|
28
|
-
include
|
28
|
+
include ObservableHashable
|
29
29
|
|
30
30
|
class Notifier
|
31
31
|
include Observer
|
@@ -40,25 +40,25 @@ module Glimmer
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
PROPERTY_WRITER_FACTORY = lambda do |property_name|
|
43
|
+
PROPERTY_WRITER_FACTORY = lambda do |property_name, options|
|
44
44
|
property_writer_name = "#{property_name}="
|
45
45
|
lambda do |value|
|
46
46
|
old_value = self.send(property_name)
|
47
|
-
unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer
|
47
|
+
unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer
|
48
48
|
self.send("__original__#{property_writer_name}", value)
|
49
49
|
notify_observers(property_name)
|
50
|
-
ensure_array_object_observer(property_name, value, old_value)
|
51
|
-
ensure_hash_object_observer(property_name, value, old_value)
|
50
|
+
ensure_array_object_observer(property_name, value, old_value, options)
|
52
51
|
end
|
53
52
|
end
|
54
|
-
|
53
|
+
|
55
54
|
def add_observer(observer, property_name, options = {})
|
56
55
|
return observer if has_observer?(observer, property_name)
|
57
56
|
property_observer_list(property_name) << observer
|
58
57
|
add_property_writer_observers(property_name, options)
|
58
|
+
add_key_writer_observer(property_name, options) if is_a?(Struct) || is_a?(OpenStruct)
|
59
59
|
observer
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
def remove_observer(observer, property_name, options = {})
|
63
63
|
if has_observer?(observer, property_name)
|
64
64
|
property_observer_list(property_name).delete(observer)
|
@@ -67,10 +67,11 @@ module Glimmer
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def remove_observers(property_name)
|
70
|
-
|
70
|
+
property_key = property_name&.to_sym
|
71
|
+
property_observer_hash[property_key].each do |observer|
|
71
72
|
remove_observer(observer, property_name)
|
72
73
|
end
|
73
|
-
property_observer_hash.delete(
|
74
|
+
property_observer_hash.delete(property_key)
|
74
75
|
end
|
75
76
|
|
76
77
|
def remove_all_observers
|
@@ -95,24 +96,32 @@ module Glimmer
|
|
95
96
|
end
|
96
97
|
|
97
98
|
def property_observer_list(property_name)
|
98
|
-
|
99
|
-
property_observer_hash[
|
99
|
+
property_key = property_name&.to_sym
|
100
|
+
property_observer_hash[property_key] = Concurrent::Set.new unless property_observer_hash[property_key]
|
101
|
+
property_observer_hash[property_key]
|
100
102
|
end
|
103
|
+
alias key_observer_list property_observer_list
|
104
|
+
|
105
|
+
def all_property_observer_list
|
106
|
+
property_observer_list(nil)
|
107
|
+
end
|
108
|
+
alias all_key_observer_list all_property_observer_list
|
101
109
|
|
102
110
|
def notify_observers(property_name)
|
103
111
|
property_observer_list(property_name).to_a.each { |observer| observer.call(send(property_name)) }
|
104
112
|
end
|
105
|
-
|
113
|
+
|
106
114
|
def add_property_writer_observers(property_name, options)
|
107
115
|
property_writer_name = "#{property_name}="
|
108
116
|
method(property_writer_name)
|
109
117
|
ensure_array_object_observer(property_name, send(property_name), nil, options)
|
110
|
-
ensure_hash_object_observer(property_name, send(property_name), nil, options)
|
111
118
|
begin
|
112
119
|
method("__original__#{property_writer_name}")
|
113
120
|
rescue
|
114
121
|
define_singleton_method("__original__#{property_writer_name}", property_writer_method(property_writer_name))
|
115
|
-
|
122
|
+
# Note the limitation that the first observe call options apply to all subsequent observations meaning even if unobserve was called, options do not change from initial ones
|
123
|
+
# It is good enough for now. If there is a need to address this in the future, this is where to start the work
|
124
|
+
define_singleton_method(property_writer_name, &PROPERTY_WRITER_FACTORY.call(property_name, options))
|
116
125
|
end
|
117
126
|
rescue => e
|
118
127
|
#ignore writing if no property writer exists
|
@@ -130,7 +139,7 @@ module Glimmer
|
|
130
139
|
end
|
131
140
|
alias deregister_dependent_observers unregister_dependent_observers
|
132
141
|
|
133
|
-
def ensure_array_object_observer(property_name, object, old_object = nil, options =
|
142
|
+
def ensure_array_object_observer(property_name, object, old_object = nil, options = nil)
|
134
143
|
options ||= {}
|
135
144
|
return unless object&.is_a?(Array)
|
136
145
|
array_object_observer = array_object_observer_for(property_name)
|
@@ -147,24 +156,6 @@ module Glimmer
|
|
147
156
|
@array_object_observers[property_name] = Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
|
148
157
|
@array_object_observers[property_name]
|
149
158
|
end
|
150
|
-
|
151
|
-
def ensure_hash_object_observer(property_name, object, old_object = nil, options)
|
152
|
-
options ||= {}
|
153
|
-
return unless object&.is_a?(Hash)
|
154
|
-
hash_object_observer = hash_object_observer_for(property_name)
|
155
|
-
hash_observer_registration = hash_object_observer.observe(object, options)
|
156
|
-
property_observer_list(property_name).each do |observer|
|
157
|
-
my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
|
158
|
-
observer.add_dependent(my_registration => hash_observer_registration)
|
159
|
-
end
|
160
|
-
hash_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableHash)
|
161
|
-
end
|
162
|
-
|
163
|
-
def hash_object_observer_for(property_name)
|
164
|
-
@hash_object_observers ||= Concurrent::Hash.new
|
165
|
-
@hash_object_observers[property_name] = Notifier.new(self, property_name) unless @hash_object_observers.has_key?(property_name)
|
166
|
-
@hash_object_observers[property_name]
|
167
|
-
end
|
168
159
|
end
|
169
160
|
end
|
170
161
|
end
|
@@ -47,11 +47,11 @@ module Glimmer
|
|
47
47
|
end
|
48
48
|
|
49
49
|
class Registration < Struct.new(:observer, :observable, :args, keyword_init: true)
|
50
|
-
def
|
50
|
+
def deregister
|
51
51
|
observer.unobserve(observable, *args)
|
52
52
|
end
|
53
|
-
alias
|
54
|
-
alias deregister
|
53
|
+
alias unregister deregister
|
54
|
+
alias unobserve deregister
|
55
55
|
end
|
56
56
|
|
57
57
|
class << self
|
@@ -82,7 +82,7 @@ module Glimmer
|
|
82
82
|
|
83
83
|
# registers observer in an observable on args usually containing a property and options (optional)
|
84
84
|
# observer maintains registration list to unregister later
|
85
|
-
def
|
85
|
+
def observe(observable, *args)
|
86
86
|
return if observable.nil?
|
87
87
|
unless observable.is_a?(Observable)
|
88
88
|
# TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle (e.g. for SomeClass, search if there is ObservableSomeClass)
|
@@ -98,9 +98,9 @@ module Glimmer
|
|
98
98
|
observable.add_observer(self, *args)
|
99
99
|
ensure_registration_for!(observable, *args)
|
100
100
|
end
|
101
|
-
alias observe
|
101
|
+
alias register observe
|
102
102
|
|
103
|
-
def
|
103
|
+
def unobserve(observable, *args)
|
104
104
|
return unless observable.is_a?(Observable)
|
105
105
|
args = compact_args(args)
|
106
106
|
registration = registration_for(observable, *args)
|
@@ -113,27 +113,27 @@ module Glimmer
|
|
113
113
|
observable.remove_observer(self, *args)
|
114
114
|
end
|
115
115
|
end
|
116
|
-
alias unobserve
|
117
|
-
alias deregister
|
116
|
+
alias unregister unobserve
|
117
|
+
alias deregister unobserve
|
118
118
|
|
119
|
-
def
|
119
|
+
def unobserve_dependents_with_observable(registration, dependent_observable)
|
120
120
|
thedependents = dependents_for(registration).select do |thedependent|
|
121
121
|
thedependent.observable == dependent_observable
|
122
122
|
end
|
123
123
|
thedependents.each(&:deregister)
|
124
124
|
end
|
125
|
-
alias unobserve_dependents_with_observable
|
126
|
-
alias deregister_dependents_with_observable
|
125
|
+
alias unregister_dependents_with_observable unobserve_dependents_with_observable
|
126
|
+
alias deregister_dependents_with_observable unobserve_dependents_with_observable
|
127
127
|
|
128
128
|
# cleans up all registrations in observables
|
129
|
-
def
|
129
|
+
def unobserve_all_observables
|
130
130
|
registrations.values.dup.each do |registration|
|
131
131
|
registration.deregister
|
132
132
|
registrations.delete([registration.observable.object_id, registration.args])
|
133
133
|
end
|
134
134
|
end
|
135
|
-
alias unobserve_all_observables
|
136
|
-
alias deregister_all_observables
|
135
|
+
alias unregister_all_observables unobserve_all_observables
|
136
|
+
alias deregister_all_observables unobserve_all_observables
|
137
137
|
|
138
138
|
# add dependent observer to unregister when unregistering observer
|
139
139
|
def add_dependent(parent_to_dependent_hash)
|
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: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AndyMaleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-11-
|
11
|
+
date: 2021-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: array_include_methods
|
@@ -192,23 +192,24 @@ dependencies:
|
|
192
192
|
name: rake-tui
|
193
193
|
requirement: !ruby/object:Gem::Requirement
|
194
194
|
requirements:
|
195
|
-
- - "
|
195
|
+
- - ">"
|
196
196
|
- !ruby/object:Gem::Version
|
197
197
|
version: '0'
|
198
198
|
type: :development
|
199
199
|
prerelease: false
|
200
200
|
version_requirements: !ruby/object:Gem::Requirement
|
201
201
|
requirements:
|
202
|
-
- - "
|
202
|
+
- - ">"
|
203
203
|
- !ruby/object:Gem::Version
|
204
204
|
version: '0'
|
205
205
|
description: Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of
|
206
206
|
a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable
|
207
207
|
Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby
|
208
|
-
Desktop Development GUI Framework), Glimmer DSL for
|
209
|
-
|
210
|
-
|
211
|
-
|
208
|
+
Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI and
|
209
|
+
Auto-Webifier of Desktop Apps), Glimmer DSL for Tk (Ruby Desktop Development GUI
|
210
|
+
Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI
|
211
|
+
Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer
|
212
|
+
DSL for XML (& HTML), and Glimmer DSL for CSS.
|
212
213
|
email: andy.am@gmail.com
|
213
214
|
executables: []
|
214
215
|
extensions: []
|
@@ -230,6 +231,7 @@ files:
|
|
230
231
|
- lib/glimmer/data_binding/observable.rb
|
231
232
|
- lib/glimmer/data_binding/observable_array.rb
|
232
233
|
- lib/glimmer/data_binding/observable_hash.rb
|
234
|
+
- lib/glimmer/data_binding/observable_hashable.rb
|
233
235
|
- lib/glimmer/data_binding/observable_model.rb
|
234
236
|
- lib/glimmer/data_binding/observer.rb
|
235
237
|
- lib/glimmer/data_binding/shine.rb
|
@@ -264,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
264
266
|
- !ruby/object:Gem::Version
|
265
267
|
version: '0'
|
266
268
|
requirements: []
|
267
|
-
rubygems_version: 3.2.
|
269
|
+
rubygems_version: 3.2.31
|
268
270
|
signing_key:
|
269
271
|
specification_version: 4
|
270
272
|
summary: Glimmer - DSL Engine for Ruby GUI and More
|