API

Last modified by Marius Dumitru Florea on 2017/08/25 12:10

The GWT WYSIWYG editor is no longer available starting with XWiki 9.7. It has been replaced by CKEditor.

This page will show you how to integrate the WYSIWYG editor in your wiki pages and how to interact with it.

Integration

First, include the JavaScript code of the editor in your page like this:

{{velocity}}
$xwiki.jsfx.use("js/xwiki/wysiwyg/xwe/XWikiWysiwyg.js", true)
{{/velocity}}

This way the editor code is loaded when your page is loaded. If you want to use the WYSIWYG editor in an AJAX fashion i.e. load its code on demand, asynchronously, then write this instead:

{{velocity}}
$xwiki.jsfx.use("js/xwiki/wysiwyg/xwe/XWikiWysiwyg.js", {'forceSkinAction': true, 'lazy': true})
{{/velocity}}

This way the editor code is loaded after your first call to Wysiwyg.onModuleLoad. Note that some WYSIWYG editor plugins might require external JavaScript libraries which you have to include also. Alternatively use the wysiwyg_import velocity macro which takes care of this for you:

{{velocity}}
#set($lazy = false)
#wysiwyg_import($lazy)
{{/velocity}}

At this point the Wysiwyg object and the WysiwygEditor class should be ready to use.

{{html}}
...
<textarea id="demo"></textarea>
...
<script type="text/javascript">
Wysiwyg.onModuleLoad(function() {
  new WysiwygEditor({hookId:'demo'});
});
</script>
...
{{/html}}

The editor can replace a plain HTML text area specified by its id. See the configuration page for a list of parameters that you can pass when creating a new editor instance.

JavaScript API

Wysiwyg

Wysiwyg.onModuleLoad(function() {
  alert('WYSIWYG code is loaded!');
});

Methods

  • load() : void
    Loads the WYSIWYG code on demand.
  • onModuleLoad(fCode : function, lazy : boolean) : void
    Schedules a function to be executed after the WYSIWYG module is loaded. A call to this method forces the WYSIWYG module to be loaded, unless the second parameter, lazy, is set to true.
  • getInstance(hookId : String) : WysiwygEditor
    Returns the WysiwygEditor instance associated with the given hook identifier, i.e. the editor instance that replaced the element with the specified identifier.

WysiwygEditor

Wysiwyg.onModuleLoad(function() {
 editor = new WysiwygEditor({hookId: 'content'});
});

Methods

  • WysiwygEditor(config : Object) : WysiwygEditor
    Creates a new editor based on the given configuration object.
  • addActionHandler(actionName : String, handler : function) : function
    Registers the given handler for the specified action. The handler function will be called each time the specified action occurs. The returned function can be used to unregister the handler. The handler function receives the action name as parameter.
  • getCommandManager() : CommandManager
    Use the returned object to execute commands on the editor and query their value.
  • getParameter(parameterName : String) : String
    Returns the value of the specified configuration parameter. See the WYSIWYG editor configuration page for a list of available parameters.
  • getParameterNames() : String[]
    Returns the list of configuration parameters.
  • getPlainTextArea() : Object
    Returns the plain HTML text area element used by the editor.
  • getRichTextArea() : Object
    Returns the rich text area element used by the editor.
  • getSelectionRange() : Object
    Returns the rich text area's selection range. See setSelectionRange(Object) for details about the returned object.
  • getSourceText(onSuccess : function, onFailure : function) : void
    Sends a request to the server to convert the HTML output of the rich text editor to source text and calls one of the given functions when the response is received.
  • release() : void
    Releases the editor so that it can be garbage collected before the page is unloaded. Call this method before the editor is physically detached from the DOM document.
  • setFocus(focused : boolean) : void
    Focuses or blurs the WYSIWYG editor.
  • setSelectionRange(range : Object) : void
    Sets rich text area's selection range. The range parameter must have these properties: startContainer (the DOM node where the selection starts), startOffset (the offset within the start container), endContainer (the DOM node where the selection ends), endOffset (the offset within the end container). Note that the range parameter follows Document Object Model Range.

CommandManager

