Méthode Swing Text editor can be extended in multiple parts by adding custom buttons and executing custom actions.

Location of the Editor extensions.

Editor extensions consist in a Javascript (*.js) file loaded with Swing.

In general, all the extensions of Méthode Swing are places under

{SWING-APP}/plugins

So, all the Javascript described in the following sections should be placed under

{SWING-APP}/app/plugins/{EXTENSION-FOLDER}/{EXTENSION-NAME}.js

Do not use the word libs as an extension folder. The libs folder is reserved for loading external libs. See the proper documentation to obtain further info on the topic.

DEBUG MODE: by default, all plugins are aggregated into a single plugins.js file at the Tomcat startup. When creating a plugin, it may be frustrating to restart the Tomcat Server every time a change is made.

To avoid this, set the configuration property debugEnabled to true.

Then, that extension will be loaded on every refresh of the page.

Namespaces of the editor extensions

Editor extensions are available under the following namespace:

eidosmedia.webclient.actions.editor

This namespace delves into more sub-namespaces, according to the part of the editor the user want to extend. At the moment, the following namespaces are supported:

eidosmedia.webclient.actions.editor.toolbar // To add buttons and actions in the editor toolbar

eidosmedia.webclient.actions.editor.components // To add buttons and actions in the swing components

The approach to use is the same throughout all the namespaces.

Configure editor actions

Throughout this document, the example will focus on the development of a simple custom command, named custom.PrependText, which will prepend a specific text to the current selection.

To add an editor action, then, it is necessary to follow the steps described below:

1. Add a custom command

The editor extensions follow the same approach of Swing commands.

See the Command configuration paragraph for further details.

Example

eidosmedia.webclient.commands.add({
    name: 'custom.PrependText',
    isActive: function( ctx ) {
        // check privileges...
        return true;
    },
    isEnabled: function( ctx ) {
        // check current selected items...
        return true;
    },
    action: function( ctx, params, callbacks ) {
        if ( ctx.component.getType() === 'toolbar') {
            // perform same difficult operations...
            if (callbacks && callbacks.success) {
                callbacks.success( /*...*/ );
            }
        }

    }
});

The details of the implementation of the isEnabled and isActive method will be explained later.

2. Register the commands in the different areas

As a second step, the newly created command must be associated to the specific areas of the editor.

2.a Editor toolbar and custom tabs

To add a button to the Editor toolbar, the following namespace must be used

eidosmedia.webclient.actions.editor.toolbar

The method to be used is addButton(settings). See the following example:

/**
 * We now register a new action in the editor toolbar using
 * the proper namespace. In this case we call the addButton
 * method for the toolbar visible in the editor instance. We
 * define a label and an icon for the button and more important
 * we tell the button which command has to trigger using the
 * action property
 */
eidosmedia.webclient.actions.editor.toolbar.addButton({
    action: "custom.PrependText",
    label: "Prepend Xml",
    icon: 'icon-edit',
    tabId: 'custom-tab-id'
});

The addButton method requires a single Javascript object ( settings ) as a parameter. This objects need to have the following properties:

  • action: the name of the previously registered command

  • label: the label associated with the command

  • icon: the icon associated with the command. See Icons supported by Swing for a comprehensive list of icons available in Swing.

Buttons can be placed under custom tabs. See Editor custom tabs for more information.

By defining an order property, it is possible to change the order of the buttons in the toolbar (only for the Editor).

If you need to use the call the same command from different parts of the editor ( e.g. the toolbar and some components ), or you need to differentiate the components for which the command is available, you can ( and should ) verify the origin of the action by checking the Context Object Component Type. This is detailed in the Verify the component type paragraph.

Editor custom tabs

Swing story editor toolbar is divided in tabs. By default, all the custom buttons are added to a specific "Custom Actions" tab. It is possible, though, to define multiple (2 at maximum) custom tabs and to place the custom buttons accordingly. To add a custom tab, write the following:

/**
 * We now register a new tab in the editor toolbar using
 * the proper namespace. The tab name must be used in the <tabId> property of the custom button.
 */
