glimmer 0.9.4 → 0.10.3
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.md +731 -147
- data/VERSION +1 -1
- data/lib/glimmer.rb +7 -12
- data/lib/glimmer/config.rb +29 -12
- data/lib/glimmer/data_binding/observable_array.rb +225 -22
- data/lib/glimmer/data_binding/observable_model.rb +5 -3
- data/lib/glimmer/data_binding/observer.rb +4 -6
- data/lib/glimmer/dsl/engine.rb +35 -43
- data/lib/glimmer/dsl/expression_handler.rb +2 -3
- metadata +24 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.3
|
data/lib/glimmer.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
# UI with domain models.
|
5
5
|
require 'logger'
|
6
6
|
require 'set'
|
7
|
+
require 'array_include_methods'
|
7
8
|
|
8
9
|
$LOAD_PATH.unshift(File.expand_path('..', __FILE__))
|
9
10
|
|
@@ -17,7 +18,6 @@ require 'glimmer/config'
|
|
17
18
|
# Glimmer DSL dynamic keywords (e.g. label, combo, etc...) are available via method_missing
|
18
19
|
module Glimmer
|
19
20
|
#TODO make it configurable to include or not include perhaps reverting to using included
|
20
|
-
REGEX_METHODS_EXCLUDED = /^(to_|\[)/
|
21
21
|
|
22
22
|
# TODO add loop detection support to avoid infinite loops (perhaps breaks after 3 repetitions and provides an option to allow it if intentional)
|
23
23
|
class << self
|
@@ -41,27 +41,22 @@ module Glimmer
|
|
41
41
|
new_loop_data = [method_symbol, args, block]
|
42
42
|
if new_loop_data == Glimmer.loop_last_data
|
43
43
|
Glimmer.loop_increment!
|
44
|
-
if Glimmer.loop == Config.loop_max_count
|
45
|
-
raise "Glimmer looped #{Config.loop_max_count} times with keyword '#{new_loop_data[0]}'! Check code for errors."
|
46
|
-
end
|
44
|
+
raise "Glimmer looped #{Config.loop_max_count} times with keyword '#{new_loop_data[0]}'! Check code for errors." if Glimmer.loop == Config.loop_max_count
|
47
45
|
else
|
48
46
|
Glimmer.loop_reset!
|
49
47
|
end
|
50
48
|
Glimmer.loop_last_data = new_loop_data
|
51
49
|
# This if statement speeds up Glimmer in girb or whenever directly including on main object
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
Glimmer::Config.logger&.debug "Interpreting keyword: #{method_symbol}"
|
50
|
+
is_excluded = Config.excluded_keyword_checkers.reduce(false) {|result, checker| result || instance_exec(method_symbol, *args, &checker) }
|
51
|
+
raise ExcludedKeywordError, "Glimmer excluded keyword: #{method_symbol}" if is_excluded
|
52
|
+
Glimmer::Config.logger.info {"Interpreting keyword: #{method_symbol}"}
|
56
53
|
Glimmer::DSL::Engine.interpret(method_symbol, *args, &block)
|
57
54
|
rescue ExcludedKeywordError => e
|
58
55
|
# TODO add a feature to show excluded keywords optionally for debugging purposes
|
59
56
|
super(method_symbol, *args, &block)
|
60
57
|
rescue InvalidKeywordError => e
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
Glimmer::Config.logger&.debug "#{e.message}\n#{e.backtrace.join("\n")}"
|
58
|
+
Glimmer::Config.logger.error {"Encountered an invalid keyword at this object: #{self}"}
|
59
|
+
Glimmer::Config.logger.error {e.full_message}
|
65
60
|
super(method_symbol, *args, &block)
|
66
61
|
end
|
67
62
|
end
|
data/lib/glimmer/config.rb
CHANGED
@@ -2,29 +2,46 @@ module Glimmer
|
|
2
2
|
module Config
|
3
3
|
class << self
|
4
4
|
LOOP_MAX_COUNT_DEFAULT = 100
|
5
|
+
REGEX_METHODS_EXCLUDED = /^(to_|\[)/
|
5
6
|
|
6
7
|
attr_writer :loop_max_count
|
7
8
|
|
9
|
+
def excluded_keyword_checkers
|
10
|
+
@excluded_keyword_checkers ||= reset_excluded_keyword_checkers!
|
11
|
+
end
|
12
|
+
|
13
|
+
def excluded_keyword_checkers=(checkers)
|
14
|
+
@excluded_keyword_checkers = checkers
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset_excluded_keyword_checkers!
|
18
|
+
@excluded_keyword_checkers = [ lambda { |method_symbol, *args| method_symbol.to_s.match(REGEX_METHODS_EXCLUDED) } ]
|
19
|
+
end
|
20
|
+
|
8
21
|
def loop_max_count
|
9
22
|
@loop_max_count ||= LOOP_MAX_COUNT_DEFAULT
|
10
23
|
end
|
11
24
|
|
12
25
|
# Returns Glimmer logger (standard Ruby logger)
|
13
|
-
def logger
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
26
|
+
def logger
|
27
|
+
reset_logger! unless defined? @@logger
|
28
|
+
@@logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger=(custom_logger)
|
32
|
+
@@logger = custom_logger
|
18
33
|
end
|
19
34
|
|
20
|
-
def
|
21
|
-
|
35
|
+
def reset_logger!
|
36
|
+
self.logger = Logger.new(STDOUT).tap do |logger|
|
37
|
+
logger.level = Logger::ERROR
|
38
|
+
begin
|
39
|
+
logger.level = ENV['GLIMMER_LOGGER_LEVEL'].strip.downcase if ENV['GLIMMER_LOGGER_LEVEL']
|
40
|
+
rescue => e
|
41
|
+
puts e.message
|
42
|
+
end
|
43
|
+
end
|
22
44
|
end
|
23
45
|
end
|
24
46
|
end
|
25
47
|
end
|
26
|
-
|
27
|
-
if ENV['GLIMMER_LOGGER_LEVEL']
|
28
|
-
Glimmer::Config.enable_logging
|
29
|
-
Glimmer::Config.logger.level = ENV['GLIMMER_LOGGER_LEVEL'].downcase
|
30
|
-
end
|
@@ -1,77 +1,280 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'array_include_methods'
|
2
3
|
|
3
4
|
require 'glimmer/data_binding/observable'
|
4
5
|
|
6
|
+
using ArrayIncludeMethods
|
7
|
+
|
5
8
|
module Glimmer
|
6
9
|
module DataBinding
|
7
10
|
# TODO prefix utility methods with double-underscore
|
8
11
|
module ObservableArray
|
9
12
|
include Observable
|
10
13
|
|
11
|
-
def add_observer(observer, element_properties
|
12
|
-
|
14
|
+
def add_observer(observer, *element_properties)
|
15
|
+
element_properties = element_properties.flatten.compact.uniq
|
16
|
+
return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
|
13
17
|
property_observer_list << observer
|
14
|
-
[
|
15
|
-
|
16
|
-
observer.observe(element, property)
|
17
|
-
end
|
18
|
-
end
|
18
|
+
observer_element_properties[observer] = element_properties_for(observer) + Set.new(element_properties)
|
19
|
+
each { |element| add_element_observer(element, observer) }
|
19
20
|
observer
|
20
21
|
end
|
22
|
+
|
23
|
+
def add_element_observers(element)
|
24
|
+
property_observer_list.each do |observer|
|
25
|
+
add_element_observer(element, observer)
|
26
|
+
end
|
27
|
+
end
|
21
28
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
29
|
+
def add_element_observer(element, observer)
|
30
|
+
element_properties_for(observer).each do |property|
|
31
|
+
observer.observe(element, property)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_observer(observer, *element_properties)
|
36
|
+
element_properties = element_properties.flatten.compact.uniq
|
37
|
+
if !element_properties.empty?
|
38
|
+
old_element_properties = element_properties_for(observer)
|
39
|
+
observer_element_properties[observer] = element_properties_for(observer) - Set.new(element_properties)
|
25
40
|
each do |element|
|
26
|
-
|
41
|
+
element_properties.each do |property|
|
42
|
+
observer.unobserve(element, property)
|
43
|
+
end
|
27
44
|
end
|
28
45
|
end
|
46
|
+
if element_properties_for(observer).empty?
|
47
|
+
property_observer_list.delete(observer)
|
48
|
+
observer_element_properties.delete(observer)
|
49
|
+
each { |element| remove_element_observer(element, observer) }
|
50
|
+
end
|
29
51
|
observer
|
30
52
|
end
|
31
53
|
|
54
|
+
def remove_element_observers(element)
|
55
|
+
property_observer_list.each do |observer|
|
56
|
+
remove_element_observer(element, observer)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_element_observer(element, observer)
|
61
|
+
element_properties_for(observer).each do |property|
|
62
|
+
observer.unobserve(element, property)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
32
66
|
def has_observer?(observer)
|
33
67
|
property_observer_list.include?(observer)
|
34
68
|
end
|
69
|
+
|
70
|
+
def has_observer_element_properties?(observer, element_properties)
|
71
|
+
element_properties_for(observer).to_a.include_all?(element_properties)
|
72
|
+
end
|
35
73
|
|
36
74
|
def property_observer_list
|
37
75
|
@property_observer_list ||= Set.new
|
38
76
|
end
|
39
77
|
|
78
|
+
def observer_element_properties
|
79
|
+
@observer_element_properties ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def element_properties_for(observer)
|
83
|
+
observer_element_properties[observer] ||= Set.new
|
84
|
+
end
|
85
|
+
|
40
86
|
def notify_observers
|
41
|
-
property_observer_list.each
|
87
|
+
property_observer_list.to_a.each(&:call)
|
42
88
|
end
|
43
89
|
|
44
90
|
def <<(element)
|
45
|
-
super(element)
|
46
|
-
|
91
|
+
super(element).tap do
|
92
|
+
add_element_observers(element)
|
93
|
+
notify_observers
|
94
|
+
end
|
47
95
|
end
|
96
|
+
alias push <<
|
48
97
|
|
49
98
|
def []=(index, value)
|
50
99
|
old_value = self[index]
|
51
100
|
unregister_dependent_observers(old_value)
|
52
|
-
|
53
|
-
|
101
|
+
remove_element_observers(old_value)
|
102
|
+
add_element_observers(value)
|
103
|
+
super(index, value).tap do
|
104
|
+
notify_observers
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def pop
|
109
|
+
popped_element = last
|
110
|
+
unregister_dependent_observers(popped_element)
|
111
|
+
remove_element_observers(popped_element)
|
112
|
+
super.tap do
|
113
|
+
notify_observers
|
114
|
+
end
|
54
115
|
end
|
55
116
|
|
56
117
|
def delete(element)
|
57
118
|
unregister_dependent_observers(element)
|
58
|
-
|
59
|
-
|
119
|
+
remove_element_observers(element)
|
120
|
+
super(element).tap do
|
121
|
+
notify_observers
|
122
|
+
end
|
60
123
|
end
|
61
124
|
|
62
125
|
def delete_at(index)
|
63
126
|
old_value = self[index]
|
64
127
|
unregister_dependent_observers(old_value)
|
65
|
-
|
66
|
-
|
128
|
+
remove_element_observers(old_value)
|
129
|
+
super(index).tap do
|
130
|
+
notify_observers
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def delete_if(&block)
|
135
|
+
if block_given?
|
136
|
+
old_array = Array.new(self)
|
137
|
+
super(&block).tap do |new_array|
|
138
|
+
(old_array - new_array).each do |element|
|
139
|
+
unregister_dependent_observers(element)
|
140
|
+
remove_element_observers(element)
|
141
|
+
end
|
142
|
+
notify_observers
|
143
|
+
end
|
144
|
+
else
|
145
|
+
super
|
146
|
+
end
|
67
147
|
end
|
68
148
|
|
69
149
|
def clear
|
70
150
|
each do |old_value|
|
71
151
|
unregister_dependent_observers(old_value)
|
152
|
+
remove_element_observers(old_value)
|
153
|
+
end
|
154
|
+
super.tap do
|
155
|
+
notify_observers
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def reverse!
|
160
|
+
super.tap do
|
161
|
+
notify_observers
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def collect!(&block)
|
166
|
+
if block_given?
|
167
|
+
each do |old_value|
|
168
|
+
unregister_dependent_observers(old_value)
|
169
|
+
remove_element_observers(old_value)
|
170
|
+
end
|
171
|
+
super(&block).tap do
|
172
|
+
each do |element|
|
173
|
+
add_element_observers(element)
|
174
|
+
end
|
175
|
+
notify_observers
|
176
|
+
end
|
177
|
+
else
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
alias map! collect!
|
182
|
+
|
183
|
+
def compact!
|
184
|
+
super.tap do
|
185
|
+
notify_observers
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def flatten!(level=nil)
|
190
|
+
each do |old_value|
|
191
|
+
unregister_dependent_observers(old_value)
|
192
|
+
remove_element_observers(old_value)
|
193
|
+
end
|
194
|
+
(level.nil? ? super() : super(level)).tap do
|
195
|
+
each do |element|
|
196
|
+
add_element_observers(element)
|
197
|
+
end
|
198
|
+
notify_observers
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def rotate!(count=1)
|
203
|
+
super(count).tap do
|
204
|
+
notify_observers
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def select!(&block)
|
209
|
+
if block_given?
|
210
|
+
old_array = Array.new(self)
|
211
|
+
super(&block).tap do
|
212
|
+
(old_array - self).each do |old_value|
|
213
|
+
unregister_dependent_observers(old_value)
|
214
|
+
remove_element_observers(old_value)
|
215
|
+
end
|
216
|
+
notify_observers
|
217
|
+
end
|
218
|
+
else
|
219
|
+
super
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def shuffle!(hash = nil)
|
224
|
+
(hash.nil? ? super() : super(random: hash[:random])).tap do
|
225
|
+
notify_observers
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def slice!(arg1, arg2=nil)
|
230
|
+
old_array = Array.new(self)
|
231
|
+
(arg2.nil? ? super(arg1) : super(arg1, arg2)).tap do
|
232
|
+
(old_array - self).each do |old_value|
|
233
|
+
unregister_dependent_observers(old_value)
|
234
|
+
remove_element_observers(old_value)
|
235
|
+
end
|
236
|
+
notify_observers
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def sort!(&block)
|
241
|
+
(block.nil? ? super() : super(&block)).tap do
|
242
|
+
notify_observers
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def sort_by!(&block)
|
247
|
+
(block.nil? ? super() : super(&block)).tap do
|
248
|
+
notify_observers
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def uniq!(&block)
|
253
|
+
each do |old_value|
|
254
|
+
unregister_dependent_observers(old_value)
|
255
|
+
remove_element_observers(old_value)
|
256
|
+
end
|
257
|
+
(block.nil? ? super() : super(&block)).tap do
|
258
|
+
each do |element|
|
259
|
+
add_element_observers(element)
|
260
|
+
end
|
261
|
+
notify_observers
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def reject!(&block)
|
266
|
+
if block_given?
|
267
|
+
old_array = Array.new(self)
|
268
|
+
super(&block).tap do
|
269
|
+
(old_array - self).each do |old_value|
|
270
|
+
unregister_dependent_observers(old_value)
|
271
|
+
remove_element_observers(old_value)
|
272
|
+
end
|
273
|
+
notify_observers
|
274
|
+
end
|
275
|
+
else
|
276
|
+
super
|
72
277
|
end
|
73
|
-
super()
|
74
|
-
notify_observers
|
75
278
|
end
|
76
279
|
|
77
280
|
def unregister_dependent_observers(old_value)
|
@@ -34,7 +34,7 @@ module Glimmer
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def has_observer_for_any_property?(observer)
|
37
|
-
property_observer_hash.values.map(&:to_a).
|
37
|
+
property_observer_hash.values.map(&:to_a).reduce(:+).include?(observer)
|
38
38
|
end
|
39
39
|
|
40
40
|
def property_observer_hash
|
@@ -47,7 +47,9 @@ module Glimmer
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def notify_observers(property_name)
|
50
|
-
property_observer_list(property_name).each
|
50
|
+
property_observer_list(property_name).to_a.each do |observer|
|
51
|
+
observer.call(send(property_name))
|
52
|
+
end
|
51
53
|
end
|
52
54
|
#TODO upon updating values, make sure dependent observers are cleared (not added as dependents here)
|
53
55
|
|
@@ -70,7 +72,7 @@ module Glimmer
|
|
70
72
|
end
|
71
73
|
rescue => e
|
72
74
|
#ignore writing if no property writer exists
|
73
|
-
Glimmer::Config.logger
|
75
|
+
Glimmer::Config.logger.debug {"No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"}
|
74
76
|
end
|
75
77
|
|
76
78
|
def unregister_dependent_observers(property_name, old_value)
|
@@ -59,6 +59,7 @@ module Glimmer
|
|
59
59
|
# registers observer in an observable on a property (optional)
|
60
60
|
# observer maintains registration list to unregister later
|
61
61
|
def register(observable, property = nil)
|
62
|
+
return if observable.nil?
|
62
63
|
unless observable.is_a?(Observable)
|
63
64
|
# TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
|
64
65
|
if observable.is_a?(Array)
|
@@ -75,6 +76,7 @@ module Glimmer
|
|
75
76
|
alias observe register
|
76
77
|
|
77
78
|
def unregister(observable, property = nil)
|
79
|
+
return unless observable.is_a?(Observable)
|
78
80
|
# TODO optimize performance in the future via indexing and/or making a registration official object/class
|
79
81
|
observable.remove_observer(*[self, property].compact)
|
80
82
|
registration = registration_for(observable, property)
|
@@ -90,16 +92,12 @@ module Glimmer
|
|
90
92
|
thedependents = dependents_for(registration).select do |thedependent|
|
91
93
|
thedependent.observable == dependent_observable
|
92
94
|
end
|
93
|
-
thedependents.each
|
94
|
-
thedependent.unregister
|
95
|
-
end
|
95
|
+
thedependents.each(&:unregister)
|
96
96
|
end
|
97
97
|
|
98
98
|
# cleans up all registrations in observables
|
99
99
|
def unregister_all_observables
|
100
|
-
registrations.each
|
101
|
-
registration.unregister
|
102
|
-
end
|
100
|
+
registrations.each(&:unregister)
|
103
101
|
end
|
104
102
|
alias unobserve_all_observables unregister_all_observables
|
105
103
|
|