FAQ for Customizations
This topic contains some frequently asked questions about Web Author customizations.
How can I prevent consecutive spaces from being inserted in documents?
- Create a plugin that implements the
AuthorDocumentFilterclass. It filters consecutive spaces from being inserted in the document. The filter should override all methods fromAuthorDocumentFilterthat intercept content changes. - Install the custom filter on all documents by implementing a
WebappEditingSessionLifecycleListenerthat can be installed via aWorkspaceAccessPluginExtension(WorkspaceAccessextension in the plugin.xml file):public class CustomFilterInstaller implements WorkspaceAccessPluginExtension { @Override public void applicationStarted(StandalonePluginWorkspace pluginWorkspaceAccess) { WebappPluginWorkspace pluginWorkspace = (WebappPluginWorkspace)PluginWorkspaceProvider.getPluginWorkspace(); pluginWorkspace.addEditingSessionLifecycleListener(new WebappEditingSessionLifecycleListener() { @Override public void editingSessionStarted(String sessionId, AuthorDocumentModel documentModel) { documentModel.getAuthorDocumentController().setDocumentFilter(new CustomAuthorDocumentFilter(documentModel)); } });
AuthorDocumentFilter, the
AuthorDocumentModel object is passed because the filter needs it to check
for duplicate spaces.How can I customize the inserted table fragment for a DITA framework extension?
See the dita-extension-replace-insert-table-action sample
project that uses a Framework Extension Script (EXF) to extend the built-in DITA framework
to replace the default Insert Table action with another action that
uses an InsertFragmentOperation to insert a CALS table element that has
@frame, @colsep, and @rowsep attributes
already set.
How can I target a specific font size in CSS, using a custom framework?
Suppose you want to modify a custom framework so that when a user selects a specific font size (small, medium, large) in Preferences, the font size selected is applied to the document.
To achieve this, it is recommended to use em units (instead of
px, for example). This causes the font size to be adjusted according to
the option chosen by the user in Preferences.
How can I obtain a URL parameter from a file open in Web Author?
sync.api.Workspace.LoadingOptions object. As a side-note, plugins can
contribute or intercept a sync.api.Workspace.LoadingOptions object by
listening on the sync.api.Workspace.EventType.BEFORE_EDITOR_LOADED event.
The LoadingOptions objects (client-side) are forwarded to the server where they end
up in ro.sync.ecss.extensions.api.access.EditingSessionContext. Thus, you
can access the URL parameters from the EditingSessionContext object
(server-side), which can be obtained like
this:EditingSessionContext editingSessionContext =
authorDocumentModel.getAuthorAccess().getEditorAccess().getEditingContext();How can I view whitespaces in Author mode?
@font-face {
font-family: Arial_spacedot;
src: url(Arial_spacedot.ttf);
unicode-range: U+1780-17FF, U+200B-200C, U+25CC;
}
* { font-family: Arial_spacedot,Arial; }How can I localize custom actions implemented in JavaScript?
To localize action messages, you must use the Translation JavaScript API:
- Register your messages via
sync.Translation.addTranslations, like this:let myMsgs={ YES_KEY_ : { "en_US" : "Yes", "de_DE" : "Ja", "fr_FR" : "Oui", "ja_JP" : "はい", "nl_NL" : "Ja", "zh_CN" : "是的" } }; sync.Translation.addTranslations(myMsgs); - Use the message key instead of the hard-coded "Yes"
string:
tr(msgs["YES_KEY_"])
How can I define a document template with a dynamic relative path?
Suppose you want to generate files from a document template that contains a link to a specific document. In the generated files, the link should be relative to the location where the template is created and the location of the link target. When the files are created in different directories, the relative path should be automatically and accurately calculated.
To make this happen, in your newly created template, you must use the
${makeRelative(base, location)} and ${currentFileURL}
Editor Variables.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="newfile">
<title>New File</title>
<body>
<p>This is a new file.</p>
<p>Link to a specific document:
<xref
href="${makeRelative(${currentFileURL},
webdav-https://your-web-dav-server/path/to/your/target-document.dita)}"
format="dita"
scope="external"
/>
</p>
</body>
</topic>location parameter of the
${makeRelative(base, location)} editor variable.<xref href="../../target-document.dita" format="dita" scope="external" /><xref href="../target-document.dita" format="dita" scope="external"/>How can I add a custom action on the DITA Map side-view?
You can extend the standard DITA Map side-view and add a custom
action in the side-view's toolbar (next to the Configure DITA Context
action) by overriding its sync.view.ViewRenderer.getToolbarDescriptor and
sync.view.ViewRenderer.getToolbarActionsMap methods.
class RefreshMapAction extends sync.actions.Action {
constructor() {
super({displayName: "Refresh"});
}
/**
* @override
*/
actionPerformed(callback) {
let ditaContext = workspace.getEditingContextManager().getDitaContext();
workspace.getEditingContextManager().updateDitaContext(new sync.api.DitaContext());
workspace.getEditingContextManager().updateDitaContext(ditaContext);
callback();
}
}
class CustomDitaMapView extends sync.api.dita.DitaMapView {
/**
* @override
*/
getToolbarActionsMap() {
let toolbarActionsMap = super.getToolbarActionsMap();
toolbarActionsMap["DMM/Refresh"] = new RefreshMapAction();
return toolbarActionsMap;
}
/**
* @override
*/
getToolbarDescriptor() {
let toolbarDescriptor = super.getToolbarDescriptor();
toolbarDescriptor.children.push({
id: "DMM/Refresh",
type: "action"
});
return toolbarDescriptor;
}
}
workspace.listenOnce(sync.api.Workspace.EventType.EDITOR_LOADED, e => {
let customView = new CustomDitaMapView();
workspace.viewManager.addView('dita-map-view');
workspace.viewManager.installView('dita-map-view', customView, {
side: 'left',
initiallyClosed: false
});
});isEnabled method in RefreshMapAction and return
true or false. When enabling status changes, you have to call
workspace.getViewManager().getActionsManager('dita-map-view').refreshActionsStatus()
to update the UI.How can I list files in a directory on the server?
To list the server-side files in a directory, you must open a connection
with java.net.URL.openConnection() then cast the
java.net.URLConnection object to ro.sync.net.protocol.FileBrowsingConnection. Then,
use the ro.sync.net.protocol.FileBrowsingConnection.listFolder()
method.
How can I retrieve document content in Java and JavaScript?
In Oxygen XML Web Author, you can retrieve the full content of the current XML document using both Java and JavaScript. Below are examples demonstrating how to achieve this in each language.
Java Example
AuthorDocumentModel or from AuthorDocumentController.- From
AuthorDocumentModel:AuthorDocumentModel authorDocumentModel = ... Reader reader = authorDocumentModel.createReader(); String documentXmlContent = new BufferedReader(reader) .lines() .collect(Collectors.joining("\n")); System.out.println(documentXmlContent);The
AuthorDocumentModelobject can be obtained fromAuthorOperationWithResultor fromWebappEditingSessionLifecycleListener. -
From
AuthorDocumentController:AuthorDocumentController authorDocumentController = ... AuthorDocumentFragment documentAsAFragment = authorDocumentController.createDocumentFragment(0, authorDocumentController.getAuthorDocumentNode().getLength() - 1); String documentXmlContent = authorDocumentController.serializeFragmentToXML(documentAsAFragment); System.out.println(documentXmlContent);The
AuthorDocumentControllerobject can be obtained fromAuthorOperationviaAuthorAccess.getDocumentController()or fromAuthorDocumentModel:authorDocumentModel.getAuthorAccess().getDocumentController.
JavaScript Example
sync.api.Editor.getContent or
sync.api.EditingSupport.getContent.workspace.currentEditor.getContent((error, documentXmlContent) => {
if (!error) {
console.log(documentXmlContent);
}
})How can I use a custom JavaScript action with a form control in a custom framework?
In a custom framework, when you implement and register a JavaScript action and attempt to trigger it from a form control (such as an oxy_button), the action’s icon may not be available when the document is first loaded in the editor, which may cause the button to be rendered incorrectly the first time. This happens because actions added only through JavaScript (and not also declared in the framework) are loaded after the form controls are rendered. To ensure that the action and its icon are available immediately when the editor loads, you need to define the action directly in the framework in addition to the JavaScript registration.
This framework-defined action does not need to implement any logic (the operation ID that is specified does not matter), as its sole purpose is to ensure that the editor loads the action configuration at startup.
How can I override a framework-defined action from the Document Type Association dialog box using JavaScript?
To extend an action defined in the Document Type Association dialog box using JavaScript in Oxygen XML Web Author, you should use the Web Author JavaScript API.
While the Document Type Association dialog box allows you to configure actions via the GUI using the built-in operations to modify the document, the JavaScript API gives you more flexibility. By adding JavaScript code to a framework, you can extend the functionality of an action without being limited by the configuration options available in the Document Type Association dialog box. With JavaScript, you can show custom dialog boxes, control the side-views, control the selection, and many other things related to the UI.
A simple example that illustrates the need to extend an action in
JavaScript is to suppose you register a new action using the graphical interface
in the Document Type Association configuration and you want this action to display a dialog
box when activated. You can define the action through the Document Type Association
configuration, while the implementation of the dialog box in Web Author can be done
using the createDialog() JavaScript API in
the .js file located in the framework’s web/
subfolder.
How do I trigger UI changes after an element is inserted?
In some workflows, you may want Web Author to perform a UI action immediately after an element is inserted (for example, displaying a prompt dialog box, updating a pane, or switching to a different view). This allows users to continue their task in the right context without additional manual steps.
Web Author provides a JavaScript API that allows switching to a UI panel
programmatically. To focus a specific view, call:
workspace.getViewManager().focusView('<view-id>');
Replace <view-id> with the desired view identifier.
For example, to focus on the Attributes pane, use:
workspace.getViewManager().focusView('attributes-panel-table');
Example: Focusing the Attributes panel from a custom JavaScript
action - The following custom action demonstrates how to focus on the Attributes pane
then insert an element (<note> in this example):
class FocusAttributesView extends sync.actions.AbstractAction {
/**
* @type {sync.api.Editor}
*/
constructor(editor) {
super(editor);
this.editor = editor;
}
/** @override */
getDisplayName() {
return 'Focus Attributes View';
}
/** @override */
isEnabled() {
return true;
}
/** @override */
actionPerformed(callback) {
// Focus the Attributes panel
workspace.getViewManager().focusView('attributes-panel-table');
// Example operation (you can replace this with your own logic)
workspace.currentEditor.getActionsManager().invokeOperation(
"ro.sync.ecss.extensions.commons.operations.InsertFragmentOperation",
{ fragment: "<note>test</note>" },
callback
);
}
}
// Register the custom action
var editor = workspace.currentEditor;
editor.getActionsManager().registerAction(
'insert.note.test',
new FocusAttributesView(editor)
);
After this customization, whenever the element is inserted using this action, Web Author automatically switches to the specified pane (the Attributes view in this example).