Simplest approach to an Author-mode customization

Post here questions and problems related to oXygen frameworks/document types.
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Simplest approach to an Author-mode customization

Sat Feb 23, 2019 3:49 am

Hi there,

I'd like to ask what the most effective approach will be to providing the following simple sequence of actions in Author mode, through a plugin. I'd like to use JavaScript rather than Java, but if Java is required, I can live with it.

This is the sequence of actions:

1. User selects text, which causes a toolbar button and context menu item to become enabled.

2. User clicks that item.

3. Check that text is selected.

4. Generate a unique @xml:id, and present it to the user to change or use as they see fit.

5. Insert an anchor at the selection start position with @next="#the_id".

5. Insert an anchor at the selection end position with @xml:id="the_id".

6. Add @ana to the first anchor, and place the cursor inside it.

7. Provide a drop-down list of values for that @ana, taken from a RelaxNG schema, with annotations also from the schema, just like when editing an attribute in the Text mode of Oxygen.

I can already do 4 through 6 using a Code Template in Text mode, and the schema then provides options for the attribute value, but this doesn't work in Author modes. I see that I can easily do 3 through 5 using JS to talk to the API:

https://www.oxygenxml.com/InstData/Edit ... rPage.html

But I can't see from the examples in the JavaScript examples on GitHub how to construct a dialog box from schema values, including the explanatory annotations, such as we see in Text mode. Can someone point me at some examples?
Radu
Posts: 6050

Re: Simplest approach to an Author-mode customization

Tue Feb 26, 2019 12:14 pm

Hi Martin,

To get you started on this, here's a sample Oxygen plugin (Javascript based) which adds a contextual menu to the Author visual editing mode:

https://github.com/oxygenxml/wsaccess-j ... AuthorPage

now going back to your questions:
1. User selects text, which causes a toolbar button and context menu item to become enabled.
Let's focus first on the contextual menu action, you can choose to add it or not to the contextual menu depending on what the "authorAccess.getEditorAccess().hasSelection()" method returns.
2. User clicks that item.
3. Check that text is selected.
Again, use authorAccess.getEditorAccess().hasSelection(). Maybe also use authorAccess.getEditorAccess().getSelectedText() because in the Author mode the end user may select element tags without selecting any relevant text.
4. Generate a unique @xml:id, and present it to the user to change or use as they see fit.
Maybe use something like:

Code: Select all

Packages.ro.sync.exml.workspace.api.PluginWorkspaceProvider.getPluginWorkspace().getUtilAccess().expandEditorVariables("${id}", null)
or do your own thing to obtain that unique ID, maybe use the Java UUID class if you want longer IDs.
Then present it to the end user using something like:

Code: Select all

var selectedValue = Packages.javax.swing.JOptionPane.showInputDialog(
pluginWorkspaceAccess.getParentFrame(),"Please set the ID value", defaultIDValue);
5. Insert an anchor at the selection start position with @next="#the_id".
Probably something like:
ro.sync.ecss.extensions.api.AuthorDocumentController.insertXMLFragment(String, int)

Code: Select all

documentController.insertXMLFragment("<anchor id='" + uniqueID + "'/>",
authorAccess.getEditorAccess().getSelectionStart());
5. Insert an anchor at the selection end position with @xml:id="the_id".
Same but instead use the "getSelectionEnd()" API.
6. Add @ana to the first anchor, and place the cursor inside it.
Either add the attribute directly when the "insertXMLFragment" is called with the anchor.
Or use our API to set an attribute on a certain element:

https://github.com/oxygenxml/javascript ... lements.js
7. Provide a drop-down list of values for that @ana, taken from a RelaxNG schema, with annotations also from the schema, just like when editing an attribute in the Text mode of Oxygen.

I can already do 4 through 6 using a Code Template in Text mode, and the schema then provides options for the attribute value, but this doesn't work in Author modes. I see that I can easily do 3 through 5 using JS to talk to the API:
....
But I can't see from the examples in the JavaScript examples on GitHub how to construct a dialog box from schema values, including the explanatory annotations, such as we see in Text mode. Can someone point me at some examples?
We have a sample plugin which shows a dialog with a list of values here:

https://github.com/oxygenxml/wsaccess-j ... Completion

but you would like those values to come from the schema associated to the XML document.
So you would probably also need to use these APIs:

https://www.oxygenxml.com/InstData/Edit ... aManager--

