joonis Logo

KSS in general

This part is adapted from the original KSS documentation, written by the KSS contributors.

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.

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.

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.

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-<key1>: <descriptor1>;
    evt-<eventname>-<key2>: <descriptor2>;
    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-" is optional; without it the parameter is applied to all event types of the selector (that could be more than one).

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.

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 the HTML5 data-* attributes to achieve the same result. These attributes can be acquired by the "kssAttr" parameter provider.

HTML:

<div id='our-node' data-kss-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 data-* attributes.

Unfortunately, we cannot use this capability for documents that use transitional XHTML syntax, because the data-* attributes are first available in HTML5. 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.

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 or JSON format. This is the raw XML result to be sent back to the client:

<?xml version="1.0" encoding="utf-8" ?>
<kukit>
  <commands>
    <command selector="#resultslot" name="replaceInnerHTML" selectorType="css">
      <param name="html"><![CDATA[<h1>Parameters: , widget2000, firstpage.htm</h1>]]></param>
    </command>
  </commands>
</kukit>

Or in JSON format:

{
  "commands": [
    {
      "selector": "#resultslot",
      "name": "replaceInnerHTML",
      "selectorType": "css",
      "params": {
        "html": "<h1>Parameters: , widget2000, firstpage.htm<\/h1>"
      }
    }
  ]
}

Can I
  help you?


Just drop me a line at
giraffe@joonis.de