if ( editor.getCommandManager().isEnabled('forecolor') ) {
 editor.getCommandManager().execute('forecolor', 'yellow');
 alert( editor.getCommandManager().getValue('forecolor') );
}

Methods

  • execute(commandName : String, parameter : String) : boolean
    Executes the specified command with the given parameter on the current selection in the rich text area. Returns true if the command was executed successfully, false otherwise.
  • getValue(commandName : String) : String
    Returns the value of the specified command for the current selection in the rich text area.
  • isEnabled(commandName : String) : boolean
    Returns true if the specified command can be executed on the current selection, false otherwise.
  • isExecuted(commandName : String) : boolean
    Returns true if the specified command was executed on the current selection, false otherwise.
  • isSupported(commandName : String) : boolean
    Returns true if the specified command is supported by the editor, false otherwise.

Custom events

document.observe('xwiki:wysiwyg:created', function(event) {
  var editor = event.memo.instance;
  alert('A new WYSIWYG editor has been created!');
});
  • xwiki:wysiwyg:created
    Fired after a WYSIWYG editor is successfully created.
  • xwiki:wysiwyg:loaded
    Fired after a WYSIWYG editor is successfully loaded.
  • xwiki:wysiwyg:showingWysiwyg
    Fired when the WYSIWYG tab was clicked and the rich text area is about to be reloaded.
  • xwiki:wysiwyg:showWysiwyg
    Fired after the WYSIWYG tab was loaded.
  • xwiki:wysiwyg:showingSource
    Fired when the Source tab was clicked and the source text area is about to be reloaded.
  • xwiki:wysiwyg:showSource
    Fired after the Source tab was loaded.

The events fired when the WYSIWYG editor is loaded are, in this order: xwiki:wysiwyg:created, xwiki:wysiwyg:loaded, xwiki:wysiwyg:showWysiwyg.

Examples

Spawn basic WYSIWYG in Wiki page

{{velocity}}
#set($lazy = false)
#wysiwyg_import($lazy)
{{/velocity}}

{{html clean=false}}
<textarea id="wysiwyg-demo"></textarea>
<script type="text/javascript">
document.observe('xwiki:dom:loaded', function () {
    Wysiwyg.onModuleLoad(function() {
        new WysiwygEditor({hookId:'wysiwyg-demo'});
    });
});
</script>
{{/html}}

Custom configuration

{{velocity}}
{{html}}
#set($className = 'Blog.BlogPostClass')
## Get the edited document. If you use this code in a sheet, then the edited document is $doc
#set($editedDocument = $xwiki.getDocument('Blog.BlogIntroduction'))
## Get the edited object.
#set($editedObject = $editedDocument.getObject($className))
#set($propertyId = "${className}_0_content")
## Manually output the plain text area that will be replaced by the WYSIWYG editor, instead of calling $editedDocument.display('content')
<textarea id="$propertyId">$!escapetool.xml($editedObject.getProperty('content').value)</textarea>
#set($parameters = $util.hashMap)
## Get the default configuration parameters.
#wysiwyg_storeConfig($parameters $editedDocument $propertyId true)
## Overwrite parameters here.
#set($discard = $parameters.put('plugins', '...'))
## Load the WYSIWYG editor with the customized parameters.
#wysiwyg_editPropertyCustom($editedDocument $parameters)
{{/html}}
{{/velocity}}

Load on demand and show source text

{{velocity}}
#if($request.method.equalsIgnoreCase('post'))
== Submitted source ==

