Simple example, demonstates how to create app with knockoutjs that will use RESTfull google apps engine service.
main.py:
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.api import mail
from google.appengine.api import users
from django.utils import simplejson
class ServerModel(db.Model):
name = db.StringProperty(required=True)
active = db.BooleanProperty()
host = db.StringProperty(required=True)
class ServerHandler(webapp.RequestHandler):
def get(self, id = None):
self.response.headers['Content-Type'] = 'application/json'
servers = ServerModel.all()
data = []
for server in servers:
data.append({
'id': server.key().id(),
'active': server.active,
'name': server.name,
'host': server.host,
})
self.response.out.write(simplejson.dumps(data))
def post(self, id = None):
self.response.headers['Content-Type'] = 'application/json'
args = simplejson.loads(self.request.body)
server = ServerModel(
active = args.get('active'),
name = args.get('name'),
host = args.get('host'),
)
server.save()
self.response.out.write(simplejson.dumps({
'id': server.key().id(),
}))
def put(self, id = None):
self.response.headers['Content-Type'] = 'application/json'
args = simplejson.loads(self.request.body)
server = ServerModel.get_by_id(args.get('id'))
server.active = args.get('active')
server.name = args.get('name')
server.host = args.get('host')
self.response.out.write('')
def delete(self, id = None):
self.response.headers['Content-Type'] = 'application/json'
args = simplejson.loads(self.request.body)
server = ServerModel.get_by_id(args.get('id'))
server.delete()
self.response.out.write('')
def handle_exception(self, exception, debug_mode):
self.error(500);
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(exception.message)
class MainHandler(webapp.RequestHandler):
def get(self):
values = {}
self.response.out.write(template.render('templates/index.html', values))
def main():
application = webapp.WSGIApplication([
('/', MainHandler),
(r'/servers(?:/(.*))?', ServerHandler)
], debug=True)
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
Note, that there is no validations etc, but hey this is just example.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Knock</title>
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.3.0/bootstrap.min.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="text/javascript"
src="http://cloud.github.com/downloads/SteveSanderson/knockout/jquery.tmpl.js"></script>
<script type="text/javascript"
src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-1.2.1.js"></script>
<script type="text/javascript" src="http://twitter.github.com/bootstrap/1.3.0/bootstrap-modal.js"></script>
<style type="text/css">
html, body {
background-color: #eee;
}
.content {
margin: 40px auto;
background-color: #fff;
padding: 20px;
box-shadow: 0 1px 2px #ccc;
}
</style>
</head>
<body>
<div class="topbar">
<div class="fill">
<div class="container">
<a class="brand" href="/">Knock</a>
</div>
</div>
</div>
<div class="container">
<div class="content">
<table id="servers">
<thead>
<tr>
<th>Active</th>
<th>Name</th>
<th>Host</th>
<th>
<button data-bind="click: add" data-controls-modal="serverForm" data-backdrop="true"
data-keyboard="true" class="btn small primary">Add server
</button>
</th>
</tr>
</thead>
<tbody data-bind="template: {name:'serverTemplate', foreach: servers}"></tbody>
</table>
<script type="text/x-jquery-tmpl" id="serverTemplate">
<tr>
<td data-bind="html: active"></td>
<td data-bind="html: name"></td>
<td data-bind="html: host"></td>
<td>
<button data-bind="click: function(){ ServerForm.peak(this) }" data-controls-modal="serverForm" data-backdrop="true"
data-keyboard="true" class="btn small primary">Edit
</button>
<button class="btn small secondary" data-bind="click: function(){ ServerHandler.delete(this) }">Remove</button>
</td>
</tr>
</script>
<div id="serverForm" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close">×</a>
<h3>Add/Edit serve</h3>
</div>
<div class="modal-body">
<form action="">
<input data-bind="value: id" type="hidden" id="id" name="id"/>
<div class="clearfix">
<label for="active">Active</label>
<div class="input">
<ul class="inputs-list">
<li>
<label>
<input data-bind="checked: active" type="checkbox"
value="true"
name="active" id="active">
<span>Check this server</span>
</label>
</li>
</ul>
</div>
</div>
<div class="clearfix">
<label for="name">Name</label>
<div class="input">
<input data-bind="value: name" type="text" size="30" name="name"
id="name"
class="xlarge">
</div>
</div>
<div class="clearfix">
<label for="host">Host</label>
<div class="input">
<input data-bind="value: host" type="text" size="30" name="host"
id="host"
class="xlarge">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button data-bind="click: save" class="btn primary">Save</button>
</div>
</div>
</div>
</div>
<!-- VIEW MODEL -->
<script type="text/javascript">
var ServerHandler = {
save: function(server) {
var type = server.id() == 0 ? 'POST' : 'PUT';
jQuery.ajax({
context: server,
data: ko.toJSON(server),
dataTypeString: 'json',
type: type,
url: '/servers',
success: function(data, textStatus, jqXHR) {
if(this.id() == 0) {
this.id(data.id);
ServerCollection.servers.push(this);
} else {
var server = ServerCollection.getById(this.id());
server.name(this.name());
server.active(this.active());
server.host(this.host());
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.log('error',textStatus);
//TODO: provide app notification
}
});
},
delete: function(server) {
jQuery.ajax({
context: server,
data: ko.toJSON(server),
dataTypeString: 'json',
type: 'DELETE',
url: '/servers',
success: function(data, textStatus, jqXHR) {
var id = this.id();
ServerCollection.servers.remove(function(item) { return item.id() == id });
},
error: function(jqXHR, textStatus, errorThrown) {
console.log('error',textStatus);
//TODO: provide app notification
}
});
}
};
var ServerModel = function(id, name, active, host) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.active = ko.observable(active);
this.host = ko.observable(host);
this.remove = function() {
}
this.save = function() {
if (this.id() == 0) ServerCollection.items.push(this);
console.log('ajax save for server');
}
this.fillForm = function() {
serverForm.id(this.id());
serverForm.name(this.name());
serverForm.active(this.active());
serverForm.host(this.host());
}
}
var ServerCollection = {
servers: ko.observableArray([]),
getById: function(id) {
for (i = 0; i < this.servers().length; i++)
if (this.servers()[i].id() == id) return this.servers()[i];
return false;
},
add: function() {
ServerForm.peak(new ServerModel(0, '', true, ''));
},
read: function() {
jQuery.ajax({
dataTypeString: 'json',
type: 'GET',
url: '/servers',
success: function(data, textStatus, jqXHR) {
jQuery.each(data, function(index, item){
ServerCollection.servers.push(new ServerModel(item.id, item.name, item.active, item.host));
});
},
error: function(jqXHR, textStatus, errorThrown) {
console.log('error',textStatus);
//TODO: provide app notification
}
});
}
}
ko.applyBindings(ServerCollection, $('#servers').get(0));
ServerCollection.read();
var ServerForm = {
id: ko.observable(0),
name: ko.observable(''),
active: ko.observable(false),
host: ko.observable(''),
peak: function(server) {
this.id(server.id());
this.name(server.name());
this.active(server.active());
this.host(server.host());
},
save: function() {
ServerHandler.save(new ServerModel(this.id(), this.name(), this.active(), this.host()));
$('#serverForm').modal('hide');
}
}
ko.applyBindings(ServerForm, $('#serverForm').get(0));
</script>
</body>
</html>
Note that for simplify, there is two models rather that just one like in knockoutjs examples, but u can do whatever way u want, also there is no validations etc.