11

I am completely novice at AJAX. I am familiar with HTML/CSS, jQuery and beginner at GAE and Python.

In an effort to understand how AJAX works, I would like to know how AJAX might be used (actual code) in this example below. Let's use a reddit-like example where vote ups/downs are ajaxified:

Here is the Story Kind:

class Story(ndb.Model):
    title = ndb.StringProperty(required = True)
    vote_count = ndb.IntegerProperty(default = 0)

The HTML would look like this:

<h2>{{story.title}}</h2>
<div>
    {{story.vote_count}} | <a href="#">Vote Up Story</a>
</div>

How does AJAX fit inside here?

2 Answers 2

26

Ok Sir here we go... A simple app with one story and infinite votes... ;-)

app.yaml

application: anotherappname
version: 1
runtime: python27
api_version: 1
threadsafe: true

default_expiration: "0d 0h 5m"

libraries:
- name: jinja2
  version: latest

- name: webapp2
  version: latest

handlers:
- url: .*
  script: main.app

main.py

import logging
from controllers import server
from config import config
import webapp2


app = webapp2.WSGIApplication([
        # Essential handlers
        ('/', server.RootPage),
        ('/vote/', server.VoteHandler)
    ],debug=True, config=config.config)


# Extra Hanlder like 404 500 etc
def handle_404(request, response, exception):
    logging.exception(exception)
    response.write('Oops! Naughty Mr. Jiggles (This is a 404)')
    response.set_status(404)

app.error_handlers[404] = handle_404

models/story.py

from google.appengine.ext import ndb


class Story(ndb.Model):
    title = ndb.StringProperty(required=True)
    vote_count = ndb.IntegerProperty(default = 0)

controllers/server.py

import os
import re
import logging
import config
import json

import webapp2
import jinja2

from google.appengine.ext import ndb
from models.story import Story


class RootPage(webapp2.RequestHandler):
    def get(self):
        story = Story.get_or_insert('Some id or so', title='A voting story again...')
        jinja_environment = self.jinja_environment
        template = jinja_environment.get_template("/index.html")
        self.response.out.write(template.render({'story': story}))


    @property
    def jinja_environment(self):
        jinja_environment = jinja2.Environment(
            loader=jinja2.FileSystemLoader(
                os.path.join(os.path.dirname(__file__),
                             '../views'
                ))
        )
        return jinja_environment


class VoteHandler(webapp2.RequestHandler):
    def post(self):
        logging.info(self.request.body)
        data = json.loads(self.request.body)
        story = ndb.Key(Story, data['storyKey']).get()
        story.vote_count += 1
        story.put()
        self.response.out.write(json.dumps(({'story': story.to_dict()})))

and finally

views/index.html

<!DOCTYPE html>
<html>
    <head>
        <base href="/">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    </head>
    <body>
        <h2>{{story.title}}</h2>
        <div>
            <span class="voteCount">{{story.vote_count}}</span>  | <a href="javascript:VoteUp('{{story.key.id()}}');" >Vote Up Story</a>
        </div>
        <script>
            function VoteUp(storyKey){
                $.ajax({
                  type: "POST",
                  url: "/vote/",
                  dataType: 'json',
                  data: JSON.stringify({ "storyKey": storyKey})
                })
                .done(function( data ) { // check why I use done
                    alert( "Vote Cast!!! Count is : " + data['story']['vote_count'] );
                    $('.voteCount').text(data['story']['vote_count']);
                });
            };
        </script>
    </body>
</html>

Assemble, read it's simple enough and run. If you need a working git example just comment.

githublink (as from comments)

Sign up to request clarification or add additional context in comments.

8 Comments

@haopei Sorry I forgot you. Here you go github.com/jimmykane/gae-voting-ajax-example . I hope you star it ;-)
can you briefly explain what self.request.body does? I ran a search on the webapp and appengine documentations and found very little on it. It almost seems there is no definition for it in the docs. Thank you.
@haopei webapp-improved.appspot.com/guide/request.html check this. It's documented there... If you need more help mention.
@haopei Datetime properties are not json serializable. Try excluding the property like so: story.to_dict(exclude='my_datetime') and then convert and attach it to the dict. Check here for the to_dict and how to use it developers.google.com/appengine/docs/python/ndb/… . Also datetimes are a pain. If you need further assistance please mention here again and I ll try to add the datetime serialization techni
Thank you so much again, @JimmyKane. This is a monumental amount of help. I should note that the exclude parameter takes a list, tuple or set, so actually this works instead: story.to_dict(exclude=["my_datetime"]). I wouldn't mind learning about datetime serialization when I need it. I may have to call on you again in the future :) Appreciate your help!
|
0

Here is a little prototype web app on GitHub to test how to handle error messages in HTML form submissions with AJAX, Python and Google App Engine. It will give a starting point to see how these three pieces of technology mesh together. You can test this "app" on https://ajax-prototype.appspot.com/

Here is how it works on the client-side:

  • This htlm form submission is used:

    <form method="post" action="javascript:ajaxScript();">
    <label>Please pick a name</label>
    <input id="input" type="text">
    <input type="submit">
    <div id="error" style="color:red"></div>
    

  • It will trigger the JavaScript function ajaxScript:

    function ajaxScript() {
        var input = $("#input").val();
        $.ajax({
            type: "POST",
            url: "/",
            data: JSON.stringify({
                "name": input
            }),
            dataType: "json"
        })
            .done(function(jsonResponse) {
                $("#error").html(jsonResponse.message);
            });
    }
    
  • The jQuery .ajax() method handles the request while the .done() method will eventually handle the response that it gets from the server.

On the server-side:

  • The main.py file handles the server side of the business using our handler class AjaxHandler, which inherits from the GAE builtin class webapp2.RequestHandler

  • Within this class, the post method handles the AJAX request:

    def post(self):
        data = json.loads(self.request.body)
        username = data["name"]
    
        if not re.match(r"^[a-zA-Z0-9_-]{3,20}$", username):
            if len(username) < 3:
                message = "Your name must be at least 3 characters long."
            else:
                message = "Allowed characters are \
                           a-z, A-Z, 0-9, underscores \
                           and hyphens."
        else:
            message = "Congrats!"
    
        self.response.write(json.dumps({"message": message}))
    
  • Basically, the handy json module provides the two key Python ingredients

    • json.loads handles the data that the browser sends to the server.
    • json.dumps handles the data sent by the server in response to the browser's request.
    • The self.request.body argument of json.loads is the only less common piece of GAE that is used in the process, as it is specific to the task. As its name suggests, it gets the body from the Ajax request sent by the client.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.