In all my travels across the world wide web, I have not encountered an elegant solution to an issue that I would have thought been a simple one. How can I embed a HTML ‘What You See Is What You Get’ (WYSIWYG) editor into a Java application. All the nice free HTML WYSIWYG editors, tend to use javascript and load from a webpage, such as TinyMCE and FCKEditor. This works fine for the J2EE world but application developers are left stranded. So after some research I decided that I would try using the SWT Browser Widget to render a TinyMCE WYSIWYG editor.

Download TinyMCE4Java v0.1

Download Here
Version 0.3 Notes:

  • Currently supports only Windows
  • Requires internet connection to connect to a hosted copy of TinyMCE
  • Contains Traverse listener fix for correctly handling Carriage Return
  • See comments below by Christian Voigt for Firefox support

Requirements

To begin, you must have the SWT Plugin which is available from the eclipse website, download and integrate it with an existing java project, or create a new one. You will aslo need to download TinyMCE from their website

The Components

For this implementation, I have chosen to host the TinyMCE files on my domain, http://www.example.com/tiny_mce. This is mainly due to secuirty features of most browsers, you can run the files from local disk, but certain TinyMCE features will not work (specifically the ones that launch a new browser window). Here is a break down of what we will need to do:

  • TinyMCE library hosted on a website http://www.example.com/tiny_mce
  • Create a HTML file containing a text area and javasript references to the TinyMCE javascript library, also hosted alongside TinyMCE, i.e. on http://www.example.com/tiny_mce/index.html
  • Create a Java Class which interacts with TinyMCE.

Implementation

First off I started by finding a suitable location where I could reference my TinyMCE library, i will use http://www.example.com/tiny_mce for the purposes of this example. Extract the contents of the TinyMCE to this directory, remember that the TinyMCE files are located under the jscript folder in the archive.

I then created a new HTML document in the same folder location called index.html, this index file will be loaded into our SWT application when we launch the WYSIWYG editor.

1
2
3
4
5
6
7
8
9
10
11
	<html xmlns="http://www.w3.org/1999/xhtml">
	<head> 
	<script type="text/javascript" src="tiny_mce.js"></script>
	</head>
	<body onContextMenu="return false">
	<form method="push"">
	 <textarea id="elm1" name="elm1" rows="15" cols="80" style="width: 100%; height:100%;">
	</textarea>
	</form>
	</body>
	</html>

This is a very basically html file which contains a text area and links to the tinymce javascript files located in the same location.

Now we go to the Java side of things, and create a file called JTNYEditor.java containing this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
 
public class JTNYEditor {
   private final static String tnyURL = "http://www.example.com/tinymce/index.html";

This simply loads all the imports necessary and defines where we will be loading the index.html file from. I then create a method called showEditor(String html) which will launch the SWT Browser widget and point to the URL and display the content of the String html in the editor.

	public String showEditor(String htmltext) {
		final Display display = new Display();
		final Shell shell = new Shell(display);
...
		browser = new Browser(shell, SWT.BORDER);
		browser.setData("htmlcontent", htmltext);
...
		browser.addProgressListener(new ProgressListener() {
			public void changed(ProgressEvent event) {
			}
			public void completed(ProgressEvent event) {
				_loadHTMLText((String)browser.getData("htmlcontent"));
			}
		});
...
		browser.setUrl(tnyURL);
		shell.open();
...

All we are doing here is setting up a very basic Browser Widget and loading our index.html file into it. The trick here is that we want to load an existing HTML document into the browser widget so we can edit it. In order to do this, we need to add the HTML content into the page using javascript, once the page has finished loading. The way we do this is using the Event system.

1
    browser.setData("htmlcontent", htmltext);

This method places our html content into the browser widget as a kind of storage area, identified by “htmlcontent”. Now that our data is in the widget, we can use the event system to wait until the page has fully loaded from our website, then call _loadHTMLText() with the HTML data stored in the widget.

	private void _loadHTMLText(String htmlText) {
		String jscript = " setHTMLText('" + _formatHTMLText(htmlText) + "');";
		boolean result = browser.execute(jscript);
		if (!result) {
			/* Script may fail or may not be supported on certain platforms. */
			System.out.println("Script was not executed.");
		}
	}

All this does is make a simple call to the javascript function setHTMLText(); telling it to assign the formatted HTML Text (_formatHTMLText simply removes apostrophe’s and new lines from the text). This means we now need to add the setHTMLText(); javascript function to our index.html file.

1
2
3
4
5
6
7
8
9
10
11
   function setHTMLText(htcontent) {
		var tempt = document.getElementById('elm1');
		tempt.value = htcontent;
 
		window.onload = function() {
		   var editorInstance = tinyMCE.get('elm1');
  		   tinyMCE.addEvent(editorInstance.getDoc(), 'keydown',refreshEventHandler);
 
	  	   editorInstance.setContent(htcontent);		
		};
   }

Communicating back to java from javascript

This is where it gets slightly tricky, as SWT does not natively support communication from javascript to java, we have to use some trickery in order to get our html content back after it has been edited. SWT provides us with a series of event listeners which we can use to get arround this.

The Status Listener is fired when the window.status DOM object is updated. So we can change the value of the status to any String value and that value will then be passed through as an StatusTextEvent. So what we can do is change the status to contain our entire edited HTML document, and this will then pass our edited HTML back to java.

In our java application, this is the event listener we need to create:

browser.addStatusTextListener(new StatusTextListener() {
   public void changed(StatusTextEvent event) {
      browser.setData("query", event.text);
   }
});

This event will now fire every time there is an update to the status. So now all we need to do is create a javascript method in our index.html file to get the HTML Text.

1
2
3
4
   function getHTMLText() {
	var editorInstance = tinyMCE.get('elm1');
	return editorInstance.getContent();
   }

Now we just need to create a Java function to actually put this all together and get us the html text.

...
	private String tnyGetHTMLScript = "window.status=getHTMLText();";
...
	public String getHTMLText() {
		boolean result = browser.execute(tnyGetHTMLScript);
		if (!result) {
			/* Script may fail or may not be supported on certain platforms. */
			System.out.println("Script was not executed.");
		}
		String res = (String)browser.getData("query");
...
		return res;
	}

When this method is called, it calls a javascript function getHTMLText() and updates the window.status field at the same time, this triggers the StatusTextListener and sets the browser.setData(”query”) property which we can read in the getHTMLText() function and returns the completely edited HTML text.

The Result

After putting all this together, we end up with a simple SWT frame containing the TinyMCE editor inside that we can just call using:

1
2
	JFCKEditor je = new JFCKEditor();
	System.out.println("html = \n" + je.showEditor("Hello World!"));