hyalite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/Rakefile +7 -0
  6. data/client/hyalite.rb +98 -0
  7. data/client/hyalite/adler32.rb +33 -0
  8. data/client/hyalite/browser_event.rb +201 -0
  9. data/client/hyalite/callback_queue.rb +22 -0
  10. data/client/hyalite/component.rb +77 -0
  11. data/client/hyalite/composite_component.rb +237 -0
  12. data/client/hyalite/dom_component.rb +329 -0
  13. data/client/hyalite/dom_operations.rb +17 -0
  14. data/client/hyalite/dom_property.rb +218 -0
  15. data/client/hyalite/dom_property_operations.rb +117 -0
  16. data/client/hyalite/element.rb +17 -0
  17. data/client/hyalite/event_dispatcher.rb +99 -0
  18. data/client/hyalite/event_plugin/change_event_plugin.rb +83 -0
  19. data/client/hyalite/event_plugin/event_plugin_registry.rb +49 -0
  20. data/client/hyalite/event_plugin/simple_event_plugin.rb +276 -0
  21. data/client/hyalite/input_wrapper.rb +94 -0
  22. data/client/hyalite/instance_handles.rb +78 -0
  23. data/client/hyalite/internal_component.rb +11 -0
  24. data/client/hyalite/linked_value_utils.rb +27 -0
  25. data/client/hyalite/mount.rb +285 -0
  26. data/client/hyalite/multi_children.rb +272 -0
  27. data/client/hyalite/reconcile_transaction.rb +41 -0
  28. data/client/hyalite/reconciler.rb +122 -0
  29. data/client/hyalite/short_hand.rb +23 -0
  30. data/client/hyalite/synthetic_event.rb +18 -0
  31. data/client/hyalite/text_component.rb +42 -0
  32. data/client/hyalite/transaction.rb +47 -0
  33. data/client/hyalite/try.rb +7 -0
  34. data/client/hyalite/update_queue.rb +52 -0
  35. data/client/hyalite/updates.rb +129 -0
  36. data/client/hyalite/utils.rb +19 -0
  37. data/example/Gemfile +5 -0
  38. data/example/Gemfile.lock +46 -0
  39. data/example/app/application.rb +27 -0
  40. data/example/config.ru +12 -0
  41. data/example/index.html.haml +8 -0
  42. data/hyalite.gemspec +27 -0
  43. data/lib/hyalite.rb +6 -0
  44. data/lib/hyalite/main.rb +5 -0
  45. data/lib/hyalite/version.rb +3 -0
  46. metadata +145 -0
