XWiki JavaScript API
- Observable XWiki Events
- DOM Events (xwiki.js)
- Document content events (actionButtons.js)
- Action events (actionButtons.js)
- Document extra events (xwiki.js)
- Suggest events (ajaxSuggest.js)
- Fullscreen events (fullScreenEdit.js)
- Annotations events (AnnotationCode/Settings jsx)
- Livetable events (livetable.js)
- Live Data events (Logic.js)
- RequireJS and jQuery APIs
- Get some information about the current document (Since 6.3M2)
- Work with Entity References (Since 4.2M1)
- Related
- Example of understanding initialization
Observable XWiki Events
Stay in touch with what happens in the wiki! XWiki will fire custom javascript events on certain moment and upon certain actions that occur in the navigation flow.
Event names are build on the following model: xwiki:modulename:eventname. Your JavaScript script or extension can get notified of such an event the following way:
// Here, do something that will be executed at the moment the event is fired
doSomething();
// The event can have an option memo object to pass to its observers some information:
console.log(event.memo.somethingINeedToKnow);
});
Check out the real examples below or read more about Prototype.js's event system.
DOM Events (xwiki.js)
xwiki:dom:loaded
This event is similar to prototype's dom:loaded event, with the difference that in the time-lapse between dom:loaded and xwiki:dom:loaded, XWiki may have transformed the DOM. Example of DOM transformations operated by XWiki is setting the right target of links that have rel="external" attribute so that the document can be XHTML valid and still have the desired effect, making internal rendering error messages expandable, insert document template handlers for links to non-existent documents, and so on. In the future there might be more transformations operated by XWiki upon DOM initialization. This event is meant for code to be notified of loading of the XWiki-transformed version of the initial DOM. As dom:loaded, it can be used as follows:document.observe("xwiki:dom:loaded", function(){
// Initialization that can rely on the fact the DOM is XWiki-tranformed goes here.
});
- xwiki:dom:loading
xwiki:dom:loading is sent between dom:loaded and xwiki:dom:loaded, before XWiki changes the DOM. This is the event that should start all scripts making important DOM changes that other scripts should see. - xwiki:dom:updated
This event is sent whenever an important change in the DOM occurs, such as loading new content in a dialog box or tab, or refreshing the document content. Scripts that add behavior to certain elements, or which enhance the DOM, should listen to this event as well and re-apply their initialization process on the updated content, the same way that the whole DOM is enhanced on xwiki:dom:loaded. The list of new or updated elements is sent in the event.memo.elements property. For example:var init = function(elements) {
// Search for special content to enhance in each DOM element in the "elements" list and enhance it
elements.each(function(element) {
element.select('.someBehavioralClass').each(function(item) {
enhance(item);
});
});
}
['xwiki:dom:loaded', 'xwiki:dom:updated'].each(function(eventName) {
document.observe(eventName, function(event) {
init(event.memo && event.memo.elements || [document.documentElement]);
});
});Document content events (actionButtons.js)
- xwiki:document:saved
This event is sent after the document has been successfully saved in an asynchronous request (i.e. after clicking the Save and Continue button). - xwiki:document:saveFailed
This event is sent when a save and continue attempt failed for some reason. The XMLHttpRequest response object is sent in the memo, as event.memo.response.
Action events (actionButtons.js)
- xwiki:actions:cancel
This event is sent after the user clicks the "Cancel" button of an editor (Wiki, WYSIWYG, object, rights, etc.), but before actually cancelling the edit. - xwiki:actions:beforePreview (7.4.1+, 8.0M1+)
This event is sent after the user clicks the "Preview" button from an edit mode, but before the edit form is validated. You can use this event to update the form fields before they are submitted to the preview action. - xwiki:actions:preview
This event is sent after the edit form has been validated, as a result of the user clicking the "Preview" button from an edit mode, but before the form is submitted. The event is fired only if the edit form is valid. - xwiki:actions:beforeSave (7.4.1+, 8.0M1+)
This event is sent after the user clicks the "Save" or "Save & Continue" button from an edit mode, but before the edit form is validated. You can use this event to update the form fields before they are submitted to the save action. xwiki:actions:save
This event is sent after the edit form has been validated, as a result of the user clicking the "Save" or "Save & Continue" button from an edit mode, but before the form is submitted. The event is fired only if the edit form is valid. A memo is available if you need to know if the intend is to continue after the save, in event.memo['continue']. You can use it as follows:document.observe("xwiki:actions:save", function(event){
var doContinue = event.memo['continue'];
if (doContinue) {
// do something specific
}
});All these events contain as extra information, in the second parameter sent to event listeners (the memo), the original click event (if any, and which can be stopped to prevent the action from completing), and the form being submitted, as event.memo.originalEvent, and event.memo.form respectively.
Document extra events (xwiki.js)
- xwiki:docextra:loaded
This event is fired upon reception of the content of a document footer tab by AJAX. This event is useful if you need to operate transformations of the received content. You can filter on which tab content to operate (comments or attachment or information or ...) using the event memo. The DOM element in which the retrieved content has been injected is also passed to facilitate transformations.document.observe("xwiki:docextra:loaded", function(event){
var tabID = event.memo.id;
if (tabID == "attachments") {
// do something with the attachments tab retrieved content.
doSomething(event.memo.element);
}
}); - xwiki:docextra:activated
This event is fired upon activation of a tab. It differs from the loaded event since tabs are loaded only once if the user clicks going back and forth between tabs. This event will notify of each tab activation, just after the tab content is actually made visible. The tab ID is passed in the memo as for xwiki:docextra:loaded
Suggest events (ajaxSuggest.js)
- xwiki:suggest:selected (since 2.3)
This event is fired on the target input when a value was selected.
Fullscreen events (fullScreenEdit.js)
- xwiki:fullscreen:enter (since 3.0 M3) (fired before entering full screen editing)
- xwiki:fullscreen:entered (since 2.5.1) (fired after entering full screen editing)
- xwiki:fullscreen:exit (since 3.0 M3) (fired before exiting full screen editing)
- xwiki:fullscreen:exited (since 2.5.1) (fired after exiting full screen editing)
- xwiki:fullscreen:resized (since 2.5.1)
All events have the target DOM element in event.memo.target.
Annotations events (AnnotationCode/Settings jsx)
- xwiki:annotations:filter:changed
- xwiki:annotations:settings:loaded
Livetable events (livetable.js)
- xwiki:livetable:newrow (event.memo.row holds the new row)
- xwiki:livetable:loadingEntries (since 2.3 RC1)
- xwiki:livetable:receivedEntries (since 2.3 RC1) (event.memo.data contains the received JSON data)
- xwiki:livetable:loadingComplete (since 2.4 M1) (event.memo.status contains the response status code)
- xwiki:livetable:displayComplete (since 2.4 M1)
- xwiki:livetable:ready (since 2.4.4)
- xwiki:livetable:loading (since 3.1.1) (should be used in place of xwiki:dom:loading to startup livetables)
The livetable sends both generic events, named as above, and events specific to each livetable, containing the table name on the third position, such as xwiki:livetable:alldocs:loadingEntries. The generic event has the table name in the memo, as event.memo.tableId.
Live Data events (Logic.js)
- xwiki:livedata:instanceCreated (since 14.4 RC1) triggered when the DOM elements have been created and the instance initialized but potentially before entries are loaded
- xwiki:livedata:beforeEntryFetch (since 13.4 RC1)
- xwiki:livedata:afterEntryFetch (since 13.4 RC1)
- xwiki:livedata:layoutChange (since 12.10)
- xwiki:livedata:layoutLoaded (since 13.4 RC1)
- xwiki:livedata:pageChange (since 12.10) with pageIndex and previousPageIndex
- xwiki:livedata:pageSizeChange (since 12.10) with pageSize and previousPageSize
- xwiki:livedata:select (since 12.10) with entry
- xwiki:livedata:deselect (since 12.10) with entry
- xwiki:livedata:selectGlobal (since 12.10)
- xwiki:livedata:sort (since 12.10) with property, level or type, and descending
- xwiki:livedata:filter (since 12.10) with type, property and removedFilters or oldEntry/newEntry
- xwiki:livedata:entriesUpdated (since 14.8RC1)
All events are dispatched on the container that is wrapping the Live Data as native custom events. All events have the Live Data instance set as detail.livedata. Additional properties are available depending on the event and the situation in which the event is triggered, some of them are listed above. Before 14.4RC1 and 13.10.6, the events were triggered on a detached DOM element and thus it was not really possible to register event listeners for them.
RequireJS and jQuery APIs
By default XWiki uses PrototypeJS which is bound to the $ symbol. Starting in XWiki 5.2, you may use jQuery by requiring it using the RequireJS AMD standard. To do this you would write your code as follows:
require(['jquery'], function ($) {
$('#xwikicontent').append('<p>Inside of this function, $ becomes jquery!</p>');
});The best part is, any scripts which are loaded using require are loaded asynchronously (all at the same time) and if they are not required, they are never loaded at all.
Deferred Dependency Loading
Loading (transitive) dependencies through RequireJS works if those modules are known by RequireJS. In order to make a module known you need to tell RequireJS where to load that module from. A module can be located in various places: in a WebJar, in the skin, in a JSX object or even in a file attached to a wiki page. If the module you need is common enough that is loaded on every page (like the 'jquery' module) then chances are it is already known (configured in javascript.vm). Otherwise you need to configure the dependency by using require.config().
require.config({
paths: {
module: "path/to/module"
},
shim: {
module: {
exports: 'someGlobalVariable',
deps: ['anotherModule']
}
}
});If two scripts need the same dependency then they will have to duplicate the require configuration. In order to avoid this you could move the configuration in a separate file and write something like this:
require(['path/to/config'], function() {
require(['module'], function(module) {
// Do something with the module.
});
});but you would still duplicate the configuration path in both scripts. Now, suppose that one of the scripts is only extending a feature provided by the dependency module. This means that it doesn't necessarily need to bring the dependency. It only needs to extend the module if it's present. This can be achieved starting with XWiki 8.1M1 like this:
require(['deferred!module'], function(modulePromise) {
modulePromise.done(function(module) {
// Do something with the module, if the module is loaded by someone else.
});
});In other words, the script says to RequireJS "let me know when this module is loaded by someone else" (someone else being an other script on the same page). This looks similar with the solution where the require configuration is in a separate file, but the advantage is that the path is not duplicated (i.e. we can move the module to a different path without affecting the scripts that use it).
Examples where this could be useful:
- extend the tree widget (if it's available on the current page)
- extend the WYSIWYG editor (if it's loaded on the current page)
An alternative is for each module that wants to support extensions to fire some custom events when they are loaded.
Bridging custom XWiki events between Prototype and jQuery
Starting with XWiki 6.4 you can catch from jQuery the custom XWiki events that are fired from Prototype.
require(['jquery', 'xwiki-events-bridge'], function($) {
$('.some-element').on('xwiki:moduleName:eventName', function(event, data) {
// Here, do something that will be executed at the moment the event is fired.
doSomething();
// The passed data is a reference to the event.memo from Prototype.
console.log(data.somethingINeedToKnow);
});
});Starting with XWiki 7.1M1 the event listeners registered from Prototype are notified when a custom XWiki event is fired using the jQuery API. This doesn't mean you should write new event listeners using Prototype but that you can rely on existing event listeners until they are rewritten using jQuery.
// Prototype (old code that you don't have time to rewrite)
document.observe('xwiki:dom:updated', function(event) {
event.memo.elements.each(function(element) {
// Do something.
});
});
...
// jQuery (new code, in a different file/page)
require(['jquery', 'xwiki-events-bridge'], function($) {
$(document).trigger('xwiki:dom:updated', {'elements': $('.some-container').toArray()});
});Get some information about the current document
In your javascript's applications, you can get (meta) information about the current document, though an AMD module.
require(['xwiki-meta'], function (xm) {
xm.documentReference // since 7.3M2, get the reference of the current document (as a DocumentReference object).
xm.document // get the current document (eg: Main.WebHome) -- deprecated since 7.3M2, use documentReference instead
xm.wiki // get the current wiki (eg: xwiki) -- deprecated since 7.3M2, use documentReference instead
xm.space // get the current space (eg: Main) -- deprecated since 7.3M2, use documentReference instead
xm.page // get the current page name (eg: WebHome) -- deprecated since 7.3M2, use documentReference instead
xm.version // get the current document version (eg: 1.1)
xm.restURL // get the REST url of the current doc (eg: /xwiki/rest/wikis/xwiki/spaces/Main/pages/WebHome)
xm.form_token // get the current CSRF token that you should pass to your scripts to avoid CSRF attacks.
xm.userReference // since 10.4RC1 and 9.11.5, get the reference of the current user
xm.isNew // since 11.2RC1, define if the current document is new or not
xm.locale // since 12.3RC1, get the locale of the current document
});Be retro-compatible with versions older than 6.3
If you want to be compatible with older version, you can use this trick:
require(['xwiki-meta'], function (xm) {
// Note that the require['xwiki-meta'] (meta information about the current document) is not available on
// XWiki versions < 6.3, then we get these meta information directly from the DOM.
var document = xm ? xm.document : $('meta[name="document"]').attr('content');
var wiki = xm ? xm.wiki : $('meta[name="wiki"]').attr('content');
var space = xm ? xm.space : $('meta[name="space"]').attr('content');
var page = xm ? xm.wiki : $('meta[name="page"]').attr('content');
var version = xm ? xm.version : $('meta[name="version"]').attr('content');
var restURL = xm ? xm.restURL : $('meta[name="restURL"]').attr('content');
var form_token = xm ? xm.form_token : $('meta[name="form_token"]').attr('content');
});Work with Entity References
You can resolve and serialize Entity References on the client side easily:
var documentReference = XWiki.Model.resolve('wiki:Space.Page', XWiki.EntityType.DOCUMENT);
var attachmentReference = new XWiki.AttachmentReference('logo.png', documentReference);
XWiki.Model.serialize(attachmentReference);You can check the full API here.
Starting with XWiki 7.2M1 the XWiki.Model JavaScript API is supporting nested spaces:
var documentReference = XWiki.Model.resolve('wiki:Path.To.My.Page', XWiki.EntityType.DOCUMENT);
documentReference.getReversedReferenceChain().map(function(entityReference) {
return entityReference.type + ': ' + entityReference.name
}).join()
// Will produce:
// 0: wiki,1: Path,1: To,1: My,2: PageStarting with 7.2M1 you can also pass a 'provider' as the third argument to XWiki.Model.resolve(). The provider is used to fill missing references, and it is either a function that gets the entity type, an array of entity names or an entity reference.
var documentReference = XWiki.Model.resolve('Page', XWiki.EntityType.DOCUMENT, function(type) {
switch(type) {
case: XWiki.EntityType.WIKI:
return 'wiki';
case: XWiki.EntityType.SPACE:
return 'Space';
default:
return null;
}
});
// Produces wiki:Space.Page
var documentReference = XWiki.Model.resolve('Page', XWiki.EntityType.DOCUMENT, ['wiki', 'Space']);
// Same output
var spaceReference = new XWiki.SpaceReference('wiki', 'Space');
var documentReference = XWiki.Model.resolve('Page', XWiki.EntityType.DOCUMENT, spaceReference);
// Same outputStarting with 7.2M2 you can construct Reference to Nested Spaces and a new equals() method has been added. Examples using Jasmine:
// Construct a Nested Space reference
var reference = new XWiki.SpaceReference('wiki', ['space1', 'space2']);
expect(XWiki.Model.serialize(reference)).toEqual('wiki:space1.space2');
reference = new XWiki.DocumentReference('wiki', ['space1', 'space2'], 'page');
expect(XWiki.Model.serialize(reference)).toEqual('wiki:space1.space2.page');
// Construct a non-Nested Space reference
reference = new XWiki.SpaceReference('wiki', 'space');
expect(XWiki.Model.serialize(reference)).toEqual('wiki:space');
// Try passing non-valid space parameters
expect(function() {new XWiki.SpaceReference('wiki', [])}).toThrow('Missing mandatory space name or invalid type for: []');
expect(function() {new XWiki.SpaceReference('wiki', 12)}).toThrow('Missing mandatory space name or invalid type for: [12]');
// Equals() examples
var reference1 = new XWiki.DocumentReference('wiki', ['space1', 'space2'], 'page');
var reference2 = new XWiki.DocumentReference('wiki', ['space1', 'space2'], 'page');
var reference3 = new XWiki.DocumentReference('wiki2', ['space1', 'space2'], 'page');
expect(reference1.equals(reference2)).toBe(true);
expect(reference1.equals(reference3)).toBe(false);In 7.2M2 a new XWiki.EntityReferenceTree class which partially mimic Java EntityReferenceTree on Javascript side. There is not much yet, it was mostly introduced to make easier to manipulate a serialized Java EntityReferenceTree.
this.entities = XWiki.EntityReferenceTree.fromJSONObject(transport.responseText.evalJSON()); - xwiki:document:saved
Related
All pages tagged with javascript
Example of understanding initialization
Here's an example of a JavaScript snippet to use in a JavaScript extension xobject to ensure it's properly initialized when the dom is loaded and updated:
var init = function(){
// The actual code you want to initialize that might need some dom elements.
};
$(document).on('xwiki:dom:updated', init)
$(init);
});
The call to $(init) will call the function when the DOM is actually ready (see the jQuery documentation), and the #xwiki:dom:updated# listener allows to trigger initialization when the dom is actually updated, if there's some asynchronous mechanism that might change it. You should be careful to ensure that init method can be called several times without side effect.
Old example
Let's take the example of the Rights page of the Admin UI and understand how its LiveTable is initialized.
The browser will read the HTML from top to bottom. We have the following HTML content:
<head>
...
<script type='text/javascript' src='/xwiki/resources/js/prototype/prototype.js?cache-version=1584467090000'></script>
...
<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/xwiki.js?cache-version=1584467090000&defer=false&minify=false'></script>
...
<script type='text/javascript' src='/xwiki/bin/skin/resources/js/xwiki/table/livetable.js?cache-version=1584467090000&minify=false' defer='defer'></script>
...
</head>
<body>
...
function startup() {
... Rights LT init...
}
...
(XWiki && XWiki.isInitialized && startup()) || document.observe('xwiki:livetable:loading', startup);
...
So here are the steps performed:
- prototype.js is executed first and registers a function ({{code}document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);{{/code}}) to execute when the DOMContentLoaded event is sent. This event is sent after the DOM has been parsed, thus after the scripts of the page have been executed. The fireContentLoadedEvent function will fire the dom:loaded event when called.
- xwiki.js is executed and registers a function (document.observe("dom:loaded", XWiki.initialize.bind(XWiki))) to execute when the dom:loaded event is sent. In turn it'll fire 2 events xwiki:dom:loading and xwiki:dom:loaded when called.
- livetable.js is not executed at this stage since it's marked as defer.
- The Rights UI is executed and XWiki && XWiki.isInitialized && startup() evaluates to false since the XWiki object has not been initialized yet at this stage. However it registers a function for the xwiki:livetable:loading event.
- The browser gets to the end of the HTML, and reads the livetable.js script that was marked as deferred. The following executes: (XWiki.isInitialized && init()) || document.observe('xwiki:dom:loading', init);. The XWiki && XWiki.isInitialized && startup() part evaluates to false since the XWiki object has not been initialized yet. The init function is registered for the xwiki:dom:loading event. When fired, it will trigger the xwiki:livetable:loading event.
- Now that the DOM has been read, the browser sends the DOMContentLoaded event.
- Thus the prototype function is called and the dom:loaded event is triggered.
- Thus the xwiki.js's initialize function is called and the xwiki:dom:loading event is fired.
- Thus the livetable.js's init function is called and the xwiki:livetable:loading event is fired.
- Thus the Rights UI's startup function is called and its LiveTable is initialized.