/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.gwt.wysiwyg.client.plugin.submit;
import org.xwiki.gwt.dom.client.DOMUtils;
import org.xwiki.gwt.dom.client.Element;
import org.xwiki.gwt.dom.client.JavaScriptObject;
import org.xwiki.gwt.user.client.Config;
import org.xwiki.gwt.user.client.StringUtils;
import org.xwiki.gwt.user.client.ui.HiddenConfig;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;
import org.xwiki.gwt.user.client.ui.rta.cmd.Command;
import org.xwiki.gwt.user.client.ui.rta.cmd.CommandListener;
import org.xwiki.gwt.user.client.ui.rta.cmd.CommandManager;
import org.xwiki.gwt.wysiwyg.client.plugin.internal.AbstractPlugin;
import org.xwiki.gwt.wysiwyg.client.plugin.internal.StatelessUIExtension;
import org.xwiki.gwt.wysiwyg.client.plugin.submit.exec.SubmitExecutable;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
/**
* Binds a {@link RichTextArea} to a form field.
*
* @version $Id: 5f0f366c86e0dfb266225f9be22ae50f9f72a4ff $
*/
public class SubmitPlugin extends AbstractPlugin implements BlurHandler, CommandListener, ClosingHandler
{
/**
* The name attribute, used by HTML form elements to pass data to the server when the form is submitted.
*/
private static final String NAME_ATTRIBUTE = "name";
/**
* The name of the syntax configuration parameter.
*/
private static final String SYNTAX = "syntax";
/**
* Default syntax. Can be overwritten from the configuration.
*/
private static final String DEFAULT_SYNTAX = "xhtml/1.0";
/**
* The command used to store the value of the rich text area before submitting the including form.
*/
private static final Command SUBMIT = new Command("submit");
/**
* This flag tells the server that it needs to convert the editor output from HTML to the storage syntax before
* processing it.
*/
private static final String REQUIRES_HTML_CONVERSION = "RequiresHTMLConversion";
/**
* The JavaScript object that catches the submit event and calls {@link #onSubmit()}. We couldn't use a FormPanel
* because it overwrites the onsubmit property of the form element instead of registering itself as a listener.
*/
private JavaScriptObject submitHandler;
/**
* Extends the root of the editor UI. Examples of similar root extensions are the tool bar and the menu bar.
*/
private final StatelessUIExtension rootExtension = new StatelessUIExtension("root");
/**
* Additional data to be sent to the server, besides the content of the rich text area.
*/
private HiddenConfig hiddenConfig;
/**
* The HTML form that contains the rich text area.
*/
private Element form;
@Override
public void init(RichTextArea textArea, Config config)
{
super.init(textArea, config);
String hookId = getConfig().getParameter("hookId");
getTextArea().getCommandManager().registerCommand(SUBMIT, new SubmitExecutable(textArea, hookId));
if (getTextArea().getCommandManager().isSupported(SUBMIT)) {
Element hook = (Element) Document.get().getElementById(hookId);
// See if the hook is inside an HTML form.
form = (Element) DOMUtils.getInstance().getFirstAncestor(hook, "form");
// We don't use hook.hasAttribute because the name attribute appears as unspecified in IE if it has been set
// from JavaScript.
if (form != null && !StringUtils.isEmpty(hook.getAttribute(NAME_ATTRIBUTE))) {
// Put additional hidden data on the HTML form.
hiddenConfig = new HiddenConfig();
// All the parameters of this hidden configuration will be prefixed with the name of the hook.
hiddenConfig.setNameSpace(hook.getAttribute(NAME_ATTRIBUTE));
// This flag tells the server that the editor output requires HTML conversion.
if (textArea.isEnabled()) {
hiddenConfig.addFlag(REQUIRES_HTML_CONVERSION);
}
// The storage syntax for this rich text area.
hiddenConfig.setParameter(SYNTAX, config.getParameter(SYNTAX, DEFAULT_SYNTAX));
rootExtension.addFeature(SUBMIT.toString(), hiddenConfig);
getUIExtensionList().add(rootExtension);
// Listen to submit event.
hookSubmitEvent(form);
}
// Save the initial content of the rich text area, after all the plug-ins have been initialized.
// Note that we can't use the "loaded" action event because event handlers are registered after the current
// event is processed. In this case the "loaded" action event is fired after all the plug-ins have been
// initialized but still while the rich text area "load" event is handled (and the reason for this is to
// ensure the order of the action events).
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
onSubmit();
}
});
// Submit the content when the rich text area looses the focus or the user navigates away.
saveRegistration(getTextArea().addBlurHandler(this));
saveRegistration(Window.addWindowClosingHandler(this));
// Prevent the rich text area from being submitted when it is disabled.
getTextArea().getCommandManager().addCommandListener(this);
}
}
@Override
public void destroy()
{
if (rootExtension.getFeatures().length > 0) {
unhookSubmitEvent(form);
form = null;
hiddenConfig.removeFromParent();
hiddenConfig = null;
submitHandler = null;
rootExtension.clearFeatures();
}
getTextArea().getCommandManager().removeCommandListener(this);
super.destroy();
}
/**
* @return the JavaScript object that catches the submit event and calls {@link #onSubmit()}
*/
protected native JavaScriptObject getSubmitHandler()
/*-{
if (!this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::submitHandler) {
var _this = this;
this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::submitHandler = function() {
_this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::onSubmit()();
};
}
return this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::submitHandler;
}-*/;
/**
* Registers {@link #getSubmitHandler()} as a listener for submit events generated by the given HTML form element.
*
* @param form the HTML form element whose submit event should be listened
*/
protected native void hookSubmitEvent(Element form)
/*-{
var handler = this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::getSubmitHandler()();
form.addEventListener('submit', handler, false);
}-*/;
/**
* Unregisters {@link #getSubmitHandler()} as a listener for submit events generated by the given HTML form element.
*
* @param form the HTML form element whose submit event shouldn't be listened anymore
*/
protected native void unhookSubmitEvent(Element form)
/*-{
var handler = this.@org.xwiki.gwt.wysiwyg.client.plugin.submit.SubmitPlugin::getSubmitHandler()();
form.removeEventListener('submit', handler, false);
}-*/;
@Override
public void onBlur(BlurEvent event)
{
if (event.getSource() == getTextArea()) {
onSubmit();
}
}
/**
* Called when the HTML form hosting the rich text area is submitted.
*/
protected void onSubmit()
{
// Submit the content of the rich text area only if it is enabled.
if (getTextArea().isAttached() && getTextArea().isEnabled()) {
getTextArea().getCommandManager().execute(SUBMIT);
}
}
@Override
public boolean onBeforeCommand(CommandManager sender, Command command, String param)
{
// ignore
return false;
}
@Override
public void onCommand(CommandManager sender, Command command, String param)
{
if (hiddenConfig != null && sender == getTextArea().getCommandManager() && Command.ENABLE.equals(command)) {
if (getTextArea().getCommandManager().isExecuted(Command.ENABLE)) {
hiddenConfig.addFlag(REQUIRES_HTML_CONVERSION);
} else {
hiddenConfig.removeFlag(REQUIRES_HTML_CONVERSION);
}
}
}
@Override
public void onWindowClosing(ClosingEvent event)
{
// Allow the browser to cache the content of the rich text area when the user navigates away from the edit page.
onSubmit();
}
}