org.hippoecm.frontend.plugins.ckeditor.CKEditorPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.hippoecm.frontend.plugins.ckeditor.CKEditorPanel.java

Source

/*
 * Copyright 2013-2016 Hippo B.V. (http://www.onehippo.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hippoecm.frontend.plugins.ckeditor;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Application;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.HeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptUrlReferenceHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.hippoecm.frontend.CmsHeaderItem;
import org.hippoecm.frontend.util.WebApplicationHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.onehippo.cms7.ckeditor.CKEditorConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Renders an instance of CKEditor to edit the HTML in the given model.
 * Additional extensions can be added via the {@link #addExtension(CKEditorPanelExtension)} method.
 */
public class CKEditorPanel extends Panel {

    private static final String WICKET_ID_EDITOR = "editor";
    private static final ResourceReference CKEDITOR_PANEL_JS = new PackageResourceReference(CKEditorPanel.class,
            "CKEditorPanel.js") {
        @Override
        public Iterable<? extends HeaderItem> getDependencies() {
            return Collections.singleton(CmsHeaderItem.get());
        }
    };
    private static final String CKEDITOR_TIMESTAMP = WebApplicationHelper.APPLICATION_HASH;
    private static final int LOGGED_EDITOR_CONFIG_INDENT_SPACES = 2;
    private static final String CONFIG_STYLES_SET_LANGUAGE_PARAM = "{language}";

    private static final Logger log = LoggerFactory.getLogger(CKEditorPanel.class);

    private final String editorConfigJson;
    private final String editorId;
    private final IModel<String> editorModel;
    private final List<CKEditorPanelExtension> extensions;

    public CKEditorPanel(final String id, final String editorConfigJson, final IModel<String> editorModel) {
        super(id);

        this.editorConfigJson = editorConfigJson;
        this.editorModel = editorModel;

        final TextArea<String> textArea = new TextArea<>(WICKET_ID_EDITOR, editorModel);
        textArea.setOutputMarkupId(true);
        add(textArea);

        editorId = textArea.getMarkupId();

        extensions = new LinkedList<>();
    }

    /**
     * @return the ID of the editor instance.
     */
    public String getEditorId() {
        return editorId;
    }

    /**
     * @return the model of the editor instance.
     */
    public IModel<String> getEditorModel() {
        return editorModel;
    }

    /**
     * Adds custom server-side behavior to this panel.
     * @param extension the behavior to add.
     */
    public void addExtension(CKEditorPanelExtension extension) {
        extensions.add(extension);

        for (Behavior behavior : extension.getBehaviors()) {
            add(behavior);
        }
    }

    @Override
    public void renderHead(final IHeaderResponse response) {
        super.renderHead(response);

        response.render(JavaScriptUrlReferenceHeaderItem.forReference(getCKEditorJsReference()));
        response.render(OnDomReadyHeaderItem.forScript(getJavaScriptForCKEditorTimestamp()));
        response.render(JavaScriptUrlReferenceHeaderItem.forReference(CKEDITOR_PANEL_JS));

        JSONObject editorConfig = getConfigurationForEditor();
        renderContentsCss(response, editorConfig);
        response.render(OnDomReadyHeaderItem.forScript(getJavaScriptForEditor(editorConfig)));
    }

    public static ResourceReference getCKEditorJsReference() {
        if (Application.get().getConfigurationType().equals(RuntimeConfigurationType.DEVELOPMENT)
                && CKEditorConstants.existsOnClassPath(CKEditorConstants.CKEDITOR_SRC_JS)) {
            log.info("Using non-optimized CKEditor sources.");
            return CKEditorConstants.CKEDITOR_SRC_JS;
        }
        log.info("Using optimized CKEditor sources");
        return CKEditorConstants.CKEDITOR_OPTIMIZED_JS;
    }

    /**
     * Generates the script to set a unique cache-busting timestamp value used by CKEditor's resource loader.
     * This ensures that changes to plugins outside the minified CKEditor sources are picked up when the
     * CKEditor source itself does not change.
     */
    private String getJavaScriptForCKEditorTimestamp() {
        return "CKEDITOR.timestamp='" + CKEDITOR_TIMESTAMP + "';";
    }

    private JSONObject getConfigurationForEditor() {
        try {
            JSONObject editorConfig = JsonUtils.createJSONObject(editorConfigJson);

            // configure extensions
            for (CKEditorPanelExtension extension : extensions) {
                extension.addConfiguration(editorConfig);
            }

            // always use the language of the current CMS locale
            final Locale locale = getLocale();
            editorConfig.put(CKEditorConstants.CONFIG_LANGUAGE, locale.getLanguage());

            // convert Hippo-specific 'declarative' keystrokes to numeric ones
            final JSONArray declarativeAndNumericKeystrokes = editorConfig
                    .optJSONArray(CKEditorConstants.CONFIG_KEYSTROKES);
            final JSONArray numericKeystrokes = DeclarativeKeystrokesConverter
                    .convertToNumericKeystrokes(declarativeAndNumericKeystrokes);
            editorConfig.putOpt(CKEditorConstants.CONFIG_KEYSTROKES, numericKeystrokes);

            // load the localized hippo styles if no other styles are specified
            String stylesSet = editorConfig.optString(CKEditorConstants.CONFIG_STYLES_SET,
                    HippoStyles.getConfigStyleSet(locale));
            stylesSet = stylesSet.replace(CONFIG_STYLES_SET_LANGUAGE_PARAM, locale.getLanguage());
            editorConfig.put(CKEditorConstants.CONFIG_STYLES_SET, stylesSet);

            // disable custom config loading if not configured
            JsonUtils.putIfAbsent(editorConfig, CKEditorConstants.CONFIG_CUSTOM_CONFIG, StringUtils.EMPTY);

            if (log.isInfoEnabled()) {
                log.info("CKEditor configuration:\n" + editorConfig.toString(LOGGED_EDITOR_CONFIG_INDENT_SPACES));
            }

            return editorConfig;
        } catch (JSONException e) {
            throw new IllegalStateException("Error creating CKEditor configuration.", e);
        }
    }

    static void renderContentsCss(IHeaderResponse response, JSONObject editorConfig) {
        final JSONArray array = editorConfig.optJSONArray(CKEditorConstants.CONFIG_CONTENTS_CSS);
        if (array != null) {
            for (int i = 0; i < array.length(); i++) {
                final String file = array.optString(i);
                response.render(CssHeaderItem.forUrl(file));
            }
        } else {
            final String file = editorConfig.optString(CKEditorConstants.CONFIG_CONTENTS_CSS);
            response.render(CssHeaderItem.forUrl(file));
        }
    }

    private String getJavaScriptForEditor(JSONObject editorConfig) {
        return "Hippo.createCKEditor('" + editorId + "', " + editorConfig.toString() + ");";
    }

    @Override
    protected void onDetach() {
        extensions.forEach(CKEditorPanelExtension::detach);
        super.onDetach();
    }

}