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,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