Dynamic Configuration File Techniques

When integrating EditLive into your application, it is often handy to generate your configuration files using a server-side web scripting language. However, as Adrian mentioned in a previous post, configuration files are cached by EditLive, which causes problems with a dynamic configuration. One of his solutions was to add a "cachebusting" parameter, the other was to use setConfigurationText. Today, I'll be expanding on the use of setConfigurationText with a dynamic configuration file.

The key issue is that you need a way to get the output from your configuration web script into a variable, so you can pass it to the setConfigurationText method. Below are several techniques for doing this. The final example provides an alternative to server-side scripting in generating dynamic configuration.

cfsavecontent

I was a ColdFusion programmer in a previous job, so the first solution that came to mind was to use the "cfsavecontent" tag. cfsavecontent is a very handy little tag, which stores the output of your script in a variable, rather than outputting it to the HTTP response. You then URL encode it, and pass it to the setConfigurationText call. e.g.

configuration.cfm

<cfsavecontent variable="myconfig">
<?xml version="1.0" encoding="utf-8"?>
<editlive>

</editlive>
</cfsavecontent>

mypage.cfm

<cfinclude template="configuration.cfm">
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script src="./editlivejava/editlivejava.js"></script>
</head>
<body>
<script language="JavaScript">
var editlivejava1;
editlivejava1 = new EditLiveJava("ELJApplet1", "700", "400");
editlivejava1.setConfigurationText("#URLEncodedFormat(myconfig)#");
editlivejava1.setDownloadDirectory("./editlivejava");
editlivejava1.show();
</script>
</body>
</html>
</cfoutput>

ASP line-by-line

I found a very clunky way to do this in ASP, as a response to a support case. This uses a "write" method to build up the configuration string, one line at a time. This isn't very manageable, as each line needs a "write" method. It may also get in the way of the dynamically-generated sections. Adding the "write" calls to the beginning and end of each line can easily be done with a regular expression replace, or with an editor which has "column editing" e.g. in JEdit you can ctrl-drag to select the first character in the line, then everything you type will end up in each line. Still, this is nowhere near as elegant as the cfcontent version.

Note: this example uses EditLive's ASP instantiation API, which does the URL encoding for you.

<%
dim config
config = ""

sub write(text)
config = config & text
end sub

function generateConfig
write("<editLiveForJava> ")
write(" <mediaSettings> ")
write(" <images> ")
write(" <imageList /> ")
write(" </images> ")
write(" </mediaSettings> ")
write(" <hyperlinks> ")
write(" <hyperlinkList /> ")
write(" </hyperlinks> ")
write("</editLiveForJava> ")

generateConfig = config
end function

Dim elglobal
set elglobal= New EditLiveForJavaGlobal
elglobal.DownloadDirectory = "/editlivejava"
elglobal.Init()

Dim editlive1
set editlive1 = new EditLiveForJava
editlive1.name="ELApplet1"
editlive1.width=600
editlive1.height=800
editlive1.configurationtext = generateConfig
editlive1.Body = "<p>loading default content.</p>"
editlive1.show
%>

AJAX

The below code uses the Prototype Javascript library to get the configuration using an AJAX request. Note: this technique causes an extra HTTP request to get the config, which negates a key benefit of setConfigurationText.

However, if you're creating an AJAX application, it may be more cohesive for you to use AJAX to get the config. 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script src="/editlivejava/editlivejava.js"></script>
<script src="prototype.js"></script>

<script language="JavaScript">

function loadELJ(transport) {
var editlivejava1;
editlivejava1 = new EditLiveJava("ELJApplet1", "700", "400");
editlivejava1.setConfigurationText(encodeURIComponent(transport.responseText));
editlivejava1.setDownloadDirectory("/editlivejava");
$("mydiv").update(editlivejava1.getAppletHTML());
}

function loadConfigFail() {
alert('Error retrieving EditLive config.');
}

new Ajax.Request('config.asp', {
method:'post',
onSuccess: loadELJ,
onFailure: loadConfigFail
});
</script>
</head>
<body>
<div id="mydiv">
loading…
</div>
</body>
</html>

Server-side HTTP Request

I won't go into this in detail, but the basic concept is to get the configuration by doing a HTTP request from your server-side scripts.

In ASP, this can be done with the Msxml2.ServerXMLHTTP or WinHttp.WinHttpRequest.5.1 COM objects. Examples of using these objects can be found at http://www.asp101.com/samples/http.asp and http://www.asp101.com/samples/winhttp5.asp

In ColdFusion, this can be done with the "cfhttp" tag.

String Replacement

Another way to dynamically generate your configuration file is to use string replacement, rather than a server-side script. With this technique, you have a static configuration file containing "tokens", e.g. 

<images allowLocalImages="[%allowLocalImages%]" allowUserSpecified="[%allowUserSpecified%]">

