volt 0.4.0 → 0.4.1
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/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
|