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,110 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
require 'volt/templates/targets/attribute_target'
|
3
|
+
|
4
|
+
class AttributeBinding < BaseBinding
|
5
|
+
def initialize(target, context, binding_name, attribute_name, getter)
|
6
|
+
# puts "New Attribute Binding: #{binding_name}, #{attribute_name}, #{getter}"
|
7
|
+
super(target, context, binding_name)
|
8
|
+
|
9
|
+
@attribute_name = attribute_name
|
10
|
+
@getter = getter
|
11
|
+
|
12
|
+
setup
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
|
17
|
+
# Find the source for the content binding
|
18
|
+
@value = value_from_getter(@getter)
|
19
|
+
|
20
|
+
# Run the initial update (render)
|
21
|
+
update
|
22
|
+
|
23
|
+
@update_listener = @value.on('changed') { update }
|
24
|
+
|
25
|
+
# Bind so when this value updates, we update
|
26
|
+
case @attribute_name
|
27
|
+
when 'value'
|
28
|
+
element.on('input.attrbind') { changed }
|
29
|
+
when 'checked'
|
30
|
+
element.on('change.attrbind') {|event| changed(event) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def changed(event=nil)
|
35
|
+
case @attribute_name
|
36
|
+
when 'value'
|
37
|
+
current_value = element.value
|
38
|
+
# puts "NEW VAL: #{current_value} : #{@value.inspect}"
|
39
|
+
else
|
40
|
+
current_value = element.is(':checked')
|
41
|
+
end
|
42
|
+
|
43
|
+
@value.cur = current_value
|
44
|
+
end
|
45
|
+
|
46
|
+
def element
|
47
|
+
Element.find('#' + binding_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def update
|
51
|
+
if @attribute_target
|
52
|
+
value = @attribute_target.to_html
|
53
|
+
else
|
54
|
+
value = @value.cur
|
55
|
+
end
|
56
|
+
|
57
|
+
if @attribute_name == 'checked'
|
58
|
+
update_checked
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
if value.is_a?(NilMethodCall) || value.nil?
|
63
|
+
value = ''
|
64
|
+
end
|
65
|
+
|
66
|
+
self.value = value
|
67
|
+
end
|
68
|
+
|
69
|
+
def value=(val)
|
70
|
+
case @attribute_name
|
71
|
+
when 'value'
|
72
|
+
element.value = val
|
73
|
+
else
|
74
|
+
element[@attribute_name] = val
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_checked
|
79
|
+
value = @value.cur
|
80
|
+
|
81
|
+
if value.is_a?(NilMethodCall) || value.nil?
|
82
|
+
value = false
|
83
|
+
end
|
84
|
+
|
85
|
+
if value
|
86
|
+
element['checked'] = 'checked'
|
87
|
+
else
|
88
|
+
element.remove_attr('checked')
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove
|
94
|
+
# Unbind events, leave the element there since attribute bindings
|
95
|
+
# aren't responsible for it being there.
|
96
|
+
case @attribute_name
|
97
|
+
when 'value'
|
98
|
+
element.off('input.attrbind')
|
99
|
+
when 'checked'
|
100
|
+
element.off('change.attrbind')
|
101
|
+
end
|
102
|
+
|
103
|
+
if @update_listener
|
104
|
+
@update_listener.remove
|
105
|
+
@update_listener = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class BaseBinding
|
2
|
+
attr_accessor :target, :context, :binding_name
|
3
|
+
|
4
|
+
def initialize(target, context, binding_name)
|
5
|
+
@target = target
|
6
|
+
@context = context
|
7
|
+
@binding_name = binding_name
|
8
|
+
|
9
|
+
@@binding_number ||= 10000
|
10
|
+
end
|
11
|
+
|
12
|
+
def section
|
13
|
+
@section ||= target.section(@binding_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove
|
17
|
+
section.remove
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_anchors
|
21
|
+
section.remove_anchors
|
22
|
+
end
|
23
|
+
|
24
|
+
def queue_update
|
25
|
+
if Volt.server?
|
26
|
+
# Run right away
|
27
|
+
update
|
28
|
+
else
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def value_from_getter(getter)
|
34
|
+
# Evaluate the getter proc in the context
|
35
|
+
return @context.instance_eval(&getter)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# The channel is the connection between the front end and the backend.
|
2
|
+
|
3
|
+
require 'volt/reactive/events'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Channel
|
7
|
+
include Events
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@socket = nil
|
11
|
+
%x{
|
12
|
+
this.socket = new SockJS('http://localhost:3000/channel');//, {reconnect: true});
|
13
|
+
|
14
|
+
this.socket.onopen = function() {
|
15
|
+
console.log('open');
|
16
|
+
self['$trigger!']("open");
|
17
|
+
};
|
18
|
+
|
19
|
+
this.socket.onmessage = function(message) {
|
20
|
+
console.log('received: ', message);
|
21
|
+
self['$message_received'](message.data);
|
22
|
+
};
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def message_received(message)
|
27
|
+
message = JSON.parse(message)
|
28
|
+
puts "Got #{message.inspect}"
|
29
|
+
|
30
|
+
trigger!('message', message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def send(message)
|
34
|
+
# TODO: Temp: wrap message in an array, so we're sure its valid JSON
|
35
|
+
message = JSON.dump([message])
|
36
|
+
%x{
|
37
|
+
//message = window.JSON.parse(message);
|
38
|
+
console.log('send: ', message);
|
39
|
+
this.socket.send(message);
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
%x{
|
45
|
+
this.socket.close();
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
|
3
|
+
class ContentBinding < BaseBinding
|
4
|
+
def initialize(target, context, binding_name, getter)
|
5
|
+
# puts "new content binding: #{getter}"
|
6
|
+
super(target, context, binding_name)
|
7
|
+
|
8
|
+
# Find the source for the content binding
|
9
|
+
@value = value_from_getter(getter)
|
10
|
+
|
11
|
+
# Run the initial render
|
12
|
+
update
|
13
|
+
|
14
|
+
@changed_listener = @value.on('changed') { update }
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
value = @value.cur.or('')
|
19
|
+
|
20
|
+
# Exception values display the exception as a string
|
21
|
+
value = value.to_s
|
22
|
+
|
23
|
+
# Update the text in this section
|
24
|
+
section.text = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove
|
28
|
+
@changed_listener.remove
|
29
|
+
@changed_listener = nil
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class DocumentEvents
|
2
|
+
def initialize
|
3
|
+
@events = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def add(event, binding, handler)
|
7
|
+
# Track each document event based on the event, element id, then binding.object_id
|
8
|
+
unless @events[event]
|
9
|
+
# We haven't defined an event of type event yet, lets attach it to the
|
10
|
+
# document.
|
11
|
+
|
12
|
+
@events[event] = {}
|
13
|
+
|
14
|
+
that = self
|
15
|
+
|
16
|
+
%x{
|
17
|
+
$('body').on(event, function(e) {
|
18
|
+
that.$handle(event, e, e.originalEvent.target);
|
19
|
+
});
|
20
|
+
}
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# puts "Register: #{event} - #{binding.binding_name} - #{binding.object_id}"
|
25
|
+
|
26
|
+
@events[event][binding.binding_name] ||= {}
|
27
|
+
@events[event][binding.binding_name][binding.object_id] = handler
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle(event_name, event, target)
|
31
|
+
# puts "Handle: #{event_name} on #{target}"
|
32
|
+
element = Element.find(target)
|
33
|
+
|
34
|
+
loop do
|
35
|
+
# Lookup the handler, make sure to not assume the group
|
36
|
+
# exists.
|
37
|
+
# TODO: Sometimes the event doesn't exist, but we still get
|
38
|
+
# an event.
|
39
|
+
handlers = @events[event_name]
|
40
|
+
handlers = handlers[element.id] if handlers
|
41
|
+
|
42
|
+
if handlers
|
43
|
+
handlers.values.each do |handler|
|
44
|
+
# Call each handler for this object
|
45
|
+
handler.call(event)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if element.size == 0
|
50
|
+
break
|
51
|
+
else
|
52
|
+
element = element.parent
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove(event, binding)
|
60
|
+
# Remove the event binding
|
61
|
+
@events[event][binding.binding_name].delete(binding.object_id)
|
62
|
+
|
63
|
+
# if there are no more handlers for this binding_name (the html id), then
|
64
|
+
# we remove the binding name hash
|
65
|
+
if @events[event][binding.binding_name].size == 0
|
66
|
+
@events[event].delete(binding.binding_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# if there are no more handlers in this event, we can unregister the event
|
70
|
+
# from the document
|
71
|
+
if @events[event].size == 0
|
72
|
+
@events.delete(event)
|
73
|
+
|
74
|
+
# Remove the event from the body
|
75
|
+
%x{
|
76
|
+
$('body').unbind(event);
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
|
3
|
+
class EachBinding < BaseBinding
|
4
|
+
def initialize(target, context, binding_name, getter, variable_name, template_name)
|
5
|
+
# puts "New EACH Binding"
|
6
|
+
|
7
|
+
super(target, context, binding_name)
|
8
|
+
|
9
|
+
@item_name = variable_name
|
10
|
+
@template_name = template_name
|
11
|
+
|
12
|
+
# Find the source for the content binding
|
13
|
+
@value = value_from_getter(getter)
|
14
|
+
|
15
|
+
@templates = []
|
16
|
+
|
17
|
+
# Run the initial render
|
18
|
+
update
|
19
|
+
|
20
|
+
@added_listener = @value.on('added') { |position, item| puts "ADDED" ; item_added(position) }
|
21
|
+
@changed_listener = @value.on('changed') { puts "CHANGED" ; reload }
|
22
|
+
@removed_listener = @value.on('removed') { |position| puts "REMOVED at #{position.inspect}" ; item_removed(position) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# When a change event comes through, its most likely upstream, so the whole
|
26
|
+
# array might have changed. In this case, just reload the whole thing
|
27
|
+
# TODO: Track to make sure the changed event isn't being called too often (it is currently)
|
28
|
+
def reload
|
29
|
+
# Remove all of the current templates
|
30
|
+
if @templates
|
31
|
+
@templates.each do |template|
|
32
|
+
template.remove
|
33
|
+
template.remove_anchors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@templates = []
|
38
|
+
|
39
|
+
# Run update again to rebuild
|
40
|
+
update
|
41
|
+
end
|
42
|
+
|
43
|
+
def item_removed(position)
|
44
|
+
position = position.cur
|
45
|
+
@templates[position].remove
|
46
|
+
@templates[position].remove_anchors
|
47
|
+
@templates.delete_at(position)
|
48
|
+
|
49
|
+
value_obj = @value.cur
|
50
|
+
|
51
|
+
if value_obj
|
52
|
+
size = value_obj.size - 1
|
53
|
+
else
|
54
|
+
size = 0
|
55
|
+
end
|
56
|
+
|
57
|
+
# puts "Position: #{position} to #{size}"
|
58
|
+
|
59
|
+
# Removed at the position, update context for every item after this position
|
60
|
+
position.upto(size) do |index|
|
61
|
+
@templates[index].context.locals[:index].cur = index
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def item_added(position)
|
66
|
+
# puts "ADDED AT #{position}"
|
67
|
+
binding_name = @@binding_number
|
68
|
+
@@binding_number += 1
|
69
|
+
|
70
|
+
# Setup new bindings in the spot we want to insert the item
|
71
|
+
section.insert_anchor_before_end(binding_name)
|
72
|
+
|
73
|
+
index = ReactiveValue.new(position)
|
74
|
+
value = @value[index]
|
75
|
+
|
76
|
+
item_context = SubContext.new({@item_name => value, :index => index, :parent => @value}, @context)
|
77
|
+
|
78
|
+
@templates << TemplateRenderer.new(@target, item_context, binding_name, @template_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def update(item=nil)
|
82
|
+
if item
|
83
|
+
values = [item]
|
84
|
+
else
|
85
|
+
values = @value.cur
|
86
|
+
return if values.is_a?(Model) || values.is_a?(Exception)
|
87
|
+
values = values.attributes
|
88
|
+
end
|
89
|
+
|
90
|
+
# TODO: Switch to #each?
|
91
|
+
values.each_with_index do |value,index|
|
92
|
+
item_added(index)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# When this each_binding is removed, cleanup.
|
97
|
+
def remove
|
98
|
+
# puts "Remove Each"
|
99
|
+
@added_listener.remove
|
100
|
+
@added_listener = nil
|
101
|
+
|
102
|
+
@changed_listener.remove
|
103
|
+
@changed_listener = nil
|
104
|
+
|
105
|
+
@removed_listener.remove
|
106
|
+
@removed_listener = nil
|
107
|
+
|
108
|
+
@templates.each(&:remove)
|
109
|
+
@templates = nil
|
110
|
+
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'volt/templates/base_binding'
|
2
|
+
|
3
|
+
# TODO: We need to figure out how we want to wrap JS events
|
4
|
+
class JSEvent
|
5
|
+
attr_reader :js_event
|
6
|
+
def initialize(js_event)
|
7
|
+
@js_event = js_event
|
8
|
+
end
|
9
|
+
|
10
|
+
def key_code
|
11
|
+
`this.js_event.keyCode`
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
# puts "STOPPING"
|
16
|
+
# `this.js_event.stopPropagation();`
|
17
|
+
`this.js_event.preventDefault();`
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class EventBinding < BaseBinding
|
23
|
+
attr_accessor :context, :binding_name
|
24
|
+
def initialize(target, context, binding_name, event_name, call_proc)
|
25
|
+
@target = target
|
26
|
+
@context = context
|
27
|
+
@binding_name = binding_name
|
28
|
+
@event_name = event_name
|
29
|
+
|
30
|
+
handler = Proc.new do |js_event|
|
31
|
+
event = JSEvent.new(js_event)
|
32
|
+
event.stop if event_name == 'submit'
|
33
|
+
|
34
|
+
# Call the proc the user setup for the event in context,
|
35
|
+
# pass in the wrapper for the JS event
|
36
|
+
result = @context.instance_exec(event, &call_proc)
|
37
|
+
end
|
38
|
+
|
39
|
+
@listener = $page.events.add(event_name, self, handler)
|
40
|
+
end
|
41
|
+
|
42
|
+
def element
|
43
|
+
Element.find('#' + binding_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Remove the event binding
|
47
|
+
def remove
|
48
|
+
# puts "REMOVE EL FOR #{@event}"
|
49
|
+
$page.events.remove(@event_name, self)
|
50
|
+
end
|
51
|
+
end
|