diff options
7 files changed, 375 insertions, 40 deletions
diff --git a/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java index 244feb3bb9..767ae66515 100644 --- a/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java +++ b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Iterator; import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import com.vaadin.server.ComponentSizeValidator; import com.vaadin.server.VaadinService; @@ -288,17 +289,33 @@ public abstract class AbstractSingleComponentContainer extends public void readDesign(Element design, DesignContext designContext) { // process default attributes super.readDesign(design, designContext); - // handle child element, checking that the design specifies at most one - // child - int childCount = design.children().size(); - if (childCount > 1) { + readDesignChildren(design.children(), designContext); + } + + /** + * Reads the content component from the list of child elements of a design. + * The list must be empty or contain a single element; if the design + * contains multiple child elements, a DesignException is thrown. This + * method should be overridden by subclasses whose design may contain + * non-content child elements. + * + * @param children + * the child elements of the design that is being read + * @param context + * the DesignContext instance used to parse the design + * + * @throws DesignException + * if there are multiple child elements + * @throws DesignException + * if a child element could not be parsed as a Component + */ + protected void readDesignChildren(Elements children, DesignContext context) { + if (children.size() > 1) { throw new DesignException("The container of type " + getClass().toString() + " can have only one child component."); - } else if (childCount == 1) { - Element childElement = design.children().get(0); - Component newChild = designContext.readDesign(childElement); - setContent(newChild); + } else if (children.size() == 1) { + setContent(context.readDesign(children.first())); } } diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 653b620746..e7764ffd8d 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -18,11 +18,18 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; @@ -42,6 +49,9 @@ import com.vaadin.shared.ui.window.WindowMode; import com.vaadin.shared.ui.window.WindowRole; import com.vaadin.shared.ui.window.WindowServerRpc; import com.vaadin.shared.ui.window.WindowState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; /** @@ -1283,4 +1293,116 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public String getTabStopBottomAssistiveText() { return getState(false).assistiveTabStopBottomText; } + + @Override + public void readDesign(Element design, DesignContext context) { + super.readDesign(design, context); + + if (design.hasAttr("center")) { + center(); + } + if (design.hasAttr("position")) { + String[] position = design.attr("position").split(","); + setPositionX(Integer.parseInt(position[0])); + setPositionY(Integer.parseInt(position[1])); + } + if (design.hasAttr("close-shortcut")) { + ShortcutAction shortcut = DesignAttributeHandler + .readAttribute("close-shortcut", design.attributes(), + ShortcutAction.class); + setCloseShortcut(shortcut.getKeyCode(), shortcut.getModifiers()); + } + } + + /** + * Reads the content and possible assistive descriptions from the list of + * child elements of a design. If an element has an + * {@code :assistive-description} attribute, adds the parsed component to + * the list of components used as the assistive description of this Window. + * Otherwise, sets the component as the content of this Window. If there are + * multiple non-description elements, throws a DesignException. + * + * @param children + * child elements in a design + * @param context + * the DesignContext instance used to parse the design + * + * @throws DesignException + * if there are multiple non-description child elements + * @throws DesignException + * if a child element could not be parsed as a Component + * + * @see #setContent(Component) + * @see #setAssistiveDescription(Component...) + */ + @Override + protected void readDesignChildren(Elements children, DesignContext context) { + List<Component> descriptions = new ArrayList<Component>(); + Elements content = new Elements(); + + for (Element child : children) { + if (child.hasAttr(":assistive-description")) { + descriptions.add(context.readDesign(child)); + } else { + content.add(child); + } + } + super.readDesignChildren(content, context); + setAssistiveDescription(descriptions.toArray(new Component[0])); + } + + @Override + public void writeDesign(Element design, DesignContext context) { + super.writeDesign(design, context); + + Window def = context.getDefaultInstance(this); + + if (getState().centered) { + design.attr("center", ""); + } + + DesignAttributeHandler.writeAttribute("position", design.attributes(), + getPosition(), def.getPosition(), String.class); + + CloseShortcut shortcut = getCloseShortcut(); + if (shortcut != null) { + // TODO What if several close shortcuts?? + + CloseShortcut defShortcut = def.getCloseShortcut(); + if (defShortcut == null + || shortcut.getKeyCode() != defShortcut.getKeyCode() + || !Arrays.equals(shortcut.getModifiers(), + defShortcut.getModifiers())) { + DesignAttributeHandler.writeAttribute("close-shortcut", + design.attributes(), shortcut, null, + CloseShortcut.class); + } + } + + for (Component c : getAssistiveDescription()) { + Element child = context.createElement(c).attr( + ":assistive-description", ""); + design.appendChild(child); + } + } + + private String getPosition() { + return getPositionX() + "," + getPositionY(); + } + + private CloseShortcut getCloseShortcut() { + Iterator<CloseShortcut> i = getCloseShortcuts().iterator(); + return i.hasNext() ? i.next() : null; + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> result = super.getCustomAttributes(); + result.add("center"); + result.add("position"); + result.add("position-y"); + result.add("position-x"); + result.add("close-shortcut"); + return result; + } } diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java index e2b6ed8e14..d6f2f65938 100644 --- a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java +++ b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java @@ -126,32 +126,37 @@ public class DesignShortcutActionConverter implements if (value.length() == 0) { return null; } - String[] data = value.split(" ", 2); + String[] data = value.split(" ", 2); String[] parts = data[0].split("-"); - // handle keycode - String keyCodePart = parts[parts.length - 1]; - int keyCode = getKeycodeForString(keyCodePart); - if (keyCode < 0) { - throw new IllegalArgumentException("Invalid shortcut definition " - + value); - } - // handle modifiers - int[] modifiers = null; - if (parts.length > 1) { - modifiers = new int[parts.length - 1]; - } - for (int i = 0; i < parts.length - 1; i++) { - int modifier = getKeycodeForString(parts[i]); - if (modifier > 0) { - modifiers[i] = modifier; - } else { - throw new IllegalArgumentException( - "Invalid shortcut definition " + value); + + try { + // handle keycode + String keyCodePart = parts[parts.length - 1]; + int keyCode = getKeycodeForString(keyCodePart); + if (keyCode < 0) { + throw new IllegalArgumentException("Invalid key '" + + keyCodePart + "'"); + } + // handle modifiers + int[] modifiers = null; + if (parts.length > 1) { + modifiers = new int[parts.length - 1]; + } + for (int i = 0; i < parts.length - 1; i++) { + int modifier = getKeycodeForString(parts[i]); + if (modifier > 0) { + modifiers[i] = modifier; + } else { + throw new IllegalArgumentException("Invalid modifier '" + + parts[i] + "'"); + } } + return new ShortcutAction(data.length == 2 ? data[1] : null, + keyCode, modifiers); + } catch (Exception e) { + throw new ConversionException("Invalid shortcut '" + value + "'", e); } - return new ShortcutAction(data.length == 2 ? data[1] : null, keyCode, - modifiers); } @Override diff --git a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java index cba981c947..10f1e5c711 100644 --- a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java +++ b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java @@ -24,6 +24,7 @@ import java.util.Map; import org.junit.Assert; +import com.vaadin.shared.Connector; import com.vaadin.ui.Component; import com.vaadin.ui.Flash; @@ -59,6 +60,11 @@ public abstract class DeclarativeTestBase<T extends Component> extends if (readMethod == null || writeMethod == null) { continue; } + if (Connector.class.isAssignableFrom(c) + && readMethod.getName().equals("getParent")) { + // Hack to break cycles in the connector hierarchy + continue; + } try { c.getDeclaredMethod(readMethod.getName()); } catch (Exception e) { @@ -99,7 +105,6 @@ public abstract class DeclarativeTestBase<T extends Component> extends } } }); - } @Override @@ -118,5 +123,4 @@ public abstract class DeclarativeTestBase<T extends Component> extends } return comp; } - } diff --git a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java index b2da98bd30..4cb627d035 100644 --- a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java +++ b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java @@ -84,12 +84,21 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { return; } - if (o1 instanceof Collection && o2 instanceof Collection) { - - } else { + if (!(o1 instanceof Collection && o2 instanceof Collection)) { Assert.assertEquals(o1.getClass(), o2.getClass()); } + if (o1 instanceof Object[]) { + Object[] a1 = ((Object[]) o1); + Object[] a2 = ((Object[]) o2); + Assert.assertEquals(message + ": array length", a1.length, + a2.length); + for (int i = 0; i < a1.length; i++) { + assertEquals(message, a1[i], a2[i]); + } + return; + } + List<EqualsAsserter<Object>> comparators = getComparators(o1); if (!comparators.isEmpty()) { for (EqualsAsserter<Object> ec : comparators) { @@ -126,7 +135,9 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { protected abstract <TT> EqualsAsserter<TT> getComparator(Class<TT> c); private boolean isVaadin(Class<?> c) { - return c.getPackage().getName().startsWith("com.vaadin"); + return c.getPackage() != null + && c.getPackage().getName().startsWith("com.vaadin"); + } public void testRead(String design, T expected) { @@ -149,6 +160,10 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { Assert.assertEquals(comparable, produced); } + protected Element createElement(Component c) { + return new DesignContext().createElement(c); + } + private String elementToHtml(Element producedElem) { StringBuilder stringBuilder = new StringBuilder(); elementToHtml(producedElem, stringBuilder); diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java index 9b01188aea..681b9d80a3 100644 --- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -24,10 +24,14 @@ import java.util.Date; import java.util.HashSet; import java.util.TimeZone; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.vaadin.data.util.converter.Converter.ConversionException; import com.vaadin.event.ShortcutAction; +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; import com.vaadin.server.ExternalResource; import com.vaadin.server.FileResource; import com.vaadin.server.Resource; @@ -203,10 +207,8 @@ public class DesignFormatterTest { @Test public void testShortcutActionNoCaption() { - ShortcutAction action = new ShortcutAction(null, - ShortcutAction.KeyCode.D, new int[] { - ShortcutAction.ModifierKey.ALT, - ShortcutAction.ModifierKey.CTRL }); + ShortcutAction action = new ShortcutAction(null, KeyCode.D, new int[] { + ModifierKey.ALT, ModifierKey.CTRL }); String formatted = formatter.format(action); assertEquals("alt-ctrl-d", formatted); @@ -216,6 +218,23 @@ public class DesignFormatterTest { } @Test + public void testInvalidShortcutAction() { + assertInvalidShortcut("-"); + assertInvalidShortcut("foo"); + assertInvalidShortcut("atl-ctrl"); + assertInvalidShortcut("-a"); + } + + protected void assertInvalidShortcut(String shortcut) { + try { + formatter.parse(shortcut, ShortcutAction.class); + Assert.fail("Invalid shortcut '" + shortcut + "' should throw"); + } catch (ConversionException e) { + // expected + } + } + + @Test public void testTimeZone() { TimeZone zone = TimeZone.getTimeZone("GMT+2"); String formatted = formatter.format(zone); diff --git a/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java new file mode 100644 index 0000000000..1ab0011442 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2014 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.server.component.window; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; +import com.vaadin.shared.ui.window.WindowMode; +import com.vaadin.shared.ui.window.WindowRole; +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.Label; +import com.vaadin.ui.Window; +import com.vaadin.ui.declarative.DesignException; + +/** + * Tests declarative support for implementations of {@link Window}. + * + * @since + * @author Vaadin Ltd + */ +public class WindowDeclarativeTest extends DeclarativeTestBase<Window> { + + @Test + public void testDefault() { + String design = "<v-window>"; + + Window expected = new Window(); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testFeatures() { + + String design = "<v-window position='100,100' window-mode='maximized' " + + "center modal=true resizable=false resize-lazy=true closable=false draggable=false " + + "close-shortcut='ctrl-alt-escape' " + + "assistive-prefix='Hello' assistive-postfix='World' assistive-role='alertdialog' " + + "tab-stop-enabled=true " + + "tab-stop-top-assistive-text='Do not move above the window' " + + "tab-stop-bottom-assistive-text='End of window'>" + + "</v-window>"; + + Window expected = new Window(); + + expected.setPositionX(100); + expected.setPositionY(100); + expected.setWindowMode(WindowMode.MAXIMIZED); + + expected.center(); + expected.setModal(!expected.isModal()); + expected.setResizable(!expected.isResizable()); + expected.setResizeLazy(!expected.isResizeLazy()); + expected.setClosable(!expected.isClosable()); + expected.setDraggable(!expected.isDraggable()); + + expected.setCloseShortcut(KeyCode.ESCAPE, ModifierKey.CTRL, + ModifierKey.ALT); + + expected.setAssistivePrefix("Hello"); + expected.setAssistivePostfix("World"); + expected.setAssistiveRole(WindowRole.ALERTDIALOG); + expected.setTabStopEnabled(!expected.isTabStopEnabled()); + expected.setTabStopTopAssistiveText("Do not move above the window"); + expected.setTabStopBottomAssistiveText("End of window"); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testInvalidPosition() { + assertInvalidPosition(""); + assertInvalidPosition("1"); + assertInvalidPosition("100,100.1"); + assertInvalidPosition("x"); + assertInvalidPosition("2,foo"); + // Should be invalid, not checked currently + // assertInvalidPosition("1,2,3"); + } + + protected void assertInvalidPosition(String position) { + try { + read("<v-window position='" + position + "'>"); + Assert.fail("Invalid position '" + position + "' should throw"); + } catch (Exception e) { + // expected + } + } + + @Test + public void testChildContent() { + + String design = "<v-window>" + createElement(new Button("OK")) + + "</v-window>"; + + Window expected = new Window(); + expected.setContent(new Button("OK")); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test(expected = DesignException.class) + public void testMultipleContentChildren() { + + String design = "<v-window>" + createElement(new Label("Hello")) + + createElement(new Button("OK")) + "</v-window>"; + + read(design); + } + + @Test + public void testAssistiveDescription() { + + Label assistive1 = new Label("Assistive text"); + Label assistive2 = new Label("More assistive text"); + + String design = "<v-window>" + + createElement(assistive1).attr(":assistive-description", "") + + createElement(new Button("OK")) + + createElement(assistive2).attr(":assistive-description", ""); + + Window expected = new Window(); + expected.setContent(new Button("OK")); + expected.setAssistiveDescription(assistive1, assistive2); + + testRead(design, expected); + + String written = "<v-window>" + createElement(new Button("OK")) + + createElement(assistive1).attr(":assistive-description", "") + + createElement(assistive2).attr(":assistive-description", ""); + + testWrite(written, expected); + } +} |