volt 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Readme.md +2 -1036
- data/VERSION +1 -1
- data/app/volt/models/user.rb +5 -0
- data/app/volt/tasks/query_tasks.rb +3 -3
- data/app/volt/tasks/store_tasks.rb +17 -15
- data/app/volt/tasks/user_tasks.rb +6 -0
- data/lib/volt/cli.rb +9 -0
- data/lib/volt/extra_core/string.rb +10 -2
- data/lib/volt/models/array_model.rb +0 -1
- data/lib/volt/models/model.rb +45 -28
- data/lib/volt/models/model_hash_behaviour.rb +16 -4
- data/lib/volt/models/model_helpers.rb +4 -2
- data/lib/volt/models/model_wrapper.rb +2 -1
- data/lib/volt/models/persistors/array_store.rb +6 -6
- data/lib/volt/models/persistors/local_store.rb +1 -1
- data/lib/volt/models/persistors/model_store.rb +3 -3
- data/lib/volt/models/persistors/query/query_listener.rb +6 -4
- data/lib/volt/models/persistors/query/query_listener_pool.rb +9 -0
- data/lib/volt/models/persistors/store.rb +1 -0
- data/lib/volt/models/url.rb +20 -10
- data/lib/volt/page/bindings/template_binding.rb +7 -1
- data/lib/volt/page/page.rb +3 -3
- data/lib/volt/page/tasks.rb +20 -19
- data/lib/volt/reactive/reactive_array.rb +44 -0
- data/lib/volt/router/routes.rb +5 -0
- data/lib/volt/server.rb +3 -1
- data/lib/volt/server/component_templates.rb +7 -1
- data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
- data/lib/volt/server/rack/component_paths.rb +1 -10
- data/lib/volt/server/socket_connection_handler.rb +3 -0
- data/lib/volt/tasks/dispatcher.rb +3 -7
- data/lib/volt/tasks/task_handler.rb +41 -0
- data/spec/integration/bindings_spec.rb +1 -1
- data/spec/models/model_spec.rb +32 -32
- data/spec/models/persistors/params_spec.rb +1 -1
- data/spec/router/routes_spec.rb +4 -4
- data/templates/model/model.rb.tt +3 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25b9037f048d7aceb00ab96a2e8e3fae6cb5a78f
|
4
|
+
data.tar.gz: 2e0d3c891a4399d096ff4304a1ad19039a02b523
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5f82f15a8499bdab393de67cc7cd16b4d2cf0308cb6491e01b438ce35e45634b7df273470d013477a4fa2d0476b20715696a6dd5b618e2c5679886e900827ae
|
7
|
+
data.tar.gz: b9eca133f9a058e1bd0a5f5d20e6355f613e23cf0ea7e7ec86f1c9dbf5f8fed231f508f2d3d782713f1c2c2e3ee24e53af35100ab1176d7268691f6891668860
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
#
|
2
|
+
- url.query, url.fragment, url.path all update reactively now.
|
3
|
+
- MAJOR CHANGE: Previously all tables and fields were created with _'s as their name prefixes. The underscores have been removed from everywhere. The only place you use underscores is when you want to access fields without creating setters and getters. Now when you do: ```model._name = 'Something'```, your setting the ```name``` attribute on the model. When you do: ```model._name```, your fetching the ```name``` attribute. If you insert a hash into a collection, you no longer use underscores:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
store._items << {name: 'Item 1'}
|
7
|
+
```
|
8
|
+
|
9
|
+
For the moment, continue using underscores in routes.
|
10
|
+
|
11
|
+
|
1
12
|
# 0.8.6 - Oct 5, 2014
|
2
13
|
|
3
14
|
- Major changes to the templating system (to address common concerns and make things simpler).
|
data/Readme.md
CHANGED
@@ -25,1040 +25,6 @@ Check out demo apps:
|
|
25
25
|
- https://github.com/voltrb/contactsdemo
|
26
26
|
|
27
27
|
|
28
|
-
|
28
|
+
# Docs
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
1. Developer happiness
|
33
|
-
2. Write once on the client and server
|
34
|
-
3. Automatic data syncing between client and server
|
35
|
-
4. Apps are built as nested components. Components can be shared (via gems)
|
36
|
-
5. Concurrent. Volt provides tools to simplify concurrency. Component rendering is done in parallel on the server
|
37
|
-
6. Intelligent asset management
|
38
|
-
7. Secure (shouldn't need to be said, but it does)
|
39
|
-
8. Be fast/light
|
40
|
-
9. Understandable code base
|
41
|
-
10. Control upgradeability
|
42
|
-
|
43
|
-
# Road Map
|
44
|
-
|
45
|
-
Many of the core Volt features are implemented. We still have a bit to go before 1.0, most of it involving models.
|
46
|
-
|
47
|
-
1. Model read/write permissions
|
48
|
-
2. User accounts, user collection, signup/login templates
|
49
|
-
|
50
|
-
# VOLT guide
|
51
|
-
|
52
|
-
This guide will take you through creating a basic web application in Volt. This tutorial assumes a basic knowledge of Ruby and web development.
|
53
|
-
|
54
|
-
To get started, install Volt:
|
55
|
-
|
56
|
-
gem install volt
|
57
|
-
|
58
|
-
Then create a new project:
|
59
|
-
|
60
|
-
volt new project_name
|
61
|
-
|
62
|
-
This will setup a basic project. Now let's run the server:
|
63
|
-
|
64
|
-
bundle exec volt server
|
65
|
-
|
66
|
-
You can access the Volt console with:
|
67
|
-
|
68
|
-
bundle exec volt console
|
69
|
-
|
70
|
-
# Guide Sections
|
71
|
-
|
72
|
-
1. [Getting Help](#getting-help)
|
73
|
-
2. [Rendering](#rendering)
|
74
|
-
1. [States and Computations](#state-and-computations)
|
75
|
-
1. [Computations](#computations)
|
76
|
-
2. [Dependencies](#dependencies)
|
77
|
-
3. [Views](#views)
|
78
|
-
1. [Bindings](#bindings)
|
79
|
-
1. [Content Binding](#content-binding)
|
80
|
-
2. [If Binding](#if-binding)
|
81
|
-
3. [Each Binding](#each-binding)
|
82
|
-
4. [Attribute Bindings](#attribute-bindings)
|
83
|
-
2. [Escaping](#escaping)
|
84
|
-
4. [Models](#models)
|
85
|
-
1. [Nil Models](#nil-models)
|
86
|
-
2. [Provided Collections](#provided-collections)
|
87
|
-
3. [Store Collection](#store-collection)
|
88
|
-
4. [Sub Collections](#sub-collections)
|
89
|
-
5. [Model Classes](#model-classes)
|
90
|
-
6. [Buffers](#buffers)
|
91
|
-
7. [Validations](#validations)
|
92
|
-
8. [Model State](#model-state)
|
93
|
-
9. [ArrayModel Events](#arraymodel-events)
|
94
|
-
10. [Automatic Model Conversion](#automatic-model-conversion)
|
95
|
-
5. [Controllers](#controllers)
|
96
|
-
1. [Reactive Accessors](#reactive-accessors)
|
97
|
-
6. [Tasks](#tasks)
|
98
|
-
7. [Components](#components)
|
99
|
-
1. [Dependencies](#dependencies)
|
100
|
-
2. [Assets](#assets)
|
101
|
-
3. [Component Generator](#component-generator)
|
102
|
-
4. [Provided Components](#provided-components)
|
103
|
-
1. [Notices](#notices)
|
104
|
-
2. [Flash](#flash)
|
105
|
-
8. [Controls](#controls)
|
106
|
-
9. [Routes](#routes)
|
107
|
-
1. [Routes file](#routes-file)
|
108
|
-
10. [Testing](#testing)
|
109
|
-
11. [Debugging](#debugging)
|
110
|
-
12. [Volt Helpers](#volt-helpers)
|
111
|
-
1. [Logging](#logging)
|
112
|
-
2. [App Configuration](#app-configuration)
|
113
|
-
13. [Contributing](#contributing)
|
114
|
-
|
115
|
-
# Getting Help
|
116
|
-
|
117
|
-
Volt is still a work in progress, but early feedback is appreciated. Use the following to communicate with the developers, someone will get back to you very quickly:
|
118
|
-
|
119
|
-
- **If you need help**: post on [stackoverflow.com](http://www.stackoverflow.com). Be sure to tag your question with `voltrb`.
|
120
|
-
- **If you found a bug**: post on [github issues](https://github.com/voltrb/volt/issues)
|
121
|
-
- **If you have an idea or need a feature**: post on [github issues](https://github.com/voltrb/volt/issues)
|
122
|
-
- **If you want to discuss Volt**: [chat on gitter](https://gitter.im/voltrb/volt), someone from the volt team is usually online and happy to help with anything.
|
123
|
-
|
124
|
-
|
125
|
-
# Rendering
|
126
|
-
|
127
|
-
When a user interacts with a web page, typically we want to do two things:
|
128
|
-
|
129
|
-
1. Change application state
|
130
|
-
2. Update the DOM
|
131
|
-
|
132
|
-
For example when a user clicks to add a new todo item to a todo list, we might create an object to represent the todo item, then add an item to the list's DOM. A lot of work needs to be done to make sure that the object and the DOM always stay in sync.
|
133
|
-
|
134
|
-
The idea of "reactive programming" can be used to simplify maintaining the DOM. Instead of having event handlers that manage a model and manage the DOM, we have event handlers that manage reactive data models. We describe our DOM layer in a declarative way so that it automatically knows how to render our data models.
|
135
|
-
|
136
|
-
## State and Computations
|
137
|
-
|
138
|
-
Web applications center around maintaining state. Many events can trigger changes to a state. Page interaction like entering text into form elements, clicking a button, links, scrolling, etc.. can all change the state of the app. In the past, each page interaction event would manually change any state stored on a page.
|
139
|
-
|
140
|
-
To simplify managing application state, all application state is kept in models that can optionally be persisted in different locations. By centralizing the application state, we reduce the amount of complex code needed to update a page. We can then build our page's html declaratively. The relationship to the page's models' are bound using function and method calls.
|
141
|
-
|
142
|
-
We want our DOM to automatically update when our model data changes. To make this happen, Volt lets you "watch" any method/proc call and have it get called again when data accessed by the method/proc call changes.
|
143
|
-
|
144
|
-
### Computations
|
145
|
-
|
146
|
-
Lets take a look at this in practice. We'll use the ```page``` collection as an example. (You'll see more on collections later)
|
147
|
-
|
148
|
-
First, we setup a computation watch. Computations are built by calling .watch! on a Proc. Here we'll use the ruby 1.9 proc shorthand syntax ```-> { ... }``` It will run once, then run again each time the data in page._name changes.
|
149
|
-
```ruby
|
150
|
-
page._name = 'Ryan'
|
151
|
-
-> { puts page._name }.watch!
|
152
|
-
# => Ryan
|
153
|
-
page._name = 'Jimmy'
|
154
|
-
# => Jimmy
|
155
|
-
```
|
156
|
-
|
157
|
-
Each time page._name is assigned to a new value, the computation is run again. A re-run of the computation will be triggered when any data accessed in the previous run is changed. This lets us access data through methods and still have watches re-triggered.
|
158
|
-
|
159
|
-
```ruby
|
160
|
-
page._first = 'Ryan'
|
161
|
-
page._last = 'Stout'
|
162
|
-
|
163
|
-
def lookup_name
|
164
|
-
return "#{page._first} #{page._last}"
|
165
|
-
end
|
166
|
-
|
167
|
-
-> do
|
168
|
-
puts lookup_name
|
169
|
-
end.watch!
|
170
|
-
# => Ryan Stout
|
171
|
-
|
172
|
-
page._first = 'Jimmy'
|
173
|
-
# => Jimmy Stout
|
174
|
-
|
175
|
-
page._last = 'Jones'
|
176
|
-
# => Jimmy Jones
|
177
|
-
```
|
178
|
-
|
179
|
-
When you call .watch! the return value is a Computation object. In the event you no longer want to receive updates, you can call .stop on the computation.
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
page._name = 'Ryan'
|
183
|
-
|
184
|
-
comp = -> { puts page._name }.watch!
|
185
|
-
# => Ryan
|
186
|
-
|
187
|
-
page._name = 'Jimmy'
|
188
|
-
# => Jimmy
|
189
|
-
|
190
|
-
comp.stop
|
191
|
-
|
192
|
-
page._name = 'Jo'
|
193
|
-
# (nothing)
|
194
|
-
```
|
195
|
-
|
196
|
-
## Dependencies
|
197
|
-
|
198
|
-
TODO: Explain Dependencies
|
199
|
-
|
200
|
-
As a Volt user, you rarely need to use Comptuations and Dependencies directly. Instead you usually just interact with models and bindings. Computations are used under the hood, and having a full understanding of what's going on is useful, but not required.
|
201
|
-
|
202
|
-
# Views
|
203
|
-
|
204
|
-
Views in Volt use a templating language similar to handlebars. They can be broken up into sections. A section header looks like the following:
|
205
|
-
|
206
|
-
```html
|
207
|
-
<:Body>
|
208
|
-
```
|
209
|
-
|
210
|
-
Section headers should start with a capital letter so as not to be confused with [controls](#controls). Section headers do not use closing tags. If section headers are not provided, the Body section is assumed.
|
211
|
-
|
212
|
-
Sections help you split up different parts of the same content (title and body usually), but within the same file.
|
213
|
-
|
214
|
-
## Bindings
|
215
|
-
|
216
|
-
In Volt, you code your views in a handlebars like template language. Volt provides several bindings, which handle rendering of something for you. Content bindings are anything inbetween {{ and }}.
|
217
|
-
|
218
|
-
### Content binding
|
219
|
-
|
220
|
-
The most basic binding is a content binding:
|
221
|
-
|
222
|
-
```html
|
223
|
-
<p>{{ some_method }}<p>
|
224
|
-
```
|
225
|
-
|
226
|
-
The content binding runs the Ruby code between {{ and }}, then renders the return value. Any time the data a content binding relies on changes, the binding will run again and update the text
|
227
|
-
|
228
|
-
### If binding
|
229
|
-
|
230
|
-
An if binding lets you provide basic flow control.
|
231
|
-
|
232
|
-
```html
|
233
|
-
{{ if _some_check? }}
|
234
|
-
<p>render this</p>
|
235
|
-
{{ end }}
|
236
|
-
```
|
237
|
-
|
238
|
-
Blocks are closed with a {{ end }}
|
239
|
-
|
240
|
-
When the if binding is rendered, it will run the ruby code after #if. If the code is true it will render the code below. Again, if the returned value is reactive, it will update as that value changes.
|
241
|
-
|
242
|
-
If bindings can also have #elsif and #else blocks.
|
243
|
-
|
244
|
-
```html
|
245
|
-
{{ if _condition_1? }}
|
246
|
-
<p>condition 1 true</p>
|
247
|
-
{{ elsif _condition_2? }}
|
248
|
-
<p>condition 2 true</p>
|
249
|
-
{{ else }}
|
250
|
-
<p>neither true</p>
|
251
|
-
{{ end }}
|
252
|
-
```
|
253
|
-
|
254
|
-
### Each binding
|
255
|
-
|
256
|
-
For iteration over objects, you can use .each
|
257
|
-
|
258
|
-
```html
|
259
|
-
{{ _items.each do |item| }}
|
260
|
-
<p>{{ item }}</p>
|
261
|
-
{{ end }}
|
262
|
-
```
|
263
|
-
|
264
|
-
Above, if _items were an array, the block would be rendered for each item, setting 'item' to the value of the array element.
|
265
|
-
|
266
|
-
You can also access the position of the item in the array with the #index method.
|
267
|
-
|
268
|
-
```html
|
269
|
-
{{ each _items as item }}
|
270
|
-
<p>{{ index }}. {{ item }}</p>
|
271
|
-
{{ end }}
|
272
|
-
```
|
273
|
-
|
274
|
-
For the array: ['one', 'two', 'three'] this would print:
|
275
|
-
|
276
|
-
0. one
|
277
|
-
1. two
|
278
|
-
2. three
|
279
|
-
|
280
|
-
You can do {{ index + 1 }} to correct the zero offset.
|
281
|
-
|
282
|
-
When items are removed or added to the array, the #each binding automatically and intelligently adds or removes the items from/to the DOM.
|
283
|
-
|
284
|
-
### Attribute Bindings
|
285
|
-
|
286
|
-
Bindings can also be placed inside of attributes.
|
287
|
-
|
288
|
-
```html
|
289
|
-
<p class="{{ if _is_cool? }}cool{{ end }}">Text</p>
|
290
|
-
```
|
291
|
-
|
292
|
-
There are some special features provided to make elements work as "two way bindings":
|
293
|
-
|
294
|
-
```html
|
295
|
-
<input type="text" value="{{ _name }}" />
|
296
|
-
```
|
297
|
-
|
298
|
-
In the example above, if _name changes, the field will update, and if the field is updated, _name will be changed:
|
299
|
-
|
300
|
-
```html
|
301
|
-
<input type="checkbox" checked="{{ _checked }}" />
|
302
|
-
```
|
303
|
-
|
304
|
-
If the value of a checked attribute is true, the checkbox will be shown checked. If it's checked or unchecked, the value will be updated to true or false.
|
305
|
-
|
306
|
-
Radio buttons bind to a checked state as well, except instead of setting the value to true or false, they set it to a supplied field value.
|
307
|
-
|
308
|
-
```html
|
309
|
-
<input type="radio" checked="{{ _radio }}" value="one" />
|
310
|
-
<input type="radio" checked="{{ _radio }}" value="two" />
|
311
|
-
```
|
312
|
-
|
313
|
-
When a radio button is checked, whatever checked is bound to is set to the field's value. When the checked binding value is changed, any radio buttons where the binding's value matches the fields value are checked. NOTE: This seems to be the most useful behaviour for radio buttons.
|
314
|
-
|
315
|
-
Select boxes can be bound to a value (while not technically a property, this is another convient behavior we add).
|
316
|
-
|
317
|
-
```html
|
318
|
-
<select value="{{ _rating }}">
|
319
|
-
<option value="1">*</option>
|
320
|
-
<option value="2">**</option>
|
321
|
-
<option value="3">***</option>
|
322
|
-
<option value="4">****</option>
|
323
|
-
<option value="5">*****</option>
|
324
|
-
</select>
|
325
|
-
```
|
326
|
-
|
327
|
-
When the selected option of the select above changes, ```_rating``` is changed to match. When ```_rating``` is changed, the selected value is changed to the first option with a matching value. If no matching values are found, the select box is unselected.
|
328
|
-
|
329
|
-
If you have a controller at app/home/controller/index_controller.rb, and a view at app/home/views/index/index.html, all methods called are called on the controller.
|
330
|
-
|
331
|
-
### Template Bindings
|
332
|
-
|
333
|
-
All views/*.html files are templates that can be rendered inside of other views using the template binding.
|
334
|
-
|
335
|
-
```html
|
336
|
-
{{ template "header" }}
|
337
|
-
```
|
338
|
-
|
339
|
-
## Escaping
|
340
|
-
|
341
|
-
When you need to use {{ and }} outside of bindings, anything in a triple mustache will be escaped and not processed as a binding:
|
342
|
-
|
343
|
-
```html
|
344
|
-
{{{ bindings look like: {{this}} }}}
|
345
|
-
```
|
346
|
-
|
347
|
-
# Models
|
348
|
-
|
349
|
-
Volt's concept of a model is slightly different from many frameworks where a model is the name for the ORM to the database. In Volt a model is a class where you can store data easily. Models can be created with a "Persistor", which is responsible for storing the data in the model somewhere. Models created without a persistor, simply store the data in the classes instance. Lets first see how to use a model.
|
350
|
-
|
351
|
-
Volt comes with many built-in models; one is called `page`. If you call `#page` on a controller, you will get access to the model.
|
352
|
-
|
353
|
-
```ruby
|
354
|
-
page._name = 'Ryan'
|
355
|
-
page._name
|
356
|
-
# => 'Ryan'
|
357
|
-
```
|
358
|
-
|
359
|
-
Models act like a hash that you can access with getters and setters that start with an underscore. If an attribute is accessed that hasn't yet been assigned, you will get back a "nil model". Prefixing with an underscore makes sure we don't accidentally try to call a method that doesn't exist and get back nil model instead of raising an exception. Fields behave similarly to a hash, but with a different access and assignment syntax.
|
360
|
-
|
361
|
-
# TODO: Add docs on fields in classes
|
362
|
-
|
363
|
-
Models also let you nest data without creating the intermediate models:
|
364
|
-
|
365
|
-
```ruby
|
366
|
-
page._settings._color = 'blue'
|
367
|
-
page._settings._color
|
368
|
-
# => @'blue'
|
369
|
-
|
370
|
-
page._settings
|
371
|
-
# => @#<Model:_settings {:_color=>"blue"}>
|
372
|
-
```
|
373
|
-
|
374
|
-
Nested data is automatically setup when assigned. In this case, page._settings is a model that is part of the page model. This allows nested models to be bound to a binding without the need to setup the model before use.
|
375
|
-
|
376
|
-
In Volt models, plural properties return an ArrayModel instance. ArrayModels behave the same way as normal arrays. You can add/remove items to the array with normal array methods (#<<, push, append, delete, delete_at, etc...)
|
377
|
-
|
378
|
-
```ruby
|
379
|
-
page._items
|
380
|
-
# #<ArrayModel:70303686333720 []>
|
381
|
-
|
382
|
-
page._items << {_name: 'Item 1'}
|
383
|
-
|
384
|
-
page._items
|
385
|
-
# #<ArrayModel:70303686333720 [<Model:70303682055800 {:_name=>"Item 1"}>]>
|
386
|
-
|
387
|
-
page._items.size
|
388
|
-
# => 1
|
389
|
-
|
390
|
-
page._items[0]
|
391
|
-
# => <Model:70303682055800 {:_name=>"Item 1"}>
|
392
|
-
```
|
393
|
-
|
394
|
-
|
395
|
-
## Nil Models
|
396
|
-
|
397
|
-
As a convience, calling something like ```page._info``` returns what's called a NilModel (assuming it isn't already initialized). NilModels are place holders for future possible Models. NilModels allow us to bind deeply nested values without initializing any intermediate values.
|
398
|
-
|
399
|
-
```ruby
|
400
|
-
page._info
|
401
|
-
# => <Model:70260787225140 nil>
|
402
|
-
|
403
|
-
page._info._name
|
404
|
-
# => <Model:70260795424200 nil>
|
405
|
-
|
406
|
-
page._info._name = 'Ryan'
|
407
|
-
# => <Model:70161625994820 {:_info=><Model:70161633901800 {:_name=>"Ryan"}>}>
|
408
|
-
```
|
409
|
-
|
410
|
-
One gotchya with NilModels is that they are a truthy value (since only nil and false are falsy in ruby). To make things easier, calling ```.nil?``` on a NilModel will return true.
|
411
|
-
|
412
|
-
One common place we use a truthy check is in setting up default values with || (logical or) Volt provides a convenient method that does the same thing `#or`, but works with NilModels.
|
413
|
-
|
414
|
-
Instead of
|
415
|
-
|
416
|
-
```ruby
|
417
|
-
a || b
|
418
|
-
```
|
419
|
-
|
420
|
-
Simply use:
|
421
|
-
|
422
|
-
```ruby
|
423
|
-
a.or(b)
|
424
|
-
```
|
425
|
-
|
426
|
-
`#and` works the same way as &&. #and and #or let you easily deal with default values involving NilModels.
|
427
|
-
|
428
|
-
-- TODO: Document .true? / .false?
|
429
|
-
|
430
|
-
|
431
|
-
## Provided Collections
|
432
|
-
|
433
|
-
Above, I mentioned that Volt comes with many default collection models accessible from a controller. Each stores in a different location.
|
434
|
-
|
435
|
-
| Name | Storage Location |
|
436
|
-
|-------------|---------------------------------------------------------------------------|
|
437
|
-
| page | page provides a temporary store that only lasts for the life of the page. |
|
438
|
-
| store | store syncs the data to the backend database and provides query methods. |
|
439
|
-
| local_store | values will be stored in the local_store |
|
440
|
-
| params | values will be stored in the params and URL. Routes can be setup to change how params are shown in the URL. (See routes for more info) |
|
441
|
-
| flash | any strings assigned will be shown at the top of the page and cleared as the user navigates between pages. |
|
442
|
-
| controller | a model for the current controller |
|
443
|
-
|
444
|
-
**more storage locations are planned**
|
445
|
-
|
446
|
-
## Store Collection
|
447
|
-
|
448
|
-
The store collection backs data in the data store. Currently the only supported data store is Mongo. (More coming soon, RethinkDb will probably be next) You can use store very similar to the other collections.
|
449
|
-
|
450
|
-
In Volt you can access ```store``` on the front-end and the back-end. Data will automatically be synced between the front-end and the backend. Any changes to the data in store will be reflected on any clients using the data (unless a [buffer](#buffers) is in use - see below).
|
451
|
-
|
452
|
-
```ruby
|
453
|
-
store._items << {_name: 'Item 1'}
|
454
|
-
|
455
|
-
store._items[0]
|
456
|
-
# => <Model:70303681865560 {:_name=>"Item 1", :_id=>"e6029396916ed3a4fde84605"}>
|
457
|
-
```
|
458
|
-
|
459
|
-
Inserting into ```store._items``` will create a ```_items``` table and insert the model into it. An pseudo-unique _id will be automatically generated.
|
460
|
-
|
461
|
-
Currently one difference between ```store``` and other collections is ```store``` does not store properties directly. Only ArrayModels are allowed directly on ```store```
|
462
|
-
|
463
|
-
```ruby
|
464
|
-
store._something = 'yes'
|
465
|
-
# => won't be saved at the moment
|
466
|
-
```
|
467
|
-
|
468
|
-
Note: We're planning to add support for direct ```store``` properties.
|
469
|
-
|
470
|
-
## Sub Collections
|
471
|
-
|
472
|
-
Models can be nested on ```store```
|
473
|
-
|
474
|
-
```ruby
|
475
|
-
store._states << {_name: 'Montana'}
|
476
|
-
montana = store._states[0]
|
477
|
-
|
478
|
-
montana._cities << {_name: 'Bozeman'}
|
479
|
-
montana._cities << {_name: 'Helena'}
|
480
|
-
|
481
|
-
store._states << {_name: 'Idaho'}
|
482
|
-
idaho = store._states[1]
|
483
|
-
|
484
|
-
idaho._cities << {_name: 'Boise'}
|
485
|
-
idaho._cities << {_name: 'Twin Falls'}
|
486
|
-
|
487
|
-
store._states
|
488
|
-
# #<ArrayModel:70129010999880 [<Model:70129010999460 {:_name=>"Montana", :_id=>"e3aa44651ff2e705b8f8319e"}>, <Model:70128997554160 {:_name=>"Montana", :_id=>"9aaf6d2519d654878c6e60c9"}>, <Model:70128997073860 {:_name=>"Idaho", :_id=>"5238883482985760e4cb2341"}>, <Model:70128997554160 {:_name=>"Montana", :_id=>"9aaf6d2519d654878c6e60c9"}>, <Model:70128997073860 {:_name=>"Idaho", :_id=>"5238883482985760e4cb2341"}>]>
|
489
|
-
```
|
490
|
-
|
491
|
-
You can also create a Model first and then insert it.
|
492
|
-
|
493
|
-
```ruby
|
494
|
-
montana = Model.new({_name: 'Montana'})
|
495
|
-
|
496
|
-
montana._cities << {_name: 'Bozeman'}
|
497
|
-
montana._cities << {_name: 'Helena'}
|
498
|
-
|
499
|
-
store._states << montana
|
500
|
-
```
|
501
|
-
|
502
|
-
## Model Classes
|
503
|
-
|
504
|
-
By default all collections use the Model class by default.
|
505
|
-
|
506
|
-
```ruby
|
507
|
-
page._info.class
|
508
|
-
# => Model
|
509
|
-
```
|
510
|
-
|
511
|
-
You can provide classes that will be loaded in place of the standard model class. You can place these in any app/{component}/models folder. For example, you could add ```app/main/info.rb``` Model classes should inherit from ```Model```
|
512
|
-
|
513
|
-
```ruby
|
514
|
-
class Info < Model
|
515
|
-
end
|
516
|
-
```
|
517
|
-
|
518
|
-
Now when you access any sub-collection called ```_info```, it will load as an instance of ```Info```
|
519
|
-
|
520
|
-
```ruby
|
521
|
-
page._info.class
|
522
|
-
# => Info
|
523
|
-
```
|
524
|
-
|
525
|
-
This lets you set custom methods and validations within collections.
|
526
|
-
|
527
|
-
## Buffers
|
528
|
-
|
529
|
-
Because the store collection is automatically synced to the backend, any change to a model's property will result in all other clients seeing the change immediately. Often this is not the desired behavior. To facilitate building [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) apps, Volt provides the concept of a "buffer". A buffer can be created from one model and will not save data back to its backing model until .save! is called on it. This lets you create a form thats not saved until a submit button is pressed.
|
530
|
-
|
531
|
-
```ruby
|
532
|
-
store._items << {_name: 'Item 1'}
|
533
|
-
|
534
|
-
item1 = store._items[0]
|
535
|
-
|
536
|
-
item1_buffer = item1.buffer
|
537
|
-
|
538
|
-
item1_buffer._name = 'Updated Item 1'
|
539
|
-
item1_buffer._name
|
540
|
-
# => 'Updated Item 1'
|
541
|
-
|
542
|
-
item1._name
|
543
|
-
# => 'Item 1'
|
544
|
-
|
545
|
-
item1_buffer.save!
|
546
|
-
|
547
|
-
item1_buffer._name
|
548
|
-
# => 'Updated Item 1'
|
549
|
-
|
550
|
-
item1._name
|
551
|
-
# => 'Updated Item 1'
|
552
|
-
```
|
553
|
-
|
554
|
-
```#save!``` on buffer also returns a [promise](http://opalrb.org/blog/2014/05/07/promises-in-opal/) that will resolve when the data has been saved back to the server.
|
555
|
-
|
556
|
-
```ruby
|
557
|
-
item1_buffer.save!.then do
|
558
|
-
puts "Item 1 saved"
|
559
|
-
end.fail do |err|
|
560
|
-
puts "Unable to save because #{err}"
|
561
|
-
end
|
562
|
-
```
|
563
|
-
|
564
|
-
Calling .buffer on an existing model will return a buffer for that model instance. If you call .buffer on an ArrayModel (plural sub-collection), you will get a buffer for a new item in that collection. Calling .save! will then add the item to that sub-collection as if you had done << to push the item into the collection.
|
565
|
-
|
566
|
-
## Validations
|
567
|
-
|
568
|
-
Within a model class, you can setup validations. Validations let you restrict the types of data that can be stored in a model. Validations are mostly useful for the ```store``` collection, though they can be used elsewhere.
|
569
|
-
|
570
|
-
At the moment we only have two validations implemented (length and presence). Though a lot more will be coming.
|
571
|
-
|
572
|
-
```ruby
|
573
|
-
class Info < Model
|
574
|
-
validate :_name, length: 5
|
575
|
-
validate :_state, presence: true
|
576
|
-
end
|
577
|
-
```
|
578
|
-
|
579
|
-
When calling save on a model with validations, the following occurs:
|
580
|
-
|
581
|
-
1. Client side validations are run; if they fail, the promise from ```save!``` is rejected with the error object.
|
582
|
-
2. The data is sent to the server and client and server side validations are run on the server; any failures are returned and the promise is rejected on the front-end (with the error object)
|
583
|
-
- re-running the validations on the server side makes sure that no data can be saved that doesn't pass the validations
|
584
|
-
3. If all validations pass, the data is saved to the database and the promise resolved on the client.
|
585
|
-
4. The data is synced to all other clients.
|
586
|
-
|
587
|
-
|
588
|
-
## Model State
|
589
|
-
|
590
|
-
**Work in progress**
|
591
|
-
|
592
|
-
| state | events bound | description |
|
593
|
-
|-------------|--------------|--------------------------------------------------------------|
|
594
|
-
| not_loaded | no | no events and no one has accessed the data in the model |
|
595
|
-
| loading | maybe | someone either accessed the data or bound an event |
|
596
|
-
| loaded | yes | data is loaded and there is an event bound |
|
597
|
-
| dirty | no | data was either accessed without binding an event, or an event was bound, but later unbound. |
|
598
|
-
|
599
|
-
|
600
|
-
## ArrayModel Events
|
601
|
-
|
602
|
-
Models trigger events when their data is updated. Currently, models emit two events: added and removed. For example:
|
603
|
-
|
604
|
-
```ruby
|
605
|
-
model = Model.new
|
606
|
-
|
607
|
-
model._items.on('added') { puts 'item added' }
|
608
|
-
model._items << 1
|
609
|
-
# => item added
|
610
|
-
|
611
|
-
model._items.on('removed') { puts 'item removed' }
|
612
|
-
model._items.delete_at(0)
|
613
|
-
# => item removed
|
614
|
-
```
|
615
|
-
|
616
|
-
## Automatic Model Conversion
|
617
|
-
|
618
|
-
### Hash -> Model
|
619
|
-
|
620
|
-
For convenience, when placing a hash inside of another model, it is automatically converted into a model. Models are similar to hashes, but provide support for things like persistence and triggering reactive events.
|
621
|
-
|
622
|
-
```ruby
|
623
|
-
user = Model.new
|
624
|
-
user._name = 'Ryan'
|
625
|
-
user._profiles = {
|
626
|
-
_twitter: 'http://www.twitter.com/ryanstout',
|
627
|
-
_dribbble: 'http://dribbble.com/ryanstout'
|
628
|
-
}
|
629
|
-
|
630
|
-
user._name
|
631
|
-
# => "Ryan"
|
632
|
-
user._profiles._twitter
|
633
|
-
# => "http://www.twitter.com/ryanstout"
|
634
|
-
user._profiles.class
|
635
|
-
# => Model
|
636
|
-
```
|
637
|
-
|
638
|
-
Models are accessed differently from hashes. Instead of using `model[:symbol]` to access, you call a method `model.method_name`. This provides a dynamic unified store where setters and getters can be added without changing any access code.
|
639
|
-
|
640
|
-
You can get a Ruby hash back out by calling `#to_h` on a Model.
|
641
|
-
|
642
|
-
### Array -> ArrayModel
|
643
|
-
|
644
|
-
Arrays inside of models are automatically converted to an instance of ArrayModel. ArrayModels behave the same as a normal Array except that they can handle things like being bound to backend data and triggering reactive events.
|
645
|
-
|
646
|
-
```ruby
|
647
|
-
model = Model.new
|
648
|
-
model._items << {_name: 'item 1'}
|
649
|
-
model._items.class
|
650
|
-
# => ArrayModel
|
651
|
-
|
652
|
-
model._items[0].class
|
653
|
-
# => Model
|
654
|
-
model._items[0]
|
655
|
-
```
|
656
|
-
|
657
|
-
|
658
|
-
To convert a Model or an ArrayModel back to a normal hash, call .to_h or .to_a respectively. To convert them to a JavaScript Object (for passing to some JavaScript code), call `#to_n` (to native).
|
659
|
-
|
660
|
-
```ruby
|
661
|
-
user = Model.new
|
662
|
-
user._name = 'Ryan'
|
663
|
-
user._profiles = {
|
664
|
-
_twitter: 'http://www.twitter.com/ryanstout',
|
665
|
-
_dribbble: 'http://dribbble.com/ryanstout'
|
666
|
-
}
|
667
|
-
|
668
|
-
user._profiles.to_h
|
669
|
-
# => {_twitter: 'http://www.twitter.com/ryanstout', _dribbble: 'http://dribbble.com/ryanstout'}
|
670
|
-
|
671
|
-
items = ArrayModel.new([1,2,3,4])
|
672
|
-
# => #<ArrayModel:70226521081980 [1, 2, 3, 4]>
|
673
|
-
|
674
|
-
items.to_a
|
675
|
-
# => [1,2,3,4]
|
676
|
-
```
|
677
|
-
|
678
|
-
You can get a normal array again by calling .to_a on an ArrayModel.
|
679
|
-
|
680
|
-
# Controllers
|
681
|
-
|
682
|
-
A controller can be any class in Volt, however it is common to have that class inherit from ModelController. A model controller lets you specify a model that the controller works off of. This is a common pattern in Volt. The model for a controller can be assigned by one of the following:
|
683
|
-
|
684
|
-
1. A symbol representing the name of a provided collection model:
|
685
|
-
|
686
|
-
```ruby
|
687
|
-
class TodosController < ModelController
|
688
|
-
model :page
|
689
|
-
|
690
|
-
# ...
|
691
|
-
end
|
692
|
-
```
|
693
|
-
|
694
|
-
2. Calling `self.model=` in a method:
|
695
|
-
|
696
|
-
```ruby
|
697
|
-
class TodosController < ModelController
|
698
|
-
def initialize
|
699
|
-
self.model = :page
|
700
|
-
end
|
701
|
-
end
|
702
|
-
```
|
703
|
-
|
704
|
-
When a model is set, any missing methods will be proxied to the model. This lets you bind within the views without prefixing the model object every time. It also lets you change out the current model and have the views update automatically.
|
705
|
-
|
706
|
-
In methods, the `#model` method returns the current model.
|
707
|
-
|
708
|
-
See the [provided collections](#provided-collections) section for a list of the available collection models.
|
709
|
-
|
710
|
-
You can also provide your own object to model.
|
711
|
-
|
712
|
-
In the example above, any methods not defined on the TodosController will fall through to the provided model. All views in views/{controller_name} will have this controller as the target for any Ruby run in their bindings. This means that calls on self (implicit or with self.) will have the model as their target (after calling through the controller). This lets you add methods to the controller to control how the model is handled, or provide extra methods to the views.
|
713
|
-
|
714
|
-
Volt is more similar to an MVVM architecture than an MVC architecture. Instead of the controllers passing data off to the views, the controllers are the context for the views. When using a ModelController, the controller automatically forwards all methods it does not handle to the model. This is convenient since you can set a model in the controller and then access its properties directly with methods in bindings. This lets you do something like ```{{ _name }}``` instead of something like ```{{ @model._name }}```
|
715
|
-
|
716
|
-
Controllers in the app/home component do not need to be namespaced, all other components should namespace controllers like so:
|
717
|
-
|
718
|
-
```ruby
|
719
|
-
module Auth
|
720
|
-
class LoginController < ModelController
|
721
|
-
# ...
|
722
|
-
end
|
723
|
-
end
|
724
|
-
```
|
725
|
-
|
726
|
-
Here "auth" would be the component name.
|
727
|
-
|
728
|
-
## Reactive Accessors
|
729
|
-
|
730
|
-
The default ModelController proxies any missing methods to its model. Sometimes you need to store additional data reactively in the controller outside of the model. (Though often you may want to condier doing another control/controller). In this case, you can add a ```reactive_accessor```. These behave just like ```attr_accessor``` except the values assigned and returned are tracked for any Computations.
|
731
|
-
|
732
|
-
```ruby
|
733
|
-
class Contacts < ModelController
|
734
|
-
reactive_accessor :query
|
735
|
-
end
|
736
|
-
```
|
737
|
-
|
738
|
-
Now from the view we can bind to query while also changing in and out the model. You can also use ```reactive_reader``` and ```reactive_writer``` When query is accessed it tracks that it was accessed and will any Computations when it changes.
|
739
|
-
|
740
|
-
# Tasks
|
741
|
-
|
742
|
-
Sometimes you need to explicitly execute some code on the server. Volt solves this problem through *tasks*. You can define your own tasks by dropping a class into your component's ```tasks``` folder.
|
743
|
-
|
744
|
-
```ruby
|
745
|
-
# app/main/tasks/logging_tasks.rb
|
746
|
-
|
747
|
-
class LoggingTasks
|
748
|
-
def initialize(channel=nil, dispatcher=nil)
|
749
|
-
@channel = channel
|
750
|
-
@dispatcher = dispatcher
|
751
|
-
end
|
752
|
-
|
753
|
-
def log(message)
|
754
|
-
puts message
|
755
|
-
end
|
756
|
-
end
|
757
|
-
```
|
758
|
-
|
759
|
-
To invoke a task from a controller use ```tasks.call```.
|
760
|
-
|
761
|
-
```ruby
|
762
|
-
class Contacts < ModelController
|
763
|
-
def hello
|
764
|
-
tasks.call('LoggingTasks', 'log', 'Hello World!')
|
765
|
-
end
|
766
|
-
end
|
767
|
-
```
|
768
|
-
|
769
|
-
You can also pass a block to ```tasks.call``` that will receive the return value of your task as soon as it's done.
|
770
|
-
|
771
|
-
```ruby
|
772
|
-
tasks.call('MathTasks', 'add', 23, 5) do |result|
|
773
|
-
# result should be 28
|
774
|
-
alert result
|
775
|
-
end
|
776
|
-
```
|
777
|
-
|
778
|
-
# Components
|
779
|
-
|
780
|
-
Apps are made up of Components. Each folder under app/ is a component. When you visit a route, it loads all of the files in the component on the front end, so new pages within the component can be rendered without a new http request. If a URL is visited that routes to a different component, the request will be loaded as a normal page load and all of that components files will be loaded. You can think of components as the "reload boundary" between sections of your app.
|
781
|
-
|
782
|
-
## Dependencies
|
783
|
-
|
784
|
-
You can also use controls (see below) from one component in another. To do this, you must require the component from the component you wish to use them in. This can be done in the ```config/dependencies.rb``` file. Just put
|
785
|
-
|
786
|
-
```ruby
|
787
|
-
component 'component_name'
|
788
|
-
```
|
789
|
-
|
790
|
-
in the file.
|
791
|
-
|
792
|
-
Dependencies act just like require in ruby, but for whole components.
|
793
|
-
|
794
|
-
Sometimes you may need to include an externally hosted JS file from a component. To do this, simply do the following in the dependencies.rb file:
|
795
|
-
|
796
|
-
```ruby
|
797
|
-
javascript_file 'http://code.jquery.com/jquery-2.0.3.min.js'
|
798
|
-
css_file '//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css'
|
799
|
-
```
|
800
|
-
|
801
|
-
Note above though that jquery and bootstrap are currently included by default. Using javascript_file and css_file will be mixed in with your component assets at the correct locations according to the order they occur in the dependencies.rb files.
|
802
|
-
|
803
|
-
## Assets
|
804
|
-
|
805
|
-
**Note, asset management is still early, and likely will change quite a bit**
|
806
|
-
|
807
|
-
In Volt, assets such as JavaScript and CSS (or sass) are automatically included on the page for you. Anything placed inside of a components asset/js or assets/css folder is served at /assets/{js,css} (via [Sprockets](https://github.com/sstephenson/sprockets)). Link and script tags are automatically added for each css and js file in assets/css and assets/js respectively. Files are included in their lexical order, so you can add numbers in front if you need to change the load order.
|
808
|
-
|
809
|
-
Any JS/CSS from an included component or component gem will be included as well. By default [bootstrap](http://getbootstrap.com/) is provided by the volt-bootstrap gem.
|
810
|
-
|
811
|
-
**Note: asset bundling is on the TODO list**
|
812
|
-
|
813
|
-
## Component Generator
|
814
|
-
|
815
|
-
Components can easily be shared as a gem. Volt provides a scaffold for component gems. In a folder (not in a volt project), simply type: volt gem {component_name} This will create the files needed for the gem. Note that all volt component gems will be prefixed with volt- so they can easily be found by others on github and rubygems.
|
816
|
-
|
817
|
-
While developing, you can use the component by placing the following in your Gemfile:
|
818
|
-
|
819
|
-
```ruby
|
820
|
-
gem 'volt-{component_name}', path: '/path/to/folder/with/component'
|
821
|
-
```
|
822
|
-
|
823
|
-
Once the gem is ready, you can release it to ruby gems with:
|
824
|
-
|
825
|
-
rake release
|
826
|
-
|
827
|
-
Remove the path: option in the gemfile if you wish to use the rubygems version.
|
828
|
-
|
829
|
-
## Provided Components
|
830
|
-
|
831
|
-
Volt provides a few components to make web developers' lives easier.
|
832
|
-
|
833
|
-
### Notices
|
834
|
-
|
835
|
-
Volt automatically places ```<:volt:notices />``` into views. This shows notices for the following:
|
836
|
-
|
837
|
-
1. flash messages
|
838
|
-
2. connection status (when a disconnect happens, lets the user know why and when a reconnect will be attempted)
|
839
|
-
3. page reloading notices (in development)
|
840
|
-
|
841
|
-
### Flash
|
842
|
-
|
843
|
-
As part of the notices component explained above, you can append messages to any collection on the flash model.
|
844
|
-
|
845
|
-
Each collection represents a different type of "flash". Common examples are ```_notices, _warnings, and _errors``` Using different collections allows you to change how you want the flash displayed. For example, you might want ```_notices``` and ```_errors``` to show with different colors.
|
846
|
-
|
847
|
-
```ruby
|
848
|
-
flash._notices << "message to flash"
|
849
|
-
```
|
850
|
-
|
851
|
-
These messages will show for 5 seconds, then disappear (both from the screen and the collection).
|
852
|
-
|
853
|
-
# Controls
|
854
|
-
|
855
|
-
Everyone wishes that we could predict the scope and required features for each part of our application, but in the real world, things we don't expect to grow large often do and things we think will be large don't end up that way. Controls let you quickly setup reusable code/views. The location of the controls code can be moved as it grows without changing the way controls are invoked.
|
856
|
-
|
857
|
-
To render a control, simply use a tag like so:
|
858
|
-
|
859
|
-
```html
|
860
|
-
<:control-name />
|
861
|
-
```
|
862
|
-
|
863
|
-
or
|
864
|
-
|
865
|
-
```html
|
866
|
-
<:control-name></:control-name>
|
867
|
-
```
|
868
|
-
|
869
|
-
To find the control's views and optional controller, Volt will search the following (in order):
|
870
|
-
|
871
|
-
| Section | View File | View Folder | Component |
|
872
|
-
|-----------|--------------|----------------|-------------|
|
873
|
-
| :{name} | | | |
|
874
|
-
| :body | {name}.html | | |
|
875
|
-
| :body | index.html | {name} | |
|
876
|
-
| :body | index.html | index | {name} |
|
877
|
-
| :body | index.html | index | gems/{name} |
|
878
|
-
|
879
|
-
**Note that anything with a view folder will also load a controller if the name/folder matches.**
|
880
|
-
|
881
|
-
|
882
|
-
Each part is explained below:
|
883
|
-
|
884
|
-
1. section
|
885
|
-
Views are composed of sections. Sections start with a ```<:SectionName>``` and are not closed. Volt will look first for a section in the same view.
|
886
|
-
|
887
|
-
2. views
|
888
|
-
Next Volt will look for a view file with the control name. If found, it will render the body section of that view.
|
889
|
-
|
890
|
-
3. view folder
|
891
|
-
Failing above, Volt will look for a view folder with the control name, and an index.html file within that folder. It will render the :body section of that view. If a controller exists for the view folder, it will make a new instance of that controller and render in that instance.
|
892
|
-
|
893
|
-
4. component
|
894
|
-
Next, all folders under app/ are checked. The view path looked for is {component}/index/index.html with a section of :body.
|
895
|
-
|
896
|
-
5. gems
|
897
|
-
Lastly the app folder of all gems that start with volt are checked. They are checked for a similar path to component.
|
898
|
-
|
899
|
-
When you create a control, you can also specify multiple parts of the search path in the name. The parts should be separated by a : Example:
|
900
|
-
|
901
|
-
```html
|
902
|
-
<:blog:comments />
|
903
|
-
```
|
904
|
-
|
905
|
-
The above would search the following:
|
906
|
-
|
907
|
-
| Section | View File | View Folder | Component |
|
908
|
-
|-----------|--------------|----------------|-------------|
|
909
|
-
| :comments | blog.html | | |
|
910
|
-
| :body | comments.html| blog | |
|
911
|
-
| :body | index.html | comments | blog |
|
912
|
-
| :body | index.html | comments | gems/blog |
|
913
|
-
|
914
|
-
Once the view file for the control or template is found, it will look for a matching controller. If the control is specified as a local template, an empty ModelController will be used. If a controller is found and loaded, a corresponding "action" method will be called on it if its exists. Action methods default to "index" unless the component or template path has two parts, in which case the last part is the action.
|
915
|
-
|
916
|
-
# Control Arguments/Attributes
|
917
|
-
|
918
|
-
Like other html tags, controls can be passed attributes. These are then converted into an object that is passed as the first argument to the initialize method on the controller. The standard ModelController's initialize will then assign the object to the attrs property which can be accessed with ```#attrs``` This makes it easy to access attributes passed in.
|
919
|
-
|
920
|
-
```html
|
921
|
-
|
922
|
-
<:Body>
|
923
|
-
|
924
|
-
<ul>
|
925
|
-
{{ _todos.each do |todo| }}
|
926
|
-
<:todo name="{{ todo._name }}" />
|
927
|
-
{{ end }}
|
928
|
-
</ul>
|
929
|
-
|
930
|
-
<:Todo>
|
931
|
-
<li>{{ attrs.name }}</li>
|
932
|
-
```
|
933
|
-
|
934
|
-
Instead of passing in individual attributes, you can also pass in a Model object with the "model" attribute and it will be set as the model for the controller.
|
935
|
-
|
936
|
-
```html
|
937
|
-
<:Body>
|
938
|
-
<ul>
|
939
|
-
{{ _todos.each do |todo| }}
|
940
|
-
<:todo model="{{ todo }}" />
|
941
|
-
{{ end }}
|
942
|
-
</ul>
|
943
|
-
|
944
|
-
<:Todo>
|
945
|
-
<li>
|
946
|
-
{{ _name }} -
|
947
|
-
{{ if _complete }}
|
948
|
-
Complete
|
949
|
-
{{ end }}
|
950
|
-
</li>
|
951
|
-
```
|
952
|
-
|
953
|
-
# Routes
|
954
|
-
|
955
|
-
Routes in Volt are very different from traditional backend frameworks. Since data is synchronized using websockets, routes are mainly used to serialize the state of the application into the url in a pretty way. When a page is first loaded, the URL is parsed with the routes and the params model's values are set from the URL. Later if the params model is updated, the URL is updated based on the routes.
|
956
|
-
|
957
|
-
This means that routes in Volt have to be able to go both from URL to params and params to URL. It should also be noted that if a link is clicked and the controller/view to render the new URL is within the current component (or an included component), the page will not be reloaded, the URL will be updated with the HTML5 history API, and the params hash will reflect the new URL. You can use the changes in params to render different views based on the URL.
|
958
|
-
|
959
|
-
## Routes file
|
960
|
-
|
961
|
-
Routes are specified on a per-component basis in the config/routes.rb file. Routes simply map from URL to params.
|
962
|
-
|
963
|
-
```ruby
|
964
|
-
get "/todos", {_view: 'todos'}
|
965
|
-
```
|
966
|
-
|
967
|
-
Routes take two arguments; a path, and a params hash. When a new URL is loaded and the path is matched on a route, the params will be set to the params provided for that route. The specified params hash acts as a constraint. An empty hash will match any url. Any params that are not matched will be placed in the query parameters.
|
968
|
-
|
969
|
-
When the params are changed, the URL will be set to the path for the route whose params hash matches.
|
970
|
-
|
971
|
-
Route paths can also contain variables similar to bindings:
|
972
|
-
|
973
|
-
```ruby
|
974
|
-
get "/todos/{{ _index }}", _view: 'todos'
|
975
|
-
```
|
976
|
-
|
977
|
-
In the case above, if any URL matches /todos/*, (where * is anything but a slash), it will be the active route. ```params._view``` would be set to 'todos', and ```params._index``` would be set to the value in the path.
|
978
|
-
|
979
|
-
If ```params._view``` were 'todos' and ```params._index``` were not nil, the route would be matched.
|
980
|
-
|
981
|
-
Routes are matched top to bottom in a routes file.
|
982
|
-
|
983
|
-
# Channel
|
984
|
-
|
985
|
-
Controllers provide a `#channel` method, that you can use to get the status of the connection to the backend. Channel's access methods are reactive and when the status changes, the watching computations will be re-triggered. It provides the following:
|
986
|
-
|
987
|
-
| method | description |
|
988
|
-
|-------------|-----------------------------------------------------------|
|
989
|
-
| connected? | true if it is connected to the backend |
|
990
|
-
| status | possible values: :opening, :open, :closed, :reconnecting |
|
991
|
-
| error | the error message for the last failed connection |
|
992
|
-
| retry_count | the number of reconnection attempts that have been made without a successful connection |
|
993
|
-
| reconnect_interval | the time until the next reconnection attempt (in seconds) |
|
994
|
-
|
995
|
-
|
996
|
-
# Testing
|
997
|
-
|
998
|
-
** Testing is being reworked at the moment.
|
999
|
-
Volt provides rspec and capybara out of the box. You can test directly against your models, controllers, etc... or you can do full integration tests via [Capybara](https://github.com/jnicklas/capybara).
|
1000
|
-
|
1001
|
-
To run Capybara tests, you need to specify a driver. The following drivers are currently supported:
|
1002
|
-
|
1003
|
-
1. Phantom (via poltergeist)
|
1004
|
-
|
1005
|
-
```BROWSER=phantom bundle exec rspec```
|
1006
|
-
|
1007
|
-
2. Firefox
|
1008
|
-
|
1009
|
-
```BROWSER=firefox bundle exec rspec```
|
1010
|
-
|
1011
|
-
3. IE - coming soon
|
1012
|
-
|
1013
|
-
Chrome is not supported due to [this issue](https://code.google.com/p/chromedriver/issues/detail?id=887#makechanges) with ChromeDriver. Feel free to go [here](https://code.google.com/p/chromedriver/issues/detail?id=887#makechanges) and pester the chromedriver team to fix it.
|
1014
|
-
|
1015
|
-
# Debugging
|
1016
|
-
|
1017
|
-
An in browser irb is in the works. We also have source maps support, but they are currently disabled by default. To enable them run:
|
1018
|
-
|
1019
|
-
MAPS=true volt s
|
1020
|
-
|
1021
|
-
This feature is disabled by default because (due to the volume of pages rendered) it slows down page rendering. We're working with the opal and sprockets teams to make it so everything is still served in one big source maps file (which would show the files as they originated on disk)
|
1022
|
-
|
1023
|
-
# Volt Helpers
|
1024
|
-
|
1025
|
-
## Logging
|
1026
|
-
|
1027
|
-
Volt provides a helper for logging. Calling ```Volt.logger``` returns an instance of the ruby logger. See [here](http://www.ruby-doc.org/stdlib-2.1.3/libdoc/logger/rdoc/Logger.html) for more.
|
1028
|
-
|
1029
|
-
```ruby
|
1030
|
-
Volt.logger.info("Some info...")
|
1031
|
-
```
|
1032
|
-
|
1033
|
-
You can change the logger with:
|
1034
|
-
|
1035
|
-
```ruby
|
1036
|
-
Volt.logger = Logger.new
|
1037
|
-
```
|
1038
|
-
|
1039
|
-
## App Configuration
|
1040
|
-
|
1041
|
-
Like many frameworks, Volt changes some default settings based on an environment flag. You can set the volt environment with the VOLT_ENV environment variable.
|
1042
|
-
|
1043
|
-
All files in the app's ```config``` folder are loaded when Volt boots. This is similar to the ```initializers``` folder in Rails.
|
1044
|
-
|
1045
|
-
Volt does its best to start with useful defaults. You can configure things like your database and app name in the config/app.rb file. The following are the current configuration options:
|
1046
|
-
|
1047
|
-
| name | default | description |
|
1048
|
-
|-----------|---------------------------|---------------------------------------------------------------|
|
1049
|
-
| app_name | the current folder name | This is used internally for things like logging. |
|
1050
|
-
| db_driver | 'mongo' | Currently mongo is the only supported driver, more coming soon|
|
1051
|
-
| db_name | "#{app_name}_#{Volt.env} | The name of the mongo database. |
|
1052
|
-
| db_host | 'localhost' | The hostname for the mongo database. |
|
1053
|
-
| db_port | 27017 | The port for the mongo database. |
|
1054
|
-
| compress_deflate | false | If true, will run deflate in the app server, its better to let something like nginx do this though |
|
1055
|
-
|
1056
|
-
## Accessing DOM section in a controller
|
1057
|
-
|
1058
|
-
TODO
|
1059
|
-
|
1060
|
-
# Contributing
|
1061
|
-
|
1062
|
-
You want to contribute? Great! Thanks for being awesome! At the moment, we have a big internal todo list, hop on https://gitter.im/voltrb/volt so we don't duplicate work. Pull requests are always welcome, but asking about helping on gitter should save some duplication.
|
1063
|
-
|
1064
|
-
[![Pledgie](https://pledgie.com/campaigns/26731.png?skin_name=chrome)](https://pledgie.com/campaigns/26731)
|
30
|
+
Read the [full docs on Volt here](http://voltframework.com/docs)
|