Skip to content

Commit dfb4853

Browse files
author
Johannes J. Schmidt
committedApr 7, 2012
Initial commit with working Todo example
0 parents  commit dfb4853

11 files changed

+13799
-0
lines changed
 

‎README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Super simple version with working Todo application.
2+
3+
# backbone-pouchdb
4+
5+
A Backbone sync adapter for PouchDB (https://raw.github.com/daleharvey/pouchdb).
6+
7+
### Getting started
8+
9+
Check out the Todo example how it works.
10+
Basically, just set a `database` property on your model or collection.

‎backbone-couchdb.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// CouchDB specific adjustments to Backbone
2+
3+
// id attribute
4+
// http://backbonejs.org/docs/backbone.html#section-34
5+
Backbone.Model.prototype.idAttribute = '_id';
6+
7+
8+
// parse models
9+
Backbone.Model.prototype.parse = function(response) {
10+
// adjust rev
11+
if (response.rev) {
12+
response._rev = response.rev;
13+
delete response.rev;
14+
}
15+
16+
// adjust id
17+
if (response.id) {
18+
response._id = response.id;
19+
delete response.id;
20+
}
21+
22+
// remove ok
23+
delete response.ok;
24+
25+
return response;
26+
};
27+
28+
29+
// parse collections
30+
Backbone.Collection.prototype.parse = function(response) {
31+
return response.rows && _.map(response.rows, function(row) { return row.doc });
32+
};
33+
34+
35+
// save models (update rev)
36+
Backbone.Model.prototype.save = (function() {
37+
var oldSave = Backbone.Model.prototype.save;
38+
39+
return function(attributes, options) {
40+
options || (options = {});
41+
42+
var success = options.success;
43+
44+
options.success = function(model, response) {
45+
model.set({ _rev: response._rev }, { silent: true });
46+
47+
if (typeof success === 'function') {
48+
success(model, response);
49+
}
50+
};
51+
52+
oldSave.call(this, attributes, options);
53+
};
54+
})();
55+
56+
57+
// delete models (append rev)
58+
Backbone.Model.prototype.destroy = (function() {
59+
var oldDestroy = Backbone.Model.prototype.destroy;
60+
61+
return function(options) {
62+
options || (options = {});
63+
options.headers || (options.headers = {});
64+
65+
options.headers['If-Match'] = this.get('_rev');
66+
67+
oldDestroy.call(this, options);
68+
};
69+
})();
70+

‎backbone-pouchdb.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Store models in *PouchDB*.
2+
3+
(function() {
4+
// all those could be higher order functions, right?
5+
function get(database, model, options) {
6+
console.log('get');
7+
pouch.open(database, function(err, db) {
8+
db.get(model.id, function(err, doc) {
9+
err === null ? options.success(doc) : options.error(err);
10+
});
11+
});
12+
}
13+
14+
function allDocs(database, options) {
15+
pouch.open(database, function(err, db) {
16+
db.allDocs({ include_docs: true }, function(err, doc) {
17+
err === null ? options.success(doc) : options.error(err);
18+
});
19+
});
20+
}
21+
22+
function write(database, model, options) {
23+
pouch.open(database, function(err, db) {
24+
db.put(model.toJSON(), function(err, resp) {
25+
err === null ? options.success(resp) : options.error(err);
26+
});
27+
});
28+
}
29+
30+
function destroy(database, model, options) {
31+
pouch.open(database, function(err, db) {
32+
db.remove(model.toJSON(), function(err, resp) {
33+
err === null ? options.success(resp) : options.error(err);
34+
});
35+
});
36+
}
37+
38+
Backbone.sync = function(method, model, options) {
39+
40+
var database = model.database || model.collection.database;
41+
42+
switch (method) {
43+
case "read": model.id ? get(database, model, options) : allDocs(database, options); break;
44+
case "create": write(database, model, options); break;
45+
case "update": write(database, model, options); break;
46+
case "delete": destroy(database, model, options); break;
47+
}
48+
};
49+
})();

‎examples/todos/destroy.png

555 Bytes
Loading

‎examples/todos/index.html

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Backbone.js Todos</title>
7+
<link rel="stylesheet" href="todos.css"/>
8+
</head>
9+
10+
<body>
11+
12+
<div id="todoapp">
13+
14+
<header>
15+
<h1>Todos</h1>
16+
<input id="new-todo" type="text" placeholder="What needs to be done?">
17+
</header>
18+
19+
<section id="main">
20+
<input id="toggle-all" type="checkbox">
21+
<label for="toggle-all">Mark all as complete</label>
22+
<ul id="todo-list"></ul>
23+
</section>
24+
25+
<footer>
26+
<a id="clear-completed">Clear completed</a>
27+
<div id="todo-count"></div>
28+
</footer>
29+
30+
</div>
31+
32+
<div id="instructions">
33+
Double-click to edit a todo.
34+
</div>
35+
36+
<div id="credits">
37+
Created by
38+
<br />
39+
<a href="http://jgn.me/">J&eacute;r&ocirc;me Gravel-Niquet</a>.
40+
<br />Rewritten by: <a href="http://addyosmani.github.com/todomvc">TodoMVC</a>.
41+
</div>
42+
43+
<script src="../../vendor/jquery-1.7.1.js"></script>
44+
<script src="../../vendor/underscore-1.3.1.js"></script>
45+
<script src="../../vendor/backbone.js"></script>
46+
<script src="../../vendor/pouchdb.js"></script>
47+
<script src="../../backbone-pouchdb.js"></script>
48+
<script src="../../backbone-couchdb.js"></script>
49+
<script src="todos.js"></script>
50+
51+
<!-- Templates -->
52+
53+
<script type="text/template" id="item-template">
54+
<div class="view">
55+
<input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
56+
<label><%= title %></label>
57+
<a class="destroy"></a>
58+
</div>
59+
<input class="edit" type="text" value="<%= title %>" />
60+
</script>
61+
62+
<script type="text/template" id="stats-template">
63+
<% if (done) { %>
64+
<a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
65+
<% } %>
66+
<div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
67+
</script>
68+
69+
</body>
70+
</html>

‎examples/todos/todos.css

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
html,
2+
body {
3+
margin: 0;
4+
padding: 0;
5+
}
6+
7+
body {
8+
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
9+
line-height: 1.4em;
10+
background: #eeeeee;
11+
color: #333333;
12+
width: 520px;
13+
margin: 0 auto;
14+
-webkit-font-smoothing: antialiased;
15+
}
16+
17+
#todoapp {
18+
background: #fff;
19+
padding: 20px;
20+
margin-bottom: 40px;
21+
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
22+
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
23+
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
24+
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
25+
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
26+
-webkit-border-radius: 0 0 5px 5px;
27+
-moz-border-radius: 0 0 5px 5px;
28+
-ms-border-radius: 0 0 5px 5px;
29+
-o-border-radius: 0 0 5px 5px;
30+
border-radius: 0 0 5px 5px;
31+
}
32+
33+
#todoapp h1 {
34+
font-size: 36px;
35+
font-weight: bold;
36+
text-align: center;
37+
padding: 0 0 10px 0;
38+
}
39+
40+
#todoapp input[type="text"] {
41+
width: 466px;
42+
font-size: 24px;
43+
font-family: inherit;
44+
line-height: 1.4em;
45+
border: 0;
46+
outline: none;
47+
padding: 6px;
48+
border: 1px solid #999999;
49+
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
50+
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
51+
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
52+
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
53+
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
54+
}
55+
56+
#todoapp input::-webkit-input-placeholder {
57+
font-style: italic;
58+
}
59+
60+
#main {
61+
display: none;
62+
}
63+
64+
#todo-list {
65+
margin: 10px 0;
66+
padding: 0;
67+
list-style: none;
68+
}
69+
70+
#todo-list li {
71+
padding: 18px 20px 18px 0;
72+
position: relative;
73+
font-size: 24px;
74+
border-bottom: 1px solid #cccccc;
75+
}
76+
77+
#todo-list li:last-child {
78+
border-bottom: none;
79+
}
80+
81+
#todo-list li.done label {
82+
color: #777777;
83+
text-decoration: line-through;
84+
}
85+
86+
#todo-list .destroy {
87+
position: absolute;
88+
right: 5px;
89+
top: 20px;
90+
display: none;
91+
cursor: pointer;
92+
width: 20px;
93+
height: 20px;
94+
background: url(destroy.png) no-repeat;
95+
}
96+
97+
#todo-list li:hover .destroy {
98+
display: block;
99+
}
100+
101+
#todo-list .destroy:hover {
102+
background-position: 0 -20px;
103+
}
104+
105+
#todo-list li.editing {
106+
border-bottom: none;
107+
margin-top: -1px;
108+
padding: 0;
109+
}
110+
111+
#todo-list li.editing:last-child {
112+
margin-bottom: -1px;
113+
}
114+
115+
#todo-list li.editing .edit {
116+
display: block;
117+
width: 444px;
118+
padding: 13px 15px 14px 20px;
119+
margin: 0;
120+
}
121+
122+
#todo-list li.editing .view {
123+
display: none;
124+
}
125+
126+
#todo-list li .view label {
127+
word-break: break-word;
128+
}
129+
130+
#todo-list li .edit {
131+
display: none;
132+
}
133+
134+
#todoapp footer {
135+
display: none;
136+
margin: 0 -20px -20px -20px;
137+
overflow: hidden;
138+
color: #555555;
139+
background: #f4fce8;
140+
border-top: 1px solid #ededed;
141+
padding: 0 20px;
142+
line-height: 37px;
143+
-webkit-border-radius: 0 0 5px 5px;
144+
-moz-border-radius: 0 0 5px 5px;
145+
-ms-border-radius: 0 0 5px 5px;
146+
-o-border-radius: 0 0 5px 5px;
147+
border-radius: 0 0 5px 5px;
148+
}
149+
150+
#clear-completed {
151+
float: right;
152+
line-height: 20px;
153+
text-decoration: none;
154+
background: rgba(0, 0, 0, 0.1);
155+
color: #555555;
156+
font-size: 11px;
157+
margin-top: 8px;
158+
margin-bottom: 8px;
159+
padding: 0 10px 1px;
160+
cursor: pointer;
161+
-webkit-border-radius: 12px;
162+
-moz-border-radius: 12px;
163+
-ms-border-radius: 12px;
164+
-o-border-radius: 12px;
165+
border-radius: 12px;
166+
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
167+
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
168+
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
169+
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
170+
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
171+
}
172+
173+
#clear-completed:hover {
174+
background: rgba(0, 0, 0, 0.15);
175+
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
176+
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
177+
-ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
178+
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
179+
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
180+
}
181+
182+
#clear-completed:active {
183+
position: relative;
184+
top: 1px;
185+
}
186+
187+
#todo-count span {
188+
font-weight: bold;
189+
}
190+
191+
#instructions {
192+
margin: 10px auto;
193+
color: #777777;
194+
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
195+
text-align: center;
196+
}
197+
198+
#instructions a {
199+
color: #336699;
200+
}
201+
202+
#credits {
203+
margin: 30px auto;
204+
color: #999;
205+
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
206+
text-align: center;
207+
}
208+
209+
#credits a {
210+
color: #888;
211+
}

