volt 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/volt/tasks/store_tasks.rb +8 -5
- data/lib/volt/cli.rb +8 -1
- data/lib/volt/console.rb +13 -1
- data/lib/volt/extra_core/extra_core.rb +5 -0
- data/lib/volt/extra_core/string.rb +10 -0
- data/lib/volt/extra_core/stringify_keys.rb +6 -0
- data/lib/volt/extra_core/symbol.rb +25 -0
- data/lib/volt/extra_core/time.rb +16 -0
- data/lib/volt/models/model.rb +3 -3
- data/lib/volt/models/store.rb +67 -55
- data/lib/volt/page/bindings/component_binding.rb +27 -0
- data/lib/volt/page/bindings/template_binding.rb +35 -32
- data/lib/volt/page/channel.rb +0 -1
- data/lib/volt/page/channel_stub.rb +38 -0
- data/lib/volt/page/page.rb +40 -27
- data/lib/volt/page/sub_context.rb +4 -0
- data/lib/volt/server/channel_handler.rb +4 -0
- data/lib/volt/server/channel_handler_stub.rb +29 -0
- data/lib/volt/server/component_handler.rb +2 -3
- data/lib/volt/server/template_parser.rb +3 -3
- data/lib/volt/server.rb +30 -1
- data/lib/volt/tasks/dispatcher.rb +3 -2
- data/lib/volt.rb +2 -2
- data/spec/page/sub_context_spec.rb +10 -0
- data/volt.gemspec +1 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4e6123d91124a8535f3f22faaa795a3fd568988
|
4
|
+
data.tar.gz: 088c3ff7fb4ea5d2c8d847111aafe413df710764
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48debc3dc906b16a0774be28c7feef8e3ba2730608dc3e901399423877105489a9b1401cb68f6028eb3f9ef05c8d21711412c88b0d1730d951403b1ba773d6b5
|
7
|
+
data.tar.gz: d61897a965dc4edd12737867c8fd95e96672ac300a39d82ff683f5d5d470e5ebf1c369f9635639c21fca43209c02b164769d66d4187e6eeab7df6c049ec0b71e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.1
|
@@ -3,6 +3,7 @@ require_relative 'channel_tasks'
|
|
3
3
|
|
4
4
|
class StoreTasks
|
5
5
|
def initialize(channel=nil, dispatcher=nil)
|
6
|
+
puts "init store tasks"
|
6
7
|
@@mongo_db ||= Mongo::MongoClient.new("localhost", 27017)
|
7
8
|
@@db ||= @@mongo_db.db("development")
|
8
9
|
|
@@ -33,13 +34,15 @@ class StoreTasks
|
|
33
34
|
end
|
34
35
|
|
35
36
|
id = id['_id']
|
36
|
-
|
37
|
-
|
38
|
-
ChannelTasks.send_message_to_channel("#{collection}##{id}", ['update', nil, id, data.merge('_id' => id)], @channel)
|
37
|
+
|
38
|
+
puts "DATA: #{data.merge('_id' => id).symbolize_keys.inspect}"
|
39
|
+
ChannelTasks.send_message_to_channel("#{collection}##{id}", ['update', nil, id, data.merge('_id' => id).symbolize_keys], @channel)
|
39
40
|
end
|
40
41
|
|
41
42
|
def find(collection, scope, query=nil)
|
42
|
-
|
43
|
-
|
43
|
+
results = @@db[collection].find(scope).to_a.map {|item| item.symbolize_keys }
|
44
|
+
puts "FIND: #{collection.inspect} - #{scope} - #{results.inspect}"
|
45
|
+
|
46
|
+
return results
|
44
47
|
end
|
45
48
|
end
|
data/lib/volt/cli.rb
CHANGED
@@ -31,7 +31,14 @@ class CLI < Thor
|
|
31
31
|
end
|
32
32
|
|
33
33
|
ENV['SERVER'] = 'true'
|
34
|
-
Thin::Runner.new(['start']).run!
|
34
|
+
Thin::Runner.new(['start', '--threaded', '--max-persistent-conns', '100', "--max-conns", "300"]).run!
|
35
|
+
|
36
|
+
# require 'volt/server'
|
37
|
+
#
|
38
|
+
# EM.run do
|
39
|
+
# thin = Rack::Handler.get("thin")
|
40
|
+
# thin.run(Server.new.app, Port: 5000)
|
41
|
+
# end
|
35
42
|
end
|
36
43
|
|
37
44
|
desc "gem GEM", "Creates a component gem where you can share a component"
|
data/lib/volt/console.rb
CHANGED
@@ -10,10 +10,22 @@ class Console
|
|
10
10
|
require 'volt/models/params'
|
11
11
|
require 'volt/server/template_parser'
|
12
12
|
require 'volt'
|
13
|
+
require 'volt/page/page'
|
14
|
+
require 'volt/server/rack/component_paths'
|
15
|
+
require 'volt/server/channel_handler_stub'
|
16
|
+
|
17
|
+
ChannelHandlerStub.dispatcher = Dispatcher.new
|
18
|
+
|
19
|
+
|
20
|
+
app_path = File.expand_path(File.join(Dir.pwd, "app"))
|
21
|
+
component_paths = ComponentPaths.new
|
22
|
+
component_paths.add_tasks_to_load_path
|
13
23
|
|
14
24
|
Pry.config.prompt_name = 'volt'
|
15
25
|
|
16
26
|
# start a REPL session
|
17
|
-
Pry.start
|
27
|
+
# Pry.start
|
28
|
+
|
29
|
+
Page.new.pry
|
18
30
|
end
|
19
31
|
end
|
@@ -5,3 +5,8 @@ require 'volt/extra_core/stringify_keys'
|
|
5
5
|
require 'volt/extra_core/string'
|
6
6
|
require 'volt/extra_core/numeric'
|
7
7
|
require 'volt/extra_core/true_false'
|
8
|
+
if RUBY_PLATFORM == 'opal'
|
9
|
+
# TODO: != does not work with opal for some reason
|
10
|
+
else
|
11
|
+
require 'volt/extra_core/symbol'
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Symbol
|
2
|
+
def camelize
|
3
|
+
to_s.camelize.to_sym
|
4
|
+
end
|
5
|
+
|
6
|
+
def underscore
|
7
|
+
to_s.underscore.to_sym
|
8
|
+
end
|
9
|
+
|
10
|
+
def pluralize
|
11
|
+
to_s.pluralize.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def singularize
|
15
|
+
to_s.singularize.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def plural?
|
19
|
+
to_s.plural?
|
20
|
+
end
|
21
|
+
|
22
|
+
def singular?
|
23
|
+
to_s.singular?
|
24
|
+
end
|
25
|
+
end
|
data/lib/volt/models/model.rb
CHANGED
@@ -178,10 +178,10 @@ class Model
|
|
178
178
|
# If this isn't a model yet, instantiate it
|
179
179
|
@parent.send(:"#{path}=", new_array_model([], @parent, @path))
|
180
180
|
result = @parent.send(path)
|
181
|
-
|
182
|
-
# Add the new item
|
183
|
-
result << value
|
184
181
|
end
|
182
|
+
|
183
|
+
# Add the new item
|
184
|
+
result << value
|
185
185
|
|
186
186
|
return result
|
187
187
|
end
|
data/lib/volt/models/store.rb
CHANGED
@@ -5,50 +5,47 @@ class Store < Model
|
|
5
5
|
|
6
6
|
@@identity_map = {}
|
7
7
|
|
8
|
+
attr_reader :state
|
9
|
+
|
8
10
|
def initialize(tasks=nil, *args)
|
9
11
|
@tasks = tasks
|
12
|
+
@state = :not_loaded
|
10
13
|
|
11
14
|
super(*args)
|
12
15
|
|
13
|
-
track_in_identity_map if attributes && attributes[
|
16
|
+
track_in_identity_map if attributes && attributes[:_id]
|
14
17
|
|
15
18
|
value_updated
|
16
19
|
end
|
17
|
-
|
18
|
-
# def _id
|
19
|
-
# return attributes && attributes['_id']
|
20
|
-
# end
|
21
20
|
|
22
21
|
def event_added(event, scope_provider, first)
|
23
22
|
if first && event == :changed
|
24
23
|
# Start listening
|
25
24
|
ensure_id
|
26
|
-
|
27
|
-
channel_name = "#{self.path[-2]}##{self.attributes['_id']}"
|
28
|
-
puts "LISTENER ON: #{channel_name.inspect} -- #{self.path.inspect}"
|
29
|
-
$page.tasks.call('ChannelTasks', 'add_listener', channel_name)
|
30
|
-
end
|
25
|
+
change_channel_connection("add")
|
31
26
|
end
|
32
27
|
end
|
33
28
|
|
34
29
|
def event_removed(event, no_more_events)
|
35
30
|
if no_more_events && event == :changed
|
36
31
|
# Stop listening
|
37
|
-
|
38
|
-
channel_name = "#{self.path[-2]}##{self.attributes['_id']}"
|
39
|
-
puts "REMOVE LISTENER ON: #{channel_name}"
|
40
|
-
$page.tasks.call('ChannelTasks', 'remove_listener', channel_name)
|
41
|
-
end
|
32
|
+
change_channel_connection("remove")
|
42
33
|
end
|
43
34
|
end
|
44
35
|
|
36
|
+
def change_channel_connection(add_or_remove)
|
37
|
+
if attributes && path.size > 1
|
38
|
+
channel_name = "#{path[-2]}##{attributes[:_id]}"
|
39
|
+
$page.tasks.call('ChannelTasks', "#{add_or_remove}_listener", channel_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
45
43
|
def self.update(model_id, data)
|
46
44
|
model = @@identity_map[model_id]
|
47
45
|
|
48
46
|
if model
|
49
47
|
data.each_pair do |key, value|
|
50
48
|
if key != '_id'
|
51
|
-
puts "update #{key} with #{value.inspect}"
|
52
49
|
model.send(:"#{key}=", value)
|
53
50
|
end
|
54
51
|
end
|
@@ -63,6 +60,11 @@ class Store < Model
|
|
63
60
|
end
|
64
61
|
|
65
62
|
def method_missing(method_name, *args, &block)
|
63
|
+
if method_name[-1] == ']'
|
64
|
+
# Load the model
|
65
|
+
self.load!
|
66
|
+
end
|
67
|
+
|
66
68
|
result = super
|
67
69
|
|
68
70
|
if method_name[0] == '_' && method_name[-1] == '='
|
@@ -74,36 +76,39 @@ class Store < Model
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def track_in_identity_map
|
77
|
-
@@identity_map[attributes[
|
79
|
+
@@identity_map[attributes[:_id]] = self
|
78
80
|
end
|
79
81
|
|
80
82
|
# When called, will setup an id if there is not one
|
81
83
|
def ensure_id
|
82
84
|
# No id yet, lets create one
|
83
|
-
if attributes && !attributes[
|
84
|
-
self.attributes[
|
85
|
+
if attributes && !attributes[:_id]
|
86
|
+
self.attributes[:_id] = generate_id
|
85
87
|
track_in_identity_map
|
86
88
|
end
|
87
89
|
end
|
88
90
|
|
89
91
|
def value_updated
|
90
|
-
|
91
|
-
if (
|
92
|
+
path_size = path.size
|
93
|
+
if !(defined?($loading_models) && $loading_models) && @tasks && path_size > 0 && !nil?
|
92
94
|
|
93
95
|
ensure_id
|
94
96
|
|
95
|
-
if
|
96
|
-
self.attributes[path[-
|
97
|
+
if path_size > 3 && parent && source = parent.parent
|
98
|
+
self.attributes[:"#{path[-4].singularize}_id"] = source._id
|
97
99
|
end
|
98
100
|
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
puts "Save: #{collection} - #{attrs.inspect}"
|
103
|
-
@tasks.call('StoreTasks', 'save', collection, attrs)
|
101
|
+
# puts "Save: #{collection} - #{attrs.inspect}"
|
102
|
+
@tasks.call('StoreTasks', 'save', collection, self_attributes)
|
104
103
|
end
|
105
104
|
end
|
106
105
|
|
106
|
+
# Return the attributes that are only for this store, not any sub-associations.
|
107
|
+
def self_attributes
|
108
|
+
# Don't store any sub-stores, those will do their own saving.
|
109
|
+
attrs = attributes.reject {|k,v| v.is_a?(Model) || v.is_a?(ArrayModel) }
|
110
|
+
end
|
111
|
+
|
107
112
|
def collection(path=nil)
|
108
113
|
path ||= self.path
|
109
114
|
|
@@ -121,41 +126,48 @@ class Store < Model
|
|
121
126
|
self.attributes ||= {}
|
122
127
|
attributes[method_name] = model
|
123
128
|
|
129
|
+
if model.state == :not_loaded
|
130
|
+
model.load!
|
131
|
+
end
|
132
|
+
|
124
133
|
return model
|
125
134
|
end
|
126
135
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
# Check to see the parents scope so we can only lookup associated
|
136
|
-
# models.
|
137
|
-
scope = {}
|
138
|
-
|
139
|
-
if parent.attributes && parent.attributes['_id'].true?
|
140
|
-
scope[path[-1].singularize + '_id'] = parent._id
|
141
|
-
end
|
136
|
+
def load!
|
137
|
+
if @state == :not_loaded
|
138
|
+
@state = :loading
|
139
|
+
|
140
|
+
if @tasks && path.last.plural?
|
141
|
+
# Check to see the parents scope so we can only lookup associated
|
142
|
+
# models.
|
143
|
+
scope = {}
|
142
144
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
$loading_models = true
|
147
|
-
results.each do |result|
|
148
|
-
# Get model again, we need to fetch it each time so it gets the
|
149
|
-
# updated model when it switches from nil.
|
150
|
-
# TODO: Strange that this is needed
|
151
|
-
model = self.send(path.last)
|
152
|
-
model << Store.new(@tasks, result, model, path + [:[]], class_paths)
|
145
|
+
# Scope to the parent
|
146
|
+
if path.size > 2 && (attrs = parent.attributes) && attrs[:_id].true?
|
147
|
+
scope[:"#{path[-3].singularize}_id"] = parent._id
|
153
148
|
end
|
154
|
-
|
149
|
+
|
150
|
+
load_child_models(scope)
|
155
151
|
end
|
156
152
|
end
|
157
153
|
|
158
|
-
return
|
154
|
+
return self
|
155
|
+
end
|
156
|
+
|
157
|
+
def load_child_models(scope)
|
158
|
+
# puts "FIND: #{collection(path).inspect} at #{scope.inspect}"
|
159
|
+
@tasks.call('StoreTasks', 'find', collection(path), scope) do |results|
|
160
|
+
# TODO: Globals evil, replace
|
161
|
+
$loading_models = true
|
162
|
+
results.each do |result|
|
163
|
+
self << Store.new(@tasks, result, self, path + [:[]], @class_paths)
|
164
|
+
end
|
165
|
+
$loading_models = false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def new_model(attributes={}, parent=nil, path=nil, class_paths=nil)
|
170
|
+
return Store.new(@tasks, attributes, parent, path, class_paths)
|
159
171
|
end
|
160
172
|
|
161
173
|
def new_array_model(*args)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'volt/page/bindings/template_binding'
|
2
|
+
|
3
|
+
# Component bindings are the same as template bindings, but handle components
|
4
|
+
# and do not pass their context through
|
5
|
+
class ComponentBinding < TemplateBinding
|
6
|
+
# The context for a component binding can be either the controller, or the
|
7
|
+
# component arguments (@model), with the $page as the context. This gives
|
8
|
+
# components access to the page collections.
|
9
|
+
def render_template(full_path, controller_name)
|
10
|
+
# TODO: at the moment a :body section and a :title will both initialize different
|
11
|
+
# controllers. Maybe we should have a way to tie them together?
|
12
|
+
controller = get_controller(controller_name)
|
13
|
+
if controller
|
14
|
+
# The user provided a controller, pass in the model as an argument (in a
|
15
|
+
# sub-context)
|
16
|
+
args = []
|
17
|
+
args << SubContext.new(@model) if @model
|
18
|
+
|
19
|
+
current_context = controller.new(*args)
|
20
|
+
else
|
21
|
+
# The user didn't specify a model, create a
|
22
|
+
current_context = SubContext.new(@model || {}, $page)
|
23
|
+
end
|
24
|
+
|
25
|
+
@current_template = TemplateRenderer.new(@target, current_context, @binding_name, full_path)
|
26
|
+
end
|
27
|
+
end
|
@@ -97,7 +97,7 @@ class TemplateBinding < BaseBinding
|
|
97
97
|
controller = nil
|
98
98
|
if path_position > 1
|
99
99
|
# Lookup the controller
|
100
|
-
controller = [full_path[0], full_path[1]]
|
100
|
+
controller = [full_path[0], full_path[1] + 'Controller']
|
101
101
|
end
|
102
102
|
return path, controller
|
103
103
|
end
|
@@ -110,34 +110,37 @@ class TemplateBinding < BaseBinding
|
|
110
110
|
full_path, controller_name = path_for_template(@path.cur, @section.cur)
|
111
111
|
|
112
112
|
@current_template.remove if @current_template
|
113
|
-
|
114
|
-
current_context = @context
|
115
|
-
|
113
|
+
|
116
114
|
if @model
|
117
115
|
# Load in any procs
|
118
116
|
@model.each_pair do |key,value|
|
119
117
|
if value.class == Proc
|
120
|
-
@model[key] = value.call
|
118
|
+
@model[key.gsub('-', '_')] = value.call
|
121
119
|
end
|
122
120
|
end
|
123
121
|
end
|
124
122
|
|
123
|
+
render_template(full_path, controller_name)
|
124
|
+
end
|
125
|
+
|
126
|
+
# The context for templates can be either a controller, or the original context.
|
127
|
+
def render_template(full_path, controller_name)
|
125
128
|
# TODO: at the moment a :body section and a :title will both initialize different
|
126
129
|
# controllers. Maybe we should have a way to tie them together?
|
127
|
-
|
130
|
+
|
131
|
+
controller = get_controller(controller_name)
|
132
|
+
if controller
|
128
133
|
args = []
|
129
134
|
args << SubContext.new(@model) if @model
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# Passed in attributes, but there is no controller
|
137
|
-
current_context = SubContext.new(@model, current_context)
|
136
|
+
# Setup the controller
|
137
|
+
current_context = controller.new(*args)
|
138
|
+
else
|
139
|
+
# Pass the context directly
|
140
|
+
current_context = @context
|
138
141
|
end
|
139
142
|
|
140
|
-
@current_template = TemplateRenderer.new(@target, current_context, @binding_name, full_path)
|
143
|
+
@current_template = TemplateRenderer.new(@target, current_context, @binding_name, full_path)
|
141
144
|
end
|
142
145
|
|
143
146
|
def remove
|
@@ -164,28 +167,28 @@ class TemplateBinding < BaseBinding
|
|
164
167
|
|
165
168
|
# Fetch the controller class
|
166
169
|
def get_controller(controller_name)
|
167
|
-
|
168
|
-
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
176
|
-
else
|
177
|
-
# Get controller directlry
|
178
|
-
base_object = Object
|
170
|
+
return nil unless controller_name && controller_name.size > 0
|
171
|
+
|
172
|
+
# Get the constant parts
|
173
|
+
parts = controller_name.map {|v| v.gsub('-', '_').camelize }
|
174
|
+
|
175
|
+
# Home doesn't get namespaced
|
176
|
+
if parts.first == 'Home'
|
177
|
+
parts.shift
|
179
178
|
end
|
180
179
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
180
|
+
# Do const lookups starting at object and working our way down.
|
181
|
+
# So Volt::ProgressBar would lookup Volt, then ProgressBar on Volt.
|
182
|
+
obj = Object
|
183
|
+
parts.each do |part|
|
184
|
+
if obj.const_defined?(part)
|
185
|
+
obj = obj.const_get(part)
|
186
|
+
else
|
187
|
+
return nil
|
185
188
|
end
|
186
189
|
end
|
187
|
-
|
188
|
-
return
|
190
|
+
|
191
|
+
return obj
|
189
192
|
end
|
190
193
|
|
191
194
|
end
|
data/lib/volt/page/channel.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Acts the same as the Channel class on the front-end, but calls
|
2
|
+
# directly instead of using sockjs.
|
3
|
+
|
4
|
+
require 'volt/reactive/events'
|
5
|
+
require 'volt/tasks/dispatcher'
|
6
|
+
|
7
|
+
# Behaves the same as the Channel class, only the Channel class uses
|
8
|
+
# sockjs to pass messages to the backend. ChannelStub, simply passes
|
9
|
+
# them directly to ChannelHandlerStub.
|
10
|
+
class ChannelStub
|
11
|
+
include ReactiveTags
|
12
|
+
|
13
|
+
attr_reader :state, :error, :reconnect_interval
|
14
|
+
|
15
|
+
def initiailze
|
16
|
+
@state = :connected
|
17
|
+
end
|
18
|
+
|
19
|
+
def opened
|
20
|
+
trigger!('open')
|
21
|
+
trigger!('changed')
|
22
|
+
end
|
23
|
+
|
24
|
+
def message_received(*message)
|
25
|
+
trigger!('message', nil, *message)
|
26
|
+
end
|
27
|
+
|
28
|
+
tag_method(:send_message) do
|
29
|
+
destructive!
|
30
|
+
end
|
31
|
+
def send_message(message)
|
32
|
+
ChannelHandlerStub.new(self).process_message(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def close!
|
36
|
+
raise "close! should not be called on the backend channel"
|
37
|
+
end
|
38
|
+
end
|
data/lib/volt/page/page.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
if RUBY_PLATFORM == 'opal'
|
2
|
+
require 'opal'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'opal-jquery'
|
4
|
+
require 'opal-jquery'
|
5
|
+
end
|
6
6
|
require 'volt/models'
|
7
7
|
require 'volt/models/params'
|
8
8
|
require 'volt/controllers/model_controller'
|
@@ -11,13 +11,19 @@ require 'volt/page/bindings/content_binding'
|
|
11
11
|
require 'volt/page/bindings/each_binding'
|
12
12
|
require 'volt/page/bindings/if_binding'
|
13
13
|
require 'volt/page/bindings/template_binding'
|
14
|
+
require 'volt/page/bindings/component_binding'
|
14
15
|
require 'volt/page/bindings/event_binding'
|
15
16
|
require 'volt/page/template_renderer'
|
16
17
|
require 'volt/page/reactive_template'
|
17
18
|
require 'volt/page/document_events'
|
18
19
|
require 'volt/page/sub_context'
|
19
20
|
require 'volt/page/targets/dom_target'
|
20
|
-
|
21
|
+
|
22
|
+
if RUBY_PLATFORM == 'opal'
|
23
|
+
require 'volt/page/channel'
|
24
|
+
else
|
25
|
+
require 'volt/page/channel_stub'
|
26
|
+
end
|
21
27
|
require 'volt/router/routes'
|
22
28
|
require 'volt/models/url'
|
23
29
|
require 'volt/page/url_tracker'
|
@@ -46,24 +52,23 @@ class Page
|
|
46
52
|
@events = DocumentEvents.new
|
47
53
|
@render_queue = RenderQueue.new
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
}
|
58
|
-
});
|
55
|
+
if RUBY_PLATFORM == 'opal'
|
56
|
+
# Setup escape binding for console
|
57
|
+
%x{
|
58
|
+
$(document).keyup(function(e) {
|
59
|
+
if (e.keyCode == 27) {
|
60
|
+
Opal.gvars.page.$launch_console();
|
61
|
+
}
|
62
|
+
});
|
59
63
|
|
60
|
-
|
61
|
-
|
62
|
-
|
64
|
+
$(document).on('click', 'a', function(event) {
|
65
|
+
Opal.gvars.page.$link_clicked($(this).attr('href'));
|
66
|
+
event.stopPropagation();
|
63
67
|
|
64
|
-
|
65
|
-
|
66
|
-
|
68
|
+
return false;
|
69
|
+
});
|
70
|
+
}
|
71
|
+
end
|
67
72
|
end
|
68
73
|
|
69
74
|
def tasks
|
@@ -90,7 +95,13 @@ class Page
|
|
90
95
|
end
|
91
96
|
|
92
97
|
def channel
|
93
|
-
@channel ||=
|
98
|
+
@channel ||= begin
|
99
|
+
if Volt.client?
|
100
|
+
ReactiveValue.new(Channel.new)
|
101
|
+
else
|
102
|
+
ReactiveValue.new(ChannelStub.new)
|
103
|
+
end
|
104
|
+
end
|
94
105
|
end
|
95
106
|
|
96
107
|
def events
|
@@ -136,9 +147,11 @@ class Page
|
|
136
147
|
end
|
137
148
|
end
|
138
149
|
|
139
|
-
|
150
|
+
if Volt.client?
|
151
|
+
$page = Page.new
|
140
152
|
|
141
|
-
# Call start once the page is loaded
|
142
|
-
Document.ready? do
|
143
|
-
|
144
|
-
end
|
153
|
+
# Call start once the page is loaded
|
154
|
+
Document.ready? do
|
155
|
+
$page.start
|
156
|
+
end
|
157
|
+
end
|
@@ -10,6 +10,10 @@ class SubContext
|
|
10
10
|
@context = context
|
11
11
|
end
|
12
12
|
|
13
|
+
def respond_to?(method_name)
|
14
|
+
!!(@locals[method_name.to_s] || (@context && @context.respond_to?(method_name)) || super)
|
15
|
+
end
|
16
|
+
|
13
17
|
def method_missing(method_name, *args, &block)
|
14
18
|
method_name = method_name.to_s
|
15
19
|
if @locals[method_name]
|
@@ -8,6 +8,10 @@ class ChannelHandler < SockJS::Session
|
|
8
8
|
@@dispatcher = val
|
9
9
|
end
|
10
10
|
|
11
|
+
def self.dispatcher
|
12
|
+
@@dispatcher
|
13
|
+
end
|
14
|
+
|
11
15
|
# Sends a message to all, optionally skipping a users channel
|
12
16
|
def self.send_message_all(skip_channel=nil, *args)
|
13
17
|
@@channels.each do |channel|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class ChannelHandlerStub
|
2
|
+
def self.dispatcher=(val)
|
3
|
+
@@dispatcher = val
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.dispatcher
|
7
|
+
@@dispatcher
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(channel_stub)
|
11
|
+
puts "INIT WITH : #{channel_stub.inspect}"
|
12
|
+
@channel_stub = channel_stub
|
13
|
+
end
|
14
|
+
|
15
|
+
# Sends a message to all, optionally skipping a users channel
|
16
|
+
def self.send_message_all(skip_channel=nil, *args)
|
17
|
+
# Stub
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_message(message)
|
21
|
+
puts "GOT: #{message.inspect}"
|
22
|
+
@@dispatcher.dispatch(self, message)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_message(*args)
|
26
|
+
puts "SEND MSG: #{args.inspect}"
|
27
|
+
@channel_stub.message_received(*args)
|
28
|
+
end
|
29
|
+
end
|
@@ -16,14 +16,13 @@ class ComponentHandler
|
|
16
16
|
component_name = req.path.strip.gsub(/^\/components\//, '').gsub(/[.]js$/, '')
|
17
17
|
|
18
18
|
code = ''
|
19
|
-
|
20
|
-
|
19
|
+
|
21
20
|
component_files = ComponentFiles.new(component_name, @component_paths, true)
|
22
21
|
component_files.component_paths.each do |component_path, component_name|
|
23
22
|
code << ComponentTemplates.new(component_path, component_name).code
|
24
23
|
code << "\n\n"
|
25
24
|
end
|
26
|
-
|
25
|
+
|
27
26
|
javascript_code = Opal.compile(code)
|
28
27
|
|
29
28
|
# puts "ENV: #{env.inspect}"
|
@@ -69,10 +69,10 @@ class Template
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
def add_template(node, content)
|
72
|
+
def add_template(node, content, name='Template')
|
73
73
|
html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
|
74
74
|
|
75
|
-
@current_scope.add_binding(@binding_number, "lambda { |target, context, id|
|
75
|
+
@current_scope.add_binding(@binding_number, "lambda { |target, context, id| #{name}Binding.new(target, context, id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")
|
76
76
|
|
77
77
|
@binding_number += 1
|
78
78
|
return html
|
@@ -351,7 +351,7 @@ class Template
|
|
351
351
|
args_str = "#{template_path.inspect}"
|
352
352
|
args_str << ", {#{attributes_string}}" if attribute_hash.size > 0
|
353
353
|
|
354
|
-
new_html = add_template(node, args_str)
|
354
|
+
new_html = add_template(node, args_str, 'Component')
|
355
355
|
|
356
356
|
node.swap(new_html)#Nokogiri::HTML::DocumentFragment.parse(new_html))
|
357
357
|
end
|
data/lib/volt/server.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
ENV['SERVER'] = 'true'
|
2
|
+
|
1
3
|
require 'opal'
|
2
4
|
require "rack"
|
3
5
|
if RUBY_PLATFORM != 'java'
|
4
6
|
require "rack/sockjs"
|
5
7
|
require "eventmachine"
|
6
8
|
end
|
7
|
-
require "sprockets-sass"
|
8
9
|
require "sass"
|
10
|
+
require "sprockets-sass"
|
9
11
|
require 'listen'
|
10
12
|
|
11
13
|
require 'volt/extra_core/extra_core'
|
@@ -18,6 +20,26 @@ require 'volt/server/rack/index_files'
|
|
18
20
|
require 'volt/server/rack/opal_files'
|
19
21
|
require 'volt/tasks/dispatcher'
|
20
22
|
|
23
|
+
module Rack
|
24
|
+
# TODO: For some reason in Rack (or maybe thin), 304 headers close
|
25
|
+
# the http connection. We might need to make this check if keep
|
26
|
+
# alive was in the request.
|
27
|
+
class KeepAlive
|
28
|
+
def initialize(app)
|
29
|
+
@app = app
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
status, headers, body = @app.call(env)
|
34
|
+
|
35
|
+
if status == 304 && env['HTTP_CONNECTION'].downcase == 'keep-alive'
|
36
|
+
headers['Connection'] = 'keep-alive'
|
37
|
+
end
|
38
|
+
|
39
|
+
[status, headers, body]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
21
43
|
|
22
44
|
class Server
|
23
45
|
def initialize
|
@@ -38,6 +60,13 @@ class Server
|
|
38
60
|
|
39
61
|
def app
|
40
62
|
@app = Rack::Builder.new
|
63
|
+
# @app.use Rack::Chunked
|
64
|
+
@app.use Rack::ContentLength
|
65
|
+
|
66
|
+
@app.use Rack::KeepAlive
|
67
|
+
@app.use Rack::ConditionalGet
|
68
|
+
@app.use Rack::ETag
|
69
|
+
|
41
70
|
@app.use Rack::CommonLogger
|
42
71
|
@app.use Rack::ShowExceptions
|
43
72
|
|
@@ -6,12 +6,13 @@ class Dispatcher
|
|
6
6
|
callback_id, class_name, method_name, *args = message
|
7
7
|
|
8
8
|
# TODO: Think about security?
|
9
|
-
|
10
9
|
if class_name[/Tasks$/] && !class_name['::']
|
10
|
+
# TODO: Improve error on a class we don't have
|
11
11
|
require(class_name.underscore)
|
12
|
-
|
12
|
+
|
13
13
|
# Get the class
|
14
14
|
klass = Object.send(:const_get, class_name)
|
15
|
+
puts "KLASS: #{klass.inspect}"
|
15
16
|
|
16
17
|
# Init and send the method
|
17
18
|
result = klass.new(channel, self).send(method_name, *args)
|
data/lib/volt.rb
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'volt/page/sub_context'
|
2
|
+
|
3
|
+
describe SubContext do
|
4
|
+
it "should respond_to correctly on locals" do
|
5
|
+
sub_context = SubContext.new({:name => 'Name'})
|
6
|
+
|
7
|
+
expect(sub_context.respond_to?(:name)).to eq(true)
|
8
|
+
expect(sub_context.respond_to?(:missing)).to eq(false)
|
9
|
+
end
|
10
|
+
end
|
data/volt.gemspec
CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_dependency "oj", "~> 2.5.0"
|
34
34
|
spec.add_dependency "rake", "~> 10.0.4"
|
35
35
|
spec.add_dependency "listen", "~> 2.4.0"
|
36
|
+
# spec.add_dependency "rack-colorized_logger", "~> 1.0.4"
|
36
37
|
|
37
38
|
|
38
39
|
spec.add_development_dependency "bundler", "~> 1.5"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: volt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stout
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -315,6 +315,8 @@ files:
|
|
315
315
|
- lib/volt/extra_core/object.rb
|
316
316
|
- lib/volt/extra_core/string.rb
|
317
317
|
- lib/volt/extra_core/stringify_keys.rb
|
318
|
+
- lib/volt/extra_core/symbol.rb
|
319
|
+
- lib/volt/extra_core/time.rb
|
318
320
|
- lib/volt/extra_core/true_false.rb
|
319
321
|
- lib/volt/extra_core/try.rb
|
320
322
|
- lib/volt/models.rb
|
@@ -328,12 +330,14 @@ files:
|
|
328
330
|
- lib/volt/models/url.rb
|
329
331
|
- lib/volt/page/bindings/attribute_binding.rb
|
330
332
|
- lib/volt/page/bindings/base_binding.rb
|
333
|
+
- lib/volt/page/bindings/component_binding.rb
|
331
334
|
- lib/volt/page/bindings/content_binding.rb
|
332
335
|
- lib/volt/page/bindings/each_binding.rb
|
333
336
|
- lib/volt/page/bindings/event_binding.rb
|
334
337
|
- lib/volt/page/bindings/if_binding.rb
|
335
338
|
- lib/volt/page/bindings/template_binding.rb
|
336
339
|
- lib/volt/page/channel.rb
|
340
|
+
- lib/volt/page/channel_stub.rb
|
337
341
|
- lib/volt/page/document_events.rb
|
338
342
|
- lib/volt/page/memory_test.rb
|
339
343
|
- lib/volt/page/page.rb
|
@@ -365,6 +369,7 @@ files:
|
|
365
369
|
- lib/volt/server.rb
|
366
370
|
- lib/volt/server/binding_setup.rb
|
367
371
|
- lib/volt/server/channel_handler.rb
|
372
|
+
- lib/volt/server/channel_handler_stub.rb
|
368
373
|
- lib/volt/server/component_handler.rb
|
369
374
|
- lib/volt/server/component_templates.rb
|
370
375
|
- lib/volt/server/if_binding_setup.rb
|
@@ -393,6 +398,7 @@ files:
|
|
393
398
|
- spec/models/reactive_value_spec.rb
|
394
399
|
- spec/models/store_spec.rb
|
395
400
|
- spec/models/string_extensions_spec.rb
|
401
|
+
- spec/page/sub_context_spec.rb
|
396
402
|
- spec/router/routes_spec.rb
|
397
403
|
- spec/server/rack/component_files_spec.rb
|
398
404
|
- spec/server/rack/component_paths_spec.rb
|
@@ -475,6 +481,7 @@ test_files:
|
|
475
481
|
- spec/models/reactive_value_spec.rb
|
476
482
|
- spec/models/store_spec.rb
|
477
483
|
- spec/models/string_extensions_spec.rb
|
484
|
+
- spec/page/sub_context_spec.rb
|
478
485
|
- spec/router/routes_spec.rb
|
479
486
|
- spec/server/rack/component_files_spec.rb
|
480
487
|
- spec/server/rack/component_paths_spec.rb
|