Head of R&D and Java Architect in ConSol MENA. Specialized in web technologies with the six-year experience in commercial projects. Michal has posted 2 posts at DZone. You can read more from them at their website. View Full User Profile

Image Upload Using TinyMCE Within Wicket Framework

04.02.2010
| 11250 views |
  • submit to reddit

The goal of this article is to show how to upload images using tinymce within wicket framework. My intension was to show how to do this using only wicket's without additional servlets, tinymce html dialogs, etc. Everything from the begining to the end is done with witckets and little of java script which tiny needs.  I assume that people who will read this article have knowledge about wicket framework and have integrated tinymce from wicketstuff-core with their project. This functionality can be divided into three parts: creating a tinymce plugin, creating wicket dialog and inserting newly created image into tiny editor.

Creating a plugin:

To do this we need to create a java script file and corresponding plugin java file. Java script file must be named editor_plugin_src.js and must be placed in resources in package which name starts with wicket.contrib.tinymce.tiny_mce.plugins . So in our case I created in src/main/resources a package wicket.contrib.tinymce.tiny_mce.plugins.imageupload and put there a java script which looks like this:

(function() {
tinymce.create('tinymce.plugins.ImageUpload', {
init : function(ed, url) {
var t = this;
t.editor = ed;
// Register command
ed.addCommand('mceImageUpload', t._showDialog, t);
// Register button
ed.addButton('upload', {title : 'Upload image', cmd : 'mceImageUpload'});
},
_showDialog : function() {
var ed = this.editor;
// TODO
}
});
// Register plugin
tinymce.PluginManager.add('imageupload', tinymce.plugins.ImageUpload);
})();

It's a standard look of almost all plugins. Thing we are most interested in is a _showDialog function which will execute callback for opening wicket dialog. But before we will define the callback we must create a Java plugin.

Java plugin:

    public class ImageUploadPlugin extends Plugin {

private PluginButton imageUploadButton;

public ImageUploadPlugin() {
super("imageupload");
imageUploadButton = new PluginButton("upload", this);
}

public PluginButton getImageUploadButton() {
return imageUploadButton;
}
}

To show our plugin in tiny we just need to add it to tiny settings.

ImageUploadPlugin imageUploadPlugin = new ImageUploadPlugin();
settings.add(imageUploadPlugin.getImageUploadButton(), TinyMCESettings.Toolbar.first, TinyMCESettings.Position.after);

Ok so we have a button placed in tiny. Now we need to create Wicket Behavior which will respond on our button click.

public class ImageUploadBehavior extends AbstractDefaultAjaxBehavior {

@Override
protected void respond(AjaxRequestTarget pTarget) {
//place show dialog logic here
}

public String getFunctionName() {
return "showImageUploadDialog";
}

@Override
public void renderHead(IHeaderResponse pResponse) {
String script = getFunctionName() + " = function () { "
+ getCallbackScript() + " }";
pResponse.renderOnDomReadyJavascript(script);
}
}

Behavior places the generated callback to itself in body of showImageUploadDialog function. This function will be rendered right after the DOM is build. Now, we have to bind functiom name to the button. We will do this by overriding definePluginSettings method in ImageUploadPlugin.

@Override
protected void definePluginSettings(StringBuffer pBuffer) {
super.definePluginSettings(pBuffer);
pBuffer.append(",\n\tuploadimage_callback: \"" + imageUploadBehavior.getFunctionName() + "\"");
}

Now the callback which bounds into our function must be executed on button click. So in editor_plugin_src.js to  _showDialog() function we are adding:

ed.execCallback('uploadimage_callback', ed);

That's it. Our button is bound with ImageUploadBehavior. Since behavior does not work without component we need to add this behavior to panel, we will do it in the next step.

Creating a dialog:

We need a wicket modal window with simple content for adding images (input type file + ajax submit button). To create a modal window we need to place it in the panel. So we are creating a panel and at once we are adding our behavior to it. I will also place here ImageUploadBehavior.class and ImageUploadPlugin.class.

public class ImageUploadPanel extends Panel  {
private ModalWindow modalWindow;

public ImageUploadPanel(String pId) {
super(pId);
setOutputMarkupId(true);
add(modalWindow = new ModalWindow("imageUploadDialog"));
modalWindow.setTitle(new ResourceModel("title.label"));
modalWindow.setInitialHeight(100);
modalWindow.setInitialWidth(300);

add(imageUploadBehavior = new ImageUploadBehavior());
}

public class ImageUploadBehavior …

public class ImageUploadPlugin …
}

