eyeballs 0.4.2 → 0.5.0
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.
- data/CHANGELOG +17 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +14 -2
- data/README.md +17 -41
- data/Rakefile +1 -1
- data/TODO +2 -0
- data/app.rb +40 -2
- data/eyeballs.gemspec +3 -3
- data/lib/eyeballs/app_generator.rb +0 -1
- data/spec/app_generator_spec.rb +1 -1
- data/src/drivers/jquery/adapters/o_O.couchdb.js +2 -2
- data/src/drivers/jquery/adapters/o_O.rest.js +59 -14
- data/src/drivers/jquery/modules/o_O.controller.js +8 -27
- data/src/drivers/jquery/modules/o_O.routes.js +5 -2
- data/src/drivers/jquery/modules/o_O.support.js +20 -2
- data/src/modules/o_O.model.js +38 -11
- data/src/modules/o_O.validations.js +4 -4
- data/templates/app_root/config/initializer.js +2 -1
- data/test/unit/test_controller.html +22 -39
- data/test/unit/test_model.html +5 -3
- data/test/unit/test_model_with_callbacks.html +12 -0
- data/test/unit/test_rest.html +122 -6
- data/test/unit/test_routing.html +3 -3
- metadata +5 -5
- data/dist/jquery/jquery.livequery.js +0 -226
data/CHANGELOG
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
Sat Jul 17
|
2
|
+
- - - - - -
|
3
|
+
- Remove data-controller data-action stuff. "data-bind" is way better.
|
4
|
+
|
5
|
+
Wed Jul 14 2010
|
6
|
+
- - - - - - - -
|
7
|
+
- Add 'url' parameter for destroy
|
8
|
+
- Add errors.on for finding errors
|
9
|
+
|
10
|
+
Fri Jul 9
|
11
|
+
- - - - -
|
12
|
+
- Add 'create' method
|
13
|
+
- Add 'url' parameter for save
|
14
|
+
- Specify whether to use a json_root (for Rails etc.)
|
15
|
+
- Allow specifying the json_root name
|
16
|
+
- Allow specifying a CSRF token
|
17
|
+
|
1
18
|
Wed Jul 7 2010
|
2
19
|
- - - - - - - -
|
3
20
|
- Add namespace and root support in routing
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
---
|
2
2
|
dependencies:
|
3
|
+
ruby-debug:
|
4
|
+
group:
|
5
|
+
- :test
|
6
|
+
version: ">= 0"
|
3
7
|
thor:
|
4
8
|
group:
|
5
9
|
- :default
|
@@ -23,6 +27,8 @@ dependencies:
|
|
23
27
|
specs:
|
24
28
|
- activesupport:
|
25
29
|
version: 2.3.8
|
30
|
+
- columnize:
|
31
|
+
version: 0.3.1
|
26
32
|
- json_pure:
|
27
33
|
version: 1.4.3
|
28
34
|
- gemcutter:
|
@@ -33,15 +39,21 @@ specs:
|
|
33
39
|
version: 2.0.4
|
34
40
|
- jeweler:
|
35
41
|
version: 1.4.0
|
42
|
+
- linecache:
|
43
|
+
version: "0.43"
|
36
44
|
- rack:
|
37
45
|
version: 1.2.1
|
38
46
|
- rspec:
|
39
47
|
version: 1.3.0
|
48
|
+
- ruby-debug-base:
|
49
|
+
version: 0.10.3
|
50
|
+
- ruby-debug:
|
51
|
+
version: 0.10.3
|
40
52
|
- sinatra:
|
41
53
|
version: "1.0"
|
42
54
|
- thor:
|
43
|
-
version: 0.13.
|
44
|
-
hash:
|
55
|
+
version: 0.13.8
|
56
|
+
hash: 18ee2e0d3e9a46cd1d8e8c39951ba9414860f401
|
45
57
|
sources:
|
46
58
|
- Rubygems:
|
47
59
|
uri: http://gemcutter.org
|
data/README.md
CHANGED
@@ -118,7 +118,7 @@ Not very exciting.
|
|
118
118
|
However, if you're familiar with Rails, you'll be familiar with the wonderful syntax for adding validations to your models. eyeballs.js lets you add validations to your models as follows:
|
119
119
|
|
120
120
|
o_O('Post', function(post){
|
121
|
-
post.validates_presence_of('title)
|
121
|
+
post.validates_presence_of('title')
|
122
122
|
})
|
123
123
|
|
124
124
|
Now, when you initialize a new Post, you can validate it, nice and easy:
|
@@ -255,52 +255,30 @@ Tasty!
|
|
255
255
|
|
256
256
|
#### Binding actions to events ####
|
257
257
|
|
258
|
-
To bind events to these controller actions, use the data-
|
258
|
+
To bind events to these controller actions, use the data-bind attribute:
|
259
259
|
|
260
|
-
<a href="/posts/new" data-controller="posts
|
260
|
+
<a href="/posts/new" data-controller="posts#new">Click me!</a>
|
261
261
|
|
262
262
|
This binds all clicks on this element to the new action on the PostsController. By default, if you add these attributes to a form, the action is bound to the submit event; to all other elements it binds to a click.
|
263
263
|
|
264
|
-
It also returns false, canceling out the default behavior. If you want the default behavior,
|
264
|
+
It also returns false, canceling out the default behavior. If you want the default behavior, prefix with `+` to "add" the action to the propagation chain:
|
265
265
|
|
266
|
-
<a href="/posts/new" data-controller="posts
|
266
|
+
<a href="/posts/new" data-controller="+posts#new">Click me!</a>
|
267
267
|
|
268
|
-
You can also bind to custom events
|
268
|
+
You can also bind to custom events:
|
269
269
|
|
270
|
-
<a href="/posts/new" data-controller="posts
|
270
|
+
<a href="/posts/new" data-controller="+mouseover:posts#new">Hover over me!</a>
|
271
271
|
|
272
|
-
|
272
|
+
You can bind multiple events and actions to a single element:
|
273
273
|
|
274
|
-
|
275
|
-
|
276
|
-
<a href="/posts/new" data-bind="posts/new">Click me!</a>
|
277
|
-
|
278
|
-
Disable returning false to continue propagating new events:
|
279
|
-
|
280
|
-
<a href="/posts/new" data-bind="+posts/new">Click me!</a>
|
281
|
-
|
282
|
-
Custom events:
|
283
|
-
|
284
|
-
<a href="/posts/new" data-bind="mouseover:posts/new">Click me!</a>
|
285
|
-
|
286
|
-
Finally, in shorthand only, you can bind multiple events to a single element:
|
287
|
-
|
288
|
-
<a href="/posts/new" data-bind="mouseover:posts/preview; click: posts/new">Hover first, then Click me!</a>
|
274
|
+
<a href="/posts/new" data-bind="mouseover:posts#preview; click: posts/new">Hover first, then Click me!</a>
|
289
275
|
|
290
|
-
|
276
|
+
It's called "obtrusive UJS" ... explicit, yet everything has its own place.
|
291
277
|
|
292
278
|
Putting it all together
|
293
279
|
-----------------------
|
294
280
|
|
295
|
-
|
296
|
-
|
297
|
-
There's a small demo app included in this package, a simple app for adding personal reviews. It's a simple Sinatra app, so you can run it with:
|
298
|
-
|
299
|
-
ruby app.rb
|
300
|
-
|
301
|
-
And then visit it in a browser at `localhost:4567`
|
302
|
-
|
303
|
-
It should all "just work" ... in a browser that supports HTML5 local storage.
|
281
|
+
Imagine a simple app for posting reviews. It will comprise a "Review" model, "ReviewsController" and associated views.
|
304
282
|
|
305
283
|
`models/review.js` looks like this:
|
306
284
|
|
@@ -309,9 +287,9 @@ It should all "just work" ... in a browser that supports HTML5 local storage.
|
|
309
287
|
review.validates_presence_of('content');
|
310
288
|
});
|
311
289
|
|
312
|
-
This defines the Review model, allowing us to initialize and save Review objects.
|
290
|
+
This defines the Review model, allowing us to initialize and save Review objects, while ensuring `title` and `content` are included.
|
313
291
|
|
314
|
-
The `create` action in `controllers/reviews_controller.js` looks like this:
|
292
|
+
The `create` action in `controllers/reviews_controller.js` looks like this (using jQuery):
|
315
293
|
|
316
294
|
...
|
317
295
|
create: function(){
|
@@ -331,7 +309,7 @@ The `create` action in `controllers/reviews_controller.js` looks like this:
|
|
331
309
|
|
332
310
|
The form that hooks up to this action is like this:
|
333
311
|
|
334
|
-
<form data-
|
312
|
+
<form data-bind="reviews#create">
|
335
313
|
<label for="review-title">Title</label><br />
|
336
314
|
<input type="text" name="title" value="" data-attribute="title"><br />
|
337
315
|
<label for="review-content">Review</label><br />
|
@@ -339,15 +317,11 @@ The form that hooks up to this action is like this:
|
|
339
317
|
<input type="submit" name="commit" value="Save">
|
340
318
|
</form>
|
341
319
|
|
342
|
-
The main things to note here are the way that the form binds automatically to the create action (using jQuery). Also, field elements have the "data-attribute" attributes ... the o\_O.params() function reads from these, returning a JSON object that can be passed to Review.initialize(...).
|
320
|
+
The main things to note here are the way that the form binds automatically to the create action (using jQuery event delegation). Also, field elements have the "data-attribute" attributes ... the o\_O.params() function reads from these, returning a JSON object that can be passed to Review.initialize(...).
|
343
321
|
|
344
322
|
Notice also `o_O.alert_errors(...)` which displays an alert of all the errors on an invalid review.
|
345
323
|
|
346
324
|
Finally, the o\_O.render function takes a template, which is a Mustache.js template stored in `views/`, the review object and a set of options.
|
347
|
-
|
348
|
-
This is all very early stuff, but please feel free to play and feedback.
|
349
|
-
|
350
|
-
That's all for now!
|
351
325
|
|
352
326
|
|
353
327
|
Running the tests
|
@@ -355,6 +329,8 @@ Running the tests
|
|
355
329
|
|
356
330
|
eyeballs.js uses QUnit for in-browser testing. Just load the files in the test directory in any browser to run them.
|
357
331
|
|
332
|
+
For the REST tests, you can use the included Sinatra app, `app.rb`. The tests expect it to be in a local virtual domain `http://eyeballs`
|
333
|
+
|
358
334
|
About me
|
359
335
|
--------
|
360
336
|
|
data/Rakefile
CHANGED
data/TODO
ADDED
data/app.rb
CHANGED
@@ -11,6 +11,44 @@ post '/reviews' do
|
|
11
11
|
'{"id": "1"}'
|
12
12
|
end
|
13
13
|
|
14
|
+
post '/alternate_reviews' do
|
15
|
+
'{"id": "2"}'
|
16
|
+
end
|
17
|
+
|
18
|
+
post '/string_back' do
|
19
|
+
'some string'
|
20
|
+
end
|
21
|
+
|
22
|
+
post '/test_id' do
|
23
|
+
if params[:alternate_object]
|
24
|
+
if params[:id]
|
25
|
+
'fail'
|
26
|
+
end
|
27
|
+
else
|
28
|
+
'ok'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
post '/rails_reviews' do
|
33
|
+
if params[:rails_review]
|
34
|
+
'ok'
|
35
|
+
else
|
36
|
+
'fail'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
post '/alternate_rails_reviews' do
|
41
|
+
if params[:alternate_object]
|
42
|
+
'ok'
|
43
|
+
else
|
44
|
+
'fail'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
post '/auth_token' do
|
49
|
+
params[:authenticity_token]
|
50
|
+
end
|
51
|
+
|
14
52
|
get '/reviews/:id' do
|
15
53
|
'{"id": "1", "title":"More Magic!"}'
|
16
54
|
end
|
@@ -20,9 +58,9 @@ get '/my/reviews/:id' do
|
|
20
58
|
end
|
21
59
|
|
22
60
|
put '/reviews/:id' do
|
23
|
-
'{"id": "1", "title":"
|
61
|
+
'{"id": "1", "title":"Updated"}'
|
24
62
|
end
|
25
63
|
|
26
64
|
delete '/reviews/:id' do
|
27
|
-
'
|
65
|
+
'Deleted OK'
|
28
66
|
end
|
data/eyeballs.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{eyeballs}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Paul Campbell"]
|
12
|
-
s.date = %q{2010-07-
|
12
|
+
s.date = %q{2010-07-17}
|
13
13
|
s.default_executable = %q{eyeballs}
|
14
14
|
s.email = %q{paul@rslw.com}
|
15
15
|
s.executables = ["eyeballs"]
|
@@ -22,12 +22,12 @@ Gem::Specification.new do |s|
|
|
22
22
|
"Gemfile.lock",
|
23
23
|
"README.md",
|
24
24
|
"Rakefile",
|
25
|
+
"TODO",
|
25
26
|
"app.rb",
|
26
27
|
"bin/eyeballs",
|
27
28
|
"config.ru",
|
28
29
|
"dist/jquery/jquery-1.4.2.min.js",
|
29
30
|
"dist/jquery/jquery.ba-hashchange.js",
|
30
|
-
"dist/jquery/jquery.livequery.js",
|
31
31
|
"dist/mustache/mustache.0.2.3.js",
|
32
32
|
"eyeballs.gemspec",
|
33
33
|
"lib/eyeballs.rb",
|
@@ -18,7 +18,6 @@ module Eyeballs
|
|
18
18
|
def build_the_app
|
19
19
|
directory "templates/app_root", new_app_path
|
20
20
|
copy_file 'dist/jquery/jquery-1.4.2.min.js', "#{new_app_path}/vendor/jquery/jquery-1.4.2.min.js"
|
21
|
-
copy_file 'dist/jquery/jquery.livequery.js', "#{new_app_path}/vendor/jquery/jquery.livequery.js"
|
22
21
|
copy_file 'dist/mustache/mustache.0.2.3.js', "#{new_app_path}/vendor/mustache/mustache.0.2.3.js"
|
23
22
|
directory "src", "#{new_app_path}/vendor/eyeballs/"
|
24
23
|
end
|
data/spec/app_generator_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe Eyeballs::AppGenerator do
|
|
43
43
|
|
44
44
|
it "should create the app files" do
|
45
45
|
jquery_file.should exist
|
46
|
-
livequery_file.
|
46
|
+
livequery_file.should_not exist
|
47
47
|
mustache_file.should exist
|
48
48
|
index_file.should exist
|
49
49
|
initializer_file.should exist
|
@@ -4,8 +4,8 @@ o_O.couchdb = {
|
|
4
4
|
|
5
5
|
var database = o_O.model.adapter.settings.database;
|
6
6
|
|
7
|
-
var ddoc_id = ['_design', model.table_name].join('/')
|
8
|
-
var view_name = model.table_name + '/all'
|
7
|
+
var ddoc_id = ['_design', model.table_name].join('/');
|
8
|
+
var view_name = model.table_name + '/all';
|
9
9
|
var get_all_documents = function(result){
|
10
10
|
var documents = result.rows;
|
11
11
|
var all_documents = [];
|
@@ -1,5 +1,19 @@
|
|
1
1
|
// REST & Rails, woop!
|
2
2
|
o_O.rest = {
|
3
|
+
figure_url: function(original_callback, object){
|
4
|
+
if(typeof original_callback.url === 'string')
|
5
|
+
{
|
6
|
+
return original_callback.url;
|
7
|
+
}
|
8
|
+
else if(typeof window[object.model_name]['url'] === 'string')
|
9
|
+
{
|
10
|
+
return window[object.model_name]['url'];
|
11
|
+
}
|
12
|
+
else
|
13
|
+
{
|
14
|
+
return '/' + object.table_name;
|
15
|
+
}
|
16
|
+
},
|
3
17
|
all: function(model, callback){
|
4
18
|
$.get('/' + model.table_name, function(response){
|
5
19
|
var documents = JSON.parse(response);
|
@@ -9,47 +23,76 @@ o_O.rest = {
|
|
9
23
|
}
|
10
24
|
})
|
11
25
|
},
|
12
|
-
destroy: function(object, callback){
|
26
|
+
destroy: function(object, callback, original_callback){
|
13
27
|
object.destroyed = true;
|
14
28
|
$.ajax({
|
15
29
|
type: 'DELETE',
|
16
|
-
url:
|
17
|
-
success: function(){
|
30
|
+
url: this.figure_url(original_callback, object) + '/' + object.id,
|
31
|
+
success: function(response){
|
18
32
|
if(typeof callback === 'function')
|
19
33
|
{
|
20
|
-
callback(object);
|
34
|
+
callback(object, response);
|
21
35
|
}
|
22
36
|
}
|
23
37
|
})
|
24
38
|
},
|
25
|
-
save: function(object, callback)
|
39
|
+
save: function(object, callback, original_callback)
|
26
40
|
{
|
27
|
-
var object_to_save = {}
|
41
|
+
var object_to_save = {};
|
42
|
+
var url;
|
28
43
|
for(var i = 0; i < object.attributes.length; i++)
|
29
44
|
{
|
30
45
|
object_to_save[object.attributes[i]] = object[object.attributes[i]];
|
31
46
|
}
|
32
47
|
var respond = function(response){
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
48
|
+
try{
|
49
|
+
var saved_object = JSON.parse(response);
|
50
|
+
for(var attribute in saved_object)
|
51
|
+
{
|
52
|
+
object_to_save[attribute] = saved_object[attribute];
|
53
|
+
}
|
54
|
+
object_to_save.new_record = false;
|
55
|
+
}
|
56
|
+
catch(e){
|
57
|
+
// keep the ID persistent
|
58
|
+
object_to_save.id = object.id;
|
37
59
|
}
|
38
|
-
object_to_save.new_record = false;
|
39
60
|
if(typeof callback === 'function')
|
40
61
|
{
|
41
|
-
callback(object_to_save);
|
62
|
+
callback(object_to_save, response);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
url = this.figure_url(original_callback, object);
|
66
|
+
|
67
|
+
if(window[object.model_name]['include_json_root'] === true)
|
68
|
+
{
|
69
|
+
var object_name;
|
70
|
+
new_object_to_save = {};
|
71
|
+
if(typeof window[object.model_name]['json_root_name'] === 'string')
|
72
|
+
{
|
73
|
+
object_name = window[object.model_name]['json_root_name'];
|
74
|
+
}
|
75
|
+
else
|
76
|
+
{
|
77
|
+
object_name = object.model_name.underscore();
|
42
78
|
}
|
79
|
+
new_object_to_save[object_name] = object_to_save;
|
80
|
+
object_to_save = new_object_to_save;
|
81
|
+
}
|
82
|
+
if(typeof o_O.config.authenticity_token === 'string')
|
83
|
+
{
|
84
|
+
object_to_save['authenticity_token'] = o_O.config.authenticity_token;
|
43
85
|
}
|
44
86
|
if(object.new_record)
|
45
87
|
{
|
46
|
-
$.post(
|
88
|
+
$.post(url, object_to_save, respond);
|
47
89
|
}
|
48
90
|
else
|
49
91
|
{
|
50
92
|
$.ajax({
|
51
93
|
type: 'PUT',
|
52
|
-
|
94
|
+
data: object_to_save,
|
95
|
+
url: url + '/' + object.id,
|
53
96
|
success: respond
|
54
97
|
})
|
55
98
|
}
|
@@ -71,3 +114,5 @@ o_O.rest = {
|
|
71
114
|
})
|
72
115
|
}
|
73
116
|
}
|
117
|
+
|
118
|
+
o_O.config.rest = {}
|
@@ -1,38 +1,19 @@
|
|
1
1
|
o_O.controller = {
|
2
2
|
initialize: function(controller_name, controller){
|
3
|
-
var action_event = function(object){
|
4
|
-
if(object.attr('data-event'))
|
5
|
-
{
|
6
|
-
return object.attr('data-event');
|
7
|
-
}
|
8
|
-
return (object.is('form')) ? 'submit' : 'click';
|
9
|
-
}
|
10
3
|
|
11
4
|
var controller_name = controller_name.replace('Controller', '').toLowerCase();
|
12
5
|
var controller = controller;
|
13
6
|
|
14
7
|
$(function(){
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
var selector = '[data-controller=' + controller_name + '][data-action=' + action + ']';
|
19
|
-
$(selector).livequery(function(){
|
20
|
-
var element = $(this);
|
21
|
-
$(this).bind(action_event(element), controller[element.attr('data-action')]);
|
22
|
-
if(!($(this).attr('data-default')))
|
23
|
-
{
|
24
|
-
$(this).bind(action_event(element), function(){ return false; });
|
25
|
-
}
|
26
|
-
})
|
27
|
-
}
|
28
|
-
$('[data-bind]').livequery(function(){
|
29
|
-
var binders = $(this).attr('data-bind').match(/[\+]?(\s+)?[^ :]?[: ]?[^ #]+[ #]+[^ ;]+[ ;]?/g)
|
8
|
+
var events = 'click submit hover mouseover blur focus change dblclick keydown keypress keyup scroll'
|
9
|
+
$('body').delegate('[data-bind]', events, function(event){
|
10
|
+
var binders = $(this).attr('data-bind').match(/[\+]?(\s+)?[^ :]?[: ]?[^ #]+[ #]+[^ ;]+[ ;]?/g);
|
30
11
|
if(binders != null && binders.length > 0)
|
31
12
|
{
|
32
13
|
for(i = 0; i < binders.length; i++)
|
33
14
|
{
|
34
15
|
var rule = binders[i];
|
35
|
-
var parts = rule.match(/([\+])?(\s+)?(([^ :]+)([: ]+))?([^ #]+)[ #]+([^ ;]+)[ ;]?/)
|
16
|
+
var parts = rule.match(/([\+])?(\s+)?(([^ :]+)([: ]+))?([^ #]+)[ #]+([^ ;]+)[ ;]?/);
|
36
17
|
var default_bit = parts[1];
|
37
18
|
var this_action_event = parts[4];
|
38
19
|
if(this_action_event === undefined)
|
@@ -41,17 +22,17 @@ o_O.controller = {
|
|
41
22
|
}
|
42
23
|
var this_controller_name = parts[6];
|
43
24
|
var this_action = parts[7];
|
44
|
-
if(this_controller_name == controller_name)
|
25
|
+
if(this_controller_name == controller_name && this_action_event === event.type)
|
45
26
|
{
|
46
|
-
|
27
|
+
controller[this_action].apply(this);
|
47
28
|
if(default_bit != '+')
|
48
29
|
{
|
49
|
-
|
30
|
+
return false;
|
50
31
|
}
|
51
32
|
}
|
52
33
|
}
|
53
34
|
}
|
54
|
-
})
|
35
|
+
})
|
55
36
|
});
|
56
37
|
return controller;
|
57
38
|
}
|