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.
@@ -26,7 +26,10 @@ o_O.routes = {
26
26
  {
27
27
  parsed_route = prefix + '/' + parsed_route
28
28
  }
29
- o_O.routes.rules[parsed_route] = {"action": o_O.routes.figure_action(options), "with": options.with};
29
+ o_O.routes.rules[parsed_route] = {
30
+ action: o_O.routes.figure_action(options),
31
+ with_args: options.with_args
32
+ };
30
33
  o_O.routes.urls.push(parsed_route);
31
34
  }
32
35
  }
@@ -48,7 +51,7 @@ o_O.routes = {
48
51
  var hash = location.hash.replace(/^(#)/, '').o_O_trim('/');
49
52
  if(o_O.routes.urls.indexOf(hash) >= 0)
50
53
  {
51
- o_O.routes.rules[hash].action(o_O.routes.rules[hash].with);
54
+ o_O.routes.rules[hash].action(o_O.routes.rules[hash].with_args);
52
55
  }
53
56
  });
54
57
 
@@ -61,10 +61,28 @@ o_O.get_template = function(template, data, callback){
61
61
  }
62
62
  }
63
63
 
64
- if(typeof String.prototype.capitalize == 'undefined')
64
+ if(typeof String.prototype.capitalize === 'undefined')
65
65
  {
66
66
  String.prototype.capitalize = function(){
67
- return this.charAt(0).toUpperCase() + this.slice(1);
67
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
68
+ }
69
+ }
70
+
71
+ if(typeof String.prototype.underscore === 'undefined')
72
+ {
73
+ String.prototype.underscore = function(){
74
+ return this.replace(/::/g, '/')
75
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
76
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
77
+ .replace(/-/g, '_')
78
+ .toLowerCase();
79
+ }
80
+ }
81
+
82
+ if(typeof String.prototype.blank === 'undefined')
83
+ {
84
+ String.prototype.blank = function(){
85
+ return /^\s*$/.test(this);
68
86
  }
69
87
  }
70
88
 
