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 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