Code: Select all

ro.sync.ecss.extensions.api.AuthorSchemaManager.createWhatPossibleValuesHasAttributeContext(AuthorElement, String)

Code: Select all

ro.sync.ecss.extensions.api.AuthorSchemaManager.whatPossibleValuesHasAttribute(WhatPossibleValuesHasAttributeContext)
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Tue Feb 26, 2019 8:56 pm

Thanks indeed for this really detailed and helpful answer!
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Wed Mar 13, 2019 12:47 am

Hi there,

This is working nicely so far, but I'm having a problem getting my plugin's right-click menu item to show up when the project itself has a document type defined, with its own Author Mode CSS. Is there something special I have to do in the Document Type configuration to make my plugin available? It works fine as long as I'm editing a document in a project with no configured document types, but I really need to be able to do this for a configured document type.

All help appreciated,
Martin
Radu
Posts: 6050

Re: Simplest approach to an Author-mode customization

Wed Mar 13, 2019 8:22 am

Hi Martin,

A plugin should work alongside any opened XML document, no matter to what document type configuration it pertains.
If you start Oxygen using its "oxygen.bat" command line executable (from the Oxygen installation folder) does it report any error?
Can you show me some source code with your current approach? If the plugin is not open source maybe you can send it to support@oxygenxml.com

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Wed Mar 13, 2019 7:22 pm

The symptom is that the menu item appears when I have no project open and just open a random XML document; but when I open a project which has a defined document type, along with code templates and so on, the menu item often fails to appear. I can get it to appear if I re-copy the plugin content into the plugin folder immediately before launching Oxygen, but once I close Oxygen and start it up again, the menu item doesn't show up. Sometimes it works for the first document I open, but when I open a second document in Author mode, the item fails to appear for that document, even though it still appears for the first document.

I'm running Oxygen on Ubuntu 18.10 using the default startup script:

/home/mholmes/Oxygen XML Editor 21/oxygen21.0

This is my plugin.xml:

Code: Select all

<!DOCTYPE plugin PUBLIC "-//Oxygen Plugin" "../plugin.dtd">
<plugin
id="com.oxygenxml.popup.endingsAna"
name="Add Analysis Tagging To Author page popup-menu"
description="Plugin adds contextual menu action to Author page pop-up menu allowing tagging of a fragment with a specific analytic category."
version="1.0"
vendor="HCMC"
class="ro.sync.exml.plugin.Plugin"
classLoaderType="preferReferencedResources">
<extension type="WorkspaceAccessJS" href="wsAccess.js"/>
</plugin>
This is my JS:

Code: Select all