@@ -4,7 +4,8 @@ o_O.model = {
4
4
  var callback = callback;
5
5
  var class_methods, instance_methods, initializer_methods;
6
6
  var validates_presence_of, validates_length_of;
7
- var table_name = model_name.toLowerCase() + 's';
7
+ var table_name = model_name.underscore() + 's';
8
+ var initial_class_methods = [];
8
9
 
9
10
  class_methods = {
10
11
  validations: {presence: [], lengthliness: [], custom: []},
@@ -20,16 +21,21 @@ o_O.model = {
20
21
  this.validations.custom.push(validation)
21
22
  }
22
23
  }
24
+ for(var method in class_methods)
25
+ {
26
+ initial_class_methods.push(method);
27
+ }
28
+
23
29
 
24
- var run_callback = function(callback, method, args){
30
+ var run_callback = function(callback, method, object, response){
25
31
  try{
26
32
  if(typeof callback === 'function' && method === 'success')
27
33
  {
28
- callback(args);
34
+ callback(object, response);
29
35
  }
30
36
  if(typeof callback === 'object' && typeof callback[method] === 'function')
31
37
  {
32
- callback[method](args);
38
+ callback[method](object, response);
33
39
  }
34
40
  }
35
41
  catch(e)
@@ -52,9 +58,9 @@ o_O.model = {
52
58
  run_callback(callback, 'loading', this)
53
59
  if(this.adapter)
54
60
  {
55
- this.adapter.destroy(this, function(returned_object){
56
- run_callback(callback, 'success', returned_object);
57
- });
61
+ this.adapter.destroy(this, function(returned_object, response){
62
+ run_callback(callback, 'success', returned_object, response);
63
+ }, callback);
58
64
  }
59
65
  else
60
66
  {
@@ -70,11 +76,11 @@ o_O.model = {
70
76
  if(this.adapter)
71
77
  {
72
78
  var model = this;
73
- this.adapter.save(this, function(returned_object){
79
+ this.adapter.save(this, function(returned_object, response){
74
80
  var initialized_object = o_O.models[model.model_name].initialize(returned_object);
75
81
  initialized_object.new_record = false;
76
- run_callback(callback, 'success', initialized_object);
77
- });
82
+ run_callback(callback, 'success', initialized_object, response);
83
+ }, callback);
78
84
  }
79
85
  else
80
86
  {
@@ -107,7 +113,7 @@ o_O.model = {
107
113
  this.save(callback);
108
114
  },
109
115
  valid: function(){
110
- this.errors = [];
116
+ this.errors.length = 0;
111
117
 
112
118
  o_O.validations.run(this);
113
119
 
@@ -123,6 +129,16 @@ o_O.model = {
123
129
  errors: [],
124
130
  validations: class_methods.validations
125
131
  }
132
+ instance_methods.errors.on = function(field){
133
+ for(var i=0; i<instance_methods.errors.length; i++)
134
+ {
135
+ if(field === instance_methods.errors[i].field)
136
+ {
137
+ return instance_methods.errors[i];
138
+ }
139
+ }
140
+ }
141
+
126
142
  for(method in class_methods.methods)
127
143
  {
128
144
  instance_methods[method] = class_methods.methods[method]
@@ -157,6 +173,9 @@ o_O.model = {
157
173
  }
158
174
  return attributes;
159
175
  },
176
+ create: function(attributes, callbacks){
177
+ this.initialize(attributes).save(callbacks);
178
+ },
160
179
  find: function(id, callback){
161
180
  if(this.adapter)
162
181
  {
@@ -182,6 +201,14 @@ o_O.model = {
182
201
  model_name: model_name,
183
202
  table_name: table_name
184
203
  }
204
+
205
+ for(var method in class_methods)
206
+ {
207
+ if(initial_class_methods.indexOf(method) === -1)
208
+ {
209
+ initializer_methods[method] = class_methods[method];
210
+ }
211
+ }
185
212
 
186
213
  return initializer_methods;
187
214
  }
@@ -20,12 +20,12 @@ o_O.validations = {
20
20
  {
21
21
  if(max && object[field].length > max)
22
22
  {
23
- var message = field + ' should be less than ' + max + ' characters';
23
+ var message = field.capitalize() + ' should be less than ' + max + ' characters';
24
24
  object.errors.push({field: field, type: 'length', message: message});
25
25
  }
26
26
  if(min && object[field].length < min)
27
27
  {
28
- var message = field + ' should be greater than ' + min + ' characters';
28
+ var message = field.capitalize() + ' should be greater than ' + min + ' characters';
29
29
  object.errors.push({field: field, type: 'length', message: message});
30
30
  }
31
31
  }
@@ -35,9 +35,9 @@ o_O.validations = {
35
35
  for(var i = 0; i < object.validations.presence.length; i++)
36
36
  {
37
37
  var field = object.validations.presence[i].field;
38
- if(object[field] == '' || object[field] == null)
38
+ if(object[field] == null || (typeof object[field] === 'string' && object[field].blank()) )
39
39
  {
40
- var message = field + ' should be present';
40
+ var message = field.capitalize() + ' should be present';
41
41
  object.errors.push({field: field, type: 'presence', message: message})
42
42
  }
43
43
  }
@@ -1,3 +1,4 @@
1
1
  // This is a very handy place to put any initial logic
2
2
  // For example, choosing your adapter
3
- // o_O.model.adapter = o_O.rest;
3
+ o_O.model.adapter = o_O.rest;
4
+ o_O.config.authenticity_token = $('meta[name=csrf-token]').attr('content')
@@ -3,7 +3,6 @@
3
3
  <html>
4
4
  <head>
5
5
  <script src="../../dist/jquery/jquery-1.4.2.min.js"></script>
6
- <script src="../../dist/jquery/jquery.livequery.js"></script>
7
6
  <script src="../../src/o_O.js"></script>
8
7
  <script src="../../src/drivers/jquery/modules/o_O.controller.js"></script>
9
8
  <link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
@@ -46,50 +45,39 @@
46
45
  $('form#myForm').trigger('submit');
47
46
  equals($('form#myForm').html(), 'created!', 'html should have been updated');
48
47
  });
49
-
50
- test('submitting an auto-bound form with data-bind shortcut', 1, function(){
51
- $('form#myShortcutForm').trigger('submit');
52
- equals($('form#myShortcutForm').html(), 'created!', 'html should have been updated');
53
- });
54
-
48
+
55
49
  test('clicking an auto-bound link', 1, function(){
56
50
  $('a#myLink').trigger('click');
57
51
  equals($('a#myLink').html(), 'shown!', 'html should have been updated');
58
52
  });
59
-
60
- test('clicking an auto-bound link with data-bind shortcut', 1, function(){
61
- $('a#myShortcutLink').trigger('click');
62
- equals($('a#myShortcutLink').html(), 'shown!', 'html should have been updated');
63
- });
64
-
53
+
54
+
65
55
  test('hovering a custom bound element', 1, function(){
66
56
  $('div#myDiv').trigger('mouseover');
67
57
  equals($('div#myDiv').html(), 'other!', 'html should have been updated');
68
58
  });
69
59
 
70
- new_element = $('<a id="anotherLink" data-controller="reviews" data-action="another">another link</a>')
60
+ new_element = $('<a id="anotherLink" data-bind="reviews#another">another link</a>')
71
61
  $('div#anotherDiv').append(new_element);
72
62
 
73
63
 
74
- setTimeout(function(){
75
- test("a new link", 1, function(){
64
+ asyncTest('new link', function(){
65
+ setTimeout(function(){
76
66
  $('a#anotherLink').trigger('click');
77
67
  equals($('a#anotherLink').html(), 'another!', 'it should have some dynamic behavior');
78
- })
79
- }, 100);
80
-
81
- test('should allow default behavior', 1, function(){
82
- $('input#myCheckbox').trigger('click');
83
- equals($('input#myCheckbox:checked').length, 1, "check box should be checked")
68
+ start();
69
+ }, 100);
84
70
  })
85
-
86
- test('should allow default behavior with data-bind', 2, function(){
87
- $('input#myOtherCheckbox').trigger('click');
88
- equals($('input#myOtherCheckbox:checked').val(), 'bacon', "check box value should change")
89
- equals($('input#myOtherCheckbox:checked').length, 1, "check box should be checked")
71
+
72
+ asyncTest('should allow default behavior', 1, function(){
73
+ setTimeout(function(){
74
+ $('input#myCheckbox').trigger('click');
75
+ equals($('input#myCheckbox:checked').length, 1, "check box should be checked")
76
+ start();
77
+ }, 100)
90
78
  })
91
79
 
92
- test('should bind on shortcuts',2, function(){
80
+ test('should bind multiple events',2, function(){
93
81
  $('div#shortcut').trigger('click');
94
82
  equals($("div#shortcut").html(), 'shortcut!', 'it should be bound with the data-bind keyword')
95
83
  $('div#shortcut').trigger('mouseover');
@@ -107,15 +95,11 @@
107
95
  <ol id="qunit-tests"></ol>
108
96
 
109
97
  <div data-id="paul">
110
- <form id="myForm" data-controller="reviews" data-action="create">
111
- test form
112
- </form>
113
-
114
- <form id="myShortcutForm" data-bind="reviews#create">
98
+ <form id="myForm" data-bind="reviews#create">
115
99
  test form
116
100
  </form>
117
-
118
- <a id="myLink" data-controller="reviews" data-action="show">
101
+
102
+ <a id="myLink" data-bind="reviews#show">
119
103
  test link
120
104
  </a>
121
105
 
@@ -123,15 +107,14 @@
123
107
  test link
124
108
  </a>
125
109
 
126
- <div id="myDiv" data-controller="reviews" data-action="other" data-event="mouseenter">stuff</div>
127
- <div id="shortcut" data-bind="click:reviews#shortcut; mouseenter:reviews#multi">
110
+ <div id="myDiv" data-bind="mouseover:reviews#other">stuff</div>
111
+ <div id="shortcut" data-bind="click:reviews#shortcut; mouseover:reviews#multi">
128
112
  I'm a shortcut
129
113
  </div>
130
114
  <div id="anotherDiv">
131
115
 
132
116
  </div>
133
- <input id="myCheckbox" data-controller="reviews" data-action="check" data-default="true" type="checkbox">
134
- <input id="myOtherCheckbox" data-bind="+click:reviews#check" type="checkbox" value="toast">
117
+ <input id="myCheckbox" data-bind="+click:reviews#check" data-default="true" type="checkbox">
135
118
  </div>
136
119
  </body>
137
120
  </html>
@@ -88,10 +88,12 @@
88
88
  equals(myReview.content, 'Dublin', 'should update the content');
89
89
  })
90
90
 
91
- test('updating a review invalid', 1, function(){
91
+ test('updating a review invalid', function(){
92
92
  myReview = Review.find('paul');
93
93
  myReview.update_attributes({title: '', content: ''});
94
94
  equals(myReview.errors.length, 2, "should have 2 errors");
95
+ equals(myReview.errors.on('title').field, 'title')
96
+ equals(myReview.errors.on('title').type, 'presence')
95
97
  })
96
98
 
97
99
  test('defining methods should work',1, function(){
@@ -105,11 +107,11 @@
105
107
  equals(myReview.errors.length, 1, "should have an error");
106
108
  equals(myReview.errors[0].field, 'title', 'should be on title');
107
109
  equals(myReview.errors[0].type, 'length', 'should be length')
108
- equals(myReview.errors[0].message, 'title should be less than 15 characters', 'should have an error message')
110
+ equals(myReview.errors[0].message, 'Title should be less than 15 characters', 'should have an error message')
109
111
  myReview.title = 'the'
110
112
  myReview.save();
111
113
  equals(myReview.errors.length, 1, "should still only have one error");
112
- equals(myReview.errors[0].message, 'title should be greater than 5 characters', 'should have an error message')
114
+ equals(myReview.errors[0].message, 'Title should be greater than 5 characters', 'should have an error message')
113
115
  });
114
116
 
115
117
  test('custom validation', 3, function(){
@@ -65,6 +65,18 @@
65
65
  });
66
66
  });
67
67
 
68
+ asyncTest('create', 2, function(){
69
+ Review.create({title: "Biscuit", content: "Some Content"},{
70
+ loading: function(){
71
+ ok('should call the loading callback before success')
72
+ },
73
+ success: function(){
74
+ ok('should call the success callback')
75
+ start();
76
+ }
77
+ });
78
+ });
79
+
68
80
  asyncTest('an invalid review',1, function(){
69
81
  var myReview = Review.initialize();
70
82
  myReview.save({
@@ -21,9 +21,12 @@
21
21
 
22
22
  o_O.model.adapter = o_O.rest;
23
23
 
24
- module("Rails Test (with jQuery)");
24
+ module("REST Interface Test (with jQuery)");
25
25
 
26
- o_O('Review', function(){})
26
+ o_O('Review', function(){});
27
+ o_O('FakeReview', function(fake_review){
28
+ fake_review.url = '/reviews'
29
+ });
27
30
 
28
31
  asyncTest('storing a basic basic thing', 3, function(){
29
32
  var review = Review.initialize({title: 'Magic!'})
@@ -35,6 +38,96 @@
35
38
  });
36
39
  });
37
40
 
41
+ asyncTest('storing a basic basic thing with custom URL set in the model', function(){
42
+ FakeReview.create({title: 'Magic!'}, function(saved_review){
43
+ equals(FakeReview.url, '/reviews', 'Should be set');
44
+ equals(saved_review.title, 'Magic!', 'title should match');
45
+ equals(saved_review.id, '1', "Rails should give back the ID");
46
+ start();
47
+ });
48
+ });
49
+
50
+ asyncTest('storing a basic basic thing with a custom URL set in the save', function(){
51
+ var review = Review.initialize({title: 'Magic!'})
52
+ review.save({
53
+ url: "/alternate_reviews",
54
+ success: function(saved_review){
55
+ equals(saved_review.title, review.title, 'title should match');
56
+ equals(saved_review.id, '2', "Rails should give back the ID");
57
+ start();
58
+ }
59
+ });
60
+ });
61
+
62
+ o_O('RailsReview', function(review){
63
+ review.include_json_root = true;
64
+ });
65
+ o_O('AlternateRailsReview', function(review){
66
+ review.include_json_root = true;
67
+ review.json_root_name = 'alternate_object'
68
+ });
69
+
70
+ asyncTest('storing a basic basic thing with include_root', function(){
71
+ RailsReview.create({title: "Awesome"},function(review, response){
72
+ equals(response, 'ok', 'should send things with a root');
73
+ start();
74
+ });
75
+ });
76
+
77
+ asyncTest('specifying a custom json_root_name', function(){
78
+ AlternateRailsReview.create({title: "Awesome"},function(review, response){
79
+ equals(response, 'ok', 'should send things to /alternate_rails_reviews');
80
+ start();
81
+ });
82
+ });
83
+
84
+ o_O.config.authenticity_token = 'a12345';
85
+ asyncTest('CSRF token', function(){
86
+ RailsReview.create({title: "Unnecessary"}, {
87
+ url: '/auth_token',
88
+ success: function(review, response){
89
+ equals(response, 'a12345', 'authenticity token should match')
90
+ start();
91
+ }
92
+ })
93
+ })
94
+
95
+ asyncTest('accepting a string back', function(){
96
+ var review = Review.initialize({title: 'Magic!'})
97
+ review.save({
98
+ url: "/string_back",
99
+ success: function(saved_review, response){
100
+ equals(response, 'some string', 'we should be able to accept a string back');
101
+ start();
102
+ }
103
+ });
104
+ });
105
+
106
+ asyncTest("ID shouldn't be sent", function(){
107
+ var string_back_id;
108
+ AlternateRailsReview.create({},{
109
+ url: "/test_id",
110
+ success: function(saved_review, response){
111
+ equals('ok', response, 'it should not sent an ID');
112
+ start();
113
+ }
114
+ });
115
+ });
116
+
117
+ asyncTest('consistent ID on string back', function(){
118
+ var string_back_id;
119
+ Review.create({},{
120
+ url: "/string_back",
121
+ loading: function(saved_review){
122
+ string_back_id = saved_review.id;
123
+ },
124
+ success: function(saved_review, response){
125
+ equals(saved_review.id, string_back_id, 'ids should be the same');
126
+ start();
127
+ }
128
+ });
129
+ });
130
+
38
131
  asyncTest('pulling something in', 2, function(){
39
132
  var review = Review.initialize({title: 'More Magic!'});
40
133
  review.save(function(){
@@ -77,28 +170,51 @@
77
170
  var review = Review.initialize({title: 'Doomed!'});
78
171
  review.save(function(saved_review){
79
172
  saved_review.update_attributes({title: 'Tennessee'}, function(saved_review){
80
- equals(saved_review.title, 'Tennessee', 'Title should have been updated');
173
+ equals(saved_review.title, 'Updated', 'Title should have been updated');
174
+ start();
175
+ });
176
+ });
177
+ });
178
+
179
+ asyncTest('updating with custom URL', 1, function(){
180
+ var review = FakeReview.initialize({title: 'Doomed!'});
181
+ review.save(function(saved_review){
182
+ saved_review.update_attributes({title: 'Tennessee'}, function(saved_review){
183
+ equals(saved_review.title, 'Updated', 'Title should have been updated');
81
184
  start();
82
185
  });
83
186
  });
84
187
  });
85
188
 
86
- asyncTest('deleting', 1, function(){
189
+ asyncTest('deleting', 2, function(){
87
190
  var review = Review.initialize({title: 'Doomed!'});
88
191
  review.save(function(saved_review){
89
- saved_review.destroy(function(destroyed_review){
192
+ saved_review.destroy(function(destroyed_review, response){
90
193
  equals(destroyed_review.destroyed, true, 'It should have deleted the doc')
194
+ equals(response, 'Deleted OK', 'It should have deleted the doc')
91
195
  start()
92
196
  });
93
197
  });
94
198
  })
199
+
200
+ asyncTest('deleting with custom URL', 2, function(){
201
+ var review = FakeReview.initialize({title: 'Doomed!'});
202
+ review.save(function(saved_review){
203
+ saved_review.destroy(function(destroyed_review, response){
204
+ equals(destroyed_review.destroyed, true, 'It should have deleted the doc')
205
+ equals(response, 'Deleted OK', 'It should have deleted the doc')
206
+ start()
207
+ });
208
+ });
209
+ })
210
+
95
211
 
96
212
  });
97
213
  </script>
98
214
 
99
215
  </head>
100
216
  <body>
101
- <h1 id="qunit-header">Rails Tests (with jQuery)</h1>
217
+ <h1 id="qunit-header">REST Interface Tests (with jQuery)</h1>
102
218
  <h2 id="qunit-banner"></h2>
103
219
  <h2 id="qunit-userAgent"></h2>
104
220
  <ol id="qunit-tests"></ol>