BaláZs Ree   Introduction To Kss, Kinetic Style Sheets
Upcoming SlideShare
Loading in...5
×
 

BaláZs Ree Introduction To Kss, Kinetic Style Sheets

on

  • 3,601 views

KSS, Kinetic Style Sheets, is a framework that enables developers to create rich (AJAX) user interfaces without knowing javascript at all. KSS itself is by default included with Plone3 and is also ...

KSS, Kinetic Style Sheets, is a framework that enables developers to create rich (AJAX) user interfaces without knowing javascript at all. KSS itself is by default included with Plone3 and is also usable with Zope3. In the future we plan to make it available for other pythonic and non-pythonic platforms as well. During the demonstration we give a step by step introduction to adding dynamic behaviour to your browser page by the KSS stylesheet and server side only python code. We also introduce the setup and debugging skills needed to add dynamicity to your application. The targeted audience for the demo are Plone developers and integrators, familiar with server side Plone scripting. A basic knowledge of HTML and CSS is also needed. As a result you will get an introduction to the usage of KSS. Knowledge of javascript is not needed for attending the demonstration.

Statistics

Views

Total Views
3,601
Views on SlideShare
3,601
Embed Views
0

Actions

Likes
0
Downloads
62
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

BaláZs Ree   Introduction To Kss, Kinetic Style Sheets BaláZs Ree Introduction To Kss, Kinetic Style Sheets Presentation Transcript

  • Simple KSS Note: This is the print view with all the tutorial pages on one page. The paginated version is available here, if you prefer that. The target audience is everyone who wants to start using KSS. Zero knowledge of javascript is required for this. Both designers and programmers should complete these lessons. Begin with KSS Teaches how to setup and use KSS through a simple example. Installation KSS is included with Plone 3.0. (We also plan provide backports for earlier versions, at a later point.) This document is valid for this version: kss.core 1.2, plone.app.kss 1.2 (Plone 3.0) It is also important for all the further work that you run zope in debug mode and that you disable caching from your browser. In FireFox, if you install the Web Developer assistant in FF, this can be achieved from the leftmost menu by ticking in the Disable cache checkbox. Note that otherwise you need to force the browser to reload the page completely after each change you have made in your kss resource files. In case you have made a change and yet nothing happens on reload, it is caching to be blamed. Also you would need to familiarize yourself with reading javascript exceptions that occur on your page. KSS will raise a javascript error in case something is wrong and you would be able to see this from your browser, some browsers (IE?) may require some debug option to set up. In case you have no browser preference we suggest to use FireFox as it comes with the most friendly debugging facilities. You are also suggested to install the FireBug extension. Note that apart from reading the error message, you do not need to be able to understand or debug javascript at all. Also you will not touch any javascript code during this tutorial. Production and Development mode There is an additional logging facility that provides you with useful debug and informational messages about what happens. Debug information is only shown in Development mode of KSS. Development mode can be activated if you enable Debug mode in portal_javascript tool. After doing so, you must reload your page in the browser. If you want to switch back to Production mode, you will need to reload the page again. Production mode offers a slightly smaller javascript to be loaded into your page, and slightly faster execution, for example, by not giving you logs and detailed error messages. If you ever see the javascript error message Unknown message (kss optimized for production mode), this means you are running in Production Mode and you need to switch to Development Mode in order to see what happens. If you are in Development Mode, and are using FireFox with the FireBug extension, you need not do
  • anything, logs will appear directly in the FireBug console. If you use Safari, logging also appears in the Safari console. If these possibilities are not available for you, you have two other choices: either use Firebug Lite (on IE). or MochiKit (all browsers). Activating logging with Firebug Lite (IE) To activate Firebug Lite, you need to manually add the javascript file ++resource++firebuglite.js into the set of resources. You need to set this script to Non-Merging, and set its compression level to None. You must also make sure that you move this file above the ++resource++kukit.js and ++resource++kukit-devel.js. (Don't miss to press Save after this!) When this is done, you need to reload the page. After this Firebug Lite can be activated by pressing F12. Activating logging with MochiKit You need to do this only if you are unable to use both FireBug or FireBug Lite for some reason, although this should not happen. First you need to enable MochiKit's javascript in the portal_javascript tool. Find the line with the id ++resource++MochiKit.js, and enable it, then press Save. Following this, reload your original page. Then enter the following url into your browser after your page has been loaded (you can bookmark this as a link to avoid typing it in each time): javascript:kukit.showLog(); Remark: This needs to be repeated each time the page gets reloaded, because each time a page is loaded, the browser starts in a clear state, as far as javascript is concerned. See the result of logging In your kss debug console (depending on the debug method you use) some log messages will start on reloading a page. They will begin this way: Loading KSS engine. ... If this does not happen, check if you have succeeded with the steps described above, most notably, are you in Development Mode? You may also need to log in to the portal, as by default KSS is disabled in Plone for non-authenticated users. The text following the first line may indicate that KSS installed more eventrules on its own. This would not be a problem, but to allow us starting from a clean situation, we will disable all existing rule files. Disable KSS's default event rule (KSS) files Plone has some KSS rules in effect by default, which produces a lot of log messages. So first we want to disable this. Go to the ZMI in the portal root, enter the tool plone_kss and unselect the checkbox left from the names of plone.kss and at.kss. Then save this page. Reload your original portal page. At this point the logging console should display some log messages:
  • Loading KSS engine. Using original cssQuery. KSS bootstrap set up in Plone DOMLoad event. Plone legacy [initializeMenus] action registered. Plone legacy [bindExternalLinks] action registered. Plone legacy [initializeCollapsible] action registered. Plone [createTableOfContents] action registered. Initializing 3 menus. KSS started by Plone DOMLoad event. Engine instantiated in [DOMLoad]. Initializing rule sheets. Count of KSS links: 0 Setup of events for document starts. 0 special rules bound in grand total. 0 nodes bound in grand total. This means that KSS is active in the page, but there were no rule sheets to set up - so nothing happened. If the Count of KSS links is not zero, you need to return to the previous steps and make the remaining kss resources inactive. Some more explanation about the messages. The lines before the Count of KSS links are messages from plugin components that initialize themselves. There is no need to worry about that now. Having arrived to this point, we can now proceed to start our first usage example. Attach an event rule (kss) file First you need to attach a kss resource file to your page. This is similar to a CSS file in purpose - even the syntax is almost valid CSS - but it has different semantics and it controls how KSS events are bound to your page elements. To do it simplest, create a file named tutorial1.kss in your custom skins directory. To make the file bound to each page in the portal, we need to make it known by ResourceRegistries. Go to the ZMI in the portal root, enter plone_kss, press Add and customize your new entry in the following way: Notice that merging should be disabled. We also set Compression type to None, and disable caching, to make it work more debugging friendly during this tutorial.o Note: In a production environment, you would want to set Compression type to safe, and allow merging and caching of the resource. You can also do this right now, just then do not forget to enable Debugging Mode on the top of the same page. This will make the resource debuggable, even if the production settings are applied for compression, caching and merging.
  • After pressing Add, navigate back to your portal frontpage, and reload it. You should see the following messages: Loading KSS engine. ... Initializing rule sheets. Count of KSS links: 1 Start loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss of type GET http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss (39ms) Finished loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss resour Setup of events for document starts. 0 special rules bound in grand total. 0 nodes bound in grand total. Setup of events for document finished in 4 ms. ... This means that your kss file is active on the page, although it is yet not doing anything. At this point you can proceed to the next chapter. (optional:) Doing it the zope3 way In the zope3 style of development instead of putting files into skins, we prefer to use resources. From the point of view of KSS this is all the same but this leads to a cleaner development style, better supported in the future versions of Plone. Do not attempt to do this however if you do not know at least a little of Five, because in this tutorial we will not teach you Five itself, we only show the way to use it for KSS. Remember: zope3 development style is easy once you have learned it, but one might feel that it has a steep learning curve. To do this, you have to create a new product for the tutorlal or use one of your existing products. In the product directory on the filesystem, create a browser subdirectory and save the kss file there with the name tutorial1.kss. Then in the product root create a configure.zcml file with the following content: <configure xmlns=quot;http://namespaces.zope.org/zopequot; xmlns:browser=quot;http://namespaces.zope.org/browserquot;> <!-- the kss resource --> <browser:resource file=quot;browser/tutorial1.kssquot; name=quot;tutorial1.kssquot; /> </configure> After this you need to restart zope: if you have made an error in this file you see it immediately as zope will refuse to start at all. If zope has started up, then check that the url http://127.0.0.1:8080/++resource++tutorial1.kss returns the kss file correctly from the browser (replace the beginning with your portal root url). Also to use this file you must use ++resource++tutorial1.kss as the name of the file in the portal_kss tool. After making this change you should reload your page from the browser, open the kss debug console and see the log messages like above. In addition, since now you have a separate tutorial product, you could also make a GenericSetup profule that automatically registers the kss resource with the kss tool. To have an example of this, you may want to peek in to Products/CMFPlone, into the profiles directory, ald learn the usage pattern from there. - However this step is totally optional and you might want to skip it at this point. Set up some sample content
  • We will need some sample content in our example. For example we will need a navtree portlet appear on our page. Since this is not appearing on the front page of a newly created portal, we advise to create the following basic structure, that consists of a folder and three pages in it: portal root ----- folder1 ----- page1 | ---------- page2 | -----------page3 In this case, if we visit the url http://127.0.0.1:8080/folder1/page1, we will get to the view of our simple page, and we can see the navigation portlet appearing in the left portal column. You also need to log in as an authenticated user, because in the standard setup of Plone, KSS is disabled for unauthenticated users. Since you just created the above structure, this should already be true. Write a simple event rule Now we will add a KSS event rule to the kss file we created. This is the base unit that controls the dynamic behaviour of your page. A KSS rule looks similarly to a CSS rule. There is a selector part that selects one or more html nodes just like in CSS, then there is an event descriptor that is attached to the CSS selector with the comma. We will use the click event that will trigger when the selected node or nodes are clicked with the mouse. First we decide which nodes we bind to. This can be any selectable element in the page. Let us first bind the event to the active element of the navtree - that is, the element where we are at the moment. Inspecting the page with Firebug, we will see the html of the required line: <a title=quot;quot; href=quot;http://127.0.0.1:8080quot; class=quot;navTreeCurrentItem visualIconPaddingquot;> Home </a> We can see that the selector of this node can be, for example, a.navTreeCurrentItem so this will be the selector we use. The full event rule that we enter into the kss file will be this: a.navTreeCurrentItem:click { action-client: alert; } Notice that after the selector we put :click, this tells KSS what event to use and is always obligatory in the KSS rule. Between the braces we have various properties that control what actions are bound to the selected nodes, and also the parameters for this actions. This rule says that when the event gets triggered, we do not want to communicate with the server yet, but simply pop up an alert box. This is just a debugging aid that helps us see if we are on the right track. The line action-client: alert; says we have a client action with the name alert. Later we will learn to use server actions. Now reload the page, and you should see the following in the KSS console log:
  • Loading KSS engine. KSS bootstrap set up in Plone DOMLoad event. Plone legacy [initializeMenus] action registered. Plone legacy [bindExternalLinks] action registered. Plone legacy [initializeCollapsible] action registered. Plone [createTableOfContents] action registered. Initializing 2 menus. KSS started by Plone DOMLoad event. Engine instantiated in [DOMLoad]. Initializing rule sheets. Count of KSS links: 1 Start loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss of type GET http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss (52ms)++resource++kukit... (line EventRule #0: a.navTreeCurrentItem EVENT=click++resource++kukit... (line 2094) Finished loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss resour Setup of events for document starts. Using original cssQuery. EventRule [#0-@@0@@click] selected 1 nodes.++resource++kukit... (line 2133) 0 special rules bound in grand total.++resource++kukit... (line 2667) Instantiating event id [@@0], className [0], namespace [null].++resource++kukit... (line 3868) 1 nodes bound in grand total.++resource++kukit... (line 2582) Setup of events for document finished in 14 ms. Important is to see that our newly created eventrule is in effect (EventRule [#0-@@0@@click]), and that it is bound to 1 node in the page. The rest of the log can be ignored right now. REMARK: If the new contents of the kss file does not come into effect, make sure that you enabled Debugging in the portal_kss tool. If this is not done, the resource is most likely got cached where it should not be. If we click on the Home line in the navtree portlet (or to the item line which is current and highlighted), the following popup dialog appears: This tells us that the event has triggered in order, and also gives some additional information, like the number of the event rule (#0), the merge id of the event which is an internal identifier of KSS (@@0@@click), and the html node that triggered the event (A). After pressing the OK button, the page will be reloaded because the link will be followed as if we would have clicked it normally. We will deal with this soon. We should however note an important principle right away. If the rule does not select to any nodes, simply nothing happens: no error message will be raised. This is the designated behaviour and it is in line with how css behaves too. This is what allows us to put a lot of rules into the same kss file. No need to set up conditions: if the selected nodes are not present in the current page, simple nothing happens. This is also the time to see what happens if we have an error in the kss file. Let us change click to klick and reload the page. We will get a javascript error. In FireFox for example a javascript error appears and we can see the followings in the console log:
  • [Exception... quot;'Error parsing KSS at http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss : ParsingError: Undefined event : [null:klick].' when calling method: [nsIDOMEventListener::handleEvent]quot; nsresult: quot;0x8057001e (NS_ERROR_XPC_JS_THREW_STRING)quot; location: quot;<unknown>quot; data: no] The important part of the message says that the klick global event does not exist, the rest is just FireFox cruft. If we just make a syntax error, we get a ParsingError that also give the position of the error in the file. As a consequence nothing will work and we are forced to fix the error immediately and reload the page afterwards. Note: The null in [null:klick] means that the event is in the global namespace. Foe events in a custom namespace, the namespace would be displayed instead of null. Specify an event binding parameter Sometimes it is necessary to customize the way the event is bound. This can be done with parameters with the event binding. In the previous example after we closed the alert, the page reloaded because the link was followed by default. The click event has a parameter preventdefault which can be used to override this. The modified rule will be: a.navTreeCurrentItem:click { evt-click-preventdefault: True; action-client: alert; } The full property name is evt-click-preventdefault where three segments are separated with dashes: evt says this is an event binding parameter click is just the name of the event. It is obligatory to show it here. the last segment is the name of the parameter itself that we are setting. Also note: The order of action-client and evt-click-preventdefault is not important. Both capitals and small capitals are allowed in the boolean values, e.g. True or true, False or false are equivalent. It is entirely up to each event which parameters they take and which ones are obligatory, or which ones have a default value. At the moment the preventdefault event is only usable for the click event and it has a default value of False. Obviously it may only be necessary to set this parameter if there is otherwise something that the browser wanted to do on the click, before we started to modify its behaviour. The order of the lines within the rule block otherwise does not matter. If we reload the page, after clicking on the link the alert will pop up but afterwards the link will not be followed. Specify an action parameter We can also specify parameters to the actions themselves. The main difference between the event binder and the action parameters is, that the previous ones are effective when the event is bound (i.e. when we load the page) while the latter ones come to effect when the event is triggered (i.e. when we clicked on the link). As was the case with the event parameters, it is entirely up to the given action to
  • decide what parameters it accepts and handles. Now, the alert action just happens to accept a message parameter, so we extend our rule with one line: a.navTreeCurrentItem:click { evt-click-preventdefault: True; action-client: alert; alert-message: quot;Hey, we are herequot;; } The property name was alert-message, the semantics is that we must always use the name of the action (alert) followed by a dash and the name of the parameter. Notice also that we stringified the value on the right side: this is only necessary if the value is not a single word. Both single and double quotes can be used and the quotes can also be used backslashed inside the string. Reload a page, click on the link and see the alert popup appearing with the text we specified, with debug information added to the end of it: Alert action dialog 2 This may also be the right moment to experiment if caching is not keeping you away from seeing your changes immediately. Principally, during development, when you have modified and saved the kss file, after reloading the page you should have the new values in effect - if you assure this, you will save yourself from a lot of headache. You have learned the basics of setting up kss rules so far, but nothing that would really do anything so far. It is now time to sit back and relax a bit before we get to the definition and usage of server actions. Continue... Begin with KSS, part 2 We will continue and learn how to create server actions and specify their execution from our event rules. . This tutorial is the direct continuation of the first part. We will learn how to specify server side actions and finally complete our first useful AJAX example. Creating a server action Until now, we called a client action (alert) from our event. This was good to test if our event was triggered. However, in a typical AJAX pattern, we want to call a server action. Server actions are implemented as methods on the server. They could be any kind of callable methods, including python scripts, and this is what we will do first. Besides doing whatever is necessary for the business logic, the task of the server method is to assemble a sequence of commands. These commands are marshalled back to the client to be executed there. A command is a call to DOM manipulation client code. A command can (in most cases, should) have a selector, to set the scope of nodes on which the command is executed, and a set of parameters for execution. Although existing components come with implemented server action methods, it is easy to create a custom one since it requires only python skills. Let's create a python script (response1) in the custom skin with the following content:
  • # import Through-The-Web(TTW) API from kss.core.ttwapi import startKSSCommands from kss.core.ttwapi import getKSSCommandSet from kss.core.ttwapi import renderKSSCommands # start a view for commands startKSSCommands(context, context.REQUEST) # add a command core = getKSSCommandSet('core') core.replaceInnerHTML('#portal-siteactions', '<h1>We did it!</h1>') # render the commands return renderKSSCommands() After the imports, we initialize the view that will hold the KSS commands that we want to send back to the client. Then we add a command. The name of the command is replaceInnerHTML. This is one of our most useful commands : it simply replaces the contents of the selected html nodes with some html string. To specify which nodes will be selected, the command also needs a selector: in this example, a standard CSS selector. We choose to replace the portal actions of a Plone portal that are on the top of the page - but we could choose any other element as well. The replaceInnerHTML method is accessed through a command set. Since we have a pluggable system, we need to refer to the component that defines the methods, in this case, the 'core' command set. In the last line, the renderKSSCommands call is mandatory : it will generate the response payload from the accumulated commands. To look at this payload, let's access this method directly from the browser: http://localhost:8080/plone/front-page/response1. We will see We did it! on the screen, but let's have a more careful look at the source of the response: <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Transitional//ENquot; quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtdquot;> <html xmlns=quot;http://www.w3.org/1999/xhtmlquot; xmlns:kukit=quot;http://www.kukit.org/commands/1.0quot;><body> <kukit:commands> <kukit:command selector=quot;#portal-siteactionsquot; name=quot;replaceInnerHTMLquot; selectorType=quot;quot;> <kukit:param name=quot;htmlquot;><h1>We did it!</h1></kukit:param> </kukit:command> </kukit:commands> </body></html> Note: In KSS version 1.4, the payload looks slightly different, but structurally it is the same. This is an XML response, where we can see how commands and parameters are actually marshalled. When the response is interpreted by the KSS engine, it will execute the commands with the given parameters. Calling the server action Now, we have finished to build our server action; we want to call it from our kss style sheet. We replace our previous KSS event rule with the following one:
  • a.navTreeCurrentItem:click { evt-click-preventdefault: True; action-server: response1; } The action-server line specifies the name of the remote method to call: response1 (since this is how we named our python script). The script will be called on the context url of the page we are at. Let's reload the page so that the new kss comes into effect. Open the loggingpane. Then press the quot;Homequot; line in the navtree portlet. It works! We can see the site actions replaced with our text. Also notice that a few things have been logged to the kss console: RequestManager notifies server http://127.0.0.1:8080/demo11/folder1/page1/response1, rid=0 (RQ: 1 OUT, 0 WA POST http://127.0.0.1:8080/demo11/folder1/page1/response1?kukitTimeStamp=1191846467877 (109ms) ... RequestManager received result with rid [0] (RQ: 0 OUT, 0 WAI). Parsing commands. Number of commands : 1. Selector type [default (css)], selector [#portal-siteactions], selected nodes [1]. [replaceInnerHTML] execution.++resource++kukit... 1 nodes inserted.++resource++kukit... ... This gives a lot of information about what happened in the client: the server is notified, the response is received, it is parsed successfully, it contains one command, the command selects 1 node to act on. Now let's change our command response in the following way: ... from DateTime import DateTime # add a command core = getKSSCommandSet('core') content = '<h1>We did it!</h1><span>%s</span>' % DateTime() core.replaceInnerHTML('#portal-siteactions', content) ... This way, the current time is sent back by the server on each click and we can see that something happens. It is interesting to note that we did not need to reload the page in order to see the effect of this change. Because we only made changes on the server, we did not need to load anything new on the client side. So we can continue to debug from the already loaded page and this will work even through server restarts. What happens if the server-side script has an error, or the client does not get a correct response for some reason? For example, changing DateTime to DateTimeX in our code, results in the following: Request failed at url http://127.0.0.1:8080/demo11/folder1/page1/response1, rid=1, server_reason=quot;ImportError: import of quot;DateTimeXquot; from quot;DateTimequot; is unauthorized. The container has no security assertions. Access to 'DateTimeX' of (module 'DateTime' from '/usr/local/lib/Zope2.10/lib/python/DateTime/__init__.pyc') denied.quot; The error Request failed indicates that we have to turn to the server to debug the problem. Our
  • best friend, the zope error log will tell us about the actual problem. However in this case KSS also logged us the server side error message. Server action parameters Like client actions, server actions can also accept parameters. The parameters will be sent to the server as form variables. Zope publisher can then pass them as usual keyword parameters to our python script. Let's render a parameter coming from the client. We add parameter mymessage to the python script. Then modify the script: ... # add a command core = getKSSCommandSet('core') content = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) core.replaceInnerHTML('#portal-siteactions', content) ... We modify our kss rule to actually send the parameter from the client: a.navTreeCurrentItem:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: quot;Hello Plone!quot;; } The key response1-mymessage is built identically to how we did it with the client action. We use the name of the action first and then, following the dash, the name of the parameter. This time, because we change the stylesheet, we need to reload the page before testing by clicking on the bound node. To understand better how all this is working, let's enter a second rule in the kss: ul#portal-globalnav li a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: quot;clicked on global navquot;; } This shows some new things. First, you can see that you can use any css selector in a rule. In this case, the selector will select all globalnav tab links. If you reload the page, you will notice that if you click on any of those links, different content is replaced because different parameter are passed to the server. If you take a look at the log after the page reload, you can see something like this:
  • Loading KSS engine. ... Initializing rule sheets. Count of KSS links: 1 Start loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss of type GET http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss (22ms) EventRule #0: a.navTreeCurrentItem EVENT=click EventRule #1: ul#portal-globalnav li a EVENT=click Finished loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss resour Setup of events for document starts. Using original cssQuery. EventRule [#0-@@0@@click] selected 1 nodes. EventRule [#1-@@0@@click] selected 5 nodes. 0 special rules bound in grand total. Instantiating event id [@@0], className [0], namespace [null]. 6 nodes bound in grand total. ... Setup of events for document finished in 52 ms. ... This shows that the second rule is also in effect now. Moreover, it has selected 5 nodes (or however many globalnav tabs you have). A lot of other information is also logged, it should not worry you at the moment. Different command selector Until now, in our command, we used the default css selector. It is possible to use other types of selectors, like a html id selector. Let's modify our command in the following way: ... # add a command core = getKSSCommandSet('core') content = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) selector = core.getHtmlIdSelector('portal-personaltools') core.replaceInnerHTML(selector, content) ... What an HTML id selector selects should be obvious. Reload the page and exercise! Commands can also select multiple nodes: ... # add a command core = getKSSCommandSet('core') content = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) selector = core.getCssSelector('dt.portletHeader a') core.replaceInnerHTML(selector, content) ... The CSS selector dt.portletHeader a selects all portlet headers in the page, so the replacement will be executed not on one node but on many nodes (as can also be seen in the logs). Try clicking the Home link in the navtree, or any of the globalnav tabs to see the effect. You can also add multiple commands: each of them will be executed, in the order they have within the payload. One thing is important to note. If a command selects no nodes, it is not an error: the behaviour designed in this case is that nothing happens. This is in line with the usual logic of CSS selectors in style sheets. Using a different event So far we have only used the click event: let's try with another one, timeout. The timeout event does not directly map to a browser event but it is a (conceptual) KSS event. This shows that in KSS anything can be an event and how an event binds itself is freely implementable.
  • Let's add the following rule to the end of our kss file (altogether we will have 3 rules then): document:timeout { evt-timeout-delay: 8000; action-server: response1; response1-mymessage: quot;from timeoutquot;; } The timeout event implements a recurring timeout tick. It has a delay parameter that specifies the time in milliseconds. In this case, the event will be triggered each 8 seconds over and over again. The event calls the server action that we already have but with a different parameter. The timeout event does not really need a node as binding scope. This is why we use document instead of a CSS selector as we did until now. This is a special KSS selector that is an extension to css and simply means : bind this event exactly once when the document loads, with a scope of no nodes but the document itself. If you reload the page you will notice that clicks work as before, however, every 8 seconds, the timeout event will trigger and do a replacement on the required nodes. There are some more advanced issues that this example opens and we will show more about them in the next tutorials. Congratulations! You have completed your first KSS tutorial, learned the basics and now you are able to start some experimentation on your own. Or, just sit back and relax. Server-side commands - the zope3 way A python script may not be the most proper implementation of a server method. Plone community is moving towards zope3 style development: the suggested way is to use a browser view (multiadapter). Previously, you have created a demo product, now create a python module demoview.py in the product root directory on the filesystem: from kss.core import KSSView, kssaction from datetime import datetime class DemoView(KSSView): @kssaction def response1(self, mymessage): # build HTML content = '<h1>We did it!</h1><p><b>%s</b> at %s</p>' date = str(datetime.now()) content = content % (mymessage, date) # KSS specific calls core = self.getCommandSet('core') core.replaceInnerHTML('#portal-siteactions', content) We inherit our view from KSSView. It further inherits from Five's BrowserView. It is maybe time to explain how the ttwapi uses those views. startKSSCommands does the instantiation of a KSSView. getKSSCommandSet is the call equivalent to self.getCommandSet. renderKSSCommands calls self.render(), which actually returns the KSS commands payload to the client. The self.render() is implicitly arranged on return of methods decorated with @kssaction. This is why we do not need to finish our method that
  • way. To be able to use the server side method, you need to add the following to your configure.zcml file: <browser:page for=quot;plone.app.kss.interfaces.IPortalObjectquot; class=quot;.demoview.DemoViewquot; attribute=quot;response1quot; name=quot;response1quot; permission=quot;zope2.Viewquot; /> The interface that the view is bound to is one setup by kss.core on all portal objects. You could also use directly the interfaces defined by Plone 2.5 directly if you wanted, however the advantage of using the marker interface defined by plone.app.kss.interfaces, like in the above example, is that the same code will work in older Plone versions as well. If you still have the response1 python script from the begin of this tutorial, do not forget to rename it. Now it is time to restart Zope. If everything goes well, the page functions as previously but you can see from the replacement message that the new method is operating on your page. Remember, when you are working with browser views, you must restart Zope each time you want to test the changes made in the method code come. Accessing DOM information In this lesson we will learn how we can access information from our page and pass it to the server actions. We will also familiarize ourselves with the most common errors we encounter with parameters passing, and learn some useful debugging tricks. In our previous lessons we learned how to use KSS to invoke a server action and how to pass different parameters to them. To start with, we will further improve the code that we used in the previous examples. You can import this code from the ZMI: go to your portal_skins, delete or remove the custom folder, and import the code. If you have just finished the previous lesson, you do not need to do this. Also remember to disable the kss files already registered from the installed products and add your new kss file into portal_css tool, as described earlier. Preface Or, why is this important at all? We have the following rule in our kss resource: a.navTreeCurrentItem:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: quot;Hello Plone!quot;; } This rule, combined with the response1 method, writes a message in the upper right corner of the page, if in the navigation portlet we click to the line representing our current folder location. (For the portal home page, this will be the quot;Homequot; line.) This works fine. We also learned how we can invoke the same server action with different parameters, resulting in different display texts in the result. Now we would like to bind the rule to all the navtree items. After having looked in the FireBug inspector we can see that the corresponding css selector will be li.navTreeItem a. Change the rule accordingly. (Do not make a duplicate for reasons to be
  • described later.): li.navTreeItem a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: quot;Hello Plone!quot;; } Save the rules and reload your page. If done right, you can see in the log that the rule is now correctly binding to the required number of nodes (for example, I have 2 items appearing in the navtree): EventRule [#0-@@0@@click] selected 2 nodes. Also you can check that clicking of any of the navtree items will activate the replacement. Now, I would like to see in the result, which line I clicked on. However I would then need to pass different parameters depending on the node. If I programmed in javascript, I would look up the correct information from the DOM. How can I do this with KSS? Obviously making 4 different eventrule is not an option, as the point is exactly to have one matching rule. The solution is using value provider functions that can be specified instead of the strings. The functions may take a number of parameters, and based on this they look up some information from the page. This results in dynamic parameter values passed to the remote request. As an example. consider we want to pass the href attribute of the clicked node. Then we modify the rule as follows: li.navTreeItem a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: nodeAttr(quot;hrefquot;); } Reload the page to see the effect! Overview of the builtin value providers The KSS core provides all the value providers that are useful to cover most of the use cases. In addition to the parameters you must supply to them, they operate on the node selected by the event. The most important ones are: Fetching content Our first objective is to be able to access the html content of the node, most of all the html attributes. nodeAttr(attrname) Produces the value of a given HTML attribute of the selected node. nodeContent() This produces the textual content of the node. We do not encourage using this, as we should use these functions for identifying the node on which the event happened, however it may come handy for debugging or for some cases - like in our following example. kssAttr(attrname) This is for reading (namespace) attributes holding information for kss only, and otherwise
  • irrelevant for the HTML content. We will talk about this later. Fetching form data The second important task is to fetch form values from the page. The following functions fetch a single form variable: formVar(formname, varname) Produces the value of a given variable within a given form. currentFormVar(varname) Produces the value of a given variable within the current form, which is the one in which the selected node is. The parameter varname is optional, and if it is ommitted, the current node will be used (in this case the node must be a form variable itself). Note: varname has to be a form variable exactly as it appears on the form. There is no semantic interpretation of the name. Therefore, multiform zope variables with the prefix :list, :record and :records cannot be fetched this way: they are not assembled as they semantica would require it. To handle zope multiform variables, currently the only way is to acquire all form variables in an entire form, as described below. For a complete and updated list, you can check the cheatsheet. All these commands are implemented in the kukit core and the rule is to keep them simple: if you need something more complicated, then a plugin extension will be needed. However the current use cases are all covered by the existing set. Acquiring an entire form The kssSubmitForm special action parameter can be used to pass an entire form to a server action. The prefix quot;kssquot; in the name signifies that this is a special parameter ( - consequently ordinary parameters are prohibited to start with the prefix quot;kssquot;). kssSubmitForm cannot be used with client actions. The following value provider functions can be used after kssSubmitForm: form(formname) Provides the values of all the variables in a given form. currentForm() Provides the values of all the variables in the form that contains the current node. In both cases, all the variables in the given form will be marshalled to the server. The server action can receive the parameters as it would receive them in the case of a normal server side method (the ZPublisher marshalls parameters that appear in the method signature, and in addition the variablesare also accessible on request.form as usual. Since there is no mangling of the variable names, Zope multiform variables of the type :list, :record, :records will work as expected. Remark:
  • In the earlier versions of KSS, these value providers were also usable in normal parameters. This usage is now deprecated. Combining more parameters, and encoding Let us have some more useful information displayed as a result. First let us pass 3 parameters: a string (like earlier), the current nodeAttr function, combined with nodeValue that yields the textual value of the node, in this case the visible title of the navtree item: li.navTreeItem a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: 'clicked on navtree item'; response1-href: nodeAttr(quot;hrefquot;); response1-value: nodeContent(); } We also need to change the response1 python scrript to accomodate the new parameters: # import Through-The-Web(TTW) API from kss.core.ttwapi import startKSSCommands from kss.core.ttwapi import getKSSCommandSet from kss.core.ttwapi import renderKSSCommands # start a view for commands startKSSCommands(context, context.REQUEST) # use the parameters txt = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) txt += '<br><span>href=<b>quot;%squot;</b>' % (href, ) txt += '<br><span>value=<b>quot;%squot;</b>' % (value, ) # add a command core = getKSSCommandSet('core') core.replaceInnerHTML('#portal-siteactions', txt) # render the commands return renderKSSCommands() We also need to change the parameter list of the string to: quot;mymessage, href, valuequot;. Let's click on the different lines in the navtree and enjoy the results. However I am using an internationalized site, and when I click on any line where there are non-ascii characters in the folder name, I receive an error in the logs: Request failed at url http://127.0.0.1:8080/demo11/folder1/page2/response1, rid=0, server_reason=quot;KSSUnicodeError: Content must be unicode or ascii string, original exception: 'ascii' codec can't decode byte 0xc3 in position 201: ordinal not in range(128)quot; We can also see the full traceback in the Zope event log: 2007-10-08 15:24:56 ERROR Zope.SiteErrorLog http://127.0.0.1:8080/demo11/folder1/page2/response1 Traceback (innermost last): Module ZPublisher.Publish, line 119, in publish Module ZPublisher.mapply, line 88, in mapply Module ZPublisher.Publish, line 42, in call_object Module Shared.DC.Scripts.Bindings, line 313, in __call__ Module Shared.DC.Scripts.Bindings, line 350, in _bindAndExec Module Products.PythonScripts.PythonScript, line 327, in _exec Module None, line 16, in response1 - <PythonScript at /demo11/response1 used for /demo11/folder1/page2> - Line 16 Module kss.core.plugins.core.commands, line 32, in replaceInnerHTML Module kss.core.parsers, line 74, in __init__ Module kss.core.unicode_quirks, line 27, in force_unicode KSSUnicodeError: Content must be unicode or ascii string, original exception: 'ascii' codec can't decode by
  • The explanation is this. When we build up the commands, the basic rule is that the supplied command parameters must be either unicode or plain ascii. We do not care autoconverting anything else: KSS has no information of what encoding is used in a string, and we have learned by now that magic can do more harm than help. So we must do explicit conversion of the parameters: from kss.core import force_unicode value = force_unicode(value, 'utf') The force_unicode method converts the input to unicode, from the encoding given as a second parameter. Why did we use utf? Actually KSS is handling charsets properly on the client side, and passes the parameters with utf encoding. The ZPublisher of Zope2 is dumb enough to marshall this to parameters as utf-8 encoded (non-unicode) strings. Hence the need for the conversion. On the other hand the twisted publisher of Zope3 is passing proper unicode values. So, if the force_unicode method meets an unicode, it just lets it pass. This is a way of thinking about future compatibility: we target to write code usable on Zope3 as is. Notice that to have the server side change in effect there was no need to reload the page in the browser! This is one convenience: the browser needs a reload only if we made changes in the client side code, in this case, the KSS. Missing parameters This works fine now. But we used to have a second KSS rule too (inherited from the previous lesson): ul#portal-globalnav li a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: quot;clicked on global navquot;; } This comes into effect when we click on the global navigation tabs. But now, this gives an error in the loggingpane. Turning to the Zope log we see a traceback: 2007-10-08 15:36:09 ERROR Zope.SiteErrorLog http://127.0.0.1:8080/demo11/folder1/page2/response1 Traceback (innermost last): Module ZPublisher.Publish, line 119, in publish Module ZPublisher.mapply, line 83, in mapply Module ZPublisher.Publish, line 47, in missing_name Module ZPublisher.HTTPResponse, line 694, in badRequestError BadRequest: <h2>Site Error</h2> <p>An error was encountered while publishing this resource. </p> <p><strong>Invalid request</strong></p> The parameter, <em>href</em>, was omitted from the request.<p>Make sure to specify all required parameter <hr noshade=quot;noshadequot;/> What happened here? One of the mandatory parameters were missing for the response1 method. solution: we need to change the list of parameters to mymessage, href=None, value=None. In addition we need to make the call to force_unicode conditional, and then for niceity, also make the text output better:
  • ... if value is not None: value = force_unicode(value, 'utf') txt = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) if href is not None: txt += '<br><span>href=<b>quot;%squot;</b>' % (href, ) if value is not None: txt += '<br><span>value=<b>quot;%squot;</b>' % (value, ) ... When we are at debugging, let's look at some other important cases as well. Nonexistent node attributes What would happen if nodeAttr wants to acquire an attribute that does not exist on the node? To test that, change corresponding line in kss to: response1-href: nodeAttr(quot;hrefXXXquot;); If we test it (this time don't miss to reload the page in the browser), it works well and we see the href informational line omitted from the output. This means that if there is a nonexistent value to be passed, the parameter will be missing from the request. If we had not put the None default value to the href parameter in the script, we would have received again the quot;parameter missingquot; error in Zope. Lucky we were smart in advance... In fact this is an important issue. The reason for the implementation is, that we do not have a representation of the value quot;Nonequot; that is passable to the ZPublisher as a method. So we have chosen to omit the value in this case, but this makes the default value for the parameter inevitable. As a second annoyance, currently we have no cross-browser compatible implementation that could distinguish between a nonexistent attribute and an attribute that equals to an empty string. So we take the latter case to be the same as a nonexistent attribute. We might polish this implementation in the future. Before continuing, don't forget to change back the kss line as used to be originally. No error in zope event log Sometimes it may happen that we see a request failed error in the loggingpane, but we see nothing in the Zope event log. One way to simulate this easily is to call a server action that does not have a corresponding server method. This would give NotFound and would not be logged by default. One way to try this is to temporarily remove the response1 method and see what happens. We could of course enable logging for this exceptions too, but we have an alternate way that gives us more control. This is the time to look at the actual communication between client and server. There are two ways for this: use the firebug extension of FireFox with the show XMLHttpRequests option switched on, or use a proxy application like tcpwatch of zope3 that can be used to tap the full client-server communication. It is advisable to get acquainted with both methods but for its simplicity we use FireBug now. If we look into the response of the failing request we can see that instead of the kukit command response we received a normal Plone error page. Inside the long long page we will see the actual error message. Congratulations! You can move to the next part at this point, or optinally, have a look at the next chapter.
  • Doing it the Zope3 way (optional) If you are familiar with Zope3 style development, you can easily transform the code into a method in a browserview yourself. Enter the file demoview.py into the product root in the filesystem: from kss.core import KSSView from kss.core import force_unicode from datetime import datetime class DemoView(KSSView): def response1(self, mymessage, href=None, value=None): if value is not None: value = force_unicode(value, 'utf') txt = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) if href is not None: txt += '<br><span>href=<b>quot;%squot;</b>' % (href, ) if value is not None: txt += '<br><span>value=<b>quot;%squot;</b>' % (value, ) # KSS specific calls core = self.getCommandSet('core') core.replaceInnerHTML('#portal-siteactions', txt) return self.render() As usual, you also need to add the following to your configure.zcml: <browser:page for=quot;plone.app.kss.interfaces.IPortalObjectquot; class=quot;.demoview.DemoViewquot; attribute=quot;response1quot; name=quot;response1quot; permission=quot;zope2.Viewquot; /> In the next tutorial lesson we look at some more usage of parameter producer functions. Accessing DOM information, part 2 This is the continuation of the previous lesson, with more complexity involved. In the previous lesson we learned how to use parameter producer functions with KSS, and faced with the most common issues in dealing with parameters on the server side. We continue where we finished. Merging (or cascading) rules again Originally we had a single kss rule matching the current node in the navtree only. Now we have a rule that matches all navtree nodes. Would there be a way to have both ? Consider entering a second kss rules after the current one: li.navTreeItem a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: 'clicked on navtree item'; response1-href: nodeAttr(quot;hrefquot;); response1-value: nodeContent(); } a.navTreeCurrentItem:click { response1-mymessage: 'clicked on the CURRENT navtree item'; } After reloading the page we see this section in the kss console logs:
  • ... Start loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss of type GET http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss (36ms) EventRule #0: li.navTreeItem a EVENT=click EventRule #1: a.navTreeCurrentItem EVENT=click EventRule #2: ul#portal-globalnav li a EVENT=click Finished loading and processing http://127.0.0.1:8080/demo11/portal_kss/Plone%20Default/tutorial1.kss resour Setup of events for document starts. Using original cssQuery. EventRule [#0-@@0@@click] selected 2 nodes. Merged rule [0,1-@@0@@click]. EventRule [#1-@@0@@click] selected 1 nodes. EventRule [#2-@@0@@click] selected 5 nodes. 0 special rules bound in grand total. Instantiating event id [@@0], className [0], namespace [null]. 7 nodes bound in grand total. Setup of events for document finished in 69 ms. ... What happens here? Eventrule #0 matches up with 2 nodes as previously. Eventrule #1 matches up with 1 node - however this node is one of the four nodes rule #0 has matched to. What happens? Rule #1 would not make much sense in itself, but in this case the two rules will merge on the common node. We can also see from the log that this happens. The result will be as expected (for those that are familiar with CSS cascading): if we click on the current navtree node, the text from rule #1 will be displayed on the screen. There are a few important things to notice. Rule lines are merged similarly to css. Merging is not done based on the selectors but on the nodes that the selectors actually select. So, merging depends on the content on which the rules are matched. Order is important: consequent rules override the earlier rules. Different eventnames do not match, for example a :click event and a :timeout event will not be merged. Actions with different names will be added after each other (as an example, place an alert in rule #1), actions with the same name will merge to one action with all their parameters merged. KSS attributes (HTML markup) As the next task, we want to extend our navtree so that we can also access the UID of the folder. The first thing that comes to our mind is that we could use another node attribute to hold this information. However, there is a problem. We could use the id attribute of the node but we may want to keep that for different purposes (besided, a node can have only a single id.) We could use a different attribute but using an attribute unknown to HTML would result in an invalid HTML content, even if the browser would not shout against it. So we want to be able to attach attributes to the nodes when we generate the template, and we want them to be accessible from KSS, but we want it to be invisible for HTML. As a solution, namespace attributes could be used. We could make attributes like kukit:attrname=quot;attrvaluequot;. Unfortunately we cannot use this in Plone. Because Plone uses transitional XHTML at the moment and this would only be valid in real XHTML with mimetype text/xhtml. To explore this feature, let us create a customized version of the portlet_navtree_macro. In this case, do not copy the template code from here, but make the necessary changes on your customized version instead. We will path the information quot;Is this item the current item?quot; to our server action. That is already available at the desired point of the template in a variable called is_current. The changes need to be done on the filesystem, because plone portlets cannot be customized through
  • the ZMI in Plone 3. Edit the file plone/app/portlets/portlets/navigation_recurse.pt in lib/python of your instance: ... <div tal:define=quot;item_class python:is_current and item_class + ' navTreeCurrentItem' or item_classquot;> <a tal:attributes=quot;href python:link_remote and remote_url or item_url; title node/Description; class string:$item_class kssattr-iscurrent-${is_current}quot;> <img tal:replace=quot;structure item_icon/html_tagquot; /> <span tal:replace=quot;node/Titlequot;>Selected Item Title</span> </a> </div> ... Note: You may need to restart Zope after you have made changes on the filesytem, to have your changes rendered. Look at the emphasized part. What we do is that instead of using real namespace attributes, we use an emulation that stores attributes into the html class attribute. The advantage is that we can have more attributes encoded into the same class after each other, and even preserve the existing classes on the node. The class entry must have the form of kssattr-key-value. The kssattr- prefix assures that we recognize the entry and do not mix with real classes. We only added the kssattr at the end of the existing classes on the node and did not change anything else on the template. After rendering the page we can see that all the navtree nodes have kss attributes, for example consider the following rendered html section: <li class=quot;navTreeItem visualNoMarkerquot;> <div> <a class=quot;state-private kssattr-iscurrent-Falsequot; title=quot;quot; href=quot;http://l12/demo11/folder1/page1quot;> </a> </div> </li> To access this attributes from KSS, we use the kssAttr value provider. This works very similarly to nodeAttr, only in this case it looks for the special kss parameters, and it is able to understand the encoding of kss attributes into classes, as described above. To use it, let us extend our kss rules in the following way: li.navTreeItem a:click { evt-click-preventdefault: True; action-server: response1; response1-mymessage: 'clicked on navtree item'; response1-href: nodeAttr(quot;hrefquot;); response1-value: nodeContent(); response1-iscurrent: kssAttr(iscurrent); } a.navTreeCurrentItem:click { response1-mymessage: 'clicked on the CURRENT navtree item'; } And, expand our response1 script too. Also add iscurrent=None to the end of the parameter list:
  • ... txt = '<h1>We did it!</h1><span><b>%s</b> at %s</span>' % (mymessage, DateTime()) if href is not None: txt += '<br><span>href=<b>quot;%squot;</b>' % (href, ) if value is not None: txt += '<br><span>value=<b>quot;%squot;</b>' % (value, ) if iscurrent is not None: txt += '<br><span>path=<b>quot;%squot;</b>' % (iscurrent, ) ... Let us test the results now. All navtree items now print if they are current or not (except for Home which comes from a different template with which we did not deal now). Homework: Modify the navtree.pt template too, to make the information available on the quot;Homequot; folder as well. This will only have a visible result if the navtree portlet becomes enabled on the portal root, too. Starting on this path, we could arrive to an ajaxified navtree portlet. We do not do that now, however we can conclude this. The ajaxification of existing components may require the customization of templates. The customization can be done by attaching simple KSS attributes to the required nodes. Why do we need to do this? For the same reason we need class attributes in order to make css work. Accessing form variables Another common task is to access the form variables in the page We need this if we want to implement in-place field validation, for example. When we click out of a field, we want to execute a server action. We will use the portal front page's edit view for our experiment. This contains a stringfield (the title), a textarea field (the description), and a rich widget (text) controlled by the kupu editor as default. We will start only with the title now. Note: Although I asked you in the first tutorial, I find it important to stress it again at this point: Before going on with this example, make sure again that Plone's builtin plone.kss and at.kss resources are made inactive. This is important before a similar functionality is already implemented in Plone, and not disabling it would contradict with our example, confusing the results. Let us use FireBug to look at the html part responsible for rendering the title field: <div id=quot;archetypes-fieldname-titlequot; class=quot;field ArchetypesStringWidget kssattr-atfieldname-titlequot;> <span/> <label for=quot;titlequot;>Title</label> <span class=quot;fieldRequiredquot; title=quot;Requiredquot;> (Required) </span> <div id=quot;title_helpquot; class=quot;formHelpquot;/> <div class=quot;fieldErrorBoxquot;/> <input id=quot;titlequot; class=quot;blurrable firstToFocusquot; type=quot;textquot; maxlength=quot;255quot; size=quot;30quot; value=quot;Welcome to Plonequot; name=quot;titlequot;/> </div> The CSS rule that would bind to the input node is div#archetypes-fieldname-title input and the event that we will use is blur. This event is a native browser event and gets triggered when the control looses focus. So let's add the following rule to our kss:
  • div#archetypes-fieldname-title input:blur { action-client: alert; } First we use the alert action to see if the event activates. Indeed, if we enter the field and then leave it, the alert popup appears. After this we can call a real server action. Let us name it validateField and create it as a python script from the custom skin. At first make it just to display a message to the upper right corner just like our response1 method did: # import Through-The-Web(TTW) API from kss.core.ttwapi import startKSSCommands from kss.core.ttwapi import getKSSCommandSet from kss.core.ttwapi import renderKSSCommands # start a view for commands startKSSCommands(context, context.REQUEST) # use the parameters txt = '<h1>In validation.</h1><span>at %s</span>' % (DateTime(), ) # add a command core = getKSSCommandSet('core') core.replaceInnerHTML('#portal-siteactions', txt) # render the commands return renderKSSCommands() And change the kss to call it: div#archetypes-fieldname-title input:blur { action-server: validateField; } This is good so far, but now we want to pass the value of the input form to the field. We have a few possibilities for this: formVar('edit_form', 'title') fetches the value by form name and field name. currentFormVar('title') fetches the value by field name, and from the current form. The quot;currentquot; form is selected by the node on which the event has been executed, in this case the input node. currentFormVar() supposes that the node on which the event has been triggered is itself a form control element, and uses its value. You can try all the three possibilities, but for our purposes the third one seems to be the best, since the input node is the same on which the blur event is triggered. So our kss rule will be: div#archetypes-fieldname-title input:blur { action-server: validateField; validateField-value: currentFormVar(); } Then we change our validateField script as follows (and we also add value as a parameter to the script):
  • # import Through-The-Web(TTW) API from kss.core.ttwapi import startKSSCommands from kss.core.ttwapi import getKSSCommandSet from kss.core.ttwapi import renderKSSCommands from kss.core.ttwapi import force_unicode # start a view for commands startKSSCommands(context, context.REQUEST) # use the parameters value = force_unicode(value, 'utf') txt = '<h1>In validation.</h1><span>at %s</span>' % (DateTime(), ) txt +='<br><span>value=<b>quot;%squot;</b>' % (value, ) # add a command core = getKSSCommandSet('core') core.replaceInnerHTML('#portal-siteactions', txt) # render the commands return renderKSSCommands() We can see now that the script really passes the value that is in the title field, when we leave it. How would we validate it? Well, we use Archetypes to do it. This method will return an error in case the field does not validate, or None if it is all right: instance = self field = instance.getField('title') error = field.validate(value, instance, {}) In addition if there is an error, we want the message to appear as a portal status message (orange). (Normally the message would appear as a field status message, but it's ok for us now.) Fortunately this is already implemented in KSS, so we don't have to implement it ourselves! This can be invoked in the following way: plone = getKSSCommandSet(quot;plonequot;) plone.issuePortalMessage(error) This calls something we call a command. It can be thought of a complex command but in fact it can emit a more complex series of commands for sending back to the client. It also does other things, like decides if there is an error at all (and if there is not, it disables the status message), and also does a translation of the thing. The full action script will be this in the end:
  • # import Through-The-Web(TTW) API from kss.core.ttwapi import startKSSCommands from kss.core.ttwapi import getKSSCommandSet from kss.core.ttwapi import renderKSSCommands from kss.core.ttwapi import force_unicode # start a view for commands startKSSCommands(context, context.REQUEST) # use the parameters value = force_unicode(value, 'utf') txt = '<h1>In validation.</h1><span>at %s</span>' % (DateTime(), ) txt +='<br><span>value=<b>quot;%squot;</b>' % (value, ) # add a command core = getKSSCommandSet('core') core.replaceInnerHTML('#portal-siteactions', txt) # do validation and send back result instance = context field = instance.getField('title') error = field.validate(value, instance, {}) plone = getKSSCommandSet(quot;plonequot;) plone.issuePortalMessage(error) # render the commands return renderKSSCommands() To test it in working, let us clear the title. Leave the field: we see the orange message appear. Enter something again, leave the field, and see the message disappear. At the same time, we can see the status updated in the upper right corner too, since we left that part in as well. For a complete experience, we are happy to observe that we can also click on the navtree items and even to the global tabs! In other words: we have not broken our previous programs. Naturally to implement in-place validation with all the archetypes fields, more things are needed. Most importantly, we need a way to be able to bind not only to a single field, but to all the fields (of the same type) at once. In addition we don't only have text inputs but checkboxes, dropdowns and more complex fields like the richtext editor. They all require tedious work on more Archetypes templates. However do not despair: this is already implemented in Plone. To look how it works, you can take a deeper look into the archetypes.kss product, together with the at.kss resource file that is contained by Products/Archetypes. In a following tutorial we will also learn how to use the kssAttr parameter producer function and a single node markup thatr becames effective for entire page regions. For now, however, we conclude the current lesson and enjoy the fruit of our efforts. Because by now we have learned what we need to know if we want to add simple dynamicity to our custom products if we wish to do so. The Zope export of the final results of this tutorial can be downloaded from here. Before importing, rename your existing custom skin to avoid conflicts. Also after importing if the modified template breaks for you, you might want to customize the template from the exact Plone version you use, instead of the one supplied here. Doing it the Zope3 way (homework) We leave this as homework for the kind Reader. One useful hint: in the script we used instance = context, and now in the view we have to use: instance = self.context.aq_inner The aq_inner is in some cases necessary to provide the exactly right acquisition context for the Archetypes scripts.
  • Also when we use the commandset adapters, the parameter for the interface that does the adaptation should be self, not commands (exactly how we did in the previous lessons).