‎examples/todos/todos.js

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// An example Backbone application contributed by
2+
// [Jérôme Gravel-Niquet](http://jgn.me/).
3+
// This demo shows the use of the
4+
// [PouchDB adapter](backbone-pouchdb.js)
5+
// to persist Backbone models within your browser
6+
// and to be able to replicate the data to and from a server.
7+
8+
// Load the application once the DOM is ready, using `jQuery.ready`:
9+
$(function(){
10+
11+
// Todo Model
12+
// ----------
13+
14+
// Our basic **Todo** model has `title`, `order`, and `done` attributes.
15+
var Todo = Backbone.Model.extend({
16+
17+
// Default attributes for the todo item.
18+
defaults: function() {
19+
return {
20+
title: "empty todo...",
21+
order: Todos.nextOrder(),
22+
done: false
23+
};
24+
},
25+
26+
// Ensure that each todo created has `title`.
27+
initialize: function() {
28+
if (!this.get("title")) {
29+
this.set({"title": this.defaults.title});
30+
}
31+
},
32+
33+
// Toggle the `done` state of this todo item.
34+
toggle: function() {
35+
this.save({done: !this.get("done")});
36+
},
37+
38+
// Remove this Todo from *PouchDB* and delete its view.
39+
clear: function() {
40+
this.destroy();
41+
}
42+
43+
});
44+
45+
// Todo Collection
46+
// ---------------
47+
48+
// The collection of todos is backed by *PouchDB* instead of a remote
49+
// server.
50+
var TodoList = Backbone.Collection.extend({
51+
52+
// Reference to this collection's model.
53+
model: Todo,
54+
55+
// Save all of the todo items in the `"todos-backbone"` database.
56+
database: 'todos-backbone',
57+
58+
// Filter down the list of all todo items that are finished.
59+
done: function() {
60+
return this.filter(function(todo){ return todo.get('done'); });
61+
},
62+
63+
// Filter down the list to only todo items that are still not finished.
64+
remaining: function() {
65+
return this.without.apply(this, this.done());
66+
},
67+
68+
// We keep the Todos in sequential order, despite being saved by unordered
69+
// GUID in the database. This generates the next order number for new items.
70+
nextOrder: function() {
71+
if (!this.length) return 1;
72+
return this.last().get('order') + 1;
73+
},
74+
75+
// Todos are sorted by their original insertion order.
76+
comparator: function(todo) {
77+
return todo.get('order');
78+
}
79+
80+
});
81+
82+
// Create our global collection of **Todos**.
83+
var Todos = new TodoList;
84+
85+
// Todo Item View
86+
// --------------
87+
88+
// The DOM element for a todo item...
89+
var TodoView = Backbone.View.extend({
90+
91+
//... is a list tag.
92+
tagName: "li",
93+
94+
// Cache the template function for a single item.
95+
template: _.template($('#item-template').html()),
96+
97+
// The DOM events specific to an item.
98+
events: {
99+
"click .toggle" : "toggleDone",
100+
"dblclick .view" : "edit",
101+
"click a.destroy" : "clear",
102+
"keypress .edit" : "updateOnEnter",
103+
"blur .edit" : "close"
104+
},
105+
106+
// The TodoView listens for changes to its model, re-rendering. Since there's
107+
// a one-to-one correspondence between a **Todo** and a **TodoView** in this
108+
// app, we set a direct reference on the model for convenience.
109+
initialize: function() {
110+
this.model.bind('change', this.render, this);
111+
this.model.bind('destroy', this.remove, this);
112+
},
113+
114+
// Re-render the titles of the todo item.
115+
render: function() {
116+
this.$el.html(this.template(this.model.toJSON()));
117+
this.$el.toggleClass('done', this.model.get('done'));
118+
this.input = this.$('.edit');
119+
return this;
120+
},
121+
122+
// Toggle the `"done"` state of the model.
123+
toggleDone: function() {
124+
this.model.toggle();
125+
},
126+
127+
// Switch this view into `"editing"` mode, displaying the input field.
128+
edit: function() {
129+
this.$el.addClass("editing");
130+
this.input.focus();
131+
},
132+
133+
// Close the `"editing"` mode, saving changes to the todo.
134+
close: function() {
135+
var value = this.input.val();
136+
if (!value) this.clear();
137+
this.model.save({title: value});
138+
this.$el.removeClass("editing");
139+
},
140+
141+
// If you hit `enter`, we're through editing the item.
142+
updateOnEnter: function(e) {
143+
if (e.keyCode == 13) this.close();
144+
},
145+
146+
// Remove the item, destroy the model.
147+
clear: function() {
148+
this.model.clear();
149+
}
150+
151+
});
152+
153+
// The Application
154+
// ---------------
155+
156+
// Our overall **AppView** is the top-level piece of UI.
157+
var AppView = Backbone.View.extend({
158+
159+
// Instead of generating a new element, bind to the existing skeleton of
160+
// the App already present in the HTML.
161+
el: $("#todoapp"),
162+
163+
// Our template for the line of statistics at the bottom of the app.
164+
statsTemplate: _.template($('#stats-template').html()),
165+
166+
// Delegated events for creating new items, and clearing completed ones.
167+
events: {
168+
"keypress #new-todo": "createOnEnter",
169+
"click #clear-completed": "clearCompleted",
170+
"click #toggle-all": "toggleAllComplete"
171+
},
172+
173+
// At initialization we bind to the relevant events on the `Todos`
174+
// collection, when items are added or changed. Kick things off by
175+
// loading any preexisting todos that might be saved in *PouchDB*.
176+
initialize: function() {
177+
178+
this.input = this.$("#new-todo");
179+
this.allCheckbox = this.$("#toggle-all")[0];
180+
181+
Todos.bind('add', this.addOne, this);
182+
Todos.bind('reset', this.addAll, this);
183+
Todos.bind('all', this.render, this);
184+
185+
this.footer = this.$('footer');
186+
this.main = $('#main');
187+
188+
Todos.fetch();
189+
},
190+
191+
// Re-rendering the App just means refreshing the statistics -- the rest
192+
// of the app doesn't change.
193+
render: function() {
194+
var done = Todos.done().length;
195+
var remaining = Todos.remaining().length;
196+
197+
if (Todos.length) {
198+
this.main.show();
199+
this.footer.show();
200+
this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
201+
} else {
202+
this.main.hide();
203+
this.footer.hide();
204+
}
205+
206+
this.allCheckbox.checked = !remaining;
207+
},
208+
209+
// Add a single todo item to the list by creating a view for it, and
210+
// appending its element to the `<ul>`.
211+
addOne: function(todo) {
212+
var view = new TodoView({model: todo});
213+
this.$("#todo-list").append(view.render().el);
214+
},
215+
216+
// Add all items in the **Todos** collection at once.
217+
addAll: function() {
218+
Todos.each(this.addOne);
219+
},
220+
221+
// If you hit return in the main input field, create new **Todo** model,
222+
// persisting it to *PouchDB*.
223+
createOnEnter: function(e) {
224+
if (e.keyCode != 13) return;
225+
if (!this.input.val()) return;
226+
227+
Todos.create({title: this.input.val()});
228+
this.input.val('');
229+
},
230+
231+
// Clear all done todo items, destroying their models.
232+
clearCompleted: function() {
233+
_.each(Todos.done(), function(todo){ todo.clear(); });
234+
return false;
235+
},
236+
237+
toggleAllComplete: function () {
238+
var done = this.allCheckbox.checked;
239+
Todos.each(function (todo) { todo.save({'done': done}); });
240+
}
241+
242+
});
243+
244+
// Finally, we kick things off by creating the **App**.
245+
var App = new AppView;
246+
247+
});

‎vendor/backbone.js

+1,431
Large diffs are not rendered by default.

‎vendor/jquery-1.7.1.js

+9,266
Large diffs are not rendered by default.

‎vendor/pouchdb.js

+1,446
Large diffs are not rendered by default.

‎vendor/underscore-1.3.1.js

+999
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.