volt 0.2.3
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 +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
|