opalla 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +394 -14
- data/lib/opalla.rb +11 -4
- data/lib/opalla/component_helper.rb +28 -0
- data/lib/opalla/controller_add_on.rb +11 -0
- data/lib/opalla/engine.rb +27 -0
- data/lib/opalla/middleware.rb +20 -0
- data/lib/opalla/util.rb +63 -0
- data/lib/opalla/version.rb +1 -1
- data/lib/rails/generators/opalla/assets_generator.rb +23 -0
- data/lib/rails/generators/opalla/collection_generator.rb +19 -0
- data/lib/rails/generators/opalla/component_generator.rb +27 -0
- data/lib/rails/generators/opalla/install_generator.rb +62 -0
- data/lib/rails/generators/opalla/model_generator.rb +19 -0
- data/opal/collection.rb +71 -0
- data/opal/component.rb +136 -0
- data/opal/controller.rb +60 -0
- data/opal/diffDOM.js +1371 -0
- data/opal/diff_dom.rb +26 -0
- data/opal/element.rb +17 -0
- data/opal/hex_random.rb +12 -0
- data/opal/model.rb +50 -0
- data/opal/opalla.rb +21 -0
- data/opal/router.rb +66 -0
- data/opal/sha1.js +366 -0
- data/opal/view_helper.rb +168 -0
- data/opalla.gemspec +8 -0
- data/opalla.gif +0 -0
- metadata +109 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b420c0145b5c3f9a19e09a13662039babd80df2f
|
4
|
+
data.tar.gz: 9aad2ce70fb5d1b98fc5039c4dbe3c727e2e63e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1ff2aeb30b377ffcbc3718b73642be5338251088750a588ebebc6d9db4aca5cad6ea9a51c1c8b8108d93df15a7c148010bf06b5120a3a1844ba751a52093fbe
|
7
|
+
data.tar.gz: 5a83dc76cdfaee6cfc9b0359dbcd981c5273c2748835ef1c405e13b50f14adee079a2049229c358ca8b489b34a9840bc7ca7e36de19bafd381d9ccf36ef7a9a8
|
data/README.md
CHANGED
@@ -1,41 +1,421 @@
|
|
1
1
|
# Opalla
|
2
|
+
![Opalla](opalla.gif)
|
2
3
|
|
3
|
-
|
4
|
+
Opalla brings Rails way to the front-end. It follows a Rails conventions mixed with a little of Backbone.
|
4
5
|
|
5
|
-
|
6
|
+
It's built on top of `opal` and `opal-rails`.
|
6
7
|
|
7
8
|
## Installation
|
8
|
-
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
+
gem 'opal-rails'
|
12
13
|
gem 'opalla'
|
13
14
|
```
|
14
15
|
|
16
|
+
Additionally, if you want to use `haml`, add:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'opal-haml'
|
20
|
+
```
|
21
|
+
(That's actually ultra-highly recommended)
|
22
|
+
|
15
23
|
And then execute:
|
16
24
|
|
17
|
-
|
25
|
+
```
|
26
|
+
$ bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
Then run:
|
30
|
+
|
31
|
+
```sh
|
32
|
+
|
33
|
+
rails g opalla:install
|
34
|
+
|
35
|
+
```
|
36
|
+
|
37
|
+
Opalla is a front-end MVC framework. So you will to have this folder structure:
|
18
38
|
|
19
|
-
|
39
|
+
```
|
40
|
+
your_app
|
41
|
+
\_app
|
42
|
+
\_assets
|
43
|
+
\_javascripts
|
44
|
+
\_components
|
45
|
+
\_controllers
|
46
|
+
\_lib
|
47
|
+
\_collections
|
48
|
+
\_models
|
49
|
+
\_views
|
50
|
+
```
|
20
51
|
|
21
|
-
|
52
|
+
And that’s it! You’re ready to drive Opalla.
|
22
53
|
|
23
54
|
## Usage
|
24
55
|
|
25
|
-
|
56
|
+
### Router & Controllers
|
57
|
+
It all starts with the Opalla router. It will catch the current URL and direct to the controller/action, exactly like Rails does.
|
26
58
|
|
27
|
-
|
59
|
+
So for example, say you have the following route:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
get 'pages#index'
|
63
|
+
```
|
28
64
|
|
29
|
-
|
65
|
+
You can generate controllers by running the default Rails controller generator:
|
30
66
|
|
31
|
-
|
67
|
+
```sh
|
32
68
|
|
33
|
-
|
69
|
+
rails g controller pages
|
34
70
|
|
35
|
-
|
71
|
+
```
|
36
72
|
|
73
|
+
If you just installed Opalla on your existing app and want to generate Opalla controllers for it, just re-run the command above and leave Rails do the rest! (It won't overwrite anything unless you explicitly ask for).
|
37
74
|
|
38
|
-
|
75
|
+
`Opalla::Router` will automatically import the routes and instantiate the `PagesController` inside your `javascripts/controller/pages_controller.rb` and trigger the `index` action:
|
39
76
|
|
40
|
-
|
77
|
+
```ruby
|
78
|
+
|
79
|
+
class PagesController < ApplicationController
|
80
|
+
|
81
|
+
def index
|
82
|
+
el.html 'Hello World!'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
That would replace the `<body>` tag with 'Hello World', as the default `el` for a controller. If you want to change that, you can add `el 'YOU_SELECTOR_HERE'`, in a jQuery selector fashion. So for example:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
|
92
|
+
class PagesController < ApplicationController
|
93
|
+
el '.main-content'
|
94
|
+
|
95
|
+
def index
|
96
|
+
el.html 'Hello World!'
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
### Templates
|
103
|
+
Your Opalla templates will live under `app/assets/javascripts/views`. There you have `./controllers` and `./components`. Note that components templates should start their names with `_`, just like `partials`.
|
104
|
+
|
105
|
+
Opalla will automatically have your templates folder added to your server side (Rails). So that means your templates will be rendered server-side and client-side. Yay!
|
106
|
+
|
107
|
+
The template will be able to get any access variables (`@variable`) and `methods`. Please note that you have to have both set on server and client sides.
|
108
|
+
|
109
|
+
Server-side:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
|
113
|
+
class PagesController < ApplicationController
|
114
|
+
helper_method :something_awesome
|
115
|
+
|
116
|
+
def index
|
117
|
+
@dude = 'Pedro'
|
118
|
+
end
|
119
|
+
|
120
|
+
def something_awesome
|
121
|
+
'Rails'
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
```
|
127
|
+
|
128
|
+
Client-side, note that all methods are available by default, no need to set them as `helper_methods`:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
|
132
|
+
class PagesController < ApplicationController
|
133
|
+
el '.main-content'
|
134
|
+
|
135
|
+
def index
|
136
|
+
@dude = 'Pedro'
|
137
|
+
render
|
138
|
+
end
|
139
|
+
|
140
|
+
def something_awesome
|
141
|
+
'Rails'
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
The following `haml` layout would work for both sides:
|
148
|
+
|
149
|
+
```haml
|
150
|
+
|
151
|
+
%main
|
152
|
+
The dude is called #{@dude}.
|
153
|
+
He works with #{something_awesome}
|
154
|
+
|
155
|
+
```
|
156
|
+
|
157
|
+
If you want to share variables with your front-end Opalla MVC, that’s ok. Use the `expose` method automatically available on your server-side controllers:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
|
161
|
+
def index
|
162
|
+
expose message: 'Hello World!',
|
163
|
+
my_array: [1,2,3]
|
164
|
+
end
|
165
|
+
|
166
|
+
```
|
167
|
+
|
168
|
+
And then you can use the data normally in your views:
|
169
|
+
|
170
|
+
```haml
|
171
|
+
%p= message
|
172
|
+
%p= my_array.join('/')
|
173
|
+
```
|
174
|
+
|
175
|
+
That variables will be automatically available on both sides of your application.
|
176
|
+
|
177
|
+
### Components
|
178
|
+
`app/assets/javascripts/components` is where you components live. They will by default render the folder located on `app/assets/javascripts/views/components/_COMPONENT_NAME` (without the '_component' part of the name)
|
179
|
+
|
180
|
+
To create components:
|
181
|
+
|
182
|
+
```sh
|
183
|
+
|
184
|
+
rails g opalla:component my_component
|
185
|
+
|
186
|
+
```
|
187
|
+
|
188
|
+
They are instantiated and rendered from the controller layout, like this:
|
189
|
+
|
190
|
+
```haml
|
191
|
+
|
192
|
+
%main
|
193
|
+
The dude is called #{@dude}.
|
194
|
+
He works with #{something_awesome}
|
195
|
+
component(:contact_box)
|
196
|
+
|
197
|
+
```
|
198
|
+
|
199
|
+
That will look for the component `app/assets/javascripts/components/contact_box_component.rb`:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
|
203
|
+
class ContactBoxComponent < ApplicationComponent
|
204
|
+
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
#### Model Data
|
209
|
+
The components can get their data from a `Opalla::Model`. Here's how it goes:
|
210
|
+
|
211
|
+
First, generate your model:
|
212
|
+
|
213
|
+
```sh
|
214
|
+
bin/rails g opalla:model contact_info
|
215
|
+
```
|
216
|
+
|
217
|
+
Let’s add a simple attr_accessor:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class Opalla::Model
|
221
|
+
attr_accessor :email
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
In the server-side controller you can expose the data, so that will be rendered from the server as default (no one wants blank pages to maybe hurt the SEO):
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
class PagesController < ApplicationController
|
229
|
+
@contact_info = ContactInfo.new
|
230
|
+
@contact_info.email = 'pedro@pedromaciel.com'
|
231
|
+
expose contact: @contact_info
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
Then, in your server side, you don’t need anything for this case, except for render:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
|
239
|
+
class PagesController < ApplicationController
|
240
|
+
el '.main-content'
|
241
|
+
|
242
|
+
def index
|
243
|
+
render
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
```
|
249
|
+
|
250
|
+
In the controller template:
|
251
|
+
|
252
|
+
```haml
|
253
|
+
|
254
|
+
%main
|
255
|
+
component(model: @contact_box)
|
256
|
+
|
257
|
+
```
|
258
|
+
|
259
|
+
In the component template:
|
260
|
+
|
261
|
+
```haml
|
262
|
+
|
263
|
+
.contact-info
|
264
|
+
.email= model.email
|
265
|
+
|
266
|
+
```
|
267
|
+
|
268
|
+
In the model (`app/assets/javascripts/models/contact_info.rb`):
|
41
269
|
|
270
|
+
```ruby
|
271
|
+
class ContactInfo
|
272
|
+
def initialize
|
273
|
+
end
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
277
|
+
### Collection Data
|
278
|
+
|
279
|
+
Generate collections:
|
280
|
+
```sh
|
281
|
+
bin/rails g opalla:collection products
|
282
|
+
```
|
283
|
+
|
284
|
+
Opalla will automatically assume that the model is the singular name. So `products` for example is a collection of `product` model. So you have to have the `product` model as well.
|
285
|
+
|
286
|
+
When working with collection components, you’ll often want to work with `data-attributes`. Opalla has a nifty way to help you:
|
287
|
+
|
288
|
+
Consider you have the following component:
|
289
|
+
```ruby
|
290
|
+
class ProductComponent < ApplicationComponent
|
291
|
+
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
With the following collection (notice the binding):
|
296
|
+
```ruby
|
297
|
+
class Products < Opalla::Collection
|
298
|
+
bind :price, :category
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
On your collection model, you set data:
|
303
|
+
```ruby
|
304
|
+
class Product < Opalla::Model
|
305
|
+
data :price, :category, :model_id
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
On your component template:
|
310
|
+
```haml
|
311
|
+
.products
|
312
|
+
collection.each |product|
|
313
|
+
.product{data=product.data}
|
314
|
+
end
|
315
|
+
end
|
316
|
+
```
|
317
|
+
|
318
|
+
That will generate data attributes: `data-price`, `data-category`, `data-model-id`, the last enabling you to incorporate events in a very simple fashion
|
319
|
+
|
320
|
+
```ruby
|
321
|
+
class ProductComponent < ApplicationComponent
|
322
|
+
events 'click .product' -> target { buy(collection.find(target)) }
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
326
|
+
That would trigger the buy element providing the `model` as argument. That’s because the `find method` in collections will look for the element or closest ancestor that has a `data-model-id` and return the model itself for you. Really easy!
|
327
|
+
|
328
|
+
Check the next chapter to see more ways on how collections can be very useful.
|
329
|
+
|
330
|
+
### Bindings
|
331
|
+
|
332
|
+
You can bind data from a model or collection to the component. That will trigger a `#render` action on the component:
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
|
336
|
+
class ContactBoxComponent < ApplicationComponent
|
337
|
+
bind :email, :name
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
```
|
342
|
+
|
343
|
+
And that's it! Everytime the resource attributes change, being it a `model` or a `collection`, the component will be re-rendered.
|
344
|
+
|
345
|
+
#### 2-way binding
|
346
|
+
If you assign a `[data-bind='ATTRIBUTE_NAME']` to an input element on your template, it will change look for the closest ancestor that has `[data-model-id]` and change its attribute whenever you change the input.
|
347
|
+
|
348
|
+
Example:
|
349
|
+
```haml
|
350
|
+
.product{data: product.data} # it is expected you set the model data
|
351
|
+
input{data-bind: 'name'}
|
352
|
+
```
|
353
|
+
|
354
|
+
### Events
|
355
|
+
Similarly to Backbone, you can add events in the following way:
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
|
359
|
+
class PagesController < ApplicationController
|
360
|
+
el '.main-content'
|
361
|
+
events 'click a' => :do_something
|
362
|
+
|
363
|
+
def index
|
364
|
+
el.html 'Hello World!'
|
365
|
+
end
|
366
|
+
|
367
|
+
def do_something
|
368
|
+
alert 'I\'m doing something!'
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
```
|
374
|
+
|
375
|
+
You can also provide a `lambda` instead:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
class PagesController < ApplicationController
|
379
|
+
el '.main-content'
|
380
|
+
events 'click a' => { alert "I'm doing something!" }
|
381
|
+
|
382
|
+
def index
|
383
|
+
el.html 'Hello World!'
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
```
|
389
|
+
You can also access the targetted object like this:
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
class PagesController < ApplicationController
|
393
|
+
el '.main-content'
|
394
|
+
events 'click a' => target { alert 'I\'m doing something!' }
|
395
|
+
|
396
|
+
def index
|
397
|
+
el.html 'Hello World!'
|
398
|
+
end
|
399
|
+
|
400
|
+
def do_something
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
```
|
407
|
+
|
408
|
+
Please note that all events have their default behavior prevented by default.
|
409
|
+
|
410
|
+
## Development
|
411
|
+
The project is on beta phase. It's missing:
|
412
|
+
|
413
|
+
* Specs
|
414
|
+
* Tests
|
415
|
+
* Rubocops
|
416
|
+
* ActiveRecord models support
|
417
|
+
|
418
|
+
Fork and make a PR. Or talk to me at `pedro@pedromaciel.com`.
|
419
|
+
|
420
|
+
## License
|
421
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/opalla.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require 'ostruct'
|
2
|
+
require 'bundler'; Bundler.require
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
require 'opal-browser'
|
5
|
+
require 'opalla/version'
|
6
|
+
require 'opalla/component_helper'
|
7
|
+
require 'opalla/controller_add_on'
|
8
|
+
require 'opalla/util'
|
9
|
+
require 'opalla/middleware'
|
10
|
+
require 'opalla/engine'
|
11
|
+
require_relative '../opal/model'
|
12
|
+
require_relative '../opal/collection'
|