Java tutorial
/** * VCKEditorTextField.java (CKEditor) * * Copyright 2017 Vaadin Ltd, Sami Viitanen <sami.viitanen@vaadin.org> * * Based on CKEditor from Yozons, Inc, Copyright (C) 2010-2016 Yozons, Inc. * * 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.vaadin.alump.ckeditor.client; import java.util.HashMap; import java.util.LinkedList; import java.util.Set; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.LayoutManager; import com.vaadin.client.Paintable; import com.vaadin.client.UIDL; import com.vaadin.client.ui.layout.ElementResizeEvent; import com.vaadin.client.ui.layout.ElementResizeListener; import com.vaadin.shared.EventId; /** * Client side CKEditor widget which communicates with the server. Messages from the * server are shown as HTML and mouse clicks are sent to the server. */ public class VCKEditorTextField extends Widget implements Paintable, CKEditorService.CKEditorListener, Focusable { /** Set the CSS class name to allow styling. */ public static final String CLASSNAME = "v-ckeditortextfield"; public static final String ATTR_FOCUS = "focus"; public static final String ATTR_IMMEDIATE = "immediate"; public static final String ATTR_READONLY = "readonly"; public static final String ATTR_VIEW_WITHOUT_EDITOR = "viewWithoutEditor"; public static final String ATTR_INPAGECONFIG = "inPageConfig"; public static final String ATTR_PROTECTED_SOURCE = "protectedSource"; public static final String ATTR_WRITERRULES_TAGNAME = "writerRules.tagName"; public static final String ATTR_WRITERRULES_JSRULE = "writerRules.jsRule"; public static final String ATTR_WRITER_INDENTATIONCHARS = "writerIndentationChars"; public static final String ATTR_KEYSTROKES_KEYSTROKE = "keystrokes.keystroke"; public static final String ATTR_KEYSTROKES_COMMAND = "keystrokes.command"; public static final String ATTR_INSERT_HTML = "insert_html"; public static final String ATTR_INSERT_TEXT = "insert_text"; public static final String ATTR_PROTECTED_BODY = "protected_body"; public static final String VAR_TEXT = "text"; public static final String VAR_VAADIN_SAVE_BUTTON_PRESSED = "vaadinsave"; public static final String VAR_VERSION = "version"; public static final String EVENT_SELECTION_CHANGE = "selectionChange"; private static String ckeditorVersion; /** The client side widget identifier */ protected String paintableId; /** Reference to the server connection object. */ protected ApplicationConnection clientToServer; private String dataBeforeEdit = null; private boolean immediate; private boolean readOnly; private boolean viewWithoutEditor; // Set to true and the editor will not be displayed, just the contents. private boolean protectedBody; private CKEditor ckEditor = null; private boolean ckEditorIsReady = false; private boolean resizeListenerInPlace = false; private boolean notifyBlankSelection = false; private LinkedList<String> protectedSourceList = null; private HashMap<String, String> writerRules = null; private String writerIndentationChars = null; private HashMap<Integer, String> keystrokeMappings = null; private int tabIndex; private boolean setFocusAfterReady; private boolean setTabIndexAfterReady; /** * The constructor should first call super() to initialize the component and * then handle any initialization relevant to Vaadin. */ public VCKEditorTextField() { // CKEditor prefers a textarea, but found too many issues trying to use createTextareaElement() instead of a simple div, // which is okay in Vaadin where an HTML form won't be used to send the data back and forth. DivElement rootDiv = Document.get().createDivElement(); rootDiv.getStyle().setOverflow(Overflow.HIDDEN); rootDiv.getStyle().setVisibility(Visibility.VISIBLE); // required for FF to show in popup windows repeatedly setElement(rootDiv); // This method call of the Paintable interface sets the component // style name in DOM tree setStyleName(CLASSNAME); } /** * Called whenever an update is received from the server */ @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { clientToServer = client; paintableId = uidl.getId(); boolean needsDataUpdate = false; boolean needsProtectedBodyUpdate = false; boolean readOnlyModeChanged = false; // This call should be made first. // It handles sizes, captions, tooltips, etc. automatically. // If clientToServer.updateComponent returns true there have been no changes // and we do not need to update anything. if (clientToServer.updateComponent(this, uidl, true)) { return; } if (!resizeListenerInPlace) { LayoutManager.get(client).addElementResizeListener(getElement(), new ElementResizeListener() { @Override public void onElementResize(ElementResizeEvent e) { doResize(); } }); resizeListenerInPlace = true; } if (uidl.hasAttribute(ATTR_IMMEDIATE)) { immediate = uidl.getBooleanAttribute(ATTR_IMMEDIATE); } if (uidl.hasAttribute(ATTR_READONLY)) { boolean newReadOnly = uidl.getBooleanAttribute(ATTR_READONLY); readOnlyModeChanged = newReadOnly != readOnly; readOnly = newReadOnly; } if (uidl.hasAttribute(ATTR_VIEW_WITHOUT_EDITOR)) { viewWithoutEditor = uidl.getBooleanAttribute(ATTR_VIEW_WITHOUT_EDITOR); } if (uidl.hasAttribute(ATTR_PROTECTED_BODY)) { boolean state = uidl.getBooleanAttribute(ATTR_PROTECTED_BODY); if (protectedBody != state) { protectedBody = state; needsProtectedBodyUpdate = true; } } if (uidl.hasVariable(VAR_TEXT)) { String data = uidl.getStringVariable(VAR_TEXT); if (ckEditor != null) dataBeforeEdit = ckEditor.getData(); needsDataUpdate = !data.equals(dataBeforeEdit); dataBeforeEdit = data; } // Save the client side identifier (paintable id) for the widget if (!paintableId.equals(getElement().getId())) { getElement().setId(paintableId); } if (viewWithoutEditor) { if (ckEditor != null) { // may update the data and change to viewWithoutEditor at the same time if (!needsDataUpdate) { dataBeforeEdit = ckEditor.getData(); } ckEditor.destroy(true); ckEditorIsReady = false; ckEditor = null; } getElement().setInnerHTML(dataBeforeEdit); } else if (ckEditor == null) { getElement().setInnerHTML(""); // in case we put contents in there while in viewWithoutEditor mode final String inPageConfig = uidl.hasAttribute(ATTR_INPAGECONFIG) ? uidl.getStringAttribute(ATTR_INPAGECONFIG) : null; writerIndentationChars = uidl.hasAttribute(ATTR_WRITER_INDENTATIONCHARS) ? uidl.getStringAttribute(ATTR_WRITER_INDENTATIONCHARS) : null; if (uidl.hasAttribute(ATTR_FOCUS)) { setFocus(uidl.getBooleanAttribute(ATTR_FOCUS)); } // See if we have any writer rules int i = 0; while (true) { if (!uidl.hasAttribute(ATTR_WRITERRULES_TAGNAME + i)) { break; } // Save the rules until our instance is ready String tagName = uidl.getStringAttribute(ATTR_WRITERRULES_TAGNAME + i); String jsRule = uidl.getStringAttribute(ATTR_WRITERRULES_JSRULE + i); if (writerRules == null) { writerRules = new HashMap<String, String>(); } writerRules.put(tagName, jsRule); ++i; } // See if we have any keystrokes i = 0; while (true) { if (!uidl.hasAttribute(ATTR_KEYSTROKES_KEYSTROKE + i)) { break; } // Save the keystrokes until our instance is ready int keystroke = uidl.getIntAttribute(ATTR_KEYSTROKES_KEYSTROKE + i); String command = uidl.getStringAttribute(ATTR_KEYSTROKES_COMMAND + i); if (keystrokeMappings == null) { keystrokeMappings = new HashMap<Integer, String>(); } keystrokeMappings.put(keystroke, command); ++i; } // See if we have any protected source regexs i = 0; while (true) { if (!uidl.hasAttribute(ATTR_PROTECTED_SOURCE + i)) { break; } // Save the regex until our instance is ready String regex = uidl.getStringAttribute(ATTR_PROTECTED_SOURCE + i); if (protectedSourceList == null) { protectedSourceList = new LinkedList<String>(); } protectedSourceList.add(regex); ++i; } ScheduledCommand scE = new ScheduledCommand() { @Override public void execute() { ckEditor = (CKEditor) CKEditorService.loadEditor(paintableId, VCKEditorTextField.this, inPageConfig, VCKEditorTextField.super.getOffsetWidth(), VCKEditorTextField.super.getOffsetHeight()); } }; CKEditorService.loadLibrary(scE); // editor data and some options are set when the instance is ready.... } else if (ckEditorIsReady) { if (needsDataUpdate) { ckEditor.setData(dataBeforeEdit); } if (needsProtectedBodyUpdate) { ckEditor.protectBody(protectedBody); } if (uidl.hasAttribute(ATTR_INSERT_HTML)) { ckEditor.insertHtml(uidl.getStringAttribute(ATTR_INSERT_HTML)); } if (uidl.hasAttribute(ATTR_INSERT_TEXT)) { ckEditor.insertText(uidl.getStringAttribute(ATTR_INSERT_TEXT)); } if (uidl.hasAttribute(ATTR_FOCUS)) { setFocus(uidl.getBooleanAttribute(ATTR_FOCUS)); } if (readOnlyModeChanged) { ckEditor.setReadOnly(readOnly); } } } // Listener callback @Override public void onSave() { if (ckEditorIsReady && !readOnly) { // Called if the user clicks the Save button. String data = ckEditor.getData(); if (!data.equals(dataBeforeEdit)) { clientToServer.updateVariable(paintableId, VAR_TEXT, data, false); dataBeforeEdit = data; } clientToServer.updateVariable(paintableId, VAR_VAADIN_SAVE_BUTTON_PRESSED, "", false); // inform that the button was pressed too clientToServer.sendPendingVariableChanges(); // ensure anything queued up goes now on SAVE } } // Listener callback @Override public void onBlur() { if (ckEditorIsReady) { boolean sendToServer = false; if (clientToServer.hasEventListeners(this, EventId.BLUR)) { sendToServer = true; clientToServer.updateVariable(paintableId, EventId.BLUR, "", false); } // Even though CKEditor 4.2 introduced a change event, it doesn't appear to fire if the user stays in SOURCE mode, // so while we do use the change event, we still are stuck with the blur listener to detect other such changes. if (!readOnly) { String data = ckEditor.getData(); if (!data.equals(dataBeforeEdit)) { clientToServer.updateVariable(paintableId, VAR_TEXT, data, false); sendToServer = true; dataBeforeEdit = data; } } if (sendToServer) { clientToServer.sendPendingVariableChanges(); } } } // Listener callback @Override public void onFocus() { if (ckEditorIsReady) { if (clientToServer.hasEventListeners(this, EventId.FOCUS)) { clientToServer.updateVariable(paintableId, EventId.FOCUS, "", true); } } } // Listener callback @Override public void onInstanceReady() { ckEditor.instanceReady(this); if (writerRules != null) { Set<String> tagNameSet = writerRules.keySet(); for (String tagName : tagNameSet) { ckEditor.setWriterRules(tagName, writerRules.get(tagName)); } writerRules = null; // don't need them anymore } if (writerIndentationChars != null) { ckEditor.setWriterIndentationChars(writerIndentationChars); writerIndentationChars = null; } if (keystrokeMappings != null) { Set<Integer> keystrokeSet = keystrokeMappings.keySet(); for (Integer keystroke : keystrokeSet) { ckEditor.setKeystroke(keystroke, keystrokeMappings.get(keystroke)); } keystrokeMappings = null; // don't need them anymore } if (protectedSourceList != null) { for (String regex : protectedSourceList) { ckEditor.pushProtectedSource(regex); } protectedSourceList = null; } if (dataBeforeEdit != null) { ckEditor.setData(dataBeforeEdit); } ckEditorIsReady = true; if (setFocusAfterReady) { setFocus(true); } if (setTabIndexAfterReady) { setTabIndex(tabIndex); } doResize(); if (protectedBody) { ckEditor.protectBody(protectedBody); } ckEditor.setReadOnly(readOnly); ckeditorVersion = CKEditorService.version(); clientToServer.updateVariable(paintableId, VAR_VERSION, ckeditorVersion, true); } // Listener callback @Override public void onChange() { if (ckEditor != null && !readOnly) { String data = ckEditor.getData(); if (!data.equals(dataBeforeEdit)) { clientToServer.updateVariable(paintableId, VAR_TEXT, data, immediate); dataBeforeEdit = data; } } } // Listener callback @Override public void onModeChange(String mode) { if (ckEditor != null) { if (!readOnly) { String data = ckEditor.getData(); if (!data.equals(dataBeforeEdit)) { clientToServer.updateVariable(paintableId, VAR_TEXT, data, true); dataBeforeEdit = data; } } if ("wysiwyg".equals(mode)) { ckEditor.protectBody(protectedBody); } } } // Listener callback @Override public void onSelectionChange() { if (ckEditorIsReady) { if (clientToServer.hasEventListeners(this, EVENT_SELECTION_CHANGE)) { String html = ckEditor.getSelectedHtml(); if (html == null) html = ""; // We'll send an update for nothing selected (unselected) only if we've sent out an event for a prior selected event. boolean isBlankSelection = "".equals(html); if (!isBlankSelection || notifyBlankSelection) { clientToServer.updateVariable(paintableId, EVENT_SELECTION_CHANGE, html, true); notifyBlankSelection = !isBlankSelection; } } } } // Listener callback @Override public void onDataReady() { if (ckEditor != null) { ckEditor.protectBody(protectedBody); } } @Override public void setWidth(String width) { super.setWidth(width); doResize(); } @Override public void setHeight(String height) { super.setHeight(height); doResize(); } protected void doResize() { if (ckEditorIsReady) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { ckEditor.resize(VCKEditorTextField.super.getOffsetWidth(), VCKEditorTextField.super.getOffsetHeight()); } }); } } @Override protected void onUnload() { if (ckEditor != null) { ckEditor.destroy(); ckEditor = null; } ckEditorIsReady = false; } @Override public int getTabIndex() { if (ckEditorIsReady) { return ckEditor.getTabIndex(); } else { return tabIndex; } } @Override public void setTabIndex(int tabIndex) { if (ckEditorIsReady) { ckEditor.setTabIndex(tabIndex); } else { setTabIndexAfterReady = true; } this.tabIndex = tabIndex; } @Override public void setAccessKey(char arg0) { return; } @Override public void setFocus(boolean arg0) { if (arg0) { if (ckEditorIsReady) ckEditor.focus(); else setFocusAfterReady = true; } else { setFocusAfterReady = false; } } }