Time for action creating a books application, take two

Run the code in books2.py and point your web browser to http://localhost:8080.

After logging in (a default username/password combination of admin/admin will be present), you will be presented with a list of entities to browse (books and authors) and after clicking on Books, a screen will present itself that closely resembles the general Browse application (the page still has a Spartan look because no CSS is added at this point):

Time for action creating a books application, take two

Thanks to some JavaScript goodness, our browse screen is embedded in the page instead of functioning standalone, yet all functionality is retained, including skipping forward and backward as well as sorting. New books or authors may be added by clicking the Add new button.

What just happened?

When we take a look at the code in books2.py, we see that its main part consists of definitions of entities, relations, and specific Browse entities that are combined together to form a CherryPy application:

Chapter7/books2.py

import os
import cherrypy
from entity import AbstractEntity, Attribute
from relation import AbstractRelation
from browse import Browse
from display import Display
from editor import Editor
from logondb import LogonDB
db="/tmp/book2.db"
class Entity(AbstractEntity):
	database = db
class Relation(AbstractRelation):
	database = db
class User(Entity):
	name = Attribute(notnull=True, unique=True,
			displayname="Name")
class Book(Entity):
	title = Attribute(notnull=True, displayname="Title")
	isbn = Attribute(displayname="Isbn")
	published = Attribute(displayname="Published")
class Author(Entity):
	name = Attribute(notnull=True, unique=True,
			displayname="Name", primary=True)
class OwnerShip(Relation):
	a = User
	b = Book
class Writer(Relation):
	a = Book
	b = Author
logon = LogonDB()
class AuthorBrowser(Browse):
	display = Display(Author)
	edit = Display(Author, edit=True, logon=logon)
	add = Display(Author, add=True, logon=logon)
class BookBrowser(Browse):
	display = Display(Book)
	edit = Display(Book, edit=True, logon=logon,
			columns=Book.columns+[Author])
	add = Display(Book, add=True, logon=logon,
			columns=Book.columns+[Author])
with open('basepage.html') as f:
	basepage=f.read(-1)
books applicationbooks applicationcreatingclass Root():
	logon = logon
	books = BookBrowser(Book,
			columns=['title','isbn','published',Author])
	authors = AuthorBrowser(Author)
	@cherrypy.expose
	def index(self):
			return Root.logon.index(returnpage='../entities')
	@cherrypy.expose
	def entities(self):
		username = self.logon.checkauth()
		if username is None :
			raise HTTPRedirect('.')
		user=User.list(pattern=[('name',username)])
		if len(user) < 1 :
			User(name=username)
		return basepage%'''<div class="navigation">
		<a href="books">Books</a>
		<a href="authors">Authors</a>
		</div><div class="content">
		</div>
		<script>
				... Javascript omitted ...
		</script>
		'''
cherrypy.engine.subscribe('start_thread',
	lambda thread_index: Root.logon.connect())
current_dir = os.path.dirname(os.path.abspath(__file__))
cherrypy.quickstart(Root(),config={
		'/':
		{ 'log.access_file' :
				os.path.join(current_dir,"access.log"),
		'log.screen': False,
		'tools.sessions.on': True
		}
	})

After importing the modules we need, we define User, Book, and Author entities and an OwnerShip class, to define the relation between a book and a user. Likewise, we define a Writer class that defines the relation between a book and its author(s).

The next step is to create an instance of a LogonDB class (highlighted) that will be used in many parts of our CherryPy application to verify that the user is authenticated.

The bulk of the CherryPy application consists of two Browse classes, one for books and one for authors. Each class has display, edit, and add class variables that point to further branches of our application that are served by Display instances.

The Root class we define ties all of this together. It refers to the LogonDb instance created earlier in its logon class variable, and its books and authors class variables point to the previously defined Browse instances. It also defines an index() method that merely presents a logon screen if the user is not yet authenticated and if he/she is, redirects the user to the entities page. The entities() method which serves this page makes sure there is a corresponding user in the database (highlighted) and presents a base page consisting of a navigation div and a content div that will be filled when one of the links in the navigation section is clicked, and some JavaScript to tie everything together.

Before we examine the JavaScript, it might be good to take a look at the illustration to see how the application tree looks:

Path

Method

/

Root.index()

/entities

Root.entities()

/logon

LogonDB.index()

/books

BooksBrowser.index()

/add

Display().index()

/edit

Display().index()

/display

Display().index()

/authors

AuthorBrowser.index()

/add

Display().index()

/edit

Display().index()

/display

Display().index()

(Note that the edit, add, and display branches are each serviced by a different instance of Display).

Earlier, we saw that the Browse class we created was able to function standalone: clicking on any of the buttons referred to the same URL that served up the form in the first place. This setup makes it possible to use different Browse instances in different parts of an application tree, but here we want to replace a part of a page with the form produced by the Browse instance using an AJAX call. The problem then is that submitting a form without an action attribute will result in a request to the current URL, that is, the one referring to the page the form is embedded in, not the one that produces the form.

Fortunately, we can use jQuery to solve this problem by altering the action attributes of the freshly loaded forms to point to the URL that served those forms:

Chapter7/books2.py

	$.ajaxSetup({cache:false,type:"GET"});
	$(".navigation a").click(function (){
		var rel = $(this).attr('href'),
		function shiftforms(){
			$(".content form").each(function(i,e){
			$(e).attr('action',
				rel+'/'+$(e).attr('action'));
				$('[type=submit]',e).bind('click',
					function(event){
					var f = $(this).parents('form'),
					var n = $(this).attr('name'),
					if (n != ''){
						n = '&'+n+'='+$(this).attr('value'),}
					$(".content").load(f.attr('action'),
						f.serialize()+n,shiftforms);
					return false;
				});
			});
		};
		// change action attributes of form elements
		$(".content").load($(this).attr('href'),shiftforms);
		return false;
	});

This is accomplished by adding a click event handler to the links in the navigation area. That will not only prevent the default action but load the HTML produced by the URL referred to by the href attribute and pass a callback function that will alter the action attributes of any freshly loaded<form> elements (highlighted).

The shiftforms() function first prepends the original href contents to the action attributes and then binds a click handler to each button or input element with a type attribute equal to submit.

It would not be sufficient to add a submit handler to the form, because we don't want to let the<form> perform its default action. When a form is submitted, the contents of the page are replaced and this is not what we want. Instead, we want to replace the contents of the content div so we have to load() the URL from the form's action attribute ourselves.

This also means that we have to serialize the contents of the form to add as parameters to this URL, but jQuery's serialize() function will not serialize submit buttons. We, therefore, end up with adding a click handler to submit buttons in order to be able to determine the submit button that was clicked, so we can construct a complete list of parameters, including the name and value of the submit button.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.239.166