volt 0.7.1 → 0.7.2
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 +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
|