volt 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -2
- data/Readme.md +97 -56
- data/VERSION +1 -1
- data/app/volt/assets/js/sockjs-0.3.4.min.js +27 -0
- data/app/volt/assets/js/vertxbus.js +216 -0
- data/app/volt/tasks/live_query/live_query.rb +5 -5
- data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +5 -0
- data/app/volt/tasks/store_tasks.rb +44 -18
- data/docs/WHY.md +10 -0
- data/lib/volt/cli.rb +18 -7
- data/lib/volt/controllers/model_controller.rb +30 -8
- data/lib/volt/extra_core/inflections.rb +63 -0
- data/lib/volt/extra_core/inflector/inflections.rb +203 -0
- data/lib/volt/extra_core/inflector/methods.rb +63 -0
- data/lib/volt/extra_core/inflector.rb +4 -0
- data/lib/volt/extra_core/object.rb +9 -0
- data/lib/volt/extra_core/string.rb +10 -14
- data/lib/volt/models/array_model.rb +45 -27
- data/lib/volt/models/cursor.rb +6 -0
- data/lib/volt/models/model.rb +127 -12
- data/lib/volt/models/model_hash_behaviour.rb +8 -5
- data/lib/volt/models/model_helpers.rb +4 -4
- data/lib/volt/models/model_state.rb +22 -0
- data/lib/volt/models/persistors/array_store.rb +49 -35
- data/lib/volt/models/persistors/base.rb +3 -3
- data/lib/volt/models/persistors/model_store.rb +17 -6
- data/lib/volt/models/persistors/query/query_listener.rb +0 -2
- data/lib/volt/models/persistors/store.rb +0 -4
- data/lib/volt/models/persistors/store_state.rb +27 -0
- data/lib/volt/models/url.rb +2 -2
- data/lib/volt/models/validations/errors.rb +0 -0
- data/lib/volt/models/validations/length.rb +13 -0
- data/lib/volt/models/validations/validations.rb +82 -0
- data/lib/volt/models.rb +1 -1
- data/lib/volt/page/bindings/attribute_binding.rb +29 -14
- data/lib/volt/page/bindings/base_binding.rb +2 -2
- data/lib/volt/page/bindings/component_binding.rb +29 -25
- data/lib/volt/page/bindings/content_binding.rb +1 -0
- data/lib/volt/page/bindings/each_binding.rb +25 -33
- data/lib/volt/page/bindings/event_binding.rb +0 -1
- data/lib/volt/page/bindings/if_binding.rb +3 -1
- data/lib/volt/page/bindings/template_binding.rb +61 -28
- data/lib/volt/page/document_events.rb +3 -1
- data/lib/volt/page/draw_cycle.rb +22 -0
- data/lib/volt/page/page.rb +10 -1
- data/lib/volt/page/reactive_template.rb +23 -16
- data/lib/volt/page/sub_context.rb +1 -1
- data/lib/volt/page/targets/attribute_section.rb +3 -2
- data/lib/volt/page/targets/attribute_target.rb +0 -4
- data/lib/volt/page/targets/base_section.rb +25 -0
- data/lib/volt/page/targets/binding_document/component_node.rb +13 -14
- data/lib/volt/page/targets/binding_document/html_node.rb +4 -0
- data/lib/volt/page/targets/dom_section.rb +16 -67
- data/lib/volt/page/targets/dom_template.rb +99 -0
- data/lib/volt/page/targets/helpers/comment_searchers.rb +29 -0
- data/lib/volt/page/template_renderer.rb +2 -14
- data/lib/volt/reactive/array_extensions.rb +0 -1
- data/lib/volt/reactive/event_chain.rb +9 -2
- data/lib/volt/reactive/events.rb +44 -37
- data/lib/volt/reactive/object_tracking.rb +1 -1
- data/lib/volt/reactive/reactive_array.rb +18 -0
- data/lib/volt/reactive/reactive_count.rb +108 -0
- data/lib/volt/reactive/reactive_generator.rb +44 -0
- data/lib/volt/reactive/reactive_value.rb +73 -73
- data/lib/volt/reactive/string_extensions.rb +1 -1
- data/lib/volt/router/routes.rb +205 -88
- data/lib/volt/server/component_handler.rb +3 -1
- data/lib/volt/server/html_parser/view_parser.rb +20 -4
- data/lib/volt/server/rack/component_paths.rb +13 -10
- data/lib/volt/server/rack/index_files.rb +4 -4
- data/lib/volt/server/socket_connection_handler.rb +5 -1
- data/lib/volt/server.rb +10 -3
- data/spec/apps/kitchen_sink/.gitignore +8 -0
- data/spec/apps/kitchen_sink/Gemfile +32 -0
- data/spec/apps/kitchen_sink/app/home/views/index/index.html +3 -5
- data/spec/apps/kitchen_sink/config.ru +4 -0
- data/spec/apps/kitchen_sink/public/index.html +2 -2
- data/spec/extra_core/inflector_spec.rb +8 -0
- data/spec/models/event_chain_spec.rb +18 -0
- data/spec/models/model_buffers_spec.rb +9 -0
- data/spec/models/model_spec.rb +22 -9
- data/spec/models/reactive_array_spec.rb +26 -1
- data/spec/models/reactive_call_times_spec.rb +28 -0
- data/spec/models/reactive_value_spec.rb +19 -0
- data/spec/models/validations_spec.rb +39 -0
- data/spec/page/bindings/content_binding_spec.rb +1 -0
- data/spec/{templates → page/bindings}/template_binding_spec.rb +54 -0
- data/spec/router/routes_spec.rb +156 -8
- data/spec/server/html_parser/sandlebars_parser_spec.rb +55 -47
- data/spec/server/html_parser/view_parser_spec.rb +3 -0
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/spec/spec_helper.rb +25 -11
- data/spec/templates/targets/binding_document/component_node_spec.rb +12 -0
- data/templates/project/Gemfile.tt +11 -0
- data/templates/project/app/home/config/routes.rb +1 -1
- data/templates/project/app/home/controllers/index_controller.rb +5 -5
- data/templates/project/app/home/views/index/index.html +6 -6
- data/volt.gemspec +5 -6
- metadata +34 -76
- data/app/volt/assets/js/sockjs-0.2.1.min.js +0 -27
- data/lib/volt/reactive/object_tracker.rb +0 -107
@@ -3,8 +3,8 @@ require 'volt/reactive/reactive_tags'
|
|
3
3
|
require 'volt/reactive/string_extensions'
|
4
4
|
require 'volt/reactive/array_extensions'
|
5
5
|
require 'volt/reactive/reactive_array'
|
6
|
-
require 'volt/reactive/object_tracker'
|
7
6
|
require 'volt/reactive/destructive_methods'
|
7
|
+
require 'volt/reactive/reactive_generator'
|
8
8
|
|
9
9
|
class Object
|
10
10
|
def cur
|
@@ -29,10 +29,11 @@ class ReactiveValue < BasicObject
|
|
29
29
|
# :methods- needs to return a straight up array to work with irb tab completion
|
30
30
|
# :eql? - needed for .uniq to work correctly
|
31
31
|
# :to_ary - in some places ruby expects to get an array back from this method
|
32
|
-
SKIP_METHODS = [:hash, :methods, :eql?, :respond_to?, :respond_to_missing?, :to_ary, :to_int]#, :instance_of?, :kind_of?, :to_s, :to_str]
|
32
|
+
SKIP_METHODS = [:object_id, :hash, :methods, :eql?, :respond_to?, :respond_to_missing?, :to_ary, :to_int]#, :instance_of?, :kind_of?, :to_s, :to_str]
|
33
33
|
|
34
34
|
def initialize(getter, setter=nil, scope=nil)
|
35
|
-
@reactive_manager = ::ReactiveManager.new(getter, setter, scope)
|
35
|
+
@reactive_manager = ::ReactiveManager.new(self, getter, setter, scope)
|
36
|
+
# @reactive_cache = {}
|
36
37
|
end
|
37
38
|
|
38
39
|
def reactive?
|
@@ -42,7 +43,7 @@ class ReactiveValue < BasicObject
|
|
42
43
|
# Proxy methods to the ReactiveManager. We want to have as few
|
43
44
|
# as possible methods on reactive values, so all other methods
|
44
45
|
# are forwarded to the object the reactive value points to.
|
45
|
-
[:cur, :cur=, :deep_cur, :on, :trigger!, :trigger_by_scope
|
46
|
+
[:cur, :cur=, :deep_cur, :on, :trigger!, :trigger_by_scope!, :with].each do |method_name|
|
46
47
|
define_method(method_name) do |*args, &block|
|
47
48
|
@reactive_manager.send(method_name, *args, &block)
|
48
49
|
end
|
@@ -80,6 +81,9 @@ class ReactiveValue < BasicObject
|
|
80
81
|
method_name, *args = args
|
81
82
|
end
|
82
83
|
|
84
|
+
# result = @reactive_cache[[method_name, args.map(&:object_id)]]
|
85
|
+
# return result if result
|
86
|
+
|
83
87
|
# For some methods, we pass directly to the current object. This
|
84
88
|
# helps ReactiveValue's be well behaved ruby citizens.
|
85
89
|
# Also skip if this is a destructive method
|
@@ -109,6 +113,11 @@ class ReactiveValue < BasicObject
|
|
109
113
|
|
110
114
|
# result = result.with(block_reactives) if block
|
111
115
|
|
116
|
+
# if args.size == 0 || method_name == :[]
|
117
|
+
# puts "STORE: #{method_name} - #{args.inspect}"
|
118
|
+
# @reactive_cache[[method_name, args.map(&:object_id)]] = result
|
119
|
+
# end
|
120
|
+
|
112
121
|
return result
|
113
122
|
end
|
114
123
|
|
@@ -135,13 +144,10 @@ class ReactiveValue < BasicObject
|
|
135
144
|
# end
|
136
145
|
|
137
146
|
def respond_to_missing?(name, include_private=false)
|
147
|
+
puts "RT"
|
138
148
|
cur.respond_to?(name)
|
139
149
|
end
|
140
150
|
|
141
|
-
def with(*args, &block)
|
142
|
-
return @reactive_manager.with(*args, &block)
|
143
|
-
end
|
144
|
-
|
145
151
|
def inspect
|
146
152
|
"@#{cur.inspect}"
|
147
153
|
end
|
@@ -187,58 +193,14 @@ class ReactiveValue < BasicObject
|
|
187
193
|
end
|
188
194
|
end
|
189
195
|
|
190
|
-
class ReactiveGenerator
|
191
|
-
# Takes a hash and returns a ReactiveValue that depends on
|
192
|
-
# any ReactiveValue's inside of the hash (or children).
|
193
|
-
def self.from_hash(hash, skip_if_no_reactives=false)
|
194
|
-
reactives = find_reactives(hash)
|
195
|
-
|
196
|
-
if skip_if_no_reactives && reactives.size == 0
|
197
|
-
# There weren't any reactives, we can just use the hash
|
198
|
-
return hash
|
199
|
-
else
|
200
|
-
# Create a new reactive value that listens on all of its
|
201
|
-
# child reactive values.
|
202
|
-
value = ReactiveValue.new(hash)
|
203
|
-
|
204
|
-
reactives.each do |child|
|
205
|
-
value.reactive_manager.add_parent!(child)
|
206
|
-
end
|
207
|
-
|
208
|
-
return value
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
# Recursively loop through the data, returning a list of all
|
213
|
-
# reactive values in the hash, array, etc..
|
214
|
-
def self.find_reactives(object)
|
215
|
-
found = []
|
216
|
-
if object.reactive?
|
217
|
-
found << object
|
218
|
-
|
219
|
-
found += find_reactives(object.cur)
|
220
|
-
elsif object.is_a?(Array)
|
221
|
-
object.each do |item|
|
222
|
-
found += find_reactives(item)
|
223
|
-
end
|
224
|
-
elsif object.is_a?(Hash)
|
225
|
-
object.each_pair do |key, value|
|
226
|
-
found += find_reactives(key)
|
227
|
-
found += find_reactives(value)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
return found.flatten
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
196
|
class ReactiveManager
|
236
197
|
include ::Events
|
237
198
|
|
238
199
|
attr_reader :scope, :parents
|
239
200
|
|
240
201
|
# When created, ReactiveValue's get a getter (a proc)
|
241
|
-
def initialize(getter, setter=nil, scope=nil)
|
202
|
+
def initialize(reactive_value, getter, setter=nil, scope=nil)
|
203
|
+
@reactive_value = reactive_value
|
242
204
|
@getter = getter
|
243
205
|
@setter = setter
|
244
206
|
@scope = scope
|
@@ -246,6 +208,10 @@ class ReactiveManager
|
|
246
208
|
@parents = []
|
247
209
|
end
|
248
210
|
|
211
|
+
def reactive_value
|
212
|
+
@reactive_value
|
213
|
+
end
|
214
|
+
|
249
215
|
def reactive?
|
250
216
|
true
|
251
217
|
end
|
@@ -259,31 +225,28 @@ class ReactiveManager
|
|
259
225
|
end
|
260
226
|
|
261
227
|
|
262
|
-
def event_added(event, scope, first)
|
228
|
+
def event_added(event, scope, first, first_for_event)
|
263
229
|
# When the first event is registered, we need to start listening on our current object
|
264
230
|
# for it to publish events.
|
265
|
-
|
231
|
+
|
232
|
+
update_followers if first
|
266
233
|
end
|
267
234
|
|
268
|
-
def event_removed(event, last)
|
235
|
+
def event_removed(event, last, last_for_event)
|
269
236
|
# If no one is listening on the reactive value, then we don't need to listen on our
|
270
237
|
# current object for events, because no one cares.
|
271
|
-
object_tracker.disable! if @listeners.size == 0
|
272
|
-
end
|
273
238
|
|
274
|
-
|
275
|
-
@object_tracker ||= ::ObjectTracker.new(self)
|
239
|
+
remove_followers if last
|
276
240
|
end
|
277
241
|
|
278
242
|
|
279
243
|
# Fetch the current value
|
280
|
-
def cur
|
281
|
-
#
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
# end
|
244
|
+
def cur(shallow=false, ignore_cache=false)
|
245
|
+
# Return from cache if it is cached
|
246
|
+
if @cur_cache && !shallow && !ignore_cache
|
247
|
+
# puts "From Cache: #{@cur_cache.inspect}"
|
248
|
+
return @cur_cache
|
249
|
+
end
|
287
250
|
|
288
251
|
if @getter.class == ::Proc
|
289
252
|
# Get the current value, capture any errors
|
@@ -297,30 +260,67 @@ class ReactiveManager
|
|
297
260
|
result = @getter
|
298
261
|
end
|
299
262
|
|
300
|
-
if result.reactive?
|
263
|
+
if !shallow && result.reactive?
|
301
264
|
# Unwrap any stored reactive values
|
302
265
|
result = result.cur
|
303
266
|
end
|
304
267
|
|
305
|
-
#
|
306
|
-
# @cached_obj = result
|
307
|
-
# @cached_version = ObjectTracker.cache_version
|
308
|
-
# end
|
268
|
+
# puts "CUR FOR: #{result.inspect}"
|
309
269
|
|
310
270
|
return result
|
311
271
|
end
|
312
272
|
|
273
|
+
|
274
|
+
def update_followers
|
275
|
+
if has_listeners?
|
276
|
+
current_obj = cur(false, true)
|
277
|
+
should_attach = current_obj.respond_to?(:on)
|
278
|
+
# puts "SA #{should_attach} - #{current_obj.inspect}"
|
279
|
+
|
280
|
+
if should_attach
|
281
|
+
if !@cur_cache || current_obj.object_id != @cur_cache.object_id
|
282
|
+
# puts "CHANGED FROM: #{@cur_cache.inspect} to #{current_obj.inspect} - #{current_obj.object_id} vs #{@cur_cache.object_id}"
|
283
|
+
remove_followers
|
284
|
+
|
285
|
+
# puts "SET TO: #{current_obj.inspect} on #{self.inspect}"
|
286
|
+
@cur_cache_chain_listener = self.event_chain.add_object(current_obj)
|
287
|
+
end
|
288
|
+
else
|
289
|
+
remove_followers
|
290
|
+
end
|
291
|
+
|
292
|
+
# Store current if we have listeners
|
293
|
+
@cur_cache = current_obj
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def remove_followers
|
298
|
+
# puts "REMOVE FOLLOWERS: #{@cur_cache.inspect} on #{self.inspect}"
|
299
|
+
# Remove from previous
|
300
|
+
if @cur_cache
|
301
|
+
@cur_cache = nil
|
302
|
+
end
|
303
|
+
|
304
|
+
if @cur_cache_chain_listener
|
305
|
+
@cur_cache_chain_listener.remove
|
306
|
+
@cur_cache_chain_listener = nil
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
313
310
|
def cur=(val)
|
314
311
|
if @setter
|
315
312
|
@setter.call(val)
|
313
|
+
# update_followers
|
316
314
|
elsif @scope == nil
|
317
315
|
@getter = val
|
318
316
|
@setter = nil
|
319
317
|
|
318
|
+
# update_followers
|
320
319
|
trigger!('changed')
|
321
320
|
else
|
322
321
|
raise "Value can not be updated"
|
323
322
|
end
|
323
|
+
|
324
324
|
end
|
325
325
|
|
326
326
|
# Returns a copy of the object with where all ReactiveValue's are replaced
|
data/lib/volt/router/routes.rb
CHANGED
@@ -1,14 +1,53 @@
|
|
1
1
|
require 'volt'
|
2
2
|
|
3
|
-
class
|
4
|
-
|
3
|
+
# The Routes class takes a set of routes and sets up methods to go from
|
4
|
+
# a url to params, and params to url.
|
5
|
+
# routes do
|
6
|
+
# get "/about", _view: 'about'
|
7
|
+
# get "/blog/{_id}/edit", _view: 'blog/edit', _action: 'edit'
|
8
|
+
# get "/blog/{_id}", _view: 'blog/show', _action: 'show'
|
9
|
+
# get "/blog", _view: 'blog'
|
10
|
+
# get "/blog/new", _view: 'blog/new', _action: 'new'
|
11
|
+
# get "/cool/{_name}", _view: 'cool'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Using the routes above, we would generate the following:
|
15
|
+
#
|
16
|
+
# @direct_routes = {
|
17
|
+
# '/about' => {_view: 'about'},
|
18
|
+
# '/blog' => {_view: 'blog'}
|
19
|
+
# '/blog/new' => {_view: 'blog/new', _action: 'new'}
|
20
|
+
# }
|
21
|
+
#
|
22
|
+
# -- nil represents a terminal
|
23
|
+
# -- * represents any match
|
24
|
+
# -- a number for a parameter means use the value in that number section
|
25
|
+
#
|
26
|
+
# @indirect_routes = {
|
27
|
+
# '*' => {
|
28
|
+
# 'edit' => {
|
29
|
+
# nil => {_id: 1, _view: 'blog/edit', _action: 'edit'}
|
30
|
+
# }
|
31
|
+
# nil => {_id: 1, _view: 'blog/show', _action: 'show'}
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# Match for params
|
37
|
+
# @param_matches = [
|
38
|
+
# {_id: nil, _view: 'blog/edit', _action: 'edit'} => Proc.new {|params| "/blog/#{params.id}/edit", params.reject {|k,v| k == :id }}
|
39
|
+
# ]
|
5
40
|
|
41
|
+
class Routes
|
6
42
|
def initialize
|
7
|
-
|
43
|
+
# Paths where there are no bindings (an optimization)
|
44
|
+
@direct_routes = {}
|
8
45
|
|
9
|
-
|
10
|
-
|
11
|
-
|
46
|
+
# Paths with bindings
|
47
|
+
@indirect_routes = {}
|
48
|
+
|
49
|
+
# Matcher for going from params to url
|
50
|
+
@param_matches = []
|
12
51
|
end
|
13
52
|
|
14
53
|
def define(&block)
|
@@ -17,125 +56,203 @@ class Routes
|
|
17
56
|
return self
|
18
57
|
end
|
19
58
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
59
|
+
# Add a route
|
60
|
+
def get(path, params={})
|
61
|
+
params = params.symbolize_keys
|
62
|
+
if has_binding?(path)
|
63
|
+
add_indirect_path(path, params)
|
24
64
|
else
|
25
|
-
|
65
|
+
@direct_routes[path] = params
|
26
66
|
end
|
27
67
|
|
28
|
-
|
68
|
+
add_param_matcher(path, params)
|
29
69
|
end
|
30
70
|
|
31
|
-
# Takes
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
71
|
+
# Takes in params and generates a path and the remaining params
|
72
|
+
# that should be shown in the url. The extra "unused" params
|
73
|
+
# will be tacked onto the end of the url ?param1=value1, etc...
|
74
|
+
#
|
75
|
+
# returns the url and new params, or nil, nil if no match is found.
|
76
|
+
def params_to_url(test_params)
|
77
|
+
@param_matches.each do |param_matcher|
|
78
|
+
# TODO: Maybe a deep dup?
|
79
|
+
result, new_params = check_params_match(test_params.dup, param_matcher[0])
|
80
|
+
|
81
|
+
if result
|
82
|
+
return param_matcher[1].call(new_params)
|
42
83
|
end
|
43
84
|
end
|
44
85
|
|
45
|
-
|
86
|
+
return nil, nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Takes in a path and returns the matching params.
|
90
|
+
# returns params as a hash
|
91
|
+
def url_to_params(path)
|
92
|
+
# First try a direct match
|
93
|
+
result = @direct_routes[path]
|
94
|
+
return result if result
|
95
|
+
|
96
|
+
# Next, split the url and walk the sections
|
97
|
+
parts = url_parts(path)
|
98
|
+
|
99
|
+
return match_path(parts, parts, @indirect_routes)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
# Recursively walk the @indirect_routes hash, return the params for a route, return
|
104
|
+
# false for non-matches.
|
105
|
+
def match_path(original_parts, remaining_parts, node)
|
106
|
+
# Take off the top part and get the rest into a new array
|
107
|
+
# part will be nil if we are out of parts (fancy how that works out, now
|
108
|
+
# stand in wonder about how much someone thought this through, though
|
109
|
+
# really I just got lucky)
|
110
|
+
part, *parts = remaining_parts
|
46
111
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
params[section[1..-2]]
|
112
|
+
if part == nil
|
113
|
+
if node[part]
|
114
|
+
# We found a match, replace the bindings and return
|
115
|
+
# TODO: Handvle nested
|
116
|
+
return setup_bindings_in_params(original_parts, node[part])
|
53
117
|
else
|
54
|
-
|
118
|
+
return false
|
55
119
|
end
|
56
|
-
|
120
|
+
elsif (new_node = node[part])
|
121
|
+
# Direct match for section, continue
|
122
|
+
return match_path(original_parts, parts, new_node)
|
123
|
+
elsif (new_node = node['*'])
|
124
|
+
# Match on binding section
|
125
|
+
return match_path(original_parts, parts, new_node)
|
126
|
+
end
|
57
127
|
end
|
58
128
|
|
59
|
-
|
60
|
-
|
129
|
+
# The params out of match_path will have integers in the params that came from bindings
|
130
|
+
# in the url. This replaces those with the values from the url.
|
131
|
+
def setup_bindings_in_params(original_parts, params)
|
132
|
+
# Create a copy of the params we can modify and return
|
133
|
+
params = params.dup
|
61
134
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
match_path = match_path + "[^\\/]+"
|
68
|
-
else
|
69
|
-
match_path = match_path + section
|
135
|
+
params.each_pair do |key, value|
|
136
|
+
if value.is_a?(Fixnum)
|
137
|
+
# Lookup the param's value in the original url parts
|
138
|
+
params[key] = original_parts[value]
|
139
|
+
end
|
70
140
|
end
|
141
|
+
|
142
|
+
return params
|
71
143
|
end
|
72
144
|
|
73
|
-
@path_matchers << (/^#{match_path}$/)
|
74
|
-
end
|
75
145
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
146
|
+
# Build up the @indirect_routes data structure.
|
147
|
+
# '*' means wildcard match anything
|
148
|
+
# nil means a terminal, who's value will be the params.
|
149
|
+
#
|
150
|
+
# In the params, an integer vaule means the index of the wildcard
|
151
|
+
def add_indirect_path(path, params)
|
152
|
+
node = @indirect_routes
|
153
|
+
|
154
|
+
parts = url_parts(path)
|
155
|
+
|
156
|
+
parts.each_with_index do |part, index|
|
157
|
+
if has_binding?(part)
|
158
|
+
params[part[1..-2].to_sym] = index
|
159
|
+
|
160
|
+
# Set the part to be '*' (anything matcher)
|
161
|
+
part = '*'
|
162
|
+
end
|
163
|
+
|
164
|
+
node = (node[part] ||= {})
|
82
165
|
end
|
166
|
+
|
167
|
+
node[nil] = params
|
83
168
|
end
|
84
169
|
|
85
|
-
return '/', params
|
86
|
-
end
|
87
170
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# Found the matching route
|
99
|
-
return route[1]
|
171
|
+
def add_param_matcher(path, params)
|
172
|
+
params = params.dup
|
173
|
+
parts = url_parts(path)
|
174
|
+
|
175
|
+
parts.each_with_index do |part, index|
|
176
|
+
if has_binding?(part)
|
177
|
+
# Setup a nil param that can match anything, but gets
|
178
|
+
# assigned into the url
|
179
|
+
params[part[1..-2].to_sym] = nil
|
180
|
+
end
|
100
181
|
end
|
182
|
+
|
183
|
+
path_transformer = create_path_transformer(parts)
|
184
|
+
|
185
|
+
@param_matches << [params, path_transformer]
|
101
186
|
end
|
102
187
|
|
103
|
-
|
104
|
-
|
188
|
+
# Takes in url parts and returns a proc that takes in params and returns
|
189
|
+
# a url with the bindings filled in, and params with the binding params
|
190
|
+
# removed. (So the remaining can be added onto the end of the url ?params1=...)
|
191
|
+
def create_path_transformer(parts)
|
192
|
+
return lambda do |input_params|
|
193
|
+
input_params = input_params.dup
|
105
194
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
195
|
+
url = parts.map do |part|
|
196
|
+
val = if has_binding?(part)
|
197
|
+
# Get the
|
198
|
+
binding = part[1..-2].to_sym
|
199
|
+
input_params.delete(binding)
|
200
|
+
else
|
201
|
+
part
|
202
|
+
end
|
110
203
|
|
111
|
-
|
112
|
-
|
113
|
-
end
|
204
|
+
val
|
205
|
+
end.join('/')
|
114
206
|
|
115
|
-
|
207
|
+
return '/' + url, input_params
|
208
|
+
end
|
116
209
|
end
|
117
210
|
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
211
|
+
# Takes in a hash of params and checks to make sure keys in param_matcher
|
212
|
+
# are in test_params. Checks for equal value unless value in param_matcher
|
213
|
+
# is nil.
|
214
|
+
#
|
215
|
+
# returns false or true, new_params - where the new params are a the params not
|
216
|
+
# used in the basic match. Later some of these may be inserted into the url.
|
217
|
+
def check_params_match(test_params, param_matcher)
|
218
|
+
param_matcher.each_pair do |key, value|
|
124
219
|
if value.is_a?(Hash)
|
125
|
-
|
220
|
+
if test_params[key]
|
221
|
+
result = check_params_match(test_params[key], value)
|
126
222
|
|
127
|
-
|
128
|
-
|
223
|
+
if result == false
|
224
|
+
return false
|
225
|
+
else
|
226
|
+
test_params.delete(key)
|
227
|
+
end
|
228
|
+
else
|
229
|
+
# test_params did not have matching key
|
230
|
+
return false
|
231
|
+
end
|
232
|
+
elsif value == nil
|
233
|
+
unless test_params.has_key?(key)
|
234
|
+
return false
|
235
|
+
end
|
236
|
+
else
|
237
|
+
if test_params[key] == value
|
238
|
+
test_params.delete(key)
|
129
239
|
else
|
130
240
|
return false
|
131
241
|
end
|
132
|
-
elsif value != nil && value != params.send(key)
|
133
|
-
# A nil value means it can match anything, so we don't want to
|
134
|
-
# fail on nil.
|
135
|
-
return false
|
136
242
|
end
|
137
243
|
end
|
138
244
|
|
139
|
-
return true
|
245
|
+
return true, test_params
|
140
246
|
end
|
247
|
+
|
248
|
+
def url_parts(path)
|
249
|
+
return path.split('/').reject(&:blank?)
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# Check if a string has a binding in it
|
254
|
+
def has_binding?(string)
|
255
|
+
string.index('{') && string.index('}')
|
256
|
+
end
|
257
|
+
|
141
258
|
end
|
@@ -15,10 +15,12 @@ class ComponentHandler
|
|
15
15
|
|
16
16
|
code = ComponentCode.new(component_name, @component_paths).code
|
17
17
|
|
18
|
+
# puts "CODE: #{code}"
|
19
|
+
|
18
20
|
javascript_code = Opal.compile(code)
|
19
21
|
|
20
22
|
# puts "ENV: #{env.inspect}"
|
21
|
-
[200, {"Content-Type" => "text/html"}, StringIO.new(javascript_code)]
|
23
|
+
[200, {"Content-Type" => "text/html; charset=utf-8"}, StringIO.new(javascript_code)]
|
22
24
|
end
|
23
25
|
|
24
26
|
|
@@ -7,17 +7,33 @@ require 'volt/server/html_parser/textarea_scope'
|
|
7
7
|
|
8
8
|
class ViewParser
|
9
9
|
attr_reader :templates
|
10
|
-
|
10
|
+
|
11
11
|
def initialize(html, template_path)
|
12
12
|
@template_path = template_path
|
13
13
|
|
14
14
|
handler = ViewHandler.new(template_path)
|
15
|
-
|
15
|
+
|
16
16
|
SandlebarsParser.new(html, handler)
|
17
|
-
|
17
|
+
|
18
18
|
# Close out the last scope
|
19
19
|
handler.scope.last.close_scope
|
20
|
-
|
20
|
+
|
21
21
|
@templates = handler.templates
|
22
22
|
end
|
23
|
+
|
24
|
+
# Returns a parsed version of the data (useful for backend rendering
|
25
|
+
# and testing)
|
26
|
+
def data
|
27
|
+
templates = @templates.deep_clone
|
28
|
+
|
29
|
+
templates.each_pair do |name, value|
|
30
|
+
if value['bindings']
|
31
|
+
value['bindings'].each_pair do |number, binding|
|
32
|
+
value['bindings'][number] = binding.map {|code| eval(code) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
return templates
|
38
|
+
end
|
23
39
|
end
|