Andriy Kornatskyy avatar Andriy Kornatskyy committed 29e0caf

Added AJAX and JSON section to tutorial.

Comments (0)

Files changed (11)

demos/guestbook/app.py

+""" ``app`` module.
+"""
+
 from wheezy.http import WSGIApplication
 from wheezy.web.middleware import bootstrap_defaults
 from wheezy.web.middleware import path_routing_middleware_factory
 from config import options
 from urls import all_urls
 
+
 main = WSGIApplication([
             bootstrap_defaults(url_mapping=all_urls),
             path_routing_middleware_factory

demos/guestbook/config.py

+""" ``config`` module.
+"""
 
 import sqlite3
 

demos/guestbook/models.py

+""" ``models`` module.
+"""
+
 from datetime import datetime
 
 from wheezy.core.comp import u

demos/guestbook/repository.py

+""" ``repository`` module.
+"""
+
 from models import Greeting
 
 

demos/guestbook/static/site.js

+
+String.prototype.format = function() {
+    var args = arguments;
+    return this.replace(/\{\d+\}/g, function(capture) {
+        return args[capture.match(/\d+/)];
+    });
+}
+
+function JSONForm(data, form) {
+    $(form).prev('span.error-message').remove();
+    $('span.error', form).remove();
+    $('.error', form).removeClass('error');
+    $.each(data.errors, function(key, value) {
+        if (key == '__ERROR__') {
+            form.before('<span class="error-message">{0}</span>'.format(
+                    value.pop()))
+        }
+        else {
+            key = key.replace(/_/g, '-');
+            $('label[for="{0}"]'.format(key), form).addClass('error')
+            var field = $('#' + key, form);
+            field.addClass('error');
+            field.after('<span class="error">{0}</span>'.format(
+                value.pop()));
+        }
+    });
+}
+
+function ajaxForm(selector, dataType) {
+    if (!dataType) dataType = 'json'
+    $(selector || 'input[type="submit"]').live('click', function(e) {
+        submit = $(this);
+        submit.attr('disabled', 'disabled');
+        var form = submit.parents('form:first');
+        var data = null;
+        if (this.name) {
+            data = form.serializeArray();
+            data.push({name: this.name, value: ''});
+            data = $.param(data);
+        }
+        else
+            data = form.serialize();
+        $.ajax({
+            type: form.attr('method') || 'get',
+            url: form.attr('action'),
+            data: data,
+            dataType: dataType,
+            success: function(data, textStatus, jqXHR) {
+                if (jqXHR.status == 207) {
+                    window.location.replace(jqXHR.getResponseHeader('Location'));
+                } else if (data.see_other) {
+                    window.location.replace(data.see_other);
+                } else if (dataType == 'json'){
+                    submit.removeAttr('disabled');
+                    JSONForm(data, form);
+                }
+            }
+        });
+        return false;
+    });
+}

demos/guestbook/templates/add.html

     </p>
 </form>
 <a href="${path_for('list')}">Back</a>
+<script type="text/javascript">
+    $(document).ready(function() {
+        ajaxForm();
+    })
+</script>

demos/guestbook/templates/layout.html

         <title>Guestbook</title>
         <link href="${path_for('static', path='site.css')}"
             type="text/css" rel="stylesheet" />
+        <script type="text/javascript"
+        src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
+        </script>
+        <script type="text/javascript"
+        src="${path_for('static', path='site.js')}"></script>
     </head>
     <body>
         <div id="main">

demos/guestbook/urls.py

+""" ``urls`` module.
+"""
 
 from wheezy.routing import url
 from wheezy.web.handlers import file_handler

demos/guestbook/validation.py

+""" ``validation`` module.
+"""
 
 from wheezy.validation import Validator
 from wheezy.validation.rules import length
 from wheezy.validation.rules import required
 
+
 greeting_validator = Validator({
     'author': [length(max=20)],
     'message': [required, length(min=5, max=512)],

demos/guestbook/views.py

+""" ``views`` module.
+"""
 
 from wheezy.web.handlers import BaseHandler
 
         greeting = Greeting()
         if (not self.try_update_model(greeting)
                 or not self.validate(greeting, greeting_validator)):
+            if self.request.ajax:
+                return self.json_response({'errors': self.errors})
             return self.get(greeting)
         with session() as db:
             repo = Repository(db)
 .. image:: static/screenshot2.png
 
 For the purpose of this tutorial we store each of identified software
-actors in own file so at the end you will get a project structure with well
+actor in own file so at the end you will get a project structure with well
 defined roles.
 
 Domain Model
     from config import options
     from urls import all_urls
 
+
     main = WSGIApplication([
                 bootstrap_defaults(url_mapping=all_urls),
                 path_routing_middleware_factory
     ], options)
 
-
     if __name__ == '__main__':
         from wsgiref.simple_server import make_server
         try:
 
     $ env/bin/python app.py
 
+Visit http://localhost:8080/ to see your site in browser.
 
+AJAX and JSON
+-------------
+
+AJAX and JSON significantly minimize HTTP traffic between web browser and
+server thus allow you save bandwidth and serve more clients.
+
+In this tutorial we will display validation errors using AJAX + JSON and
+fallback to regular HTML rendering is case browser has JavaScript disabled
+for some reason.
+
+Add changes to ``views.py``::
+
+    class AddHandler(BaseHandler):
+
+        ...
+
+        def post(self):
+            greeting = Greeting()
+            if (not self.try_update_model(greeting)
+                    or not self.validate(greeting, greeting_validator)):
+                if self.request.ajax:
+                    return self.json_response({'errors': self.errors})
+                return self.get(greeting)
+            ...
+
+What we added here is check if the current request is AJAX request and if so
+we return JSON response with errors reported::
+
+    if self.request.ajax:
+        return self.json_response({'errors': self.errors})
+
+Now we need some JavaScript code to:
+
+* submit HTML form via AJAX
+* display errors
+* correctly handle redirect response
+
+Create a new file ``site.js`` and place it in ``static`` directory with the
+following content (we will be using `jQuery`_):
+
+.. literalinclude:: ../demos/guestbook/static/site.js
+   :lines: 1-
+
+Open ``layout.html`` and add link to `jQuery`_ library and ``site.js``
+somewhere within head HTML tag::
+
+    <head>
+        ...
+        <script type="text/javascript"
+        src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
+        </script>
+        <script type="text/javascript"
+        src="${path_for('static', path='site.js')}">
+        </script>
+    </head>
+
+Try run application by issuing the following command::
+
+    $ env/bin/python app.py
+
+Visit http://localhost:8080/ to see your site in browser (try both with
+JavaScript enabled and disabled).
 
 .. _`wheezy.html`: http://packages.python.org/wheezy.html
 .. _`wheezy.validation`: http://packages.python.org/wheezy.validation
+.. _`jQuery`: http://docs.jquery.com/Downloading_jQuery
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.