/* Adapted from example for working with the Author editing mode, started from: https://www.oxygenxml.com/forum/post49889.html#p49886 */
function applicationStarted(pluginWorkspaceAccess) {
Packages.java.lang.System.err.println("Application started " + pluginWorkspaceAccess);
var edChangedListener = {
/*Called when a document is opened*/
/*See: https://www.oxygenxml.com/InstData/Editor/SDK/javadoc/ro/sync/exml/plugin/workspace/WorkspaceAccessPluginExtension.html */
editorOpened: function(editorLocation) {
Packages.java.lang.System.err.println("\nrunning " + editorLocation);
/*Get the opened editor, each opened XML document corresponds to "WSEditor" object*/
/* See: https://www.oxygenxml.com/InstData/Editor/SDK/javadoc/ro/sync/exml/workspace/api/PluginWorkspace.html#getEditorAccess-java.net.URL-int- */
var editor = pluginWorkspaceAccess.getEditorAccess(editorLocation, Packages.ro.sync.exml.workspace.api.PluginWorkspace.MAIN_EDITING_AREA);
/* An editor (WSEditor) has three editing modes (Text/Grid/Author) so we need to check that it's opened in the "Author" editing mode */
/* We can also add listeners to know when end user switches between edit modes */
/* WSEditor API: https://www.oxygenxml.com/InstData/Editor/SDK/javadoc/ro/sync/exml/workspace/api/editor/WSEditor.html */
if (editor.getCurrentPageID() == "Author") {
//The current editor is opened in the "Author" visual editing mode
authorPage = editor.getCurrentPage();
/*Add listener called when right click is performed in the Author editing mode*/
/* See: https://www.oxygenxml.com/InstData/Editor/SDK/javadoc/ro/sync/exml/workspace/api/editor/page/author/WSAuthorEditorPageBase.html */
customizerObj = {
customizePopUpMenu: function(popUp, authorAccess) {
Packages.java.lang.System.err.println("RIGHT CLICK" + popUp);
try {
/*Create menu items, the Javascript plugin creates Swing components (menu items in this case)
* because Oxygen is Java Swing based. */
mi = new Packages.javax.swing.JMenuItem("Tag analysis");
popUp.add(mi);
actionPerfObj = {
actionPerformed: function(e) {
var selText = authorAccess.getEditorAccess().getSelectedText();
Packages.java.lang.System.err.println("Selection length: " + selText.length());
try {
/* Check if have selected content */
if (selText.length() > 0) {
var uid = Packages.ro.sync.exml.workspace.api.PluginWorkspaceProvider.getPluginWorkspace().getUtilAccess().expandEditorVariables("${id}", null);
Packages.java.lang.System.err.println("Generated id: " + uid);
documentController = authorAccess.getDocumentController();
var selEnd = authorAccess.getEditorAccess().getSelectionEnd();
var selStart = authorAccess.getEditorAccess().getSelectionStart();
Packages.java.lang.System.err.println("selStart: " + selStart + "; selEnd: " + selEnd);

//We have to add the anchor at selEnd first, otherwise the offsets get screwed and the
//second anchor is inserted in the wrong place.
documentController.insertXMLFragment("<anchor xml:id='" + uid + "'/>", selEnd);
documentController.insertXMLFragment("<anchor next='#" + uid + "'/>", selStart);

//Now we need to add the attribute value. First find the element we just added.
var newEl = documentController.findNodesByXPath("//anchor[@xml:id = '" + uid + "']", true, true, true);
documentController.setAttribute("ana", new Packages.ro.sync.ecss.extensions.api.node.AttrValue("testing"), newEl[0]);

}
} catch (e1) {
e1.printStackTrace();
}
}
}
mi.addActionListener(new JavaAdapter(Packages.java.awt.event.ActionListener, actionPerfObj));
} catch (e1) {
Packages.java.lang.System.err.println(e1);
}
}
}
//Add the popup menu customizer.
authorPage.addPopUpMenuCustomizer(new Packages.ro.sync.ecss.extensions.api.structure.AuthorPopupMenuCustomizer(customizerObj));
}
}
}
var edChangedListenerAdapter = new JavaAdapter(Packages.ro.sync.exml.workspace.api.listeners.WSEditorChangeListener, edChangedListener);
/* Add the editor changed listener */
pluginWorkspaceAccess.addEditorChangeListener(
edChangedListenerAdapter,
Packages.ro.sync.exml.workspace.api.PluginWorkspace.MAIN_EDITING_AREA);
}

function applicationClosing(pluginWorkspaceAccess) {
/* The application is closing */
Packages.java.lang.System.err.println("Application closing " + pluginWorkspaceAccess);
}
Sometimes I see an error like this:

Code: Select all

Exception in thread "AWT-EventQueue-0" org.mozilla.javascript.EcmaError: TypeError: Cannot find function printStackTrace in object InternalError: Can't find method ro.sync.ecss.g.i.setAttribute(string,ro.sync.ecss.extensions.api.node.AttrValue,org.mozilla.javascript.Undefined). (file:/home/mholmes/Oxygen%20XML%20Editor%2021/plugins/endingsPluginAna/wsAccess.js#49). (file:/home/mholmes/Oxygen%20XML%20Editor%2021/plugins/endingsPluginAna/wsAccess.js#53)
I can't share project data, because it contains confidential interviews. I tried to include the XPR file for the project, but it's too large; I can send that if it will help.

One last question: how do I specify a namespace for elements I insert? They're being inserted in the empty namespace right now.
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Wed Mar 13, 2019 11:05 pm

One more thing I'm trying to figure out:

At the moment, the operation (which inserts two elements and adds an attribute) is carried out in three steps; that means that if the user wants to undo it, they need to undo three times, which is a little confusing for them. Is there a way to wrap a sequence of actions in the plugin such that they become a single undo step?

Thanks,
Martin
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Thu Mar 14, 2019 3:16 am

I think I've figured out the problem with regard to some files getting the new menu item and some not; but I'm not sure how to solve it.

If a document is opened in Author mode, then it gets the item. If it's opened in Text mode (which is the default for most of our projects), then it doesn't get the item, even when it's switched to Author mode, because the edChangedListener is listening for document open events only.