$request.getParameter('Blog.BlogPostClass_0_content')
#else
== Edit blog post ==
{{html wiki="true"}}
#wysiwyg_import(true)
{{html}}
<script type="text/javascript">
function hideAndShowLoadingAnimation(fieldId) {
 var field = document.getElementById(fieldId);
 if (field) {
   // Hide the element that will be wrapped by the WYSIWYG editor.
   field.style.visibility = 'hidden';
   // Show the loading animation.
   var loading = document.createElement('span');
   loading.className = 'loading';
   loading.style.display = 'block';
   loading.style.position = 'absolute';
   loading.style.width = field.offsetWidth + 'px';
   loading.style.height = field.offsetHeight + 'px';
   field.parentNode.insertBefore(loading, field);
   // Remove the loading animation after the WYSIWYG module has been loaded.
   Wysiwyg.onModuleLoad(function() {
     loading.parentNode.removeChild(loading);
    });
  }
}
function loadOnDemand(fieldId) {
 hideAndShowLoadingAnimation(fieldId);
 Wysiwyg.onModuleLoad(function() {
   Wysiwyg[fieldId] = new WysiwygEditor({
     hookId: fieldId,
     syntax: 'xwiki/2.1',
     displayTabs: true,
     defaultEditor: 'wysiwyg',
     inputURL: '$xwiki.getURL('Blog.BlogIntroduction''edit', "xpage=wysiwyginput&token=$services.csrf.getToken()")',
     plugins: 'submit line separator embed text valign list indent history format symbol link image table macro import',
     menu: '[{"feature": "link", "subMenu":["linkEdit", "linkRemove", "linkWikiPage", "linkAttachment", "|", "linkWebPage", "linkEmail"]}, {"feature":"image", "subMenu":["imageInsertAttached", "imageInsertURL", "imageEdit", "imageRemove"]}, {"feature":"table", "subMenu":["inserttable", "insertcolbefore", "insertcolafter", "deletecol", "|", "insertrowbefore", "insertrowafter", "deleterow", "|", "deletetable"]}, {"feature":"macro", "subMenu":["macroInsert", "macroEdit", "|", "macroRefresh", "|", "macroCollapse", "macroExpand"]}, {"feature":"import", "subMenu":["importOffice"]}]',
     toolbar: 'bold italic underline strikethrough | subscript superscript | unorderedlist orderedlist | outdent indent | undo redo | format | hr symbol',
     wiki: 'xwiki',
     space: 'Blog',
     page: 'BlogIntroduction'
    });
  });
}
function showSourceText(fieldId) {
 var editor = Wysiwyg[fieldId];
 if (!editor) {
   alert('Source: ' + $(fieldId).value);
  } else {
   editor.getSourceText(function onSuccess(result){
     alert('Source: ' + result);
    }, function(message) {
     alert('Error: ' + message);
    });
  }
}
</script>
{{/html}}
<form action="" method="post">
<div>
 $xwiki.getDocument('Blog.BlogIntroduction').display('content', 'edit')
<input type="submit" value="Submit"/>
</div>
</form>
<div>
<button onclick="loadOnDemand('Blog.BlogPostClass_0_content'); this.disabled=true">Load Editor</button>
<button onclick="showSourceText('Blog.BlogPostClass_0_content')">Show source text</button>
</div>
{{/html}}
#end
{{/velocity}}

Load on demand using velocity macros

{{velocity}}
{{html}}
##
## Specify what will be edited.
##
#set($editedDocument = $xwiki.getDocument("Blog.BlogIntroduction"))
#set($editedProperty = "Blog.BlogPostClass_0_content")
##
## Configure the WYSIWYG editor.
##
## Import the JavaScript code.
#wysiwyg_import(true)
## Define the map of parameters to be used to configure the WYSIWYG editor.
#set($parameters = {})
## Store the default WYSIWYG editor configuration parameters in the $parameters map.
#wysiwyg_storeConfig($parameters $editedDocument $editedProperty false)
## At this point you can customize the default WYSIWYG editor configuration parameters.
##set($discard = $parameters.put('parameterName', 'parameterValue'))
## Write the $parameters map to a JavaScript variable.
#wysiwyg_writeConfig("editorConfig" $parameters)
{{/html}}

##
## Display the content to be edited.
##
(% id="preview" %)
(((
$editedDocument.display('content')
)))

##
## Hide the edit mode. Display it on demand.
##
(% class="hidden" %)
(((
$editedDocument.display('content', 'edit')
)))

