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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +37 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/Rakefile +23 -0
  9. data/Readme.md +34 -0
  10. data/VERSION +1 -0
  11. data/bin/volt +4 -0
  12. data/docs/GETTING_STARTED.md +7 -0
  13. data/docs/GUIDE.md +33 -0
  14. data/lib/volt.rb +15 -0
  15. data/lib/volt/benchmark/benchmark.rb +25 -0
  16. data/lib/volt/cli.rb +34 -0
  17. data/lib/volt/console.rb +19 -0
  18. data/lib/volt/controllers/model_controller.rb +29 -0
  19. data/lib/volt/extra_core/array.rb +10 -0
  20. data/lib/volt/extra_core/blank.rb +88 -0
  21. data/lib/volt/extra_core/extra_core.rb +7 -0
  22. data/lib/volt/extra_core/numeric.rb +9 -0
  23. data/lib/volt/extra_core/object.rb +36 -0
  24. data/lib/volt/extra_core/string.rb +29 -0
  25. data/lib/volt/extra_core/stringify_keys.rb +7 -0
  26. data/lib/volt/extra_core/true_false.rb +44 -0
  27. data/lib/volt/extra_core/try.rb +31 -0
  28. data/lib/volt/models.rb +5 -0
  29. data/lib/volt/models/array_model.rb +37 -0
  30. data/lib/volt/models/model.rb +210 -0
  31. data/lib/volt/models/model_wrapper.rb +23 -0
  32. data/lib/volt/models/params.rb +67 -0
  33. data/lib/volt/models/url.rb +192 -0
  34. data/lib/volt/page/url_tracker.rb +36 -0
  35. data/lib/volt/reactive/array_extensions.rb +13 -0
  36. data/lib/volt/reactive/event_chain.rb +126 -0
  37. data/lib/volt/reactive/events.rb +283 -0
  38. data/lib/volt/reactive/object_tracker.rb +99 -0
  39. data/lib/volt/reactive/object_tracking.rb +15 -0
  40. data/lib/volt/reactive/reactive_array.rb +222 -0
  41. data/lib/volt/reactive/reactive_tags.rb +64 -0
  42. data/lib/volt/reactive/reactive_value.rb +368 -0
  43. data/lib/volt/reactive/string_extensions.rb +34 -0
  44. data/lib/volt/router/routes.rb +83 -0
  45. data/lib/volt/server.rb +121 -0
  46. data/lib/volt/server/binding_setup.rb +2 -0
  47. data/lib/volt/server/channel_handler.rb +31 -0
  48. data/lib/volt/server/component_handler.rb +88 -0
  49. data/lib/volt/server/if_binding_setup.rb +29 -0
  50. data/lib/volt/server/request_handler.rb +16 -0
  51. data/lib/volt/server/scope.rb +43 -0
  52. data/lib/volt/server/source_map_server.rb +31 -0
  53. data/lib/volt/server/template_parser.rb +452 -0
  54. data/lib/volt/store/mongo.rb +5 -0
  55. data/lib/volt/templates/attribute_binding.rb +110 -0
  56. data/lib/volt/templates/base_binding.rb +37 -0
  57. data/lib/volt/templates/channel.rb +48 -0
  58. data/lib/volt/templates/content_binding.rb +35 -0
  59. data/lib/volt/templates/document_events.rb +80 -0
  60. data/lib/volt/templates/each_binding.rb +115 -0
  61. data/lib/volt/templates/event_binding.rb +51 -0
  62. data/lib/volt/templates/if_binding.rb +74 -0
  63. data/lib/volt/templates/memory_test.rb +26 -0
  64. data/lib/volt/templates/page.rb +146 -0
  65. data/lib/volt/templates/reactive_template.rb +38 -0
  66. data/lib/volt/templates/render_queue.rb +5 -0
  67. data/lib/volt/templates/sub_context.rb +23 -0
  68. data/lib/volt/templates/targets/attribute_section.rb +33 -0
  69. data/lib/volt/templates/targets/attribute_target.rb +18 -0
  70. data/lib/volt/templates/targets/base_section.rb +14 -0
  71. data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
  72. data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
  73. data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
  74. data/lib/volt/templates/targets/dom_section.rb +147 -0
  75. data/lib/volt/templates/targets/dom_target.rb +11 -0
  76. data/lib/volt/templates/template_binding.rb +159 -0
  77. data/lib/volt/templates/template_renderer.rb +50 -0
  78. data/spec/models/event_chain_spec.rb +129 -0
  79. data/spec/models/model_spec.rb +340 -0
  80. data/spec/models/old_model_spec.rb +109 -0
  81. data/spec/models/reactive_array_spec.rb +262 -0
  82. data/spec/models/reactive_tags_spec.rb +35 -0
  83. data/spec/models/reactive_value_spec.rb +336 -0
  84. data/spec/models/string_extensions_spec.rb +57 -0
  85. data/spec/router/routes_spec.rb +24 -0
  86. data/spec/server/template_parser_spec.rb +50 -0
  87. data/spec/spec_helper.rb +20 -0
  88. data/spec/store/mongo_spec.rb +4 -0
  89. data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
  90. data/spec/templates/template_binding_spec.rb +98 -0
  91. data/templates/.gitignore +12 -0
  92. data/templates/Gemfile.tt +8 -0
  93. data/templates/app/.empty_directory +0 -0
  94. data/templates/app/home/config/routes.rb +1 -0
  95. data/templates/app/home/controllers/index_controller.rb +5 -0
  96. data/templates/app/home/css/.empty_directory +0 -0
  97. data/templates/app/home/models/.empty_directory +0 -0
  98. data/templates/app/home/views/index/about.html +9 -0
  99. data/templates/app/home/views/index/home.html +7 -0
  100. data/templates/app/home/views/index/index.html +28 -0
  101. data/templates/config.ru +4 -0
  102. data/templates/public/css/ansi.css +0 -0
  103. data/templates/public/css/bootstrap-theme.css +459 -0
  104. data/templates/public/css/bootstrap.css +7098 -0
  105. data/templates/public/css/jumbotron.css +79 -0
  106. data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
  107. data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
  108. data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  109. data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
  110. data/templates/public/index.html +25 -0
  111. data/templates/public/js/bootstrap.js +0 -0
  112. data/templates/public/js/jquery-2.0.3.js +8829 -0
  113. data/templates/public/js/sockjs-0.2.1.min.js +27 -0
  114. data/templates/spec/spec_helper.rb +20 -0
  115. data/volt.gemspec +41 -0
  116. metadata +412 -0
