Tutorial: creating editable web pages with AXEL
By S. Sire, Oppidoc, (Last edited 2013-08-21)
This document describes the new API (since AXEL 1.3.2) to generate document editors inside web application pages and to interact with the editors to load and save XML data. This API is based on a $axel object, it supersedes the legacy API based on the xtiger.util.form object.
The $axel
object is a wrapper function that takes a node set specification and returns a wrapped set object supporting several functions that act on the first element (or on all elements) of the node set. This tutorial presents the transform
, load
and save
functions that act on the first element of the wrapped set to respectively generate an editor, load XML data into it, and serialize XML data from it.
It is very convenient to use the $axel object together with the jQuery library since you can then directly pass a node set specification as a jQuery selector string or as a jQuery wrapped set. You can still use the $axel object without jQuery if you pass it directly DOM node(s) to interact with. For latest information about the $axel object and for a description of other wrapped set functionalities you may consult its wiki page.
Step 1 : write an XTiger XML template
Follow the XTiger XML specification. You can use the Template.xhtml provided with the distribution as a skeleton (show source). For a quick introduction to XTiger XML you may also have a look at this executive summary.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xt="http://ns.inria.org/xtiger"> <head> <!-- Use UTF-8 and omit xml protocol at the top for IE --> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>XTiger XML with AXEL tutorial</title> <xt:head version="1.1" templateVersion="1.0" label="greetings"> <xt:component name="personList"> <ul> <xt:repeat minOccurs="0" maxOccurs="*" label="persons"> <li> <xt:use types="text" param="shape=parent-75px;type=textarea" label="name">name</xt:use><xt:menu-marker/> </li> </xt:repeat> </ul> </xt:component> </xt:head> <!-- See Step 2 --> </head> <body> <div id="document"> <p class="template">Template directly embedded within this page</p> <p>List of persons to great:</p> <xt:use types="personList"/> </div> </body> </html>
The best compromise to be able to open your Web page in standard compliant browsers AND in Internet Explorer is to save the file as UTF-8 and to omit the xml protocol declaration (i.e. <?xml version="1.0" encoding="UTF-8"?>). So the file should start directly with an XHTML DOCTYPE declaration. It is recommended to read the HTML Compatibility Guidelines from the XHTML 1.0 Recommendation before writing template files. You may also use the XHTML syntax of HTML5.
It is wised to specify the content-type with a meta element, this improves MIME-Type sniffing when opening the file directly from a local file system on Internet Explorer. If you access the file from a Web server, you should serve it with a MIME TYPE set to application/xhtml+xml, except for Internet Explorer that only supports text/html when the template is also the Web page (variant a in Step 3). Eventually you can specify Microsoft X-UA-Compatible property to tell Internet Explorer to open the page in a specific mode, currently AXEL works with IE starting from 7, and on all other major browsers (at least Firefox, Safari, Chrome, Opera).
Step 2 : include AXEL library inside your web application page
You need to include both the axel.js
file containing the library and the corresponding axel.css
file. In addition the bundles
path of the AXEL distribution must be avalaible on your server (show source)
<script type="text/javascript" src="{PATH-TO}/axel/axel.js"></script> <link rel="stylesheet" href="{PATH-TO}/axel/axel.css" type="text/css"></link>
The src
attribute of the script
element must point to the location where you have copied the AXEL library file axel.js
which is distributed inside the axel/
folder.
You must include the global AXEL CSS file axel.css
which is distributed under the same axel/
folder (excepted if you are applying variant C of step 3).
Finally you also must make the axel/bundles/
folder of the AXEL distribution available and provide a bundlesPath parameter to the $axel.setup
function with a string path (relative to your web application page) leading to the bundles
folder of the AXEL distribution.
You can generate a new AXEL library file with your own selection of plugins by editing the scripts/build.xml
file and with a build.lib target with ant (cd scripts; ant build.lib). This supposes you have installed the Yahoo UI compressor and set its path into the javascript.compressor variable in the file scripts/ant.properties
. Otherwise you may generate an uncompressed version of the library with the ant build.debug
target.
Step 3 : write the template transformation code inside your web application page
Variant a : your web page is also the XTiger XML template page
The div
below contains some XTiger XML declarations which have been transformed immediately after page loading through a registered load
event handler (show source). Consequently you can edit a list of persons :
Template self-contained into this page
List of persons to great:
To generate the editor defined in the <div id="embedded"> element in a web application page that is also an XTiger XML template, you can write :
<script type="text/javascript"> function init() { var n = document.getElementById('embedded'); $axel.setup({ bundlesPath : '{PATH-TO}/axel/bundles', enableTabGroupNavigation: true }); $axel(n).transform(); } xtdom.addEventListener(window, 'load', init, false); </script>
Alternatively if you have included jQuery inside your web page, you can directly pass a jQuery selector string to the $axel
object :
<script type="text/javascript"> function initApp() { $axel.setup({ bundlesPath : '{PATH-TO}/axel/bundles', enableTabGroupNavigation: true }); $axel('#embedded').transform(); } jQuery(function() { initApp(); }); </script>
Note that if you want to treat the whole page as a single editor, you can directly pass the document
object to the $axel
object such as :
$axel(document).transform()
You can also directly pass the AXEL setup as an option hash to the transform
method :
$axel('#embedded').transform({ bundlesPath : '{PATH-TO}/axel/bundles', enableTabGroupNavigation: true })
You can call the $axel.setup
function in a page load event handler or right before generating an editor. Note that you can also initialize some other globals by the same occasion, such as enableTabGroupNavigation to enable tab navigation between editor's fields, however this functionality is not yet fully functional on all browsers. The settings are memorized so that you just need to set them once even if you use the $axel object multiple times.
Note that the xtdom.addEventListener
function is an AXEL function to register event handlers, you may of course use any other function, especially since that function may be deprecated in the future.
Variant b : your web page contains a div that you fill with an external XTiger XML template
Click to load and transform the external Hello-world.xhtml XTiger XML template into the div
below (show source) :
Template dynamically loaded with Ajax
To generate the editor defined by the "Hello-world.xhtml" template inside the <div id="placeholder"> element of a web application page, you can write :
$axel('#placeholder').transform('Hello-world.xhtml')
Once again you can also directly pass the AXEL setup as an auxiliary option hash to the transform
method :
$axel('#placeholder').transform('Hello-world.xhtml', { bundlesPath : '{PATH-TO}/axel/bundles', enableTabGroupNavigation: true })
By passing a string to the transform
function you instruct the $axel object to first retrieve an XTiger XML document at the URL/path specified by the string, and then to transform it and to copy the result of the transformation inside the first element of the wrapped set passed to the $axel object.
The URL/path string is passed to an internal xtiger.cross.loadDocument
function to retrieve the XTiger XML document. It uses an XHR object in synchronous mode. Alternatively you can use your own function to first load the XTiger XML document. In that case you just need to pass the resulting XML Document object (typically your own xhr.responseXML
if you are using XHR) to the transform
function instead of a URL/path string.
Variant c : your web page contains an iframe that contains an external XTiger XML template
Click to transform the XTiger XML template inside the iframe below into an editor (show source) :
Template inside an iframe
To generate the editor defined by the "Hello-world.xhtml" template inside the <iframe id="iframe" src="Hello-world.xhtml"> element of a web application page, you can write :
$axel('#iframe').transform({ injectStylesheet : '{PATH-TO}/axel/axel.css' })
Once again you can also directly pass the AXEL setup as supplementary options in the options hash parameter of the transform
method :
$axel('#iframe').transform({ bundlesPath : '{PATH-TO}/axel/bundles', enableTabGroupNavigation: true, injectStylesheet : '{PATH-TO}/axel/axel.css' })
By invoking the transform
method on an $axel object wrapping an iframe
element, it will treat the content of the iframe
as an XTiger XML template and transform it into an editor. You do not need to include the AXEL Javascript file into the XTiger XML template as using this procedure the iframe
content will be transformed by the AXEL library of the parent window (your web application page). However you should pass an option hash to the transform
method with an injectStylesheet key containing the path leading to the AXEL css file so that it will be automatically injected by the $axel
object into the iframe
document. You may omit this if your template directly imports the AXEL css file, but it is usually more convenient to write XTiger XML templates independent of the AXEL library code location.
The advantage of this approach is that the XTiger XML template may include its own CSS files that you do not need to include in the parent web application page.
NOT YET AVAILABLE : Note that you can also pass a URL/path string to the transform
function. In that case it will set the src attribute of the iframe
element to that string and register a load
event handler on the iframe to transform its content when loaded.
Any eventual nested iframe
elements inside the iframe
document will not be transformed using this method !
Step 4 : Load an XML document (sample.xml) into the editor
The procedure to load XML content into an editor generated with the transform
method is independant of the variant. You just need to call the load
method that applies to the first element of the $axel
wrapped set. It takes as input either a complete XML string containing a valid XML document, a URL/path string pointing to an XML document, or an XML document object (show explanation)
Variant a | Variant b | Variant c |
---|---|---|
Click to load data into variant a editor (show source) $('#embedded').load('sample.xml') |
Click to load data into variant b editor (show source) $('#placeholder').load('sample.xml') |
Click to load data into variant c editor (show source) $('#iframe').load('sample.xml') |
A string containing an XML document is parsed first into an XML document using internal methods and then wrapped into an internal xtiger.util.DOMDataSource
that is passed to the XML loading algorithm.
A URL/path string to an XML document is first loaded with an internal xtiger.cross.loadDocument
function in synchronous mode and then treated as in the first case.
An XML document is directly wrapped into an internal xtiger.util.DOMDataSource
that is passed to the XML loading algorithm.
Step 5 : Dump the XML document from the editor
The procedure to dump XML content from an editor generated with the transform
method is independant of the variant. You just need to call the xml
method that applies to the first element of the $axel
wrapped set. It will return a string with the current XML content of the editor (show explanation)
Variant a | Variant b | Variant c |
---|---|---|
Click to copy the XML data from variant a editor in the $('#xml-a').text($axel('#embedded').xml()) |
Click to copy the XML data from variant a editor in the $('#xml-b').text($axel('#placeholder').xml()) |
Clickto copy the XML data from variant a editor in the $('#xml-b').text($axel('#iframe').xml()) |
The xml
function returns the editor's content into an XML string. You are then free to do whatever you want with that string such as submitting it to a server using an XMLHTTPRequest object or any higher level-library for doing XHR (e.g. jQuery's $.ajax function with data set to the XML string and contentType set to "application/xml; charset=UTF-8"
). For instance in the code above we use jQuery's text
function to paste the XML string into a div
The xml
function may be invoked on any wrapped set. It will serializes to XML the content below the first node. If that first node has not been transformed to an editor using the transform
function the xml
function will arbitrarily generate a data
root node (since it cannot guess the root node) and serialize whatever it can find inside. This is a way to serialize partial content of an editor. The roadmap is to better support partial serialization with some extra parameters to control the name of the root node and/or disable its generation.
A current limitation is that if you are trying to serialize a sub-tree containing several generated editors, the xml
function behavior is not specified. For instance Click to see the effect of $axel(document).xml()
on this document that contains several editors.
Note : Error reporting
Currently the errors raised inside $axel
functions are reported by an internal function that shows an alert with the error message. You can overwrite that function by providing a new error function handler to the error
parameter of the global settings. The function receives a single string parameter containing an error message (currently in english, the localization of error messages is on the roadmap).
For instance Click to load the content of an unkown file inside the variant a editor using the internal XHR request: that should result in an error.
The roadmap is to add success as well as error callbacks to the $axel
object to report success / error with some more complete description of the conditions.
Annexe 1 : Compatibility with AXEL-FORMS
To use the AXEL-FORMS companion library you just need to include the axel-forms.js
script after the axel.js
script in your web page as described in Step 2.
The load
and xml
functions of the $axel
object are compatible with AXEL-FORMS out of the box. The transform
function may require some extra initialization steps for some functionalities.
There is no extra initialization step to use the primitive editors and filters from AXEL-FORMS (e.g. 'choice' or 'input' plugins, 'select2' filter).
If you want to use some primitive editors from AXEL-FORMS with some filters from AXEL you may need to explicitely register them by calling code such as $axel.filter.applyTo({ 'optional' : 'input', 'event' : 'input' })
before calling the transform
function. For instance the previous example extends AXEL-FORMS 'input' plugin with AXEL 'optional' and 'event' filters.
If you are using some commands and bindings from AXEL-FORMS, you SHOULD use the 'template' command to generate the editor instead of the $axel.transform
function. This is preferable since as a side effect it will extend primitive editors from AXEL-FORMS with filters from AXEL as explained in the previous paragraph, and perform some extra initializations required by the commands and bindings. However if you still want to use the transform
function then you should manually do those initializations immediately after the template transformation. More explanations to come.
The roadmap is to reduce the extra initialization steps to nothing more than inclusion of axel-forms.js
.
Annexe 2 : Beyond creating editors, creating transformation chains with client-side XSLT
You can use the xml
function to retrieve the xml content of the editor. Then you can generate a new XML document to be processed by a client-side XSLT processor such as Saxon-CE.
More explanations to come.