]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement new RichTextArea
authorArtur Signell <artur@vaadin.com>
Thu, 1 Sep 2016 11:56:41 +0000 (14:56 +0300)
committerVaadin Code Review <review@vaadin.com>
Mon, 12 Sep 2016 08:11:33 +0000 (08:11 +0000)
Change-Id: I6f430c77caaad6d610133f340eba960f2268897e

74 files changed:
all/src/main/templates/release-notes.html
client/src/main/java/com/vaadin/client/ui/VRichTextArea.java [new file with mode: 0644]
client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java [new file with mode: 0644]
client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java [new file with mode: 0644]
client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java
client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java
client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java
client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif [new file with mode: 0644]
client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/AbstractField.java
server/src/main/java/com/vaadin/ui/AbstractTextField.java
server/src/main/java/com/vaadin/ui/HasValueChangeMode.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/PasswordField.java
server/src/main/java/com/vaadin/ui/RichTextArea.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/abstracttextfield/AbstractTextFieldDeclarativeTest.java
server/src/test/java/com/vaadin/ui/ComponentTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/ui/RichTextAreaTest.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/ValueChangeMode.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/ui/textfield/AbstractTextFieldState.java
shared/src/main/java/com/vaadin/shared/ui/textfield/ValueChangeMode.java [deleted file]
themes/src/main/themes/VAADIN/themes/base/textfield/textfield.scss
uitest/src/main/java/com/vaadin/tests/TestCaptionWrapper.java
uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java [deleted file]
uitest/src/main/java/com/vaadin/tests/components/AbstractComponentContainerTest.java
uitest/src/main/java/com/vaadin/tests/components/notification/NotificationsWaiAria.java
uitest/src/main/java/com/vaadin/tests/components/popupview/PopupViewWithRTE.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaEmptyString.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaRelativeHeightResize.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaScrolling.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaSize.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaTest.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaUpdateWhileTyping.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaWithKeyboardShortcuts.java
uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreas.java
uitest/src/main/java/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java
uitest/src/main/java/com/vaadin/tests/components/splitpanel/SplitPanelWithRichTextArea.java
uitest/src/main/java/com/vaadin/tests/components/textfield/MultipleTextChangeEvents.java
uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/components/tree/TreeScrolling.java
uitest/src/main/java/com/vaadin/tests/components/uitest/components/TextFieldsCssTest.java
uitest/src/main/java/com/vaadin/tests/components/window/WindowCloseShortcuts.java
uitest/src/main/java/com/vaadin/tests/fields/TabIndexes.java
uitest/src/main/java/com/vaadin/tests/navigator/NavigatorTest.java
uitest/src/main/java/com/vaadin/tests/themes/valo/Forms.java
uitest/src/main/java/com/vaadin/tests/themes/valo/TextFields.java
uitest/src/main/java/com/vaadin/v7/tests/components/textarea/TextAreaCursorPosition.java
uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java [deleted file]
uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java [new file with mode: 0644]

index 8e2d29d955ca04f6625c03ec41bdd22cbf6d85f0..568006e6229a45ace23b5d768a281a91f5fbe01e 100644 (file)
             <li>Old input prompts have been replaced with placeholders utilizing the related browser functionality</li>
             <li>The old liferay theme (Liferay 6.0 look) has been removed</li>
             <li>Components in the compatibility packages now use the prefix "vaadin7-" in declarative design files</li>
+            <li>RichTextArea no longer receives a special "v-richtextarea-readonly" class when readonly, only the standard "v-readonly" class</li>
         </ul>
         <h3 id="knownissues">Known Issues and Limitations</h3>
         <ul>