@@ -0,0 +1,17 @@
1
+ require 'hyalite/dom_property_operations'
2
+
3
+ module Hyalite
4
+ module DOMOperations
5
+ class << self
6
+ def update_property_by_id(id, name, value)
7
+ node = Mount.node(id)
8
+
9
+ if value
10
+ DOMPropertyOperations.set_value_for_property(node, name, value)
11
+ else
12
+ DOMPropertyOperations.delete_value_for_property(node, name)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,218 @@
1
+ module Hyalite
2
+ module DOMProperty
3
+ ID_ATTRIBUTE_NAME = 'data-hyalite-id'
4
+
5
+ MUST_USE_ATTRIBUTE = 1
6
+ MUST_USE_PROPERTY = 2
7
+ HAS_BOOLEAN_VALUE = 4
8
+ HAS_SIDE_EFFECTS = 8
9
+ HAS_NUMERIC_VALUE = 16
10
+ HAS_POSITIVE_NUMERIC_VALUE = 32
11
+ HAS_OVERLOADED_BOOLEAN_VALUE = 64
12
+
13
+ PROPERTIES = {
14
+ ###############################
15
+ ### Standard Properties
16
+ ###############################
17
+ accept: nil,
18
+ acceptCharset: nil,
19
+ accessKey: nil,
20
+ action: nil,
21
+ allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
22
+ allowTransparency: MUST_USE_ATTRIBUTE,
23
+ alt: nil,
24
+ async: HAS_BOOLEAN_VALUE,
25
+ autoComplete: nil,
26
+ # autoFocus is polyfilled/normalized by AutoFocusUtils
27
+ # autoFocus: HAS_BOOLEAN_VALUE,
28
+ autoPlay: HAS_BOOLEAN_VALUE,
29
+ capture: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
30
+ cellPadding: nil,
31
+ cellSpacing: nil,
32
+ charSet: MUST_USE_ATTRIBUTE,
33
+ challenge: MUST_USE_ATTRIBUTE,
34
+ checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
35
+ classID: MUST_USE_ATTRIBUTE,
36
+ className: MUST_USE_PROPERTY,
37
+ cols: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
38
+ colSpan: nil,
39
+ content: nil,
40
+ contentEditable: nil,
41
+ contextMenu: MUST_USE_ATTRIBUTE,
42
+ controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
43
+ coords: nil,
44
+ crossOrigin: nil,
45
+ data: nil, # For `<object />` acts as `src`.
46
+ dateTime: MUST_USE_ATTRIBUTE,
47
+ defer: HAS_BOOLEAN_VALUE,
48
+ dir: nil,
49
+ disabled: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
50
+ download: HAS_OVERLOADED_BOOLEAN_VALUE,
51
+ draggable: nil,
52
+ encType: nil,
53
+ form: MUST_USE_ATTRIBUTE,
54
+ formAction: MUST_USE_ATTRIBUTE,
55
+ formEncType: MUST_USE_ATTRIBUTE,
56
+ formMethod: MUST_USE_ATTRIBUTE,
57
+ formNoValidate: HAS_BOOLEAN_VALUE,
58
+ formTarget: MUST_USE_ATTRIBUTE,
59
+ frameBorder: MUST_USE_ATTRIBUTE,
60
+ headers: nil,
61
+ height: MUST_USE_ATTRIBUTE,
62
+ hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
63
+ high: nil,
64
+ href: nil,
65
+ hrefLang: nil,
66
+ htmlFor: nil,
67
+ httpEquiv: nil,
68
+ icon: nil,
69
+ id: MUST_USE_PROPERTY,
70
+ inputMode: MUST_USE_ATTRIBUTE,
71
+ is: MUST_USE_ATTRIBUTE,
72
+ keyParams: MUST_USE_ATTRIBUTE,
73
+ keyType: MUST_USE_ATTRIBUTE,
74
+ label: nil,
75
+ lang: nil,
76
+ list: MUST_USE_ATTRIBUTE,
77
+ loop: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
78
+ low: nil,
79
+ manifest: MUST_USE_ATTRIBUTE,
80
+ marginHeight: nil,
81
+ marginWidth: nil,
82
+ max: nil,
83
+ maxLength: MUST_USE_ATTRIBUTE,
84
+ media: MUST_USE_ATTRIBUTE,
85
+ mediaGroup: nil,
86
+ method: nil,
87
+ min: nil,
88
+ minLength: MUST_USE_ATTRIBUTE,
89
+ multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
90
+ muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
91
+ name: nil,
92
+ noValidate: HAS_BOOLEAN_VALUE,
93
+ open: HAS_BOOLEAN_VALUE,
94
+ optimum: nil,
95
+ pattern: nil,
96
+ placeholder: nil,
97
+ poster: nil,
98
+ preload: nil,
99
+ radioGroup: nil,
100
+ readOnly: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
101
+ rel: nil,
102
+ required: HAS_BOOLEAN_VALUE,
103
+ role: MUST_USE_ATTRIBUTE,
104
+ rows: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
105
+ rowSpan: nil,
106
+ sandbox: nil,
107
+ scope: nil,
108
+ scoped: HAS_BOOLEAN_VALUE,
109
+ scrolling: nil,
110
+ seamless: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
111
+ selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
112
+ shape: nil,
113
+ size: MUST_USE_ATTRIBUTE | HAS_POSITIVE_NUMERIC_VALUE,
114
+ sizes: MUST_USE_ATTRIBUTE,
115
+ span: HAS_POSITIVE_NUMERIC_VALUE,
116
+ spellCheck: nil,
117
+ src: nil,
118
+ srcDoc: MUST_USE_PROPERTY,
119
+ srcSet: MUST_USE_ATTRIBUTE,
120
+ start: HAS_NUMERIC_VALUE,
121
+ step: nil,
122
+ style: nil,
123
+ tabIndex: nil,
124
+ target: nil,
125
+ title: nil,
126
+ type: nil,
127
+ useMap: nil,
128
+ value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
129
+ width: MUST_USE_ATTRIBUTE,
130
+ wmode: MUST_USE_ATTRIBUTE,
131
+
132
+ ###############################
133
+ ### Non-standard Properties
134
+ ###############################
135
+ # autoCapitalize and autoCorrect are supported in Mobile Safari for
136
+ # keyboard hints.
137
+ autoCapitalize: nil,
138
+ autoCorrect: nil,
139
+ # itemProp, itemScope, itemType are for
140
+ # Microdata support. See http:#schema.org/docs/gs.html
141
+ itemProp: MUST_USE_ATTRIBUTE,
142
+ itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
143
+ itemType: MUST_USE_ATTRIBUTE,
144
+ # itemID and itemRef are for Microdata support as well but
145
+ # only specified in the the WHATWG spec document. See
146
+ # https:#html.spec.whatwg.org/multipage/microdata.html#microdata-dom-api
147
+ itemID: MUST_USE_ATTRIBUTE,
148
+ itemRef: MUST_USE_ATTRIBUTE,
149
+ # property is supported for OpenGraph in meta tags.
150
+ property: nil,
151
+ # IE-only attribute that specifies security restrictions on an iframe
152
+ # as an alternative to the sandbox attribute on IE<10
153
+ security: MUST_USE_ATTRIBUTE,
154
+ # IE-only attribute that controls focus behavior
155
+ unselectable: MUST_USE_ATTRIBUTE,
156
+ }
157
+
158
+ DOM_ATTRIBUTE_NAMES = {
159
+ acceptCharset: 'accept-charset',
160
+ className: 'class',
161
+ htmlFor: 'for',
162
+ httpEquiv: 'http-equiv'
163
+ }
164
+
165
+ class << self
166
+ def property_info(name)
167
+ return nil unless PROPERTIES.has_key? name.to_sym
168
+
169
+ @property_info ||= {}
170
+ unless @property_info.has_key? name
171
+ property = PROPERTIES[name.to_sym]
172
+
173
+ attribute_name = DOM_ATTRIBUTE_NAMES.has_key?(name) ? DOM_ATTRIBUTE_NAMES[name] : name.downcase
174
+
175
+ @property_info[name] = {
176
+ attribute_name: attribute_name,
177
+ attribute_namespace: nil,
178
+ property_name: name,
179
+ mutation_method: nil,
180
+ must_use_attribute: property && property & MUST_USE_ATTRIBUTE > 0,
181
+ must_use_property: property && property & MUST_USE_PROPERTY > 0,
182
+ has_side_effects: property && property & HAS_SIDE_EFFECTS > 0,
183
+ has_boolean_value: property && property & HAS_BOOLEAN_VALUE > 0,
184
+ has_numeric_value: property && property & HAS_NUMERIC_VALUE > 0,
185
+ has_positive_numeric_value: property && property & HAS_POSITIVE_NUMERIC_VALUE > 0,
186
+ has_overloaded_boolean_value: property && property & HAS_OVERLOADED_BOOLEAN_VALUE > 0
187
+ }
188
+ end
189
+
190
+ @property_info[name]
191
+ end
192
+
193
+ def include?(name)
194
+ PROPERTIES.has_key? name
195
+ end
196
+
197
+ def default_value_for_property(node_name, prop)
198
+ node_defaults = default_value_cache[node_name]
199
+ unless node_defaults
200
+ default_value_cache[node_name] = node_defaults = {}
201
+ end
202
+ unless node_defaults.include? prop
203
+ test_element = $document.create_element(node_name)
204
+ node_defaults[prop] = test_element[prop]
205
+ end
206
+ node_defaults[prop]
207
+ end
208
+
209
+ def default_value_cache
210
+ @default_value_cache ||= {}
211
+ end
212
+
213
+ def is_custom_attribute(attribute_name)
214
+ /^(data|aria)-[a-z_][a-z\d_.\-]*$/ =~ attribute_name
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,117 @@
1
+ require 'hyalite/dom_property'
2
+ require 'hyalite/utils'
3
+
4
+ module Hyalite
5
+ module DOMPropertyOperations
6
+ VALID_ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z_\.\-\d]*$/
7
+
8
+ class << self
9
+ def create_markup_for_property(element, name, value)
10
+ property_info = DOMProperty.property_info(name)
11
+ if property_info
12
+ return if should_ignore_value(property_info, value)
13
+
14
+ attribute_name = property_info[:attribute_name]
15
+ if property_info[:has_boolean_value] || property_info[:has_overloaded_boolean_value] && value == true
16
+ element[attribute_name] = ""
17
+ return
18
+ end
19
+
20
+ element[attribute_name]=Hyalite.escape_text_content_for_browser(value)
21
+ elsif DOMProperty.is_custom_attribute(name)
22
+ return if value.nil?
23
+
24
+ element[name]=Hyalite.escape_text_content_for_browser(value)
25
+ end
26
+ end
27
+
28
+ def create_markup_for_styles(element, styles)
29
+ element.style(styles)
30
+ end
31
+
32
+ def create_markup_for_custom_attribute(element, name, value)
33
+ return if (!is_attribute_name_safe(name) || value == null)
34
+
35
+ element[name]=Hyalite.escape_text_content_for_browser(value)
36
+ end
37
+
38
+ def is_attribute_name_safe(attribute_name)
39
+ @illegal_attribute_name_cache ||= {}
40
+ @validated_attribute_name_cache ||= {}
41
+
42
+ return true if @validated_attribute_name_cache.has_key? attribute_name
43
+ return false if @illegal_attribute_name_cache.has_key? attribute_name
44
+
45
+ if VALID_ATTRIBUTE_NAME_REGEX =~ attribute_name
46
+ @validated_attribute_name_cache[attributeName] = true
47
+ return true
48
+ end
49
+
50
+ @illegal_attribute_name_cache[attributeName] = true
51
+ false
52
+ end
53
+
54
+ def set_value_for_property(node, name, value)
55
+ property_info = DOMProperty.property_info(name)
56
+ if property_info
57
+ mutation_method = property_info[:mutation_method]
58
+ if mutation_method
59
+ mutation_method.call(node, value);
60
+ elsif should_ignore_value(property_info, value)
61
+ delete_value_for_property(node, name)
62
+ elsif property_info[:must_use_attribute]
63
+ attribute_name = property_info[:attribute_name]
64
+ namespace = property_info[:attribute_namespace]
65
+ if namespace
66
+ node[attribute_name, {namespace: namespace}] = value.to_s
67
+ elsif property_info[:has_boolean_value] ||
68
+ (property_info[:has_overloaded_boolean_value] && value == true)
69
+ node[attribute_name] = ''
70
+ else
71
+ node[attribute_name] = value.to_s
72
+ end
73
+ else
74
+ prop_name = property_info[:property_name]
75
+ unless property_info[:has_side_effects] && `'' + node[#{prop_name}]` == value.to_s
76
+ `node.native[#{prop_name}] = value`
77
+ end
78
+ end
79
+ elsif DOMProperty.is_custom_attribute(name)
80
+ DOMPropertyOperations.set_value_for_attribute(node, name, value)
81
+ end
82
+ end
83
+
84
+ def delete_value_for_property(node, name)
85
+ property_info = DOMProperty.property_info(name)
86
+ if property_info
87
+ mutation_method = property_info[:mutation_method]
88
+ if mutation_method
89
+ mutation_method.call(node, nil)
90
+ elsif property_info[:must_use_attribute]
91
+ node.remove_attribute(property_info[:attribute_name])
92
+ else
93
+ prop_name = property_info[:property_name]
94
+ default_value = DOMProperty.default_value_for_property(node.node_name, prop_name)
95
+ unless property_info[:has_side_effects] || (node[prop_name].to_s == default_value)
96
+ node[prop_name] = default_value
97
+ end
98
+ end
99
+ elsif DOMProperty.is_custom_attribute(name)
100
+ node.remove_attribute(name)
101
+ end
102
+ end
103
+
104
+ def set_attribute_for_id(node, id)
105
+ node[DOMProperty::ID_ATTRIBUTE_NAME] = id
106
+ end
107
+
108
+ def should_ignore_value(property_info, value)
109
+ value.nil? ||
110
+ (property_info[:has_boolean_value] && !value) ||
111
+ (property_info[:has_numeric_value] && value.nan?) ||
112
+ (property_info[:has_positive_numeric_value] && (value < 1)) ||
113
+ (property_info[:has_overloaded_boolean_value] && value == false)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,17 @@
1
+ module Hyalite
2
+ class ElementObject
3
+ attr_reader :type, :key, :ref, :props, :owner
4
+
5
+ def initialize(type, key, ref, owner, props)
6
+ @type = type
7
+ @key = key
8
+ @ref = ref
9
+ @owner = owner
10
+ @props = props
11
+ end
12
+
13
+ def inspect
14
+ "<#{type} #{props.inspect} />"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,99 @@
1
+ module Hyalite
2
+ class EventDispatcher
3
+ def initialize(&extract_events)
4
+ @enabled = true
5
+ @listener_bank = {}
6
+ @extract_events = extract_events
7
+ @event_queue = []
8
+ end
9
+
10
+ def enabled?
11
+ @enabled
12
+ end
13
+
14
+ def enabled=(enabled)
15
+ @enabled = enabled
16
+ end
17
+
18
+ def trap_bubbled_event(top_level_type, handler_base_name, element)
19
+ return nil unless element
20
+
21
+ element.on handler_base_name do |event|
22
+ dispatch_event(top_level_type, event)
23
+ end
24
+ end
25
+
26
+ def find_parent(node)
27
+ node_id = Mount.node_id(node)
28
+ root_id = InstanceHandles.root_id_from_node_id(node_id)
29
+ container = Mount.container_for_id(root_id)
30
+ Mount.find_first_hyalite_dom(container)
31
+ end
32
+
33
+ def handle_top_level(book_keeping)
34
+ ancestor = Mount.find_first_hyalite_dom(book_keeping.event.target)
35
+ while ancestor
36
+ book_keeping.ancestors << ancestor
37
+ ancestor = find_parent(ancestor)
38
+ end
39
+
40
+ book_keeping.ancestors.each do |top_level_target|
41
+ top_level_target_id = Mount.node_id(top_level_target) || ''
42
+ synthetic_events = @extract_events.call(
43
+ book_keeping.top_level_type,
44
+ top_level_target,
45
+ top_level_target_id,
46
+ book_keeping.event,
47
+ ).flatten
48
+
49
+ synthetic_events.each do |synthetic_event|
50
+ synthetic_event.each_listener do |listener, dom_id|
51
+ target = Mount.node(dom_id);
52
+ listener.call(synthetic_event.event, target)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def dispatch_event(top_level_type, event)
59
+ return unless @enabled
60
+
61
+ book_keeping = TopLevelCallbackBookKeeping.new(top_level_type, event)
62
+ Hyalite.updates.batched_updates { handle_top_level(book_keeping) }
63
+ end
64
+
65
+ def put_listener(id, registration_name, listener)
66
+ listeners = @listener_bank[registration_name] ||= {}
67
+ listeners[id] = listener
68
+ end
69
+
70
+ def get_listener(id, registration_name)
71
+ @listener_bank[registration_name].try {|listeners| listeners[id] }
72
+ end
73
+
74
+ def delete_listener(id, registration_name)
75
+ if @listener_bank.has_key? registration_name
76
+ yield(id, registration_name)
77
+ @listener_bank[registration_name].delete(id)
78
+ end
79
+ end
80
+
81
+ def delete_all_listeners(id, &block)
82
+ @listener_bank.each do |registration_name, bank|
83
+ next unless bank[id]
84
+ yield(id, registration_name) if block_given?
85
+ bank.delete(id)
86
+ end
87
+ end
88
+
89
+ class TopLevelCallbackBookKeeping
90
+ attr_reader :event, :top_level_type, :ancestors
91
+
92
+ def initialize(top_level_type, event)
93
+ @top_level_type = top_level_type
94
+ @event = event
95
+ @ancestors = []
96
+ end
97
+ end
98
+ end
99
+ end