Inserting Content Between Uneditable Sections
One of our long time partners came to us with a problem recently - they use the contenteditable attribute heavily to make sections of the document uneditable for users. The problem was that when two uneditable sections were next to each other, users can't insert anything between them. For many of our clients this is the desired behavior but it doesn't suit everyone. To solve the problem we actually need a new method to temporarily ignore the contenteditable attribute and allow our code to make changes. That new method is now available in all the early access builds via the DocumentModifier.setOverrideContentEditable method. We'll also make use of the technique for finding specific parent elements described in last week's article.
The solution we'll create is to add a custom menu item that allows a user to insert content before the current element. In the process, we'll demonstrate a very powerful form of the insertHtml method available from DocumentModifier that enables a wide range of possibilities.
First we need to define the custom menu item in our XML configuration file:
<shrtMenuItem name="insertBefore" action="raiseEvent" value="insertBefore" text="Insert Before" />
Simple enough, when the menu's selected it will raise an event we can catch with the value "insertBefore". We need to register an editor event listener to catch that event:
bean.addEditorEventListener(this);
Then implement the EventListener interface with a raise event method:
public void raiseEvent(TextEvent e) {
if (e.getActionCommand() == TextEvent.CUSTOM_ACTION &&
e.getExtraInt() == TextEvent.CustomAction.RAISE_EVENT &&
"insertBefore".equals(e.getExtraString())) {
insertBeforeSection();
}
}
We're looking for the text event with the TextEvent.CUSTOM_ACTION action command, TextEvent.CustomAction.RAISE_EVENT as the extra int and "insertBefore" as the extra string. When we get it we call the method that does all the work.
The basic algorithm we'll follow is:
- Find the element for the uneditable section.
- Override the contenteditable attribute.
- Insert an empty paragraph before the uneditable section.
- Put the caret into the new paragraph.
- Enable the contenteditable attribute again.
To find the uneditable section we use the getUneditableBlock method from the previous article.
Element uneditableBlock = getUneditableBlock(bean);
Next we use the new setOverrideContentEditable method to make the section editable temporarily.
DocumentModifier modifier = bean.getDocumentModifier(); modifier.setOverrideContentEditable(true);
Inserting the empty paragraph is a little complex because we need to insert it at the right point in the HTML element structure. If we used the normal insertHTMLAtCursor function, the paragraph would be inserted into the uneditable section:
What we really want is to insert the new paragraph into the parent element of the uneditable DIV so that it becomes a sibling of the DIV:
To achieve this, we need to use the full form of the insertHTML method in DocumentModifier:
modifier.insertHtml(uneditableBlock.getStartOffset(), "<p></p>", HTML.Tag.P, uneditableBlock.getParentElement());
The first two arguments are fairly simple - the character offset to insert the content at and the HTML string to insert. The next two arguments are what give this method so much power.
The HTML.Tag.P argument specifies the first tag to start inserting content from. When the parser parses the HTML string we're inserting, it always creates a complete, well-formed HTML document including the HTML and BODY tags. To insert just a snippet of HTML instead we have to specify when to start inserting. With the simple form of insertHTML this is automatically set to the first tag you actually specify in the HTML string, but complex insertions occasionally require manually setting this parameter. In this case, we want to start inserting from the P tag we specified so we pass in HTML.Tag.P.
The final argument is the one we really need in this case. It allows us to specify how far up the element tree to insert the new content. The simple form of insertHtml always inserts the content as close to the leaf elements as allowed in HTML - in our case that would insert the P tag into the uneditable DIV because that's the first element that is allowed to contain a P tag. If we were inserting an IMG tag it would be inserted into the P tag inside the DIV if there were one etc. By specifying the parent of the uneditable DIV here, we ensure that the inserted paragraph has the same parent element as the uneditable DIV, making them siblings.
Now we need to move the character into the newly inserted paragraph. Since the inserted paragraph is immediately before the uneditable DIV we can just set the caret one position before the start of the DIV and it will be in the new paragraph.
bean.getHTMLPane().setCaretPosition(uneditableBlock.getStartOffset() - 1);
Finally we enable the contenteditable attribute again.
modifier.setOverrideContentEditable(false);
When we put the method together it winds up looking like:
private void insertBeforeSection() {
Element uneditableBlock = getUneditableBlock(bean);
DocumentModifier modifier = bean.getDocumentModifier();
modifier.setOverrideContentEditable(true);
try {
modifier.insertHtml(uneditableBlock.getStartOffset(),
"<p></p>", HTML.Tag.P,
uneditableBlock.getParentElement());
} catch (BadLocationException e1) {
e1.printStackTrace();
}
bean.getHTMLPane().setCaretPosition(uneditableBlock.getStartOffset() - 1);
modifier.setOverrideContentEditable(false);
}
While some of the concepts involved are quite foreign to developers who haven't worked with the Swing text APIs before, the resulting code is fairly simple and our users can now insert content between uneditable sections with a simple menu item.

