Java WYSIWYG editor using SWT
Java May 29th, 2008In 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!")); |

June 5th, 2008 at 8:55 pm
Hey! thanks a lot for your detailed explanation. I have used your solution in a wizard and stumbled across a mysterious phenomenon: Everything works fine as long as one does not try to use the enter key to insert a line break.
The key event does not get to the tinyMce editor. Instead the finish button of the wizard gets the event and calls performFinish so that the wizard closes. That is mysterious because as long as one uses a simple form element in the browser widget, everything works just fine.
do you have any ideas how to prevent this or what the cause of it is?
regards,
christian
June 5th, 2008 at 11:04 pm
problem solved.
Just in case anyone has the same problem:
The solution can be found here: http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg40895.html
The problem is caused by the shell’s setDefaultButton and can be solved by adding a traverse listener to the browser widget.
regards,
christian
June 6th, 2008 at 5:33 am
found the solution here:
http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg40881.html
the problem is the shell’s setDefaultButton and it can be solved by adding a traverse listener
christian
June 6th, 2008 at 8:19 pm
Thanks for that Christian,
Victor
June 8th, 2008 at 8:41 pm
Sorry for bothering you again. I have tested this under Ubuntu where Firefox is used for the browser widget. Everything works fine up to the point where window.status=getHTMLText(); is executed.
Problem seems to be that window.status does not work in Firefox. For example try: window.status=”hello world”; alert(window.status);
In firefox the alert is empty.
That’s too bad, because I really need a HTMLEditor in my program. Only other way I can think of is using a local server for sending post data via xmlhttprequest. But that seems like overkill…
Do you know of any other way to get to the data inside the browser widget?
regards,
christian
June 9th, 2008 at 4:25 pm
Hey Christian,
Had a bit of a play around with the issue, and am still thinking about the problem, but here are my thoughts so far;
Firefox seems to have security feature enabled by default which renders window.status unusable by javascript so this code does fail. Simply enabling this feature isnt really good enough, because that would mean that all of your users would need to enable this security setting before running it. I have been thinking of maybe changing the event listener I am using to another one, perhaps a LocationListener. This way i just need to change the address bar location in javascript to be a spoof url like ‘http://www.example.com&htmltext=xxxx’ where the xxxx part is the entire html contents of the WYSIWYG editor. That would also fire a LocationEvent where I could grab the url and parse the HTMLtext out of it.
This should work, however, there may be restrictions on URL length that I am unaware of that may chop the HTML text prematurely. Ill let you know more as I discover more.
Cheers
VC
June 10th, 2008 at 8:16 am
yes, there are browser-dependant url length restrictions.
But there is another listener left: the titlelistener, which we can use in combination with document.title in javascript. Yesterday I have tested this option.
Good news: Now it works in firefox. Bad news: In ie longer titles are truncated. Seems like ie only gives us the string that is actually visible in the browsers title bar. Nice!
So now we have two methods that each work only in one browser. Only solution seems to be, to use a browser detection to decide which method to use (I could not test these methods in safari, so maybe we even have to use a third version for that).
It’s not nice, but it seems to be the only way. I have not come up yet with a good browser detection. It is impossible to get the class name of the browser implementation because these classes are not public. One can use SWT.MOZILLA to try to load Firefox. If this succeeds it is safe to use document.title. If it fails we can instantiate the browser with SWT.NONE and use window.status. This seems to work so far on windows and linux.
greetings,
christian
June 17th, 2008 at 9:46 pm
There exists also a project which tries to solve SWT WYSIWYG Editing with tinyMCE, see http://www.richclient2.eu/richhtml4eclipse/
December 18th, 2008 at 2:20 am
[...] SWT Browser Widget to render a TinyMCE WYSIWYG editor [...]
March 26th, 2009 at 3:34 am
The DJ Sweet project uses the same approach to offer either FCKeditor or TinyMCE.
The DJ Native Swing project uses the SWT browser with all the bridging required to have these WYSIWYG HTML editors for Swing.