Code


Version 7 (modified by msaelices, 7 years ago) (diff)

--

KSS in Django with kss.django application

What is KSS?

KSS means Kinetic Style Sheets. A beautiful AJAX framework.

A KSS definition taked from KSS project site:

KSS is a javascript framework that aims to allow Ajax development without javascript. It uses stylesheets with CSS-compliant syntax to setup behaviours in the client and a set of well-defined commands that are marshalled back from the server to manipulate the DOM.

KSS was designed by the developers for making an AJAX UI's. The main concern was to get AJAX features but without losing accesibility.

The goals of KSS are:

  • To have a framework for javascript in a declarative way but without using helpers that been attached with an specific technology.
  • Don't lose accesibility.
  • To do things in a non intrusive way. You don't change HTML for getting an accesibility version. It's the same version for all.

A brief introduction to KSS. How KSS works

In server side, KSS load all the plugins your application has configured (i.e. Scriptaculous). In Python code, you load plugins like this:

from kss.base import load_plugins

load_plugins(['kss-core', 'django', 'scriptaculous-effects'])

This activates the selected plugins. Activation does the following:

  1. Load all command sets (later explain what command set means for KSS).
  2. Makes the javascripts files needed for the plugin in HTML page available to the system.

In client side, there is a kss.js Javascript file that is a concatination of the available plugins. This Javascript loads a KSS file, put in a line on HTML source like:

<link rel="kinetic-stylesheet" href="/site_media/kss/wiki.kss" type="text/kss" />

After this it binds all the events to the matching nodes. It also sets up the server actions in a declarative way, see the example below:

.page-link a:click{
  evt-click-preventdefault: True;
  action-server: ajax/view;
  ajax/view-title: nodeContent();
}

The lines above mean that if the user clicks on a link element like <span class="page-link"><a href="wiki/view?title=WikiWord">WikiWord</a></span>, KSS will execute the previous fragment (like CSS selector does) and will:

  1. Override href argument, and send an AJAX request to URL defined in action-server KSS attribute. In this case the link goes to ajax/view, but without refreshing the page.
  2. Pass a HTTP parameter (POST by default) named title (as defined in ajax/view-title) with the value taken from the content of the node (WikiWord in this case). In this case it is like ajax/view?title=WikiWord request, but in a POST method.
  3. The server action takes the AJAX request and returns KSS commands to the browser. Commands are XML fragments that do things like (I explain better later):
    • replace breadcrumbs with Home / News / Foo News item
    • do a scriptaculous effect in top of screen
    • replace main content with the new content i am seeing

The first step is very important for accesibility. If you have javascript disabled and the users clicks he would go to wiki/view?title=WikiWord, and it works perfectly both with or without javascript.

The last thing to explain is how to send KSS commands from server side. Normal code at server side (i.e. a django view) could be something like this:

from kss.base import KSSCommands
from kss.base.selectors import css

def a_django_view(request):
    commands = KSSCommands()
    commands.core.replaceInnerHTML(css('div.content'), '<h1>Hello world</h1>')
    commands.scriptaculous.effect(css('div.message'), 'blinddown')
    commands.scriptaculous.effect(css('div.message'), 'blindup', delay=2)
    return HttpResponse(commands.render(), mimetype='text/xml')

This returns a XML code that is caught in client side by KSS javascript, and then he execute this commands in the browser.

More on KSS website.

KSS in django

At Plone Conference 2007 I developed kss.django, a django application for accelerating KSS development with django.

Django is a perfect mix with KSS due to features like templatetags, url mappings, settings and so on.

Demo site

I created this demo site for show KSS features and a real site that uses kss.django. You also can take a look to the source code

Installing kss.django

 $ bzr checkout http://bazaar.launchpad.net/~kissbooth/kss.django/trunk/ kss.django
 $ cd kss.django
 $ python setup.py install   ---> [or python setup.py develop ]

KSSing your website with kss.django application

I will use the coolwiki project code as an example of KSSing a web site.

Register kss.django application in your project Register kss.django application in your settings.py:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'coolwiki.wiki',
    'kss.django', # KSS django application
)

Setup your KSS plugins If you want to use scriptaculous effects, or other plugins, you can put on KSS_EXTRA_PLUGINS settings parameter, like that:

KSS_EXTRA_PLUGINS = ['scriptaculous-effects']

URL configuration One of the main goals for kss.django application was the advocacy of using exactly the same django views for Javascript version and classic version of web sites. First of all, in your root urls.py, you have to put this:

urlpatterns = patterns('',
    (r'^kss/', include('kss.django.urls')),
    ...
)

This line is needed for automatically loading of all the KSS Javascripts.

Later, in your application, you must change your URLs. For example I put the URLs like below for my coolwiki demosite:

from wiki import views

urlpatterns = patterns('',
    (r'^$', views.index),
    (r'^view$', views.view),
    (r'^edit$', views.edit),
    (r'^save$', views.save),
    (r'^history$', views.history),
    (r'^ajax/view$', views.view, {'is_kss': True}),
    (r'^ajax/edit$', views.edit, {'is_kss': True}),
    (r'^ajax/save$', views.save, {'is_kss': True}),
    (r'^ajax/history$', views.history, {'is_kss': True}),
)

As you can see, It is using the same view for the AJAX version and the normal version in every URL. All the URLs begin with wiki/... are standard, but the ajax/... the URLs go to the same view in KSS version (with is_kss parameter enabled).

What decides the URL is that the user goes to when he clicks in a wiki word? The answer is: KSS. In your template, you put some HTML code like this:

This is a <span class="page-link"><a href="/wiki/view?title=WikiPage">WikiPage</a></span>.

