volt 0.9.5.pre4 → 0.9.5.pre5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +13 -5
  4. data/app/volt/assets/css/{notices.css.scss → notices.scss} +0 -0
  5. data/app/volt/models/active_volt_instance.rb +1 -1
  6. data/app/volt/tasks/live_query/live_query.rb +11 -3
  7. data/app/volt/tasks/store_tasks.rb +14 -17
  8. data/lib/volt/cli.rb +22 -0
  9. data/lib/volt/cli/asset_compile.rb +63 -63
  10. data/lib/volt/cli/base_index_renderer.rb +26 -0
  11. data/lib/volt/cli/generate.rb +1 -1
  12. data/lib/volt/config.rb +1 -0
  13. data/lib/volt/controllers/model_controller.rb +37 -1
  14. data/lib/volt/extra_core/array.rb +22 -0
  15. data/lib/volt/models/array_model.rb +7 -1
  16. data/lib/volt/models/errors.rb +1 -1
  17. data/lib/volt/models/field_helpers.rb +36 -21
  18. data/lib/volt/models/model.rb +16 -0
  19. data/lib/volt/models/validations/validations.rb +21 -6
  20. data/lib/volt/models/validators/type_validator.rb +35 -3
  21. data/lib/volt/page/bindings/content_binding.rb +1 -1
  22. data/lib/volt/page/bindings/event_binding.rb +40 -16
  23. data/lib/volt/page/document_events.rb +8 -6
  24. data/lib/volt/reactive/reactive_array.rb +18 -1
  25. data/lib/volt/server/forking_server.rb +7 -1
  26. data/lib/volt/server/html_parser/attribute_scope.rb +26 -0
  27. data/lib/volt/server/html_parser/component_view_scope.rb +30 -22
  28. data/lib/volt/server/middleware/default_middleware_stack.rb +6 -1
  29. data/lib/volt/server/rack/asset_files.rb +5 -3
  30. data/lib/volt/server/rack/opal_files.rb +35 -23
  31. data/lib/volt/server/rack/sprockets_helpers_setup.rb +71 -0
  32. data/lib/volt/server/template_handlers/view_processor.rb +1 -2
  33. data/lib/volt/utils/promise_extensions.rb +1 -1
  34. data/lib/volt/version.rb +1 -1
  35. data/lib/volt/volt/app.rb +0 -2
  36. data/lib/volt/volt/client_setup/browser.rb +11 -0
  37. data/spec/apps/kitchen_sink/Gemfile +37 -14
  38. data/spec/apps/kitchen_sink/app/main/config/routes.rb +3 -0
  39. data/spec/apps/kitchen_sink/app/main/controllers/events_controller.rb +26 -0
  40. data/spec/apps/kitchen_sink/app/main/views/events/index.html +30 -0
  41. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +3 -0
  42. data/spec/apps/kitchen_sink/app/main/views/main/yield.html +1 -6
  43. data/spec/apps/kitchen_sink/app/main/views/{yield-component → yield_component}/index.html +0 -0
  44. data/spec/extra_core/array_spec.rb +26 -0
  45. data/spec/integration/bindings_spec.rb +9 -0
  46. data/spec/integration/event_spec.rb +19 -0
  47. data/spec/models/array_model_spec.rb +13 -0
  48. data/spec/models/field_helpers_spec.rb +2 -2
  49. data/spec/models/validations_spec.rb +31 -0
  50. data/spec/models/validators/type_validator_spec.rb +47 -1
  51. data/spec/reactive/reactive_array_spec.rb +46 -0
  52. data/spec/server/forking_server_spec.rb +27 -0
  53. data/spec/server/html_parser/view_scope_spec.rb +44 -0
  54. data/spec/server/rack/asset_files_spec.rb +2 -2
  55. data/templates/project/Gemfile.tt +8 -0
  56. data/templates/project/config/app.rb.tt +2 -1
  57. data/volt.gemspec +1 -1
  58. metadata +31 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 237e158164bd2f394e80998fa3314f52024fbebc
