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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10cc84012e873b87e79b5cd770e387ab1c292b03
4
- data.tar.gz: 2ba97e0d7442c60768ba4ceacf5c9ca13d96db6f
3
+ metadata.gz: d4e6123d91124a8535f3f22faaa795a3fd568988
4
+ data.tar.gz: 088c3ff7fb4ea5d2c8d847111aafe413df710764
5
5
  SHA512:
6
- metadata.gz: 5cf7ccd40cea80341c5138c59a7d8057143478f8ebbf6148fd3a2f050e1088fa6a94cf8a5591f9de93f848884d9ec50ef84ebb7031936a634583b6e3b357e686
7
- data.tar.gz: a28b2acab7c75e6fb85e48ee54c5c1ebc29a0851bd8d31e9badde216be6b23c820900fdca47c58a4ca9d476ed0aedc39f1ce83bfa07a984ad0b06435bad78266
6
+ metadata.gz: 48debc3dc906b16a0774be28c7feef8e3ba2730608dc3e901399423877105489a9b1401cb68f6028eb3f9ef05c8d21711412c88b0d1730d951403b1ba773d6b5
7
+ data.tar.gz: d61897a965dc4edd12737867c8fd95e96672ac300a39d82ff683f5d5d470e5ebf1c369f9635639c21fca43209c02b164769d66d4187e6eeab7df6c049ec0b71e
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
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
- # ChannelHandler.send_message_all(@channel, 'update', nil, id, data.merge('_id' => id))
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
- puts "FIND: #{collection.inspect} - #{scope}"
43
- return @@db[collection].find(scope).to_a
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
@@ -26,4 +26,14 @@ class String
26
26
  return self
27
27
  end
28
28
  end
29
+
30
+ def plural?
31
+ # TODO: Temp implementation
32
+ self[-1] == 's'
33
+ end
34
+
35
+ def singular?
36
+ # TODO: Temp implementation
37
+ self[-1] != 's'
38
+ end
29
39
  end
@@ -4,4 +4,10 @@ class Object
4
4
  hash[key.to_s] = value
5
5
  }
6
6
  end
7
+
8
+ def symbolize_keys
9
+ self.each_with_object({}) { |(key, value), hash|
10
+ hash[key.to_sym] = value
11
+ }
12
+ end
7
13
  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
@@ -0,0 +1,16 @@
1
+ class Temp1
2
+ include Events
3
+
4
+ attr_accessor :seconds
5
+ def initialize
6
+ @seconds = ReactiveValue.new(nil)
7
+ end
8
+
9
+ def seconds=(val)
10
+ @seconds.cur = val
11
+ end
12
+
13
+ def live_seconds
14
+ @seconds
15
+ end
16
+ end
@@ -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
@@ -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['_id']
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
- if self.attributes && self.path.size > 1
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
- if self.attributes && self.path.size > 1
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['_id']] = self
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['_id']
84
- self.attributes['_id'] = generate_id
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
- # puts "VU: #{@tasks.inspect} = #{path.inspect} - #{attributes.inspect}"
91
- if (!defined?($loading_models) || !$loading_models) && @tasks && path.size > 0 && !self.nil?
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 path.size > 2 && parent && source = parent.parent
96
- self.attributes[path[-2].to_s.singularize+'_id'] = source._id
97
+ if path_size > 3 && parent && source = parent.parent
98
+ self.attributes[:"#{path[-4].singularize}_id"] = source._id
97
99
  end
98
100
 
99
- # Don't store any sub-stores, those will do their own saving.
100
- attrs = attributes.reject {|k,v| v.is_a?(Model) || v.is_a?(ArrayModel) }
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
- def new_model(attributes={}, parent=nil, path=nil, class_paths=nil)
130
- model = Store.new(@tasks, attributes, parent, path, class_paths)
131
-
132
- if @tasks && path.last[-1] == 's'
133
- # puts "FIND NEW MODEL: #{path.inspect} - #{attributes.inspect}"
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
- puts "FIND: #{collection(path).inspect} at #{scope.inspect}"
144
- @tasks.call('StoreTasks', 'find', collection(path), scope) do |results|
145
- # TODO: Globals evil, replace
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
- $loading_models = false
149
+
150
+ load_child_models(scope)
155
151
  end
156
152
  end
157
153
 
158
- return model
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
- if controller_name
130
+
131
+ controller = get_controller(controller_name)
132
+ if controller
128
133
  args = []
129
134
  args << SubContext.new(@model) if @model
130
135
 
131
- controller = get_controller(controller_name)
132
-
133
- # Initialize the new controller
134
- current_context = (controller || ModelController).new(*args)
135
- elsif @model
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
- name = controller_name[1].gsub('-', '_').camelize
168
-
169
- # For the home object, we do not need to namespace our controller
170
- if controller_name[0] != 'home'
171
- # Controller is namespaced, lookup outer module first
172
- base_name = controller_name[0].gsub('-', '_').camelize.to_sym
173
- if Object.send(:const_defined?, base_name)
174
- base_object = Object.send(:const_get, base_name)
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
- if base_object
182
- name = (name + 'Controller').to_sym
183
- if base_object.send(:const_defined?, name)
184
- return base_object.send(:const_get, name)
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 nil
190
+
191
+ return obj
189
192
  end
190
193
 
191
194
  end
@@ -82,7 +82,6 @@ class Channel
82
82
  destructive!
83
83
  end
84
84
  def send_message(message)
85
- `console.log('do send message');`
86
85
  puts "Send #{message.inspect}"
87
86
  if @state != :open
88
87
  @queue << message
@@ -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
@@ -1,8 +1,8 @@
1
- require 'opal'
1
+ if RUBY_PLATFORM == 'opal'
2
+ require 'opal'
2
3
 
3
- ENV['CLIENT'] = true
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
- require 'volt/page/channel'
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
- # Add event for link clicks to handle all a onclick
50
- # EventBinding.new(self, )
51
-
52
- # Setup escape binding for console
53
- %x{
54
- $(document).keyup(function(e) {
55
- if (e.keyCode == 27) {
56
- Opal.gvars.page.$launch_console();
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
- $(document).on('click', 'a', function(event) {
61
- Opal.gvars.page.$link_clicked($(this).attr('href'));
62
- event.stopPropagation();
64
+ $(document).on('click', 'a', function(event) {
65
+ Opal.gvars.page.$link_clicked($(this).attr('href'));
66
+ event.stopPropagation();
63
67
 
64
- return false;
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 ||= ReactiveValue.new(Channel.new)
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
- $page = Page.new
150
+ if Volt.client?
151
+ $page = Page.new
140
152
 
141
- # Call start once the page is loaded
142
- Document.ready? do
143
- $page.start
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| TemplateBinding.new(target, context, id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")
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
@@ -6,11 +6,11 @@ class Volt
6
6
  end
7
7
 
8
8
  def self.server?
9
- !ENV['CLIENT']
9
+ !!ENV['SERVER']
10
10
  end
11
11
 
12
12
  def self.client?
13
- !!ENV['CLIENT']
13
+ !ENV['SERVER']
14
14
  end
15
15
 
16
16
  def self.source_maps?
@@ -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.0
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-21 00:00:00.000000000 Z
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