volt 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|