views:

162

answers:

2

I've been getting into Pylons lately and was wondering how I could go about easily integrating AJAX functionality into my websites.

Basically, lets say I had a login form that usually gets accessed via site.com/user/login. Now, generally, this will be handled via something like:

class UserController(BaseController):
   def login(self):
      render('/login.html')

login.html would be a template that inherits the base, has a header, footer and navigation sidebar. A plani, simple website.

How could I AJAXify this? I would need to create two login.html templates, right? What would be a good way to handle the controller's login() method? Should I set a GET variable of something like &ajax=true then check for that when issuing render()?

I want a nice and clean way to choose how my controllers render content instead of some ugly hack (like the GET method above).

Thoughts?

+1  A: 

I'm not sure why your AJAX code would want to do a GET on that login page -- GET is only for getting information, and what info would the JS code client-side want to obtain from a login form?

Anyway, assuming there are pages that you want AJAX code to be able to GET in order to obtain useful info, I recommend a query string such as ?format=json to allow such requests to explicitly ask for "useful JSON-format info only, no decoration please".

Not only does this approach allow your app to know that this is an automated request (AJAX or otherwise, who cares? point is, no cosmetics are to be sent in response, just useful info!) but specifically that the requested format is JSON (so, should you ever want to supply XML or whatever as an alternative, there's an obvious growth path -- ?format=xml and the like).

There is nothing particularly Python-specific, much less Pylons-specific, in this -- it's the approach I would recommend for any "mixed" site (able, at least in some pages, to respond in more than one format, e.g. HTML with decorations or JSON, at clients' choice) no matter what sever-side language it was planning on using.

If your rendering is always of a form such as somefunction(sometemplate, somecontext), though, you may tweak things to ensure that the somefunction also gets the crucial bit about requested format -- if the requested format is JSON (or, who knows, in the future maybe XML or whatever) then somefunction knows it can ignore the template (which after all is or should be a purely view related functionality, and therefore should have presentation contents only) and just proceed to render the info that's in the context as JSON or whatever.

Alex Martelli
1.12 version of Routes provides format-as-file-extension syntax, http://routes.groovie.org/setting_up.html#format-extensions which, IMHO, looks better than query strings.
Daniel Kluev
Carl
@Carl, but what info would you need from the server to open the login form? Couldn't it usefully be part of the JS functionality, which after all the server _has_ already served earlier?-) Anyway, if you _do_ need some info from that page, then requesting `/user/login?format=json`, or indifferently `/user/login.json` if you prefer the approach @Daniel's comment suggests, seems the right way to go. After all, AJAX queries are no different from other programmatic clients you may want to make tomorrow for your server (e.g. native iPhone or Android apps, etc, etc).
Alex Martelli
+1  A: 

All modern Javascript libraries set an "X-Requested-With: XMLHttpRequest" header in their AJAX wrappers. As a convenience, Pylons sets the request.is_xhr boolean if it finds this header.

Conditional inheritance is a little tricky in Mako because of how <%inherit> is handled, but here's what you do:

  1. Change the render() call in your controller to render('/login.html', {'ajax': request.is_xhr})

  2. In your template, separate out anything you don't want in your AJAX template using template inheritance.

  3. Use an <%inherit> something like this: <%inherit file="${None if context.get('ajax') else 'login_base.html'}"/>

(Note: There's nothing special about the render() syntax used. You could just as easily use c.ajax = request.is_xhr and context.get('c').ajax instead)

ssokolow