hyalite 0.0.1

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.
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,78 @@
1
+ module Hyalite
2
+ module InstanceHandles
3
+ @root_index = 0
4
+
5
+ SEPARATOR = '.'
6
+
7
+ class << self
8
+ def root_id_string(index)
9
+ SEPARATOR + index.to_s(36)
10
+ end
11
+
12
+ def create_root_id
13
+ root_id_string(root_index);
14
+ end
15
+
16
+ def root_id_from_node_id(id)
17
+ if id && id.start_with?(SEPARATOR)
18
+ index = id.index(SEPARATOR, 1)
19
+ index ? id[0...index] : id
20
+ end
21
+ end
22
+
23
+ def root_index
24
+ index = @root_index
25
+ @root_index += 1
26
+ index
27
+ end
28
+
29
+ def traverse_two_phase(target_id, &cb)
30
+ traverse_parent_path('', target_id, true, false, &cb)
31
+ traverse_parent_path(target_id, '', false, true, &cb)
32
+ end
33
+
34
+ def traverse_ancestors(target_id, &cb)
35
+ traverse_parent_path('', target_id, true, false, &cb)
36
+ end
37
+
38
+ def traverse_parent_path(start, stop, skip_first, skip_last, &cb)
39
+ start = start || ''
40
+ stop = stop || ''
41
+ traverse_up = is_ancestor_id_of(stop, start)
42
+
43
+ id = start
44
+ loop do
45
+ unless (skip_first && id == start) || (skip_last && id == stop)
46
+ ret = yield(id, traverse_up)
47
+ end
48
+
49
+ if ret == false || id == stop
50
+ break
51
+ end
52
+
53
+ id = traverse_up ? parent_id(id) : next_descendant_id(id, stop)
54
+ end
55
+ end
56
+
57
+ def parent_id(id)
58
+ id.empty? ? '' : id[0, id.rindex(SEPARATOR)]
59
+ end
60
+
61
+ def is_ancestor_id_of(ancestor_id, descendant_id)
62
+ descendant_id.index(ancestor_id) == 0 && is_boundary(descendant_id, ancestor_id.length)
63
+ end
64
+
65
+ def is_boundary(id, index)
66
+ id[index] == SEPARATOR || index == id.length
67
+ end
68
+
69
+ def next_descendant_id(ancestor_id, destination_id)
70
+ return ancestor_id if ancestor_id == destination_id
71
+
72
+ start = ancestor_id.length + SEPARATOR.length
73
+ last = destination_id.index(SEPARATOR, start) || destination_id.length
74
+ destination_id[0,last]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,11 @@
1
+ module Hyalite
2
+ module InternalComponent
3
+ attr_accessor :mount_index
4
+
5
+ def mount_component(root_id, mount_ready, context)
6
+ end
7
+
8
+ def recieve_component(next_element, mount_ready)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ module Hyalite
2
+ module LinkedValueUtils
3
+ class << self
4
+ def value(props)
5
+ if props.has_key? :valueLink
6
+ props[:valueLink][:value]
7
+ else
8
+ props[:value]
9
+ end
10
+ end
11
+
12
+ def checked(props)
13
+ if props.has_key? :checkedLink
14
+ props[:checkedLink][:value]
15
+ else
16
+ props[:checked]
17
+ end
18
+ end
19
+
20
+ def execute_on_change(props, event)
21
+ if props.has_key? :onChange
22
+ props[:onChange].call(event)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,285 @@
1
+ require 'hyalite/transaction'
2
+ require 'hyalite/adler32'
3
+ require 'hyalite/try'
4
+ require 'hyalite/transaction'
5
+ require 'hyalite/element'
6
+ require 'hyalite/component'
7
+ require 'hyalite/instance_handles'
8
+ require 'hyalite/updates'
9
+ require 'hyalite/composite_component'
10
+
11
+ module Hyalite
12
+ module Mount
13
+ ID_ATTR_NAME = 'data-hyalite-id'
14
+ CHECKSUM_ATTR_NAME = 'data-react-checksum'
15
+
16
+ @instances_by_root_id = {}
17
+ @containers_by_root_id = {}
18
+ @is_batching_updates = false
19
+
20
+ class << self
21
+ def instances_by_root_id(root_id)
22
+ @instances_by_root_id[root_id]
23
+ end
24
+
25
+ def render_subtree_into_container(parent_component, next_element, container, &block)
26
+ next_wrapped_element = ElementObject.new(TopLevelWrapper, nil, nil, nil, next_element)
27
+ prev_component = @instances_by_root_id[root_id(container)]
28
+ if prev_component
29
+ prev_wrapped_element = prev_component.current_element
30
+ prev_element = prev_wrapped_element.props;
31
+ if Reconciler.should_update_component(prev_element, next_element)
32
+ return update_root_component(
33
+ prev_component,
34
+ next_wrapped_element,
35
+ container,
36
+ &block
37
+ ).rendered_component.public_instance
38
+ else
39
+ unmount_component_at_node(container)
40
+ end
41
+ end
42
+
43
+ root_element = root_element_in_container(container)
44
+ container_has_markup = root_element && is_rendered(root_element)
45
+ should_reuse_markup = container_has_markup && prev_component.nil?
46
+
47
+ component = render_new_root_component(
48
+ next_wrapped_element,
49
+ container,
50
+ should_reuse_markup,
51
+ parent_component ?
52
+ parent_component.internal_instance.process_child_context(parent_component.internal_instance.context) :
53
+ {}
54
+ ).rendered_component.public_instance
55
+
56
+ if block_given?
57
+ yield component
58
+ end
59
+
60
+ component
61
+ end
62
+
63
+ def is_rendered(node)
64
+ return false if node.node_type != Browser::DOM::Node::ELEMENT_NODE
65
+
66
+ id = node_id(node)
67
+ id ? id[0] == SEPARATOR : false
68
+ end
69
+
70
+ def render_new_root_component(next_element, container, should_reuse_markup, context)
71
+ component_instance = Hyalite.instantiate_component(next_element, nil)
72
+ root_id = register_component(component_instance, container)
73
+
74
+ Hyalite.updates.batched_updates do
75
+ mount_component_into_node(component_instance, root_id, container, should_reuse_markup, context)
76
+ end
77
+
78
+ component_instance
79
+ end
80
+
81
+ def register_component(next_component, container)
82
+ #ReactBrowserEventEmitter.ensureScrollValueMonitoring();
83
+
84
+ root_id = register_container(container)
85
+ @instances_by_root_id[root_id] = next_component;
86
+ root_id
87
+ end
88
+
89
+ def register_container(container)
90
+ root_id = root_id(container)
91
+ if root_id
92
+ root_id = InstanceHandles.root_id_from_node_id(root_id)
93
+ end
94
+
95
+ unless root_id
96
+ root_id = InstanceHandles.create_root_id
97
+ end
98
+
99
+ @containers_by_root_id[root_id] = container
100
+ root_id
101
+ end
102
+
103
+ def mount_component_into_node(component_instance, root_id, container, should_reuse_markup, context)
104
+ Hyalite.updates.reconcile_transaction.perform do |transaction|
105
+ markup = Reconciler.mount_component(component_instance, root_id, transaction.mount_ready, context)
106
+ component_instance.rendered_component.top_level_wrapper = component_instance
107
+ mount_image_into_node(markup, container, should_reuse_markup)
108
+ end
109
+ end
110
+
111
+ def mount_image_into_node(markup, container, should_reuse_markup)
112
+ if should_reuse_markup
113
+ root_element = root_element_in_container(container)
114
+ checksum = Adler32.checksum markup
115
+ checksum_attr = root_element.attr(CHECKSUM_ATTR_NAME)
116
+ if checksum == checksum_attr
117
+ return
118
+ end
119
+
120
+ root_element.remove_attr(CHECKSUM_ATTR_NAME)
121
+ root_element.attr(CHECKSUM_ATTR_NAME, checksum)
122
+ end
123
+
124
+ container.inner_dom = markup
125
+ end
126
+
127
+ def root_element_in_container(container)
128
+ if container.node_type == Browser::DOM::Node::DOCUMENT_NODE
129
+ $document
130
+ else
131
+ container.child
132
+ end
133
+ end
134
+
135
+ def root_id(container)
136
+ root_element = root_element_in_container(container)
137
+ root_element && node_id(root_element)
138
+ end
139
+
140
+ def node_cache
141
+ @node_cache ||= {}
142
+ end
143
+
144
+ def purge_id(id)
145
+ @node_cache.delete(id)
146
+ end
147
+
148
+ def node_id(node)
149
+ id = internal_id(node)
150
+ if id
151
+ if node_cache.has_key?(id)
152
+ cached = node_cache[id]
153
+ if cached != node
154
+ #raise "Mount: Two valid but unequal nodes with the same `#{ID_ATTR_NAME}`: #{id}"
155
+ node_cache[id] = node
156
+ end
157
+ else
158
+ node_cache[id] = node
159
+ end
160
+ end
161
+
162
+ id
163
+ end
164
+
165
+ def internal_id(node)
166
+ if node.node_type == Browser::DOM::Node::ELEMENT_NODE
167
+ node.attr(ID_ATTR_NAME)
168
+ end
169
+ end
170
+
171
+ # cf. ReactMount#findReactContainerForID
172
+ def container_for_id(id)
173
+ root_id = InstanceHandles.root_id_from_node_id(id)
174
+ @containers_by_root_id[root_id]
175
+ end
176
+
177
+ def node(id)
178
+ node = node_cache[id]
179
+ unless node && is_valid(node, id)
180
+ root_id = InstanceHandles.root_id_from_node_id(id)
181
+ node = node_cache[id] = find_component_root(@containers_by_root_id[root_id], id)
182
+ end
183
+ node
184
+ end
185
+
186
+ def is_valid(node, id)
187
+ if node
188
+ container = @containers_by_root_id[id]
189
+ if container && contains_node(container, node)
190
+ return true
191
+ end
192
+ end
193
+
194
+ false
195
+ end
196
+
197
+ def find_component_root(ancestor_node, target_id)
198
+ deepest_ancestor = find_deepest_cached_ancestor(target_id) || ancestor_node
199
+
200
+ first_children = [ deepest_ancestor.child ]
201
+
202
+ while first_children.length > 0
203
+ child = first_children.shift
204
+
205
+ while child
206
+ child_id = node_id(child)
207
+ if child_id
208
+ if target_id == child_id
209
+ return child
210
+ elsif InstanceHandles.is_ancestor_id_of(child_id, target_id)
211
+ first_children = [ child.child ]
212
+ end
213
+ else
214
+ first_children.push(child.child)
215
+ end
216
+
217
+ child = child.next_sibling
218
+ end
219
+ end
220
+
221
+ raise "can't find component_root"
222
+ end
223
+
224
+ def find_deepest_cached_ancestor(target_id)
225
+ deepest_node_so_far = nil
226
+ InstanceHandles.traverse_ancestors(target_id) do |ancestor_id|
227
+ ancestor = node_cache[ancestor_id]
228
+ if ancestor && is_valid(ancestor, ancestor_id)
229
+ deepest_node_so_far = ancestor;
230
+ else
231
+ false
232
+ end
233
+ end
234
+
235
+ deepest_node_so_far
236
+ end
237
+
238
+ def contains_node(outer_node, inner_node)
239
+ case
240
+ when outer_node.nil? || inner_node.nil?
241
+ false
242
+ when outer_node == inner_node
243
+ true
244
+ when outer_node.node_type == Browser::DOM::Node::TEXT_NODE
245
+ false
246
+ else
247
+ contains_node(outer_node, inner_node.parent)
248
+ end
249
+ end
250
+
251
+ def find_first_hyalite_dom(node)
252
+ while node && node.parent != node
253
+ unless node.node_type == Browser::DOM::Node::ELEMENT_NODE && node_id = internal_id(node)
254
+ node = node.parent
255
+ next
256
+ end
257
+
258
+ root_id = InstanceHandles.root_id_from_node_id(node_id)
259
+
260
+ current = node
261
+ loop do
262
+ last_id = internal_id(current)
263
+ return nil unless current = current.parent
264
+ break if last_id == root_id
265
+ end
266
+
267
+ return node if current == @containers_by_root_id[root_id]
268
+
269
+ node = node.parent
270
+ end
271
+
272
+ nil
273
+ end
274
+
275
+ def update_root_component(prev_component, next_element, container, &callback)
276
+ UpdateQueue.enqueue_element_internal(prev_component, next_element)
277
+ if block_given?
278
+ UpdateQueue.enqueue_callback_internal(prev_component, &callback)
279
+ end
280
+
281
+ prev_component
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,272 @@
1
+ module Hyalite
2
+ module MultiChildren
3
+ def mount_children(nested_children, mount_ready, context)
4
+ children = instantiate_children(nested_children, mount_ready, context)
5
+ @rendered_children = children
6
+ index = 0
7
+ children.keys.map do |name|
8
+ child = children[name]
9
+ root_id = root_node_id + name
10
+ mount_image = Reconciler.mount_component(children[name], root_id, mount_ready, context)
11
+ child.mount_index = index
12
+ index += 1
13
+ mount_image
14
+ end
15
+ end
16
+
17
+ def unmount_children
18
+ if @rendered_children
19
+ Reconciler.unmount_children(@rendered_children)
20
+ @rendered_children = nil
21
+ end
22
+ end
23
+
24
+ def update_children(next_nested_children, mount_ready, context)
25
+ MultiChildren.wrap_update do
26
+ prev_children = @rendered_children
27
+ next_children = Reconciler.update_children(prev_children, next_nested_children, mount_ready, context)
28
+ @rendered_children = next_children
29
+ return if next_children.nil? && prev_children.nil?
30
+
31
+
32
+ last_index = 0
33
+ next_index = 0
34
+ next_children.each do |name, next_child|
35
+ prev_child = prev_children && prev_children[name]
36
+ if prev_child == next_child
37
+ move_child(prev_child, next_index, last_index)
38
+ last_index = [prev_child.mount_index, last_index].max
39
+ prev_child.mount_index = next_index
40
+ else
41
+ if prev_child
42
+ last_index = [prev_child.mount_index, last_index].max
43
+ unmount_child(prev_child)
44
+ end
45
+
46
+ mount_child_by_name_at_index(next_child, name, next_index, mount_ready, context)
47
+ end
48
+ next_index += 1
49
+ end
50
+
51
+ prev_children.each do |name, prev_child|
52
+ unless next_children && next_children.has_key?(name)
53
+ unmount_child(prev_child)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def update_text_content(next_content)
60
+ MultiChildren.wrap_update do
61
+ prev_children = @rendered_children
62
+ if prev_children
63
+ Reconciler.unmount_children(prev_children)
64
+ prev_children.each do |prev_child|
65
+ unmount_child(prev_child)
66
+ end
67
+ end
68
+ set_text_content(next_content)
69
+ end
70
+ end
71
+
72
+ def move_child(child, to_index, last_index)
73
+ if child.mount_index < last_index
74
+ enqueue_move(root_node_id, child.mount_index, to_index)
75
+ end
76
+ end
77
+
78
+ def remove_child(child)
79
+ enqueue_remove(root_node_id, child.mount_index)
80
+ end
81
+
82
+ def unmount_child(child)
83
+ remove_child(child)
84
+ child.mount_index = nil
85
+ end
86
+
87
+ def instantiate_children(nested_child_nodes, context)
88
+ Reconciler.flatten_children(nested_child_nodes).map {|name, child|
89
+ child_instance = Hyalite.instantiate_component(child, nil)
90
+ [name, child_instance]
91
+ }.to_h
92
+ end
93
+
94
+ private
95
+
96
+ class << self
97
+ def wrap_update(&block)
98
+ self.update_depth += 1
99
+ error_thrown = false
100
+ yield
101
+ ensure
102
+ self.update_depth -= 1
103
+ if self.update_depth == 0
104
+ unless error_thrown
105
+ self.process_queue
106
+ else
107
+ self.clear_queue
108
+ end
109
+ end
110
+ end
111
+
112
+ def update_depth
113
+ @update_depth ||= 0
114
+ end
115
+
116
+ def update_depth=(depth)
117
+ @update_depth = depth
118
+ end
119
+
120
+ def update_queue
121
+ @update_queue ||= []
122
+ end
123
+
124
+ def markup_queue
125
+ @markup_queue ||= []
126
+ end
127
+
128
+ def clear_queue
129
+ self.update_queue.clear
130
+ self.markup_queue.clear
131
+ end
132
+
133
+ def process_queue
134
+ if MultiChildren.update_queue.any?
135
+ process_children_updates(MultiChildren.update_queue, MultiChildren.markup_queue)
136
+ clear_queue
137
+ end
138
+ end
139
+
140
+ def process_children_updates(updates, markup)
141
+ updates.each do |update|
142
+ update[:parentNode] = Mount.node(update[:parentID])
143
+ end
144
+ process_updates(updates, markup)
145
+ end
146
+
147
+ def process_updates(updates, markup_list)
148
+ initial_children = {}
149
+ updated_children = []
150
+
151
+ updates.each_with_index do |update, updated_index|
152
+ if update[:type] == :move_existing || update[:type] == :remove_node
153
+ updated_index = update[:fromIndex]
154
+ updated_child = update[:parentNode].elements[updated_index]
155
+ parent_id = update[:parentID]
156
+
157
+ initial_children[parent_id] ||= []
158
+ initial_children[parent_id] << updated_child
159
+
160
+ updated_children << updated_child
161
+ end
162
+ end
163
+
164
+ if markup_list.any? && markup_list[0].is_a?(String)
165
+ #rendered_markup = Danger.dangerouslyRenderMarkup(markupList);
166
+ raise "not implemented"
167
+ else
168
+ rendered_markup = markup_list
169
+ end
170
+
171
+ updated_children.each do |child|
172
+ child.remove
173
+ end
174
+
175
+ updates.each do |update|
176
+ case update[:type]
177
+ when :insert_markup
178
+ insert_child_at(
179
+ update[:parentNode],
180
+ rendered_markup[update[:markupIndex]],
181
+ update[:toIndex])
182
+ when :move_existing
183
+ insert_child_at(
184
+ update[:parentNode],
185
+ initial_children[update[:parentID]][update[:fromIndex]],
186
+ update[:toIndex])
187
+ when :set_markup
188
+ update[:parentNode].inner_html = update[:textContent]
189
+ when :text_content
190
+ update[:parentNode].content = update[:textContent]
191
+ when :remove_node
192
+ # Already removed above.
193
+ end
194
+ end
195
+ end
196
+
197
+
198
+ def insert_child_at(parent_node, child_node, index)
199
+ if index >= parent_node.children.to_ary.length
200
+ parent_node.add_child(child_node)
201
+ else
202
+ parent_node[index].add_previous_sibling(child_node)
203
+ end
204
+ end
205
+ end
206
+
207
+ def mount_child_by_name_at_index(child, name, index, mount_ready, context)
208
+ root_id = root_node_id + name
209
+ mount_image = Reconciler.mount_component(child, root_id, mount_ready, context)
210
+ child.mount_index = index
211
+ create_child(child, mount_image)
212
+ end
213
+
214
+ def create_child(child, mount_image)
215
+ enqueue_markup(root_node_id, mount_image, child.mount_index)
216
+ end
217
+
218
+ def set_text_content(text_content)
219
+ enqueue_text_content(root_node_id, text_content)
220
+ end
221
+
222
+ def enqueue_remove(parent_id, from_index)
223
+ MultiChildren.update_queue << {
224
+ parentID: parent_id,
225
+ parentNode: nil,
226
+ type: :remove_node,
227
+ markupIndex: nil,
228
+ content: nil,
229
+ fromIndex: from_index,
230
+ toIndex: nil
231
+ }
232
+ end
233
+
234
+ def enqueue_move(parent_id, from_index, to_index)
235
+ MultiChildren.update_queue << {
236
+ parentID: parent_id,
237
+ parentNode: nil,
238
+ type: :move_existing,
239
+ markupIndex: nil,
240
+ content: nil,
241
+ fromIndex: from_index,
242
+ toIndex: to_index
243
+ }
244
+ end
245
+
246
+ def enqueue_text_content(parent_id, text_content)
247
+ MultiChildren.update_queue << {
248
+ parentID: parent_id,
249
+ parentNode: nil,
250
+ type: :text_content,
251
+ markupIndex: nil,
252
+ textContent: text_content,
253
+ fromIndex: nil,
254
+ toIndex: nil
255
+ }
256
+ end
257
+
258
+ def enqueue_markup(parent_id, markup, to_index)
259
+ MultiChildren.markup_queue << markup
260
+ MultiChildren.update_queue << {
261
+ parentID: parent_id,
262
+ parentNode: nil,
263
+ type: :insert_markup,
264
+ markupIndex: MultiChildren.markup_queue.length - 1,
265
+ textContent: nil,
266
+ fromIndex: nil,
267
+ toIndex: to_index
268
+ }
269
+ end
270
+
271
+ end
272
+ end