And you put this in your KSS file:

.page-link a:click{
  evt-click-preventdefault: True;
  action-server: ajax/view;
  ajax/view-title: nodeContent();
}

If you have Javascript disabled, the link you click goes to /wiki/view?title=WikiPage, that pass to wiki.views.view with is_kss=False. But if you have enabled Javascript, KSS will do an Ajax request to /ajax/view with a title HTTP parameter with WikiPage value. The views will change the wiki content but without returning a normal HttpResponse.

Warning: KSS does not yet support for dynamic URL actions, that is needed for to get RESTful URLs in your application. For example, in the wiki demo site, I have to change the /wiki/WikiWord/view URL format to a /wiki/view?title=WikiWord format, to get working both Javascript and non Javascript version with exactly the same code in the django views.

KSSing django views The low level way to use a django view with KSS is this:

def view(request, is_kss=False):
    page_name = request.REQUEST['title'] # it take the wiki word
    page = Page.objects.get(name=page_name)
    if is_kss:
        commands = KSSCommands()
        commands.core.replaceInnerHTML(css('div.content'), page.cooked_content)
        return HttpResponse(commands.render(), mimetype='text/xml')
    else:
        return render_to_response('wiki/view.html', {'page': page})

It is valid and clean way to get AJAX rendering and also works with Javascript disabled.

For the django templates, kss.django has several templatetags that helps your development. One of those is include_kssjs. It looks for the plugins you have installed and puts all of your <script type="text/javascript" src="..." /> tags in automatically.

The wiki/view.html may look like this:

<html>
<head>
  {% load ksslib %}
  {% include_kssjs %}
</head>
<div class="content">
</div>
</html>

But often you have a more complex template with a lot of rendering thing and you don't want to do the rendering by hand. For example, if you have this template:

<html>
<head>
  {% load ksslib %}
  {% include_kssjs %}
</head>
<div class="content">
  <h1>{{ page.name }}</h1>
  <div>
    {{ page.cooked_content }}
    <table>
      <tr><td>Version:</td><td>{{ page.version }}</td></tr>
    </table>
  </div>
</div>
</html>

If you want to change all div.content fragment, you must to do ugly things like this:

def view(request, is_kss=False):
    ...
    tpl = Template('''<h1>{{ page.name }}</h1>
    <div>
      {{ page.cooked_content }}
      <table>
        <tr><td>Version:</td><td>{{ page.version }}</td></tr>
      </table>
    </div>''')
    ctx = Context({'page': page})
    if is_kss:
       commands.core.replaceInnerHTML(css('div.content'), tpl.render(ctx))
    ...

To avoid this ugly example and to keep with the DRY principle, kss.django has a ksswidget templatetag, and several generic views like render_widget. The template becomes:

<html>
<head>
  {% load ksslib %}
  {% include_kssjs %}
</head>
<div class="content">
 {% ksswidget main %}
  <h1>{{ page.name }}</h1>
  <div>
    {{ page.cooked_content }}
    <table>
      <tr><td>Version:</td><td>{{ page.version }}</td></tr>
    </table>
  </div>
 {% endksswidget %}
</div>
{% ksswidget footer %}
 You are on {{ page.name }}
{% endksswidget %}
</html>

And the views becomes:

def view(request, is_kss=False):
    page_name = request.REQUEST['title'] # it take the wiki word
    page = Page.objects.get(name=page_name)
    if is_kss:
        commands = KSSCommands()
        commands.django.replace_widgets('wiki/view.html',
                                        ['main', 'footer'], {'page': page})
        return HttpResponse(commands.render(), mimetype='text/xml')
    else:
        return render_to_response('wiki/view.html', {'page': page})

commands.django is a command set that is registered in KSS when django.kss initializes. replace_widgets is a KSS command that renders the ksswidget nodes and returns the HTML rendered as a KSS action. The effect is that both the main content and footer are updated in the page, but without refreshing the page.

There is a shortcut for avoid the return return HttpResponse(commands.render(), mimetype='text/xml'). Is the kss_response method. The code becomes:

from kss.django.render import kss_response

def view(request, is_kss=False):
    page_name = request.REQUEST['title'] # it take the wiki word
    page = Page.objects.get(name=page_name)
    if is_kss:
        commands = KSSCommands()
        commands.django.replace_widgets('wiki/view.html',
                                        ['main', 'footer'], {'page': page})
        return kss_response(commands)
    else:
        return render_to_response('wiki/view.html', {'page': page})

There is also a shortcut for this, the render_kss_response generic view. By using this the view code can be reduced to:

from kss.django.render import render_kss_response

def view(request, is_kss=False):
    page_name = request.REQUEST['title'] # it take the wiki word
    page = Page.objects.get(name=page_name)
    commands = KSSCommands()
    return render_kss_response('wiki/view.html', {'page': page},
                               is_kss, ksswidgets=['main', 'footer'],
                               commands=commands)

The render_kss_response will do the if is_kss: ... else: ... stuff for us. Remember that this small amount of code works with Javascript and without.

If you want to add some extra effects to the ksswidget rendering, you can also create KSSCommands and pass it to the render_kss_response, like this:

from kss.django.render import render_kss_response

def view(request, is_kss=False):
    page_name = request.REQUEST['title'] # it take the wiki word
    page = Page.objects.get(name=page_name)
    commands = KSSCommands()
    if is_kss:
        commands.scriptaculous.effect(css('div.message'), 'blinddown')
        commands.scriptaculous.effect(css('div.message'), 'blindup', delay=2)
    return render_kss_response('wiki/view.html', {'page': page}, 
                               is_kss, ksswidgets=['main', 'footer'], commands=commands))