Google apps engine + KnockoutJs + REST
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.