EditLive! Support For Safari

A number of our clients have been trying out Safari for Windows and finding that EditLive! doesn't load, instead falling back to a standard text area and thinking that we don’t support Safari at all. The reality is that Safari comes in two distinct versions - one for Mac OS X which we support and one for Windows which we don’t. While the two versions look almost identical, there are some key differences around the way they handle applets which is currently preventing EditLive! from running in the Windows version. We also want to make sure we’ve completely tested it out so there aren’t any nasty surprises.

So to be clear - Safari on Mac OS X is completely supported and works great. Safari on Windows isn’t yet supported and will use a text area instead.

Hidden Delights Of 6.4

While the CSS improvements in the upcoming 6.4 release may take center stage, there are a number of neat little quality of life improvements that you might be interested in:

Keyboard Shortcuts

Keyboard shortcuts are enabled for all items on the toolbar, even if they are not present in the menu or if there is no menu. This has been requested by clients a number of times. Sometimes space is extremely limited so you want to remove the menu bar entirely. Previously, this also disabled the keyboard shortcuts like control-B. With 6.4 adding items to the toolbar will also enable the keyboard shortcut.

Paragraphs In DIVs and Tables

We've improved the way content in DIVs and tables works when the user presses enter. Since the usual case for these elements is to have content directly inside them without any P tags, the editor inserted a BR tag when the user pressed enter. In 6.4 this has changed so that the existing content is wrapped in a P tag and a new empty paragraph tag is inserted. This makes the resulting content semantically correct and works a lot better when applying block styles or changing heading levels.

Navigation In Nested and Adjacent Elements

With previous versions of EditLive! it was difficult or impossible to position the caret between two adjacent tables or position it accurately in nested DIVs.  With 6.4 you can simply position the caret in the nested DIV and press up or down. EditLive! will automatically create a space for the caret to fit in so you can start typing. If you just keep pressing up to go past there it will remove the space again automatically. It's a bit hard to explain but we think it works really well.

Improved File Dialogs On OS X

This is my personal favorite. The open and save dialogs on OS X have always been pretty ordinary but in 6.4 we've dramatically improved them so they work just like the native ones (in fact, they are the native ones). While having a more consistent UI is a big benefit on it's own, the native dialogs let you easily browse your iPhoto library to select local images.

Gridlines Around DIVs

EditLive! 6.4 adds a lot of support for DIVs and one of the enhancements is that we make the visible to users by displaying gridlines around them just like we do for tables. Of course, if you've been working with DIVs already you might find those gridlines just get in the way so we've provided a simple switch to turn them off. Just set the wysiwygEdior attribute “showSectionGridlines” to “false”.

And More…

For all the details, including the long list of bug fixes, the change log is available on the early access page where you can also download the latest build to try out.

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:

  1. Find the element for the uneditable section.
  2. Override the contenteditable attribute.
  3. Insert an empty paragraph before the uneditable section.
  4. Put the caret into the new paragraph.
  5. 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.

Finding Specific Parent Elements

When developing custom functionality within EditLive! you occasionally want to work with a specific element in the tree. For example, you might want to find the element that wraps an uneditable section that the user has clicked within. Other examples might be implementing a properties dialog for a custom block tag you've registered or finding the table tag that the caret is within.

We'll use the uneditable section example in this article, but it's simple to adjust this technique for whatever type of element you need to find.

Set Up

We'll assume you've already got a plugin set up and have access to the ELJBean instance for the editor. If not, take a look at our previous article on creating plugins.

Main Loop

The basic idea is to start from the character element (the leaf element in the tree) and work our way back up until we find an element that matches our criteria. If there isn't a matching element we'll eventually reach the root of the tree and return null.

public Element getUneditableBlock(ELJBean bean) {
  JTextPane pane = bean.getHTMLPane();
  HTMLDocument document = (HTMLDocument) pane.getDocument();
  Element element = document.getCharacterElement(pane.getCaretPosition());
  while (element != null && !isUneditableBlock(element)) {
    element = element.getParentElement();
  }
  return element;
}

Note that the getHTMLPane() method returns a standard JTextPane so all the usual Swing text component methods are available. Iterating up the tree is very similar to iterating up a standard DOM.

Conditional Selection

All that's left is to implement the isUneditableBlock method which provides the conditional logic for selecting the element we're looking for. This is the method that will vary based on exactly what you're looking for. In this case we're looking for an element that has a "contenteditable" attribute that's set to "false".