with the corresponding markup:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
<body>
<wicket:panel>
<div wicket:id="imageUploadDialog"></div>
</wicket:panel>
</body>
</html>

and properties file:
title.label=Upload image

Modal window with title and initial height and width is created. Now we will create panel for uploading images which will be placed inside our modal window. We will create a form with FileUploadField and AjaxButton for submiting a form and a Feedback panel which will display error when something goes wrong.

public class ImageUploadContentPanel extends Panel {
public ImageUploadContentPanel(String pId) {
super(pId);
setOutputMarkupId(true);
Form<?> form = new Form<Void>("form");
final FeedbackPanel feedback = new FeedbackPanel("feedback");
feedback.setOutputMarkupId(true);
form.add(feedback);
final FileUploadField fileUploadField = new FileUploadField("file");
fileUploadField.setLabel(new ResourceModel("required.label"));
fileUploadField.setRequired(true);
form.add(fileUploadField);
form.add(new AjaxButton("uploadButton", form) {

@Override
protected void onSubmit(AjaxRequestTarget pTarget, Form<?> pForm) {
}
@Override
protected void onError(AjaxRequestTarget pTarget, Form<?> pForm) {
pTarget.addComponent(feedback);
}
});
add(form);
}

/**
* Method invoked after image upload.
* @param pTarget - ajax target
* @param pImageName - image name
* @param pContentType – image content type
*/
public void onImageUploaded(String pImageName, String pContentType, AjaxRequestTarget pTarget) {}
}

with the corresponding markup:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/">
<body>
<wicket:panel>
<form wicket:id="form">
<input wicket:id="file" type="file"/>
<button wicket:id="uploadButton" type="button">
<wicket:message key="upload.button.label"/>
</button>
<span class="error" wicket:id="feedback"/>
</form>
</wicket:panel>
</body>
</html>

and properties file:
upload.button.label=Upload
required.label=Image upload

We can also add a validator which will check if uploaded files are images.

public static class FileExtensionValidator implements IValidator<FileUpload>  {
private static final long serialVersionUID = -8116224338791429342L;
public static final List<String> extensions = Arrays.asList(
"jpg","gif","jpeg","png","bmp");
public void validate(IValidatable<FileUpload> pValidatable) {
FileUpload image = pValidatable.getValue();
String extension = StringUtils.getFilenameExtension(
image.getClientFileName());
if (extension!=null && !extensions.contains(
extension.toLowerCase())) {
ValidationError error = new ValidationError();
error.addMessageKey("WrongExtensionValidator");
error.setVariable("extensions", extensions.toString());
pValidatable.error(error);
}
}
}

 

property to UploadConentPanel.properties
WrongExtensionValidator=Invalid file type. Allowes file types are : ${extensions}

and we must add it to FileUploadField

fileUploadField.add(new FileExtensionValidator()); 

Now we need to add this panel as a content to our modal window and show it from behaviour. So we are implementing respond method in our behavior.

        @Override