You then read the file in (via HTTP, or straight off the filesystem), then use string replacement functions to replace the tokens with their actual values. 

This technique is useful when using the EditLive Swing SDK, as you may not have a web scripting environment available. That said, the technique can also be used from server-side and client-side web scripts. It's particularly useful when you only have minor changes to the configuration since it's very simple to implement.

Improving The English Dictionary

When compiling dictionaries for spelling checkers you need to find a balance between including all the words likely to be used and including too many which increases the chances of a typo resulting in a valid but incorrect word. Additionally, the more words in the dictionary the larger the download and the longer it takes to actually check the spelling. The default dictionaries that come with EditLive! are generally fairly small and contain the most common English words. You can extend that with any words from your specific vocabulary by creating a custom dictionary jar file (also see what the spelling checker defines as a word).

Some people, particularly those in the publishing and technology industries, have particularly expansive vocabularies and are frustrated by the limited number of words in the default dictionary. Compiling a comprehensive word list is a major undertaking so often these people just struggle through with the default. To help with that, we've gone out and found some freely available word lists to add in to your dictionary jars that significantly expand the set of recognized words.

To help pick the words that are most appropriate to your users, we've split these word lists up into a few separate files:

  • en_us.clx - a combination of words from the enable2k word list and 12dicts (specifically the 6of12 list) compiled by Alan Beale.
  • jargon.clx - a combination of common words from the jargon file (Ver 4.2.0-1) and words we've specifically added to update it and add some of the words that fall in between the technical and business worlds, plus a few words and company names to bring it up to date.
  • names.clx - The 500 most common male names and 500 most common female names from the US census data filtering out those that are likely to be misspellings of English words.
  • ephox.clx - a few Ephox product names.

We suggest you add any of these dictionaries that are relevant to the existing dictionary files in the en_us jar file. Simply unzip the jar file with your favorite zip utility then create a new zip file with the extra files. Make sure you include just the dictionary files and not any directories.

If you find these dictionaries useful or if you have suggested improvements or problems, please take a moment to let us know on the LiveWorks! mailing list (you can also use the web interface to the list provided by Nabble).

Filtering Pasted Content

Sometimes you need to go beyond the options that EditLive! provides for pasting content into the editor and filter the content yourself. For example, to remove images, update or change the URL of links or just forbid certain tags from being used. You can achieve this through our advanced APIs by setting a paste filter.

To provide a simple example, let's write a paste filter for a cat lovers site to change any reference to the word "dog" to be "cat". It may be silly, but it shows the important concepts to let you filter pasted content however you want.

Firstly, we need a class that implements com.ephox.editlive.PasteFilter:

public class SimplePasteFilter implements PasteFilter {

Next, we need the usual constructor that all plugins have, which accepts an ELJBean instance and we'll simply set the current class as the paste filter:

public SimplePasteFilter(ELJBean bean) {
  bean.setPasteFilter(this);
}

Finally, the one method that PasteFilter specifies in String filterIn(String) which is called whenever text or HTML content is pasted to allow the paste filter to make any required changes before the content is inserted into the document. In our case, we do a simple replaceAll:

public String filterIn(String source) {
  return source.replaceAll("dog", "cat");
}

That's it. Now whenever you paste content that contains the string "dog", it is replaced with "cat". You can download the complete source code for the SimplePasteFilter class as a starting point for whatever filter you need.

LiveWorks! Plugins Now Signed With Trusted Certificate

If you've ever used one of the plugins from LiveWorks! you've probably seen the extra security dialog that they display which can be pretty scary for end users. The dialogs comes up because the jar files are signed with a self-signed certificate which we distribute with the plugins to allow you to make changes and rebuild the plugin easily.

We've now updated all the plugins so that they are signed with the same certificate as EditLive! which avoids this extra security dialog. If you make changes and rebuild, the result will still be signed with the self-signed certificate, but the pre-compiled versions are signed with our certificate.

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.

Ephox at Lotusphere

A number of the Ephox crew will be at Lotusphere this year and we'd love to take the opportunity to meet as many people as we can. We'll have a booth on the showroom floor (booth 712) so please take some time to drop in, say hello and chat about the future of web content management.

I'll also be facilitating a birds of a feature session, BOF112: Mashup Web 2.0 with Web Content Management on Thursday, January 24 at 7:00am at "Swan Hotel, Peacock 1". We'll be discussing how to separate the hype of Web 2.0 from the real benefits and apply them to your WCM deployments. We'll also delve into some of the ways to integrate with IWWCM and Lotus Connections.

Finally, and perhaps most importantly, Ephox is cosponsoring a couple of parties on Tuesday January 22. Firstly the Ascendant party from 6pm to 8:30pm and then kicking on with the Australia Day party from 8:30pm to 12:30am.