Wiki source code of Generate Skin Extension Content with Velocity
Last modified by Eleni Cojocariu on 2026/09/30 18:26
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | Generate a [[skin extension>>doc:documentation.xs.dev.skin-extensions.WebHome]]'s code with Velocity instead of writing it by hand, so one extension can serve different CSS or JavaScript depending on the reader, on the request and on the pages around it. | ||
| 2 | |||
| 3 | 1. Set the object's [["Parse content" field>>doc:documentation.xs.dev.skin-extensions.object-fields.WebHome]] to "Yes". What the browser receives is still plain CSS or JavaScript: the Velocity runs on the server, and none of it is left in the served file. | ||
| 4 | 1. Three global Velocity variables are available in parsed content: ##$doc##, ##$request## and ##$xcontext##. ##$doc## is the page holding the extension object, not the page being read, which is what makes the attachments of the extension page addressable from its own code. | ||
| 5 | 1. Keep in mind that Velocity knows nothing about CSS. Everything shaped like a directive or a reference is evaluated, and anything undefined is printed unchanged, which is the only reason a CSS id survives at all. Define a macro of that name in the same content and the selector is eaten, with nothing to say so:((( | ||
| 6 | {{code language="velocity"}} | ||
| 7 | #macro (xwikicontent)/* eaten */#end | ||
| 8 | #xwikicontent { background-color: lightBlue; } | ||
| 9 | {{/code}} | ||
| 10 | |||
| 11 | That is served as: | ||
| 12 | |||
| 13 | {{code language="css"}} | ||
| 14 | /* eaten */ { background-color: lightBlue; } | ||
| 15 | {{/code}} | ||
| 16 | ))) | ||
| 17 | 1. Serve different content per reader with the ordinary directives. In a JavaScript extension:((( | ||
| 18 | {{code language="velocity"}} | ||
| 19 | #if (!$xcontext.userReference) | ||
| 20 | alert("Hello guest!"); | ||
| 21 | #else | ||
| 22 | alert("Hello user!"); | ||
| 23 | #end | ||
| 24 | {{/code}} | ||
| 25 | |||
| 26 | and in a style sheet extension: | ||
| 27 | |||
| 28 | {{code language="velocity"}} | ||
| 29 | #xwikicontent { | ||
| 30 | #if (!$xcontext.userReference) | ||
| 31 | background-color: #f5f5f5; | ||
| 32 | #else | ||
| 33 | background-color: #ffffff; | ||
| 34 | #end | ||
| 35 | } | ||
| 36 | {{/code}} | ||
| 37 | |||
| 38 | An extension that varies like this must not be [[cached>>doc:documentation.xs.dev.skin-extensions.object-fields.WebHome]]: its address is the same for every reader. | ||
| 39 | ))) | ||
| 40 | 1. Escape every value that reaches the JavaScript, because Velocity substitutes it as code and not as text. ##alert(false);## works, ##false## being a literal in both languages, but ##alert($xcontext.user);## arrives as ##alert(XWiki.Admin);##, which the browser reads as a property of the global ##XWiki## object it already defines: it alerts ##undefined## instead of the user name. Quoting alone is not enough either, since a value may hold characters that are illegal inside a JavaScript string. The rule is:((( | ||
| 41 | {{code language="velocity"}} | ||
| 42 | alert('$escapetool.javascript($xcontext.user)'); | ||
| 43 | {{/code}} | ||
| 44 | ))) | ||
| 45 | 1. Build the address of a file rather than hard-coding it. This is the most common reason a background image never appears: with "Parse content" left at "No" the code building the address is never evaluated. A file on disk is looked up in the current skin, then the base skin, the default skin and the templates directory, which [[Skins>>doc:documentation.xs.admin.skins.WebHome]] describes:((( | ||
| 46 | {{code language="velocity"}} | ||
| 47 | background-image: url($xwiki.getSkinFile("path/to/the/image.png")); | ||
| 48 | {{/code}} | ||
| 49 | |||
| 50 | An attachment of the extension page itself, and an attachment of any other page: | ||
| 51 | |||
| 52 | {{code language="velocity"}} | ||
| 53 | background-image: url($doc.getAttachmentURL("image.png")); | ||
| 54 | background-image: url($xwiki.getDocument("Some.Document").getAttachmentURL("image.png")); | ||
| 55 | {{/code}} | ||
| 56 | |||
| 57 | Naming the attachments after what they decorate then lets one loop write a rule per case instead of one rule per file: | ||
| 58 | |||
| 59 | {{code language="velocity"}} | ||
| 60 | #foreach ($ext in ['odt', 'ods', 'pdf', 'zip']) | ||
| 61 | #xwikicontent a.$ext { | ||
| 62 | background: transparent url($doc.getAttachmentURL("${ext}.png")) no-repeat scroll right center; | ||
| 63 | } | ||
| 64 | #end | ||
| 65 | {{/code}} | ||
| 66 | ))) | ||
| 67 | 1. Pass the extension something to work with. There are three ways, and the first is the one to use:((( | ||
| 68 | {{code language="velocity"}} | ||
| 69 | #set ($discard = $xwiki.jsx.use('My.JavaScript', {'myParameter': 'value'})) | ||
| 70 | {{/code}} | ||
| 71 | |||
| 72 | * The parameter map of the pull call. Its entries reach the extension as request parameters, read in the content as ##$request.myParameter##; [[Skin Extension Script API>>doc:documentation.xs.dev.skin-extensions.script-api.WebHome]] documents the map and the four names that already mean something. | ||
| 73 | * Reading the data out of the HTML page, from the JavaScript itself. | ||
| 74 | * Setting an attribute in the user session and reading it back in the parsed content. This one costs on both counts: the value is cached by the server unless caching is turned off, and the session grows. | ||
| 75 | |||
| 76 | All three serve a style sheet extension too, except the second: CSS cannot read anything out of the page. | ||
| 77 | ))) | ||
| 78 | 1. Read the generated file back to see what the browser gets. Its address is the one written into the ##head## element of any page that pulls it, ##/xwiki/bin/ssx/My/CSS## for a style sheet and ##/xwiki/bin/jsx/My/JavaScript## for a script; add [[##minify=false##>>doc:documentation.xs.dev.skin-extensions.script-api.WebHome]] to it and the Velocity is already gone:((( | ||
| 79 | {{code language="css"}} | ||
| 80 | #xwikicontent { | ||
| 81 | background-color: #ffffff; | ||
| 82 | } | ||
| 83 | #xwikicontent a.pdf { | ||
| 84 | background: transparent url(/xwiki/bin/download/Sandbox/ParsedStyleSheet/WebHome/pdf.png) no-repeat scroll right center; | ||
| 85 | } | ||
| 86 | {{/code}} | ||
| 87 | ))) |