Creating A Custom View

One of the most powerful, and unfortunately most complex, features of EditLive! is the ability to register your own custom view for an element. You can do this either for existing elements such as IMG to handle them your own way, or generally more usefully for your own custom elements so they don't just render as yellow boxes. As a trivial example, let's look at creating a custom view for the rather simplistic tag:

<simple value="Hello Custom View World" />

We want to give users the ability to easily change the value attribute inline without popping up a separate dialog. To do so, we'll embed a JTextField into the HTML document and keep the contents in sync with the attribute's value. First we create a really simple class that registers the custom view:

public class SimplePlugin {
  // Must have a constructor that takes an ELJBean
  public SimplePlugin(ELJBean bean) {
    // We pass the bean in as the "extra data".
    // This could be anything but it tends to be really useful to
    // pass in the bean so you can access all of EditLive! from within the view.
    bean.registerCustomEmptyView("simple", SimpleView.class.getName(), bean);
  }
}

The key parts of this are:

  • We're registering a view for an "empty" tag. This lets the HTML parser know that there shouldn't be any content in the "simple" tag (just like for an img tag). We could have chosen "inline" or "block" if there was content and it would have been parsed like a span or div tag respectively.
  • The tag name is "simple".
  • We pass in the fully qualified class name to specify the view to load. The class will be instantiated automatically when needed. Note that from EditLive! 6.4 onwards you can (and should) pass in the actual class instead of the name (so SimpleView.class instead of SimpleView.class.getName()).
  • The third parameter takes any object and passes it into the view's constructor. This is a useful way to pass configuration information through to the view or in this case, we pass in the ELJBean instance to let the view interact with the rest of the editor easily.

Now we just have to define the SimpleView class. Firstly, it must extend from javax.swing.text.View - in this case we're extending from a subclass of View designed to make it easy to embed Swing components, ComponentView:

public class SimpleView extends ComponentView implements DocumentListener {

We implement DocumentListener to get the updates from the JTextField which we'll see later.

Secondly, the constructor must either accept just a javax.swing.text.Element or a javax.swing.text.Element and an Object. The Object parameter is that third parameter in the call to registerCustomEmptyView. So our constructor is:

public SimpleView(Element elem, Object extraData) {
  super(elem);
  _bean = (ELJBean)extraData;
} 

The meat of this view is to create the actual JTextField which we do by overriding the createComponent method that ComponentView defines:

protected Component createComponent() {
  _textField = new JTextField();
  String value = (String)getAttributes().getAttribute(HTML.Attribute.VALUE);
  if (value != null) {
    _textField.setText(value);
  }
  _textField.getDocument().addDocumentListener(this);
  return _textField;
}

Creating the text field is pretty straight forward, but we need to keep the text in sync with the value attribute in our tag. To retrieve the initial value we get the attributes for the view using getAttributes() (which is defined in javax.swing.text.View). The values in the returned AttributeSet are in a parsed form so any standard HTML attributes (like value) are actually stored as instances of HTML.Attribute instead of just under the string "value". The same applies to recognized CSS values which are stored under CSS.Attribute instances. The values themselves may also be parsed (particularly for CSS values) but the toString() methods will give you the original String value back.

Finally, we add the view as a document listener on the text field so we can apply any changes the user makes back to the original document. Each of the DocumentListener methods delegate to:

private void updateValue() {
  String currentValue = _textField.getText();
  SimpleAttributeSet add = new SimpleAttributeSet();
  add.addAttribute(HTML.Attribute.VALUE, currentValue);
  try {
    // Always make changes to the document through the
    // document modifier otherwise track changes
    // data will be corrupted and you may accidentally
    // break one of the many assumptions that Swing makes
    // about the way the document is structured.
    _bean.getDocumentModifier().adjustCharacterAttributes(
      getStartOffset(), getEndOffset(), add, Collections.EMPTY_LIST);
  } catch (BadLocationException e) {
    e.printStackTrace();
  }
}

So we simply retrieve the new value from the text field and use the DocumentModifier class. It's important that you always use the DocumentModifier class to make changes to the document as it ensures that the changes are tracked correctly with track changes (and that any existing tracked changes are updated to account for the change) and that the document structure remains valid.

That's it. Whenever the user makes changes in the text field, the attribute in the HTML is updated automatically. You can download the full source of both classes for a better look.