Wiki source code of Skin Extension Tutorial

Last modified by Vincent Massol on 2023/10/10

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 This tutorial demonstrates how to write a XWiki Skin Extension.
6
7 {{velocity}}
8 ## $xwiki.ssx.use("$doc.fullName") ## Load the SSX object held in this document
9 {{/velocity}}
10
11 = Prerequisites =
12
13 * Basic knowledge of application development with XWiki. If this is not the case, we strongly advise you to start with the [[FAQ application tutorial>>Documentation.DevGuide.Tutorials.FAQTutorial.FAQTutorialManual]] or the [[TODO application tutorial>>http://www.theserverside.com/news/1363830/XWiki-A-Platform-for-Collaborative-Apps]].
14 * Your user must be an [[advanced users>>Documentation.UserGuide.Features.PageEditing||anchor="HAdvancedMode"]] (The object editor is available only to advanced users).
15
16 = Introduction to XWiki Skin Extensions =
17
18 XWiki Skins extensions (abbreviated as **SX**) is a mechanism available in XWiki that allows to customize the layout of your wiki, or just some pages of your wiki, without the need of changing its skin templates and/or stylesheets. For this, the [[Skin Extension plugin>>extensions:Extension.Skin Extension Plugin]] (bundled in all XWiki versions superior to 1.5) provides the ability to send to the browser extra JavaScript and CSS files that are not part of the actual skin of the wiki. The code for these //extensions// is defined in [[wiki objects>>Documentation.DevGuide.DataModel.WebHome]].
19
20 To illustrate usage of Skin Extension in XWiki, this tutorial will guide you through the creation of minimal JavaScript and StyleSheet working extensions. Then, will push it further to build a fully functional extension based on Natalie Downe's ##addSizes.js## script.
21
22 A minimal [[JavaScript>>http://en.wikipedia.org/wiki/JavaScript]] and [[CSS>>http://en.wikipedia.org/wiki/CSS]] knowledge is also needed to take full advantage of XWiki Skin Extensions, although expert knowledge in those fields is not needed to follow the tutorial.
23
24 {{info}}
25 If you are interested by the Skin extension mechanism itself and its internals, you should read its [[plugin page>>extensions:Extension.Skin Extension Plugin]], and its [[design page on dev.xwiki.org>>dev:Design.Skin Extensions]]. This tutorial does not address this topic. Or, since this is an Open Source project, feel free to {{scm path="xwiki-platform-core/xwiki-platform-skin/xwiki-platform-skin-skinx"}}browse the code{{/scm}}, and [[propose enhancements or improvements>>dev:Community.Contributing]].
26 {{/info}}
27
28 = My first Skin extension =
29
30 Skin extensions are defined as [[XWiki Objects>>Documentation.DevGuide.DataModel.WebHome||anchor="HXWikiClasses2CObjects2CandProperties"]]. As a consequence, you can create them from your browser.
31
32 Two types of extensions are currently supported:
33
34 * JavaScript extensions (incarnated by XWiki objects of class **XWiki.JavaScriptExtension**), also known as **JSX**
35 * StyleSheet extensions (incarnated by XWiki objects of class **XWiki.StyleSheetExtension**), also known as **SSX**
36
37 The very first step to create an extension is then... to create its object!
38
39 == Minimal JavaScript extension ==
40
41 === Creating an extension object ===
42
43 Point your wiki on the page you want to create your extension in, and edit it with the object editor. The page itself can be any XWiki page - an existing page or a new page. I use in this example the page **XWiki.SkinExt**. From the **New Object** drop-down list of the object editor choose **XWiki.JavaScriptExtension**. Then, click the "Add" button.
44
45 {{image reference="CreateJSXObject.png"/}}
46
47 Once the page is loaded, you should see your extension object in the object list.
48
49 === Writing the extension ===
50
51 Now that the object is available, we can just start writing the actual extension. For this, we will fill in all the fields of the created object. The first one is the extension name. This is easy! We can just write here **Hello World** (this information is only descriptive, it is not actually used by the SX plugin). The next field name is **code**, and this is where we will write the javascript code we want our extension to execute. This extension is supposed to be minimalist, so let's write something very basic here: a traditional greeting alert
52
53 {{code}}
54 alert("Hello World!");
55 {{/code}}
56
57 Now the next field asks us if we want this extension to be used **Always** or **On Demand**. We will explore all the differences between those two modes later in the tutorial, let us for now just precise we want it **On Demand**, which will force us to call the extension explicitly to see it executed.
58
59 Next thing our extension wants to know is if we want its content being parsed or not. This option allows to write **[[velocity code>>Documentation.DevGuide.Scripting.WebHome]]**, for example to dynamically generate the javascript code to be executed. We did not write any velocity, so we can just say **No**. We will see later on an example of an extension with parsed content.
60
61 Finally, we can precise a **caching policy**, to tune the HTTP headers that will be returned with the generated javascript file. Let's not go wild, and chose the **default** one here
62
63 That's it ! our extension is production-ready ! It should by now look like the following:
64
65 {{image reference="MyFirstJSX.png"/}}
66
67 //Note: the "code" area size has been intentionally reduced for this screenshot.//
68
69 === Testing the actual extension ===
70
71 Let's now test the whole thing! Remember we chose that our extension should be used //on demand// ? Well, that's what we are going to do right now. For this we will make a call to the [[Skin Extension plugin>>extensions:Extension.Skin Extension Plugin]]. We can do it for instance in the wiki content of our extension page, or any other page. For this, we edit the target page in Wiki mode, and write the following:
72
73 {{code}}
74 {{velocity}}
75 #set ($discard = $xwiki.jsx.use('XWiki.SkinExt.WebHome'))
76 {{/velocity}}
77 {{/code}}
78
79 Of course, if you did not use this page name for your extension, you should adapt it. Click "Save and View", et voila! If everything is fine, you should see the magic:
80
81 {{image reference="JSXMagic.png"/}}
82
83 You may have noticed that the javascript alert displays before the document is fully loaded in the browser. This is actually expected! If you look close at the generated sources, you will see that your extension has actually been added in the HTML header as any other **.js files** from the skin: (comments added for this tutorial)
84
85 {{code}}
86 <script type="text/javascript" src="/xwiki/skins/albatross/skin.js"></script>
87 <!-- [SNIP] here are all others javascript files from the skin -->
88 <script type="text/javascript" src="/xwiki/bin/skin/skins/albatross/scripts/shortcuts.js"></script>
89
90 <!-- And here is your JSX ! You can open its URL in a browser and see the code -->
91 <script type='text/javascript' src='/xwiki/bin/jsx/XWiki/SkinExt?lang=en'></script>
92 {{/code}}
93
94 You may also note, that the browser is delivered a minified version of the script given in the object's text. This is good practice for the memory of the browser but may make it hard to debug. Using the extra parameter ##debug=true## is a way to prevent it as explained in the [[Debugging Page>>doc:dev:Community.Debugging]].
95
96 == Minimal StyleSheet extension ==
97
98 Good, we wrote our first javascript extension. But, we see things big and we already are looking forward to modify the graphical appearance of wiki pages using those extensions. That's what **StyleSheet extensions** are meant for. And the good news is that it just works the same as javascript extensions, the only difference being that the code written is **CSS code**.
99
100 Create a new page named **XWiki.MyFirstStylesheetExtension**. From the **New Object** drop-down list of the object editor choose **XWiki.StyleSheetExtension**. Then, click the "Add" button. We will name it **Blue Background**, give it a **default** cache policy, ask it not to parse the content, and write the following **code**:
101
102 {{code}}
103 #xwikicontent {
104 background-color: lightBlue;
105 }
106 {{/code}}
107
108 {{info}}
109 If you want to use the colors of your active ColorTheme you can check how to [[call those variables>>extensions:Extension.Color Theme Application||anchor="HUsingColorThemesvariables"]].
110 {{/info}}
111
112 == Put all together ==
113
114 Now let's try something new with this extension. Instead of loading it "On Demand", we can ask to have it used **"Always on this wiki"**. For this to happen however, you need to save the extension document with [[programming rights>>Documentation.AdminGuide.Access Rights.WebHome||anchor="HSpecialpermissions"]].
115
116 Your StyleSheet extension should now look like the following:
117
118 {{image reference="MyFirstSSX.png"/}}
119
120 //Note: the "code" area size has been intentionally reduced for this Screenshot.//
121
122 It's time to test it. No need to call the SkinExtension plugin this time, this is the power of **Use Always** extensions, just click "Save and View" and see the SSX Magic. You can browse your wiki, all pages will be affected by your extension, for example the Main.WebHome page:
123
124 {{image reference="SSXMagic.png"/}}
125
126 Note: if you want to use StyleSheet extension on demand, the principle is the same as for javascript, except that the plugin name is **ssx**, not **jsx**. Just make your call like this, and you are done:
127
128 {{code}}
129 {{velocity}}
130 #set ($discard = $xwiki.ssx.use('XWiki.MyFirstStylesheetExtension.WebHome'))
131 {{/velocity}}
132 {{/code}}
133
134 {{info}}
135 A document can have as many **ssx** or **jsx** object as it needs, but a skin extension is identified by the name of the document, so in the end an extension is a document. The content of a skin extension is the concatenation of the objects in that document, so it's impossible to write two different extensions in a single document, only different parts of the same extension.
136 {{/info}}
137
138 = Real-world extension with addSizes.js =
139
140 {{warning}}
141 This tutorial is kept here for documentation purpose but beware it uses an external JSON-P API service [[previously hosted>>https://simonwillison.net/2008/Jul/29/jsonhead/]] on Google App Engine but not available anymore.
142 {{/warning}}
143
144 Let's now go further with this idea, and build a complete extension that will dynamically add the file type and size next to certain links that are present in a wiki document. This extension will make usage of the **addSizes.js** script published by Natalie Downe. This Javascript snippet itself relies on **json-head**, a Google App Engine application by Simon Willison which //"provides a JSON-P API for running [[HEAD requests>>http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4]] against an arbitrary URL"//. **addSizes.js** consumes this service to dynamically add the file type and size next to links in HTML documents. And this is what we will do in our new extension, using the aforementioned script and service.
145
146 Our new skin extension will be composed of a javascript and a stylesheet extension. We will hold the two objects in the same wiki page, namely **XWiki.AddSizesExtension**.
147
148 Once the document is loaded the javascript extension will be in charge of finding all the interesting links we want to decorate with sizes and file type icons, actually query for their size on the cloud, and finally inject this information next to each concerned link.
149
150 The stylesheet extension will just define the style we want for the extra size information that is injected next to the links.
151
152 The implementation below looks for the following file formats:
153
154 * OpenOffice.org Writer, Calc, and Impress (.odt, .ods, .odp)
155 * The most well known proprietary equivalents of the formats above
156 * Zip archives (.zip)
157 * PDFs (.pdf)
158
159 Of course, this can be adapted to look for other formats that are relevant for your business
160
161 == Requesting and injecting files size with JSX ==
162
163 Our javascript extension will be composed of two code snippets. The second one will be the actual addSizes.js code, ported to work with Prototype instead of jQuery. The first one is a function needed by this portage.
164
165 AddSizes.js relies on the [[JSON with padding technique>>http://en.wikipedia.org/wiki/JSONP]] to query the **json-head** service, which is located on a different domain than the wiki, in a transparent manner. An alternative to this would be to have a similar service on the wiki itself (for example, in the [[groovy language>>Documentation.DevGuide.Scripting.WebHome||anchor="HGroovy"]]), and query it using a traditional AJAX request. [[Prototype.js>>http://prototypejs.org/]], the javascript framework bundled with XWiki, does not yet provide support for JSON-P requests. To do JSON-P requests, we will use a code snippet by Yuriy Zaytsev written for this purpose. Let's first paste his code in a new **JSX** object, in **XWiki.AddSizesExtension** :
166
167 {{code}}
168 // getJSON function by Juriy Zaytsev
169 // http://github.com/kangax/protolicious/tree/master/get_json.js
170 (function(){
171 var id = 0, head = $$('head')[0], global = this;
172 global.getJSON = function(url, callback) {
173 var script = document.createElement('script'), token = '__jsonp' + id;
174
175 // callback should be a global function
176 global[token] = callback;
177
178 // url should have "?" parameter which is to be replaced with a global callback name
179 script.src = url.replace(/\?(&|$)/, '__jsonp' + id + '$1');
180
181 // clean up on load: remove script tag, null script variable and delete global callback function
182 script.onload = function() {
183 script.remove();
184 script = null;
185 delete global[token];
186 };
187 head.appendChild(script);
188
189 // callback name should be unique
190 id++;
191 }
192 })();
193 {{/code}}
194
195 With this we can now have a prototype version of addSizes.js. We can just paste this second snippet under the first one in the **code** area of our extension object, or add a new JavaScriptExtension object to the page (as SX combines all the objects of the same page into a single response):
196
197 {{code}}
198 // addSizes was written by Natalie Downe
199 // http://natbat.net/2008/Aug/27/addSizes/
200 // ported to prototype.js by Jerome Velociter, and adapted to XWiki for this tutorial
201
202 // Copyright (c) 2008, Natalie Downe under the BSD license
203 // http://www.opensource.org/licenses/bsd-license.php
204
205 Event.observe(window, 'load', function(event) {
206 $('xwikicontent').select(
207 'a[href$=".pdf"], a[href$=".doc"], a[href$=".zip"], a[href$=".xls"], a[href$=".odt"], a[href$=".ods"], a[href$=".odp"], a[href$=".ppt"]')
208 .each(function(link){
209 var bits = link.href.split('.');
210 var type = bits[bits.length -1];
211
212 var url = "http://json-head.appspot.com/?url="+encodeURIComponent (link.href)+"&callback=?";
213
214 getJSON(url, function(json){
215 var content_length = json.headers['Content-Length'];
216 if(!content_length) {
217 content_length = json.headers['content-length'];
218 }
219 if(json.ok && content_length) {
220 var length = parseInt(content_length, 10);
221
222 // divide the length into its largest unit
223 var units = [
224 [1024 * 1024 * 1024, 'GB'],
225 [1024 * 1024, 'MB'],
226 [1024, 'KB'],
227 [1, 'bytes']
228 ];
229
230 for(var i = 0; i < units.length; i++){
231
232 var unitSize = units[i][0];
233 var unitText = units[i][1];
234 if (length >= unitSize) {
235 length = length / unitSize;
236 // 1 decimal place
237 length = Math.ceil(length * 10) / 10;
238 var lengthUnits = unitText;
239 break;
240 }
241 }
242
243 // insert the text in a span directly after the link and add a class to the link
244 Element.insert(link, {'after':
245 ' <span class="filesize">(' + length + ' ' + lengthUnits + ')</span>'});
246 link.addClassName(type);
247 }
248 });
249 }); // each matched link
250 });
251 {{/code}}
252
253 This is it! At this point, the extension should be already functional. If you test it now, you should be able to see the size of links getting injected next to matching each link in the content of a wiki document.
254
255 We will now make this information look nicer, and add an icon to represent the file type of the link, thanks to a stylesheet extension.
256
257 == Making it look nice with SSX ==
258
259 This time, we will take advantage of the **Parse** attribute of extensions that has been evoked earlier in this tutorial. This way, we can be lazy and generate the CSS code using the velocity templating language, instead of writing a rule for each file format manually. Thanks to velocity and to the XWiki api, we will also be able to reference images attached to the extension document.
260
261 The class name that is added to each matching link by the JSX is actually the matching file extension itself (doc, pdf, etc.). Thus, we can then iterate over the extensions that we target and generate a rule for each one of them. And more, if we name our icons with the convention of using the file extension, we can also reference the image within the same iteration.
262
263 An archive with the set of icons used for this tutorial can be [[downloaded here>>attach:addSizesIcons.zip]]. The icons for MS products, for zip and pdf files are from the **[[Silk Icons Set>>http://www.famfamfam.com/lab/icons/silk/]]** by Mark James, available under the [[Creative Commons Attribution 2.5 License>>http://creativecommons.org/licenses/by/2.5/]]. To add the icons to your extension, just unzip the archive and attach them manually to your **XWiki.AddSizesExtension** document. Of course, you can also use your own set of icons. If you change the name of the files however, keep in mind that you will have to adapt the stylesheet extension below.
264
265 Once you have the icons attached, create the stylesheet extension, set its parse attribute to **Yes**, and paste this code in the "Code" section:
266
267 {{code language="velocity"}}
268 /* A little padding on the right of links for the icons to fit */
269 #foreach($ext in ['odt', 'ods', 'odp', 'doc', 'xls', 'ppt', 'pdf', 'zip'])
270 #xwikicontent a.${ext} #if($velocityCount < 8), #end
271 #end {
272 padding-right:20px;
273 }
274
275 /* File icons as background for the links */
276 #foreach($ext in ['odt', 'ods', 'odp', 'doc', 'xls', 'ppt', 'pdf', 'zip'])
277 #xwikicontent a.${ext} {
278 background:transparent url($doc.getAttachmentURL("${ext}.png")) no-repeat scroll right center;
279 }
280 #end
281
282 /* Nice spans for file size information */
283 #xwikicontent span.filesize {
284 font-size: 0.6em;
285 background-color:lightYellow;
286 }
287 {{/code}}
288
289 When asked to serve the CSS file, XWiki will evaluate this code using its Velocity Rendering engine, and will return a file that contains pure CSS code!
290
291 == Testing the final extension ==
292
293 Ok, it's time for us to see the whole thing in action! The snippet below is intended to showcase the extension on its own wiki page. It requests the **jsx** and **ssx** plugins to load javascript and css objects we created and showcases the links.
294
295 {{code}}
296 {{velocity}}
297 #set($discard = $xwiki.jsx.use($doc.fullName))
298 #set($discard = $xwiki.ssx.use($doc.fullName))
299 {{/velocity}}
300
301 * [[An OpenOffice.org Writer document>>http://pt.openoffice.org/coop/ooo2prodspeca4pt.odt]]
302 * [[A MS Word document>>http://www.microsoft.com/hiserver/techinfo/Insurance.doc]]
303 * [[An OOo Spreadshet>>http://documentation.openoffice.org/Samples_Templates/Samples/Business_planner.ods]]
304 * [[Link to a MS Excel document>>http://www.microsoft.com/MSFT/download/financialhistoryT4Q.xls]]
305 * [[An OOo Presentation>>http://pt.openoffice.org/coop/ooo2prodintroen.odp]]
306 * [[Link to a MS Powerpoint file>>http://research.microsoft.com/ACM97/nmNoVid.ppt]]
307 * [[A great archive>>http://maven.xwiki.org/releases/com/xpn/xwiki/products/xwiki-enterprise-jetty-hsqldb/2.0/xwiki-enterprise-jetty-hsqldb-2.0.zip]]
308 * [[A PDF file>>http://www.adobe.com/motion/pdfs/sjsu_fumi_ss.pdf]]
309 {{/code}}
310
311 Now there are two things to keep in mind:
312
313 * The browser must be able to reach the Internet, since the extension does need the help of the json-head service hosted on Google App Engine.
314 * As Natalie Downe wrote, //"this may not be 100% reliable due to App Engine being occasionally and unavoidably flakey"//. You may for example experience a long loading time (but since the extension triggers only once the whole wiki document is loaded, this will not penalize the wiki users).
315
316 In a future extension of this tutorial, we will address these two issues. We will write our own version of the json-head service on the wiki itself, using the [[groovy programming language>>Documentation.DevGuide.Scripting.WebHome||anchor="HGroovy"]].
317
318 Enough talk, let's see the result!
319
320 {{image reference="AddSizesMagic.png"/}}
321
322 == Bonus: links to activate/deactivate the extension ==
323
324 [[image:bonus.gif]]
325
326 You can add this snippet in the content section of the extension document, and users with the programming right granted will be provided a link to activate or not the extension on all pages of the wiki:
327
328 {{code}}
329 {{velocity}}{{html}}
330 #if($xwiki.hasAccessLevel('programming', $context.user)) ## Only programmers should be able to change the loading type
331 ## otherwise, Always-used extensions will not work
332
333 #if($doc.getObject('XWiki.JavaScriptExtension').getProperty('use').value == 'always')
334 #info('This extension is configured to be loaded on all the pages of this wiki.')
335
336 <span class=buttonwrapper>
337 <a href="$doc.getURL('save','XWiki.JavaScriptExtension_0_use=onDemand&XWiki.StyleSheetExtension_0_use=onDemand')">
338 De-activate loading for all pages.
339 </a>
340 </span>
341 #else
342 #info('This extension is configured to be loaded only on pages that request it.')
343
344 <span class=buttonwrapper>
345 <a href="$doc.getURL('save','XWiki.JavaScriptExtension_0_use=always&XWiki.StyleSheetExtension_0_use=always')">
346 Activate loading for all pages.
347 </a>
348 </span>
349 #end
350
351 #end
352 {{/html}}{{/velocity}}
353 {{/code}}
354
355 = Additional Details =
356
357 == How to use Velocity in parsed content ==
358
359 Example for **XWiki.JavaScriptExtension** code:
360
361 {{code}}
362 #if (!$xcontext.userReference)
363 alert("Hello guest!");
364 #else
365 alert("Hello user!");
366 #end
367 {{/code}}
368
369 will show different alerts for different users on page refresh.
370
371 Example for **XWiki.StyleSheetExtension** code:
372
373 {{code}}
374 #if (!$xcontext.userReference)
375 #mainContentArea {
376 background-color: grey;
377 }
378 #else
379 #mainContentArea {
380 background-color: blue;
381 }
382 #end
383 {{/code}}
384
385 will show different background colors for authenticated and anonymous users.
386
387 Velocity doesn't know about CSS. Everything that looks like a Velocity macro or variable is evaluated. So, undefined macros and
388 variables are printed as is. E.g. **#xwikicontent** will be used as CSS ID field, unless **xwikicontent** Velocity macro is defined.
389
390 Velocity is a template language, so when the Velocity code is evaluated the Velocity variables are simply substituted in the template by their values. So:
391
392 {{code}}
393 alert(false);
394 {{/code}}
395
396 works because **false*** is a literal boolean value in JavaScript.
397
398 Though:
399
400 {{code}}
401 alert($xcontext.user);
402 {{/code}}
403
404 will be evaluated e.g. as **alert(XWiki.User);** which will throw an exception unless **User** is a JavaScript variable previously defined, therefore you need to wrap the value in quotes, e.g.:
405
406 {{code}}
407 alert('$xcontext.user');
408 {{/code}}
409
410 Or even more, because the value coming from Velocity can contain characters that are not allowed in a JavaScript string literal. So safest is to write:
411
412 {{code}}
413 alert('$escapetool.javascript($xcontext.user)');
414 {{/code}}
415
416 {{info}}
417 You can use XWiki global velocity variables **$doc**, **$request** and **$xcontext** in parsed content.
418 {{/info}}
419
420 == How to communicate data to JSX or SSX? ==
421
422 It's often useful to communicate data to JSX or SSX in order to have them serve some customized content depending on the context, the user, data from the page etc.
423
424 === How to communicate data to JSX ===
425
426 There are three main ways to communicate data to JSX:
427
428 * Via the [[JSX plugin>>extensions:Extension.Skin Extension Plugin]], using the map parameter in a Velocity script: {{code}}$xwiki.jsx.use('Document.Name', {'minify' : false, 'language': $context.language, 'myParameter': 'value'}){{/code}}. The parameters are then available as query parameters, so it can be used in a Javasript Object by calling {{code}}$request.myParameter{{/code}} - this is the recommended practice.
429 * By reading data directly in the HTML page from the JSX (jQuery or Prototype can be helpful to do this).
430 * By setting an attribute in the user session in Velocity, and reading this attribute in Velocity from the JSX. However, this approach has several downsides: the variable will be cached by the server (except if you disable the JSX cache, but this can raise performance issues), and it can bloat the session.
431
432 === How to communicate data to SSX ===
433
434 Data can also be communicated to SSX in the same ways as to JSX, except the second option (accessing HTML elements from the page), since CSS cannot read data from the HTML page.
435
436 = LESS =
437
438 Since XWiki 6.4M2, you can use [[LESS>>http://www.lesscss.org/]] in your Skin Extensions. Get a look to [[extensions:Extension.Skin Extension Plugin||anchor="HUseLESS"]] for more information.
439
440 When you add styles to your application, there are several languages you can use: ##CSS##, ##LESS##, ##Velocity##. What language to use depends on if you need advanced functions, access to the XWiki API, if you need to reuse existing variables, etc.
441
442 For example, depending on where your code is (in an ##SSX##, in a skin or in a ##LESS## file), you can use the [[old Color Themes variables>>extensions:Extension.Color Theme Application||anchor="HUsingColorThemesvariables"]] (needs ##Velocity## to be parsed) or the [[Flamingo Theme variables>>extensions:Extension.Flamingo Theme Application||anchor="HUsingthemevariables"]] (needs ##LESS## to be compiled).
443
444 If you are not sure what to use and where, here is a small summary:
445
446 (% class="table table-bordered" %)
447 | | CSS                                             | LESS                                                   | Velocity
448 |CSS      | SSX-CSS; Skin style.css; Web Resources          | SSX-LESS; ColorTheme Advanced; Skin LESS files; Bootstrap/XWiki variables;| SSX-CSS-Parse; Skin style.css; $theme variables
449 |LESS     | SSX-LESS; ColorTheme Advanced; Skin LESS files; Bootstrap/XWiki variables; | SSX-LESS; ColorTheme Advanced; Skin LESS files; Bootstrap/XWiki variables;        | SSX-LESS-Parse; style.less.vm; $theme variables; Bootstrap/XWiki variables;
450 |Velocity | SSX-CSS-Parse; Skin style.css; $theme variables | SSX-LESS-Parse; style.less.vm; $theme variables; Bootstrap/XWiki variables;| ##{ {Velocity} }## macro; Generic Templates; Skin Templates
451
452 = References =
453
454 * [[extensions:Extension.Skin Extension Plugin]]
455 * [[JSON with Padding>>http://en.wikipedia.org/wiki/JSONP]]
456 * [[HTTP HEAD Request>>http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4]]
457 * [[get_json.js>>https://github.com/kangax/protolicious/blob/master/get_json.js]]
458 * [[json-head>>http://json-head.appspot.com/]]

Get Connected