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>
                    &nbsp;
                    <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">&times;</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.