private boolean isUneditableBlock(Element element) {
  AttributeSet attributes = element.getAttributes();
  return attributes.isDefined("contenteditable") &&
    attributes.getAttribute("contenteditable").equals("false");
}

Note that we check attributes.isDefined("contenteditable") as well as that it's set to false because getAttribute will search through the parent elements as well. Also note that the contenteditable attribute isn't recognized by the parser so it appears as a string - recognized attributes use usually instances of HTML.Attribute or CSS.Attribute, for example HTML.Attribute.WIDTH or CSS.Attribute.COLOR.

An alternate implementation of the method that finds the table element would be:

private boolean isUneditableBlock(Element element) {
  return HTML.Tag.TABLE ==
    element.getAttributes().getAttribute(AttributeSet.NameAttribute);
}

Doing Something Useful

Just to round out our example, here's a mouseClicked implementation that would use these methods to select the entire uneditable section whenever the user clicks within it.

public void mouseClicked(MouseEvent e) {
  Element block = getUneditableBlock(bean);
  if (block != null) {
    bean.raiseEvent(new TextEvent(this, TextEvent.SELECT_NODE_ACTION, block, -1));
  }
}

Note that you have to wait for the LOADING_COMPLETE event to fire before adding the mouse listener, otherwise ELJBean.getHTMLPane will return null. For more information, see the previous article on initializing plugins.

Creating A “See Also” Panel

With the improved styles support in EditLive! 6.4 (grab the latest build from early access), and a little CSS, it's easy for users to make their content look great and better engage users. Let's look at a fairly simple example where users can add a set of links to related resources in their document. Currently, users would simply add a plain bullet list which is pretty ugly. For example:

See Also

That does the job, but with the improvements in EditLive! 6.4 and some fairly simple CSS we can make it look much more compelling. The first step is to group the heading and the list together in a group. EditLive! 6.4 provides a simple way to do that - just select them both and choose "Create Section" (the default configuration includes it in the Format menu). In terms of the HTML, this wraps a DIV element around the heading and the list which gives us some extra structure to work with but doesn't change the actual rendering.

That extra structure gives us a great place to add styles to control the see also panel from one place. The goal is that the user can simply select one item from the styles drop down and all of our formatting is applied. Let's start by adding some simple styles to the DIV itself:

div.SeeAlso {
    border: 1px solid #D4D4D4;
    width: 20em;
    margin: 0px 10px 0px 10px;
    float: right;
}

Those styles add a simple gray border, set the width of the DIV to 20em (ems are a relative sizing that's newly supported in 6.4 and is much better for accessibility than using px) and floats the DIV to the right so content can wrap around it. The result is on the right, titled "See Also (2)".

Now we want to make the heading and the list itself look better, but we don't want to make the user apply styles to them separately. Fortunately, CSS provides a descendant selector which fits this use case perfectly. We add the styles below which will automatically apply to the content inside the DIV:

div.SeeAlso h4 {
    margin: 0px;
    padding: 5px;
    font-size: 1em;
    background-image: url(largeTitleBar.png);
    color: #FFFFFF;
    background-color: #5E657B;
    background-repeat: repeat-x;
    background-position: center;
    background-attachment: scroll;
}

div.SeeAlso ul {
    margin: 0px;
    padding: 2px 5px 2px 35px;
    list-style-image: url(bluearrow.png);
}

div.SeeAlso li {
    margin: 0px;
    padding: 3px;
}

div.SeeAlso a {
    font-weight: bold;
    text-decoration: none;
    color: #2B3D72;
}

div.SeeAlso a:hover {
    text-decoration: none;
    text-decoration: underline;
    color: #8599D3;
}

See Also Panel With StylesWhile there's a fair bit of CSS there, it's all standard techniques that web designers are quite used to using for styling content. It also uses a couple of background images to provide nice gradients so that the final result looks like the image on the right. Here's the important elements that make it work well with the editor. Keep these in minds when you develop style sheets for your site:

  • Make it easy for users to apply by using a single class on the div element and then apply styles via descendant selectors whenever possible.
  • The class name you use will appear in the styles drop down, so make it self explanatory to help users know when to apply it.
  • The styles combo is context sensitive so it will show an accurate preview of how the style will look in the current context. So with the stylesheet above, the "Heading 4" entry would appear differently in the styles drop down if the current selection is within a "SeeAlso" div because the descendant style applies. Elsewhere, the default rendering of h4 elements would apply. This lets your users know what things will look like without having to try out each style. You could use this to provide different types of rendering for the header based on different heading levels and the user can pick the most appropriate one.