protected void respond(AjaxRequestTarget pTarget) {
ImageUploadContentPanel content = new ImageUploadContentPanel(modalWindow.getContentId()) {
modalWindow.setContent(content);
modalWindow.show(pTarget);
}

So in summary to this section - now we have our panel showed when tiny button is clicked. Last thing we must do is to show uploaded image inside tiny editor.

Inserting image into editor:

Before placing image inside editor we must upload image into some temporary directory. To do this we must implement onSubmit method from AjaxButton which is inside our ImageUploadContentPanel.

            @Override
protected void onSubmit(AjaxRequestTarget pTarget, Form<?> pForm) {
FileUpload fileUpload = fileUploadField.getFileUpload();
String fileName = fileUpload.getClientFileName();
try {
File currentEngineerDir = new File(getTemporaryDirPath());
if(!currentEngineerDir.exists()){
currentEngineerDir.mkdir();
}
fileUpload.writeTo(new File(currentEngineerDir, fileName));
} catch (IOException ex) {
ImageUploadContentPanel.this.error("Can't upload image");
pTarget.addComponent(feedback);
return;
} finally {
fileUpload.closeStreams();
}
onImageUploaded(fileName, fileUpload.getContentType(), pTarget);
}

Path to our directory will be placed in servlet context temp dir + sessionId.

public String getTemporaryDirPath() {
ServletContext servletContext =WebApplication.get().getServletContext();
return ((File)servletContext.getAttribute("javax.servlet.context.tempdir")).getPath() +
File.separatorChar + Session.get().getId() + File.separatorChar;
}

Please remember to delete those temporary images when session is invalidated.

Here comes the most tricky part. As I said in the beginning we want to do everything with wickets so we will create img tag and generate src attribute with wicket component path. Url is generated from component instance -> urlFor method. It takes as parameter RequestListenerInterface or ResourceReference. Since our images are dynamic we will choose first solution.We will do it in our ImageUploadPanel but first we need to transfer image name and image content type from our modal content panel to ImageUploadPanel. I did it already by defining  onImageUploaded method. So we just need to override it and create there our img tag (we can also close our modal window).

ImageUploadContentPanel content = new ImageUploadContentPanel(modalWindow.getContentId()) {
                @Override
                public void onImageUploaded(String pImageName, String pContentType, AjaxRequestTarget pTarget) {
                   modalWindow.close(pTarget);
                   XmlTag xmlImageTag = createImageTag(pImageName, pContentType);
		   // TODO inject tag into editor
                }
            };

    public XmlTag createImageTag(String pImageName, String pContentType) {
        XmlTag tag = new XmlTag();
        tag.setName("img");
        tag.setType(XmlTag.OPEN_CLOSE);
        CharSequence url = ImageUploadPanel.this.urlFor(IResourceListener.INTERFACE);
        StringBuilder sb = new StringBuilder(url);
        sb.append("&").append(ImageFactory.IMAGE_FILE_NAME).append("=").append(pImageName);
        sb.append("&").append(IMAGE_CONTENT_TYPE).append("=").append(pContentType);
        tag.put("src", RequestCycle.get().getOriginalResponse().encodeURL(
                Strings.replaceAll(sb.toString(), "&", "&")));
        return tag;
    }

where:

public static final String IMAGE_FILE_NAME = "filename";
public static final String IMAGE_CONTENT_TYPE = "contentType";

Those parameters which are added to url are needed for finding correct image.

So we have url which points to our panel but if we want to listen for requests regarding resources, we must implement IResourceListener. In implementation we must invoke onResourceRequested() method on Resource which we will create. Wickets will do the rest ;)

Code:

public class ImageUploadPanel extends Panel implements IResourceListener …

public void onResourceRequested() {
final String fileName = RequestCycle.get().getRequest().getParameter(
IMAGE_FILE_NAME);
final String contentType = RequestCycle.get().getRequest().getParameter(
IMAGE_CONTENT_TYPE);
Resource resource = new Resource() {
@Override
public IResourceStream getResourceStream() {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(getTemporaryDirPath() +"/"+fileName);
} catch(FileNotFoundException ex) {
throw new RuntimeException("Problem with getting image");
}
return new FileResourceStream(contentType, inputStream);
}
};
resource.onResourceRequested();
}

where:

public class FileResourceStream extends AbstractResourceStream {

private String contentType;
private InputStream image;

public FileResourceStream(String pContentType, InputStream pImage){
super();
this.image = pImage;
this.contentType = pContentType;
}

@Override
public String getContentType() {
return contentType;
}

public InputStream getInputStream() throws ResourceStreamNotFoundException {
return image;
}

public void close() throws IOException {
image.close();
}
}

One thing that's left is injecting generated img into tiny editor. This must be done by java script. So in onImageUploaded method we add:

pTarget.appendJavascript("tinyMCE.execCommand('mceInsertContent', false, '"+xmlImageTag.toString()+"');");

And that's all.

I tested it with Wicket 1.4.7 and FireFox.

Published at DZone with permission of its author, Michal Letynski.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Basit Sen replied on Tue, 2010/05/11 - 9:56am

Hi

 

Great stuff, you have written elsewhere that the code could be founded in the tinymce wicketstuf-project.

But i could not find it anywhere under the wicketstuff project.

 Can you please inform me on where to find it.

 

Thanks in advance...

Basit

Weijie Yan replied on Sun, 2010/06/06 - 8:25am

very good .I have learn a lot

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.