eyeballs 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +15 -0
- data/README.md +293 -0
- data/Rakefile +45 -0
- data/bin/eyeballs +9 -0
- data/config.ru +2 -0
- data/dist/jquery-1.4.2.min.js +154 -0
- data/dist/jquery.livequery.js +226 -0
- data/dist/mustache.js +324 -0
- data/eyeballs.gemspec +86 -0
- data/eyeballs.js.gemspec +83 -0
- data/lib/eyeballs.rb +11 -0
- data/lib/eyeballs/app_detector.rb +29 -0
- data/lib/eyeballs/app_generator.rb +30 -0
- data/lib/eyeballs/cli.rb +14 -0
- data/lib/eyeballs/controller_generator.rb +26 -0
- data/lib/eyeballs/model_generator.rb +26 -0
- data/lib/eyeballs/scaffold_generator.rb +66 -0
- data/spec/app_generator_spec.rb +51 -0
- data/spec/controller_generator_spec.rb +22 -0
- data/spec/model_generator_spec.rb +23 -0
- data/spec/rack_app_detector_spec.rb +25 -0
- data/spec/scaffold_generator_spec.rb +42 -0
- data/spec/spec_helper.rb +42 -0
- data/src/jquery.o_O.couchdb.js +107 -0
- data/src/jquery.o_O.dom.js +37 -0
- data/src/jquery.o_O.js +120 -0
- data/src/jquery.o_O.rails.js +68 -0
- data/src/o_O.js +286 -0
- data/src/o_O.localstorage.js +60 -0
- data/templates/app_root/index.html +26 -0
- data/templates/controller.js +3 -0
- data/templates/model.js +3 -0
- data/templates/scaffold_controller.js +75 -0
- data/templates/scaffold_edit.html.mustache +13 -0
- data/templates/scaffold_index.html +47 -0
- data/templates/scaffold_partial.html.mustache +12 -0
- data/test/unit/qunit.css +119 -0
- data/test/unit/qunit.js +1069 -0
- data/test/unit/test_controller.html +137 -0
- data/test/unit/test_dom.html +81 -0
- data/test/unit/test_dom_with_callbacks.html +121 -0
- data/test/unit/test_form.html +42 -0
- data/test/unit/test_localstorage.html +79 -0
- data/test/unit/test_model.html +136 -0
- data/test/unit/test_model_with_callbacks.html +118 -0
- data/test/unit/test_rails.html +97 -0
- metadata +117 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Eyeballs::ModelGenerator do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
create_test_root
|
7
|
+
FileUtils.cd(test_root)
|
8
|
+
Eyeballs::ModelGenerator.start(["Paul"])
|
9
|
+
end
|
10
|
+
let(:model_file) { file('app', 'models', 'paul.js') }
|
11
|
+
|
12
|
+
it "should create my model" do
|
13
|
+
model_file.should exist
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should create my file" do
|
17
|
+
model_file.read.should include("o_O('Paul', function(paul){")
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
remove_test_root
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Eyeballs::AppGenerator do
|
4
|
+
describe "generate a simple app" do
|
5
|
+
before(:all) do
|
6
|
+
create_test_root
|
7
|
+
FileUtils.mkdir(test_root + '/public')
|
8
|
+
FileUtils.cd(test_root)
|
9
|
+
Eyeballs::AppGenerator.start('test')
|
10
|
+
end
|
11
|
+
let(:app_dir) { file('public', 'javascripts', 'app') }
|
12
|
+
|
13
|
+
it "should create the test root" do
|
14
|
+
File.exists?(test_root).should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create the test app in the public javascripts directory" do
|
18
|
+
app_dir.should exist
|
19
|
+
end
|
20
|
+
|
21
|
+
after(:all) do
|
22
|
+
remove_test_root
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Eyeballs::ModelGenerator do
|
4
|
+
before(:all) do
|
5
|
+
create_test_root
|
6
|
+
FileUtils.cd(test_root)
|
7
|
+
Eyeballs::ScaffoldGenerator.start(["Review", "title:string", "content:text"])
|
8
|
+
end
|
9
|
+
let(:model_file) { file('app', 'models', 'review.js') }
|
10
|
+
let(:controller_file) { file('app', 'controllers', 'reviews_controller.js') }
|
11
|
+
let(:html_file) { file('reviews.html') }
|
12
|
+
let(:edit_view_file) { file('app', 'views', 'reviews', 'edit.html.mustache') }
|
13
|
+
let(:partial_view_file) { file('app', 'views', 'reviews', '_review.html.mustache') }
|
14
|
+
|
15
|
+
it "should create my files" do
|
16
|
+
model_file.should exist
|
17
|
+
controller_file.should exist
|
18
|
+
html_file.should exist
|
19
|
+
edit_view_file.should exist
|
20
|
+
partial_view_file.should exist
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should fill out the model file" do
|
24
|
+
model_file.read.should include("o_O('Review', function(review){")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should fill out the controller file" do
|
28
|
+
controller_file.read.should include("o_O('ReviewsController', {")
|
29
|
+
controller_file.read.should include("Review.all(function(reviews){")
|
30
|
+
controller_file.read.should include("for(var id in reviews)")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should fill out the html file" do
|
34
|
+
html_file.read.should include("Reviews")
|
35
|
+
html_file.read.should include(%Q[<input type="text" name="title" value="" data-attribute="title">])
|
36
|
+
html_file.read.should include(%Q[<textarea name="content" data-attribute="content"></textarea>])
|
37
|
+
end
|
38
|
+
|
39
|
+
after(:all) do
|
40
|
+
remove_test_root
|
41
|
+
end
|
42
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'eyeballs'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'ruby-debug'
|
4
|
+
Debugger.settings[:autoeval] = true
|
5
|
+
|
6
|
+
def create_test_root
|
7
|
+
FileUtils.mkdir_p(test_root)
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove_test_root
|
11
|
+
FileUtils.rm_r(test_root)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_command(cmd)
|
15
|
+
system("cd #{test_root}; ../../bin/#{cmd}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def file(*parts)
|
19
|
+
FileChecker.new([test_root] + parts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_root
|
23
|
+
File.join(File.dirname(File.expand_path(__FILE__)), 'test_root')
|
24
|
+
end
|
25
|
+
|
26
|
+
class FileChecker
|
27
|
+
def initialize(parts)
|
28
|
+
@file = File.join(parts)
|
29
|
+
end
|
30
|
+
|
31
|
+
def exist?
|
32
|
+
File.exists?(@file)
|
33
|
+
end
|
34
|
+
|
35
|
+
def read
|
36
|
+
File.read(@file)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
@file
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
// We're relaxing, baby!
|
2
|
+
o_O.couchdb = {
|
3
|
+
all: function(model, callback){
|
4
|
+
|
5
|
+
var database = o_O.model.adapter.settings.database;
|
6
|
+
|
7
|
+
var ddoc_id = ['_design', model.table_name].join('/')
|
8
|
+
var view_name = model.table_name + '/all'
|
9
|
+
var get_all_documents = function(result){
|
10
|
+
var documents = result.rows;
|
11
|
+
var all_documents = [];
|
12
|
+
for(var i = 0; i < documents.length; i++)
|
13
|
+
{
|
14
|
+
var document = documents[i];
|
15
|
+
document.value.id = document.id
|
16
|
+
all_documents.push(document.value);
|
17
|
+
}
|
18
|
+
if(typeof callback === 'function')
|
19
|
+
{
|
20
|
+
callback(all_documents);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
$.couch.db(database).view(view_name, {
|
24
|
+
success:get_all_documents,
|
25
|
+
error: function(){
|
26
|
+
// create the doc
|
27
|
+
var design_doc = {
|
28
|
+
"_id": ddoc_id,
|
29
|
+
"language": "javascript",
|
30
|
+
"views": {
|
31
|
+
"all": {
|
32
|
+
"map": "function(doc) {\n if(doc.model_name === \
|
33
|
+
'" + model.model_name + "')\n emit(null, doc);\n}"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
$.couch.db(database).saveDoc(design_doc, {success:function(){
|
38
|
+
$.couch.db(database).view(view_name, {
|
39
|
+
success: get_all_documents,
|
40
|
+
error: function(){
|
41
|
+
if(typeof console === 'object')
|
42
|
+
{
|
43
|
+
console.log("Design Doc couldn't be written");
|
44
|
+
}
|
45
|
+
}
|
46
|
+
})
|
47
|
+
}})
|
48
|
+
}
|
49
|
+
});
|
50
|
+
},
|
51
|
+
destroy: function(object, callback){
|
52
|
+
var database = o_O.model.adapter.settings.database;
|
53
|
+
this.find(object, object.id, function(found){
|
54
|
+
var url = '/' + o_O.model.adapter.settings.database + '/' + object.id;
|
55
|
+
$.ajax({
|
56
|
+
type: 'DELETE',
|
57
|
+
url: '/' + database + '/' + object.id + '?rev=' + found._rev,
|
58
|
+
success: function(data){
|
59
|
+
var response = JSON.parse(data);
|
60
|
+
if(typeof callback === 'function')
|
61
|
+
{
|
62
|
+
callback(response);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
});
|
66
|
+
});
|
67
|
+
},
|
68
|
+
save: function(object, callback)
|
69
|
+
{
|
70
|
+
var database = o_O.model.adapter.settings.database;
|
71
|
+
var object_to_save = {}
|
72
|
+
|
73
|
+
// we only want the attributes, not all extra model data
|
74
|
+
for(var i = 0; i < object.attributes.length; i++)
|
75
|
+
{
|
76
|
+
object_to_save[object.attributes[i]] = object[object.attributes[i]];
|
77
|
+
}
|
78
|
+
object_to_save._id = object.id;
|
79
|
+
object_to_save.model_name = object.model_name;
|
80
|
+
$.couch.db(database).saveDoc(object_to_save, {
|
81
|
+
success:function(response){
|
82
|
+
object_to_save._rev = response.rev;
|
83
|
+
object_to_save._id = response.id;
|
84
|
+
object_to_save.id = response.id;
|
85
|
+
if(typeof callback === 'function')
|
86
|
+
{
|
87
|
+
callback(object_to_save);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
});
|
91
|
+
},
|
92
|
+
table: function(object)
|
93
|
+
{
|
94
|
+
|
95
|
+
},
|
96
|
+
find: function(model, id, callback)
|
97
|
+
{
|
98
|
+
var database = o_O.model.adapter.settings.database;
|
99
|
+
$.couch.db(database).openDoc(id, {success: function(document){
|
100
|
+
document.id = id;
|
101
|
+
if(typeof callback === 'function')
|
102
|
+
{
|
103
|
+
callback(document);
|
104
|
+
}
|
105
|
+
}});
|
106
|
+
}
|
107
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
o_O.dom = {
|
2
|
+
save: function(object, callback){
|
3
|
+
if(typeof callback === 'function')
|
4
|
+
{
|
5
|
+
callback(object);
|
6
|
+
}
|
7
|
+
return object;
|
8
|
+
},
|
9
|
+
all: function(model, callback){
|
10
|
+
var output = [];
|
11
|
+
$('[data-model=' + model.model_name + ']').each(function(){
|
12
|
+
output.push(model.initialize(o_O.find_attributes($(this), function(field) { return field.text();})))
|
13
|
+
})
|
14
|
+
if(typeof callback === 'function')
|
15
|
+
{
|
16
|
+
callback(output);
|
17
|
+
}
|
18
|
+
},
|
19
|
+
destroy: function(object, callback){
|
20
|
+
$('[data-model=' + object.model_name +'][data-id= ' + object.id + ']').remove()
|
21
|
+
if(typeof callback === 'function')
|
22
|
+
{
|
23
|
+
callback(object);
|
24
|
+
}
|
25
|
+
},
|
26
|
+
find: function(model, id, callback){
|
27
|
+
var template = $('[data-model=' + model.model_name + '][data-id=' + id + ']');
|
28
|
+
var new_object;
|
29
|
+
if(typeof callback === 'function')
|
30
|
+
{
|
31
|
+
new_object = model.initialize(o_O.find_attributes(template, function(field){return field.text();}));
|
32
|
+
new_object.id = id;
|
33
|
+
callback(new_object);
|
34
|
+
}
|
35
|
+
return new_object;
|
36
|
+
}
|
37
|
+
}
|
data/src/jquery.o_O.js
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
o_O.controller = {
|
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
|
+
|
11
|
+
var controller_name = controller_name.replace('Controller', '').toLowerCase();
|
12
|
+
var controller = controller;
|
13
|
+
|
14
|
+
$(function(){
|
15
|
+
for(var action in controller)
|
16
|
+
{
|
17
|
+
var selector = '[data-controller=' + controller_name + '][data-action=' + action + ']';
|
18
|
+
$(selector).livequery(function(){
|
19
|
+
var element = $(this);
|
20
|
+
$(this).bind(action_event(element), controller[element.attr('data-action')]);
|
21
|
+
if(!($(this).attr('data-default')))
|
22
|
+
{
|
23
|
+
$(this).bind(action_event(element), function(){ return false; });
|
24
|
+
}
|
25
|
+
})
|
26
|
+
}
|
27
|
+
$('[data-bind]').livequery(function(){
|
28
|
+
var binders = $(this).attr('data-bind').match(/[\+]?(\s+)?[^ :]?[: ]?[^ \/]+[ \/]+[^ ;]+[ ;]?/g)
|
29
|
+
if(binders != null && binders.length > 0)
|
30
|
+
{
|
31
|
+
for(i = 0; i < binders.length; i++)
|
32
|
+
{
|
33
|
+
var rule = binders[i];
|
34
|
+
var parts = rule.match(/([\+])?(\s+)?(([^ :]+)([: ]+))?([^ \/]+)[ \/]+([^ ;]+)[ ;]?/)
|
35
|
+
var default_bit = parts[1];
|
36
|
+
var this_action_event = parts[4];
|
37
|
+
if(this_action_event === undefined)
|
38
|
+
{
|
39
|
+
this_action_event = ($(this).is('form')) ? 'submit' : 'click';
|
40
|
+
}
|
41
|
+
var this_controller_name = parts[6];
|
42
|
+
var this_action = parts[7];
|
43
|
+
if(this_controller_name == controller_name)
|
44
|
+
{
|
45
|
+
$(this).bind(this_action_event,controller[this_action]);
|
46
|
+
if(default_bit != '+')
|
47
|
+
{
|
48
|
+
$(this).bind(this_action_event, function(){ return false; });
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
});
|
54
|
+
});
|
55
|
+
return controller;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
o_O.find_attributes = function(template, callback){
|
60
|
+
var object = {};
|
61
|
+
for(i = 0; i<template.find('[data-attribute]').length; i++)
|
62
|
+
{
|
63
|
+
field = $(template.find('[data-attribute]')[i]);
|
64
|
+
object[field.attr('data-attribute')] = callback(field);
|
65
|
+
}
|
66
|
+
return object;
|
67
|
+
}
|
68
|
+
|
69
|
+
o_O.params = function(form){
|
70
|
+
return o_O.find_attributes(form, function(field){
|
71
|
+
if(field.is('[type=radio]'))
|
72
|
+
{
|
73
|
+
return $('[data-attribute=' + field.attr('data-attribute') + ']:checked').val()
|
74
|
+
}
|
75
|
+
else
|
76
|
+
{
|
77
|
+
return field.val();
|
78
|
+
}
|
79
|
+
});
|
80
|
+
}
|
81
|
+
|
82
|
+
o_O.render = function(template, data, options){
|
83
|
+
o_O.get_template(template, data, function(data, template){
|
84
|
+
var rendered = Mustache.to_html(template, data);
|
85
|
+
if(typeof options === 'object')
|
86
|
+
{
|
87
|
+
if(options.append)
|
88
|
+
{
|
89
|
+
$(options.append).append(rendered);
|
90
|
+
}
|
91
|
+
if(options.prepend)
|
92
|
+
{
|
93
|
+
$(options.prepend).prepend(rendered);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
});
|
97
|
+
}
|
98
|
+
|
99
|
+
o_O.get_template = function(template, data, callback){
|
100
|
+
if(o_O.templates[template])
|
101
|
+
{
|
102
|
+
callback(data, o_O.templates[template]);
|
103
|
+
}
|
104
|
+
else
|
105
|
+
{
|
106
|
+
var url;
|
107
|
+
if(o_O.config.template_path)
|
108
|
+
{
|
109
|
+
url = o_O.config.template_path + '/';
|
110
|
+
}
|
111
|
+
else
|
112
|
+
{
|
113
|
+
url = 'app/views/'
|
114
|
+
}
|
115
|
+
$.get(url + template + '.html.mustache', function(response){
|
116
|
+
o_O.templates[template] = response;
|
117
|
+
callback(data, response);
|
118
|
+
});
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
// REST & Rails, woop!
|
2
|
+
o_O.rails = {
|
3
|
+
all: function(model, callback){
|
4
|
+
$.get('/' + model.table_name, function(response){
|
5
|
+
var documents = JSON.parse(response);
|
6
|
+
if(typeof callback === 'function')
|
7
|
+
{
|
8
|
+
callback(documents);
|
9
|
+
}
|
10
|
+
})
|
11
|
+
},
|
12
|
+
destroy: function(object, callback){
|
13
|
+
object.destroyed = true;
|
14
|
+
$.ajax({
|
15
|
+
type: 'DELETE',
|
16
|
+
url: '/' + object.table_name + '/' + object.id,
|
17
|
+
success: function(){
|
18
|
+
if(typeof callback === 'function')
|
19
|
+
{
|
20
|
+
callback(object);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
})
|
24
|
+
},
|
25
|
+
save: function(object, callback)
|
26
|
+
{
|
27
|
+
var object_to_save = {}
|
28
|
+
for(var i = 0; i < object.attributes.length; i++)
|
29
|
+
{
|
30
|
+
object_to_save[object.attributes[i]] = object[object.attributes[i]];
|
31
|
+
}
|
32
|
+
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];
|
37
|
+
}
|
38
|
+
object_to_save.new_record = false;
|
39
|
+
if(typeof callback === 'function')
|
40
|
+
{
|
41
|
+
callback(object_to_save);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
if(object.new_record)
|
45
|
+
{
|
46
|
+
$.post('/' + object.table_name, object_to_save, respond);
|
47
|
+
}
|
48
|
+
else
|
49
|
+
{
|
50
|
+
$.ajax({
|
51
|
+
type: 'PUT',
|
52
|
+
url: '/' + object.table_name + '/' + object.id,
|
53
|
+
success: respond
|
54
|
+
})
|
55
|
+
}
|
56
|
+
},
|
57
|
+
find: function(model, id, callback)
|
58
|
+
{
|
59
|
+
$.get('/' + model.table_name + '/' + id, function(response){
|
60
|
+
var retrieved_object = JSON.parse(response);
|
61
|
+
if(typeof callback === 'function')
|
62
|
+
{
|
63
|
+
retrieved_object.new_record = false;
|
64
|
+
callback(retrieved_object);
|
65
|
+
}
|
66
|
+
})
|
67
|
+
}
|
68
|
+
}
|