@@ -0,0 +1,74 @@
1
+ require 'volt/templates/base_binding'
2
+
3
+ class IfBinding < BaseBinding
4
+ def initialize(target, context, binding_name, branches)
5
+ getter, template_name = branches[0]
6
+ # puts "New If Binding: #{binding_name}, #{getter.inspect}"
7
+
8
+
9
+ super(target, context, binding_name)
10
+
11
+ @branches = []
12
+ @listeners = []
13
+
14
+ branches.each do |branch|
15
+ getter, template_name = branch
16
+
17
+ if getter.present?
18
+ # Lookup the value
19
+ value = value_from_getter(getter)
20
+
21
+ if value.reactive?
22
+ # Trigger change when value changes
23
+ @listeners << value.on('changed') { update }
24
+ end
25
+ else
26
+ # A nil value means this is an unconditional else branch, it
27
+ # should always be true
28
+ value = true
29
+ end
30
+
31
+ @branches << [value, template_name]
32
+ end
33
+
34
+ update
35
+ end
36
+
37
+ def update
38
+ # Find the true branch
39
+ true_template = nil
40
+ @branches.each do |branch|
41
+ value, template_name = branch
42
+
43
+ # TODO: A bug in opal requires us to check == true
44
+ if value.cur.true? == true
45
+ # This branch is currently true
46
+ true_template = template_name
47
+ break
48
+ end
49
+ end
50
+
51
+ # Change out the template only if the true branch has changed.
52
+ if @last_true_template != true_template
53
+ @last_true_template = true_template
54
+
55
+ if @template
56
+ @template.remove
57
+ @template = nil
58
+ end
59
+
60
+ if true_template
61
+ @template = TemplateRenderer.new(@target, @context, binding_name, true_template)
62
+ end
63
+ end
64
+ end
65
+
66
+ def remove
67
+ # Remove all listeners on any reactive values
68
+ @listeners.each(&:remove)
69
+
70
+ @template.remove if @template
71
+
72
+ super
73
+ end
74
+ end
@@ -0,0 +1,26 @@
1
+ require 'opal'
2
+ require 'volt/models'
3
+
4
+ class Test
5
+ def self.test1
6
+ a = ReactiveValue.new(1)
7
+ listener = a.on('changed') { puts "CHANGED" }
8
+ a.cur = 5
9
+ listener.remove
10
+
11
+ ObjectTracker.process_queue
12
+ end
13
+
14
+ def self.test
15
+ a = ReactiveValue.new(Model.new)
16
+ a._cool = [1,2,3]
17
+
18
+ listener = a._cool.on('added') { puts "ADDED" }
19
+ a._cool << 4
20
+ puts a._cool[3]
21
+
22
+ listener.remove
23
+
24
+ ObjectTracker.process_queue
25
+ end
26
+ end
@@ -0,0 +1,146 @@
1
+ require 'opal'
2
+
3
+ ENV['CLIENT'] = true
4
+
5
+ require 'opal-jquery'
6
+ require 'volt/models'
7
+ require 'volt/models/params'
8
+ require 'volt/controllers/model_controller'
9
+ require 'volt/templates/attribute_binding'
10
+ require 'volt/templates/content_binding'
11
+ require 'volt/templates/each_binding'
12
+ require 'volt/templates/if_binding'
13
+ require 'volt/templates/template_binding'
14
+ require 'volt/templates/template_renderer'
15
+ require 'volt/templates/reactive_template'
16
+ require 'volt/templates/event_binding'
17
+ require 'volt/templates/document_events'
18
+ require 'volt/templates/sub_context'
19
+ require 'volt/templates/targets/dom_target'
20
+ require 'volt/templates/channel'
21
+ require 'volt/router/routes'
22
+ require 'volt/models/url'
23
+ require 'volt/page/url_tracker'
24
+ require 'volt'
25
+ require 'volt/benchmark/benchmark'
26
+ require 'volt/templates/render_queue'
27
+
28
+ class Page
29
+ attr_reader :url, :params, :page, :store, :templates, :routes, :render_queue
30
+
31
+ def initialize
32
+
33
+ # debugger
34
+ puts "------ Page Loaded -------"
35
+ @model_classes = {}
36
+
37
+ # Run the code to setup the page
38
+ @page = ReactiveValue.new(Model.new)#({}, nil, 'page', @model_classes))
39
+ @store = ReactiveValue.new(Model.new)#({}, nil, 'store', @model_classes))
40
+
41
+ @url = ReactiveValue.new(URL.new)
42
+ @params = @url.params
43
+ @url_tracker = UrlTracker.new(self)
44
+
45
+ @events = DocumentEvents.new
46
+ @render_queue = RenderQueue.new
47
+
48
+ # Add event for link clicks to handle all a onclick
49
+ # EventBinding.new(self, )
50
+
51
+ # Setup escape binding for console
52
+ %x{
53
+ $(document).keyup(function(e) {
54
+ if (e.keyCode == 27) {
55
+ Opal.gvars.page.$launch_console();
56
+ }
57
+ });
58
+
59
+ $(document).on('click', 'a', function(event) {
60
+ Opal.gvars.page.$link_clicked($(this).attr('href'));
61
+ event.stopPropagation();
62
+
63
+ return false;
64
+ });
65
+ }
66
+
67
+ # channel
68
+ # channel.on('message') do |message|
69
+ # # puts "GOT: #{message}"
70
+ # # `console.log('got: ', message);`
71
+ # end
72
+ end
73
+
74
+ def link_clicked(url)
75
+ puts "Link Clicked to: #{url}"
76
+ # Skip when href == ''
77
+ return if url.blank?
78
+
79
+ # Normalize url
80
+ # Benchmark.bm(1) do
81
+ @url.parse("http://localhost:3000" + url)
82
+ # end
83
+ end
84
+
85
+ # We provide a binding_name, so we can bind events on the document
86
+ def binding_name
87
+ 'page'
88
+ end
89
+
90
+ def launch_console
91
+ puts "Launch Console"
92
+ end
93
+
94
+ def channel
95
+ @channel ||= Channel.new
96
+ end
97
+
98
+ def events
99
+ @events
100
+ end
101
+
102
+ def add_model(model_name)
103
+ # puts "ADD MODEL: #{model_name.inspect} - #{model_name.camelize.inspect}"
104
+
105
+ @model_classes[["*", "_#{model_name}"]] = Object.const_get(model_name.camelize)
106
+ end
107
+
108
+ def add_template(name, template, bindings)
109
+ # puts "Add Template: #{name}\n#{template.inspect}\n#{bindings.inspect}"
110
+ @templates ||= {}
111
+ @templates[name] = {'html' => template, 'bindings' => bindings}
112
+ # puts "Add Template: #{name}"
113
+ end
114
+
115
+ def add_routes(&block)
116
+ @routes = Routes.new.define(&block)
117
+ @url.cur.router = @routes
118
+ end
119
+
120
+ def start
121
+ # Setup to render template
122
+ Element.find('body').html = "<!-- $CONTENT --><!-- $/CONTENT -->"
123
+
124
+ main_controller = IndexController.new
125
+
126
+ # Setup main page template
127
+ TemplateRenderer.new(DomTarget.new, main_controller, 'CONTENT', 'home/index/index/body')
128
+
129
+ # Setup title listener template
130
+ title_target = AttributeTarget.new
131
+ title_target.on('changed') do
132
+ title = title_target.to_html
133
+ `document.title = title;`
134
+ end
135
+ TemplateRenderer.new(title_target, main_controller, "main", "home/index/index/title")
136
+
137
+ @url_tracker.url_updated(true)
138
+ end
139
+ end
140
+
141
+ $page = Page.new
142
+
143
+ # Call start once the page is loaded
144
+ Document.ready? do
145
+ $page.start
146
+ end
@@ -0,0 +1,38 @@
1
+ class ReactiveTemplate
2
+ include Events
3
+
4
+ def initialize(context, template_path)
5
+ @template_path = template_path
6
+ @target = AttributeTarget.new
7
+ @template = TemplateRenderer.new(@target, context, "main", template_path)
8
+ end
9
+
10
+ def event_added(event, scope_provider, first)
11
+ if first && !@template_listener
12
+ @template_listener = @target.on('changed') { update }
13
+ end
14
+ end
15
+
16
+ def event_removed(event, last)
17
+ if last && @template_listener
18
+ @template_listener.remove
19
+ @template_listener = nil
20
+ end
21
+ end
22
+
23
+ # Render the template and get the current value
24
+ def cur
25
+ @target.to_html
26
+ end
27
+
28
+ # TODO: improve
29
+ def skip_current_queue_flush
30
+ true
31
+ end
32
+
33
+
34
+ def update
35
+ trigger!('changed')
36
+ end
37
+
38
+ end
@@ -0,0 +1,5 @@
1
+ class RenderQueue
2
+ def initialize
3
+ # @queue
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # A sub context takes in a hash of local variables that should be available
2
+ # in front of the current context. It basically proxies the local variables
3
+ # first, then failing those proxies the context.
4
+
5
+ class SubContext
6
+ attr_reader :locals
7
+
8
+ def initialize(locals, context=nil)
9
+ @locals = locals.stringify_keys
10
+ @context = context
11
+ end
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ method_name = method_name.to_s
15
+ if @locals[method_name]
16
+ return @locals[method_name]
17
+ elsif @context
18
+ return @context.send(method_name, *args, &block)
19
+ end
20
+
21
+ raise NoMethodError.new("undefined method `#{method_name}' for \"#{self.inspect}\":#{self.class.to_s}")
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # AttributeSection provides a place to render templates that
2
+ # will be placed as text into an attribute.
3
+
4
+ require 'volt/templates/targets/base_section'
5
+
6
+ class AttributeSection
7
+ def initialize(target, binding_name)
8
+ @target = target
9
+ @binding_name = binding_name
10
+ # puts "init attr section on #{binding_name}"
11
+ end
12
+
13
+ def text=(text)
14
+ set_content_and_rezero_bindings(text, {})
15
+ end
16
+
17
+ # Takes in our html and bindings, and rezero's the comment names, and the
18
+ # bindings. Returns an updated bindings hash
19
+ def set_content_and_rezero_bindings(html, bindings)
20
+ if @binding_name == 'main'
21
+ @target.html = html
22
+ else
23
+ @target.find_by_binding_id(@binding_name).html = html
24
+ end
25
+
26
+ return bindings
27
+ end
28
+
29
+ def remove
30
+ node = @target.find_by_binding_id(@binding_name)
31
+ node.remove
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require 'volt/templates/targets/base_section'
2
+ require 'volt/templates/targets/attribute_section'
3
+ require 'volt/templates/targets/binding_document/component_node'
4
+ require 'volt/templates/targets/binding_document/html_node'
5
+
6
+ # AttributeTarget's provide an interface that can render bindings into
7
+ # a string that can then be used to update a attribute binding.
8
+
9
+ class AttributeTarget < ComponentNode
10
+ # TODO: improve
11
+ def skip_current_queue_flush
12
+ true
13
+ end
14
+
15
+ def section(*args)
16
+ return AttributeSection.new(self, *args)
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # Class to describe the interface for sections
2
+ class BaseSection
3
+ def remove
4
+ raise "not implemented"
5
+ end
6
+
7
+ def remove_anchors
8
+ raise "not implemented"
9
+ end
10
+
11
+ def insert_anchor_before_end
12
+ raise "not implemented"
13
+ end
14
+ end
@@ -0,0 +1,112 @@
1
+ require 'volt/templates/targets/binding_document/html_node'
2
+ require 'volt/reactive/events'
3
+
4
+ # Component nodes contain an array of both HtmlNodes and ComponentNodes.
5
+ # Instead of providing a full DOM API, component nodes are the branch
6
+ # nodes and html nodes are the leafs. This is all we need to produce
7
+ # the html from templates outside of a normal dom.
8
+ class ComponentNode < BaseNode
9
+ include Events
10
+
11
+ attr_accessor :parent, :binding_id, :nodes
12
+ def initialize(binding_id=nil, parent=nil)
13
+ @nodes = []
14
+ @binding_id = binding_id
15
+ @parent = parent
16
+
17
+ @change_listener = on('changed') do
18
+ if @parent
19
+ @parent.trigger!('changed')
20
+ end
21
+ end
22
+ end
23
+
24
+ # TODO: improve
25
+ def skip_current_queue_flush
26
+ true
27
+ end
28
+
29
+ def text=(text)
30
+ self.html = text
31
+ end
32
+
33
+ def html=(html)
34
+ parts = html.split(/(\<\!\-\- \$\/?[0-9]+ \-\-\>)/).reject {|v| v == '' }
35
+
36
+ # Clear current nodes
37
+ @nodes = []
38
+
39
+ current_node = self
40
+
41
+ parts.each do |part|
42
+ case part
43
+ when /\<\!\-\- \$[0-9]+ \-\-\>/
44
+ # Open
45
+ binding_id = part.match(/\<\!\-\- \$([0-9]+) \-\-\>/)[1].to_i
46
+
47
+ sub_node = ComponentNode.new(binding_id, current_node)
48
+ current_node << sub_node
49
+ current_node = sub_node
50
+ when /\<\!\-\- \$\/[0-9]+ \-\-\>/
51
+ # Close
52
+ # binding_id = part.match(/\<\!\-\- \$\/([0-9]+) \-\-\>/)[1].to_i
53
+
54
+ current_node = current_node.parent
55
+ else
56
+ # html string
57
+ current_node << HtmlNode.new(part)
58
+ end
59
+ end
60
+
61
+ trigger!('changed')
62
+ end
63
+
64
+ def <<(node)
65
+ @nodes << node
66
+ end
67
+
68
+ def to_html
69
+ str = []
70
+ @nodes.each do |node|
71
+ str << node.to_html
72
+ end
73
+
74
+ return str.join('')
75
+ end
76
+
77
+ def find_by_binding_id(binding_id)
78
+ if @binding_id == binding_id
79
+ return self
80
+ end
81
+
82
+ @nodes.each do |node|
83
+ if node.cur.is_a?(ComponentNode)
84
+ val = node.find_by_binding_id(binding_id)
85
+ return val if val
86
+ end
87
+ end
88
+
89
+ return nil
90
+ end
91
+
92
+ def remove
93
+ @nodes = []
94
+
95
+ trigger!('changed')
96
+ end
97
+
98
+ def remove_anchors
99
+ raise "not implemented"
100
+
101
+ @parent.nodes.delete(self)
102
+
103
+ @change_listener.remove
104
+ @change_listener = nil
105
+
106
+ trigger!('changed')
107
+ end
108
+
109
+ def inspect
110
+ "<ComponentNode:#{@binding_id} #{@nodes.inspect}>"
111
+ end
112
+ end