So I need to add an event listener for when the edit mode is changed, in addition to when a document is opened or selected. I can't see a way to do that.

Cheers,
Martin
Radu
Posts: 6050

Re: Simplest approach to an Author-mode customization

Thu Mar 14, 2019 8:51 am

Hello Martin,
So:
If a document is opened in Author mode, then it gets the item. If it's opened in Text mode (which is the default for most of our projects), then it doesn't get the item, even when it's switched to Author mode, because the edChangedListener is listening for document open events only.
So I need to add an event listener for when the edit mode is changed, in addition to when a document is opened or selected. I can't see a way to do that.
That's the problem with this particular popup menu customizer API you are using, indeed you would need to add an extra listener on the opened WSEditor, and we have this listener:

https://www.oxygenxml.com/InstData/Edit ... dListener-

so this can be done but the code becomes more complicated. This is why in Oxygen 18 we added this new API:

https://www.oxygenxml.com/InstData/Edit ... ustomizer-

and each time the popup menu is shown in the Author visual editing mode the customizer issues a specific callback that you can implement on your side:
https://www.oxygenxml.com/InstData/Edi ... horAccess-

I do not seem to have a sample about how to do this from Javascript, if you have difficulties with it I can try to come up with some sample code.
The new "MenusAndToolbarsContributorCustomizer" API also has callbacks for populating the main Author menu and also the Author toolbar (which might also come handy).
At the moment, the operation (which inserts two elements and adds an attribute) is carried out in three steps; that means that if the user wants to undo it, they need to undo three times, which is a little confusing for them. Is there a way to wrap a sequence of actions in the plugin such that they become a single undo step?
Right, use:

Code: Select all

documentController.beginCompoundEdit();
////DO STUFF HERE
documentController.endCompoundEdit();
https://www.oxygenxml.com/InstData/Edit ... oundEdit--

One last question: how do I specify a namespace for elements I insert? They're being inserted in the empty namespace right now.
Specify the namespace directly in the XML like:

Code: Select all

documentController.insertXMLFragment("<anchor xml:id='" + uid + "' xmlns=\"http://www.tei-c.org/ns/1.0\"/>", selEnd);
The namespace declaration itself will not appear in the serialized XML document as it is already declared on the root element but it's a way for Oxygen to know exactly what namespace the inserted XML fragment is in.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Thu Mar 14, 2019 6:21 pm

Thanks Radu! I'll have a go at figuring out the MenusAndToolbarsContributorCustomizer thing. I'm increasingly wondering if it would make sense to move this work to Java, but the JS approach is very convenient (no need for the SDK etc).

All the best,
Martin
Radu
Posts: 6050

Re: Simplest approach to an Author-mode customization

Thu Mar 14, 2019 6:30 pm

Hi Martin,

Well, I originally created this Javascript-based plugin extension in order to get more people to start creating plugins for Oxygen. As the code grows in complexity I think you need to start to consider moving to Java because it provides compilation time errors, better IDEs (Eclipse, IntelliJ) and the code looks cleaner in Java.
The easiest start to create a Java-based Workspace Access plugin is by cloning this Maven-based project in the Eclipse workspace (Eclipse being an open source IDE for editing Java and other languages):

https://github.com/oxygenxml/sample-plu ... ace-access

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Fri Mar 15, 2019 2:04 am

Just for completeness I'd like to try one more time with JS -- could I get a hint as to the process of using the MenusAndToolbarsContributorCustomizer approach in JS?

Meanwhile, I'll download the SDK and start getting comfortable with the Java.

Cheers,
Martin
Radu
Posts: 6050

Re: Simplest approach to an Author-mode customization

Fri Mar 15, 2019 9:28 am

Hi Martin,

I re-wrote the Author contextual popup menu example to use the new API, this greatly simplifies the code, you no longer need to add an editor opened listener:

https://github.com/oxygenxml/wsaccess-j ... sAccess.js

and I did the same thing for the Text page popup menu customizer:

https://github.com/oxygenxml/wsaccess-j ... sAccess.js

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
martindholmes
Posts: 123
Location: Victoria, BC, Canada

Re: Simplest approach to an Author-mode customization

Mon Mar 18, 2019 7:06 pm

Thanks Radu, that works perfectly!

Return to “SDK-API, Frameworks - Document Types”

Who is online

Users browsing this forum: No registered users and 1 guest