glimmer 0.3.5 → 0.4.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 +4 -4
- data/README.markdown +94 -32
- data/lib/glimmer.rb +4 -6
- data/lib/glimmer/command_handler.rb +10 -0
- data/lib/glimmer/command_handler_chain_factory.rb +32 -0
- data/lib/glimmer/command_handler_chain_link.rb +21 -0
- data/lib/{command_handlers.rb → glimmer/command_handlers.rb} +21 -17
- data/lib/glimmer/command_handlers/bind_command_handler.rb +51 -0
- data/lib/glimmer/command_handlers/color_command_handler.rb +26 -0
- data/lib/glimmer/command_handlers/combo_selection_data_binding_command_handler.rb +40 -0
- data/lib/glimmer/command_handlers/data_binding_command_handler.rb +69 -0
- data/lib/glimmer/command_handlers/display_command_handler.rb +16 -0
- data/lib/glimmer/command_handlers/list_selection_data_binding_command_handler.rb +45 -0
- data/lib/glimmer/command_handlers/models/g_color.rb +34 -0
- data/lib/glimmer/command_handlers/models/g_display.rb +26 -0
- data/lib/glimmer/command_handlers/models/g_font.rb +62 -0
- data/lib/glimmer/command_handlers/models/g_runnable.rb +13 -0
- data/lib/glimmer/command_handlers/models/g_shell.rb +27 -0
- data/lib/glimmer/command_handlers/models/g_swt.rb +22 -0
- data/lib/glimmer/command_handlers/models/g_tab_item_composite.rb +33 -0
- data/lib/glimmer/command_handlers/models/g_widget.rb +199 -0
- data/lib/glimmer/command_handlers/models/g_widget_listener.rb +11 -0
- data/lib/glimmer/command_handlers/models/list_selection_binding.rb +47 -0
- data/lib/glimmer/command_handlers/models/model_binding.rb +206 -0
- data/lib/glimmer/command_handlers/models/observable.rb +11 -0
- data/lib/glimmer/command_handlers/models/observable_array.rb +104 -0
- data/lib/glimmer/command_handlers/models/observable_model.rb +105 -0
- data/lib/glimmer/command_handlers/models/observer.rb +115 -0
- data/lib/glimmer/command_handlers/models/table_items_binding.rb +45 -0
- data/lib/glimmer/command_handlers/models/tree_items_binding.rb +49 -0
- data/lib/glimmer/command_handlers/models/widget_binding.rb +29 -0
- data/lib/glimmer/command_handlers/shell_command_handler.rb +17 -0
- data/lib/glimmer/command_handlers/tab_item_command_handler.rb +21 -0
- data/lib/glimmer/command_handlers/table_column_properties_data_binding_command_handler.rb +25 -0
- data/lib/glimmer/command_handlers/table_items_data_binding_command_handler.rb +30 -0
- data/lib/glimmer/command_handlers/tree_items_data_binding_command_handler.rb +29 -0
- data/lib/glimmer/command_handlers/tree_properties_data_binding_command_handler.rb +25 -0
- data/lib/glimmer/command_handlers/widget_command_handler.rb +22 -0
- data/lib/glimmer/command_handlers/widget_listener_command_handler.rb +39 -0
- data/lib/glimmer/command_handlers/widget_method_command_handler.rb +21 -0
- data/lib/glimmer/parent.rb +7 -0
- data/lib/{shine.rb → glimmer/shine.rb} +1 -1
- data/lib/glimmer/swt_packages.rb +13 -0
- data/lib/{xml_command_handlers.rb → glimmer/xml_command_handlers.rb} +10 -8
- data/lib/glimmer/xml_command_handlers/html_command_handler.rb +47 -0
- data/lib/glimmer/xml_command_handlers/models/depth_first_search_iterator.rb +19 -0
- data/lib/glimmer/xml_command_handlers/models/name_space_visitor.rb +20 -0
- data/lib/glimmer/xml_command_handlers/models/node.rb +82 -0
- data/lib/glimmer/xml_command_handlers/models/node_visitor.rb +11 -0
- data/lib/glimmer/xml_command_handlers/models/xml_visitor.rb +61 -0
- data/lib/glimmer/xml_command_handlers/xml_command_handler.rb +20 -0
- data/lib/glimmer/xml_command_handlers/xml_name_space_command_handler.rb +33 -0
- data/lib/glimmer/xml_command_handlers/xml_tag_command_handler.rb +25 -0
- data/lib/glimmer/xml_command_handlers/xml_text_command_handler.rb +21 -0
- metadata +53 -54
- data/lib/command_handler.rb +0 -8
- data/lib/command_handler_chain_factory.rb +0 -30
- data/lib/command_handler_chain_link.rb +0 -19
- data/lib/command_handlers/bind_command_handler.rb +0 -49
- data/lib/command_handlers/color_command_handler.rb +0 -24
- data/lib/command_handlers/combo_selection_data_binding_command_handler.rb +0 -38
- data/lib/command_handlers/data_binding_command_handler.rb +0 -67
- data/lib/command_handlers/list_selection_data_binding_command_handler.rb +0 -43
- data/lib/command_handlers/models/block_observer.rb +0 -14
- data/lib/command_handlers/models/list_selection_binding.rb +0 -45
- data/lib/command_handlers/models/model_binding.rb +0 -205
- data/lib/command_handlers/models/observable.rb +0 -9
- data/lib/command_handlers/models/observable_array.rb +0 -102
- data/lib/command_handlers/models/observable_model.rb +0 -103
- data/lib/command_handlers/models/observer.rb +0 -88
- data/lib/command_handlers/models/r_color.rb +0 -32
- data/lib/command_handlers/models/r_font.rb +0 -60
- data/lib/command_handlers/models/r_runnable.rb +0 -11
- data/lib/command_handlers/models/r_shell.rb +0 -24
- data/lib/command_handlers/models/r_swt.rb +0 -18
- data/lib/command_handlers/models/r_tab_item_composite.rb +0 -31
- data/lib/command_handlers/models/r_widget.rb +0 -183
- data/lib/command_handlers/models/r_widget_listener.rb +0 -9
- data/lib/command_handlers/models/table_items_binding.rb +0 -43
- data/lib/command_handlers/models/tree_items_binding.rb +0 -47
- data/lib/command_handlers/models/widget_binding.rb +0 -27
- data/lib/command_handlers/shell_command_handler.rb +0 -15
- data/lib/command_handlers/tab_item_command_handler.rb +0 -19
- data/lib/command_handlers/table_column_properties_data_binding_command_handler.rb +0 -23
- data/lib/command_handlers/table_items_data_binding_command_handler.rb +0 -28
- data/lib/command_handlers/tree_items_data_binding_command_handler.rb +0 -27
- data/lib/command_handlers/tree_properties_data_binding_command_handler.rb +0 -23
- data/lib/command_handlers/widget_command_handler.rb +0 -20
- data/lib/command_handlers/widget_listener_command_handler.rb +0 -37
- data/lib/command_handlers/widget_method_command_handler.rb +0 -19
- data/lib/parent.rb +0 -5
- data/lib/string.rb +0 -8
- data/lib/swt_packages.rb +0 -11
- data/lib/symbol.rb +0 -10
- data/lib/xml_command_handlers/html_command_handler.rb +0 -45
- data/lib/xml_command_handlers/models/depth_first_search_iterator.rb +0 -17
- data/lib/xml_command_handlers/models/name_space_visitor.rb +0 -18
- data/lib/xml_command_handlers/models/node.rb +0 -80
- data/lib/xml_command_handlers/models/node_visitor.rb +0 -9
- data/lib/xml_command_handlers/models/xml_visitor.rb +0 -59
- data/lib/xml_command_handlers/xml_command_handler.rb +0 -18
- data/lib/xml_command_handlers/xml_name_space_command_handler.rb +0 -31
- data/lib/xml_command_handlers/xml_tag_command_handler.rb +0 -23
- data/lib/xml_command_handlers/xml_text_command_handler.rb +0 -19
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'observable'
|
2
|
+
require_relative 'observer'
|
3
|
+
|
4
|
+
module Glimmer
|
5
|
+
# SWT List widget selection binding
|
6
|
+
class ListSelectionBinding
|
7
|
+
include Glimmer
|
8
|
+
include Observable
|
9
|
+
include Observer
|
10
|
+
|
11
|
+
attr_reader :widget
|
12
|
+
@@property_type_updaters = {
|
13
|
+
:string => lambda { |widget, value| widget.widget.select(widget.widget.index_of(value.to_s)) },
|
14
|
+
:array => lambda { |widget, value| widget.widget.selection=((value or []).to_java :string) }
|
15
|
+
}
|
16
|
+
@@property_evaluators = {
|
17
|
+
:string => lambda do |selection_array|
|
18
|
+
return nil if selection_array.empty?
|
19
|
+
selection_array[0]
|
20
|
+
end,
|
21
|
+
:array => lambda do |selection_array|
|
22
|
+
selection_array
|
23
|
+
end
|
24
|
+
}
|
25
|
+
# Initialize with list widget and property_type
|
26
|
+
# property_type :string represents default list single selection
|
27
|
+
# property_type :array represents list multi selection
|
28
|
+
def initialize(widget, property_type)
|
29
|
+
property_type = :string if property_type.nil? or property_type == :undefined
|
30
|
+
@widget = widget
|
31
|
+
@property_type = property_type
|
32
|
+
add_contents(@widget) {
|
33
|
+
on_widget_disposed { |dispose_event|
|
34
|
+
unregister_all_observables
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
def call(value)
|
39
|
+
@@property_type_updaters[@property_type].call(@widget, value) unless evaluate_property == value
|
40
|
+
end
|
41
|
+
def evaluate_property
|
42
|
+
selection_array = @widget.widget.send("selection").to_a
|
43
|
+
property_value = @@property_evaluators[@property_type].call(selection_array)
|
44
|
+
return property_value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require_relative 'observable'
|
2
|
+
require_relative 'observer'
|
3
|
+
|
4
|
+
module Glimmer
|
5
|
+
class ModelBinding
|
6
|
+
include Observable
|
7
|
+
include Observer
|
8
|
+
|
9
|
+
attr_reader :property_type, :binding_options
|
10
|
+
@@property_type_converters = {
|
11
|
+
:undefined => lambda { |value| value },
|
12
|
+
:fixnum => lambda { |value| value.to_i },
|
13
|
+
:array => lambda { |value| value.to_a }
|
14
|
+
}
|
15
|
+
def initialize(base_model, property_name_expression, property_type = :undefined, binding_options = nil)
|
16
|
+
property_type = :undefined if property_type.nil?
|
17
|
+
@base_model = base_model
|
18
|
+
@property_name_expression = property_name_expression
|
19
|
+
@property_type = property_type
|
20
|
+
@binding_options = binding_options || {}
|
21
|
+
if computed?
|
22
|
+
@computed_model_bindings = computed_by.map do |computed_by_property_expression|
|
23
|
+
self.class.new(base_model, computed_by_property_expression, :undefined, computed_binding_options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def model
|
28
|
+
nested_property? ? nested_model : base_model
|
29
|
+
end
|
30
|
+
# e.g. person.address.state returns [person, person.address]
|
31
|
+
def nested_models
|
32
|
+
@nested_models = [base_model]
|
33
|
+
model_property_names.reduce(base_model) do |reduced_model, nested_model_property_name|
|
34
|
+
if reduced_model.nil?
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
invoke_property_reader(reduced_model, nested_model_property_name).tap do |new_reduced_model|
|
38
|
+
@nested_models << new_reduced_model
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@nested_models
|
43
|
+
end
|
44
|
+
def nested_model
|
45
|
+
nested_models.last
|
46
|
+
end
|
47
|
+
def base_model
|
48
|
+
@base_model
|
49
|
+
end
|
50
|
+
def property_name
|
51
|
+
nested_property? ? nested_property_name : property_name_expression
|
52
|
+
end
|
53
|
+
# All nested property names
|
54
|
+
# e.g. property name expression "address.state" gives ['address', 'state']
|
55
|
+
# If there are any indexed property names, this returns indexes as properties.
|
56
|
+
# e.g. property name expression "addresses[1].state" gives ['addresses', '[1]', 'state']
|
57
|
+
def nested_property_names
|
58
|
+
@nested_property_names ||= property_name_expression.split(".").map {|pne| pne.match(/([^\[]+)(\[[^\]]+\])?/).to_a.drop(1)}.flatten.compact
|
59
|
+
end
|
60
|
+
# Final nested property name
|
61
|
+
# e.g. property name expression "address.state" gives :state
|
62
|
+
def nested_property_name
|
63
|
+
nested_property_names.last
|
64
|
+
end
|
65
|
+
# Model representing nested property names
|
66
|
+
# e.g. property name expression "address.state" gives [:address]
|
67
|
+
def model_property_names
|
68
|
+
nested_property_names[0...-1]
|
69
|
+
end
|
70
|
+
def nested_property?
|
71
|
+
property_name_expression.match(/[.\[]/)
|
72
|
+
end
|
73
|
+
def property_name_expression
|
74
|
+
@property_name_expression
|
75
|
+
end
|
76
|
+
def computed?
|
77
|
+
!!computed_by
|
78
|
+
end
|
79
|
+
def computed_by
|
80
|
+
@binding_options[:computed_by]
|
81
|
+
end
|
82
|
+
def computed_binding_options
|
83
|
+
@binding_options.reject {|k,v| k == :computed_by}
|
84
|
+
end
|
85
|
+
def nested_property_observers_for(observer)
|
86
|
+
@nested_property_observers_collection ||= {}
|
87
|
+
unless @nested_property_observers_collection.has_key?(observer)
|
88
|
+
@nested_property_observers_collection[observer] = nested_property_names.reduce({}) do |output, property_name|
|
89
|
+
output.merge(
|
90
|
+
property_name => Observer.proc do |new_value|
|
91
|
+
# Ensure reattaching observers when a higher level nested property is updated (e.g. person.address changes reattaches person.address.street observer)
|
92
|
+
add_observer(observer)
|
93
|
+
observer.call(evaluate_property)
|
94
|
+
end
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# @nested_property_observers_collection[observer].keys.each_with_index do |property_name, i|
|
99
|
+
# previous_property_name = nested_property_names[i-1]
|
100
|
+
# previous_observer = @nested_property_observers_collection[observer][previous_property_name]
|
101
|
+
# nested_property_observer = @nested_property_observers_collection[observer][property_name]
|
102
|
+
# previous_observer.add_dependent(nested_property_observer) unless previous_observer.nil?
|
103
|
+
# end
|
104
|
+
# TODO remove this brainstorming
|
105
|
+
# person.addresses[1].streets[2].number
|
106
|
+
# person.addresses[1] = ...
|
107
|
+
@nested_property_observers_collection[observer]
|
108
|
+
end
|
109
|
+
def add_observer(observer)
|
110
|
+
if computed?
|
111
|
+
add_computed_observers(observer)
|
112
|
+
elsif nested_property?
|
113
|
+
add_nested_observers(observer)
|
114
|
+
else
|
115
|
+
observer.observe(model, property_name)
|
116
|
+
observer.add_dependent([self, nil] => [observer, model, property_name])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
def remove_observer(observer)
|
120
|
+
if computed?
|
121
|
+
@computed_model_bindings.each do |computed_model_binding|
|
122
|
+
computed_observer_for(observer).unobserve(computed_model_binding)
|
123
|
+
end
|
124
|
+
@computed_observer_collection.delete(observer)
|
125
|
+
elsif nested_property?
|
126
|
+
nested_property_observers_for(observer).clear
|
127
|
+
else
|
128
|
+
observer.unobserve(model, property_name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def computed_observer_for(observer)
|
132
|
+
@computed_observer_collection ||= {}
|
133
|
+
unless @computed_observer_collection.has_key?(observer)
|
134
|
+
@computed_observer_collection[observer] = Observer.proc do |new_value|
|
135
|
+
observer.call(evaluate_property)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
@computed_observer_collection[observer]
|
139
|
+
end
|
140
|
+
def add_computed_observers(observer)
|
141
|
+
@computed_model_bindings.each do |computed_model_binding|
|
142
|
+
computed_observer_for(observer).observe(computed_model_binding)
|
143
|
+
observer.add_dependent([self, nil] => [computed_observer_for(observer), computed_model_binding, nil])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
def add_nested_observers(observer)
|
147
|
+
nested_property_observers = nested_property_observers_for(observer)
|
148
|
+
nested_models.zip(nested_property_names).each_with_index do |zip, i|
|
149
|
+
model, property_name = zip
|
150
|
+
nested_property_observer = nested_property_observers[property_name]
|
151
|
+
previous_index = i - 1
|
152
|
+
parent_model = previous_index.negative? ? self : nested_models[previous_index]
|
153
|
+
parent_property_name = previous_index.negative? ? nil : nested_property_names[previous_index]
|
154
|
+
parent_observer = previous_index.negative? ? observer : nested_property_observers[parent_property_name]
|
155
|
+
parent_property_name = nil if parent_property_name.to_s.start_with?('[')
|
156
|
+
unless model.nil?
|
157
|
+
if property_indexed?(property_name)
|
158
|
+
# TODO figure out a way to deal with this more uniformly
|
159
|
+
nested_property_observer.observe(model)
|
160
|
+
parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, nil])
|
161
|
+
else
|
162
|
+
nested_property_observer.observe(model, property_name)
|
163
|
+
parent_observer.add_dependent([parent_model, parent_property_name] => [nested_property_observer, model, property_name])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
def call(value)
|
169
|
+
return if model.nil?
|
170
|
+
converted_value = @@property_type_converters[@property_type].call(value)
|
171
|
+
invoke_property_writer(model, "#{property_name}=", converted_value) unless evaluate_property == converted_value
|
172
|
+
end
|
173
|
+
def evaluate_property
|
174
|
+
invoke_property_reader(model, property_name) unless model.nil?
|
175
|
+
end
|
176
|
+
def evaluate_options_property
|
177
|
+
model.send(options_property_name) unless model.nil?
|
178
|
+
end
|
179
|
+
def options_property_name
|
180
|
+
self.property_name + "_options"
|
181
|
+
end
|
182
|
+
def property_indexed?(property_expression)
|
183
|
+
property_expression.start_with?('[')
|
184
|
+
end
|
185
|
+
def invoke_property_reader(object, property_expression)
|
186
|
+
if property_indexed?(property_expression)
|
187
|
+
property_method = '[]'
|
188
|
+
property_argument = property_expression[1...-1]
|
189
|
+
property_argument = property_argument.to_i if property_argument.match(/\d+/)
|
190
|
+
object.send(property_method, property_argument)
|
191
|
+
else
|
192
|
+
object.send(property_expression)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
def invoke_property_writer(object, property_expression, value)
|
196
|
+
if property_indexed?(property_expression)
|
197
|
+
property_method = '[]='
|
198
|
+
property_argument = property_expression[1...-2]
|
199
|
+
property_argument = property_argument.to_i if property_argument.match(/\d+/)
|
200
|
+
object.send(property_method, property_argument, value)
|
201
|
+
else
|
202
|
+
object.send(property_expression, value)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require_relative 'observable'
|
4
|
+
|
5
|
+
module Glimmer
|
6
|
+
# TODO prefix utility methods with double-underscore
|
7
|
+
module ObservableArray
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
def add_observer(observer, element_properties=nil)
|
11
|
+
return observer if has_observer?(observer) && element_properties.nil?
|
12
|
+
property_observer_list << observer
|
13
|
+
[element_properties].flatten.compact.each do |property|
|
14
|
+
each do |element|
|
15
|
+
observer.observe(element, property)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
observer
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove_observer(observer, element_properties=nil)
|
22
|
+
property_observer_list.delete(observer)
|
23
|
+
[element_properties].flatten.compact.each do |property|
|
24
|
+
each do |element|
|
25
|
+
observer.unobserve(element, property)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
observer
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_observer?(observer)
|
32
|
+
property_observer_list.include?(observer)
|
33
|
+
end
|
34
|
+
|
35
|
+
def property_observer_list
|
36
|
+
@property_observer_list ||= Set.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify_observers
|
40
|
+
property_observer_list.each {|observer| observer.call}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.extend_object(array)
|
44
|
+
array.instance_eval("alias __original_add <<")
|
45
|
+
array.instance_eval <<-end_eval, __FILE__, __LINE__
|
46
|
+
def <<(value)
|
47
|
+
self.__original_add(value)
|
48
|
+
notify_observers
|
49
|
+
end
|
50
|
+
end_eval
|
51
|
+
|
52
|
+
array.instance_eval("alias __original_set_value []=")
|
53
|
+
array.instance_eval <<-end_eval, __FILE__, __LINE__
|
54
|
+
def []=(index, value)
|
55
|
+
old_value = self[index]
|
56
|
+
unregister_dependent_observers(old_value)
|
57
|
+
self.__original_set_value(index, value)
|
58
|
+
notify_observers
|
59
|
+
end
|
60
|
+
end_eval
|
61
|
+
|
62
|
+
array.instance_eval("alias __original_delete delete")
|
63
|
+
array.instance_eval <<-end_eval, __FILE__, __LINE__
|
64
|
+
def delete(value)
|
65
|
+
unregister_dependent_observers(value)
|
66
|
+
self.__original_delete(value)
|
67
|
+
notify_observers
|
68
|
+
end
|
69
|
+
end_eval
|
70
|
+
|
71
|
+
array.instance_eval("alias __original_delete_at delete_at")
|
72
|
+
array.instance_eval <<-end_eval, __FILE__, __LINE__
|
73
|
+
def delete_at(index)
|
74
|
+
old_value = self[index]
|
75
|
+
unregister_dependent_observers(old_value)
|
76
|
+
self.__original_delete_at(index)
|
77
|
+
notify_observers
|
78
|
+
end
|
79
|
+
end_eval
|
80
|
+
|
81
|
+
array.instance_eval("alias __original_clear clear")
|
82
|
+
array.instance_eval <<-end_eval, __FILE__, __LINE__
|
83
|
+
def clear
|
84
|
+
each do |old_value|
|
85
|
+
unregister_dependent_observers(old_value)
|
86
|
+
end
|
87
|
+
self.__original_clear
|
88
|
+
notify_observers
|
89
|
+
end
|
90
|
+
end_eval
|
91
|
+
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def unregister_dependent_observers(old_value)
|
96
|
+
# TODO look into optimizing this
|
97
|
+
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
|
98
|
+
property_observer_list.each do |observer|
|
99
|
+
observer.unregister_dependents_with_observable([self, nil], old_value)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require_relative 'observable'
|
4
|
+
require_relative 'observer'
|
5
|
+
|
6
|
+
module Glimmer
|
7
|
+
# TODO prefix utility methods with double-underscore
|
8
|
+
module ObservableModel
|
9
|
+
include Observable
|
10
|
+
|
11
|
+
class Notifier
|
12
|
+
include Observer
|
13
|
+
def initialize(observable_model, property_name)
|
14
|
+
@observable_model = observable_model
|
15
|
+
@property_name = property_name
|
16
|
+
end
|
17
|
+
def call(new_value=nil)
|
18
|
+
@observable_model.notify_observers(@property_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_observer(observer, property_name)
|
23
|
+
return observer if has_observer?(observer, property_name)
|
24
|
+
property_observer_list(property_name) << observer
|
25
|
+
add_property_writer_observers(property_name)
|
26
|
+
observer
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_observer(observer, property_name)
|
30
|
+
property_observer_list(property_name).delete(observer)
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_observer?(observer, property_name)
|
34
|
+
property_observer_list(property_name).include?(observer)
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_observer_for_any_property?(observer)
|
38
|
+
property_observer_hash.values.map(&:to_a).sum.include?(observer)
|
39
|
+
end
|
40
|
+
|
41
|
+
def property_observer_hash
|
42
|
+
@property_observers = Hash.new unless @property_observers
|
43
|
+
@property_observers
|
44
|
+
end
|
45
|
+
|
46
|
+
def property_observer_list(property_name)
|
47
|
+
property_observer_hash[property_name.to_sym] = Set.new unless property_observer_hash[property_name.to_sym]
|
48
|
+
property_observer_hash[property_name.to_sym]
|
49
|
+
end
|
50
|
+
|
51
|
+
def notify_observers(property_name)
|
52
|
+
property_observer_list(property_name).each {|observer| observer.call(send(property_name))}
|
53
|
+
end
|
54
|
+
#TODO upon updating values, make sure dependent observers are cleared (not added as dependents here)
|
55
|
+
|
56
|
+
def add_property_writer_observers(property_name)
|
57
|
+
property_writer_name = "#{property_name}="
|
58
|
+
method(property_writer_name)
|
59
|
+
ensure_array_object_observer(property_name, send(property_name))
|
60
|
+
begin
|
61
|
+
method("__original_#{property_writer_name}")
|
62
|
+
rescue
|
63
|
+
instance_eval "alias __original_#{property_writer_name} #{property_writer_name}"
|
64
|
+
instance_eval <<-end_eval, __FILE__, __LINE__
|
65
|
+
def #{property_writer_name}(value)
|
66
|
+
old_value = self.#{property_name}
|
67
|
+
unregister_dependent_observers('#{property_name}', old_value)
|
68
|
+
self.__original_#{property_writer_name}(value)
|
69
|
+
notify_observers('#{property_name}')
|
70
|
+
ensure_array_object_observer('#{property_name}', value, old_value)
|
71
|
+
end
|
72
|
+
end_eval
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
# ignore writing if no property writer exists
|
76
|
+
Glimmer.logger.debug "No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def unregister_dependent_observers(property_name, old_value)
|
80
|
+
# TODO look into optimizing this
|
81
|
+
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
|
82
|
+
property_observer_list(property_name).each do |observer|
|
83
|
+
observer.unregister_dependents_with_observable([self, property_name], old_value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def ensure_array_object_observer(property_name, object, old_object = nil)
|
88
|
+
return unless object.is_a?(Array)
|
89
|
+
array_object_observer = array_object_observer_for(property_name)
|
90
|
+
array_object_observer.observe(object)
|
91
|
+
property_observer_list(property_name).each do |observer|
|
92
|
+
observer.add_dependent([self, property_name] => [array_object_observer, object, nil])
|
93
|
+
end
|
94
|
+
array_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableArray)
|
95
|
+
end
|
96
|
+
|
97
|
+
def array_object_observer_for(property_name)
|
98
|
+
@array_object_observers ||= {}
|
99
|
+
unless @array_object_observers.has_key?(property_name)
|
100
|
+
@array_object_observers[property_name] = ObservableModel::Notifier.new(self, property_name)
|
101
|
+
end
|
102
|
+
@array_object_observers[property_name]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|