eyeballs 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -10,4 +10,5 @@ end
10
10
 
11
11
  group :test do
12
12
  gem 'rspec'
13
+ gem 'ruby-debug'
13
14
  end
@@ -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.7
44
- hash: 17f409790dfc3ab7e279201874a3aab85e4c7c95
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-controller and data-action attributes:
258
+ To bind events to these controller actions, use the data-bind attribute:
259
259
 
260
- <a href="/posts/new" data-controller="posts" data-action="new">Click me!</a>
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, you can add `data-default=true`:
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" data-action="new" data-default="true">Click me!</a>
266
+ <a href="/posts/new" data-controller="+posts#new">Click me!</a>
267
267
 
268
- You can also bind to custom events, using data-event:
268
+ You can also bind to custom events:
269
269
 
270
- <a href="/posts/new" data-controller="posts" data-action="new" data-default="true" data-event="mouseover">Hover over me!</a>
270
+ <a href="/posts/new" data-controller="+mouseover:posts#new">Hover over me!</a>
271
271
 
272
- There's also a shorthand for binding:
272
+ You can bind multiple events and actions to a single element:
273
273
 
274
- Bind clicks to PostsController#new
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
- Isn't that cool?
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
- TODO: The demo app isn't working right now. There should be a generator to generate a new demo app after each release.
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-controller="reviews" data-action="create">
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
@@ -27,7 +27,7 @@ begin
27
27
  require 'jeweler'
28
28
  Jeweler::Tasks.new do |s|
29
29
  s.name = "eyeballs"
30
- s.version = "0.4.2"
30
+ s.version = "0.5.0"
31
31
  s.author = "Paul Campbell"
32
32
  s.email = "paul@rslw.com"
33
33
  s.homepage = "http://www.github.com/paulca/eyeballs.js"
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ - alert_errors
2
+ - Rails helper to automatically include "data-attributes"
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":"Tennessee"}'
61
+ '{"id": "1", "title":"Updated"}'
24
62
  end
25
63
 
26
64
  delete '/reviews/:id' do
27
- '{"id": "1", "title":"Tennessee"}'
65
+ 'Deleted OK'
28
66
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{eyeballs}
8
- s.version = "0.4.2"
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-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
@@ -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.should exist
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: '/' + object.table_name + '/' + object.id,
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
- var saved_object = JSON.parse(response);
34
- for(var attribute in saved_object)
35
- {
36
- object_to_save[attribute] = saved_object[attribute];
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('/' + object.table_name, object_to_save, respond);
88
+ $.post(url, object_to_save, respond);
47
89
  }
48
90
  else
49
91
  {
50
92
  $.ajax({
51
93
  type: 'PUT',
52
- url: '/' + object.table_name + '/' + object.id,
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
- for(var action in controller)
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
- $(this).bind(this_action_event,controller[this_action]);
27
+ controller[this_action].apply(this);
47
28
  if(default_bit != '+')
48
29
  {
49
- $(this).bind(this_action_event, function(){ return false; });
30
+ return false;
50
31
  }
51
32
  }
52
33
  }
53
34
  }
54
- });
35
+ })
55
36
  });
56
37
  return controller;
57
38
  }