##
## Load the editor on demand.
##
{{html}}
<div>
<script type="text/javascript">
function hideAndShowLoadingAnimation(fieldId) {
 var field = document.getElementById(fieldId);
 if (field) {
   // Hide the element that will be wrapped by the WYSIWYG editor.
   field.style.visibility = 'hidden';
   // Show the loading animation.
   var loading = document.createElement('span');
   loading.className = 'loading';
   loading.style.display = 'block';
   loading.style.position = 'absolute';
   loading.style.width = field.offsetWidth + 'px';
   loading.style.height = field.offsetHeight + 'px';
   field.parentNode.insertBefore(loading, field);
   // Remove the loading animation after the WYSIWYG module has been loaded.
   Wysiwyg.onModuleLoad(function() {
     loading.parentNode.removeChild(loading);
    });
  }
}
function loadOnDemand(config) {
  $('preview').hide();

  $(config.hookId).up().removeClassName('hidden');
 // Show the loading animation.
 hideAndShowLoadingAnimation(config.hookId);

 // Load the editor.
 Wysiwyg.onModuleLoad(function() {
   new WysiwygEditor(config);
  });
}
</script>
<button onclick="loadOnDemand(window.editorConfig); this.disabled=true">Edit</button>
</div>
{{/html}}
{{/velocity}}

Edit in-place an object property

{{velocity}}
##
## Prepare the edited document/object.
##
#set($blogPostReference = $services.model.createDocumentReference($doc.wiki, 'Blog', 'BlogIntroduction'))
#set($blogPostDocument = $xwiki.getDocument($blogPostReference))
#set($blogPostObject = $blogPostDocument.getObject('Blog.BlogPostClass'))
#set($editedPropertyId = 'Blog.BlogPostClass_0_content')

##
## View
##
== $blogPostObject.getProperty('title').getValue() ==

(% id="${editedPropertyId}View" %)
(((
$blogPostObject.getProperty('content').getValue()
)))

##
## Edit
##
{{html}}
## Download the JavaScript files needed by the WYSIWYG editor (lazy=true).
#wysiwyg_import(true)
## Download the JavaScript and style sheet for the Save button (AJAX Save).
$xwiki.jsfx.use('js/xwiki/actionbuttons/actionButtons.js', true)
$xwiki.ssfx.use('js/xwiki/actionbuttons/actionButtons.css', true)

## Generate the WYSIWYG editor configuration
#set($parameters = $util.hashMap)
#wysiwyg_storeConfig($parameters $blogPostDocument $editedPropertyId false)
#set($jsVarName = "editorConfig$!{util.generateRandomString(4)}")
#wysiwyg_writeConfig($jsVarName $parameters)

## Utility JavaScript functions.
<script type="text/javascript">
## Map the editor configuration to the edited property id.
var WysiwygConfig = WysiwygConfig || {};
WysiwygConfig['$editedPropertyId'] = $jsVarName;

// Hides the text area with the specified id and displays a loading animation until the WYSIWYG editor is fully loaded.
function hideAndShowLoadingAnimation(fieldId) {
 var field = document.getElementById(fieldId);
 if (field) {
   // Hide the element that will be wrapped by the WYSIWYG editor.
   field.style.visibility = 'hidden';
   // Show the loading animation.
   var loading = document.createElement('span');
   loading.className = 'loading';
   loading.style.display = 'block';
   loading.style.position = 'absolute';
   loading.style.width = field.offsetWidth + 'px';
   loading.style.height = field.offsetHeight + 'px';
   field.parentNode.insertBefore(loading, field);
   // Remove the loading animation after the WYSIWYG module has been loaded.
   Wysiwyg.onModuleLoad(function() {
     loading.parentNode.removeChild(loading);
    });
  }
}

// Hides the content and displays the WYSIWYG editor.
function onEdit(fieldId) {
  $(fieldId + 'View').hide();
  $(fieldId + 'EditIcon').hide();
  $(fieldId + 'Edit').show();
 hideAndShowLoadingAnimation(fieldId);
 Wysiwyg.onModuleLoad(function() {
   new WysiwygEditor(WysiwygConfig[fieldId]);
  });
}

// Hides the WYSIWYG editor and displays the updated content.
function onClose(fieldId) {
 var editor = Wysiwyg.getInstance(fieldId);
 editor.getSourceText(function(sourceText) {
    $(fieldId + 'View').update($(fieldId).value);
    $(fieldId).value = sourceText;
   editor.release();
   // Remove the WYSIWYG editor from the document (it was inserted before the plain text area).
    $(fieldId).previous().remove();
    $(fieldId + 'View').show();
    $(fieldId + 'EditIcon').show();
    $(fieldId + 'Edit').hide();
  });
}