eidosmedia.webclient.extensions.editor.tabs.add({
    name: 'em-tab-custom',
    label: 'Swing custom actions'
});

To add the button to a tab, use the following syntax:

eidosmedia.webclient.actions.editor.toolbar.addButton({
    action: "custom.PrependText",
    label: "Prepend Xml",
    icon: 'icon-edit',
    tabId: 'em-tab-custom'
});
  • If the registered tab has no associated buttons, it will not be shown.

  • All the custom buttons who do not have a "tabId" property will be added to the usual "Custom Actions" tab.

2.b Editor components

To add a button to the Editor components, the following namespace must be used

eidosmedia.webclient.actions.editor.components

The method to be used is addButton(settings). See the following example:

/**
 * We now register a new action in the editor component using
 * the proper namespace. In this case we call the addButton
 * method for the components available in the editor instance. We
 * define a label and an icon for the button and more important
 * we tell the button which command has to trigger using the
 * action property
 */
eidosmedia.webclient.actions.editor.components.addButton({
    action: "custom.PrependText",
    label: "Prepend Xml",
    icon: 'icon-edit',
    allowReadOnly: false,
    singleItem: true
});

The addButton method requires a single Javascript object ( settings ) as a parameter. This objects need to have the following properties:

  • action: the name of the previously registered command

  • label: the label associated with the command

  • icon: the icon associated with the command. See Icons supported by Swing for a comprehensive list of icons available in Swing.

  • allowReadOnly: if true the button is visible in the component when the story is read only.

  • singleItem: if true the button is visible in a single element of a component with multiple items, for example Collection and Gallery component.

If you need to use the call the same command from different parts of the editor ( e.g. the toolbar and some components ), or you need to differentiate the components for which the command is available, you can ( and should ) verify the origin of the action by checking the Context Object Component Type. This is detailed in the Verify the component type paragraph.

singleItem attributte is available only for elements in a Collection and Gallery component. In the action method the second paramater contains the selectedItem object.

2.c Editor components Drop Down menu

It’s possible to add one or more buttons in a drop down menu. In this case the submenu property has to be added to the settings object:

  • submenu: the object containing the buttons to be added in the drop down menu

/**
 * We now register a new drop down menu action in the editor component using
 * the proper namespace. In this case we call the addButton
 * method for the components available in the editor instance. We
 * define a label and an icon for the drow down menu and more important
 * we set the submenu property that contains the button of the drop down menu
 */
eidosmedia.webclient.actions.editor.components.addButton({
    action: "example.customDropDown",
    label: "Custom popup",
    name: "submenu.customDropDown",
    icon: 'icon-copy',
    allowReadOnly: false,
    submenu: {
            videoPreview: {
                action: "example.openVideoPreview",
                label: "Open Video Preview",
                icon: 'fa fa-video-camera'
            },
            customEdit: {
                action: "example.customEditing",
                label: "Custom editing",
                icon: 'icon-edit',
            }
    }
});

Implementation of the editor extensions

The editor extensions are powerful because they make use of an editor-related version of the context object. See Context Object for the editor extensions for further details.

As of any command implementation, it is possible to specify the isActive, isEnabled and action methods.

  • isActive method is called in different points according to the editor part it refers to.

    • For the toolbar the method is called after the story is loaded to determine whether a custom action is available.

    • For the components the method is called when a new component is created ( or an existing one is loaded from the story )

  • isEnabled method is called in different points according to the editor part it refers to.

    • For the toolbar the method is called whenever the user changes the current selection or cursor position.

  • the action is called when the user clicks on the corresponding action button.

All the methods are called with the same Context Object ( ctx ), which is enhanced for the editor and allows to access the current document ( activeDocument ). See Context Object for the editor extensions foo further details.

Starting from 5.2020.11 version the same Context Object ( ctx ), is enhanced for the editor, ONLY for the editor of story, and allows to access the current document with a new property ( activeDocumentV2 ). In Swing Mobile App, due to Android platform (ONLY for Android), the activeDocument object is null. The activeDocumentV2 provides the same functionality of activeDocument in an asynchronous fashion way. See Context Object for the editor extensions foo further details.

