KSS3
KSS format - outdated
This is not yet a formal syntax description, but it tries to show all aspects of KSS via a set of examples taken from our demo cases.
Revision
This is revision 3 of the KSS format. Superseded.
Simple cases
These are the simple scenarios that more or less contain our original use cases. Here the new format does not add extra functionality, but let's have a look at how this would look like.
Examples:
#title_save:click {
action-server: saveTitle;
}
Another example:
#kukitportlet-main:timeout {
evt-timeout-delay: 5000;
action-server: refreshRecentPortletBody;
}
Complex cases
The complex cases define the full kss syntax, of which the base case is only a specialization.
The goal of the complex cases is that the plugin programmer can develop 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: main point is that once they are triggered and then we will handle them within the system - like as if they were normal browser events.
(Implementation note. In this case, the plugin writer will choose to hijack normal browser events and use those to trigger his own custom events. So we need an API for the hijacking these events because there can be more plugin component that want to hijack the same events. The mechanism should work in such a way that if a hijacker decides to skip the the event, it goes to the next applicant. This is an implementation detail worth to mention here, but it has no consequence to the kss format itself.)
As referring to a previous strategical argument of the azax team, this biases the framework to the direction that we do not necessarily bind the low level browser events directly from the kss format. In the kss format we always bind our conceptual events, which in some cases ("native") map to real browser events, in some other cases however not. In the case of the more conceptual events the binding (hijacking) of the real events goes in a blackboxed way from the plugin implementation.
Rules
The rules resemble css rules but at the last tag there must be a kss event selector attached with a comma to it.. The attributes are also alike but with different semantics than in css. We will see more about the parametrizing semantics a bit later. In outline, the rule contains different properties that define
- parameters for the event binding
- server and client actions, with parameters; there can be more of these
- parameters for the "default action" (explained later)
[<CSS> <CSS> <CSS> ...] <CSS_SELECTOR>:<KSS_EVENT_SELECTOR> {
/* 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';
}
Explanation: there is a "timeout" event bound, in 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 event is bound with a parameter "delay=3000" specifying e 3000 ms timeout recurring tick. When the events are executed, two actions, updateInfo and log are executed, each one with the supplied parameters. One of the actions is a remote action that is executed on the server (and commanding back the client), the other one is a special local 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 but instead of it a KSS special selector is used.
Remark: More css rules for the same description block, separated with comma, are not supported at the moment.
Event selectors
(the "input arrows" of the event node) This is a full-blown solution and remember, in most of the cases, you will only need the shortcut forms. I don't have enough use case examples to sufficiently demonstrate the use of this, this makes the concept more difficult to understand at first.
Events are declared by different event classes, it is possible to declare new event classes in a plugin. An event class is implemented by a piece of javascript code that declares how the event is bound. Each event class has "incoming" arrows that are the kss events (not equal to browser events!) and outgoing arrows, the kss actions. It is an important principle that the names of events and actions are in the namespace of the event class.
The outgoing arrow in many case has the same name as the incoming arrow, this means that the event pass through the plugin unbothered.
The binding of kss actions to different nodes, and the specification of what a given action will execute (in other words the "wiring" of the events) is specified in the kss resource file. For this reason we first need to get acquainted to our event selectors.
General form:
:namespace-eventname(eventid)
example:
:dnd-drag(originals)
:dnd-drop(originals)
:dnd-drag(inchart)
:dnd-drop(inchart)
In this case, the event type binder is instantiated in two instances, with two ids (originals and inchart), and the drag and drop are selector classes that the event recognizes.
The (class) name of the event type binder cannot be specified and is not visible from the format. Instead of this, the namespace is specified. The namespace is defined by the event plugin: in a typical case more classes would be defined within the same namespace. The method names, obviously, may not conflict within the same namespace.
Notice that an "event instance" is never created when an event is triggered, but rather when an event is bound to a particular node or nodes. So event instance creation happens at page loading time (or when page content is replaced from an action, and events are rebound).
Shortcut forms:
Id-s can be omitted. In this case each event class creates a singleton instance with a default id. That means that all events share the same object space. (Since most event binder lacks a logic that would require to share data between two consecutively triggered events, this is the expected behaviour anyway.)
:dnd-drag
:myproduct-eventname
There is no namespace. This is to be used for event names that are registered into the global namespace. Any plugin can register event names into the global namespace, in case they do not clash.
timeout
click
The full event rule
The event selectors follow the CSS selector.
div#recent-portlet:click { ... }
div.warehouse-item:dnd-drag(originals) { ... }
The kss event selector must be preceded by a normal css selector. Cannot stand in itself.
Special kss selectors
In addition to css style node selection, there are special kss selectiors that can select the event. The following ones are implemented:
- document
- The rule will be matching exactly once for each document. (The methods will receive node=null as a parameter.) The match is only done at the initial pageload, and not when dome content is injected to a page.
- kss
- The rule will never be matched for any node. However, it is possible to call this event method programmatically from the plugin javascript code, and in this case the given actions and parameters are used. If the event id is omitted, the rule will be in effect for the singleton event instance. You can think about these as fake events, that are generated not by the browser itself but are triggered automatically by other plugin components.
General form:
document:namespace-method
kss:namespace-method(eventid)
Eventids must be omitted but if present,. must be existing ones, or else the selected rule will never execute and no warning will be made.
Full example for an event method rule is later in this document.
Specifying parameters
The general schema for this:
.... {
evt-<eventname>-<key1>: <descriptor1>;
evt-<eventname>-<key3>: <descriptor3>;
...
default-<key1>: <descriptor1>;
default-<key2>: <descriptor2>;
...
...
action-[server|client]: <actionname1>;
<actionname1>-<key1>: <descriptor1>;
<actionname1>-<key2>: <descriptor2>;
...
}
The keys themselves cannot contain -, as this has a special semantics in kss. Also the action names cannot be "default" or "evt". Action names can be in camelcase and although original css disallows the usage or uppercase characters in the identifier, here they are allowed.
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 the 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';
}
For those events whose plugin programatically declares a so called default action, there is a way also to specify parameters to that. Only the parameters can be set: the default action is always the same for that given event name, and it is always on the client. The default action, if we think about the event binder as a class, corresponds to a method of the class so it can be thought of as an action that is bound to an event binder class, it can access its state.
#buttonupdate:bluekit-update {
default-url: kukitupdate.html;
default-nodeid: target;
}
The default methods are designated to do the task themselves but it is also possible to also specify local and global actions for the same node and in this case all these actions will be executed.
Parameter producer functions
a value simply represents a value. Can stand with or without quotes.
size: 12pt;
typeface: "Bitstream Vera Sans";
typeface: 'Bitstream Vera Sans';
There are possible extensions to acquire a parameter in another way as a constant, these are called parameter producer functions. These are preset functions that can take the eventruls, the state of the event and the page in consideration when calculating a value.
There is a possibility to send an attribute of the selected node.
node_id: nodeattr(id);
or the textual content of a node.
text: nodecontent();
There is a possibility to send a given form variable. Parameters can optionally be quoted.
member: formvar(edit, member);
member: formvar("edit", "member");
member: formvar('edit', 'member');
member: currentformvar(member);
(Implementation note. The parameter descriptors should also be implemented plugin-like.)
A complete example 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.
Another example:
div.menu-item:load {
action-remote: replaceWithRenderedText;
replaceWithRenderedText-text: nodecontent();
replaceWithRenderedText-size: 12pt;
replaceWithRenderedText-typeface: "Bitstream Vera Sans";
replaceWithRenderedText-selector: samenode();
}
Remark: nodecontent() and samenode() are planned, but not yet executed.
load is an example for a method that is executed on page loading time. There is a particular form of load:
document:load {
...
}
that would be executed unconditonally once a page is loaded. So to say "document" is a special selector that binds to each page, exactly once.
How rule merging is done
Like 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 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 secion, 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
- Rules for two different events are not merged: for example, "click" and "timeout" can be applied to the same node.
- even if 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.
- if the event id is different
However the kss-action is not significant from the point of view of the merging, and can simply be overwritten in a following rule if applies to the same node.
As a consequence, for example you cannot specify two "timeout" events to the same node. They will be simply merged and there will be only one timeout event. However if you do deliberately want two timeout events triggered by the presence of the same node, you can specify explicit event ids and this will explicitely make two events. (You could apply the same approach on two "click" events too, but obviously it would not work: because they share the same physical event, so one event would hijack the other in this case, even if they are unmerged.)
Using default methods and method rules
Method rules are special cases of an "outgoing" arrow from the event node. They represent actions that can be triggered programmatically from an event plugin implementation. Method rules are selected by the special method selector kss. This means that the rule will never be triggered by any event, however it can be triggered automatically from the default method of other events.
So when an event has a default method, usually there are no outgoing arrows but the default method does what it needs to. Most importantly, it can decide to execute another "event method" of the same event instance. These rules are defined with the "kss" special event descriptor.
As an example, let us consider the annoyClicker event type plugin. The goal of the plugin is to annoy users. The click event of the plugin 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" kss rule is only executed every 5th time. For all the other clicks, the annoy action is executed.
The special selector in this example will be annoyclicker-click. This enables binding the action that will be carried out if our annoyclicker-click event decides so in its implementation. (The dashes in the event name show that the event is looked up from the annoyclicker 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 is does not have now.
The special selectors kss:annoyclicker-annoy and kss:annoyclicker-doit will never select to any nodes by themselves; instead they are called up from the default action, when the click event is triggered.
The special selectors can be parametrized in the same way, as the event selectors, but obviously evt- keys
(parameters for the event binder) are not allowed here. Also kss must stand in itself and not prefixed by some other css selector (in the latter case it would be actually counting as a regular css selector, not a special one)
.clickable:annoyclicker-click {
}
kss:annoyclicker-doit {
action-server: clickedButton;
clickedButton-id: nodeattr(id);
action-client: log;
log-message: "Was here.";
}
kss:annoyclicker-annoy {
action-client: alert;
alert-message: "You are an idiot! Ha ha ha. (But just keep on trying...)";
}
In the previous example we did not use an event id, as it was not necessary. We just bound by event class. But let's see the same example in case we need to specify an event id. This may be possible, for example, if we want to instantiate different counters to different selectors, and count separately. So we need two different states, two different event binder instances. Both with a different id of our selection, annoyMe and annoyYou.
#button-one:annoyclicker-click(annoyMe) {
}
kss:annoyclicker-doit(annoyMe) {
action-server: clickedButton;
clickedButton-id: nodeattr(id);
action-client: log;
log-message: "Was here.";
}
kss:annoyclicker-annoy(annoyMe) {
action-client: alert;
alert-message: "You are an idiot! Ha ha ha. (But just keep on trying...)";
}
#button-two:annoyclicker-click(annoyYou) {
evt-click-count: 2;
}
kss:annoyclicker-doit(annoyYou) {
action-server: clickedButton;
clickedButton-id: nodeattr(id);
}
kss:annoyclicker-annoy(annoyYou) {
action-client: alert;
alert-message: "You are an idiot too, but not so big as I am.";
}
Error handlers
It is also possible to attach error handlers to a rule. An error handler is a (registered) command that in case there is an error during the remote
method execution (or a timeout), will be executed locally. It will be called with the same parameters as the action would be.
#title_save click {
kss-action: saveTitle;
kss-error: handleError;
}
handleError is a command in the same sense as are the commands that the remote procedure executes. We did not talk about these as they are out of the reach of kss (and would lead to the topics of marshalling commands from the server to the client, and how to implement a command plugin in javascript).
Both local and remote actions, and default methods can have error handlers.
Action names
Action names are declared following the action-client or action-server keys in the definition.
An action name is either a server action name in case of action-server, or if action-client then a local action implemented as a javascript action plugin.
If an action key is missing from an event or method rule, means that no action (besides the default action) will be carried out. That could also be a use case that we did not cover here: an incoming-only event with no action to be taken.
In the definition there can be more client and server actions defined, but only one is accepted with the same name. More definitions with the same action name will result in overriding the parameters for the original action, and not attaching a second action with the same name.
Implementation notes
Worries about if KSS parsing would be slow
Comparing to the original XML resource parsing, the KSS parsing can take more CPU resources. This is happening in the client browser at page loading time and occasinally when new content is injected into the page. Comaring with the XML parser that is implemented as a native library in the various browser implementations, for the KSS parsing we use a parser implemented in javascript. The main reason for this is that at first we want to provide a library that is independent from the server side.
I might also add that the overhead of KSS parsing will not be substantial, in my opinion, in typical cases with a few or a dozen rules per page. However it is worth considering the possible implementation enhancements such as:
- Use a native CSS parser. Since KSS is valid CSS syntax and in fact only uses a subset of CSS, any CSS parser could be used to parse KSS. At this time I am not aware of browsers offering their internal CSS parser through an API, but situation can change.
- Use a "cooked" format. This way the KSS will get parsed on the server side and the browser will get a cacheable response containing pre-cooked data. We plan to implement this soon and the flexible JSON format will be used for data transfer. The JSON format requires in the client only a plain evaluation of the data by the browser's internal javascript parser, so it will be an effective speedup, however it requires a parser to be written for the server.
- Use the old-style XML resources, this is disabled at the moment but we will re-enable it.
As the library can accept and handle multiple format resources, the "raw" KSS, "cooked" KSS and old-style XML resource formats will be enabled simultaneously, even for use within the same page.
Slowdown caused by ResourceRegistries
Remark: If you notice slow page loads, that's not kss parsing, the problem is that ResourceRegistries repacks all files each time. If you switch on browser side caching or otherwise have a cache between the client and the server, you will see the difference.
The problem typically occurs if you run zope in debug mode and switch of caching in the browser. Not sure if it would be desirable to make ResourceRegistries respond correctly to the last modified headers, or we could say people install cacheFu as an upfront cache anyway or use they browser cache.
Features
The implementation is partial:
- currently the only implemented parameter functions are these:
- nodeattr
- formvar
- currentformvar
- error handling is not implemented