KSS in general
Preview of the first chapter of the manual
1.1 Introduction
----------------
KSS is an acronym for Kinetic Style Sheets. KSS is a client side
framework for implementing rich user interfaces with AJAX
functionality.
In this document, we assume the reader understands AJAX and its
purpose: to make pages in a browser behave more like a desktop
application. A primary AJAX characteristic
is that, instead of the user loading or reloading a web page, the web client
can contact the server and recieve information from it, then can change the
page's content or appearance without leaving it. As a result, the user
experiences faster interactions and a more "desktop"-like interface.
Plus, several tasks can be implemented with AJAX that are normally not
possible to achieve with static HTML pages.
We (the developers of KSS) did not want to create another JavaScript
framework for AJAX. Rather, we wanted to reuse existing libraries.
However, we also identified a few major problems in our everyday life
as AJAX developers, and by creating KSS we are attempting to offer
better solutions.
1. JavaScript is difficult to develop with. Our development background
is Python, and JavaScript talents are hard to
find in that community. However, Web development projects need AJAX
applications, so we designed KSS to help developers create sites with AJAX
without requring them to know JavaScript at all.
2. Browser incompatibility problems create difficult situations. Lots of
resources are burned by making an application run in a limited set of
supported browsers. We try to hide these details in our implementation.
3. We want to develop and debug our applications
efficiently. Therefore we provide development tools with the
framework, most importantly an efficient way of logging. The
problem with JavaScript is that debugging applications is
cumbersome and time consuming. The KSS framework is specifically
designed to make debugging easier.
4. In many use cases, the "thin client" approach provides the best
results. Instead of performing complex DOM
modifications from JavaScript, the client consults the server, and the
server sends back an HTML page which is simply used by the client to
replace a part of the page.
5. Developing a "fat client" should seldom be necessary. But for the
use cases when fat clients are desirable, the process should work in
a reusable way, by encapsulation of plugins.
If you are experienced with JavaScript and are just moving to Zope, you
should find much of this architecture familiar. However, we do
not require you to be a Javascript expert to understand this
document. On the other hand, you'll find it comfortable if you're
experienced in Python programming.
Developers have different skills and interests: site design,
application programming, plug-in creation, and so forth. Designers,
for example, are largely interested in tools that help them style a
site's look-and-feel, while programmers want to focus on
implementing that user interface with responsive, desktop-like
efficiency. As a result,
throughout this documention we highlight information peculiar to the
interests of several types of KSS user:
- KSS designer. A completed application can be bound to a page by writing
a KSS resource file. This task can be achieved by a
designer or by a programmer without any JavaScript knowledge. The
similarity of the KSS format to CSS assures the stylesheet is
developer friendly.
- KSS application developer. A developer can create an
application by reusing several KSS "plugins" that contain the
JavaScript code necessary for most client tasks. A developer
needs to write KSS resource files, and also must create server
side methods that command the client. This activity requires
you to know server side programming (Python, Zope, Plone), but
JavaScript knowledge is not required.
- KSS plugin developer. Creating a plugin is the most complex but least often
needed activity. Plug-in development requires an extensive
knowledge of both browser side JavaScript programming and the
browsers themselves. Knowledge of the KSS plugin API is also
needed.
In this document, we assume you are familiar with HTML and
how web pages are created. We also assume that you know CSS, and, in
particular, that you understand CSS selectors. Some parts of
the text assume that you can program in Python, and that you can build simple
web applications with Zope or Plone. We do not (apart from the most advanced
chapters) assume explicit knowledge of JavaScript.
A typical KSS application has the following characteristics:
- There is no JavaScript code or event binding in the HTML itself
(although we include JavaScript that contains the necessary client
side software for KSS to work).
- The behaviour is defined by a KSS resource file. A resource file has a syntax
similar to CSS, and its functionality is similar too. But instead of
being a static stylesheet, a KSS resource file defines how the
browser events should be bound to the HTML page.
- The client calls server side code, called "server actions," if certain browser
events are triggered (such as a user clicking on a node) In the current
implementation, these "server actions" are
implemented in Python. This server side code assembles a response that
consists of "kss commands" which are sent back to the client and which execute the
required behaviour.
Our goal in KSS to avoid the need to write JavaScript. JavaScript
is still necessary for some use cases, but even such
circumstance, we advise you choose JavaScript only as a last
choice. That is, first develop a "thin client" solution, and turn
it in to a "fat client" later, when you can't find another way to
solve the programming problem or you decide to spend excess time
on optimization. Besides, this gives you a possibility to
deliver a working application, and thus to learn the application
domain sooner. But often, it turns out that the thin client is
"good enough" and the development of the fat client can be
omitted altogether.
At the moment, KSS runs on standalone Zope platforms (including
Zope2 and Zope3) and on Plone. However, since the main part of
KSS is in JavaScript, we plan to provide KSS for other Pythonic
and non-Pythonic web application frameworks as well.
1.2 Architectural overview
--------------------------
In this section, we provide a high level overview of KSS' system
architecture.
The principal characteristics of the KSS process are:
1. Process control is embedded into the KSS resource file
(including how to bind browser events to the plugins, and how to
dispatch them to the server), and server action code (that decides
what commands to marshal in return). Thus, application control logic
is not implemented on the client side, but is the
responsibility of the server.
2. All JavaScript code (apart from the KSS core) is a KSS
plugin, in most cases an event binder plugin or a client
action plugin. Apart from this, no JavaScript code appears in
the system. In particular, no JavaScript
ever appears in the HTML pages.
.. image:: 1-kss-architecture.png
This diagram shows the architecture of KSS. Magenta and yellow
identify the two different types of plugins, and red highlights
the controlling information that governs the actual AJAX
functionality.
A major intent of the architecture of KSS is to separate design
from implementation.
KSS provides the execution flow similar to a simple programming language,
however yet does not have a "program." A command language can
execute a set of parametrized commands.
You can start implementing a thin client solution by using the
built-in set of KSS plugins. If it becomes necessary, you can
gradually fatten your client by developing new custom plugins.
Doing the latter naturally requires JavaScript programming, but
even so the result should be a well-componentized implementation
wherein
the details are totally decoupled from the level of the skin design.
To understand the entire KSS process, contemplate the complete flow of events
of an AJAX action. First, all actions are triggered from the client browser by
browser events. These are the same events that a programmer normally would bind
to JavaScript functions. Some of these events are generated by the user's
interaction (such as clicking on the page) or are triggered in some other
way (e.g. timer events).
Normally, JavaScript code
on the page would handle these events; in KSS, however, the events are
handled by event binder plugins. These plugins control both how the
event is bound and the actual process that executes when the event is
triggered.
Several event binder plugins are built into the KSS system. The
simplest plugin binds to a browser event via a
CSS selector. Other event type plugins are built-in. It is also
possible to extend the system by writing a custom event binder
plugin.
Such event binder plugins can bind to additional browser events and perform
more complex functionality. The way to bind these event plugins to browser
events is specified by the KSS resource file, a static resource file that
accompanies the HTML page. The format of this resource is called KSS which is
similar to CSS in syntax and nature; however, while CSS specifies
the static style of the content, KSS binds the dynamic behaviour
to a page.
The binding code for the event binder plugins runs at page
loading time. Event binder plugins may also specify the code to
be executed when the event is triggered, but usually they pass
execution to the event rule action dispatcher. The dispatcher
uses the KSS rule to decide what action to take. When
the action is a server action (the most common), control is
passed to the server. Client actions can also execute locally,
such as to support debugging. It is also possible to execute
multiple actions for the same event.
If an event triggers a server action, control is passed through
the request/response queue, which sends events
events in the form of XMLHttpRequests to the server.
The main task of
the request/response queue is to limit the maximum number of the
pending requests to the server. The queue also perform request error and
timeout handling. This functionality should really be
carried out by the XMLHttpRequest stack itself, but at the moment this
requirement is not fulfilled by the browser implementation, hence the
need for a separate layer.
Once the XMLHttpRequests are sent, they arrive on the server as
special URL requests. One server action is designated to reply to each
request. On Zope, this incarnates method calls on the server. The
methods do whatever their designated task is, then command the
client to do something in return.
This is a principal point of KSS: at this point in the process the server actions
decide what should happen on the client. The server side event
handlers do this by assembling a sequence of commands with
parameters. This command calling sequence is sent back to the
client as a response.
The commands consist of a selector (typically) and a client
action that are called with parameters. When the command
marshaller receives this sequence of commands, it looks up the
nodes identified by the selector. The command marshaller calls
each assigned client action in the required order,
with the parameters supplied by the server. The client actions
continue the flow of execution on the client side; this may
result in manipulating the DOM content of the page, binding
further events, and as a consequence, the execution of further
actions. In the end, the change is visible for the user,
finishing the entire AJAX cycle.
The client actions, like the event binders, ship as "core" plugins into the KSS
system. The simplest and also most useful client action
(replaceInnerHTML) replaces a selected tag in the DOM with a
string value supplied by the server as a parameter of the command.
This covers the most important KSS use case: in response to a user
action, the browser replaces part of the page that is
completely rendered on the server side. This results in a thin
browser client where most tasks are delegated to the server;
in many AJAX use cases, this is also the proper solution.
However, additional
local actions can carry out other common tasks, and you can
extend the system for your needs with very complex actions. In
the more complex cases, you may even want the server to return a
set of data to be turned into more complex DOM manipulation by
the accompanying command plugin.
1.3 Syntax and semantics of the KSS resource file
-------------------------------------------------
In this section we detail what a KSS resource file contains and how to use it.
Simple cases
""""""""""""
KSS is a resource format similar to CSS. It contains "rules." A rule
selects a HTML element from the page, binds it to an event, and
specifies the "actions" to happen when this event occurs.
A simple example is this::
#title_save:click {
action-server: saveTitle;
}
This binds the node selected by the CSS selector ``#title_save`` to the
``click`` event. If this node is clicked, we want an action to happen.
In this case, this is a "server action" executed on the server.
Another example::
#sportlet-main:timeout {
evt-timeout-delay: 5000;
action-server: refreshRecentPortletBody;
}
This binds the ``timeout`` event to the HTML node selected by the
CSS selector #portlet-main. The evt-timeout-delay specifies a
parameter used during the binding of the event. In this example,
it specifies how often the timeout event should poll. Similar to
the previous case, we also execute a server action here. The
method refreshRecentPortletBody is called every four seconds, and
results in refreshing the "recent portlet" in a Plone page.
Complex cases
"""""""""""""
The goal is to enable the creation of complex event types with
abstract events that are not equivalent to browser events. A
simple example is ``timeout``: this is not a
browser event, but it is relatively
simple to bind it. In even more complex cases, the plugin writer can freely
implement how the events should be bound. The key point to keep
in mind is that once the events are triggered, we handle them
within the system, as if they were normal browser events.
The ``timeout`` event is contained in the "core" plugins of KSS.
The KSS stylesheet rules resemble CSS rules, but
we attach a KSS event name at the end to designate the event
to be bound to the nodes selected by CSS. The event
name is prefixed with a colon.
The attributes inside the rule also follow a syntax very similar
to CSS, but with different semantics. We
explain more about the parametrizing semantics a bit later.
Overall, however, the rule contains different properties that
define:
- parameters for the event binding
- server and client actions, with parameters (there can be more of these), and
- parameters for the "default action" (explained later)
A rule follows the following syntax::
[<CSS> <CSS> <CSS> ...] <CSS_SELECTOR>:<KSS_EVENT_NAME> {
/* event parameters */
...
/* actions (server or client) */
...
/* default action */
...
}
Consider the following example::
div#thisnode a:timeout {
/* event parameters */
evt-timeout-delay: 3000;
/* actions */
action-server: updateInfo;
updateInfo-remark: 'Updating from timeout';
updateInfo-color: red;
action-client: log;
log-message: 'Updating from timeout';
}
A ``timeout`` event is bound; in this case the ``div#thisnode a`` CSS
selector selects any nodes. The event is bound to the selected nodes, but in
case of the timeout event the node itself does not matter.
The parameter ``delay=3000`` specifies a 3000ms
timeout recurring tick. When the events are executed, two actions
(updateInfo and log) are executed, each one with the supplied
parameters. The remote action is executed on the server, and then
sends back its command response to the client. The "log" action
is a special client action that is
implemented as a JavaScript action plugin; this
particular one gives a log message. (There is no default action
for this rule).
There is a special type of rule where there is no real CSS
selector; instead, a KSS special selector is used.
**Remark**: Additional CSS rules for the same description block, separated with comma,
are not supported at the moment.
Event selectors
"""""""""""""""
The event selector follows the css selector with a semicolon in
the KSS rule::
:namespace-eventname
Each event has a namespace. The exception is core events which are in the global namespace.
Examples::
:click
:plone-submitCurrentForm
In more advanced cases, the event can have an "event binder state
identifier" after the name in parentheses::
:sdnd-sortable(originals)
:sdnd-sortable(inchart)
The event binder identifier is useful for events that represent
and store internal states. To specify an identifier explicitly,
the internal state of the two events are separated from each
other. Consequently their rules are also not merged. (We cover
this in more detail later.)
Another way of thinking of the stateful events is that there are
as many distinct event binder instances, as there are identifiers
specified.
(**Remark**: the sensible example above, with the drag and drop,
currently lacks a working implementation, due to limitations in
the source code that we reuse. Yet this is the best example at
the moment.)
The full event rule
"""""""""""""""""""
The name of the event follow the CSS selector after a comma::
div#recent-portlet:click { ... }
div.warehouse-item:sdnd-drag(originals) { ... }
The event name in KSS must be preceded by a normal CSS selector.
It cannot stand by itself.
Special KSS selectors
"""""""""""""""""""""
In addition to CSS style node selection, we have special KSS selectors
to select the event. These are:
document
The rule is matched exactly once for the whole document.
(The methods receive ``node=null`` as a parameter.) The
match is only done at the initial pageload, and not when the DOM
content is injected to a page.
behavior
The rule will not match for any particular node. However, it is
possible to call this event method programmatically from the
plugin JavaScript code, and when this happens the given actions
and parameters are used. The behavior event's purpose
is to enable events to be triggered automatically by
other plugin components, not by the browser itself as normal events.
General form::
document:namespace-method
behaviour:namespace-method(stateid)
State IDs may be omitted. If state IDs are present, they must be existing
IDs, or the selected rule will never execute. No warning is given.
A full example for an event method rule is provided later in this document.
Specifying parameters
"""""""""""""""""""""
The general schema for the parameter specification::
.... {
evt-<eventname>-<key1>: <descriptor1>;
evt-<eventname>-<key3>: <descriptor3>;
...
default-<key1>: <descriptor1>;
default-<key2>: <descriptor2>;
...
...
action-[server|client]: <actionname1>;
<actionname1>-kss<ActionParm1>: <descriptor1>;
<actionname1>-kss<ActionParm2>: <descriptor2>;
<actionname1>-<key1>: <descriptor3>;
<actionname1>-<key2>: <descriptor4>;
...
}
The keys themselves cannot contain a hyphen (-), as hyphens have
a special semantics in KSS. Also, the action names cannot be
"default" or "evt" as these are keywords. Action names can be in
camelcase (CamelCase). Although the original CSS disallows the
usage of uppercase characters in the identifier, they are allowed
in KSS.
"Event parameters" mean that these are parameters to the event
binding itself::
#thisnode:timeout {
evt-timeout-delay: 2000;
}
The repetition of the event name after "evt-" has no semantics;
it just increases readability.
Event actions can be defined with their parameters. An event
action must be a server or a client action. There is a
restriction: only one event action with the same name can be
executed within one rule::
#thisnode:timeout {
action-server: updateInfo;
updateInfo-remark: 'Updating from timeout';
updateInfo-color: red;
action-client: log;
log-message: 'Updating from timeout';
}
Events and their plugins may programatically declares a "default
action" including parameters. Only the parameters can be set; the
default action is always the same for a event name, and it is
always on the client. The default action, if we think about the
event binder as a class (as this is how it is actually implemented
too), corresponds to a method of the class::
#buttonupdate:bluekit-update {
default-url: kssupdate.htm;
default-nodeid: target;
}
The default methods are designed to do the task themselves, but
you can also specify client and server actions for the same
node. In such cases, all these actions are executed.
Parameter producer functions
""""""""""""""""""""""""""""
A value simply represents a value. Quotes are optional for single words.
And as seen in this example, both single and double quotes can be used
to delimit string values that contain spaces::
... {
size: 12pt;
typeface: "Bitstream Vera Sans";
typeface: 'Bitstream Vera Sans';
}
It is possible to create extensions to acquire a parameter in another
way as a constant; these are called parameter producer functions.
When calculating a value, these preset functions can take the
event rules, the state of the event, and the page in
consideration. We show some examples here, and we will give the
complete list of the selectors, too, later::
... {
node_id: nodeAttr(id);
rownum: kssAttr("rownum", true);
}
The parameter producer functions operate on the scope of the
node which triggered the current event.
KSS action parameters
"""""""""""""""""""""
KSS action parameters are special predefined parameters. Strictly
speaking, they are not really freely usable parameters to specify
a value for an event; rather, they modify the way the (client or
server) action works.
The action parameters are distinguishable from normal parameters
by their name: they have the KSS prefix. (Consequently, normal
parameters are not allowed to start with this prefix.)
Only a few action parameters are implemented, so we present them
all here:
kssSelector
'''''''''''
kssSelector can be a parameter of client actions. It modifies the scope
of the action. The default execution scope is the same node where
the event was triggered. With kssSelector, we can execute
the action on a different or multiple nodes::
... {
action-client: actionName;
actionName-kssSelector: selector;
}
The declaration can specify different selection methods, as seen in the following examples::
xxx-kssSelector: 'css_selector';
xxx-kssSelector: css('css_selector');
xxx-kssSelector: htmlid('id_selector');
kssSubmitForm
'''''''''''''
kssSubmitForm transparently submits an entire
form as a request of the KSS server action. It is global to the
action: all field variables of the form are submitted with their
name as they appear in the form. (This also assures that, for
example, Zope multiform variables of the style :list, :record,
:records will work as expected.)::
... {
action-server: actionName;
actionName-kssSubmitForm: formname;
}
The value of the declaration can specify to submit a given form
or the current form (in which the event has been triggered).
Variations can be seen in the next example::
xxx-kssSubmitForm: 'formname';
xxx-kssSubmitForm: form('formname');
xxx-kssSubmitForm: currentForm();
Summarizing the rule definition basics
""""""""""""""""""""""""""""""""""""""
A complete example rule follows here::
div#portlet-recent:timeout {
evt-timeout-delay: 2000;
action-server: replaceMacro;
replaceMacro-selector: #portlet-recent;
replaceMacro-macropath: portlet_recent/macros/portlet;
}
Here, ``delay="2000"`` is used as a parameter for the binding of the
timeout macro, and ``selector="div#portlet-recent"``,
``macropath="portlet_recent/macros/portlet"`` are used for calling
the "replaceMacro" method. In this example,
``replaceMacro-selector`` is not a
kss action method; it is a normal parameter passed to the
server.
Another example::
div.menu-item:load {
action-remote: replaceWithRenderedText;
replaceWithRenderedText-text: nodeContent();
replaceWithRenderedText-size: 12pt;
replaceWithRenderedText-typeface: "Bitstream Vera Sans";
replaceWithRenderedText-selector: sameNode();
}
How rule merging is done
""""""""""""""""""""""""
A very important characteristics of KSS is that, as in CSS,
parameters with the same key can be overwritten in a selected
node. So, it is possible to say::
div#portlet-recent:timeout {
evt-timeout-delay: 2000;
action-server: replaceMacro;
replaceMacro-selector: #portlet-recent;
replaceMacro-macropath: portlet_recent/macros/portlet;
}
#portlet-recent:timeout {
evt--timeout-delay: 3000;
}
...In which case the delay will be 3000 (on node(s) which are selected by both rules).
The merging of the rules is similar to how you would think if you
think in terms of CSS: the last section, the KSS selector, counts
too. Simply, all rules with the same action name and same event ID are merged on the same node.
This means that rules are not merged if:
- Rules for two different events are not merged. For example,
"click" and "timeout" can be applied to the same node.
- The two different events are in the same event binder
class. For example, a "drag" and a "drop" rule of the dnd event
binder can be applied to the same node.
- The event state ID is different
However, the kss-action is not significant from the viewpoint
of the merge, and can be overwritten in a rule that follows
if it applies to the same node.
As just one example: You cannot specify two "timeout"
events to the same node. They are simply merged, with only one
timeout event. However, if you deliberately want two timeout
events to be triggered by the presence of the same node, you can
specify explicit event state IDs.
Programmatic constructs
"""""""""""""""""""""""
There are constructions to support the building of more complex
events. Typically, they are only useful for special event
plugins.
Using custom events with a default action
'''''''''''''''''''''''''''''''''''''''''
Custom events may have a default action which is wired into the
event (and implemented in JavaScript directly). There is no KSS
action name but the keyword "default" can be used to specify
parameters to the default action.
The behavior selector
'''''''''''''''''''''
"Behavior" is a special selector that enables events to be
triggered automatically by other plugin components. These events
can be triggered programmatically from an event plugin
implementation. The rule is never triggered by any event,
although it can be triggered automatically from other events.
With events that are programmatic-only, the binding of parameters
can be accomplished by using normal selectors. In such cases, the
selection is applied for merging the actions and parameters,
but the event does not listen to a physical browser event. Or, if
there is no point in specifying different actions and parameters
on different nodes, you can use the behavior selector to specify
actions and parameters globally for the event.
The behavior selector is only legal for events that trigger
programmatically. The current implementation does not check for
this explicitly; this will be added in a later KSS version.
Examples of complex events
''''''''''''''''''''''''''
As an example, consider the selective event type
plugin. The plugin implements a special "click" event that can
be bound to any node just like an ordinary
click event. However, the event instantiates itself and starts
counting the incoming clicks. The action specified in the "doit"
behavior rule is only executed every fifth time. For all the other
clicks, the "miss" action is executed.
The event selective-click has a default method as its
implementation. (The dashes in the event name show that the event
is looked up from the "selective" plugin namespace instead of the
global one.) The click rule does not specify actions since the
default action is supposed to do its work. It could have
parameters to the default action though, which it does not have
now.
The special selectors ``behavior:selective-miss`` and
``behavior:selective-doit`` will never select to any nodes by
themselves; instead, they are called up from the default action,
when the click event is triggered. Their role is to bind and
specify parameters that are used for the action but they do
not start listening to any real browser event.
The behavior selectors can be parametrized in the same way, as the
event selectors, but evt- keys (parameters for the
event binder) are not allowed here. Also, "behavior" must stand
by itself. It cannot be prefixed by another CSS selector (in the
latter case, it would be actually interpreted as a regular CSS
selector, not a special one)::
.clickable:selective-click {
}
behavior:selective-doit {
action-server: clickedButton;
clickedButton-id: nodeAttr(id);
action-client: log;
log-message: "Was here.";
}
behavior:selective-miss {
action-client: alert;
alert-message: "Missed it. (But just keep on trying...)";
}
The previous example does not use an event state ID, as it is not
necessary. We just bound by event class. But let's examine a
variation of the same example in case we need to specify an event
state ID. This would be necessary to instantiate different
counters to operate on the same page and keep track of two
different things. The state of these events must be distinct, two
different event binder instances are required. In this example,
each has a different ID in our selection, "mine" and "yours"::
#button-one:selective-click(mine) {
}
behavior:selective-doit:mine {
action-server: clickedButton;
clickedButton-id: nodeAttr(id);
action-client: log;
log-message: "Was here.";
}
behavior:selective-miss(mine) {
action-client: alert;
alert-message: "Keep trying until you get there";
}
#button-two:selective-click(yours) {
evt-click-count: 2;
}
behavior:selective-doit(yours) {
action-server: clickedButton;
clickedButton-id: nodeAttr(id);
}
behavior:selective-miss(yours) {
action-client: alert;
alert-message: "Keep trying until you get there, from the second button";
}
Error handlers
""""""""""""""
It is possible to attach error handlers to a server action.
As in an error handler, an error action can be specified. An error
action is always a client action, as we cannot rely on error
handling to the server::
#title_save click {
action-server: saveTitle;
saveTitle-error: handleError;
}
The error action can accept parameters. Such parameters are specified in
the same way as in other actions.
In practice, the main reasons that an error will occur during a remote action
execution are server errors or timeouts. It is also possible
for a server method to raise an exception on purpose, with the expectation that
the error handler will address the case on the client side in a
designated way.
Cancelling an action with merging
"""""""""""""""""""""""""""""""""
It is possible to cancel an action::
ul.contentViews li a:click {
action-client: alert;
}
thisId#a:click {
action-cancel: alert;
}
This example binds the alert action to the click event for the
nodes selected in the first rule. The second rule cancels the
alert action for the nodes it selects; in this example, that's
the node with the ID thisId.
Order is important for cancelling actions.
Action names
""""""""""""
Action names are declared following the action-client or
action-server keys in the rule definition.
An action name is the name of either a server or client action,
depending if action-client or action-server is applied. These are
the two choices we have: pass the control flow to the server
(the most common case), or handle an action locally on the
client (in which case, a JavaScript plugin will be called).
If an action key is missing from an event or method rule, no
action (other than a default action) is carried out.
A rule definition can have multiple client and server actions
defined, but each name must be unique. Definitions given the same
action name will result in overriding the parameters for the
original action; KSS does not attach a
second action with the same name.
1.4 Getting information from the DOM (parameter providers)
----------------------------------------------------------
Actions must be executed with parameters. Although static
parameters can be sufficient for some application, most of the
time a program expects to fetch some information from the DOM
(the page where the event executing the action has triggered).
This pattern is solved by the "parameter providers" that we
introduced earlier in chapter 1.3.
For those not familiar with the term, we mention that DOM stands
for the Document Object Model. This refers to the internal
representation of our HTML page. So when talking about the DOM,
we mean accessing information from our page from a program or as in
this case, from KSS. And this is the exact purpose of the
KSS parameter providers.
A parameter provider looks like a function with a number of
parameters, and can stand in place of the value of a parameter::
... {
action-server: doIt;
doIt-whatisit: parmProvider(arg1, arg2, ... argn);
}
There are two standard examples used most often.
In the first case, we want to take an attribute of a node.
HTML::
<div id='our-node'>The content</div>
KSS::
div#our-node:click {
action-server: doIt;
doIt-id: nodeAttr(id);
}
This server side action example is usable with Zope. We won't
examine this line-by-line, at this point, but the important piece
to look at is the parameter 'id' that is passed with the value
'our-node'.
Server action (Zope)::
@kssaction
def doIt(self, id):
# id == 'our-node'
In the other use case, we want to include special markup with our
HTML, but we can not use HTML attributes for that. Existing HTML
attributes already have a specific purpose; using an attribute
not contained in the HTML standard would result in non-validating
pages. To avoid this problem, we use special namespace
attributes to achieve the same result. These namespace
attributes can be acquired by the "kssAttr" parameter provider.
HTML::
<div id='our-node' kssattr:marker='marker-id'>The content</div>
KSS::
div#our-node:click {
action-server: doIt;
doIt-id: kssAttr(marker);
}
The value 'marker-id' is marshalled to the doIt action. We can
use any variable name, since they all become attributes within
the kssattr HTML namespace.
Unfortunately, we cannot use this capability in Zope or Plone.
Zope and Plone use transitional XHTML syntax, and namespace
attributes are only available in the real XHTML. Instead, we use
a special encoding to fit the attributes into our HTML, which
encodes the values into "class"::
<div id='our-node' class="kssattr-marker-marker-id">The content</div>
The ``kssattr-marker-marker-id`` string takes the general form
``kssattr-${key}-${value}``. There are some restrictions of what can
be used as a key: dashes (-) are not allowed inside keys, and
generic rules for HTML content must be respected.
It is a further enhancement of this use case to acquire these
attributes in a recursive way. For example, let us consider
a widget we created. Using KSS attributes recursively lets us
mark up an entire widget with a single KSS attribute, and we can
access the value of the single attribute from kssAttr if an event
happens anywhere inside the widget.
HTML::
<div id='our-node' class="kssattr-widgetid-widget2000">
<a class='link' href='firstpage.htm'>First page</a>
<a class='link' href='secondpage.htm'>Second page</a>
</div>
KSS::
a.link:click {
action-server: doIt;
doIt-href: nodeAttr(href);
doIt-widgetid: kssAttr(widgetid, True);
}
The second parameter in ``kssAttr(widgetid, True)`` tells KSS to look
further in the parents of the node, until it finds the attribute.
1.5 Introduction to commands
----------------------------
"KSS commands" serve a very important role in a KSS application.
They enable the application to use server-side code to instruct
what the browser should do on the client side. A command
specifies:
- A client action that the command will execute. This action
stands with parameters that travel in the command from the
server to the client.
- In the majority of commands this is accompanied by a "selector"
parameter identfies the node or nodes on which the action needs
to execute. (Commands with a selector are called "selector"
commands, versus "global" commands that contain no selector.)
Several commands are preimplemented in the KSS plugins.
Practically, all the client actions are also defined as commands,
ensuring that they can be executed both from the client and from
the server.
When a server action executes, it assembles a response which it
will send back to the client. This response consists of a set of
commands.
A command instructs KSS on the browser to execute a client action
combined with a selector that is applied to select nodes for the
execution. This means that the selector is executed first on the
page, and the client action is executed on the resulting nodes.
The execution may occur on zero or more nodes. Executing on zero
nodes is permitted and results in no error condition, although a
warning is entered into the client side logs.
Only one part of the commands has a selector as described above.
Global commands have no selector, as they do operate on the page
globally, and not on any of the nodes.
Examples of global commands are "alert" and "log", both are used to help
debugging, and have no concept of an actual "node" on which they execute.
Commands are constructed on the server side. To demonstrate a complete
example, let's examine an example usable with Zope. Look at the doIt action::
from kss.core import KSSView, kssaction
class MyView(KSSView):
@kssaction
def doIt(self, widgetid, href):
ksscore = self.getCommandSet('core')
selector = ksscore.getHtmlIdSelector('resultslot')
ksscore.replaceInnerHTML(selector,
'<h1>Parameters: %s, %s</h1>' % (widgetid, href))
This Zope-specific example contains a few new things. We
intentionally mention them in passing here, and will provide a
deeper explanation in a later.
- We implement the method in a Zope view. This is a special
browser view implemented from the ``KssView`` class which
contains a few utility methods.
- The kssaction decorator forces the method to return the
payload of the set of commands upon return.
- self.getCommandSet('core') looks up a commandset by name. This
adapts on the view and contains the commands that are defined
in the "core plugin".
- For commands requiring a selector, the selector can be
specified by ``getHtmlIdSelector``, ``getCssSelector``, any
other selector defined in the plugin, or a string which is
implemented as a css selector.
The doIt action, as implemented above, commits a single command
``replaceInnerHTML`` that causes the target node to be replaced
by the value of the single parameter 'html' of the action.
The commands, as the response given by the server action, are
marshalled in XML format. This is the raw result to be sent back
to the client::
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:kukit="http://www.kukit.org/commands/1.0"><body>
<kukit:commands>
<kukit:command selector="resultslot"
name="replaceInnerHTML" selectorType="htmlid">
<kukit:param name="html"><h1>Parameters: , widget2000, firstpage.htm</h1></kukit:param>
</kukit:command>
</kukit:commands>
</body></html>