// Inserts the edit icon at the end of the heading preceding the editable content.
function showEditIcon(fieldId) {
 var editIcon = new Element('img', {"src" : "$xwiki.getSkinFile('icons/silk/pencil.gif')", "title" : "Edit", "alt" : "edit", "id" : fieldId + 'EditIcon'});
 editIcon.observe('click', onEdit.bind(window, fieldId));
  $(fieldId + 'View').previous().appendChild(editIcon);
}

// Insert the edit icons and set the action for the cancel buttons.
document.observe('xwiki:dom:loaded', function() {
 showEditIcon('$editedPropertyId');

  $$('.cancel').each(function(item) {
   var fieldId = item.up('form').down('textarea').id;
   item.observe('click', function(event) {
     Event.stop(event);
     onClose(fieldId);
    }.bindAsEventListener(window));
  });
});

// Hides the WYSIWYG editor and displays the updated content after a successful save action.
document.observe('xwiki:actions:save', function(event) {
 if (event.stopped) {
   return;
  }
 var form = $(event.memo.form);
 var fieldId = form.down('textarea').id;
 document.observe('xwiki:document:saved', function() {
   document.stopObserving('xwiki:document:saved', arguments.callee);
   onClose(fieldId);
  });
});
</script>
{{/html}}

{{html wiki="true"}}
## The edit form, hidden at first. It is displayed when the edit icon is clicked.
<div id="${editedPropertyId}Edit" style="display:none">
 <form id="${editedPropertyId}EditForm" action="$blogPostDocument.getURL('save')" method="post" class="xform half">
   <div class="hidden">
     <input type="hidden" name="form_token" value="$services.csrf.getToken()" />
   </div>
   <div>
     $blogPostDocument.display('content', 'edit')
   </div>
   <div class="buttons">
     <span class="buttonwrapper"><input type="submit" value="Save" name="action_saveandcontinue" class="button"></span>
     <span class="buttonwrapper"><button class="button secondary cancel">Cancel</button></span>
   </div>
 </form>
</div>
{{/html}}
{{/velocity}}

Adjust the editor UI from a JavaScript Extension

// Starting with XWiki 2.4 we can listen to 'xwiki:wysiwyg:loaded'
// event directly but this code was written to work with XWiki 2.1
Event.observe(document, 'xwiki:wysiwyg:created', function(event) {
 var editor = event.memo.instance;
 var iframe = editor.getRichTextArea();
 Event.observe(iframe, 'load', function() {
   Event.stopObserving(iframe, 'load', arguments.callee);
   var toolBar = Element.previous(iframe, '.xToolbar');
   // We expect the title list box to be the first one on the tool bar.
   var titleSelect = Element.down(toolBar, 'select');
   var replace = function(value, newLabel) {
     for(var i=0; i<titleSelect.length; i++) {
       if (titleSelect.options[i].value == value) {
         titleSelect.options[i].text = newLabel;
         break;
        }
      }
    };
   replace('h5', 'Custom 5');
   replace('h6', 'Custom 6');
  });
});

Use the WYSIWYG editor outside XWiki

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
 <title>WYSIWYG Editor Demo</title>
 <script type="text/javascript" src="xwe/xwe.nocache.js"></script>
</head>
<body>
 <form action="" method="post">
   <dl>
     <dt><label for="content">Content</label></dt>
     <dd><textarea id="content">initial content</textarea></dd>
   </dl>
   <script type="text/javascript">
     document.addEventListener('load', function() {
       document.removeEventListener('load', arguments.callee, true);
       var tryCounter = 10;
       (function() {
         // The load event is sometimes fired before the external JavaScript code is fully evaluated.
         if (typeof WysiwygEditor != 'undefined') {
           new WysiwygEditor({hookId: 'content'});
          } else if (tryCounter-- > 0) {
           setTimeout(arguments.callee, 100);
          }
        })();
      }, true);
   </script>
 </form>
</body>
</html>

Get Connected