4
- data.tar.gz: abd1d018ffd679afba929518326f0139cce4deb0
3
+ metadata.gz: 848470b46efd1f1d8835f8501af6fc39d052cd4c
4
+ data.tar.gz: 6ff661da03f4f1e572c566b95d2b91868748148e
5
5
  SHA512:
6
- metadata.gz: 48b5fa1a075beab16bbe901532b11e46e6ee4aa065d21db169bc425efc9def807d2d286f5ea4a2f6b569458b05fcb7052721ceb3df26aa7d585990a1d4fe6ffb
7
- data.tar.gz: d18c325ef1f789d19a6ceaf2327452fd5b06dcefda74d0eeeca8e66b070599544ebf5176779d465a67bf641987f9af40de34a3eb7bbcffcd4b8d469910b17aea
6
+ metadata.gz: 11b26392d7fab2365755625fafd4708cc8398eecf3aad5c83757a3fbdb6f52baa1c9f38f483d23a52ca61ae98e5e42955028e382e87abf524495851d72684644
7
+ data.tar.gz: fe2f9c21b79e7da0b478b84aaffaa86da7cf63bf73b2f0b793723c73351de40971027c17044907822b43adbc88d3028ed525a0a7a5aca54fa8718068e998b915
data/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@
7
7
  - Page load performance was improved, and more of sprockets was used for component loading.
8
8
  - You can now return promises in permissions blocks. Also, can_read?, can_create?, and .can_delete?
9
9
  - Anything in /public is now served via Rack::Static in the default middleware stack. (So you can put user uploaded images in there)
