Edit online

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?

  1. Create a plugin that implements the AuthorDocumentFilter class. It filters consecutive spaces from being inserted in the document. The filter should override all methods from AuthorDocumentFilter that intercept content changes.
  2. Install the custom filter on all documents by implementing a WebappEditingSessionLifecycleListener that can be installed via a WorkspaceAccessPluginExtension (WorkspaceAccess extension 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));
            }
          });
Note:
When instantiating the 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?

The URL parameters are forwarded to the 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?

Note:
Starting from Version 28.0.0, users can view whitespace markers in the editor by enabling the Show whitespace marks option in the Preferences dialog box that is accessed from the user drop-down menu in the far-right side of the top header.
You can create a custom font and combine it with the default font for any element:
@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:

  1. 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);
  2. Use the message key instead of the hard-coded "Yes" string:
    tr(msgs["YES_KEY_"])
Notice:
This only allows adding new messages for the default languages. You cannot add a new language.

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.

For example, in a WebDav server, your template may look like this:
<?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>
Note:
Make sure that you use the absolute OXY-URL in the location parameter of the ${makeRelative(base, location)} editor variable.
Depending on where you create the new file from the template, the relative path to the target document will be properly computed. It may look like this:
<xref href="../../target-document.dita" format="dita" scope="external" />
or like this (and so on):
<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.

For example, if you want to add an action to reload the view, you can do this from a plugin with the following script:
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
  });
});
Notice:
When installing the view, you have to use "dita-map-view" to override the view that is installed by the framework.
Note:
To enable the refresh action, you can do this by implementing the 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

In Java, you can obtain the document content either from 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 AuthorDocumentModel object can be obtained from AuthorOperationWithResult or from WebappEditingSessionLifecycleListener.

  • From AuthorDocumentController:
    AuthorDocumentController authorDocumentController = ...
    
    AuthorDocumentFragment documentAsAFragment = authorDocumentController.createDocumentFragment(0, authorDocumentController.getAuthorDocumentNode().getLength() - 1);
    String documentXmlContent = authorDocumentController.serializeFragmentToXML(documentAsAFragment);
    System.out.println(documentXmlContent);

    The AuthorDocumentController object can be obtained from AuthorOperation via AuthorAccess.getDocumentController() or from AuthorDocumentModel: authorDocumentModel.getAuthorAccess().getDocumentController.

JavaScript Example

In JavaScript, you can invoke the 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.

Important:
The id, name, and description of the action defined in the framework must exactly match those used in the JavaScript implementation.

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.

Important:
To successfully override the action created in the Action subtab of the Document Type Association dialog box, the action ID defined in your JavaScript code must exactly match the one specified there.

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).