wicket.contrib.tinymce.TinyMceBehavior.java Source code

Java tutorial

Introduction

Here is the source code for wicket.contrib.tinymce.TinyMceBehavior.java

Source

/*
This file is part of Wicket-Contrib-TinyMce. See
<http://http://wicketstuff.org/confluence/display/STUFFWIKI/wicket-contrib-tinymce>
    
Wicket-Contrib-TinyMce 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 3 of the
License, or (at your option) any later version.
    
Wicket-Contrib-TinyMce 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 Wicket-Contrib-TinyMce.  If not, see
<http://www.gnu.org/licenses/>.
 */
package wicket.contrib.tinymce;

import java.util.Collection;
import java.util.Collections;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.resource.ResourceReference;

import wicket.contrib.tinymce.settings.TinyMCESettings;
import wicket.contrib.tinymce.settings.TinyMCESettings.Mode;

/**
 * Renders a component (textarea) as WYSIWYG editor, using TinyMce.
 */
public class TinyMceBehavior extends Behavior {
    private static final long serialVersionUID = 3L;

    private Component component;
    private TinyMCESettings settings;
    private boolean rendered = false;

    public TinyMceBehavior() {
        this(new TinyMCESettings());
    }

    public TinyMceBehavior(TinyMCESettings settings) {
        this.settings = settings;
    }

    @Override
    public void renderHead(Component c, IHeaderResponse response) {
        super.renderHead(c, response);
        if (component == null)
            throw new IllegalStateException("TinyMceBehavior is not bound to a component");

        // TinyMce javascript:
        if (mayRenderJavascriptDirect()) {
            response.render(JavaScriptHeaderItem.forReference(getTinyMCEReference()));
        } else {
            lazyLoadTinyMCEResource(response);
        }

        String renderOnDomReady = getAddTinyMceSettingsScript(Mode.exact, Collections.singletonList(component));
        response.render(wrapTinyMceSettingsScript(renderOnDomReady, component));
    }

    /**
     * <p>
     * TinyMCE javascript resource.
     * </p>
     * <p>
     * <strong>Note</strong>: The TinyMCE source cannot be lazily loaded via ajax. Therefore, adding
     * this in a {@link IHeaderContributor#renderHead(IHeaderResponse)} must be done in a component
     * that is not rendered via Ajax. If you wish to load this via Ajax, you can use the very hacky
     * workaround {@link TinyMceBehavior#lazyLoadTinyMCEResource(IHeaderResponse)}.
     * </p>
     * 
     * @return
     */
    protected ResourceReference getTinyMCEReference() {
        Application app = Application.get();
        if (RuntimeConfigurationType.DEVELOPMENT.equals(app.getConfigurationType()))
            return TinyMCESettings.REFERENCE;
        else
            return TinyMCESettings.REFERENCE_MIN;
    }

    /**
     * <p>
     * Normally, TinyMCE cannot be natively loaded lazily; you must have the 'tiny_mce.js' script
     * rendered directly to your page instead of through an Ajax loaded component. This method
     * provides a workaround similar to the one described on:
     * </p>
     * 
     * <pre>
     *   <a href="http://tinymce.moxiecode.com/forum/viewtopic.php?pid=66531#p66531">http://tinymce.moxiecode.com/forum/viewtopic.php?pid=66531#p66531</a>
     * </pre>
     * <p>
     * @param response
     */
    protected final void lazyLoadTinyMCEResource(IHeaderResponse response) {
        String url = RequestCycle.get().urlFor(getTinyMCEReference(), null).toString();
        String base = url.substring(0, url.lastIndexOf("/"));
        response.render(JavaScriptHeaderItem.forScript(
                "window.tinyMCEPreInit = {base : '" + base + "', suffix : '', query : ''};", "tinyMceHackPreload"));
        response.render(JavaScriptHeaderItem.forReference(getTinyMCEReference()));
        response.render(JavaScriptHeaderItem.forScript("window.tinymce.dom.Event.domLoaded = true;",
                "tinyMceHackPostload"));
    }

    /**
     * Wrap the initialization script for TinyMCE into a HeaderItem. In this way we can control
     * when and how the script should be executed.
     * 
     * @param settingScript
     *          the actual initialization script for TinyMCE
     * @param component
     *          the target component that must be decorated with TinyMCE 
     * @return
     *          the HeaderItem containing {@paramref settingScript}
     * 
     */
    protected HeaderItem wrapTinyMceSettingsScript(String settingScript, Component component) {
        return OnDomReadyHeaderItem.forScript(settingScript);
    }

    private boolean mayRenderJavascriptDirect() {
        return RequestCycle.get().getRequest() instanceof WebRequest
                && !((WebRequest) RequestCycle.get().getRequest()).isAjax();
    }

    protected String getAddTinyMceSettingsScript(Mode mode, Collection<Component> components) {
        StringBuffer script = new StringBuffer();
        // If this behavior is run a second time, it means we're redrawing this
        // component via an ajax call. The tinyMCE javascript does not handle
        // this scenario, so we must remove the old editor before initializing
        // it again.
        if (rendered) {
            for (Component c : components) {
                String tryToRemoveJS = "try{tinyMCE.remove(tinyMCE.get('%s'));}catch(e){}\n";
                script.append(String.format(tryToRemoveJS, c.getMarkupId()));
            }
        }

        script.append(settings.getLoadPluginJavaScript());
        script.append(" tinyMCE.init({" + settings.toJavaScript(mode, components) + " });\n");
        script.append(settings.getAdditionalPluginJavaScript());
        rendered = true;

        return script.toString();
    }

    @Override
    public void bind(Component component) {
        if (this.component != null)
            throw new IllegalStateException("TinyMceBehavior can not bind to more than one component");
        super.bind(component);
        if (isMarkupIdRequired())
            component.setOutputMarkupId(true);
        this.component = component;
    }

    protected boolean isMarkupIdRequired() {
        return true;
    }

    protected Component getComponent() {
        return component;
    }
}