Context Object for the editor extensions

Basic structure of the Context Object

The Context object is made of a specific list of properties, and a number of publicly available methods.

{
    application: {
        getId: function()
    },
    area: {
        getType: function(),
        getName: function(),
        getContext: function()
    },
    component: {
        getType: function()
    },
    activeObject: {
        // Methods of the single object.
    },
    activeObjects: [{activeObject}] // An array of activeObject
    selection: [ ] // Array of objects. See below.
}
Table 1. Parameters list
Parameter Value type Description Values

application.getId()

String

Returns the application Id.

"swing", …​

area.getType()

String

Returns the area type.

"main" (for the main views), "editor"

area.getName()

String

Returns the area name.

"explorer", "dashboard", "myarea", "liveblogmanagement" (for "main" areas). "story" (for editors).

area.getContext()

String

Returns the area context.

"story-editor", "report-editor", "dwp-editor", "topic@editor", "topic@search", "topic@binder", "topicplan@editor", "topicplan@search", "topicplan@binder", "topicitem", "upload", "diagram-workflow", "search", "planning-calendar"

component.getType()

String

Returns the component type.

"toolbar", "grid", "objectpanel"…​

activeObject

Object

Returns an object with a set of methods. Refer to : Methods of the single object for the list of methods.

activeObjects

Array

Returns an array of activeObject(s). In bulk selection mode all selected items are returned here as array. In single selection mode array length is 1. Each activeObject is coupled with one selected item. Methods of the single object operate to that item.

selection

Array of Object

Returns an array with the currently selected items. Each item has the same set of methods available. Refer to : Methods of the single object for the list of methods.

Each context has a different value for these properties.

Methods available in the Context Object

Further details on the methods available in the Context Object can be found in the Context Object Methods reference.

In the Editor context, activeObj and selection are NOT available. The available methods are:

  • {ctx}.component.getType(), which returns one of the following values:

    • toolbar for the Editor toolbar

    • image for the Editor Component "Image"

    • video for the Editor Component "Video"

    • More to come…​

  • {ctx}.component.getSubType(), in case a component can embed, in addition to the main type, another type, which returns one of the following values:

    • video for the element "Video"

    • More to come…​

Example the image component can embed also a video element.

In addtion only in teh Editor context the following methods are available for the element info:

  • {ctx}.component.getXPath(), The xpath of the element info

  • {ctx}.component.getAttributes(), The attributes of the element info

The Editor Context Object is enriched by an additional object, named activeDocument, which contains a subset of methods to access the current document. These methods are described in the Editor API paragraph.

Starting from 5.2020.11 version, the Editor Context Object is enriched by an additional object, named activeDocumentV2, which contains a subset of methods to access, in an asynchronous way, the current document. These methods are described in the Editor API paragraph.

Editor API

Suggestions for the Editor extensions

Verify the component type

Since the Editor Context object contains a method

ctx.component.getType();

it is possible to use it in the isEnabled and isActive methods to check the kind of component which is calling the action.

    // EXCERPT FROM THE COMMAND CODE
    isActive: function( ctx ) {
        if ( ctx.component.getType() === 'toolbar') {
            return true;
        }
        return false;
    },
    isEnabled: function( ctx ) {
        // Enabling the action for the editable image only.
        if ( ctx.component.getType() === ctx.activeDocument.CONSTANTS.BLOCK_IMAGE) {
            return true;
        }
        return false;
    },

Checking the component type is mandatory in case of actions added to the Editor components namespace. Otherwise, the action will be available for all components. It’s possible to use some constants defined in the activeDocument:

ctx.activeDocument.CONSTANTS.BLOCK_IMAGE ctx.activeDocument.CONSTANTS.BLOCK_VIDEO

Use of Editor API and extensions in Object Panel

Only for the Object Panel of an opened story, the activeDocument object of the Editor API is available. So, it is possible, from the Object Panel, to call custom actions and manipulate the current document.

It is suggested, though, to verify if the {ctx}.activeDocument is available to show and hide editor-related parts in the Object Panel. See example below:

