Problems when developing a custom macro

Hi, I am trying to develop a macro which using embed external to draw charts in xwiki page.
the code of XWiki.WikiMacroClass

#if($wikimacro.content == '')
{{html clean="false"}}
<img class="drawio" style="cursor: default" src="data:image/png;base64,$wikimacro.content" />

the code of XWiki.JavaScriptExtension:

// Edits an image with drawio class on double click
document.observe("xwiki:actions:save", function(event){

document.observe("xwiki:document:saved", function(event){

document.addEventListener('dblclick', function(evt) {
  var url = '';
  var source = evt.srcElement ||;

  if (source.nodeName == 'IMG' && source.className == 'drawio'){
    if (source.drawIoWindow == null || source.drawIoWindow.closed) {
      // Implements protocol for loading and exporting with embedded XML
      var receive = function(evt) {
        if ( > 0 && evt.source == source.drawIoWindow) {
          var msg = JSON.parse(;
          // Received if the editor is ready
          if (msg.event == 'init') {
            // Sends the data URI with embedded XML to editor
            source.drawIoWindow.postMessage(JSON.stringify({action: 'load', xmlpng: source.getAttribute('src')}), '*');
          // Received if the user clicks save
          else if (msg.event == 'save')
            // Sends a request to export the diagram as XML with embedded PNG
              {action: 'export', format: 'xmlpng', spinKey: 'saving'}), '*');
          // Received if the export request was processed
          else if (msg.event == 'export')
            // Updates the data URI of the image
            // TODO save to xwiki server as macro's content

          // Received if the user clicks exit or after export
          if (msg.event == 'exit' || msg.event == 'export')
            // Closes the editor
            window.removeEventListener('message', receive);
            source.drawIoWindow = null;

      // Opens the editor
      window.addEventListener('message', receive);
      source.drawIoWindow =;
      // Shows existing editor window

the problem is:

  1. how to check current page is in editing mode from client side javascript? so I can ignore double click when in view mode. I know thre is property window.XWiki.currentaction, but when editing using real-time WYSISWYG editor, this property is also equal to ‘view’. currently I am checking with this.URL.endsWith('#edit')
  2. when editing with CKEditor editor(not the real-time one), my double click handler is not fired, it will open the macro editing dialog. I don’t know how to override the default handler.
  3. when window finish editing, image data come back as img’s src attribute, but I don’t know how to update back to xwiki server from client side, I have tried xwiki:actions:save event by inspect what can be modifed before post to server, but found nothing helpful.

please help!

I am using newest version of xwiki.

ad 1:
The only method I found was checking the not existence of variables in the binding (with groovy):

editmode= ! binding.hasVariable("inplaceEditingConfig")
{{velocity}}edit: $editmode{{/velocity}}

I wonder if the URL contains “#edit”, as local anchors are not part of the URL - may be a browser property.

ad 3: Are you in (WYSIWYG-) edit mode during editing with

Yes, I am using WYSIWYG editor, do you know how to save from client side using javascript ?

I have found a way to save macro’s content in client side, just update the data-macro attribute.

but still don’t know how to prevent the macro editing dialog.

You can trigger the editor buttons (save, save and continue, abort) within javascript by

function exitEditorWithSave() {require(["jquery"],function($) {

function abortEditor() {require(["jquery"],function($) {

function saveEditorAndContinue() {require(["jquery"],function($) {

If you want to change a document by content which is known within the javascript, it is more complicated - as far as I know:

You can use the rest api of xwiki by AJAX or XMLHttpRequest.

see: REST API (
and: Bitloom - An overview of the XWiki’s RESTful API

The http/AJAX requests needs a defined xml structure which defines the document.

A little tip: You can see the required xml elements, if you look at the returned data of document view call with the query “?xpage=xml” added. This information even contains documents objects like comments.
This XML format is used by the XAR-Export (=ZIP compressed) of a document.

I hope this helps.


I finally got everything work:

 * A XWiki extension to integrate external chartting service. 
 * please note: this extension doesn't support the in-place editing feature, aka rtWYSIWYG.
 * you can disable rtWYSIWYG by setting edit.document.inPlaceEditing.enabled = false
 * @author

const drawioUrl = ''
const imgDBLClick = function (evt) {
    var source =
    if (XWiki.editor != 'wysiwyg') return
    if (source.drawIoWindow == null || source.drawIoWindow.closed) {
        var receive = function (evt) {
            if ( > 0 && evt.source == source.drawIoWindow) {
                var msg = JSON.parse(
                if (msg.event == 'init') {
                    source.drawIoWindow.postMessage(JSON.stringify({ action: 'load', xmlpng: source.getAttribute('src') }), '*')
                else if (msg.event == 'save') {
                    source.drawIoWindow.postMessage(JSON.stringify({ action: 'export', format: 'xmlpng', spinKey: 'saving' }), '*')
                else if (msg.event == 'export') {
                    // TODO this seems hacky, looking for a offical way to do it
                    source.parentNode.parentNode.parentNode.parentNode.setAttribute('data-macro', 'startmacro:drawio|-||-|' +
                if (msg.event == 'exit' || msg.event == 'export') {
                    window.removeEventListener('message', receive)
                    source.drawIoWindow = null
        window.addEventListener('message', receive)
        source.drawIoWindow =
    } else {

require(['jquery', 'bootstrap'], function ($) {
    const observer = new MutationObserver(function (mutations_list) {
        mutations_list.forEach(function (mutation) {
            mutation.addedNodes.forEach(function (node) {
                if (node.tagName == 'IFRAME' && node.className.indexOf('cke_wysiwyg_frame') >= 0) {
                    const iframe = node
                    iframe.contentWindow.addEventListener('DOMContentLoaded', onFrameDOMContentLoaded, true)
                    function onFrameDOMContentLoaded(ee) {
                        iframe.onload = function () {
                            iframe.contentWindow.document.querySelector('img.drawio').addEventListener('dblclick', imgDBLClick)
    observer.observe(document.querySelector('body'), { subtree: true, childList: true })

Great that you succeeded!

small note: rtWYSIWYG is realtime WYSIWYG which is different from in-place editing :wink: