[[PageOutline]]
= KSS in Django with kss.django application =
== What is KSS? ==
KSS means '''Kinetic Style Sheets'''. A beautiful AJAX framework.
A KSS definition taken from [http://kssproject.org/ 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:
{{{
#!python
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).
1. 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:
{{{
#!xml
}}}
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:
{{{
#!css
.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 {{{WikiWord}}}, 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.
1. 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.
1. 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:
{{{
#!python
from kss.base import KSSCommands
from kss.base.selectors import css
def a_django_view(request):
commands = KSSCommands()
commands.core.replaceInnerHTML(css('div.content'), '
Hello world
')
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 [http://kssproject.org KSS website].
== KSS in django ==
At Plone Conference 2007 I developed [https://code.launchpad.net/kss.django 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 [http://coolwiki.yaco.es/wiki/ demo site] for show KSS features and a real site that uses ``kss.django``. Try enter first with javascrip enabled and later disabled.
You also can take a look to the [http://codebrowse.launchpad.net/~kissbooth/coolwiki/trunk/files 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 [http://codebrowse.launchpad.net/~kissbooth/coolwiki/trunk/files 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``:
{{{
#!python
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:
{{{
#!python
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:
{{{
#!python
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:
{{{
#!python
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:
{{{
#!xml
This is a WikiPage.
}}}
And you put this in your KSS file:
{{{
#!css
.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:KSSInDjango#Demosite 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:
{{{
#!python
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 {{{}}} tags in automatically.
The ``wiki/view.html`` may look like this:
{{{
#!xml
{% load ksslib %}
{% include_kssjs %}
}}}
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:
{{{
#!xml
{% load ksslib %}
{% include_kssjs %}
{{ page.name }}
{{ page.cooked_content }}
Version:
{{ page.version }}
}}}
If you want to change all ``div.content`` fragment, you must to do ugly things like this:
{{{
#!python
def view(request, is_kss=False):
...
tpl = Template('''
{{ page.name }}
{{ page.cooked_content }}
Version:
{{ page.version }}
''')
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:
{{{
#!xml
{% load ksslib %}
{% include_kssjs %}
{% ksswidget main %}
{{ page.name }}
{{ page.cooked_content }}
Version:
{{ page.version }}
{% endksswidget %}
{% ksswidget footer %}
You are on {{ page.name }}
{% endksswidget %}
}}}
And the views becomes:
{{{
#!python
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:
{{{
#!python
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:
{{{
#!python
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:
{{{
#!python
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))
}}}