volt 0.2.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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +37 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +23 -0
- data/Readme.md +34 -0
- data/VERSION +1 -0
- data/bin/volt +4 -0
- data/docs/GETTING_STARTED.md +7 -0
- data/docs/GUIDE.md +33 -0
- data/lib/volt.rb +15 -0
- data/lib/volt/benchmark/benchmark.rb +25 -0
- data/lib/volt/cli.rb +34 -0
- data/lib/volt/console.rb +19 -0
- data/lib/volt/controllers/model_controller.rb +29 -0
- data/lib/volt/extra_core/array.rb +10 -0
- data/lib/volt/extra_core/blank.rb +88 -0
- data/lib/volt/extra_core/extra_core.rb +7 -0
- data/lib/volt/extra_core/numeric.rb +9 -0
- data/lib/volt/extra_core/object.rb +36 -0
- data/lib/volt/extra_core/string.rb +29 -0
- data/lib/volt/extra_core/stringify_keys.rb +7 -0
- data/lib/volt/extra_core/true_false.rb +44 -0
- data/lib/volt/extra_core/try.rb +31 -0
- data/lib/volt/models.rb +5 -0
- data/lib/volt/models/array_model.rb +37 -0
- data/lib/volt/models/model.rb +210 -0
- data/lib/volt/models/model_wrapper.rb +23 -0
- data/lib/volt/models/params.rb +67 -0
- data/lib/volt/models/url.rb +192 -0
- data/lib/volt/page/url_tracker.rb +36 -0
- data/lib/volt/reactive/array_extensions.rb +13 -0
- data/lib/volt/reactive/event_chain.rb +126 -0
- data/lib/volt/reactive/events.rb +283 -0
- data/lib/volt/reactive/object_tracker.rb +99 -0
- data/lib/volt/reactive/object_tracking.rb +15 -0
- data/lib/volt/reactive/reactive_array.rb +222 -0
- data/lib/volt/reactive/reactive_tags.rb +64 -0
- data/lib/volt/reactive/reactive_value.rb +368 -0
- data/lib/volt/reactive/string_extensions.rb +34 -0
- data/lib/volt/router/routes.rb +83 -0
- data/lib/volt/server.rb +121 -0
- data/lib/volt/server/binding_setup.rb +2 -0
- data/lib/volt/server/channel_handler.rb +31 -0
- data/lib/volt/server/component_handler.rb +88 -0
- data/lib/volt/server/if_binding_setup.rb +29 -0
- data/lib/volt/server/request_handler.rb +16 -0
- data/lib/volt/server/scope.rb +43 -0
- data/lib/volt/server/source_map_server.rb +31 -0
- data/lib/volt/server/template_parser.rb +452 -0
- data/lib/volt/store/mongo.rb +5 -0
- data/lib/volt/templates/attribute_binding.rb +110 -0
- data/lib/volt/templates/base_binding.rb +37 -0
- data/lib/volt/templates/channel.rb +48 -0
- data/lib/volt/templates/content_binding.rb +35 -0
- data/lib/volt/templates/document_events.rb +80 -0
- data/lib/volt/templates/each_binding.rb +115 -0
- data/lib/volt/templates/event_binding.rb +51 -0
- data/lib/volt/templates/if_binding.rb +74 -0
- data/lib/volt/templates/memory_test.rb +26 -0
- data/lib/volt/templates/page.rb +146 -0
- data/lib/volt/templates/reactive_template.rb +38 -0
- data/lib/volt/templates/render_queue.rb +5 -0
- data/lib/volt/templates/sub_context.rb +23 -0
- data/lib/volt/templates/targets/attribute_section.rb +33 -0
- data/lib/volt/templates/targets/attribute_target.rb +18 -0
- data/lib/volt/templates/targets/base_section.rb +14 -0
- data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
- data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
- data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
- data/lib/volt/templates/targets/dom_section.rb +147 -0
- data/lib/volt/templates/targets/dom_target.rb +11 -0
- data/lib/volt/templates/template_binding.rb +159 -0
- data/lib/volt/templates/template_renderer.rb +50 -0
- data/spec/models/event_chain_spec.rb +129 -0
- data/spec/models/model_spec.rb +340 -0
- data/spec/models/old_model_spec.rb +109 -0
- data/spec/models/reactive_array_spec.rb +262 -0
- data/spec/models/reactive_tags_spec.rb +35 -0
- data/spec/models/reactive_value_spec.rb +336 -0
- data/spec/models/string_extensions_spec.rb +57 -0
- data/spec/router/routes_spec.rb +24 -0
- data/spec/server/template_parser_spec.rb +50 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/store/mongo_spec.rb +4 -0
- data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
- data/spec/templates/template_binding_spec.rb +98 -0
- data/templates/.gitignore +12 -0
- data/templates/Gemfile.tt +8 -0
- data/templates/app/.empty_directory +0 -0
- data/templates/app/home/config/routes.rb +1 -0
- data/templates/app/home/controllers/index_controller.rb +5 -0
- data/templates/app/home/css/.empty_directory +0 -0
- data/templates/app/home/models/.empty_directory +0 -0
- data/templates/app/home/views/index/about.html +9 -0
- data/templates/app/home/views/index/home.html +7 -0
- data/templates/app/home/views/index/index.html +28 -0
- data/templates/config.ru +4 -0
- data/templates/public/css/ansi.css +0 -0
- data/templates/public/css/bootstrap-theme.css +459 -0
- data/templates/public/css/bootstrap.css +7098 -0
- data/templates/public/css/jumbotron.css +79 -0
- data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/templates/public/index.html +25 -0
- data/templates/public/js/bootstrap.js +0 -0
- data/templates/public/js/jquery-2.0.3.js +8829 -0
- data/templates/public/js/sockjs-0.2.1.min.js +27 -0
- data/templates/spec/spec_helper.rb +20 -0
- data/volt.gemspec +41 -0
- metadata +412 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class SourceMapServer
|
|
2
|
+
def initialize sprockets
|
|
3
|
+
@sprockets = sprockets
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
attr_reader :sprockets
|
|
7
|
+
|
|
8
|
+
attr_writer :prefix
|
|
9
|
+
|
|
10
|
+
def prefix
|
|
11
|
+
@prefix ||= '/__opal_source_maps__'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inspect
|
|
15
|
+
"#<#{self.class}:#{object_id}>"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(env)
|
|
19
|
+
path_info = env['PATH_INFO']
|
|
20
|
+
|
|
21
|
+
if path_info =~ /\.js\.map$/
|
|
22
|
+
path = env['PATH_INFO'].gsub(/^\/|\.js\.map$/, '')
|
|
23
|
+
asset = sprockets[path]
|
|
24
|
+
return [404, {}, []] if asset.nil?
|
|
25
|
+
|
|
26
|
+
return [200, {"Content-Type" => "text/json"}, [$OPAL_SOURCE_MAPS[asset.pathname].to_s]]
|
|
27
|
+
else
|
|
28
|
+
return [200, {"Content-Type" => "text/text"}, [File.read(sprockets.resolve(path_info))]]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
require 'volt/server/scope'
|
|
2
|
+
require 'volt/server/if_binding_setup'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
# TODO: The section_name that we're passing in should probably be
|
|
6
|
+
# abstracted out. Possibly this whole thing needs a rewrite.
|
|
7
|
+
|
|
8
|
+
class Template
|
|
9
|
+
attr_accessor :current_scope, :section_name
|
|
10
|
+
|
|
11
|
+
def initialize(template_parser, section_name, template, scope=Scope.new)
|
|
12
|
+
@binding_number = 0
|
|
13
|
+
|
|
14
|
+
@template_parser = template_parser
|
|
15
|
+
@section_name = section_name
|
|
16
|
+
@template = template
|
|
17
|
+
@scopes = [scope]
|
|
18
|
+
@current_scope = @scopes.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def html
|
|
22
|
+
if @template.respond_to?(:name) && @template.name[0] == ':'
|
|
23
|
+
# Don't return the <:section> tags
|
|
24
|
+
return @template.children.to_html
|
|
25
|
+
else
|
|
26
|
+
# if @template.class == Nokogiri::XML::NodeSet
|
|
27
|
+
# result = ''
|
|
28
|
+
# @template.each do |node|
|
|
29
|
+
# result << node.to_html
|
|
30
|
+
# end
|
|
31
|
+
# else
|
|
32
|
+
result = @template.to_html
|
|
33
|
+
# end
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_binding(node, content)
|
|
40
|
+
if content[0] == '/'
|
|
41
|
+
add_close_mustache(node)
|
|
42
|
+
elsif content[0] == '#'
|
|
43
|
+
command, *content = content.split(/ /)
|
|
44
|
+
content = content.join(' ')
|
|
45
|
+
|
|
46
|
+
case command
|
|
47
|
+
when '#template'
|
|
48
|
+
return add_template(node, content)
|
|
49
|
+
when '#each'
|
|
50
|
+
return add_each_binding(node, content)
|
|
51
|
+
when '#if'
|
|
52
|
+
return add_if_binding(node, content)
|
|
53
|
+
when '#elsif'
|
|
54
|
+
return add_else_binding(node, content)
|
|
55
|
+
when '#else'
|
|
56
|
+
if content.present?
|
|
57
|
+
# TODO: improve error, include line/file
|
|
58
|
+
raise "#else should not include a condition, use #elsif instead. #{content} was passed as a condition."
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return add_else_binding(node, nil)
|
|
62
|
+
else
|
|
63
|
+
# TODO: Handle invalid command
|
|
64
|
+
raise "Invalid Command"
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
# text binding
|
|
68
|
+
return add_text_binding(content)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_template(node, content)
|
|
73
|
+
html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
|
74
|
+
|
|
75
|
+
@current_scope.add_binding(@binding_number, "lambda { |target, context, id| TemplateBinding.new(target, context, id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")
|
|
76
|
+
|
|
77
|
+
@binding_number += 1
|
|
78
|
+
return html
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def add_each_binding(node, content)
|
|
82
|
+
html = "<!-- $#{@binding_number} -->"
|
|
83
|
+
|
|
84
|
+
content, variable_name = content.strip.split(/ as /)
|
|
85
|
+
|
|
86
|
+
template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
|
|
87
|
+
@current_scope.add_binding(@binding_number, "lambda { |target, context, id| EachBinding.new(target, context, id, Proc.new { #{content} }, #{variable_name.inspect}, #{template_name.inspect}) }")
|
|
88
|
+
|
|
89
|
+
# Add the node, the binding number, then store the location where the
|
|
90
|
+
# bindings for this block starts.
|
|
91
|
+
@current_scope = Scope.new(@binding_number)
|
|
92
|
+
@scopes << @current_scope
|
|
93
|
+
|
|
94
|
+
@binding_number += 1
|
|
95
|
+
return html
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_if_binding(node, content)
|
|
99
|
+
html = "<!-- $#{@binding_number} -->"
|
|
100
|
+
|
|
101
|
+
template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
|
|
102
|
+
if_binding_setup = IfBindingSetup.new
|
|
103
|
+
if_binding_setup.add_branch(content, template_name)
|
|
104
|
+
|
|
105
|
+
@current_scope.start_if_binding(@binding_number, if_binding_setup)
|
|
106
|
+
|
|
107
|
+
# Add the node, the binding number, then store the location where the
|
|
108
|
+
# bindings for this block starts.
|
|
109
|
+
@current_scope = Scope.new(@binding_number)
|
|
110
|
+
@scopes << @current_scope
|
|
111
|
+
|
|
112
|
+
@binding_number += 1
|
|
113
|
+
return html
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def add_else_binding(node, content)
|
|
117
|
+
html = add_close_mustache(node, false)
|
|
118
|
+
|
|
119
|
+
html += "<!-- $#{@binding_number} -->"
|
|
120
|
+
template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
|
|
121
|
+
|
|
122
|
+
@current_scope.current_if_binding[1].add_branch(content, template_name)
|
|
123
|
+
|
|
124
|
+
# Add the node, the binding number, then store the location where the
|
|
125
|
+
# bindings for this block starts.
|
|
126
|
+
@current_scope = Scope.new(@binding_number)
|
|
127
|
+
@scopes << @current_scope
|
|
128
|
+
|
|
129
|
+
@binding_number += 1
|
|
130
|
+
|
|
131
|
+
return html
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def add_close_mustache(node, close_if=true)
|
|
135
|
+
scope = @scopes.pop
|
|
136
|
+
@current_scope = @scopes.last
|
|
137
|
+
|
|
138
|
+
# Close an outstanding if binding (if it exists)
|
|
139
|
+
@current_scope.close_if_binding! if close_if
|
|
140
|
+
|
|
141
|
+
# Track that this scope was closed out
|
|
142
|
+
@current_scope.add_closed_child_scope(scope)
|
|
143
|
+
|
|
144
|
+
html = "<!-- $/#{scope.outer_binding_number} -->"
|
|
145
|
+
|
|
146
|
+
return html
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# When we find a binding, we pass it's content in here and replace it with
|
|
150
|
+
# the return value
|
|
151
|
+
def add_text_binding(content)
|
|
152
|
+
html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
|
153
|
+
|
|
154
|
+
@current_scope.add_binding(@binding_number, "lambda { |target, context, id| ContentBinding.new(target, context, id, Proc.new { #{content} }) }")
|
|
155
|
+
|
|
156
|
+
@binding_number += 1
|
|
157
|
+
return html
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def setup_node_id(node)
|
|
161
|
+
id = node['id']
|
|
162
|
+
# First assign this node an id if it doesn't have one
|
|
163
|
+
unless id
|
|
164
|
+
id = node['id'] = "id#{@binding_number}"
|
|
165
|
+
@binding_number += 1
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Attribute bindings support multiple handlebar listeners
|
|
170
|
+
# Exvoltle:
|
|
171
|
+
# <button click="{_primary} {_important}">...
|
|
172
|
+
#
|
|
173
|
+
# To accomplish this, we create a new listener from the existing ones in the Proc
|
|
174
|
+
# that we pass to the binding when it is created.
|
|
175
|
+
def add_attribute_binding(node, attribute, content)
|
|
176
|
+
setup_node_id(node)
|
|
177
|
+
|
|
178
|
+
if content =~ /^\{[^\{]+\}$/
|
|
179
|
+
# Getter is the content inside of { ... }
|
|
180
|
+
add_single_getter(node, attribute, content)
|
|
181
|
+
else
|
|
182
|
+
add_multiple_getters(node, attribute, content)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def add_single_getter(node, attribute, content)
|
|
188
|
+
if attribute == 'checked' || true
|
|
189
|
+
# For a checkbox, we don't want to add
|
|
190
|
+
getter = content[1..-2]
|
|
191
|
+
else
|
|
192
|
+
# Otherwise we should combine them
|
|
193
|
+
# TODO: We should make .or handle assignment
|
|
194
|
+
getter = "_tmp = #{content[1..-2]}.or('') ; _tmp.reactive_manager.setter! { |val| self.#{content[1..-2]} = val } ; _tmp"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
@current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { #{getter} }) }")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def add_multiple_getters(node, attribute, content)
|
|
201
|
+
case attribute
|
|
202
|
+
when 'checked', 'value'
|
|
203
|
+
if parts.size > 1
|
|
204
|
+
# Multiple ReactiveValue's can not be passed to value or checked attributes.
|
|
205
|
+
raise "Multiple bindings can not be passed to a #{attribute} binding."
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
reactive_template_path = add_reactive_template(content)
|
|
210
|
+
|
|
211
|
+
@current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { ReactiveTemplate.new(context, #{reactive_template_path.inspect}) }) }")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Returns a path to a template for the content. This can be passed
|
|
215
|
+
# into ReactiveTemplate.new, along with the current context.
|
|
216
|
+
def add_reactive_template(content)
|
|
217
|
+
# Return a template path instead
|
|
218
|
+
template_name = "__attribute/#{@binding_number}"
|
|
219
|
+
full_template_path = "#{@template_parser.template_path}/#{section_name}/#{template_name}"
|
|
220
|
+
@binding_number += 1
|
|
221
|
+
|
|
222
|
+
attribute_template = Template.new(@template_parser, section_name, Nokogiri::HTML::DocumentFragment.parse(content))
|
|
223
|
+
@template_parser.add_template("#{section_name}/#{template_name}", attribute_template)
|
|
224
|
+
attribute_template.start_walk
|
|
225
|
+
attribute_template.pull_closed_block_scopes
|
|
226
|
+
|
|
227
|
+
return full_template_path
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def add_event_binding(node, attribute_name, content)
|
|
231
|
+
setup_node_id(node)
|
|
232
|
+
|
|
233
|
+
event = attribute_name[2..-1]
|
|
234
|
+
|
|
235
|
+
if node.name == 'a'
|
|
236
|
+
# For links, we need to add blank href to make it clickable.
|
|
237
|
+
node['href'] ||= ''
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
@current_scope.add_binding(node['id'], "lambda { |target, context, id| EventBinding.new(target, context, id, #{event.inspect}, Proc.new {|event| #{content} })}")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def pull_closed_block_scopes(scope=@current_scope)
|
|
244
|
+
if scope.closed_block_scopes
|
|
245
|
+
scope.closed_block_scopes.each do |sub_scope|
|
|
246
|
+
# Loop through any subscopes first, pull them in.
|
|
247
|
+
pull_closed_block_scopes(sub_scope)
|
|
248
|
+
|
|
249
|
+
# Grab everything between the start/end html comments
|
|
250
|
+
start_node = find_by_comment("$#{sub_scope.outer_binding_number}")
|
|
251
|
+
end_node = find_by_comment("$/#{sub_scope.outer_binding_number}")
|
|
252
|
+
|
|
253
|
+
move_nodes_to_new_template(start_node, end_node, sub_scope)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def move_nodes_to_new_template(start_node, end_node, scope)
|
|
260
|
+
# TODO: currently this doesn't handle spanning nodes within seperate containers.
|
|
261
|
+
# so doing tr's doesn't work for some reason.
|
|
262
|
+
|
|
263
|
+
start_parent = start_node.parent
|
|
264
|
+
start_parent = start_parent.children if start_parent.is_a?(Nokogiri::HTML::DocumentFragment) || start_parent.is_a?(Nokogiri::XML::Element)
|
|
265
|
+
start_index = start_parent.index(start_node) + 1
|
|
266
|
+
|
|
267
|
+
end_parent = end_node.parent
|
|
268
|
+
end_parent = end_parent.children if end_parent.is_a?(Nokogiri::HTML::DocumentFragment) || end_parent.is_a?(Nokogiri::XML::Element)
|
|
269
|
+
end_index = end_parent.index(end_node) - 1
|
|
270
|
+
|
|
271
|
+
move_nodes = start_parent[start_index..end_index]
|
|
272
|
+
move_nodes.remove
|
|
273
|
+
|
|
274
|
+
new_template = Template.new(@template_parser, section_name, move_nodes, scope)
|
|
275
|
+
|
|
276
|
+
@template_parser.add_template("#{section_name}/__template/#{scope.outer_binding_number}", new_template)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def find_by_comment(name)
|
|
281
|
+
return @template.xpath("descendant::comment()[. = ' #{name} ']").first
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def start_walk
|
|
285
|
+
walk(@template)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# We implement a dom walker that can walk down the dom and spit out output
|
|
289
|
+
# html as we go
|
|
290
|
+
def walk(node)
|
|
291
|
+
case node.type
|
|
292
|
+
when 1
|
|
293
|
+
# html node
|
|
294
|
+
walk_html_node(node)
|
|
295
|
+
when 3
|
|
296
|
+
# text node
|
|
297
|
+
walk_text_node(node)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
node.children.each do |child|
|
|
301
|
+
walk(child)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def walk_html_node(node)
|
|
306
|
+
if node.name[0] == ':' && node.path.count('/') > 1
|
|
307
|
+
parse_component(node)
|
|
308
|
+
elsif node.name == 'textarea'
|
|
309
|
+
parse_textarea(node)
|
|
310
|
+
else
|
|
311
|
+
parse_html_node(node)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# We provide a quick way to render components with tags starting
|
|
316
|
+
# with a :
|
|
317
|
+
# Count the number of /'s in the path, if we are at the root node
|
|
318
|
+
# we can ignore it, since this is the template its self.
|
|
319
|
+
# TODO: Root node might not be the template if we parsed directly
|
|
320
|
+
# without a subtemplate specifier. We need to find a good way to
|
|
321
|
+
# parse only within the subtemplate.
|
|
322
|
+
def parse_component(node)
|
|
323
|
+
template_path = node.name[1..-1].gsub(':', '/')
|
|
324
|
+
|
|
325
|
+
# Take the attributes and turn them into a hash
|
|
326
|
+
attribute_hash = {}
|
|
327
|
+
node.attribute_nodes.each do |attribute_node|
|
|
328
|
+
content = attribute_node.value
|
|
329
|
+
|
|
330
|
+
if !content.index('{')
|
|
331
|
+
# passing in a string
|
|
332
|
+
value = content.inspect
|
|
333
|
+
elsif content =~ /^\{[^\}]+\}$/
|
|
334
|
+
# Has one binding, just get it
|
|
335
|
+
value = "Proc.new { #{content[1..-2]} }"
|
|
336
|
+
else
|
|
337
|
+
# Has multiple bindings, we need to render a template here
|
|
338
|
+
attr_template_path = add_reactive_template(content)
|
|
339
|
+
|
|
340
|
+
value = "Proc.new { ReactiveTemplate.new(context, #{attr_template_path.inspect}) }"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
attribute_hash[attribute_node.name] = value
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
attributes_string = attribute_hash.to_a.map do |key, value|
|
|
347
|
+
"#{key.inspect} => #{value}"
|
|
348
|
+
end.join(', ')
|
|
349
|
+
|
|
350
|
+
# Setup the arguments string, which goes to the TemplateBinding
|
|
351
|
+
args_str = "#{template_path.inspect}"
|
|
352
|
+
args_str << ", {#{attributes_string}}" if attribute_hash.size > 0
|
|
353
|
+
|
|
354
|
+
new_html = add_template(node, args_str)
|
|
355
|
+
|
|
356
|
+
node.swap(new_html)#Nokogiri::HTML::DocumentFragment.parse(new_html))
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def parse_textarea(node)
|
|
360
|
+
# The contents of textareas should really be treated like a
|
|
361
|
+
# value= attribute. So here we pull the content into a value attribute
|
|
362
|
+
# if the textarea has bindings in the content.
|
|
363
|
+
if node.inner_html =~ /\{[^\}]+\}/
|
|
364
|
+
node[:value] = node.inner_html
|
|
365
|
+
node.children.remove
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
parse_html_node(node)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def parse_html_node(node)
|
|
372
|
+
node.attribute_nodes.each do |attribute_node|
|
|
373
|
+
if attribute_node.name =~ /^e\-/
|
|
374
|
+
# We have an e- binding
|
|
375
|
+
add_event_binding(node, attribute_node.name, attribute_node.value)
|
|
376
|
+
|
|
377
|
+
# remove the attribute
|
|
378
|
+
attribute_node.remove
|
|
379
|
+
elsif attribute_node.value.match(/\{[^\}]+\}/)
|
|
380
|
+
# Has bindings
|
|
381
|
+
add_attribute_binding(node, attribute_node.name, attribute_node.value)
|
|
382
|
+
|
|
383
|
+
# remove the attribute
|
|
384
|
+
attribute_node.remove
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def walk_text_node(node)
|
|
390
|
+
new_html = node.content.gsub(/\{([^\}]+)\}/) do |template_binding|
|
|
391
|
+
add_binding(node, $1)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# puts "------! #{new_html.inspect} - #{node.class.inspect} - #{node.inspect}"
|
|
395
|
+
|
|
396
|
+
# TODO: Broke here in jruby
|
|
397
|
+
node.swap(new_html)# if new_html.blank?
|
|
398
|
+
|
|
399
|
+
#Nokogiri::HTML::DocumentFragment.parse(new_html))
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
class TemplateParser
|
|
404
|
+
attr_accessor :dom, :bindings, :template_path
|
|
405
|
+
|
|
406
|
+
def initialize(template, template_path)
|
|
407
|
+
@templates = {}
|
|
408
|
+
@template_path = template_path
|
|
409
|
+
|
|
410
|
+
template_fragment = Nokogiri::HTML::DocumentFragment.parse(template)
|
|
411
|
+
|
|
412
|
+
# Add templates for each section
|
|
413
|
+
|
|
414
|
+
# Check for sections
|
|
415
|
+
sections = []
|
|
416
|
+
if template_fragment.children[0].name[0] == ':'
|
|
417
|
+
template_fragment.children.each do |child|
|
|
418
|
+
if child.is_a?(Nokogiri::XML::Element)
|
|
419
|
+
sections << [child, child.name[1..-1]]
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
else
|
|
423
|
+
sections << [template_fragment, 'body']
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
sections.each do |section, name|
|
|
427
|
+
template = Template.new(self, name, section)
|
|
428
|
+
add_template(name, template)
|
|
429
|
+
template.start_walk
|
|
430
|
+
template.pull_closed_block_scopes
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def add_template(name, template)
|
|
436
|
+
@templates[@template_path + '/' + name] = template
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Return the templates, but map the html from nokogiri to html
|
|
440
|
+
def templates
|
|
441
|
+
mapped = {}
|
|
442
|
+
@templates.each_pair do |name, template|
|
|
443
|
+
mapped[name] = {
|
|
444
|
+
'html' => template.html,
|
|
445
|
+
'bindings' => template.current_scope.bindings
|
|
446
|
+
}
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
return mapped
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
end
|