diff --git a/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java b/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java
new file mode 100644 (file)
index 0000000..75bec94
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.BodyElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.IFrameElement;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasEnabled;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.richtextarea.VRichTextToolbar;
+
+/**
+ * This class implements a basic client side rich text editor component.
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+public class VRichTextArea extends Composite implements Field, KeyPressHandler,
+        KeyDownHandler, Focusable, HasEnabled {
+
+    /**
+     * The input node CSS classname.
+     */
+    public static final String CLASSNAME = "v-richtextarea";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String id;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ApplicationConnection client;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean immediate = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public RichTextArea rta;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VRichTextToolbar formatter;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public HTML html = new HTML();
+
+    private final FlowPanel fp = new FlowPanel();
+
+    private boolean enabled = true;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int maxLength = -1;
+
+    private int toolbarNaturalWidth = 500;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public HandlerRegistration keyPressHandler;
+
+    private ShortcutActionHandlerOwner hasShortcutActionHandler;
+
+    private boolean readOnly = false;
+
+    private final Map<BlurHandler, HandlerRegistration> blurHandlers = new HashMap<BlurHandler, HandlerRegistration>();
+
+    private List<Command> inputHandlers = new ArrayList<>();
+
+    public VRichTextArea() {
+        createRTAComponents();
+        fp.add(formatter);
+        fp.add(rta);
+
+        initWidget(fp);
+        setStyleName(CLASSNAME);
+
+        TouchScrollDelegate.enableTouchScrolling(html, html.getElement());
+    }
+
+    private void createRTAComponents() {
+        rta = new RichTextArea();
+        rta.setWidth("100%");
+        rta.addKeyDownHandler(this);
+        rta.addInitializeHandler(e -> {
+            // Must wait until iframe is attached to be able to access body
+            BodyElement rtaBody = IFrameElement.as(rta.getElement())
+                    .getContentDocument().getBody();
+            addInputListener(rtaBody, event -> {
+                inputHandlers.forEach(handler -> handler.execute());
+            });
+        });
+
+        formatter = new VRichTextToolbar(rta);
+
+        // Add blur handlers
+        for (Entry<BlurHandler, HandlerRegistration> handler : blurHandlers
+                .entrySet()) {
+
+            // Remove old registration
+            handler.getValue().removeHandler();
+
+            // Add blur handlers
+            addBlurHandler(handler.getKey());
+        }
+    }
+
+    private native void addInputListener(Element element,
+            Consumer<NativeEvent> listener)
+    /*-{
+        element.addEventListener("input", $entry(function(event) {
+            listener.@java.util.function.Consumer::accept(Ljava/lang/Object;)(event);
+        }));
+    }-*/;
+
+    public void setMaxLength(int maxLength) {
+        if (maxLength >= 0) {
+            if (this.maxLength == -1) {
+                keyPressHandler = rta.addKeyPressHandler(this);
+            }
+            this.maxLength = maxLength;
+        } else if (this.maxLength != -1) {
+            this.maxLength = -1;
+            keyPressHandler.removeHandler();
+        }
+
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (this.enabled != enabled) {
+            // rta.setEnabled(enabled);
+            swapEditableArea();
+            this.enabled = enabled;
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Swaps html to rta and visa versa.
+     */
+    private void swapEditableArea() {
+        String value = getValue();
+        if (html.isAttached()) {
+            fp.remove(html);
+            if (BrowserInfo.get().isWebkit()) {
+                fp.remove(formatter);
+                createRTAComponents(); // recreate new RTA to bypass #5379
+                fp.add(formatter);
+            }
+            fp.add(rta);
+        } else {
+            fp.remove(rta);
+            fp.add(html);
+        }
+        setValue(value);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void selectAll() {
+        /*
+         * There is a timing issue if trying to select all immediately on first
+         * render. Simple deferred command is not enough. Using Timer with
+         * moderated timeout. If this appears to fail on many (most likely slow)
+         * environments, consider increasing the timeout.
+         *
+         * FF seems to require the most time to stabilize its RTA. On Vaadin
+         * tiergarden test machines, 200ms was not enough always (about 50%
+         * success rate) - 300 ms was 100% successful. This however was not
+         * enough on a sluggish old non-virtualized XP test machine. A bullet
+         * proof solution would be nice, GWT 2.1 might however solve these. At
+         * least setFocus has a workaround for this kind of issue.
+         */
+        new Timer() {
+            @Override
+            public void run() {
+                rta.getFormatter().selectAll();
+            }
+        }.schedule(320);
+    }
+
+    public void setReadOnly(boolean b) {
+        if (isReadOnly() != b) {
+            swapEditableArea();
+            readOnly = b;
+        }
+        // reset visibility in case enabled state changed and the formatter was
+        // recreated
+        formatter.setVisible(!readOnly);
+    }
+
+    private boolean isReadOnly() {
+        return readOnly;
+    }
+
+    @Override
+    public void setHeight(String height) {
+        super.setHeight(height);
+        if (height == null || height.equals("")) {
+            rta.setHeight("");
+        }
+    }
+
+    @Override
+    public void setWidth(String width) {
+        if (width.equals("")) {
+            /*
+             * IE cannot calculate the width of the 100% iframe correctly if
+             * there is no width specified for the parent. In this case we would
+             * use the toolbar but IE cannot calculate the width of that one
+             * correctly either in all cases. So we end up using a default width
+             * for a RichTextArea with no width definition in all browsers (for
+             * compatibility).
+             */
+
+            super.setWidth(toolbarNaturalWidth + "px");
+        } else {
+            super.setWidth(width);
+        }
+    }
+
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        if (maxLength >= 0) {
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    if (rta.getHTML().length() > maxLength) {
+                        rta.setHTML(rta.getHTML().substring(0, maxLength));
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        // delegate to closest shortcut action handler
+        // throw event from the iframe forward to the shortcuthandler
+        ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner()
+                .getShortcutActionHandler();
+        if (shortcutHandler != null) {
+            shortcutHandler.handleKeyboardEvent(
+                    com.google.gwt.user.client.Event.as(event.getNativeEvent()),
+                    ConnectorMap.get(client).getConnector(this));
+        }
+    }
+
+    private ShortcutActionHandlerOwner getShortcutHandlerOwner() {
+        if (hasShortcutActionHandler == null) {
+            Widget parent = getParent();
+            while (parent != null) {
+                if (parent instanceof ShortcutActionHandlerOwner) {
+                    break;
+                }
+                parent = parent.getParent();
+            }
+            hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent;
+        }
+        return hasShortcutActionHandler;
+    }
+
+    @Override
+    public int getTabIndex() {
+        return rta.getTabIndex();
+    }
+
+    @Override
+    public void setAccessKey(char key) {
+        rta.setAccessKey(key);
+    }
+
+    @Override
+    public void setFocus(boolean focused) {
+        /*
+         * Similar issue as with selectAll. Focusing must happen before possible
+         * selectall, so keep the timeout here lower.
+         */
+        new Timer() {
+
+            @Override
+            public void run() {
+                rta.setFocus(true);
+            }
+        }.schedule(300);
+    }
+
+    @Override
+    public void setTabIndex(int index) {
+        rta.setTabIndex(index);
+    }
+
+    /**
+     * Sets the value of the text area.
+     *
+     * @param value
+     *            The text value, as HTML
+     */
+    public void setValue(String value) {
+        if (rta.isAttached()) {
+            rta.setHTML(value);
+        } else {
+            html.setHTML(value);
+        }
+    }
+
+    /**
+     * Gets the value of the text area.
+     *
+     * @return the value as HTML
+     */
+    public String getValue() {
+        if (rta.isAttached()) {
+            return rta.getHTML();
+        } else {
+            return html.getHTML();
+        }
+    }
+
+    /**
+     * Browsers differ in what they return as the content of a visually empty
+     * rich text area. This method is used to normalize these to an empty
+     * string. See #8004.
+     *
+     * @return cleaned html string
+     */
+    public String getSanitizedValue() {
+        BrowserInfo browser = BrowserInfo.get();
+        String result = getValue();
+        if (browser.isFirefox()) {
+            if ("<br>".equals(result)) {
+                result = "";
+            }
+        } else if (browser.isWebkit() || browser.isEdge()) {
+            if ("<br>".equals(result) || "<div><br></div>".equals(result)) {
+                result = "";
+            }
+        } else if (browser.isIE()) {
+            if ("<P>&nbsp;</P>".equals(result)) {
+                result = "";
+            }
+        } else if (browser.isOpera()) {
+            if ("<br>".equals(result) || "<p><br></p>".equals(result)) {
+                result = "";
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Adds a blur handler to the component.
+     *
+     * @param blurHandler
+     *            the blur handler to add
+     */
+    public void addBlurHandler(BlurHandler blurHandler) {
+        blurHandlers.put(blurHandler, rta.addBlurHandler(blurHandler));
+    }
+
+    /**
+     * Removes a blur handler.
+     *
+     * @param blurHandler
+     *            the handler to remove
+     */
+    public void removeBlurHandler(BlurHandler blurHandler) {
+        HandlerRegistration registration = blurHandlers.remove(blurHandler);
+        if (registration != null) {
+            registration.removeHandler();
+        }
+    }
+
+    public HandlerRegistration addInputHandler(Command inputHandler) {
+        inputHandlers.add(inputHandler);
+        return () -> inputHandlers.remove(inputHandler);
+    }
+}
diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java
new file mode 100644 (file)
index 0000000..87c5b93
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.client.ui.richtextarea;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
+import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VRichTextArea;
+import com.vaadin.client.ui.textfield.ValueChangeHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaClientRpc;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaState;
+import com.vaadin.ui.RichTextArea;
+
+/**
+ * Connector for RichTextArea.
+ */
+@Connect(value = RichTextArea.class, loadStyle = LoadStyle.EAGER)
+public class RichTextAreaConnector extends AbstractFieldConnector
+        implements SimpleManagedLayout, ValueChangeHandler.Owner {
+
+    private class RichTextAreaClientRpcImpl implements RichTextAreaClientRpc {
+        @Override
+        public void selectAll() {
+            getWidget().selectAll();
+        }
+    }
+
+    private ValueChangeHandler valueChangeHandler;
+
+    @Override
+    protected void init() {
+        getWidget().addBlurHandler(new BlurHandler() {
+            @Override
+            public void onBlur(BlurEvent event) {
+                flush();
+            }
+        });
+        getWidget().addInputHandler(() -> {
+            valueChangeHandler.scheduleValueChange();
+        });
+
+        registerRpc(RichTextAreaClientRpc.class,
+                new RichTextAreaClientRpcImpl());
+        ConnectorFocusAndBlurHandler.addHandlers(this);
+
+        valueChangeHandler = new ValueChangeHandler(this);
+
+        getLayoutManager().registerDependency(this,
+                getWidget().formatter.getElement());
+    }
+
+    @OnStateChange("valueChangeMode")
+    private void updateValueChangeMode() {
+        valueChangeHandler.setValueChangeMode(getState().valueChangeMode);
+    }
+
+    @OnStateChange("valueChangeTimeout")
+    private void updateValueChangeTimeout() {
+        valueChangeHandler.setValueChangeTimeout(getState().valueChangeTimeout);
+    }
+
+    @OnStateChange("readOnly")
+    private void updateReadOnly() {
+        getWidget().setReadOnly(getState().readOnly);
+    }
+
+    @Override
+    public void onUnregister() {
+        super.onUnregister();
+        getLayoutManager().unregisterDependency(this,
+                getWidget().formatter.getElement());
+    }
+
+    @Override
+    public VRichTextArea getWidget() {
+        return (VRichTextArea) super.getWidget();
+    }
+
+    @Override
+    public void layout() {
+        if (!isUndefinedHeight()) {
+            int rootElementInnerHeight = getLayoutManager()
+                    .getInnerHeight(getWidget().getElement());
+            int formatterHeight = getLayoutManager()
+                    .getOuterHeight(getWidget().formatter.getElement());
+            int editorHeight = rootElementInnerHeight - formatterHeight;
+            if (editorHeight < 0) {
+                editorHeight = 0;
+            }
+            getWidget().rta.setHeight(editorHeight + "px");
+        }
+    }
+
+    @Override
+    public RichTextAreaState getState() {
+        return (RichTextAreaState) super.getState();
+    }
+
+    private boolean hasStateChanged(String widgetValue) {
+        return !widgetValue.equals(getState().value);
+    }
+
+    @Override
+    public void sendValueChange() {
+        String widgetValue = getWidget().getSanitizedValue();
+        if (!hasStateChanged(widgetValue)) {
+            return;
+        }
+
+        getRpcProxy(RichTextAreaServerRpc.class).setText(widgetValue);
+        getState().value = widgetValue;
+
+    }
+
+    @Override
+    public void flush() {
+        super.flush();
+        sendValueChange();
+    }
+}
diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java
new file mode 100644 (file)
index 0000000..10c1de7
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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.
+ */
+/*
+ * Copyright 2007 Google 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 com.vaadin.client.ui.richtextarea;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.ToggleButton;
+
+/**
+ * A modified version of sample toolbar for use with {@link RichTextArea}. It
+ * provides a simple UI for all rich text formatting, dynamically displayed only
+ * for the available functionality.
+ */
+public class VRichTextToolbar extends Composite {
+
+    /**
+     * This {@link ClientBundle} is used for all the button icons. Using a
+     * bundle allows all of these images to be packed into a single image, which
+     * saves a lot of HTTP requests, drastically improving startup time.
+     */
+    public interface Images extends ClientBundle {
+
+        ImageResource bold();
+
+        ImageResource createLink();
+
+        ImageResource hr();
+
+        ImageResource indent();
+
+        ImageResource insertImage();
+
+        ImageResource italic();
+
+        ImageResource justifyCenter();
+
+        ImageResource justifyLeft();
+
+        ImageResource justifyRight();
+
+        ImageResource ol();
+
+        ImageResource outdent();
+
+        ImageResource removeFormat();
+
+        ImageResource removeLink();
+
+        ImageResource strikeThrough();
+
+        ImageResource subscript();
+
+        ImageResource superscript();
+
+        ImageResource ul();
+
+        ImageResource underline();
+    }
+
+    /**
+     * This {@link Constants} interface is used to make the toolbar's strings
+     * internationalizable.
+     */
+    public interface Strings extends Constants {
+
+        String black();
+
+        String blue();
+
+        String bold();
+
+        String color();
+
+        String createLink();
+
+        String font();
+
+        String green();
+
+        String hr();
+
+        String indent();
+
+        String insertImage();
+
+        String italic();
+
+        String justifyCenter();
+
+        String justifyLeft();
+
+        String justifyRight();
+
+        String large();
+
+        String medium();
+
+        String normal();
+
+        String ol();
+
+        String outdent();
+
+        String red();
+
+        String removeFormat();
+
+        String removeLink();
+
+        String size();
+
+        String small();
+
+        String strikeThrough();
+
+        String subscript();
+
+        String superscript();
+
+        String ul();
+
+        String underline();
+
+        String white();
+
+        String xlarge();
+
+        String xsmall();
+
+        String xxlarge();
+
+        String xxsmall();
+
+        String yellow();
+    }
+
+    /**
+     * We use an inner EventHandler class to avoid exposing event methods on the
+     * RichTextToolbar itself.
+     */
+    private class EventHandler
+            implements ClickHandler, ChangeHandler, KeyUpHandler {
+
+        @Override
+        @SuppressWarnings("deprecation")
+        public void onChange(ChangeEvent event) {
+            Object sender = event.getSource();
+            if (sender == backColors) {
+                basic.setBackColor(
+                        backColors.getValue(backColors.getSelectedIndex()));
+                backColors.setSelectedIndex(0);
+            } else if (sender == foreColors) {
+                basic.setForeColor(
+                        foreColors.getValue(foreColors.getSelectedIndex()));
+                foreColors.setSelectedIndex(0);
+            } else if (sender == fonts) {
+                basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
+                fonts.setSelectedIndex(0);
+            } else if (sender == fontSizes) {
+                basic.setFontSize(
+                        fontSizesConstants[fontSizes.getSelectedIndex() - 1]);
+                fontSizes.setSelectedIndex(0);
+            }
+        }
+
+        @Override
+        @SuppressWarnings("deprecation")
+        public void onClick(ClickEvent event) {
+            Object sender = event.getSource();
+            if (sender == bold) {
+                basic.toggleBold();
+            } else if (sender == italic) {
+                basic.toggleItalic();
+            } else if (sender == underline) {
+                basic.toggleUnderline();
+            } else if (sender == subscript) {
+                basic.toggleSubscript();
+            } else if (sender == superscript) {
+                basic.toggleSuperscript();
+            } else if (sender == strikethrough) {
+                extended.toggleStrikethrough();
+            } else if (sender == indent) {
+                extended.rightIndent();
+            } else if (sender == outdent) {
+                extended.leftIndent();
+            } else if (sender == justifyLeft) {
+                basic.setJustification(RichTextArea.Justification.LEFT);
+            } else if (sender == justifyCenter) {
+                basic.setJustification(RichTextArea.Justification.CENTER);
+            } else if (sender == justifyRight) {
+                basic.setJustification(RichTextArea.Justification.RIGHT);
+            } else if (sender == insertImage) {
+                final String url = Window.prompt("Enter an image URL:",
+                        "http://");
+                if (url != null) {
+                    extended.insertImage(url);
+                }
+            } else if (sender == createLink) {
+                final String url = Window.prompt("Enter a link URL:",
+                        "http://");
+                if (url != null) {
+                    extended.createLink(url);
+                }
+            } else if (sender == removeLink) {
+                extended.removeLink();
+            } else if (sender == hr) {
+                extended.insertHorizontalRule();
+            } else if (sender == ol) {
+                extended.insertOrderedList();
+            } else if (sender == ul) {
+                extended.insertUnorderedList();
+            } else if (sender == removeFormat) {
+                extended.removeFormat();
+            } else if (sender == richText) {
+                // We use the RichTextArea's onKeyUp event to update the toolbar
+                // status. This will catch any cases where the user moves the
+                // cursur using the keyboard, or uses one of the browser's
+                // built-in keyboard shortcuts.
+                updateStatus();
+            }
+        }
+
+        @Override
+        public void onKeyUp(KeyUpEvent event) {
+            if (event.getSource() == richText) {
+                // We use the RichTextArea's onKeyUp event to update the toolbar
+                // status. This will catch any cases where the user moves the
+                // cursor using the keyboard, or uses one of the browser's
+                // built-in keyboard shortcuts.
+                updateStatus();
+            }
+        }
+    }
+
+    private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
+            RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
+            RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
+            RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
+            RichTextArea.FontSize.XX_LARGE };
+
+    private final Images images = (Images) GWT.create(Images.class);
+    private final Strings strings = (Strings) GWT.create(Strings.class);
+    private final EventHandler handler = new EventHandler();
+
+    private final RichTextArea richText;
+    @SuppressWarnings("deprecation")
+    private final RichTextArea.BasicFormatter basic;
+    @SuppressWarnings("deprecation")
+    private final RichTextArea.ExtendedFormatter extended;
+
+    private final FlowPanel outer = new FlowPanel();
+    private final FlowPanel topPanel = new FlowPanel();
+    private final FlowPanel bottomPanel = new FlowPanel();
+    private ToggleButton bold;
+    private ToggleButton italic;
+    private ToggleButton underline;
+    private ToggleButton subscript;
+    private ToggleButton superscript;
+    private ToggleButton strikethrough;
+    private PushButton indent;
+    private PushButton outdent;
+    private PushButton justifyLeft;
+    private PushButton justifyCenter;
+    private PushButton justifyRight;
+    private PushButton hr;
+    private PushButton ol;
+    private PushButton ul;
+    private PushButton insertImage;
+    private PushButton createLink;
+    private PushButton removeLink;
+    private PushButton removeFormat;
+
+    private ListBox backColors;
+    private ListBox foreColors;
+    private ListBox fonts;
+    private ListBox fontSizes;
+
+    /**
+     * Creates a new toolbar that drives the given rich text area.
+     *
+     * @param richText
+     *            the rich text area to be controlled
+     */
+    @SuppressWarnings("deprecation")
+    public VRichTextToolbar(RichTextArea richText) {
+        this.richText = richText;
+        basic = richText.getBasicFormatter();
+        extended = richText.getExtendedFormatter();
+
+        outer.add(topPanel);
+        outer.add(bottomPanel);
+        topPanel.setStyleName("gwt-RichTextToolbar-top");
+        bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
+
+        initWidget(outer);
+        setStyleName("gwt-RichTextToolbar");
+
+        if (basic != null) {
+            topPanel.add(
+                    bold = createToggleButton(images.bold(), strings.bold()));
+            topPanel.add(italic = createToggleButton(images.italic(),
+                    strings.italic()));
+            topPanel.add(underline = createToggleButton(images.underline(),
+                    strings.underline()));
+            topPanel.add(subscript = createToggleButton(images.subscript(),
+                    strings.subscript()));
+            topPanel.add(superscript = createToggleButton(images.superscript(),
+                    strings.superscript()));
+            topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
+                    strings.justifyLeft()));
+            topPanel.add(justifyCenter = createPushButton(
+                    images.justifyCenter(), strings.justifyCenter()));
+            topPanel.add(justifyRight = createPushButton(images.justifyRight(),
+                    strings.justifyRight()));
+        }
+
+        if (extended != null) {
+            topPanel.add(strikethrough = createToggleButton(
+                    images.strikeThrough(), strings.strikeThrough()));
+            topPanel.add(indent = createPushButton(images.indent(),
+                    strings.indent()));
+            topPanel.add(outdent = createPushButton(images.outdent(),
+                    strings.outdent()));
+            topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
+            topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
+            topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
+            topPanel.add(insertImage = createPushButton(images.insertImage(),
+                    strings.insertImage()));
+            topPanel.add(createLink = createPushButton(images.createLink(),
+                    strings.createLink()));
+            topPanel.add(removeLink = createPushButton(images.removeLink(),
+                    strings.removeLink()));
+            topPanel.add(removeFormat = createPushButton(images.removeFormat(),
+                    strings.removeFormat()));
+        }
+
+        if (basic != null) {
+            bottomPanel.add(backColors = createColorList("Background"));
+            bottomPanel.add(foreColors = createColorList("Foreground"));
+            bottomPanel.add(fonts = createFontList());
+            bottomPanel.add(fontSizes = createFontSizes());
+
+            // We only use these handlers for updating status, so don't hook
+            // them up unless at least basic editing is supported.
+            richText.addKeyUpHandler(handler);
+            richText.addClickHandler(handler);
+        }
+    }
+
+    private ListBox createColorList(String caption) {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(caption);
+        lb.addItem(strings.white(), "white");
+        lb.addItem(strings.black(), "black");
+        lb.addItem(strings.red(), "red");
+        lb.addItem(strings.green(), "green");
+        lb.addItem(strings.yellow(), "yellow");
+        lb.addItem(strings.blue(), "blue");
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private ListBox createFontList() {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(strings.font(), "");
+        lb.addItem(strings.normal(), "inherit");
+        lb.addItem("Times New Roman", "Times New Roman");
+        lb.addItem("Arial", "Arial");
+        lb.addItem("Courier New", "Courier New");
+        lb.addItem("Georgia", "Georgia");
+        lb.addItem("Trebuchet", "Trebuchet");
+        lb.addItem("Verdana", "Verdana");
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private ListBox createFontSizes() {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(strings.size());
+        lb.addItem(strings.xxsmall());
+        lb.addItem(strings.xsmall());
+        lb.addItem(strings.small());
+        lb.addItem(strings.medium());
+        lb.addItem(strings.large());
+        lb.addItem(strings.xlarge());
+        lb.addItem(strings.xxlarge());
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private PushButton createPushButton(ImageResource img, String tip) {
+        final PushButton pb = new PushButton(new Image(img));
+        pb.addClickHandler(handler);
+        pb.setTitle(tip);
+        pb.setTabIndex(-1);
+        return pb;
+    }
+
+    private ToggleButton createToggleButton(ImageResource img, String tip) {
+        final ToggleButton tb = new ToggleButton(new Image(img));
+        tb.addClickHandler(handler);
+        tb.setTitle(tip);
+        tb.setTabIndex(-1);
+        return tb;
+    }
+
+    /**
+     * Updates the status of all the stateful buttons.
+     */
+    @SuppressWarnings("deprecation")
+    private void updateStatus() {
+        if (basic != null) {
+            bold.setDown(basic.isBold());
+            italic.setDown(basic.isItalic());
+            underline.setDown(basic.isUnderlined());
+            subscript.setDown(basic.isSubscript());
+            superscript.setDown(basic.isSuperscript());
+        }
+
+        if (extended != null) {
+            strikethrough.setDown(extended.isStrikethrough());
+        }
+    }
+}
index 1f98b0431072b43d71263e455aab585f654b2c89..79f529ae5e8f49edbd29c29abbf5a5db205f4bb2 100644 (file)
@@ -25,7 +25,6 @@ import com.vaadin.client.ui.textfield.AbstractTextFieldConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.textarea.TextAreaServerRpc;
 import com.vaadin.shared.ui.textarea.TextAreaState;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
 import com.vaadin.ui.TextArea;
 
 @Connect(TextArea.class)
@@ -46,10 +45,9 @@ public class TextAreaConnector extends AbstractTextFieldConnector {
         super.init();
         getWidget().addChangeHandler(event -> sendValueChange());
         getWidget().addDomHandler(event -> {
-            if (getState().valueChangeMode != ValueChangeMode.BLUR) {
-                scheduleValueChange();
-            }
+            getValueChangeHandler().scheduleValueChange();
         }, InputEvent.getType());
+
         getWidget().addMouseUpHandler(new ResizeMouseUpHandler());
     }
 
index bc8c82f5493602b72a745f924742b22c896dbfb3..d774ba22623bd2345d6eca149a94337284fb5a3d 100644 (file)
@@ -15,8 +15,6 @@
  */
 package com.vaadin.client.ui.textfield;
 
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.annotations.OnStateChange;
 import com.vaadin.client.ui.AbstractComponentConnector;
@@ -25,14 +23,13 @@ import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldClientRpc;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldServerRpc;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldState;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
 import com.vaadin.ui.AbstractTextField;
 
 /**
  * Connector class for AbstractTextField.
  */
 public abstract class AbstractTextFieldConnector
-        extends AbstractComponentConnector {
+        extends AbstractComponentConnector implements ValueChangeHandler.Owner {
 
     private class AbstractTextFieldClientRpcImpl
             implements AbstractTextFieldClientRpc {
@@ -62,19 +59,18 @@ public abstract class AbstractTextFieldConnector
     }
 
     private int lastSentCursorPosition = -1;
-
-    private Timer valueChangeTrigger = new Timer() {
-        @Override
-        public void run() {
-            Scheduler.get().scheduleDeferred(() -> sendValueChange());
-        }
-    };
+    private ValueChangeHandler valueChangeHandler;
 
     @Override
     protected void init() {
         registerRpc(AbstractTextFieldClientRpc.class,
                 new AbstractTextFieldClientRpcImpl());
         ConnectorFocusAndBlurHandler.addHandlers(this);
+        valueChangeHandler = new ValueChangeHandler(this);
+    }
+
+    protected ValueChangeHandler getValueChangeHandler() {
+        return valueChangeHandler;
     }
 
     /**
@@ -88,50 +84,19 @@ public abstract class AbstractTextFieldConnector
         return (AbstractTextFieldWidget) getWidget();
     }
 
-    /**
-     * Called whenever a change in the value has been detected. Schedules a
-     * value change to be sent to the server, depending on the current value
-     * change mode.
-     * <p>
-     * Note that this method does not consider the {@link ValueChangeMode#BLUR}
-     * mode but assumes that {@link #sendValueChange()} is called directly for
-     * this mode.
-     */
-    protected void scheduleValueChange() {
-        switch (getState().valueChangeMode) {
-        case LAZY:
-            lazyTextChange();
-            break;
-        case TIMEOUT:
-            timeoutTextChange();
-            break;
-        case EAGER:
-            eagerTextChange();
-            break;
-        case BLUR:
-            // Nothing to schedule for this mode
-            break;
-        }
-    }
-
-    private void lazyTextChange() {
-        valueChangeTrigger.schedule(getState().valueChangeTimeout);
-    }
-
-    private void timeoutTextChange() {
-        if (valueChangeTrigger.isRunning()) {
-            return;
-        }
-        valueChangeTrigger.schedule(getState().valueChangeTimeout);
+    @Override
+    public AbstractTextFieldState getState() {
+        return (AbstractTextFieldState) super.getState();
     }
 
-    private void eagerTextChange() {
-        valueChangeTrigger.run();
+    @OnStateChange("valueChangeMode")
+    private void updateValueChangeMode() {
+        valueChangeHandler.setValueChangeMode(getState().valueChangeMode);
     }
 
-    @Override
-    public AbstractTextFieldState getState() {
-        return (AbstractTextFieldState) super.getState();
+    @OnStateChange("valueChangeTimeout")
+    private void updateValueChangeTimeout() {
+        valueChangeHandler.setValueChangeTimeout(getState().valueChangeTimeout);
     }
 
     @OnStateChange("readOnly")
@@ -151,10 +116,12 @@ public abstract class AbstractTextFieldConnector
      * Sends the updated value and cursor position to the server, if either one
      * has changed.
      */
-    protected void sendValueChange() {
+    @Override
+    public void sendValueChange() {
         if (!hasStateChanged()) {
             return;
         }
+
         lastSentCursorPosition = getAbstractTextField().getCursorPos();
         getRpcProxy(AbstractTextFieldServerRpc.class).setText(
                 getAbstractTextField().getValue(), lastSentCursorPosition);
index 0a937de3496a7992ddf4ab9334452a5e846d61f3..84e8e7401b3c3444896fcf360654a13ff97aa1df 100644 (file)
@@ -20,7 +20,6 @@ import com.vaadin.client.ui.VTextField;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.textfield.TextFieldState;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
 import com.vaadin.ui.TextField;
 
 /**
@@ -34,9 +33,7 @@ public class TextFieldConnector extends AbstractTextFieldConnector {
         super.init();
         getWidget().addChangeHandler(event -> sendValueChange());
         getWidget().addDomHandler(event -> {
-            if (getState().valueChangeMode != ValueChangeMode.BLUR) {
-                scheduleValueChange();
-            }
+            getValueChangeHandler().scheduleValueChange();
         }, InputEvent.getType());
     }
 
diff --git a/client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java b/client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java
new file mode 100644 (file)
index 0000000..89cca23
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.client.ui.textfield;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.user.client.Timer;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.shared.ui.ValueChangeMode;
+
+/**
+ * Helper for dealing with scheduling value change events based on a given mode
+ * and possibly timeout.
+ */
+public class ValueChangeHandler {
+
+    /**
+     * Must be implemented by any user of a ValueChangeHandler.
+     */
+    public interface Owner extends ComponentConnector {
+        /**
+         * Sends the current value to the server, if it has changed.
+         */
+        void sendValueChange();
+    }
+
+    private Owner owner;
+
+    private Timer valueChangeTrigger = new Timer() {
+        @Override
+        public void run() {
+            Scheduler.get().scheduleDeferred(() -> owner.sendValueChange());
+        }
+    };
+
+    private int valueChangeTimeout = -1;
+
+    private ValueChangeMode valueChangeMode;
+
+    /**
+     * Creates a value change handler for the given owner.
+     *
+     * @param owner
+     *            the owner connector
+     */
+    public ValueChangeHandler(Owner owner) {
+        this.owner = owner;
+    }
+
+    /**
+     * Called whenever a change in the value has been detected. Schedules a
+     * value change to be sent to the server, depending on the current value
+     * change mode.
+     * <p>
+     * Note that this method does not consider the {@link ValueChangeMode#BLUR}
+     * mode but assumes that {@link #sendValueChange()} is called directly for
+     * this mode.
+     */
+    public void scheduleValueChange() {
+        switch (valueChangeMode) {
+        case LAZY:
+            lazyTextChange();
+            break;
+        case TIMEOUT:
+            timeoutTextChange();
+            break;
+        case EAGER:
+            eagerTextChange();
+            break;
+        case BLUR:
+            // Nothing to schedule for this mode
+            break;
+        default:
+            throw new IllegalStateException("Unknown mode: " + valueChangeMode);
+        }
+    }
+
+    private void lazyTextChange() {
+        valueChangeTrigger.schedule(valueChangeTimeout);
+    }
+
+    private void timeoutTextChange() {
+        if (valueChangeTrigger.isRunning()) {
+            return;
+        }
+        valueChangeTrigger.schedule(valueChangeTimeout);
+    }
+
+    private void eagerTextChange() {
+        valueChangeTrigger.run();
+    }
+
+    /**
+     * Sets the value change mode to use.
+     *
+     * @see ValueChangeMode
+     *
+     * @param valueChangeMode
+     *            the value change mode to use
+     */
+    public void setValueChangeMode(ValueChangeMode valueChangeMode) {
+        this.valueChangeMode = valueChangeMode;
+    }
+
+    /**
+     * Sets the value change timeout to use.
+     *
+     * @see ValueChangeMode
+     *
+     * @param valueChangeTimeout
+     *            the value change timeout
+     */
+    public void setValueChangeTimeout(int valueChangeTimeout) {
+        this.valueChangeTimeout = valueChangeTimeout;
+    }
+
+}
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties b/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties
new file mode 100644 (file)
index 0000000..363b704
--- /dev/null
@@ -0,0 +1,35 @@
+bold = Toggle Bold
+createLink = Create Link
+hr = Insert Horizontal Rule
+indent = Indent Right
+insertImage = Insert Image
+italic = Toggle Italic
+justifyCenter = Center
+justifyLeft = Left Justify
+justifyRight = Right Justify
+ol = Insert Ordered List
+outdent = Indent Left
+removeFormat = Remove Formatting
+removeLink = Remove Link
+strikeThrough = Toggle Strikethrough
+subscript = Toggle Subscript
+superscript = Toggle Superscript
+ul = Insert Unordered List
+underline = Toggle Underline
+color = Color
+black = Black
+white = White
+red = Red
+green = Green
+yellow = Yellow
+blue = Blue
+font = Font
+normal = Normal
+size = Size
+xxsmall = XX-Small
+xsmall = X-Small
+small = Small
+medium = Medium
+large = Large
+xlarge = X-Large
+xxlarge = XX-Large
\ No newline at end of file
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif
new file mode 100644 (file)
index 0000000..ddfc1ce
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif
new file mode 100644 (file)
index 0000000..7c22eaa
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif
new file mode 100644 (file)
index 0000000..1a1412f
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif
new file mode 100644 (file)
index 0000000..c2f4c8c
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif
new file mode 100644 (file)
index 0000000..1629cab
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif
new file mode 100644 (file)
index 0000000..2bb89ef
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png
new file mode 100644 (file)
index 0000000..8072818
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif
new file mode 100644 (file)
index 0000000..d507082
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif
new file mode 100644 (file)
index 0000000..905421e
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif
new file mode 100644 (file)
index 0000000..394ec43
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif
new file mode 100644 (file)
index 0000000..ffe0e97
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif
new file mode 100644 (file)
index 0000000..f7d4c46
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif
new file mode 100644 (file)
index 0000000..bc37a3e
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif
new file mode 100644 (file)
index 0000000..892d569
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif
new file mode 100644 (file)
index 0000000..54f8e4f
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif
new file mode 100644 (file)
index 0000000..78fd1b5
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif
new file mode 100644 (file)
index 0000000..cf92c97
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif
new file mode 100644 (file)
index 0000000..40721a7
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif
new file mode 100644 (file)
index 0000000..a7a233c
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif
new file mode 100644 (file)
index 0000000..58b6fbb
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif
new file mode 100644 (file)
index 0000000..a6270f6
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif
new file mode 100644 (file)
index 0000000..83f1562
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif differ
diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif
new file mode 100644 (file)
index 0000000..06f0200
Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif differ
index f64cec9e66180a57857ff13ea4f4700b55d7ad8d..4946d3c95b9cec342d1d3d101e2fefb938471e73 100644 (file)
@@ -142,21 +142,26 @@ public abstract class AbstractField<T> extends AbstractComponent
      *
      * @param value
      *            the new value to set
-     * @return {@code true} if this event originates from the client,
-     *         {@code false} otherwise.
+     * @param userOriginated
+     *            {@code true} if this event originates from the client,
+     *            {@code false} otherwise.
+     * @return <code>true</code> if the value was updated, <code>false</code>
+     *         otherwise
      */
-    protected void setValue(T value, boolean userOriginated) {
+    protected boolean setValue(T value, boolean userOriginated) {
         if (userOriginated && isReadOnly()) {
-            return;
+            return false;
         }
         if (Objects.equals(value, getValue())) {
-            return;
+            return false;
         }
         doSetValue(value);
         if (!userOriginated) {
             markAsDirty();
         }
         fireEvent(createValueChange(userOriginated));
+
+        return true;
     }
 
     /**
index e1b0273c6c7035ab4640e2e437ec4f4c076583e7..b5119ec92c23306b60552423dfd41ade8a938aab 100644 (file)
@@ -27,10 +27,10 @@ import com.vaadin.event.FieldEvents.FocusEvent;
 import com.vaadin.event.FieldEvents.FocusListener;
 import com.vaadin.shared.Registration;
 import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldClientRpc;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldServerRpc;
 import com.vaadin.shared.ui.textfield.AbstractTextFieldState;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
 import com.vaadin.ui.declarative.DesignAttributeHandler;
 import com.vaadin.ui.declarative.DesignContext;
 
@@ -40,7 +40,8 @@ import com.vaadin.ui.declarative.DesignContext;
  * @author Vaadin Ltd.
  * @since 8.0
  */
-public abstract class AbstractTextField extends AbstractField<String> {
+public abstract class AbstractTextField extends AbstractField<String>
+        implements HasValueChangeMode {
 
     private final class AbstractTextFieldServerRpcImpl
             implements AbstractTextFieldServerRpc {
@@ -173,6 +174,7 @@ public abstract class AbstractTextField extends AbstractField<String> {
     /**
      * Returns the last known cursor position of the field.
      *
+     * @return the last known cursor position
      */
     public int getCursorPosition() {
         return lastKnownCursorPosition;
@@ -212,41 +214,17 @@ public abstract class AbstractTextField extends AbstractField<String> {
                 listener);
     }
 
-    /**
-     * Sets the mode how the TextField triggers {@link ValueChange}s.
-     *
-     * @param mode
-     *            the new mode
-     *
-     * @see ValueChangeMode
-     */
+    @Override
     public void setValueChangeMode(ValueChangeMode mode) {
         getState().valueChangeMode = mode;
     }
 
-    /**
-     * Returns the currently set {@link ValueChangeMode}.
-     *
-     * @return the mode used to trigger {@link ValueChange}s.
-     *
-     * @see ValueChangeMode
-     */
+    @Override
     public ValueChangeMode getValueChangeMode() {
         return getState(false).valueChangeMode;
     }
 
-    /**
-     * Sets how often {@link ValueChange}s are triggered when the
-     * {@link ValueChangeMode} is set to either {@link ValueChangeMode#LAZY} or
-     * {@link ValueChangeMode#TIMEOUT}.
-     *
-     * @param timeout
-     *            timeout in milliseconds, must be greater or equal to 0
-     * @throws IllegalArgumentException
-     *             if given timeout is smaller than 0
-     *
-     * @see ValueChangeMode
-     */
+    @Override
     public void setValueChangeTimeout(int timeout) {
         if (timeout < 0) {
             throw new IllegalArgumentException(
@@ -255,15 +233,7 @@ public abstract class AbstractTextField extends AbstractField<String> {
         getState().valueChangeTimeout = timeout;
     }
 
-    /**
-     * Returns the currently set timeout, in milliseconds, for how often
-     * {@link ValueChange}s are triggered if the current {@link ValueChangeMode}
-     * is set to either {@link ValueChangeMode#LAZY} or
-     * {@link ValueChangeMode#TIMEOUT}.
-     *
-     * @return the timeout in milliseconds of how often {@link ValueChange}s are
-     *         triggered.
-     */
+    @Override
     public int getValueChangeTimeout() {
         return getState(false).valueChangeTimeout;
     }
@@ -303,7 +273,8 @@ public abstract class AbstractTextField extends AbstractField<String> {
     /**
      * Checks if the field is empty.
      *
-     * @return true if the field value is an empty string, false otherwise
+     * @return <code>true</code> if the field value is an empty string,
+     *         <code>false</code> otherwise
      */
     public boolean isEmpty() {
         return "".equals(getValue());
diff --git a/server/src/main/java/com/vaadin/ui/HasValueChangeMode.java b/server/src/main/java/com/vaadin/ui/HasValueChangeMode.java
new file mode 100644 (file)
index 0000000..0d48a29
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.ui;
+
+import com.vaadin.data.HasValue.ValueChange;
+import com.vaadin.shared.ui.ValueChangeMode;
+
+/**
+ * Implemented by components which support value change modes.
+ */
+public interface HasValueChangeMode extends Component {
+    /**
+     * Sets the mode how the TextField triggers {@link ValueChange}s.
+     *
+     * @param valueChangeMode
+     *            the new mode
+     *
+     * @see ValueChangeMode
+     */
+    public void setValueChangeMode(ValueChangeMode valueChangeMode);
+
+    /**
+     * Returns the currently set {@link ValueChangeMode}.
+     *
+     * @return the mode used to trigger {@link ValueChange}s.
+     *
+     * @see ValueChangeMode
+     */
+    public ValueChangeMode getValueChangeMode();
+
+    /**
+     * Sets how often {@link ValueChange}s are triggered when the
+     * {@link ValueChangeMode} is set to either {@link ValueChangeMode#LAZY} or
+     * {@link ValueChangeMode#TIMEOUT}.
+     *
+     * @param valueChangeTimeout
+     *            timeout in milliseconds, must be greater or equal to 0
+     * @throws IllegalArgumentException
+     *             if given timeout is smaller than 0
+     *
+     * @see ValueChangeMode
+     */
+    public void setValueChangeTimeout(int valueChangeTimeout);
+
+    /**
+     * Returns the currently set timeout, in milliseconds, for how often
+     * {@link ValueChange}s are triggered if the current {@link ValueChangeMode}
+     * is set to either {@link ValueChangeMode#LAZY} or
+     * {@link ValueChangeMode#TIMEOUT}.
+     *
+     * @return the timeout in milliseconds of how often {@link ValueChange}s are
+     *         triggered.
+     */
+    public int getValueChangeTimeout();
+
+}
index a3fb4d265a4f4a311989dad2d0ce453a46119676..231236c01fdabef8da2ff1e1678eddd4720c5914 100644 (file)
@@ -59,12 +59,6 @@ public class PasswordField extends TextField {
         setCaption(caption);
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see com.vaadin.ui.AbstractField#readDesign(org.jsoup.nodes.Element ,
-     * com.vaadin.ui.declarative.DesignContext)
-     */
     @Override
     public void readDesign(Element design, DesignContext designContext) {
         super.readDesign(design, designContext);
@@ -75,12 +69,6 @@ public class PasswordField extends TextField {
         }
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see com.vaadin.ui.AbstractTextField#writeDesign(org.jsoup.nodes.Element
-     * , com.vaadin.ui.declarative.DesignContext)
-     */
     @Override
     public void writeDesign(Element design, DesignContext designContext) {
         super.writeDesign(design, designContext);
diff --git a/server/src/main/java/com/vaadin/ui/RichTextArea.java b/server/src/main/java/com/vaadin/ui/RichTextArea.java
new file mode 100644 (file)
index 0000000..4934692
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.ui;
+
+import org.jsoup.nodes.Element;
+
+import com.vaadin.shared.ui.ValueChangeMode;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaClientRpc;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaState;
+import com.vaadin.ui.declarative.DesignContext;
+
+/**
+ * A simple RichTextArea to edit HTML format text.
+ */
+public class RichTextArea extends AbstractField<String>
+        implements HasValueChangeMode {
+
+    private class RichTextAreaServerRpcImpl implements RichTextAreaServerRpc {
+        @Override
+        public void setText(String text) {
+            getUI().getConnectorTracker().getDiffState(RichTextArea.this)
+                    .put("value", text);
+            if (!setValue(text, true)) {
+                // The value was not updated, this could happen if the field has
+                // been set to readonly on the server and the client does not
+                // know about it yet. Must re-send the correct state back.
+                markAsDirty();
+            }
+        }
+    }
+
+    /**
+     * Constructs an empty <code>RichTextArea</code> with no caption.
+     */
+    public RichTextArea() {
+        super();
+        registerRpc(new RichTextAreaServerRpcImpl());
+        setValue("");
+    }
+
+    /**
+     * Constructs an empty <code>RichTextArea</code> with the given caption.
+     *
+     * @param caption
+     *            the caption for the editor.
+     */
+    public RichTextArea(String caption) {
+        this();
+        setCaption(caption);
+    }
+
+    /**
+     * Constructs a new <code>RichTextArea</code> with the given caption and
+     * initial text contents.
+     *
+     * @param caption
+     *            the caption for the editor.
+     * @param value
+     *            the initial text content of the editor.
+     */
+    public RichTextArea(String caption, String value) {
+        this(caption);
+        setValue(value);
+    }
+
+    @Override
+    public void readDesign(Element design, DesignContext designContext) {
+        super.readDesign(design, designContext);
+        setValue(design.html());
+    }
+
+    @Override
+    public void writeDesign(Element design, DesignContext designContext) {
+        super.writeDesign(design, designContext);
+        design.html(getValue());
+    }
+
+    @Override
+    protected RichTextAreaState getState() {
+        return (RichTextAreaState) super.getState();
+    }
+
+    @Override
+    protected RichTextAreaState getState(boolean markAsDirty) {
+        return (RichTextAreaState) super.getState(markAsDirty);
+    }
+
+    @Override
+    public void setValue(String value) {
+        if (value == null) {
+            setValue("", false);
+        } else {
+            setValue(value, false);
+        }
+    }
+
+    @Override
+    public String getValue() {
+        return getState(false).value;
+    }
+
+    @Override
+    protected void doSetValue(String value) {
+        getState().value = value;
+    }
+
+    /**
+     * Selects all text in the rich text area. As a side effect, focuses the
+     * rich text area.
+     *
+     * @since 6.5
+     */
+    public void selectAll() {
+        getRpcProxy(RichTextAreaClientRpc.class).selectAll();
+        focus();
+    }
+
+    @Override
+    public void setValueChangeMode(ValueChangeMode mode) {
+        getState().valueChangeMode = mode;
+    }
+
+    @Override
+    public ValueChangeMode getValueChangeMode() {
+        return getState(false).valueChangeMode;
+    }
+
+    @Override
+    public void setValueChangeTimeout(int timeout) {
+        getState().valueChangeTimeout = timeout;
+
+    }
+
+    @Override
+    public int getValueChangeTimeout() {
+        return getState(false).valueChangeTimeout;
+    }
+
+    /**
+     * Checks if the field is empty.
+     *
+     * @return <code>true</code> if the field value is an empty string,
+     *         <code>false</code> otherwise
+     */
+    public boolean isEmpty() {
+        return getValue().length() == 0;
+    }
+
+    /**
+     * Clears the value of this field.
+     */
+    public void clear() {
+        setValue("");
+    }
+}
index 63367363d5023a807fb84647a50149d8215e7ab9..d7fd06dbe8b62e5a12458773885e0b35567bfc02 100644 (file)
@@ -17,7 +17,7 @@ package com.vaadin.tests.server.component.abstracttextfield;
 
 import org.junit.Test;
 
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.tests.design.DeclarativeTestBase;
 import com.vaadin.ui.AbstractTextField;
 import com.vaadin.ui.TextField;
diff --git a/server/src/test/java/com/vaadin/ui/ComponentTest.java b/server/src/test/java/com/vaadin/ui/ComponentTest.java
new file mode 100644 (file)
index 0000000..8cd9afa
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.ui;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ServerRpcMethodInvocation;
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * Base class for component unit tests, providing helper methods for e.g.
+ * invoking RPC and updating diff state.
+ */
+public class ComponentTest {
+
+    /**
+     * Perform operations on the component similar to what would be done when
+     * the component state is communicated to the client, e.g. update diff state
+     * and mark as clean.
+     *
+     * @param component
+     *            the component to update
+     */
+    public static void syncToClient(AbstractComponent component) {
+        updateDiffState(component);
+        component.getUI().getConnectorTracker().markClean(component);
+    }
+
+    /**
+     * Checks if the connector has been marked dirty.
+     *
+     * @param connector
+     *            the connector to check
+     * @return <code>true</code> if the connector has been marked dirty,
+     *         <code>false</code> otherwise
+     */
+    public static boolean isDirty(ClientConnector connector) {
+        return connector.getUI().getConnectorTracker().isDirty(connector);
+    }
+
+    /**
+     * Updates the stored diff state from the current component state.
+     *
+     * @param rta
+     *            the component to update
+     */
+    public static void updateDiffState(AbstractComponent component) {
+        component.getUI().getSession().getCommunicationManager()
+                .encodeState(component, component.getState());
+
+    }
+
+    /**
+     * Gets a proxy object which invokes ServerRpc methods.
+     *
+     * @param component
+     *            the component which listens to the RPC
+     * @param serverRpcClass
+     *            the server RPC class
+     * @return a proxy which can be used to invoke RPC methods
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends ServerRpc> T getRpcProxy(Component component,
+            Class<T> serverRpcClass) {
+        return (T) Proxy.newProxyInstance(component.getClass().getClassLoader(),
+                new Class[] { serverRpcClass }, new InvocationHandler() {
+
+                    @Override
+                    public Object invoke(Object proxy, Method method,
+                            Object[] args) throws Throwable {
+                        ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
+                                component.getConnectorId(), serverRpcClass,
+                                method.getName(), args.length);
+                        invocation.setParameters(args);
+                        component.getRpcManager(serverRpcClass.getName())
+                                .applyInvocation(invocation);
+                        return null;
+                    }
+                });
+    }
+
+}
diff --git a/server/src/test/java/com/vaadin/ui/RichTextAreaTest.java b/server/src/test/java/com/vaadin/ui/RichTextAreaTest.java
new file mode 100644 (file)
index 0000000..554e002
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.ui;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ServerRpcManager.RpcInvocationException;
+import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc;
+import com.vaadin.tests.util.MockUI;
+
+public class RichTextAreaTest extends ComponentTest {
+
+    @Test
+    public void initiallyEmpty() {
+        RichTextArea tf = new RichTextArea();
+        Assert.assertTrue(tf.isEmpty());
+    }
+
+    @Test
+    public void setValueServerWhenReadOnly() {
+        RichTextArea tf = new RichTextArea();
+        tf.setReadOnly(true);
+        tf.setValue("foo");
+        Assert.assertEquals("foo", tf.getValue());
+    }
+
+    @Test
+    public void diffStateAfterClientSetValueWhenReadOnly() {
+        UI ui = new MockUI();
+
+        // If the client has a non-readonly text field which is set to read-only
+        // on the server, then any update from the client must cause both the
+        // readonly state and the old value to be sent
+        RichTextArea rta = new RichTextArea();
+        ui.setContent(rta);
+        rta.setValue("bar");
+        rta.setReadOnly(true);
+        syncToClient(rta);
+
+        // Client thinks the field says "foo" but it won't be updated because
+        // the field is readonly
+        getRpcProxy(rta, RichTextAreaServerRpc.class).setText("foo");
+
+        // The real value will be sent back as long as the field is marked as
+        // dirty and diffstate contains what the client has
+        Assert.assertEquals("foo", getDiffStateString(rta, "value"));
+        Assert.assertTrue("Component should be marked dirty", isDirty(rta));
+    }
+
+    @Test
+    public void setValueClientNotSentBack() throws RpcInvocationException {
+        UI ui = new MockUI();
+        RichTextArea rta = new RichTextArea();
+        ui.setContent(rta);
+        rta.setValue("bar");
+
+        updateDiffState(rta);
+        getRpcProxy(rta, RichTextAreaServerRpc.class).setText("foo");
+        Assert.assertEquals("foo", getDiffStateString(rta, "value"));
+    }
+
+    private String getDiffStateString(ClientConnector connector, String key) {
+        return connector.getUI().getConnectorTracker().getDiffState(connector)
+                .get(key).asString();
+    }
+
+    @Test
+    public void setValueClientRefusedWhenReadOnly() {
+        RichTextArea tf = new RichTextArea();
+        tf.setValue("bar");
+        tf.setReadOnly(true);
+        tf.setValue("foo", true);
+        Assert.assertEquals("bar", tf.getValue());
+    }
+
+    @Test
+    public void setValueNullBecomesEmptyString() {
+        RichTextArea tf = new RichTextArea();
+        tf.setValue(null);
+        Assert.assertEquals("", tf.getValue());
+    }
+
+    @Test
+    public void emptyAfterClear() {
+        RichTextArea tf = new RichTextArea();
+        tf.setValue("foobar");
+        Assert.assertFalse(tf.isEmpty());
+        tf.clear();
+        Assert.assertTrue(tf.isEmpty());
+    }
+
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/ValueChangeMode.java b/shared/src/main/java/com/vaadin/shared/ui/ValueChangeMode.java
new file mode 100644 (file)
index 0000000..00c7f2c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.shared.ui;
+
+/**
+ * Different modes for when and how often field value changes are transmitted
+ * from the client to the server.
+ */
+public enum ValueChangeMode {
+
+    /**
+     * Fires a server-side event when the field loses focus.
+     */
+    BLUR,
+
+    /**
+     * Fires a server-side event every time the client-side value changes. This
+     * gives the least latency but may cause unnecessary traffic.
+     */
+    EAGER,
+
+    /**
+     * Fires a server-side event at defined intervals as long as the value
+     * changes from one event to the next. For instance, you can use this mode
+     * to transmit a snapshot of the contents of a text area every second as
+     * long as the user keeps typing.
+     */
+    TIMEOUT,
+
+    /**
+     * On every user event, schedule a server-side event after a defined
+     * interval, cancelling the currently-scheduled event if any. This is a good
+     * choice if you want to, for instance, wait for a small break in the user's
+     * typing before sending the event.
+     */
+    LAZY
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java
new file mode 100644 (file)
index 0000000..34c7e75
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.shared.ui.richtextarea;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * Server to client RPC interface for RichTextArea.
+ */
+public interface RichTextAreaClientRpc extends ClientRpc {
+
+    /**
+     * Selects everything in the field.
+     */
+    void selectAll();
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java
new file mode 100644 (file)
index 0000000..f8d6d91
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.shared.ui.richtextarea;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * Client to server RPC interface for RichTextArea.
+ *
+ */
+public interface RichTextAreaServerRpc extends ServerRpc {
+
+    /**
+     * Sends the updated text to the server.
+     *
+     * @param text
+     *            the text in the field
+     */
+    void setText(String text);
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java
new file mode 100644 (file)
index 0000000..75ce561
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.shared.ui.richtextarea;
+
+import com.vaadin.shared.AbstractFieldState;
+import com.vaadin.shared.annotations.DelegateToWidget;
+import com.vaadin.shared.annotations.NoLayout;
+import com.vaadin.shared.ui.ValueChangeMode;
+
+/**
+ * State for RichTextArea.
+ *
+ */
+public class RichTextAreaState extends AbstractFieldState {
+    {
+        primaryStyleName = "v-richtextarea";
+    }
+
+    /**
+     * Maximum character count in text field.
+     */
+    @DelegateToWidget
+    @NoLayout
+    public int maxLength = -1;
+
+    /**
+     * The text in the field.
+     */
+    @DelegateToWidget
+    @NoLayout
+    public String value = "";
+
+    @NoLayout
+    public ValueChangeMode valueChangeMode = ValueChangeMode.LAZY;
+
+    @NoLayout
+    public int valueChangeTimeout = 400;
+
+}
index 2eac070b5e89aaf146067fe5e749d95890bc7c0c..2e7b60d463712006a6754722f16cb911e675aef5 100644 (file)
@@ -18,6 +18,7 @@ package com.vaadin.shared.ui.textfield;
 import com.vaadin.shared.AbstractFieldState;
 import com.vaadin.shared.annotations.DelegateToWidget;
 import com.vaadin.shared.annotations.NoLayout;
+import com.vaadin.shared.ui.ValueChangeMode;
 
 /**
  * State class for AbstractTextField.
diff --git a/shared/src/main/java/com/vaadin/shared/ui/textfield/ValueChangeMode.java b/shared/src/main/java/com/vaadin/shared/ui/textfield/ValueChangeMode.java
deleted file mode 100644 (file)
index 39b1bb6..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * 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 com.vaadin.shared.ui.textfield;
-
-/**
- * Different modes for when and how often field value changes are transmitted
- * from the client to the server.
- */
-public enum ValueChangeMode {
-
-    /**
-     * Fires a server-side event when the field loses focus.
-     */
-    BLUR,
-
-    /**
-     * Fires a server-side event every time the client-side value changes. This
-     * gives the least latency but may cause unnecessary traffic.
-     */
-    EAGER,
-
-    /**
-     * Fires a server-side event at defined intervals as long as the value
-     * changes from one event to the next. For instance, you can use this mode
-     * to transmit a snapshot of the contents of a text area every second as
-     * long as the user keeps typing.
-     */
-    TIMEOUT,
-
-    /**
-     * On every user event, schedule a server-side event after a defined
-     * interval, cancelling the currently-scheduled event if any. This is a good
-     * choice if you want to, for instance, wait for a small break in the user's
-     * typing before sending the event.
-     */
-    LAZY
-}
index 8eb4d18740dda68a459670f67f55fce06abb91f4..5c05228ad8ef84228b04a62926c10701c9caa170 100644 (file)
@@ -113,7 +113,7 @@ textarea.v-textarea-readonly:focus {
        margin-right: 2px;
 }
 
-.v-richtextarea-readonly {
+.v-richtextarea.v-readonly {
        border: none;
 }
 
index 43960ef70c3fc9321e19c3ba7e80cb27bfe7d2b2..4519b28a6d589dedab1c4b076b00290c3f614fde 100644 (file)
@@ -41,7 +41,7 @@ import com.vaadin.ui.Window;
 import com.vaadin.v7.ui.NativeSelect;
 import com.vaadin.v7.ui.OptionGroup;
 import com.vaadin.v7.ui.ProgressIndicator;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.Select;
 import com.vaadin.v7.ui.Table;
 import com.vaadin.v7.ui.TextField;
diff --git a/uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java b/uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java
deleted file mode 100644 (file)
index 5ad1032..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * 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 com.vaadin.tests;
-
-import com.vaadin.shared.ui.label.ContentMode;
-import com.vaadin.ui.Button;
-import com.vaadin.ui.CheckBox;
-import com.vaadin.ui.CustomComponent;
-import com.vaadin.ui.Label;
-import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.data.Property.ValueChangeEvent;
-import com.vaadin.v7.data.Property.ValueChangeListener;
-import com.vaadin.v7.ui.RichTextArea;
-
-/**
- *
- * @author Vaadin Ltd.
- */
-public class TestForRichTextEditor extends CustomComponent
-        implements ValueChangeListener {
-
-    private final VerticalLayout main = new VerticalLayout();
-
-    private Label l;
-
-    private RichTextArea rte;
-
-    public TestForRichTextEditor() {
-
-        setCompositionRoot(main);
-        createNewView();
-    }
-
-    public void createNewView() {
-        main.removeAllComponents();
-        main.addComponent(new Label(
-                "RTE uses google richtextArea and their examples toolbar."));
-
-        rte = new RichTextArea();
-        rte.addListener(this);
-
-        main.addComponent(rte);
-
-        main.addComponent(new Button("commit content to label below"));
-
-        l = new Label("", ContentMode.HTML);
-        main.addComponent(l);
-
-        CheckBox b = new CheckBox("enabled");
-        b.setImmediate(true);
-        b.addValueChangeListener(event -> rte.setEnabled(!rte.isEnabled()));
-        main.addComponent(b);
-
-    }
-
-    @Override
-    public void valueChange(ValueChangeEvent event) {
-        l.setValue(rte.getValue());
-    }
-
-}
index ef0a2731725119de23e71456c7f1c5d538312710..f385dfe84c71545dc8cf1eb700a842a2b27448c2 100644 (file)
@@ -17,7 +17,7 @@ import com.vaadin.ui.NativeButton;
 import com.vaadin.ui.PopupDateField;
 import com.vaadin.ui.TabSheet;
 import com.vaadin.ui.VerticalSplitPanel;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.Table;
 import com.vaadin.v7.ui.TextArea;
 import com.vaadin.v7.ui.TextField;
index cc0fcd36b999b4c3b131d6c9e450c818aea961e8..7eefa585b28b7b5ad178e7b00f87bede9eb7974f 100644 (file)
@@ -2,7 +2,7 @@ package com.vaadin.tests.components.notification;
 
 import com.vaadin.server.Page;
 import com.vaadin.server.VaadinRequest;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.shared.ui.ui.NotificationRole;
 import com.vaadin.tests.components.AbstractTestUI;
 import com.vaadin.ui.Button;
index 200bc94be64e969db63f4ffb9eb25c21fe532e02..8ee6f3844e9cba68e255a9868cc51197429cbd50 100644 (file)
@@ -5,7 +5,7 @@ import com.vaadin.ui.Component;
 import com.vaadin.ui.PopupView;
 import com.vaadin.ui.PopupView.Content;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class PopupViewWithRTE extends TestBase {
 
index 17a8c2ea51bb8cfa0f0f49677ac836c0d22d33ac..01dc10220f5b64ce35582ebcaf46419298e97319 100644 (file)
@@ -6,7 +6,7 @@ import com.vaadin.ui.Button;
 import com.vaadin.ui.Button.ClickEvent;
 import com.vaadin.ui.Button.ClickListener;
 import com.vaadin.ui.Label;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreaEmptyString extends TestBase {
 
index af5b3a12530ce124d07fdf96d0fcc6d9cc5d65f9..f8f2a400203e854d3231c863fc2528d3898456a3 100644 (file)
@@ -20,7 +20,7 @@ import com.vaadin.tests.components.AbstractTestUI;
 import com.vaadin.ui.Button;
 import com.vaadin.ui.Button.ClickEvent;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreaRelativeHeightResize extends AbstractTestUI {
 
index f3a16636b754083c39bdb5108cad05b0cd874f1a..2c76c0fbdb822d75f0c05780570cc5b3cb4f2131 100644 (file)
@@ -3,7 +3,7 @@ package com.vaadin.tests.components.richtextarea;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.ui.HorizontalLayout;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreaScrolling extends TestBase {
 
index 3884f1d3d1b9b8b5c73246dbb7bd859dd133af88..139b36ece85a6e9c343ffe7dfd5a4e850908a500 100644 (file)
@@ -3,7 +3,7 @@ package com.vaadin.tests.components.richtextarea;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.ui.HorizontalLayout;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreaSize extends TestBase {
 
index 376e84a1cb47fce8f17bc3c4784dc5e6f1bf0e1d..689663c84ad83a24a4440ce1fe54b04cca097a3e 100644 (file)
@@ -1,56 +1,21 @@
 package com.vaadin.tests.components.richtextarea;
 
-import java.util.LinkedHashMap;
+import com.vaadin.tests.components.abstractfield.AbstractFieldTest;
+import com.vaadin.ui.RichTextArea;
 
-import com.vaadin.tests.components.abstractfield.LegacyAbstractFieldTest;
-import com.vaadin.v7.ui.RichTextArea;
-
-public class RichTextAreaTest extends LegacyAbstractFieldTest<RichTextArea> {
+public class RichTextAreaTest extends AbstractFieldTest<RichTextArea, String> {
 
     @Override
     protected Class<RichTextArea> getTestClass() {
         return RichTextArea.class;
     }
 
-    private Command<RichTextArea, Boolean> nullSelectionAllowedCommand = new Command<RichTextArea, Boolean>() {
-
-        @Override
-        public void execute(RichTextArea c, Boolean value, Object data) {
-            c.setNullSettingAllowed(value);
-
-        }
-    };
-    private Command<RichTextArea, String> nullRepresentationCommand = new Command<RichTextArea, String>() {
-
-        @Override
-        public void execute(RichTextArea c, String value, Object data) {
-            c.setNullRepresentation(value);
-        }
-    };
-
     @Override
     protected void createActions() {
         super.createActions();
-
-        createSetTextValueAction(CATEGORY_ACTIONS);
-
-        createNullSettingAllowedAction(CATEGORY_FEATURES);
-        createNullRepresentationAction(CATEGORY_FEATURES);
-    }
-
-    private void createNullSettingAllowedAction(String category) {
-        createBooleanAction("Null selection allowed", category, true,
-                nullSelectionAllowedCommand);
-    }
-
-    private void createNullRepresentationAction(String category) {
-        LinkedHashMap<String, String> options = new LinkedHashMap<>();
-        options.put("-", null);
-        options.put("null", "null");
-        options.put("This is empty", "This is empty");
-        options.put("- Nothing -", "- Nothing -");
-        createSelectAction("Null representation", category, options, "null",
-                nullRepresentationCommand);
+        createClickAction("Select all", CATEGORY_FEATURES, (rta, a, b) -> {
+            rta.selectAll();
+        }, null);
     }
 
 }
index f23e145ec36df29dc6350c970f4d8991b1827ed4..846579e6c3edad2c0295eb1f518e79c6160cb94b 100644 (file)
@@ -4,7 +4,7 @@ import com.vaadin.server.VaadinRequest;
 import com.vaadin.tests.components.AbstractTestUI;
 import com.vaadin.v7.shared.ui.progressindicator.ProgressIndicatorServerRpc;
 import com.vaadin.v7.ui.ProgressIndicator;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreaUpdateWhileTyping extends AbstractTestUI {
 
index 481ea55267033e63417b774491a7cc46f6a3feaa..2ed52a2d95a3c89c8bcc9675b52395a544dbeb89 100644 (file)
@@ -11,7 +11,7 @@ import com.vaadin.ui.Panel;
 import com.vaadin.ui.VerticalLayout;
 import com.vaadin.ui.Window;
 import com.vaadin.v7.ui.AbstractField;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 @SuppressWarnings("serial")
 public class RichTextAreaWithKeyboardShortcuts extends TestBase {
index 37b9283eb45250a1b7d1f58eaf721b533ddbf243..8585d19bc25258d463871989aaf920e7415e6525 100644 (file)
@@ -1,7 +1,7 @@
 package com.vaadin.tests.components.richtextarea;
 
 import com.vaadin.tests.components.ComponentTestCase;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class RichTextAreas extends ComponentTestCase<RichTextArea> {
 
index 98f90553a13214f3fcb21e5102df84a2a1217273..12040900bb9cfcbe50fcc30040b11bf32f3c1694 100644 (file)
@@ -2,7 +2,7 @@ package com.vaadin.tests.components.splitpanel;
 
 import com.vaadin.annotations.Theme;
 import com.vaadin.server.VaadinRequest;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.ui.CheckBox;
 import com.vaadin.ui.GridLayout;
 import com.vaadin.ui.Label;
index 6bd2eb838e28284f3a8e86bc281b55ad4d05f069..41a14bb178fa80e7a89844c95ba4eb96c8041d2a 100644 (file)
@@ -3,7 +3,7 @@ package com.vaadin.tests.components.splitpanel;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.ui.Label;
 import com.vaadin.ui.VerticalSplitPanel;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class SplitPanelWithRichTextArea extends TestBase {
 
index 1f6eb569cc2a203129a12172d3c360033342bf46..0654c77851a787df9eef23b69fcdeea79cf75e75 100644 (file)
@@ -2,8 +2,8 @@ package com.vaadin.tests.components.textfield;
 
 import com.vaadin.event.Action;
 import com.vaadin.event.Action.Handler;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.event.ShortcutAction;
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.tests.util.Log;
 import com.vaadin.ui.TextField;
diff --git a/uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java b/uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java
new file mode 100644 (file)
index 0000000..c989d49
--- /dev/null
@@ -0,0 +1,152 @@
+package com.vaadin.tests.components.textfield;
+
+import com.vaadin.data.HasValue.ValueChange;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.ValueChangeMode;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.tests.util.TestUtils;
+import com.vaadin.ui.AbstractField;
+import com.vaadin.ui.AbstractTextField;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.HasValueChangeMode;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.RichTextArea;
+import com.vaadin.ui.TextArea;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.VerticalLayout;
+
+public class TextFieldsValueChangeMode extends AbstractTestUIWithLog {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        log.setNumberLogRows(false);
+        HorizontalLayout hl = new HorizontalLayout();
+        hl.addComponent(createFields(TextField.class));
+        hl.addComponent(createFields(TextArea.class));
+        hl.addComponent(createFields(RichTextArea.class));
+        addComponent(hl);
+    }
+
+    private Component createFields(Class<?> fieldClass) {
+        VerticalLayout vl = new VerticalLayout();
+        String id = fieldClass.getSimpleName().toLowerCase();
+        try {
+            AbstractField<String> f = (AbstractField<String>) fieldClass
+                    .newInstance();
+            f.setId(id + "-default");
+            f.setCaption(f.getId());
+            f.addValueChangeListener(this::logValueChange);
+
+            vl.addComponent(f);
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+
+        try {
+            AbstractField<String> eager = (AbstractField<String>) fieldClass
+                    .newInstance();
+            eager.setId(id + "-eager");
+            eager.setCaption(eager.getId());
+
+            eager.addValueChangeListener(this::logValueChange);
+            ((HasValueChangeMode) eager)
+                    .setValueChangeMode(ValueChangeMode.EAGER);
+            vl.addComponent(eager);
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        try {
+            AbstractField<String> timeout = (AbstractField<String>) fieldClass
+                    .newInstance();
+            timeout.setId(id + "-timeout");
+            timeout.setCaption(timeout.getId());
+            timeout.addValueChangeListener(this::logValueChange);
+            ((HasValueChangeMode) timeout)
+                    .setValueChangeMode(ValueChangeMode.TIMEOUT);
+            ((HasValueChangeMode) timeout).setValueChangeTimeout(1000);
+            vl.addComponent(timeout);
+        } catch (InstantiationException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+
+        return vl;
+    }
+
+    private void logValueChange(ValueChange<String> listener) {
+        AbstractField<String> field = (AbstractField<String>) listener
+                .getConnector();
+        String msg = "Value change event for " + field.getCaption()
+                + ", new value: '" + listener.getValue() + "'";
+        if (field instanceof AbstractTextField) {
+            msg += " Cursor at index:"
+                    + ((AbstractTextField) field).getCursorPosition();
+        }
+        log(msg);
+
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Simple TextChangeEvent test cases.";
+    }
+
+    /**
+     * "Autosuggest"
+     *
+     * Known issue is timing if suggestion comes while typing more content. IMO
+     * we will not support this kind of features in default TextField, but
+     * hopefully make it easily extendable to perfect suggest feature. MT
+     * 2010-10
+     *
+     */
+    private class VaadinDeveloperNameField extends TextField {
+        private String[] names = new String[] { "Matti Tahvonen",
+                "Marc Englund", "Joonas Lehtinen", "Jouni Koivuviita",
+                "Marko Grönroos", "Artur Signell" };
+
+        public VaadinDeveloperNameField() {
+            setCaption("Start typing 'old' Vaadin developers.");
+            addValueChangeListener(listener -> {
+                boolean atTheEndOfText = listener.getValue()
+                        .length() == getCursorPosition();
+                String match = findMatch(listener.getValue());
+                if (match != null) {
+                    setStyleName("match");
+                    String curText = listener.getValue();
+                    int matchlenght = curText.length();
+                    // autocomplete if caret is at the end of the text
+                    if (atTheEndOfText) {
+                        suggest(match, matchlenght);
+                    }
+                } else {
+                    setStyleName("nomatch");
+                }
+            });
+            setStyleName("nomatch");
+        }
+
+        @Override
+        public void attach() {
+            super.attach();
+            TestUtils.injectCSS(getUI(), ".match { background:green ;} "
+                    + ".nomatch {background:red;}");
+        }
+
+        private void suggest(String match, int matchlenght) {
+            setValue(match);
+            setSelection(matchlenght, match.length() - matchlenght);
+        }
+
+        private String findMatch(String currentTextContent) {
+            if (currentTextContent.length() > 0) {
+                for (int i = 0; i < names.length; i++) {
+                    if (names[i].startsWith(currentTextContent)) {
+                        return names[i];
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+}
index e72498e6d7395752548725fa8b163c0beba81368..7d9f48c75b94a73178dce64adde2bae932120fe6 100644 (file)
@@ -3,7 +3,7 @@ package com.vaadin.tests.components.tree;
 import com.vaadin.tests.components.AbstractTestCase;
 import com.vaadin.ui.LegacyWindow;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.TextField;
 import com.vaadin.v7.ui.Tree;
 
index a304ae33f0ebb96585b91e26cc914cf75154f7c4..284914f2646aa7d12017645a0869cd0aaf230f14 100644 (file)
@@ -7,7 +7,7 @@ import com.vaadin.ui.PasswordField;
 import com.vaadin.ui.TextField;
 import com.vaadin.ui.themes.ChameleonTheme;
 import com.vaadin.ui.themes.Reindeer;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.TextArea;
 
 public class TextFieldsCssTest extends GridLayout {
index dca7074f2e16f1a0216de1c6cd30defbb9afade1..f5ab65a65b3d1c1acf25c835e227ab127be5d9c0 100644 (file)
@@ -39,7 +39,7 @@ import com.vaadin.ui.VerticalLayout;
 import com.vaadin.ui.Window;
 import com.vaadin.ui.declarative.Design;
 import com.vaadin.ui.declarative.DesignContext;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 @Theme("valo")
 @SuppressWarnings("serial")
index 0ec27edf5ab02897a080938efb04834e4813940d..12539b84cd7c989e0c4a77788c1dd42d55aeab3b 100644 (file)
@@ -14,13 +14,13 @@ import com.vaadin.ui.GridLayout;
 import com.vaadin.ui.HorizontalLayout;
 import com.vaadin.ui.InlineDateField;
 import com.vaadin.ui.PopupDateField;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.ui.Slider;
 import com.vaadin.v7.ui.ComboBox;
 import com.vaadin.v7.ui.ListSelect;
 import com.vaadin.v7.ui.NativeSelect;
 import com.vaadin.v7.ui.OptionGroup;
 import com.vaadin.v7.ui.PasswordField;
-import com.vaadin.v7.ui.RichTextArea;
 import com.vaadin.v7.ui.Table;
 import com.vaadin.v7.ui.TextArea;
 import com.vaadin.v7.ui.TextField;
@@ -28,7 +28,6 @@ import com.vaadin.v7.ui.Tree;
 import com.vaadin.v7.ui.TreeTable;
 import com.vaadin.v7.ui.TwinColSelect;
 
-@SuppressWarnings("rawtypes")
 public class TabIndexes extends AbstractTestUIWithLog {
 
     private List<Focusable> fields;
index 276e7e90d5b371ecf1c3e9210ee2c4ad8618debf..392a3f3854ec22b12ca5f68c7c8c19bb1cf7e8a4 100644 (file)
@@ -16,7 +16,7 @@ import com.vaadin.ui.Layout;
 import com.vaadin.ui.TextField;
 import com.vaadin.ui.UI;
 import com.vaadin.ui.VerticalLayout;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.Table;
 
 public class NavigatorTest extends UI {
index e35318209f86559af9f897bdc661a3b493f607b6..c7a677de7fd0f98e39703314086132cdc088a601 100644 (file)
@@ -34,7 +34,7 @@ import com.vaadin.ui.VerticalLayout;
 import com.vaadin.ui.themes.ValoTheme;
 import com.vaadin.v7.ui.ComboBox;
 import com.vaadin.v7.ui.OptionGroup;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 import com.vaadin.v7.ui.TextArea;
 import com.vaadin.v7.ui.TextField;
 
index 4daf6f90e00b19c35ba7a17612a106cdbf181a4c..a62634512857ad96b5b954e1be3353700e5d4976 100644 (file)
@@ -28,7 +28,7 @@ import com.vaadin.ui.TextArea;
 import com.vaadin.ui.TextField;
 import com.vaadin.ui.VerticalLayout;
 import com.vaadin.ui.themes.ValoTheme;
-import com.vaadin.v7.ui.RichTextArea;
+import com.vaadin.ui.RichTextArea;
 
 public class TextFields extends VerticalLayout implements View {
     private TestIcon testIcon = new TestIcon(140);
index eb08a9494bd0ee09910a2272365a66b95f5babad..f5f0b6890bc8e5c0a13581391109701bad6de2d9 100644 (file)
@@ -1,6 +1,6 @@
 package com.vaadin.v7.tests.components.textarea;
 
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
+import com.vaadin.shared.ui.ValueChangeMode;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.ui.AbstractField;
 import com.vaadin.ui.AbstractTextField;
diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java b/uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java
deleted file mode 100644 (file)
index ec66f3b..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-package com.vaadin.v7.tests.components.textfield;
-
-import com.vaadin.shared.ui.textfield.ValueChangeMode;
-import com.vaadin.tests.components.TestBase;
-import com.vaadin.tests.util.Log;
-import com.vaadin.tests.util.TestUtils;
-import com.vaadin.ui.TextArea;
-import com.vaadin.ui.TextField;
-import com.vaadin.v7.event.FieldEvents.TextChangeEvent;
-import com.vaadin.v7.event.FieldEvents.TextChangeListener;
-
-public class TextChangeEvents extends TestBase {
-    Log l = new Log(10);
-
-    @Override
-    protected void setup() {
-
-        TextField tf = new TextField("Default");
-
-        TextChangeListener inputEventListener = new TextChangeListener() {
-
-            @Override
-            public void textChange(TextChangeEvent event) {
-                l.log("Text change event for  "
-                        + event.getComponent().getCaption()
-                        + ", text content currently:'" + event.getText()
-                        + "' Cursor at index:" + event.getCursorPosition());
-            }
-        };
-
-        tf.addValueChangeListener(listener -> {
-            l.log("Text change event for  " + tf.getCaption()
-                    + ", text content currently:'" + listener.getValue()
-                    + "' Cursor at index:" + tf.getCursorPosition());
-        });
-
-        getLayout().addComponent(tf);
-
-        TextField eager = new TextField("Eager");
-        eager.addValueChangeListener(listener -> {
-            l.log("Text change event for  " + eager.getCaption()
-                    + ", text content currently:'" + listener.getValue()
-                    + "' Cursor at index:" + eager.getCursorPosition());
-        });
-        eager.setValueChangeMode(ValueChangeMode.EAGER);
-        getLayout().addComponent(eager);
-
-        TextField to = new TextField("Timeout 3s");
-        to.addValueChangeListener(listener -> {
-            l.log("Text change event for  " + to.getCaption()
-                    + ", text content currently:'" + listener.getValue()
-                    + "' Cursor at index:" + to.getCursorPosition());
-        });
-        to.setValueChangeMode(ValueChangeMode.TIMEOUT);
-        to.setValueChangeTimeout(3000);
-        getLayout().addComponent(to);
-
-        TextArea ta = new TextArea("Default text area");
-        ta.addValueChangeListener(listener -> {
-            l.log("Text change event for  " + ta.getCaption()
-                    + ", text content currently:'" + listener.getValue()
-                    + "' Cursor at index:" + ta.getCursorPosition());
-        });
-        getLayout().addComponent(ta);
-
-        VaadinDeveloperNameField vd = new VaadinDeveloperNameField();
-        vd.addValueChangeListener(listener -> {
-            l.log("Text change event for  " + vd.getCaption()
-                    + ", text content currently:'" + listener.getValue()
-                    + "' Cursor at index:" + vd.getCursorPosition());
-        });
-        getLayout().addComponent(vd);
-
-        getLayout().addComponent(l);
-    }
-
-    @Override
-    protected String getDescription() {
-        return "Simple TextChangeEvent test cases.";
-    }
-
-    @Override
-    protected Integer getTicketNumber() {
-        return null;
-    }
-
-    /**
-     * "Autosuggest"
-     *
-     * Known issue is timing if suggestion comes while typing more content. IMO
-     * we will not support this kind of features in default TextField, but
-     * hopefully make it easily extendable to perfect suggest feature. MT
-     * 2010-10
-     *
-     */
-    private class VaadinDeveloperNameField extends TextField {
-        private String[] names = new String[] { "Matti Tahvonen",
-                "Marc Englund", "Joonas Lehtinen", "Jouni Koivuviita",
-                "Marko Grönroos", "Artur Signell" };
-
-        public VaadinDeveloperNameField() {
-            setCaption("Start typing 'old' Vaadin developers.");
-            addValueChangeListener(listener -> {
-                boolean atTheEndOfText = listener.getValue()
-                        .length() == getCursorPosition();
-                String match = findMatch(listener.getValue());
-                if (match != null) {
-                    setStyleName("match");
-                    String curText = listener.getValue();
-                    int matchlenght = curText.length();
-                    // autocomplete if caret is at the end of the text
-                    if (atTheEndOfText) {
-                        suggest(match, matchlenght);
-                    }
-                } else {
-                    setStyleName("nomatch");
-                }
-            });
-            setStyleName("nomatch");
-        }
-
-        @Override
-        public void attach() {
-            super.attach();
-            TestUtils.injectCSS(getUI(), ".match { background:green ;} "
-                    + ".nomatch {background:red;}");
-        }
-
-        private void suggest(String match, int matchlenght) {
-            setValue(match);
-            setSelection(matchlenght, match.length() - matchlenght);
-        }
-
-        private String findMatch(String currentTextContent) {
-            if (currentTextContent.length() > 0) {
-                for (int i = 0; i < names.length; i++) {
-                    if (names[i].startsWith(currentTextContent)) {
-                        return names[i];
-                    }
-                }
-            }
-            return null;
-        }
-    }
-
-}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java b/uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java
new file mode 100644 (file)
index 0000000..a9417c7
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.tests.components.textfield;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class TextFieldsValueChangeModeTest extends MultiBrowserTest {
+
+    @Test
+    public void textFieldEager() {
+        testEager("textfield-eager");
+    }
+
+    @Test
+    public void textAreaEager() {
+        testEager("textarea-eager");
+    }
+
+    @Test
+    @Ignore("No support for typing in a RichTextArea in TestBench")
+    public void richTextAreaEager() {
+        testEager("richtextarea-eager");
+    }
+
+    @Test
+    public void textFieldDefault() {
+        testDefault("textfield-default");
+    }
+
+    @Test
+    public void textAreaDefault() {
+        testDefault("textarea-default");
+    }
+
+    @Test
+    @Ignore("No support for typing in a RichTextArea in TestBench")
+    public void richTextAreaDefault() {
+        testEager("richtextarea-default");
+    }
+
+    @Test
+    public void textFieldTimeout() {
+        testTimeout("textfield-timeout");
+    }
+
+    @Test
+    public void textAreaTimeout() {
+        testTimeout("textarea-timeout");
+    }
+
+    @Test
+    @Ignore("No support for typing in a RichTextArea in TestBench")
+    public void richTextAreaTimeout() {
+        testEager("richtextarea-timeout");
+    }
+
+    private void testEager(String id) {
+        openTestURL();
+        WebElement eagerTextField = findElement(By.id(id));
+        eagerTextField.sendKeys("f");
+        eagerTextField.sendKeys("o");
+        eagerTextField.sendKeys("o");
+        assertLog(id, "f", "fo", "foo");
+    }
+
+    private void testDefault(String id) {
+        openTestURL();
+        WebElement eagerTextField = findElement(By.id(id));
+        eagerTextField.sendKeys("f");
+        eagerTextField.sendKeys("o");
+        eagerTextField.sendKeys("o");
+        sleep(400); // Default timeout is 400ms
+        assertLog(id, "foo");
+
+    }
+
+    private void testTimeout(String id) {
+        openTestURL();
+        WebElement eagerTextField = findElement(By.id(id));
+        eagerTextField.sendKeys("f");
+        eagerTextField.sendKeys("o");
+        eagerTextField.sendKeys("o");
+        sleep(1000); // Timer set to 1000ms
+        eagerTextField.sendKeys("b");
+        eagerTextField.sendKeys("a");
+        eagerTextField.sendKeys("a");
+        sleep(1000); // Timer set to 1000ms
+        assertLog(id, "foo", "foobaa");
+    }
+
+    private void assertLog(String id, String... messages) {
+        for (int i = 0; i < messages.length; i++) {
+            String expected = "Value change event for " + id + ", new value: '"
+                    + messages[i] + "'";
+
+            String log = getLogRow(messages.length - 1 - i);
+            int tail = log.indexOf(" Cursor at");
+            if (tail != -1) {
+                log = log.substring(0, tail);
+            }
+            Assert.assertEquals(expected, log);
+        }
+
+    }
+}