10
+ - You can now use _ or - in volt tag names and attributes. (We're moving to using dash ( - ) as the standard in html)
11
+ - You can now trigger events on controllers rendered as tags. The events will bubble up through the DOM and can be caught by any e- bindings. See the docs for more information.
12
+ - Rewrote the precompile pipeline.
13
+ - Added image compression by default. (using image_optim)
14
+ - All volt CLI tasks now can run from inside of any directory in the volt app (or the root)
15
+
16
+ ### Changed
17
+ - fix issue with ```raw``` and promises (#275)
18
+ - fix issue with .length on store (#269)
10
19
 
11
20
  ## 0.9.4
12
21
  ### Lingo Change
@@ -34,6 +43,7 @@ the base collections will now be called "Repositories" or "Repo's" for short. T
34
43
  - fixed issue with local_store not persisting in some cases
35
44
  - runners now block until messages have propigated to the message bus and updates have been pushed.
36
45
  - upgraded some dependency gems to fix a conflict
46
+ - fixed bug with .last on ReactiveArray (#259)
37
47
 
38
48
  ## 0.9.3
39
49
  [0.9.3 Update Blog Post](http://blog.voltframework.com/post/121128931859/0-9-3-stuff-you-asked-for)
data/README.md CHANGED
@@ -35,11 +35,19 @@ There is also a [work in progress tutorial](https://github.com/rhgraysonii/volt_
35
35
 
36
36
  # More Videos
37
37
 
38
- Rick Carlino has been putting together some great volt tutorial videos also.
39
-
40
- - [Volt Tasks](http://datamelon.io/blog/2015/creating-volt-task-objects.html)
41
- - [Build a Realtime Chat App with Volt](http://datamelon.io/blog/2015/building-a-chat-app-in-volt.html)
42
- - [Understanding Volt Views](http://datamelon.io/blog/2015/understanding-views-in-volt-with-a-card-game.html)
38
+ Rick Carlino has been putting together some [great volt tutorial videos](http://datamelon.io/blog) also.
39
+
40
+ - [Volt URL Routing](http://datamelon.io/blog/2015/routes-and-multi-view-apps.html)
41
+ - [Volt Tasks](http://datamelon.io/blog/2015/creating-volt-task-objects.html)
42
+ - [Volt Views](http://datamelon.io/blog/2015/understanding-views-in-volt-with-a-card-game.html)
43
+ - [Volt Permissions](http://datamelon.io/blog/2015/twitter-clone-demonstrates-volt-permissions.html)
44
+ - [Volt Runners](http://datamelon.io/blog/2015/automation-of-everything-with-volt-runners.html)
45
+ - [Volt Components](http://datamelon.io/blog/2015/staying-productive-with-the-volt-component-ecosystem.html)
46
+ - [REST APIs in Volt](http://datamelon.io/blog/2015/building-rest-apis-with-volt.html)
47
+ - [Javascript Library Interop](http://datamelon.io/blog/2015/using-js-libraries-with-opal.html)
48
+ - [Credit Card Payments with Volt](http://datamelon.io/blog/2015/payment-form-using-volt-and-stripe.html)
49
+ - [Build a Realtime Chat App](http://datamelon.io/blog/2015/building-a-chat-app-in-volt.html)
50
+ - [6 Key Concepts for New Volt Learners](http://datamelon.io/blog/2015/6-concepts-for-volt-beginners.html)
43
51
 
44
52
  @ahnbizcad maintains a [playlist of Volt related videos](https://www.youtube.com/watch?v=McxtO8ybxy8&list=PLmQFeDKFCPXatHb-zEXwfeMH01DPiZjP7).
45
53
 
@@ -2,5 +2,5 @@ class ActiveVoltInstance < Volt::Model
2
2
  field :server_id, String
3
3
  field :ips, String
4
4
  field :port, Fixnum
5
- field :time, Time
5
+ field :time#, Time
6
6
  end
@@ -30,11 +30,14 @@ class LiveQuery
30
30
  end
31
31
 
32
32
  def notify_added(index, data, skip_channel)
33
- # puts "Added: #{index} - #{data.inspect}"
34
33
  # Make model for testing permissions against
35
- model = model_for_filter(data)
34
+ model = nil
36
35
 
37
36
  notify! do |channel|
37
+ # Only load the model for filtering if we are sending to a channel
38
+ # (skip if we are the only one listening)
39
+ model ||= model_for_filter(data)
40
+
38
41
  filtered_data = nil
39
42
  Volt.as_user(channel.user_id) do
40
43
  filtered_data = model.filtered_attributes.sync
@@ -52,9 +55,14 @@ class LiveQuery
52
55
  end
53
56
 
54
57
  def notify_changed(id, data, skip_channel)
55
- model = model_for_filter(data)
58
+ # puts "NOTIFY CHANGED"
59
+ model = nil
56
60
 
57
61
  notify!(skip_channel) do |channel|
62
+ # Only load the model for filtering if we are sending to a channel
63
+ # (skip if we are the only one listening)
64
+ model ||= model_for_filter(data)
65
+
58
66
  filtered_data = nil
59
67
  Volt.as_user(channel.user_id) do
60
68
  filtered_data = model.filtered_attributes.sync
@@ -9,31 +9,21 @@ class StoreTasks < Volt::Task
9
9
  model_name = collection.singularize.camelize
10
10
 
11
11
  # Fetch the model
12
- collection = store.send(:"_#{path[-2]}")
12
+ collection = store.get(path[-2])
13
13
 
14
14
  # See if the model has already been made
15
- collection.where(id: data[:id]).first.then do |model|
16
- # Otherwise assign to the collection
17
- model ||= collection
15
+ model_promise = collection.where(id: data[:id]).first
18
16
 
19
- # Create a buffer
20
- buffer = model.buffer
21
-
22
- # Assign the changed data to the buffer
23
- buffer.assign_attributes(data, false, true)
24
-
25
- buffer
26
- end
17
+ return collection, model_promise
27
18
  end
28
19
 
29
20
  def save(collection, path, data)
30
21
  data = data.symbolize_keys
31
- promise = nil
22
+ model_promise = nil
32
23
 
33
- # Don't check the permissions when we load the model, since we want all fields
34
24
  Volt.skip_permissions do
35
25
  Volt::Model.no_validate do
36
- promise = load_model(collection, path, data)
26
+ collection, model_promise = load_model(collection, path, data)
37
27
  end
38
28
  end
39
29
 
@@ -44,9 +34,16 @@ class StoreTasks < Volt::Task
44
34
  # who sent the update.
45
35
  #
46
36
  # return another promise
47
- promise.then do |model|
37
+ model_promise.then do |model|
48
38
  Thread.current['in_channel'] = @channel
49
- save_promise = model.save!.then do |result|
39
+
40
+ result = if model
41
+ model.update(data)
42
+ else
43
+ collection.create(data)
44
+ end
45
+
46
+ save_promise = result.then do |result|
50
47
  next nil
51
48
  end.fail do |err|
52
49
  # An error object, convert to hash
data/lib/volt/cli.rb CHANGED
@@ -131,5 +131,27 @@ end
131
131
  # Add in more features
132
132
  require 'volt/cli/asset_compile'
133
133
 
134
+ unless Gem.win_platform?
135
+ # Change CWD to the root of the volt project
136
+ pwd = Dir.pwd
137
+ changed = false
138
+ loop do
139
+ if File.exists?(pwd + '/Gemfile')
140
+ Dir.chdir(pwd) if changed
141
+ break
142
+ else
143
+ changed = true
144
+
145
+ # Move up a directory and try again
146
+ pwd = pwd.gsub(/\/[^\/]+$/, '')
147
+
148
+ if pwd == ''
149
+ puts "You are not currently in a volt project directory"
150
+ exit 1
151
+ end
152
+ end
153
+ end
154
+ end
155
+
134
156
  puts "Volt #{Volt::Version::STRING}"
135
157
  Volt::CLI.start(ARGV)
@@ -9,9 +9,11 @@ module Volt
9
9
  private
10
10
 
11
11
  def compile
12
- puts 'compiling project...'
12
+ say "Starting Precompile...", :red
13
13
  require 'fileutils'
14
14
  ENV['SERVER'] = 'true'
15
+ ENV['MAPS'] = 'false'
16
+ ENV['NO_FORKING'] = 'true'
15
17
 
16
18
  require 'opal'
17
19
  require 'rack'
@@ -19,90 +21,88 @@ module Volt
19
21
  require 'volt/volt/core'
20
22
  require 'volt/boot'
21
23
  require 'volt/server'
24
+ require 'volt/server/rack/component_paths'
25
+ require 'volt/server/rack/component_code'
22
26
 
23
27
  @root_path ||= Dir.pwd
24
28
  Volt.root = @root_path
25
29
 
26
- volt_app = Volt.boot(@root_path)
27
-
28
- require 'volt/server/rack/component_paths'
29
- require 'volt/server/rack/component_code'
30
+ @volt_app = Volt.boot(@root_path)
30
31
 
31
32
  @app_path = File.expand_path(File.join(@root_path, 'app'))
32
33
 
33
- @component_paths = ComponentPaths.new(@root_path)
34
- @app = Rack::Builder.new
35
- @opal_files = OpalFiles.new(@app, @app_path, @component_paths)
36
- @index_files = IndexFiles.new(@app, volt_app, @component_paths, @opal_files)
37
-
38
- puts 'Compile Opal for components'
39
- write_component_js
40
- puts 'Copy assets'
41
- write_sprockets
42
- puts 'Compile JS/CSS'
43
- write_js_and_css
44
- puts 'Write index files'
34
+ say 'Compiling RB, JS, CSS, and Images...', :red
35
+ write_files_and_manifest
36
+ compile_manifests
37
+ say 'Write index files...', :red
45
38
  write_index
46
-
47
- puts "compiled"
48
- end
49
-
50
- def logical_paths_and_full_paths
51
- env = @opal_files.environment
52
- env.each_file do |full_path|
53
- # logical_path = env[full_path].logical_path
54
- # logical_path = @opal_files.environment.send(:logical_path_for_filename, full_path, []).to_s
55
- # puts "FULL PATH: #{full_path.inspect} -- #{logical_path}"
56
-
57
- # yield(logical_path, full_path.to_s)
58
- end
59
-
39
+ say "Done", :green
60
40
  end
61
41
 
62
- def write_sprockets
63
- # Serve the opal files
64
- logical_paths_and_full_paths do |logical_path, full_path|
65
- # Only include files that aren't compiled elsewhere, like fonts
66
- if !logical_path[/[.](y|css|js|html|erb)$/] &&
67
- File.extname(logical_path) != '' &&
68
- # opal includes some node modules in the standard lib that we don't need to compile in
69
- (full_path !~ /\/opal/ && full_path !~ /\/stdlib\// && logical_path !~ /^node_js\//)
70
- write_sprocket_file(logical_path)
42
+ def write_files_and_manifest
43
+ asset_files = AssetFiles.from_cache('main', @volt_app.component_paths)
44
+ # Write a temp css file
45
+ js = asset_files.javascript(@volt_app)
46
+ css = asset_files.css
47
+ @tmp_files = []
48
+
49
+ File.open(Volt.root + '/app/main/app.js', 'wb') do |file|
50
+ js.each do |type, src_or_body|
51
+ if type == :src
52
+ src = src_or_body
53
+ url = src.gsub(/^\/assets\//, '')
54
+ file.write("//= require '#{url}'\n")
55
+ else
56
+ body = src_or_body
57
+
58
+ # Write to a tempfile, since sprockets can't mix requires and
59
+ # code.
60
+
61
+ require 'securerandom'
62
+ hex = SecureRandom.hex
63
+ tmp_path = Volt.root + "/app/main/__#{hex}.js"
64
+ url = "main/__#{hex}"
65
+ file.write("//= require '#{url}'\n")
66
+
67
+ @tmp_files << tmp_path
68
+ File.open(tmp_path, 'wb') {|f| f.write("#{body}\n") }
69
+ end
71
70
  end
72
71
  end
73
- end
74
72
 
75
- def write_js_and_css
76
- (@index_files.javascript_files + @index_files.css_files).each do |logical_path|
77
- if logical_path =~ /^\/assets\//
78
- logical_path = logical_path.gsub(/^\/assets\//, '')
79
- write_sprocket_file(logical_path)
73
+ File.open(Volt.root + '/app/main/app.scss', 'wb') do |file|
74
+ css.each do |link|
75
+ url = link.gsub(/^\/assets\//, '')
76
+ file.write("//= require '#{url}'\n")
80
77
  end
81
78
  end
82
79
  end
83
80
 
84
- def write_sprocket_file(logical_path)
85
- path = "#{@root_path}/public/assets/#{logical_path}"
86
-
87
- begin
88
- # Only write out the assets
89
- # if logical_path =~ /\/assets\//
90
- content = @opal_files.environment[logical_path].to_s
91
- write_file(path, content)
92
- # end
93
- rescue Sprockets::FileNotFound, SyntaxError => e
94
- # ignore
95
- end
96
- end
81
+ def compile_manifests
82
+ manifest = Sprockets::Manifest.new(@volt_app.sprockets, './public/assets/manifest.json')
97
83
 
98
- def write_component_js
99
- write_sprocket_file('components/main.js')
84
+ # Compile the files (and linked assets)
85
+ manifest.compile('main/app.js')
86
+ manifest.compile('main/app.css')
87
+
88
+ # Clear temp files
89
+ @tmp_files.each {|path| FileUtils.rm(path) }
90
+
91
+ # Remove the temp files
92
+ FileUtils.rm(Volt.root + '/app/main/app.js')
93
+ FileUtils.rm(Volt.root + '/app/main/app.scss')
100
94
  end
101
95
 
102
96
  def write_index
103
- path = "#{@root_path}/public/index.html"
97
+ require 'volt/cli/base_index_renderer'
98
+
99
+ output_path = "#{@root_path}/public/index.html"
100
+ require 'json'
101
+
102
+ @manifest = JSON.parse(File.read(@root_path + '/public/assets/manifest.json'))
103
+ output_html = BaseIndexRenderer.new(@manifest).html
104
104
 
105
- write_file(path, @index_files.html)
105
+ write_file(output_path, output_html)
106
106
  end
107
107
 
108
108
  def write_file(path, data)
@@ -0,0 +1,26 @@
1
+ # Render the config/base/index.html when precompiling. Here we only render
2
+ # one js and one css file.
3
+
4
+ module Volt
5
+ class BaseIndexRenderer
6
+ def initialize(manifest)
7
+ @manifest = manifest
8
+ end
9
+
10
+ def html
11
+ index_path = File.expand_path(File.join(Volt.root, 'config/base/index.html'))
12
+ html = File.read(index_path)
13
+
14
+ ERB.new(html, nil, '-').result(binding)
15
+ end
16
+
17
+ # When writing the index, we render the
18
+ def javascript_tags
19
+ "<script src=\"/assets/#{@manifest['assets']['main/app.js']}\"></script>"
20
+ end
21
+
22
+ def css_tags
23
+ "<link href=\"/assets/#{@manifest['assets']['main/app.css']}\" media=\"all\" rel=\"stylesheet\" type=\"text/css\" />"
24
+ end
25
+ end
26
+ end
@@ -81,7 +81,7 @@ class Generate < Thor
81
81
  method_option :name, type: :string, banner: 'The name of the task.'
82
82
  method_option :component, type: :string, default: 'main', banner: 'The component the task should be created in.', required: false
83
83
  def task(name, component = 'main')
84
- name = name.underscore.gsub(/_tasks$/, '').singularize + '_task'
84
+ name = name.underscore.gsub(/_tasks$/, '').singularize.gsub('_task', '') + '_task'
85
85
  output_file = Dir.pwd + "/app/#{component}/tasks/#{name}.rb"
86
86
  spec_file = Dir.pwd + "/spec/app/#{component}/tasks/#{name}_spec.rb"
87
87
  template('task/task.rb.tt', output_file, task_name: name.camelize.singularize)
data/lib/volt/config.rb CHANGED
@@ -60,6 +60,7 @@ else
60
60
 
61
61
  compress_javascript: Volt.env.production?,
62
62
  compress_css: Volt.env.production?,
63
+ compress_images: Volt.env.production?,
63
64
  abort_on_exception: true,
64
65
 
65
66
  min_worker_threads: 1,
@@ -27,16 +27,19 @@ module Volt
27
27
 
28
28
  # Container returns the node that is parent to all nodes in the section.
29
29
  def container
30
+ check_section!('container')
30
31
  section.container_node
31
32
  end
32
33
 
33
34
  def dom_nodes
35
+ check_section!('dom_nodes')
34
36
  section.range
35
37
  end
36
38
 
37
39
  # Walks the dom_nodes range until it finds an element. Typically this will
38
40
  # be the container element without the whitespace text nodes.
39
41
  def first_element
42
+ check_section!('first_element')
40
43
  range = dom_nodes
41
44
  nodes = `range.startContainer.childNodes`
42
45
 
@@ -76,6 +79,24 @@ module Volt
76
79
  end
77
80
  end
78
81
 
82
+ def trigger(event, *args)
83
+ # Trigger on the current controller if an e- was setup on the component.
84
+ component_event = attrs.send(:"e_#{event}")
85
+
86
+ if component_event
87
+ # Add a nil arg for the event, trim to arity
88
+ args2 = (args + [nil])[0...component_event.arity]
89
+ component_event.call(*args2)
90
+ end
91
+
92
+ args.unshift(self)
93
+ # Trigger via jquery, so it bubbles up through the DOM
94
+ `$(#{first_element}).trigger(#{event}, #{args});`
95
+
96
+ # return nil, so we return a ruby object
97
+ nil
98
+ end
99
+
79
100
  def self.model(val)
80
101
  self.default_model = val
81
102
  end
@@ -208,7 +229,14 @@ module Volt
208
229
  # Raw marks a string as html safe, so bindings can be rendered as html.
209
230
  # With great power comes great responsibility.
210
231
  def raw(str)
211
- str = str.to_s unless str.is_a?(String)
232
+ # Promises need to have .to_s called using .then, since .to_s is a promise
233
+ # method, so it won't be passed down to the value.
234
+ if str.is_a?(Promise)
235
+ str = str.then(&:to_s)
236
+ else
237
+ str = str.to_s unless str.is_a?(String)
238
+ end
239
+
212
240
  str.html_safe
213
241
  end
214
242
 
@@ -230,5 +258,13 @@ module Volt
230
258
  super
231
259
  end
232
260
  end
261
+
262
+ private
263
+ def check_section!(method_name)
264
+ unless section
265
+ raise "##{method_name} can't be called before the {action}_ready method is called"
266
+ end
267
+ end
268
+
233
269
  end
234
270
  end