// From the object panel API
    ready: function( ctx ) {
        // some other code...
        if ( ctx.activeDocument ) {
            // We are in the Editor.
            $('.custom-editor-metadata-panel').show();

            // Use it to manipulate the document

            var sel = ctx.activeDocument.getSelection();
            // ...

        }
    }

Starting form 5.2020.11 version only for the Object Panel of an opened story, the activeDocumentV2 object of the Editor API is available. So, it is possible, from the Object Panel, to call custom actions and manipulate the current document.

It is suggested, though, to verify if the {ctx}.activeDocumentV2 is available to show and hide editor-related parts in the Object Panel.

To learn how to extend the Object Panel, see Object panel documentation.

Full example of Editor extension

eidosmedia.webclient.commands.add({
    name: "custom.PrependText",
    action: function(ctx, params, callback) {
        var editorApi = ctx.activeDocument;
        // Obtain the current selection
        var sel = editorApi.getSelection();
        // Supposedly check the current selection ( if it a Content Item, ... )
        sel.insertXml('<p>test</p>', 'insertBefore');
    },

    isActive: function( ctx ) {
        // Enables it only for toolbar or Editable Image
        var componentType = ctx.component.getType();
        if ( componentType === 'toolbar' || componentType === 'editableimage' )
            return true;
        else
            return false;
    },

    isEnabled: function( ctx ) {
        var selection = ctx.activeDocument.getSelection();
        var contentItem = sel.getContentItem();

        if ( contentItem ) {
            return true;
        }
        return false;
    }
});

/**
 * We now register a new action in the editor toolbar using
 * the proper namespace. In this case we call the addButton
 * method for the toolbar visible in the editor instance. We
 * define a label and an icon for the button and more important
 * we tell the button which command has to trigger using the
 * action property
 */
eidosmedia.webclient.actions.editor.toolbar.addButton({
    action: "custom.PrependText",
    label: "Prepend Xml",
    icon: 'icon-edit'
});

eidosmedia.webclient.actions.editor.components.addButton({
    action: "custom.PrependText",
    label: "Prepend Xml",
    icon: 'icon-gear'
});

// Example for custom action for a single element in a Collection or Gallery component
eidosmedia.webclient.commands.add({
    name: "example.openVideoPreview",
    action: function(ctx, params, callback) {

        // params contains the selectedItem
        var selectedItem = params.selectedItem;

        // checks selectedItem values

    },

    isActive: function( ctx ) {
        // Handle condition activation
        return true;
    },

    isEnabled: function( ctx ) {
        // Handle condition enable
        return true;
    }
});

/**
 * We now register a new action in the editor components using
 * the proper namespace. We tell the button which command has to trigger using the
 * action property
 */
eidosmedia.webclient.actions.editor.components.addButton({
    action: "example.openVideoPreview",
    label: "Open Video Preview",
    icon: 'icon-facetime-video',
    singleItem: true
});

Override editor’s default counter

Swing editor comes with a default counter for chars and words. On the one hand, it is possible to specify an additional counter under the Editor configuration ( see Story editor configuration for more details on that) ; on the other hand, it is possible to completely override the counter behaviour by defining the following function.

eidosmedia.webclient.editor.counter = function( counterInfo, storyInfo, userInfo ) { /* function here */ }
Table 2. Method information

Method

counter

Parameter

{object} counterInfo - An object containing the information about the counter.

  • {integer} chars - the number of chars in the relative contentItem ( the document, unless different configurations )

  • {integer} words - the number of words in the relative contentItem ( the document, unless different configurations )

  • {string} contentItemType - the name of the selected contentItem ( only in case of "contentItem" setting for Editor/Story/Count/Type ).

  • {string} xpath - the xpath of the selected item ( only in case of "contentItem" setting for Editor/Story/Count/Type and a list of conteintItems is configured Editor/Story/Count/contentItems).

  • {string} customLabel - the value for the custom label ( only if specified in the Story editor configuration )

  • {integer} customValue - the value for the custom value ( only if specified in the Story editor configuration )

  • {boolean} currentNodeEmpty - true if the current node is empty

  • {string} occupation - the task occupation value inherited by the document.

