volt 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +37 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +23 -0
- data/Readme.md +34 -0
- data/VERSION +1 -0
- data/bin/volt +4 -0
- data/docs/GETTING_STARTED.md +7 -0
- data/docs/GUIDE.md +33 -0
- data/lib/volt.rb +15 -0
- data/lib/volt/benchmark/benchmark.rb +25 -0
- data/lib/volt/cli.rb +34 -0
- data/lib/volt/console.rb +19 -0
- data/lib/volt/controllers/model_controller.rb +29 -0
- data/lib/volt/extra_core/array.rb +10 -0
- data/lib/volt/extra_core/blank.rb +88 -0
- data/lib/volt/extra_core/extra_core.rb +7 -0
- data/lib/volt/extra_core/numeric.rb +9 -0
- data/lib/volt/extra_core/object.rb +36 -0
- data/lib/volt/extra_core/string.rb +29 -0
- data/lib/volt/extra_core/stringify_keys.rb +7 -0
- data/lib/volt/extra_core/true_false.rb +44 -0
- data/lib/volt/extra_core/try.rb +31 -0
- data/lib/volt/models.rb +5 -0
- data/lib/volt/models/array_model.rb +37 -0
- data/lib/volt/models/model.rb +210 -0
- data/lib/volt/models/model_wrapper.rb +23 -0
- data/lib/volt/models/params.rb +67 -0
- data/lib/volt/models/url.rb +192 -0
- data/lib/volt/page/url_tracker.rb +36 -0
- data/lib/volt/reactive/array_extensions.rb +13 -0
- data/lib/volt/reactive/event_chain.rb +126 -0
- data/lib/volt/reactive/events.rb +283 -0
- data/lib/volt/reactive/object_tracker.rb +99 -0
- data/lib/volt/reactive/object_tracking.rb +15 -0
- data/lib/volt/reactive/reactive_array.rb +222 -0
- data/lib/volt/reactive/reactive_tags.rb +64 -0
- data/lib/volt/reactive/reactive_value.rb +368 -0
- data/lib/volt/reactive/string_extensions.rb +34 -0
- data/lib/volt/router/routes.rb +83 -0
- data/lib/volt/server.rb +121 -0
- data/lib/volt/server/binding_setup.rb +2 -0
- data/lib/volt/server/channel_handler.rb +31 -0
- data/lib/volt/server/component_handler.rb +88 -0
- data/lib/volt/server/if_binding_setup.rb +29 -0
- data/lib/volt/server/request_handler.rb +16 -0
- data/lib/volt/server/scope.rb +43 -0
- data/lib/volt/server/source_map_server.rb +31 -0
- data/lib/volt/server/template_parser.rb +452 -0
- data/lib/volt/store/mongo.rb +5 -0
- data/lib/volt/templates/attribute_binding.rb +110 -0
- data/lib/volt/templates/base_binding.rb +37 -0
- data/lib/volt/templates/channel.rb +48 -0
- data/lib/volt/templates/content_binding.rb +35 -0
- data/lib/volt/templates/document_events.rb +80 -0
- data/lib/volt/templates/each_binding.rb +115 -0
- data/lib/volt/templates/event_binding.rb +51 -0
- data/lib/volt/templates/if_binding.rb +74 -0
- data/lib/volt/templates/memory_test.rb +26 -0
- data/lib/volt/templates/page.rb +146 -0
- data/lib/volt/templates/reactive_template.rb +38 -0
- data/lib/volt/templates/render_queue.rb +5 -0
- data/lib/volt/templates/sub_context.rb +23 -0
- data/lib/volt/templates/targets/attribute_section.rb +33 -0
- data/lib/volt/templates/targets/attribute_target.rb +18 -0
- data/lib/volt/templates/targets/base_section.rb +14 -0
- data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
- data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
- data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
- data/lib/volt/templates/targets/dom_section.rb +147 -0
- data/lib/volt/templates/targets/dom_target.rb +11 -0
- data/lib/volt/templates/template_binding.rb +159 -0
- data/lib/volt/templates/template_renderer.rb +50 -0
- data/spec/models/event_chain_spec.rb +129 -0
- data/spec/models/model_spec.rb +340 -0
- data/spec/models/old_model_spec.rb +109 -0
- data/spec/models/reactive_array_spec.rb +262 -0
- data/spec/models/reactive_tags_spec.rb +35 -0
- data/spec/models/reactive_value_spec.rb +336 -0
- data/spec/models/string_extensions_spec.rb +57 -0
- data/spec/router/routes_spec.rb +24 -0
- data/spec/server/template_parser_spec.rb +50 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/store/mongo_spec.rb +4 -0
- data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
- data/spec/templates/template_binding_spec.rb +98 -0
- data/templates/.gitignore +12 -0
- data/templates/Gemfile.tt +8 -0
- data/templates/app/.empty_directory +0 -0
- data/templates/app/home/config/routes.rb +1 -0
- data/templates/app/home/controllers/index_controller.rb +5 -0
- data/templates/app/home/css/.empty_directory +0 -0
- data/templates/app/home/models/.empty_directory +0 -0
- data/templates/app/home/views/index/about.html +9 -0
- data/templates/app/home/views/index/home.html +7 -0
- data/templates/app/home/views/index/index.html +28 -0
- data/templates/config.ru +4 -0
- data/templates/public/css/ansi.css +0 -0
- data/templates/public/css/bootstrap-theme.css +459 -0
- data/templates/public/css/bootstrap.css +7098 -0
- data/templates/public/css/jumbotron.css +79 -0
- data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/templates/public/index.html +25 -0
- data/templates/public/js/bootstrap.js +0 -0
- data/templates/public/js/jquery-2.0.3.js +8829 -0
- data/templates/public/js/sockjs-0.2.1.min.js +27 -0
- data/templates/spec/spec_helper.rb +20 -0
- data/volt.gemspec +41 -0
- metadata +412 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
class Object
|
2
|
+
# Setup a default pretty_inspect
|
3
|
+
# alias_method :pretty_inspect, :inspect
|
4
|
+
|
5
|
+
def instance_values
|
6
|
+
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Provides the same functionality as ||, but since ReactiveValue's only
|
10
|
+
# work with method calls, we provide .or as a convience.
|
11
|
+
def or(other)
|
12
|
+
if self.true?
|
13
|
+
return self
|
14
|
+
else
|
15
|
+
return other
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Provides the same functionality as &&, but since ReactiveValue's only
|
20
|
+
# work with method calls, we provide .and as a convience
|
21
|
+
def and(other)
|
22
|
+
if self.true?
|
23
|
+
return other
|
24
|
+
else
|
25
|
+
return self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def try(*a, &b)
|
30
|
+
if a.empty? && block_given?
|
31
|
+
yield self
|
32
|
+
else
|
33
|
+
__send__(*a, &b)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class String
|
2
|
+
# TODO: replace with better implementations
|
3
|
+
# NOTE: strings are currently immutable in Opal, so no ! methods
|
4
|
+
def camelize
|
5
|
+
self.split("_").map {|s| s.capitalize }.join("")
|
6
|
+
end
|
7
|
+
|
8
|
+
def underscore
|
9
|
+
self.scan(/[A-Z][a-z]*/).join("_").downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def pluralize
|
13
|
+
# TODO: Temp implementation
|
14
|
+
if self[-1] != 's'
|
15
|
+
return self + 's'
|
16
|
+
else
|
17
|
+
return self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def singularize
|
22
|
+
# TODO: Temp implementation
|
23
|
+
if self[-1] == 's'
|
24
|
+
return self[0..-2]
|
25
|
+
else
|
26
|
+
return self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# The true?/false? predicates are convience methods since if ..reactive_value.. does
|
2
|
+
# not correctly evaluate. The reason for this is that ruby currently does not allow
|
3
|
+
# anything besides nil and false to be falsy.
|
4
|
+
|
5
|
+
class Object
|
6
|
+
def true?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def false?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FalseClass
|
16
|
+
def true?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def false?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NilClass
|
26
|
+
def true?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def false?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Opal only has a single class for true/false
|
36
|
+
class Boolean
|
37
|
+
def true?
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def false?
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
class TryProxy
|
4
|
+
def initialize(original)
|
5
|
+
@original = original
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(method_name, *args, &block)
|
9
|
+
if @original.respond_to?(method_name)
|
10
|
+
|
11
|
+
else
|
12
|
+
NilProxy.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NilProxy
|
18
|
+
def method_missing(method_name, *args, &block)
|
19
|
+
if @original.respond_to?(method_name)
|
20
|
+
|
21
|
+
else
|
22
|
+
NilProxy.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def try
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/volt/models.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'volt/models/model_wrapper'
|
2
|
+
|
3
|
+
class ArrayModel < ReactiveArray
|
4
|
+
include ModelWrapper
|
5
|
+
|
6
|
+
def initialize(array=[], parent=nil, path=nil)
|
7
|
+
@parent = parent
|
8
|
+
@path = path
|
9
|
+
|
10
|
+
array = wrap_values(array)
|
11
|
+
|
12
|
+
super(array)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# Make sure it gets wrapped
|
20
|
+
def <<(*args)
|
21
|
+
args = wrap_values(args)
|
22
|
+
|
23
|
+
super(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Make sure it gets wrapped
|
27
|
+
def inject(*args)
|
28
|
+
args = wrap_values(args)
|
29
|
+
super(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Make sure it gets wrapped
|
33
|
+
def +(*args)
|
34
|
+
args = wrap_values(args)
|
35
|
+
super(*args)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'volt/models/model_wrapper'
|
2
|
+
require 'volt/models/array_model'
|
3
|
+
require 'volt/reactive/object_tracking'
|
4
|
+
|
5
|
+
class NilMethodCall < NoMethodError
|
6
|
+
def true?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def false?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Model
|
16
|
+
include ReactiveTags
|
17
|
+
include ModelWrapper
|
18
|
+
include ObjectTracking
|
19
|
+
|
20
|
+
attr_accessor :attributes
|
21
|
+
attr_reader :parent, :path
|
22
|
+
|
23
|
+
def nil?
|
24
|
+
attributes.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def false?
|
28
|
+
attributes.false?
|
29
|
+
end
|
30
|
+
|
31
|
+
def true?
|
32
|
+
attributes.true?
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(attributes={}, parent=nil, path=nil, class_paths=nil)
|
36
|
+
self.attributes = wrap_values(attributes)
|
37
|
+
@parent = parent
|
38
|
+
@path = path
|
39
|
+
end
|
40
|
+
|
41
|
+
# Pass the comparison through
|
42
|
+
def ==(val)
|
43
|
+
attributes == val
|
44
|
+
end
|
45
|
+
|
46
|
+
# Pass through needed
|
47
|
+
def !
|
48
|
+
!attributes
|
49
|
+
end
|
50
|
+
|
51
|
+
tag_method(:delete) do
|
52
|
+
destructive!
|
53
|
+
end
|
54
|
+
def delete(*args)
|
55
|
+
__clear_element(args[0])
|
56
|
+
attributes.delete(*args)
|
57
|
+
trigger_by_attribute!('changed', args[0])
|
58
|
+
end
|
59
|
+
|
60
|
+
tag_all_methods do
|
61
|
+
destructive! do |method_name|
|
62
|
+
method_name[0] == '_' && method_name[-1] == '='
|
63
|
+
end
|
64
|
+
pass_reactive! do |method_name|
|
65
|
+
method_name[0] == '_' && method_name[-1] == '='
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def method_missing(method_name, *args, &block)
|
69
|
+
if method_name[0] == '_'
|
70
|
+
if method_name[-1] == '='
|
71
|
+
# Assigning an attribute with =
|
72
|
+
assign_attribute(method_name, *args, &block)
|
73
|
+
else
|
74
|
+
read_attribute(method_name)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
# Call method directly on attributes. (since they are
|
78
|
+
# not using _ )
|
79
|
+
attributes.send(method_name, *args, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Do the assignment to a model and trigger a changed event
|
84
|
+
def assign_attribute(method_name, *args, &block)
|
85
|
+
self.expand!
|
86
|
+
# Assign, without the =
|
87
|
+
attribute_name = method_name[0..-2].to_sym
|
88
|
+
|
89
|
+
value = args[0]
|
90
|
+
__assign_element(attribute_name, value)
|
91
|
+
|
92
|
+
attributes[attribute_name] = wrap_value(value)
|
93
|
+
trigger_by_attribute!('changed', attribute_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# When reading an attribute, we need to handle reading on:
|
97
|
+
# 1) a nil model, which returns a wrapped error
|
98
|
+
# 2) reading directly from attributes
|
99
|
+
# 3) trying to read a key that doesn't exist.
|
100
|
+
def read_attribute(method_name)
|
101
|
+
# Reading an attribute, we may get back a nil model.
|
102
|
+
method_name = method_name.to_sym
|
103
|
+
|
104
|
+
if attributes == nil
|
105
|
+
# The method we are calling is on a nil model, return a wrapped
|
106
|
+
# exception.
|
107
|
+
return return_undefined_method(method_name)
|
108
|
+
elsif attributes && attributes.has_key?(method_name)
|
109
|
+
# Method has the key, look it up directly
|
110
|
+
return attributes[method_name]
|
111
|
+
else
|
112
|
+
return new_model(nil, self, method_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def return_undefined_method(method_name)
|
117
|
+
# Methods called on nil capture an error so the user can know where
|
118
|
+
# their nil calls are. This error can be re-raised at a later point.
|
119
|
+
begin
|
120
|
+
raise NilMethodCall.new("undefined method `#{method_name}' for #{self.to_s}")
|
121
|
+
rescue => e
|
122
|
+
result = e
|
123
|
+
|
124
|
+
# Cleanup backtrace around ReactiveValue's
|
125
|
+
# TODO: this could be better
|
126
|
+
result.backtrace.reject! {|line| line['lib/models/model.rb'] || line['lib/models/live_value.rb'] }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def new_model(*args)
|
131
|
+
Model.new(*args)
|
132
|
+
end
|
133
|
+
|
134
|
+
def trigger_by_attribute!(event_name, attribute, *passed_args)
|
135
|
+
trigger_by_scope!(event_name, *passed_args) do |scope|
|
136
|
+
method_name, *args, block = scope
|
137
|
+
|
138
|
+
# TODO: Opal bug
|
139
|
+
args ||= []
|
140
|
+
|
141
|
+
# Any methods without _ are not directly related to one attribute, so
|
142
|
+
# they should all trigger
|
143
|
+
!method_name || method_name[0] != '_' || (method_name == attribute.to_sym && args.size == 0)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# If this model is nil, it makes it into a hash model, then
|
148
|
+
# sets it up to track from the parent.
|
149
|
+
def expand!
|
150
|
+
if attributes.nil?
|
151
|
+
self.attributes = {}
|
152
|
+
if @parent
|
153
|
+
@parent.expand!
|
154
|
+
|
155
|
+
@parent.attributes[@path] = self
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
tag_method(:<<) do
|
161
|
+
destructive!
|
162
|
+
pass_reactive!
|
163
|
+
end
|
164
|
+
# Initialize an empty array and append to it
|
165
|
+
def <<(value)
|
166
|
+
@parent.expand!
|
167
|
+
result = @parent.send(@path)
|
168
|
+
|
169
|
+
if result.nil?
|
170
|
+
# If this isn't a model yet, instantiate it
|
171
|
+
@parent.send(:"#{@path}=", ArrayModel.new([], @parent, @path))
|
172
|
+
result = @parent.send(@path)
|
173
|
+
|
174
|
+
# Add the new item
|
175
|
+
result << value
|
176
|
+
end
|
177
|
+
|
178
|
+
return result
|
179
|
+
end
|
180
|
+
|
181
|
+
def inspect
|
182
|
+
"<#{self.class.to_s}:#{@path} #{attributes.inspect}>"
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
private
|
187
|
+
# Clear the previous value and assign a new one
|
188
|
+
def __assign_element(key, value)
|
189
|
+
__clear_element(key)
|
190
|
+
__track_element(key, value)
|
191
|
+
end
|
192
|
+
|
193
|
+
# TODO: Somewhat duplicated from ReactiveArray
|
194
|
+
def __clear_element(key)
|
195
|
+
# Cleanup any tracking on an index
|
196
|
+
# TODO: is this send a security risk?
|
197
|
+
# puts "TRY TO CLEAR: #{key} - #{@reactive_element_listeners && @reactive_element_listeners.keys.inspect}"
|
198
|
+
if @reactive_element_listeners && @reactive_element_listeners[key]
|
199
|
+
@reactive_element_listeners[key].remove
|
200
|
+
@reactive_element_listeners.delete(key)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def __track_element(key, value)
|
205
|
+
puts "TRACK: #{key} - #{value.inspect}"
|
206
|
+
__setup_tracking(key, value) do |event, key, args|
|
207
|
+
trigger_by_attribute!(event, key, *args)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ModelWrapper
|
2
|
+
# For cretain values, we wrap them to make the behave as a
|
3
|
+
# model.
|
4
|
+
def wrap_value(value)
|
5
|
+
if value.cur.is_a?(Array)
|
6
|
+
value = ArrayModel.new(value, self, nil)
|
7
|
+
elsif value.cur.is_a?(Hash)
|
8
|
+
value = Model.new(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
return value
|
12
|
+
end
|
13
|
+
|
14
|
+
def wrap_values(values)
|
15
|
+
if values.cur.is_a?(Array)
|
16
|
+
values = values.map {|v| wrap_value(v) }
|
17
|
+
elsif values.cur.is_a?(Hash)
|
18
|
+
values = Hash[values.map {|k,v| [k, wrap_value(v)] }]
|
19
|
+
end
|
20
|
+
|
21
|
+
return values
|
22
|
+
end
|
23
|
+
end
|