I was talking with a coworker some time ago about his project, and he needed to update a piece of the page in-place when you go back to the page, and setting the page as uncacheable didn’t really work. Which probably makes sense; I think at one time browsers did respect those cache controls, but as a result going back in history through a page could cause some intermediate page to be refreshed and needlessly slow down your progress.
Anyway, Rails uses partials to facilitate this kind of stuff in a general way. Bigger chunks of your page are defined in their own template, and instead of rendering the full page you can ask just for a chunk of the page. Then you do something like document.getElementById('some_block').innerHTML = req.responseText. Mike Bayer just described how to do this in Mako too, using template functions.
When asked, another technique also occurred to me, using just HTML. Just add a general way of fetching an element by ID. At any time you say "refresh the element with id X", and it asks the server for the current version of that element (using a query string variable document_id=X) and replaces the content of that element in the browser.
The client side looks like this (it would be much simpler if you used a Javascript library):
function refreshId(id) {
var el = document.getElementById(id);
if (! el) {
throw("No element by id '" + id + "'");
}
function handler(data) {
if (this.readyState == 4) {
if (this.status == 200) {
el.innerHTML = this.responseText;
} else {
throw("Bad response getting " + idURL + ": "
+ this.status);
}
}
}
var req = new XMLHttpRequest();
req.onreadystatechange = handler;
var idURL = location.href + '';
if (idURL.indexOf('?') == -1) {
idURL += '?';
} else {
idURL += '&';
}
idURL += 'document_id='+escape(id);
req.open("GET", idURL);
req.send();
}
Then you need the server-side component. Here’s something written for Pylons (using lxml.html, and Pylons 0.9.7 which is configured to use WebOb):
from pylons import request, response
from lxml import html
def get_id(response, id):
if (response.content_type == 'text/html'
and response.status_int == 200):
doc = html.fromstring(response.body)
try:
el = doc.get_element_by_id(id)
except KeyError:
pass
else:
response.body = html.tostring(el)
return response
class BaseController(WSGIController):
def __after__(self):
id = req.GET.get('document_id')
if id:
get_id(response, id)
Though I’m not sure this is appropriate for middleware, you could do it as middleware too:
from webob import Request
class DocumentIdMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
req = Request(environ)
id = req.GET.get('document_id')
if not id:
return self.app(environ, start_response)
resp = req.get_response(self.app)
resp = get_id(resp, id)
return resp(environ, start_response)
Automatically generated list of related posts:
- Throw out your frameworks! (forms included) No, I should say forms particularly. I have lots of...
- WebOb decorator Lately I’ve been writing a few applications (e.g., PickyWiki and...
- What Does A WebOb App Look Like? Lately I’ve been writing code using WebOb and just a...
- Decorators and Descriptors So, decorators are neat (maybe check out a new tutorial...
- Making a proxy with WSGI and lxml You can use WSGI to make rewriting middleware; WebOb specifically...
I am actually doing that on a genshi powered project. Just that for the extracting of the element I use Genshi’s xpath support and render just a part of the genshi template rather than rendering the template, parsing it with lxml and extracting a part of the tree.
Regards, Armin
A middleware is like the old-school way (SSI, ESI). Only a WSGI mock is needed for development phase (if you don’t want deploy a not python server).
A specialized server can mix the parts better than me ;-)
At the risk of pointing out the obvious, Ian’s code saves on bandwidth but the server still incurs the overhead of rendering the entire page. Armin’s approach sounds cool.
I wonder how much overlap there is between this and what Chris Dent is trying to do with TiddlyWeb … (a TiddlyWiki derived server which lets you construct pages dynamically from bags of elements http://cdent.tumblr.com/tagged/tiddlyweb )
It doesn’t just save on bandwidth — extracting an element from a chunk of HTML is non-trivial in Javascript. Obviously it should be trivial in Javascript, but for some reason Javascript woefully lacks access to a reasonable parser. You can use
.innerHTML
as a kind of parser, but the side effects are considerable since you have to inject, at least temporarily, a bunch of live markup into your page.Phil: Thanks for the TiddlyWeb link. I think this is similar in some of its techniques, though the implementation is very focused and limited in my example.
I’ve thought about things that might be similar to TiddlyWeb, where “objects” are primarily markup, and an actual web page might be an aggregation of those objects. Records would contain not just their data, but also potentially a visualization of the data. Keeping data and display of data together is somewhat in line with Wiki notions of data management, as opposed traditional relation database-backed websites where there’s a server side component managing the display and update of the data. I think systems where display and data are the same are far more accessible — there’s a single canonical metaphor that people can understand, where with a traditional website there’s a hidden translation that people must infer.
I did client-side JS fragment-grabbing a while ago with MochiKit. You can see it in the wild here: http://havel.columbia.edu/media_panels/js/MochiPlus.js (see functions swapFromHttp(), and safeGetElement()) The two requirements on the server side for best results is that it serve the document as text/xml, and be valid XML (in IE, that means no named entities, just numbered ones)
I found this approach especially useful for list-type refreshes, to keep the templates in template files rather than mucky JS.