Parameter

{object} storyInfo - An object containing the information about the current document.

  • {string} channel - The story channel

  • {various info} - try to use a console.log(storyInfo) to see all the passed information. Please stick to the "classic" properties ( channel, id, name ) for the others may change in the future.

Parameter

{object} userInfo - An object containing the information about the current user.

  • {various info} - try to use a console.log(userInfo) to see all the passed information.

Returns

{String} - the function should return the HTML for the counter area.

So, if the user wants to override the counter behaviour, the eidosmedia.webclient.editor.counter method provides all the available information. The result of the function MUST be a valid HTML which is placed in the "counter area" of the Editor.

To create a separator, use an empty DIV with the class "em-info-separator" (see examples below).
If you need to use this features, you’ll probably need to change the Editor > Story > Count configuration to contentItem. See the Story Editor Configuration.

Full working example

The following example warns the user if the number of characters in a particular content item is reaching ( or above ) the maximum level. It only works with the "contentItem" configuration.

(function() {
    eidosmedia.webclient.editor.counter = function( countInfo, storyInfo, userInfo ) {
        var charsStyle = '';
        var html = '';

        var countLimits = {
            'headline': 50,
            'text': 1500
        };

        var countXpathLimits = {
            '/doc/story/grouphead/headline': 10,
        };
        var found = false;

        if (countInfo.xpath) {
            var MAX_CHARS_IN_XPATH = countXpathLimits[ countInfo.xpath ];
            found = MAX_CHARS_IN_XPATH != null;
            if ( MAX_CHARS_IN_XPATH &&
                 countInfo.chars > MAX_CHARS_IN_XPATH ) {
                 charsStyle = 'color: white; font-weight: bold; background-color: red;';
            } else if ( MAX_CHARS_IN_XPATH &&
                        countInfo.chars > (MAX_CHARS_IN_XPATH*0.75) ) {
                // We have gone over the 75% of the allowed number of characters
                charsStyle = 'color: black; font-weight: bold; background-color: orange;';
            }
        }

        if ( !found && countInfo.contentItemType ) {
            var MAX_CHARS_IN_CONTENTITEM = countLimits[ countInfo.contentItemType ];
            if ( MAX_CHARS_IN_CONTENTITEM &&
                 countInfo.chars > MAX_CHARS_IN_CONTENTITEM ) {
                charsStyle = 'color: white; font-weight: bold; background-color: red;';
            } else if ( MAX_CHARS_IN_CONTENTITEM &&
                        countInfo.chars > (MAX_CHARS_IN_CONTENTITEM*0.75) ) {
                // We have gone over the 75% of the allowed number of characters
                charsStyle = 'color: black; font-weight: bold; background-color: orange;';
            }
        }

        html += '<span class="em-info-container" style="' + charsStyle +
                '"><b>Chars:</b> <span id="chars_counter">' + (countInfo.chars || 0) +
                '</span></span>';
        html += '<span class="em-info-container" id="words_counter_box">' +
                '<b>Words:</b> <span id="words_counter">' + (countInfo.words || 0) +
                '</span></span>';

        if ( countInfo.customValue ) {
            html += '<span class="em-info-container"><b id="custom_counter_label">' +
                    countInfo.customLabel + '</b><span id="custom_counter">' +
                    countInfo.customValue + '</span></span>';
        }
        return html;
    }
})();

Loaded document callback action

It is possible to register an action that would be called when the Swing editor has finished loading the current document. This action will be provided with the context object for the editor extensions and will be able to access the current document Editor API (activeDocument) and activeObject.

Example

(function () {
  // Register the after load callback
  eidosmedia.webclient.extensions.editor.afterLoad.register(function (ctx) {
    var team = ctx.getCurrentUserInfo().team;

    // Access the current loaded document
    var node = ctx.activeDocument.getNode({ xpath: "/doc/story/summary/p" });
    node.replaceTextContent("Team: " + team);
  });
})();