Skip to content

Commit 39f1999

Browse files
committed
initial commit
0 parents  commit 39f1999

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1118
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
_book
2+
.DS_Store

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Introduction
2+
3+
Volt is a Ruby web framework where your ruby code runs on both the server and the client (via [opal](https://github.com/opal/opal)). The DOM automatically updates as the user interacts with the page. Page state can be stored in the URL. If the user hits a URL directly, the HTML will first be rendered on the server for faster load times and easier indexing by search engines.
4+
5+
Instead of syncing data between the client and server via HTTP, Volt uses a persistent connection between the client and server. When data is updated on one client, it is updated in the database and any other listening clients (with almost no setup code needed).
6+
7+
Pages HTML is written in a template language where you can put ruby between ```{{``` and ```}}```. Volt uses data flow/reactive programming to automatically and intelligently propagate changes to the DOM (or any other code wanting to know when a value updates). When something in the DOM changes, Volt intelligently updates only the nodes that need to be changed.
8+
9+
See some demo videos here:
10+
** Note: These videos are outdated, new videos coming tomorrow.
11+
- [Volt Todos Example](https://www.youtube.com/watch?v=6ZIvs0oKnYs)
12+
- [Build a Blog with Volt](https://www.youtube.com/watch?v=c478sMlhx1o)
13+
- [Reactive Values in Volt](https://www.youtube.com/watch?v=yZIQ-2irY-Q)
14+
15+
Check out demo apps:
16+
- https://github.com/voltrb/todos3
17+
- https://github.com/voltrb/contactsdemo

SUMMARY.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Summary
2+
3+
* [Introduction](README.md)
4+
* [Goals](introduction/README.md)
5+
* [Tutorial](tutorial/README.md)
6+
* [A Sample App](tutorial/a_sample_app.md)
7+
* [Docs](docs/README.md)
8+
* [Rendering](docs/rendering.md)
9+
* [Views](docs/views.md)
10+
* [Content Binding](docs/content_binding.md)
11+
* [If Binding](docs/if_binding.md)
12+
* [Each Binding](docs/each_binding.md)
13+
* [Attribute Bindings](docs/attribute_bindings.md)
14+
* [Template Bindings](docs/template_bindings.md)
15+
* [Escaping](docs/escaping.md)
16+
* [Models](docs/models.md)
17+
* [Nil Models](docs/nil_models.md)
18+
* [Provided Collections](docs/provided_collections.md)
19+
* [Store Collections](docs/store_collections.md)
20+
* [Sub Collections](docs/sub_collections.md)
21+
* [Model Classes](docs/model_classes.md)
22+
* [Buffers](docs/buffers.md)
23+
* [Validations](docs/validations.md)
24+
* [Model State](docs/model_state.md)
25+
* [ArrayModel Events](docs/arraymodel_events.md)
26+
* [Automatic Model Conversion](docs/automatic_model_conversion.md)
27+
* [Controllers](docs/controllers.md)
28+
* [Reactive Accessors](docs/reactive_accessors.md)
29+
* [Tasks](docs/tasks.md)
30+
* [Components](docs/components.md)
31+
* [Dependencies](docs/dependencies.md)
32+
* [Assets](docs/assets.md)
33+
* [Component Generator](docs/component_generator.md)
34+
* [Provided Components](docs/provided_components.md)
35+
* [Controls](docs/controls.md)
36+
* [Control Arguments/Attributes](docs/control_argumentsattributes.md)
37+
* [Routes](docs/routes.md)
38+
* [Routes File](docs/routes_file.md)
39+
* [Channel](docs/channel.md)
40+
* [Testing](docs/testing.md)
41+
* [Debugging](docs/debugging.md)
42+
* [Volt Helpers](docs/volt_helpers.md)
43+
* [Why Volt](why_volt/README.md)
44+
* [Getting Help](getting_help/README.md)
45+
* [Contributing](contributing/README.md)
46+

book.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"title": "Volt Introduction and Docs",
3+
"description": "Volt is a Ruby web framework where your ruby code runs on both the server and the client (via opal). The DOM automatically updates as the user interacts with the page. Page state can be stored in the URL. If the user hits a URL directly, the HTML will first be rendered on the server for faster load times and easier indexing by search engines.\n\nInstead of syncing data between the client and server via HTTP, Volt uses a persistent connection between the client and server. When data is updated on one client, it is updated in the database and any other listening clients (with almost no setup code needed).\n\nPages HTML is written in a handlebars-like template language. Volt uses data flow/reactive programming to automatically and intelligently propagate changes to the DOM (or any other code wanting to know when a value updates). When something in the DOM changes, Volt intelligently updates only the nodes that need to be changed."
4+
}

contributing/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Contributing
2+
3+
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.

docs/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Volt Documentation
2+
3+
The following attempts to document all of the features of Volt. While the code is always the final source of authority, we will attempt to keep these docs as up to date as possible.
4+

docs/arraymodel_events.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## ArrayModel Events
2+
3+
Models trigger events when their data is updated. Currently, models emit two events: added and removed. For example:
4+
5+
```ruby
6+
model = Model.new
7+
8+
model._items.on('added') { puts 'item added' }
9+
model._items << 1
10+
# => item added
11+
12+
model._items.on('removed') { puts 'item removed' }
13+
model._items.delete_at(0)
14+
# => item removed
15+
```

docs/assets.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Assets
2+
3+
**Note, asset management is still early, and likely will change quite a bit**
4+
5+
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.
6+
7+
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.
8+
9+
**Note: asset bundling is on the TODO list**

docs/attribute_bindings.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Attribute Bindings
2+
3+
Bindings can also be placed inside of attributes.
4+
5+
```html
6+
<p class="{{ if _is_cool? }}cool{{ end }}">Text</p>
7+
```
8+
9+
There are some special features provided to make elements work as "two way bindings":
10+
11+
```html
12+
<input type="text" value="{{ _name }}" />
13+
```
14+
15+
## CheckBoxes
16+
17+
In the example above, if ```_name``` changes, the field will update, and if the field is updated, ```_name``` will be changed:
18+
19+
```html
20+
<input type="checkbox" checked="{{ _checked }}" />
21+
```
22+
23+
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``` respectively.
24+
25+
## Radio Buttons
26+
27+
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.
28+
29+
```html
30+
<input type="radio" checked="{{ _radio }}" value="one" />
31+
<input type="radio" checked="{{ _radio }}" value="two" />
32+
```
33+
34+
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.
35+
36+
## Select Boxes
37+
38+
Select boxes can be bound to a value (while not technically a DOM property, this is another convient behavior Volt adds).
39+
40+
```html
41+
<select value="{{ _rating }}">
42+
<option value="1">*</option>
43+
<option value="2">**</option>
44+
<option value="3">***</option>
45+
<option value="4">****</option>
46+
<option value="5">*****</option>
47+
</select>
48+
```
49+
50+
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.
51+

docs/automatic_model_conversion.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Automatic Model Conversion
2+
3+
### Hash -> Model
4+
5+
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.
6+
7+
```ruby
8+
user = Model.new
9+
user._name = 'Ryan'
10+
user._profiles = {
11+
_twitter: 'http://www.twitter.com/ryanstout',
12+
_dribbble: 'http://dribbble.com/ryanstout'
13+
}
14+
15+
user._name
16+
# => "Ryan"
17+
user._profiles._twitter
18+
# => "http://www.twitter.com/ryanstout"
19+
user._profiles.class
20+
# => Model
21+
```
22+
23+
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.
24+
25+
You can get a Ruby hash back out by calling `#to_h` on a Model.
26+
27+
### Array -> ArrayModel
28+
29+
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.
30+
31+
```ruby
32+
model = Model.new
33+
model._items << {_name: 'item 1'}
34+
model._items.class
35+
# => ArrayModel
36+
37+
model._items[0].class
38+
# => Model
39+
model._items[0]
40+
```
41+
42+
43+
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).
44+
45+
```ruby
46+
user = Model.new
47+
user._name = 'Ryan'
48+
user._profiles = {
49+
_twitter: 'http://www.twitter.com/ryanstout',
50+
_dribbble: 'http://dribbble.com/ryanstout'
51+
}
52+
53+
user._profiles.to_h
54+
# => {_twitter: 'http://www.twitter.com/ryanstout', _dribbble: 'http://dribbble.com/ryanstout'}
55+
56+
items = ArrayModel.new([1,2,3,4])
57+
# => #<ArrayModel:70226521081980 [1, 2, 3, 4]>
58+
59+
items.to_a
60+
# => [1,2,3,4]
61+
```
62+
63+
You can get a normal array again by calling .to_a on an ArrayModel.

docs/buffers.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## Buffers
2+
3+
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.
4+
5+
```ruby
6+
store._items << {_name: 'Item 1'}
7+
8+
item1 = store._items[0]
9+
10+
item1_buffer = item1.buffer
11+
12+
item1_buffer._name = 'Updated Item 1'
13+
item1_buffer._name
14+
# => 'Updated Item 1'
15+
16+
item1._name
17+
# => 'Item 1'
18+
19+
item1_buffer.save!
20+
21+
item1_buffer._name
22+
# => 'Updated Item 1'
23+
24+
item1._name
25+
# => 'Updated Item 1'
26+
```
27+
28+
```#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.
29+
30+
```ruby
31+
item1_buffer.save!.then do
32+
puts "Item 1 saved"
33+
end.fail do |err|
34+
puts "Unable to save because #{err}"
35+
end
36+
```
37+
38+
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.

docs/channel.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Channel
2+
3+
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:
4+
5+
| method | description |
6+
|-------------|-----------------------------------------------------------|
7+
| connected? | true if it is connected to the backend |
8+
| status | possible values: :opening, :open, :closed, :reconnecting |
9+
| error | the error message for the last failed connection |
10+
| retry_count | the number of reconnection attempts that have been made without a successful connection |
11+
| reconnect_interval | the time until the next reconnection attempt (in seconds) |

docs/component_generator.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Component Generator
2+
3+
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.
4+
5+
While developing, you can use the component by placing the following in your Gemfile:
6+
7+
```ruby
8+
gem 'volt-{component_name}', path: '/path/to/folder/with/component'
9+
```
10+
11+
Once the gem is ready, you can release it to ruby gems with:
12+
13+
rake release
14+
15+
Remove the path: option in the gemfile if you wish to use the rubygems version.

docs/components.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Components
2+
3+
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.

docs/content_binding.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Content binding
2+
3+
The most basic binding is a content binding:
4+
5+
```html
6+
<p>Hello {{ name }}</p>
7+
```
8+
9+
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. Text in content bindings is html escaped by default.

docs/control_argumentsattributes.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Control Arguments/Attributes
2+
3+
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.
4+
5+
```html
6+
7+
<:Body>
8+
9+
<ul>
10+
{{ _todos.each do |todo| }}
11+
<:todo name="{{ todo._name }}" />
12+
{{ end }}
13+
</ul>
14+
15+
<:Todo>
16+
<li>{{ attrs.name }}</li>
17+
```
18+
19+
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.
20+
21+
```html
22+
<:Body>
23+
<ul>
24+
{{ _todos.each do |todo| }}
25+
<:todo model="{{ todo }}" />
26+
{{ end }}
27+
</ul>
28+
29+
<:Todo>
30+
<li>
31+
{{ _name }} -
32+
{{ if _complete }}
33+
Complete
34+
{{ end }}
35+
</li>
36+
```

docs/controllers.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Controllers
2+
3+
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:
4+
5+
1. A symbol representing the name of a provided collection model:
6+
7+
```ruby
8+
class TodosController < ModelController
9+
model :page
10+
11+
# ...
12+
end
13+
```
14+
15+
2. Calling `self.model=` in a method:
16+
17+
```ruby
18+
class TodosController < ModelController
19+
def initialize
20+
self.model = :page
21+
end
22+
end
23+
```
24+
25+
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.
26+
27+
In methods, the `#model` method returns the current model.
28+
29+
See the [provided collections](#provided-collections) section for a list of the available collection models.
30+
31+
You can also provide your own object to model.
32+
33+
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.
34+
35+
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 }}```
36+
37+
Controllers in the app/home component do not need to be namespaced, all other components should namespace controllers like so:
38+
39+
```ruby
40+
module Auth
41+
class LoginController < ModelController
42+
# ...
43+
end
44+
end
45+
```
46+
47+
Here "auth" would be the component name.

0 commit comments

Comments
 (0)