From cc995bbe0fd7d3f2c992ff20786f969f4505975b Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 7 May 2013 09:21:10 +0300 Subject: Update merge specification for the master branch. Change-Id: Ia8ad2d2e44e2d29bfa9e2271e4e4cad0a672ec22 --- scripts/automerge7.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/automerge7.sh b/scripts/automerge7.sh index 524b0ff912..8d2cfb5cb8 100755 --- a/scripts/automerge7.sh +++ b/scripts/automerge7.sh @@ -1,7 +1,7 @@ #!/bin/bash -FROM=7.0 -TO=7.1 +FROM=7.1 +TO=master FROM_HEAD=origin/$FROM PUSH="origin HEAD:refs/for/$TO" -- cgit v1.2.3 From 3af82a92984805f4bef45f621e5f3654dcb7bcc9 Mon Sep 17 00:00:00 2001 From: Teemu Pöntelin Date: Wed, 8 May 2013 16:29:17 +0300 Subject: Fixed a typo in UI class Javadoc. #11804 Change-Id: I609489bd9022285565e227e63b11a73e91831503 --- server/src/com/vaadin/ui/UI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 6433bebbe4..3574c5527d 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -63,7 +63,7 @@ import com.vaadin.util.CurrentInstance; * When a new UI instance is needed, typically because the user opens a URL in a * browser window which points to e.g. {@link VaadinServlet}, all * {@link UIProvider}s registered to the current {@link VaadinSession} are - * queried for the UI class that should be used. The selection is by defaylt + * queried for the UI class that should be used. The selection is by default * based on the {@value VaadinSession#UI_PARAMETER} parameter from web.xml. *

*

-- cgit v1.2.3 From b58e849e240b512067c05e75358fe3feb6fb321c Mon Sep 17 00:00:00 2001 From: Teemu Pöntelin Date: Wed, 8 May 2013 16:35:18 +0300 Subject: Fixed a typo in @Widgetset annotation Javadoc. #11803 Change-Id: Iba58755f7a70d92d0a48395abc271f7bc5dcfd73 --- server/src/com/vaadin/annotations/Widgetset.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java index 40276c18a2..006bf59acf 100644 --- a/server/src/com/vaadin/annotations/Widgetset.java +++ b/server/src/com/vaadin/annotations/Widgetset.java @@ -24,7 +24,7 @@ import java.lang.annotation.Target; import com.vaadin.ui.UI; /** - * Defines a specific theme for a {@link UI}. + * Defines a specific widgetset for a {@link UI}. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -- cgit v1.2.3 From ab210b2a8d08c26ab449ce73dec9ae6e2cd3e2c2 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 13 May 2013 13:07:20 +0000 Subject: Revert "Assert that connector needing layout is attached (#11698)" This reverts commit cb7b02d4e50e91b9377410e79c3578587a900d94 The assert is not a good idea because it is triggered when a connector registers a dependency in its init method. Change-Id: I3709c4749927e0333b7557cf553080b770639e50 --- client/src/com/vaadin/client/LayoutManager.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/client/src/com/vaadin/client/LayoutManager.java b/client/src/com/vaadin/client/LayoutManager.java index 381aff5afa..14b155c92f 100644 --- a/client/src/com/vaadin/client/LayoutManager.java +++ b/client/src/com/vaadin/client/LayoutManager.java @@ -827,7 +827,6 @@ public class LayoutManager { * the managed layout that should be layouted */ public final void setNeedsHorizontalLayout(ManagedLayout layout) { - assert isAttached(layout); needsHorizontalLayout.add(layout.getConnectorId()); } @@ -843,21 +842,9 @@ public class LayoutManager { * the managed layout that should be layouted */ public final void setNeedsVerticalLayout(ManagedLayout layout) { - assert isAttached(layout); needsVerticalLayout.add(layout.getConnectorId()); } - private boolean isAttached(ServerConnector connector) { - while (connector != null) { - connector = connector.getParent(); - if (connector == connection.getUIConnector()) { - return true; - } - } - // Reaching null parent before reaching UI connector -> not attached - return false; - } - /** * Gets the outer height (including margins, paddings and borders) of the * given element, provided that it has been measured. These elements are -- cgit v1.2.3 From f7ee755e7dd94612c84e0b99735cc602fdbe7634 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Wed, 15 May 2013 07:31:59 +0300 Subject: Show loading indicator immediately in init as before (#11850) Change-Id: Ibe91db13b301aaf0a2b5e6e5fb08f566bf5cadab --- client/src/com/vaadin/client/ApplicationConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index d77a98a83b..1faf39394d 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -438,7 +438,7 @@ public class ApplicationConnection { tooltip.setOwner(uIConnector.getWidget()); - getLoadingIndicator().trigger(); + getLoadingIndicator().show(); scheduleHeartbeat(); -- cgit v1.2.3 From 8d1ccd76bef6ead837ddb93de307ceee97d6b97b Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 7 Jun 2013 12:13:43 +0300 Subject: Ignore changes merged from the previous maintenance branch Change-Id: I16a203db76fd6dc7e091a2b2708f80b9d4cef079 --- scripts/automerge7.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/automerge7.sh b/scripts/automerge7.sh index 7b8c37a5aa..935604fb5d 100755 --- a/scripts/automerge7.sh +++ b/scripts/automerge7.sh @@ -1,8 +1,10 @@ #!/bin/bash +IGNORE=7.0 FROM=7.1 TO=master +IGNORE_HEAD=origin/$IGNORE FROM_HEAD=origin/$FROM PUSH="origin HEAD:refs/for/$TO" @@ -86,7 +88,7 @@ fi git checkout $TO git fetch -pending=`git log $TO..$FROM_HEAD --reverse|grep "^commit "|sed "s/commit //"` +pending=`git log $TO..$FROM_HEAD ^$IGNORE_HEAD --reverse|grep "^commit "|sed "s/commit //"` pendingCommit= pendingCommitMessage= -- cgit v1.2.3 From fe8d330c3c9f2ae2c0ace28fa5c34e5b2b131112 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 7 Jun 2013 14:03:50 +0300 Subject: Escape \ in commit messages Change-Id: I1c5773dd593d7877833a5b1798f3e812b53a1b1e --- scripts/automerge7.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/automerge7.sh b/scripts/automerge7.sh index 935604fb5d..2b98530902 100755 --- a/scripts/automerge7.sh +++ b/scripts/automerge7.sh @@ -96,7 +96,7 @@ for commit in $pending do echo "Checking $commit..." mergeDirective=`git log -n 1 --format=%B $commit|grep "^Merge:"|sed "s/Merge: //"` - commitMsg=`git log -n 1 --format=oneline --abbrev-commit $commit` + commitMsg=`git log -n 1 --format=oneline --abbrev-commit $commit | sed 's/\\\\/\\\\\\\\/g'` #Multiple levels of unescaping, sed just changes \ to \\ if [ "$mergeDirective" == "" ] then if can_merge $commit -- cgit v1.2.3 From cdb8d682af602cc8731e7a63827b499c2da5144e Mon Sep 17 00:00:00 2001 From: tapio Date: Mon, 17 Jun 2013 14:41:18 +0300 Subject: Add support for setId to TabSheet.Tab (#12064) Change-Id: Ia88b9d03a26b9670ab4026f8083a0b932dafa1c0 --- client/src/com/vaadin/client/ui/VTabsheet.java | 15 ++++- server/src/com/vaadin/ui/Component.java | 6 +- server/src/com/vaadin/ui/TabSheet.java | 39 ++++++++++- .../components/tabsheet/TabSheetWithTabIds.html | 76 +++++++++++++++++++++ .../components/tabsheet/TabSheetWithTabIds.java | 77 ++++++++++++++++++++++ 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.html create mode 100644 uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.java diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index fe29e2ebc0..dc20c27837 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -112,6 +112,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private String styleName; + private String id; + private Tab(TabBar tabBar) { super(DOM.createTD()); this.tabBar = tabBar; @@ -204,10 +206,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, String newStyleName = tabUidl .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME); // Find the nth td element - if (newStyleName != null && newStyleName.length() != 0) { + if (newStyleName != null && !newStyleName.isEmpty()) { if (!newStyleName.equals(styleName)) { // If we have a new style name - if (styleName != null && styleName.length() != 0) { + if (styleName != null && !styleName.isEmpty()) { // Remove old style name if present td.removeClassName(TD_CLASSNAME + "-" + styleName); } @@ -221,6 +223,15 @@ public class VTabsheet extends VTabsheetBase implements Focusable, td.removeClassName(TD_CLASSNAME + "-" + styleName); styleName = null; } + + String newId = tabUidl.getStringAttribute("id"); + if (newId != null && !newId.isEmpty()) { + td.setId(newId); + id = newId; + } else if (id != null) { + td.removeAttribute("id"); + id = null; + } } public void recalculateCaptionWidth() { diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java index 485327bb54..c385805675 100644 --- a/server/src/com/vaadin/ui/Component.java +++ b/server/src/com/vaadin/ui/Component.java @@ -651,7 +651,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public Locale getLocale(); /** - * Adds an unique id for component that get's transferred to terminal for + * Adds an unique id for component that is used in the client-side for * testing purposes. Keeping identifiers unique is the responsibility of the * programmer. * @@ -661,7 +661,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public void setId(String id); /** - * Get's currently set debug identifier + * Gets currently set debug identifier * * @return current id, null if not set */ @@ -669,7 +669,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable { /** *

- * Gets the component's description, used in tooltips and can be displayed + * Gets the components description, used in tooltips and can be displayed * directly in certain other components such as forms. The description can * be used to briefly describe the state of the component to the user. The * description string may contain certain XML tags: diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java index 36022adb74..2c85b279cd 100644 --- a/server/src/com/vaadin/ui/TabSheet.java +++ b/server/src/com/vaadin/ui/TabSheet.java @@ -431,7 +431,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, icon); } final String caption = tab.getCaption(); - if (caption != null && caption.length() > 0) { + if (caption != null && !caption.isEmpty()) { target.addAttribute( TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION, caption); } @@ -449,10 +449,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, } final String styleName = tab.getStyleName(); - if (styleName != null && styleName.length() != 0) { + if (styleName != null && !styleName.isEmpty()) { target.addAttribute(TabsheetConstants.TAB_STYLE_NAME, styleName); } + final String id = tab.getId(); + if (id != null && !id.isEmpty()) { + target.addAttribute("id", id); + } + target.addAttribute("key", keyMapper.key(component)); if (component.equals(selected)) { target.addAttribute("selected", true); @@ -1015,6 +1020,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @see #setStyleName(String) */ public String getStyleName(); + + /** + * Adds an unique id for component that is used in the client-side for + * testing purposes. Keeping identifiers unique is the responsibility of + * the programmer. + * + * @param id + * An alphanumeric id + */ + public void setId(String id); + + /** + * Gets currently set debug identifier + * + * @return current id, null if not set + */ + public String getId(); } /** @@ -1030,6 +1052,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, private String description = null; private ErrorMessage componentError = null; private String styleName; + private String id; public TabSheetTabImpl(String caption, Resource icon) { if (caption == null) { @@ -1150,6 +1173,18 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, public String getStyleName() { return styleName; } + + @Override + public void setId(String id) { + this.id = id; + markAsDirty(); + + } + + @Override + public String getId() { + return id; + } } /** diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.html new file mode 100644 index 0000000000..64e85f55e3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.html @@ -0,0 +1,76 @@ + + + + + + +TabSheetWithTabIds + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TabSheetWithTabIds
open/run/com.vaadin.tests.components.tabsheet.TabSheetWithTabIds
assertElementNotPresenttab1
assertElementNotPresenttab2
assertElementNotPresenttab3
clickvaadin=runcomvaadintestscomponentstabsheetTabSheetWithTabIds::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VButton[0]/domChild[0]/domChild[0]
assertElementPresenttab1
assertElementPresenttab2
assertElementPresenttab3
clickvaadin=runcomvaadintestscomponentstabsheetTabSheetWithTabIds::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]
assertElementNotPresenttab1
assertElementNotPresenttab2
assertElementNotPresenttab3
+ + diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.java new file mode 100644 index 0000000000..ae5adea45e --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetWithTabIds.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2013 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.tabsheet; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Label; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TabSheet.Tab; + +public class TabSheetWithTabIds extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + TabSheet tabSheet = new TabSheet(); + + final Tab tab1 = tabSheet.addTab(new Label("Label 1"), "Tab 1", null); + + final Tab tab2 = tabSheet.addTab(new Label("Label 2"), "Tab 2", null); + + final Tab tab3 = tabSheet.addTab(new Label("Label 3"), "Tab 3", null); + + addComponent(tabSheet); + + Button b = new Button("Set ids", new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + tab1.setId("tab1"); + tab2.setId("tab2"); + tab3.setId("tab3"); + } + }); + addComponent(b); + + Button b2 = new Button("Clear ids", new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + tab1.setId(null); + tab2.setId(null); + tab3.setId(null); + } + }); + addComponent(b2); + } + + @Override + protected String getTestDescription() { + return "Add support for setId to TabSheet.Tab"; + } + + @Override + protected Integer getTicketNumber() { + return 12064; + } + +} -- cgit v1.2.3 From 6a97757139e6c544cf9d62328546eb96856e8df0 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 18 Jun 2013 22:00:14 +0300 Subject: Removed StringToNumberConverter (#12092) Change-Id: Iada5bb27eefef788143625c4d63a121f6c2589ea --- .../util/converter/DefaultConverterFactory.java | 2 - .../util/converter/StringToNumberConverter.java | 65 ---------------------- .../converter/TestStringToNumberConverter.java | 24 -------- 3 files changed, 91 deletions(-) delete mode 100644 server/src/com/vaadin/data/util/converter/StringToNumberConverter.java delete mode 100644 server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java index bbd3945a37..3c365ecdff 100644 --- a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java +++ b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java @@ -103,8 +103,6 @@ public class DefaultConverterFactory implements ConverterFactory { return new StringToIntegerConverter(); } else if (Boolean.class.isAssignableFrom(sourceType)) { return new StringToBooleanConverter(); - } else if (Number.class.isAssignableFrom(sourceType)) { - return new StringToNumberConverter(); } else if (Date.class.isAssignableFrom(sourceType)) { return new StringToDateConverter(); } else { diff --git a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java b/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java deleted file mode 100644 index 22df42403f..0000000000 --- a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2000-2013 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.data.util.converter; - -import java.text.NumberFormat; -import java.util.Locale; - -/** - * A converter that converts from {@link Number} to {@link String} and back. - * Uses the given locale and {@link NumberFormat} for formatting and parsing. - *

- * Override and overwrite {@link #getFormat(Locale)} to use a different format. - *

- * - * @author Vaadin Ltd - * @since 7.0 - */ -public class StringToNumberConverter extends - AbstractStringToNumberConverter { - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, - * java.lang.Class, java.util.Locale) - */ - @Override - public Number convertToModel(String value, - Class targetType, Locale locale) - throws ConversionException { - if (targetType != getModelType()) { - throw new ConversionException("Converter only supports " - + getModelType().getName() + " (targetType was " - + targetType.getName() + ")"); - } - - return convertToNumber(value, targetType, locale); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.data.util.converter.Converter#getModelType() - */ - @Override - public Class getModelType() { - return Number.class; - } - -} diff --git a/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java b/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java deleted file mode 100644 index 66fc4f6532..0000000000 --- a/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.vaadin.tests.data.converter; - -import junit.framework.TestCase; - -import com.vaadin.data.util.converter.StringToNumberConverter; - -public class TestStringToNumberConverter extends TestCase { - - StringToNumberConverter converter = new StringToNumberConverter(); - - public void testNullConversion() { - assertEquals(null, converter.convertToModel(null, Number.class, null)); - } - - public void testEmptyStringConversion() { - assertEquals(null, converter.convertToModel("", Number.class, null)); - } - - public void testValueConversion() { - assertEquals(Long.valueOf(10), - converter.convertToModel("10", Number.class, null)); - assertEquals(10.5, converter.convertToModel("10.5", Number.class, null)); - } -} -- cgit v1.2.3 From b84d2feabf34a972b42c82ead780768883591378 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 18 Jun 2013 22:15:54 +0300 Subject: Adds String <-> BigDecimal converter (#9997) * BigDecimal properties connected to String based fields (TextField/TextArea/...) are now supported without custom converters Change-Id: I2dab67875b3cfdb46b17e9d4cc35ffb94c114478 --- .../util/converter/DefaultConverterFactory.java | 3 + .../converter/StringToBigDecimalConverter.java | 60 ++++++++++ .../converter/TestStringToBigDecimalConverter.java | 53 +++++++++ .../components/textfield/BigDecimalTextField.html | 123 +++++++++++++++++++++ .../components/textfield/BigDecimalTextField.java | 123 +++++++++++++++++++++ 5 files changed, 362 insertions(+) create mode 100644 server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java create mode 100644 server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java create mode 100644 uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.html create mode 100644 uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.java diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java index 3c365ecdff..0b3cfcd1b0 100644 --- a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java +++ b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java @@ -16,6 +16,7 @@ package com.vaadin.data.util.converter; +import java.math.BigDecimal; import java.util.Date; import java.util.logging.Logger; @@ -101,6 +102,8 @@ public class DefaultConverterFactory implements ConverterFactory { return new StringToFloatConverter(); } else if (Integer.class.isAssignableFrom(sourceType)) { return new StringToIntegerConverter(); + } else if (BigDecimal.class.isAssignableFrom(sourceType)) { + return new StringToBigDecimalConverter(); } else if (Boolean.class.isAssignableFrom(sourceType)) { return new StringToBooleanConverter(); } else if (Date.class.isAssignableFrom(sourceType)) { diff --git a/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java b/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java new file mode 100644 index 0000000000..75d4cedd23 --- /dev/null +++ b/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2013 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.data.util.converter; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * A converter that converts from {@link String} to {@link BigDecimal} and back. + * Uses the given locale and a {@link NumberFormat} instance for formatting and + * parsing. + *

+ * Leading and trailing white spaces are ignored when converting from a String. + *

+ *

+ * Override and overwrite {@link #getFormat(Locale)} to use a different format. + *

+ * + * @author Vaadin Ltd + * @since 7.2 + */ +public class StringToBigDecimalConverter extends + AbstractStringToNumberConverter { + @Override + protected NumberFormat getFormat(Locale locale) { + NumberFormat numberFormat = super.getFormat(locale); + if (numberFormat instanceof DecimalFormat) { + ((DecimalFormat) numberFormat).setParseBigDecimal(true); + } + + return numberFormat; + } + + @Override + public BigDecimal convertToModel(String value, + Class targetType, Locale locale) + throws com.vaadin.data.util.converter.Converter.ConversionException { + return (BigDecimal) convertToNumber(value, BigDecimal.class, locale); + } + + @Override + public Class getModelType() { + return BigDecimal.class; + } +} diff --git a/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java b/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java new file mode 100644 index 0000000000..5db33691b6 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2013 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.data.converter; + +import java.math.BigDecimal; +import java.util.Locale; + +import junit.framework.TestCase; + +import com.vaadin.data.util.converter.StringToBigDecimalConverter; + +public class TestStringToBigDecimalConverter extends TestCase { + + StringToBigDecimalConverter converter = new StringToBigDecimalConverter(); + + public void testNullConversion() { + assertEquals(null, + converter.convertToModel(null, BigDecimal.class, null)); + } + + public void testEmptyStringConversion() { + assertEquals(null, converter.convertToModel("", BigDecimal.class, null)); + } + + public void testValueParsing() { + BigDecimal converted = converter.convertToModel("10", BigDecimal.class, + null); + BigDecimal expected = new BigDecimal(10); + assertEquals(expected, converted); + } + + public void testValueFormatting() { + BigDecimal bd = new BigDecimal(12.5); + String expected = "12,5"; + + String converted = converter.convertToPresentation(bd, String.class, + Locale.GERMAN); + assertEquals(expected, converted); + } +} diff --git a/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.html b/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.html new file mode 100644 index 0000000000..2428e8b4bb --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.html @@ -0,0 +1,123 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.components.textfield.BigDecimalTextField?restartApplication
clickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]
assertValuevaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]15,2
clickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[3]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::PID_SLog_row_01. Commit ok. Property value: 15.2
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]70,12
typevaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]130
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::160,327
clickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[3]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::PID_SLog_row_02. Commit ok. Property value: 130
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]75,16
typevaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]130abc
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::96,280
clickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[3]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::PID_SLog_row_03. Commit failed: Commit failed
assertCSSClassvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[0]v-errorindicator
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]71,12
typevaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[1]/VFormLayout[0]/VFormLayout$VFormLayoutTable[0]/VTextField[0]130,130
mouseClickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::118,280
clickvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::/VVerticalLayout[0]/Slot[3]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestscomponentstextfieldBigDecimalTextField::PID_SLog_row_04. Commit ok. Property value: 130.13
+ + diff --git a/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.java b/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.java new file mode 100644 index 0000000000..18d8679c2f --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/textfield/BigDecimalTextField.java @@ -0,0 +1,123 @@ +/* + * Copyright 2000-2013 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 java.io.Serializable; +import java.math.BigDecimal; +import java.util.Locale; + +import com.vaadin.data.fieldgroup.FieldGroup; +import com.vaadin.data.util.BeanItem; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +/** + * @since 7.2 + * @author Vaadin Ltd + */ +public class BigDecimalTextField extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setLocale(new Locale("fi", "FI")); + + BeanBigDecimal beanBigDecimal = new BeanBigDecimal(); + BeanItem beanItem = new BeanItem( + beanBigDecimal); + + FormLayout formLayout = new FormLayout(); + TextField textField = new TextField("BigDecimal field"); + textField.setImmediate(true); + textField.setValue("12"); + formLayout.addComponent(textField); + + final FieldGroup fieldGroup = new FieldGroup(beanItem); + fieldGroup.bind(textField, "decimal"); + + Button setValue = new Button("Set value to 15,2", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + ((TextField) fieldGroup.getField("decimal")).setValue("15,2"); + } + }); + + Button button = new Button("Commit"); + button.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(Button.ClickEvent event) { + try { + fieldGroup.commit(); + log("Commit ok. Property value: " + + fieldGroup.getItemDataSource() + .getItemProperty("decimal").getValue()); + } catch (FieldGroup.CommitException e) { + log("Commit failed: " + e.getMessage()); + } + } + }); + + layout.addComponent(formLayout); + layout.addComponent(setValue); + layout.addComponent(button); + + setContent(layout); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTestDescription() + */ + @Override + protected String getTestDescription() { + return "Tests that BigDecimals work correctly with TextFields"; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTicketNumber() + */ + @Override + protected Integer getTicketNumber() { + return 9997; + } + + public static class BeanBigDecimal implements Serializable { + BigDecimal decimal; + + public BeanBigDecimal() { + + } + + public BigDecimal getDecimal() { + return decimal; + } + + public void setDecimal(BigDecimal decimal) { + this.decimal = decimal; + } + } + +} -- cgit v1.2.3 From 24b0386b4f0d3033766562573a6a322f656bf0ed Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Tue, 2 Jul 2013 09:22:25 +0300 Subject: Fix two tests broken by removal of StringToNumberConverter (#12092) Change-Id: Ic1520ddf670d83ce804acf240067c85d493196ca --- uitest/src/com/vaadin/tests/components/table/DoublesInTable.java | 4 ++-- .../src/com/vaadin/tests/minitutorials/v7a1/FormatTableValue.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/table/DoublesInTable.java b/uitest/src/com/vaadin/tests/components/table/DoublesInTable.java index 355cd43d37..4cf2506eeb 100644 --- a/uitest/src/com/vaadin/tests/components/table/DoublesInTable.java +++ b/uitest/src/com/vaadin/tests/components/table/DoublesInTable.java @@ -9,7 +9,7 @@ import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.BeanItemContainer; import com.vaadin.data.util.converter.Converter; -import com.vaadin.data.util.converter.StringToNumberConverter; +import com.vaadin.data.util.converter.StringToDoubleConverter; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.data.bean.Address; import com.vaadin.tests.data.bean.Country; @@ -276,7 +276,7 @@ public class DoublesInTable extends TestBase { }); - t.setConverter("rent", new StringToNumberConverter() { + t.setConverter("rent", new StringToDoubleConverter() { @Override protected NumberFormat getFormat(Locale locale) { return NumberFormat.getCurrencyInstance(locale); diff --git a/uitest/src/com/vaadin/tests/minitutorials/v7a1/FormatTableValue.java b/uitest/src/com/vaadin/tests/minitutorials/v7a1/FormatTableValue.java index be2768a5f7..8485bba499 100644 --- a/uitest/src/com/vaadin/tests/minitutorials/v7a1/FormatTableValue.java +++ b/uitest/src/com/vaadin/tests/minitutorials/v7a1/FormatTableValue.java @@ -3,7 +3,7 @@ package com.vaadin.tests.minitutorials.v7a1; import java.text.NumberFormat; import java.util.Locale; -import com.vaadin.data.util.converter.StringToNumberConverter; +import com.vaadin.data.util.converter.StringToDoubleConverter; import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.ui.Table; @@ -30,14 +30,14 @@ public class FormatTableValue extends AbstractTestUI { table.getItem(itemId).getItemProperty(DEFAULT_PROPERTY) .setValue(3.1415); - table.setConverter(PERCENT_PROPERTY, new StringToNumberConverter() { + table.setConverter(PERCENT_PROPERTY, new StringToDoubleConverter() { @Override protected NumberFormat getFormat(Locale locale) { return NumberFormat.getPercentInstance(locale); } }); - table.setConverter(CURRENCY_PROPERTY, new StringToNumberConverter() { + table.setConverter(CURRENCY_PROPERTY, new StringToDoubleConverter() { @Override protected NumberFormat getFormat(Locale locale) { return NumberFormat.getCurrencyInstance(locale); -- cgit v1.2.3 From d23c06a6145ca4c4f03377fc324e08693926873b Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Fri, 10 May 2013 17:45:21 +0300 Subject: Accessibility for Notification (#11820) Change-Id: Ic9c1a417fa791927897b6fcdf35a1fb4444dfd70 --- client/src/com/vaadin/client/ui/VNotification.java | 67 ++++- client/src/com/vaadin/client/ui/VOverlay.java | 2 +- server/src/com/vaadin/server/SystemMessages.java | 12 +- server/src/com/vaadin/ui/Notification.java | 136 ++++++++++- .../com/vaadin/ui/NotificationConfiguration.java | 269 +++++++++++++++++++++ server/src/com/vaadin/ui/UI.java | 12 + .../ui/ui/NotificationConfigurationBean.java | 137 +++++++++++ shared/src/com/vaadin/shared/ui/ui/UIState.java | 18 ++ .../notification/NotificationsWaiAria.html | 117 +++++++++ .../notification/NotificationsWaiAria.java | 113 +++++++++ 10 files changed, 872 insertions(+), 11 deletions(-) create mode 100644 server/src/com/vaadin/ui/NotificationConfiguration.java create mode 100644 shared/src/com/vaadin/shared/ui/ui/NotificationConfigurationBean.java create mode 100644 uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html create mode 100644 uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java index 7019394e3b..0ddc8bf7c5 100644 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ b/client/src/com/vaadin/client/ui/VNotification.java @@ -21,19 +21,25 @@ import java.util.Date; import java.util.EventObject; import java.util.Iterator; +import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; +import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.Position; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; import com.vaadin.shared.ui.ui.UIConstants; public class VNotification extends VOverlay { @@ -149,15 +155,67 @@ public class VNotification extends VOverlay { } public void show(Widget widget, Position position, String style) { - setWidget(widget); + NotificationConfigurationBean styleSetup = getUiState(style); + setWaiAriaRole(styleSetup); + + FlowPanel panel = new FlowPanel(); + if (styleSetup.hasAssistivePrefix()) { + panel.add(new Label(styleSetup.getAssistivePrefix())); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + + panel.add(widget); + + if (styleSetup.hasAssistivePostfix()) { + panel.add(new Label(styleSetup.getAssistivePostfix())); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + setWidget(panel); show(position, style); } public void show(String html, Position position, String style) { - setWidget(new HTML(html)); + NotificationConfigurationBean styleSetup = getUiState(style); + String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE; + + setWaiAriaRole(styleSetup); + + String type = ""; + String usage = ""; + + if (styleSetup != null && styleSetup.hasAssistivePrefix()) { + type = "" + + styleSetup.getAssistivePrefix() + ""; + } + + if (styleSetup != null && styleSetup.hasAssistivePostfix()) { + usage = "" + + styleSetup.getAssistivePostfix() + ""; + } + + setWidget(new HTML(type + html + usage)); show(position, style); } + private NotificationConfigurationBean getUiState(String style) { + NotificationConfigurationBean styleSetup = getApplicationConnection() + .getUIConnector().getState().notificationConfiguration.setup + .get(style); + return styleSetup; + } + + private void setWaiAriaRole(NotificationConfigurationBean styleSetup) { + Roles.getAlertRole().set(getElement()); + + if (styleSetup != null && styleSetup.getAssistiveRole() != null) { + if (Role.STATUS == styleSetup.getAssistiveRole()) { + Roles.getStatusRole().set(getElement()); + } + } + } + public void show(Position position, String style) { setOpacity(getElement(), startOpacity); if (style != null) { @@ -377,7 +435,8 @@ public class VNotification extends VOverlay { final String parsedUri = client .translateVaadinUri(notification .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)); - html += ""; + html += "\"\""; } if (notification .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) { @@ -417,6 +476,8 @@ public class VNotification extends VOverlay { public static VNotification createNotification(int delayMsec, Widget owner) { final VNotification notification = GWT.create(VNotification.class); + notification.setWaiAriaRole(null); + notification.delayMsec = delayMsec; if (BrowserInfo.get().isTouchDevice()) { new Timer() { diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java index ced476f9dd..206e26b960 100644 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ b/client/src/com/vaadin/client/ui/VOverlay.java @@ -172,7 +172,7 @@ public class VOverlay extends PopupPanel implements CloseHandler { * * See default theme 'shadow.css' for implementation example. */ - private static final String SHADOW_HTML = "
"; + private static final String SHADOW_HTML = "
"; /** * Matches {@link PopupPanel}.ANIMATION_DURATION diff --git a/server/src/com/vaadin/server/SystemMessages.java b/server/src/com/vaadin/server/SystemMessages.java index 5e0fde1d4a..299c725207 100644 --- a/server/src/com/vaadin/server/SystemMessages.java +++ b/server/src/com/vaadin/server/SystemMessages.java @@ -63,32 +63,32 @@ public class SystemMessages implements Serializable { protected String sessionExpiredURL = null; protected boolean sessionExpiredNotificationEnabled = true; protected String sessionExpiredCaption = "Session Expired"; - protected String sessionExpiredMessage = "Take note of any unsaved data, and click here to continue."; + protected String sessionExpiredMessage = "Take note of any unsaved data, and click here or press ESC key to continue."; protected String communicationErrorURL = null; protected boolean communicationErrorNotificationEnabled = true; protected String communicationErrorCaption = "Communication problem"; - protected String communicationErrorMessage = "Take note of any unsaved data, and click here to continue."; + protected String communicationErrorMessage = "Take note of any unsaved data, and click here or press ESC to continue."; protected String authenticationErrorURL = null; protected boolean authenticationErrorNotificationEnabled = true; protected String authenticationErrorCaption = "Authentication problem"; - protected String authenticationErrorMessage = "Take note of any unsaved data, and click here to continue."; + protected String authenticationErrorMessage = "Take note of any unsaved data, and click here or press ESC to continue."; protected String internalErrorURL = null; protected boolean internalErrorNotificationEnabled = true; protected String internalErrorCaption = "Internal error"; - protected String internalErrorMessage = "Please notify the administrator.
Take note of any unsaved data, and click here to continue."; + protected String internalErrorMessage = "Please notify the administrator.
Take note of any unsaved data, and click here or press ESC to continue."; protected String outOfSyncURL = null; protected boolean outOfSyncNotificationEnabled = true; protected String outOfSyncCaption = "Out of sync"; - protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.
Take note of any unsaved data, and click here to re-sync."; + protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.
Take note of any unsaved data, and click here or press ESC to re-sync."; protected String cookiesDisabledURL = null; protected boolean cookiesDisabledNotificationEnabled = true; protected String cookiesDisabledCaption = "Cookies disabled"; - protected String cookiesDisabledMessage = "This application requires cookies to function.
Please enable cookies in your browser and click here to try again."; + protected String cookiesDisabledMessage = "This application requires cookies to function.
Please enable cookies in your browser and click here or press ESC to try again."; /** * Use {@link CustomizedSystemMessages} to customize diff --git a/server/src/com/vaadin/ui/Notification.java b/server/src/com/vaadin/ui/Notification.java index cf1d03ab5c..f9bb1521e7 100644 --- a/server/src/com/vaadin/ui/Notification.java +++ b/server/src/com/vaadin/ui/Notification.java @@ -21,6 +21,7 @@ import java.io.Serializable; import com.vaadin.server.Page; import com.vaadin.server.Resource; import com.vaadin.shared.Position; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; /** * A notification message, used to display temporary messages to the user - for @@ -190,21 +191,32 @@ public class Notification implements Serializable { case WARNING_MESSAGE: delayMsec = 1500; styleName = "warning"; + setNavigationConfiguration("Warning: ", "", Role.ALERT); break; case ERROR_MESSAGE: delayMsec = -1; styleName = "error"; + setNavigationConfiguration("Error: ", " - close with ESC", + Role.ALERT); break; case TRAY_NOTIFICATION: delayMsec = 3000; position = Position.BOTTOM_RIGHT; styleName = "tray"; - + setNavigationConfiguration("Info: ", "", Role.STATUS); + break; case HUMANIZED_MESSAGE: default: + styleName = "humanized"; + setNavigationConfiguration("Info: ", "", Role.ALERT); break; } + } + private void setNavigationConfiguration(String prefix, String postfix, + Role ariaRole) { + UI.getCurrent().getNotificationConfiguration() + .setStyleConfiguration(styleName, prefix, postfix, ariaRole); } /** @@ -321,6 +333,128 @@ public class Notification implements Serializable { return styleName; } + /** + * Sets the accessibility prefix for a notification type. + * + * This prefix is read to assistive device users before the content of the + * notification, but not visible on the page. + * + * @param type + * Type of the notification + * @param prefix + * String that is placed before the notification content + */ + public void setAssistivePrefixForType(Type type, String prefix) { + UI.getCurrent().getNotificationConfiguration() + .setAssistivePrefixForStyle(getStyle(type), prefix); + } + + /** + * Gets the accessibility prefix for a notification type. + * + * This prefix is read to assistive device users before the content of the + * notification, but not visible on the page. + * + * @param type + * Type of the notification + * @return The accessibility prefix for the provided notification type + */ + public String getAssistivePrefixForType(Type type) { + return UI.getCurrent().getNotificationConfiguration() + .getAssistivePrefixForStyle(getStyle(type)); + } + + /** + * Sets the accessibility postfix for a notification type. + * + * This postfix is read to assistive device users after the content of the + * notification, but not visible on the page. + * + * @param type + * Type of the notification + * @param postfix + * String that is placed after the notification content + */ + public void setAssistivePostfixForType(Type type, String postfix) { + UI.getCurrent().getNotificationConfiguration() + .setAssistivePostfixForStyle(getStyle(type), postfix); + } + + /** + * Gets the accessibility postfix for a notification type. + * + * This postfix is read to assistive device users after the content of the + * notification, but not visible on the page. + * + * @param type + * Type of the notification + * @return The accessibility postfix for the provided notification type + */ + public String getAssistivePostfixForType(Type type) { + return UI.getCurrent().getNotificationConfiguration() + .getAssistivePostfixForStyle(getStyle(type)); + } + + /** + * Sets the WAI-ARIA role for a notification type. + * + * This role defines how an assistive device handles a notification. + * Available roles are alert and status (@see Roles + * Model). + * + * The default role is alert. + * + * @param type + * Type of the notification + * @param role + * Role to set for the notification type + */ + public void setAssistiveRoleForType(Type type, Role role) { + UI.getCurrent().getNotificationConfiguration() + .setAssistiveRoleForStyle(getStyle(type), role); + } + + /** + * Gets the WAI-ARIA role for a notification type. + * + * This role defines how an assistive device handles a notification. + * Available roles are alert and status (@see Roles + * Model) + * + * The default role is alert. + * + * @param type + * Type of the notification + * @return Role to set for the notification type + */ + public Role getAssistiveRoleForType(Type type) { + return UI.getCurrent().getNotificationConfiguration() + .getAssistiveRoleForStyle(getStyle(type)); + } + + private String getStyle(Type type) { + String style = ""; + + switch (type) { + case WARNING_MESSAGE: + style = "warning"; + break; + case ERROR_MESSAGE: + style = "error"; + break; + case TRAY_NOTIFICATION: + style = "tray"; + case HUMANIZED_MESSAGE: + default: + style = "humanized"; + break; + } + + return style; + } + /** * Sets whether html is allowed in the caption and description. If set to * true, the texts are passed to the browser as html and the developer is diff --git a/server/src/com/vaadin/ui/NotificationConfiguration.java b/server/src/com/vaadin/ui/NotificationConfiguration.java new file mode 100644 index 0000000000..52d3e76d63 --- /dev/null +++ b/server/src/com/vaadin/ui/NotificationConfiguration.java @@ -0,0 +1,269 @@ +/* + * Copyright 2000-2013 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.io.Serializable; + +import com.vaadin.shared.ui.ui.NotificationConfigurationBean; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; +import com.vaadin.shared.ui.ui.UIState.NotificationConfigurationState; + +/** + * Provides methods for configuring the notification. + * + * @author Vaadin Ltd + * @since 7.1 + */ +public interface NotificationConfiguration extends Serializable { + public void setStyleConfiguration(String style, String prefix, + String postfix, Role ariaRole); + + /** + * Returns the complete configuration object for the given notification + * style. + * + * @param style + * String of the notification style to return + * @return The notification configuration object + */ + public NotificationConfigurationBean getStyleConfiguration(String style); + + /** + * Sets the accessibility prefix for the given notification style. + * + * This prefix is read to assistive device users in front of the content of + * the notification, but not visible on the page. + * + * @param style + * String of the notification style + * @param prefix + * String that is placed before the notification content + */ + public void setAssistivePrefixForStyle(String style, String prefix); + + /** + * Returns the accessibility prefix for the given notification style. + * + * This prefix is read to assistive device users in front of the content of + * the notification, but not visible on the page. + * + * @param style + * String of the notification style + * @return The prefix of the provided notification style + */ + public String getAssistivePrefixForStyle(String style); + + /** + * Sets the accessibility postfix for the given notification style. + * + * This postfix is read to assistive device users after the content of the + * notification, but not visible on the page. + * + * @param style + * String of the notification style + * @param postfix + * String that is placed after the notification content + */ + public void setAssistivePostfixForStyle(String style, String postfix); + + /** + * Returns the accessibility postfix for the given notification style. + * + * This postfix is read to assistive device users after the content of the + * notification, but not visible on the page. + * + * @param style + * String of the notification style + * @return The postfix of the provided notification style + */ + public String getAssistivePostfixForStyle(String style); + + /** + * Sets the WAI-ARIA role for a notification style. + * + * This role defines how an assistive device handles a notification. + * Available roles are alert, alertdialog and status (@see Roles + * Model) + * + * The default role is alert. + * + * @param style + * String of the notification style + * @param role + * Role to set for the notification type + */ + public void setAssistiveRoleForStyle(String style, Role role); + + /** + * Returns the WAI-ARIA role for a notification style. + * + * This role defines how an assistive device handles a notification. + * Available roles are alert, alertdialog and status (@see Roles + * Model ) + * + * The default role is alert. + * + * @param style + * String of the notification style + * @return The current Role for the notification type + */ + public Role getAssistiveRoleForStyle(String style); +} + +class NotificationConfigurationImpl implements NotificationConfiguration { + + private UI ui; + + public NotificationConfigurationImpl(UI ui) { + this.ui = ui; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#setStyleConfiguration(java.lang + * .String, java.lang.String, java.lang.String, + * com.vaadin.ui.NotificationConfiguration.Role) + */ + @Override + public void setStyleConfiguration(String style, String prefix, + String postfix, Role ariaRole) { + getState().setup.put(style, new NotificationConfigurationBean(prefix, + postfix, ariaRole)); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#getStyleConfiguration(java.lang + * .String) + */ + @Override + public NotificationConfigurationBean getStyleConfiguration(String style) { + return getState(false).setup.get(style); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#setStylePrefix(java.lang.String, + * java.lang.String) + */ + @Override + public void setAssistivePrefixForStyle(String style, String prefix) { + getConfigurationBean(style).setAssistivePrefix(prefix); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#getStylePrefix(java.lang.String) + */ + @Override + public String getAssistivePrefixForStyle(String style) { + NotificationConfigurationBean styleSetup = getState().setup.get(style); + if (styleSetup != null) { + return styleSetup.getAssistivePrefix(); + } + + return null; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#setStylePostfix(com.vaadin.ui + * .Notification.Type, java.lang.String) + */ + @Override + public void setAssistivePostfixForStyle(String style, String postfix) { + getConfigurationBean(style).setAssistivePostfix(postfix); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.NotificationConfiguration#getStylePostfix(com.vaadin.ui + * .Notification.Type) + */ + @Override + public String getAssistivePostfixForStyle(String style) { + NotificationConfigurationBean styleSetup = getState().setup.get(style); + if (styleSetup != null) { + return styleSetup.getAssistivePostfix(); + } + + return null; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.NotificationConfiguration#setStyleRole(com.vaadin.ui. + * Notification.Type, com.vaadin.ui.NotificationConfiguration.Role) + */ + @Override + public void setAssistiveRoleForStyle(String style, Role role) { + getConfigurationBean(style).setAssistiveRole(role); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.NotificationConfiguration#getStyleRole(com.vaadin.ui. + * Notification.Type) + */ + @Override + public Role getAssistiveRoleForStyle(String style) { + NotificationConfigurationBean styleSetup = getState().setup.get(style); + if (styleSetup != null) { + return styleSetup.getAssistiveRole(); + } + + return null; + } + + private NotificationConfigurationBean getConfigurationBean(String style) { + NotificationConfigurationBean styleSetup = getState().setup.get(style); + if (styleSetup == null) { + styleSetup = new NotificationConfigurationBean(); + getState().setup.put(style, styleSetup); + } + + return styleSetup; + } + + private NotificationConfigurationState getState() { + return ui.getState().notificationConfiguration; + } + + private NotificationConfigurationState getState(boolean markAsDirty) { + return ui.getState(markAsDirty).notificationConfiguration; + } + +} diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index e0811e51f7..2138edd6ac 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -218,6 +218,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements private PushConfiguration pushConfiguration = new PushConfigurationImpl( this); + private NotificationConfiguration notificationConfiguration = new NotificationConfigurationImpl( + this); + /** * Creates a new empty UI without a caption. The content of the UI must be * set by calling {@link #setContent(Component)} before using the UI. @@ -1281,6 +1284,15 @@ public abstract class UI extends AbstractSingleComponentContainer implements return tooltipConfiguration; } + /** + * Retrieves the object used for configuring notifications. + * + * @return The instance used for notification configuration + */ + public NotificationConfiguration getNotificationConfiguration() { + return notificationConfiguration; + } + /** * Retrieves the object used for configuring the loading indicator. * diff --git a/shared/src/com/vaadin/shared/ui/ui/NotificationConfigurationBean.java b/shared/src/com/vaadin/shared/ui/ui/NotificationConfigurationBean.java new file mode 100644 index 0000000000..05a1706763 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/ui/NotificationConfigurationBean.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2013 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.ui; + +import java.io.Serializable; + +/** + * Holds configuration information for a notification type. + * + * @author Vaadin Ltd + */ +public class NotificationConfigurationBean implements Serializable { + /** + * Available WAI-ARIA roles for a notification. + */ + public enum Role { + ALERT, STATUS + }; + + private String prefix; + private String postfix; + private Role role = Role.ALERT; + + public NotificationConfigurationBean() { + } + + public NotificationConfigurationBean(String prefix, String postfix, + Role role) { + this.prefix = prefix; + this.postfix = postfix; + this.role = role; + } + + /** + * Returns the accessibility prefix, which is placed before the notification + * content. + * + * @return the prefix + */ + public String getAssistivePrefix() { + return prefix; + } + + /** + * Sets the accessibility prefix, which is placed before the notification + * content. + * + * @param pefix + * the prefix to set + */ + public void setAssistivePrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Checks if an accessibility prefix is set. + * + * @return true when assistivePrefix is not null and has a length > 0, false + * otherwise + */ + public boolean hasAssistivePrefix() { + return prefix != null && !prefix.isEmpty(); + } + + /** + * Returns the accessibility postfix, which is placed after the notification + * content. + * + * @return the postfix + */ + public String getAssistivePostfix() { + return postfix; + } + + /** + * Sets the accessibility postfix, which is placed after the notification + * content. + * + * @param postfix + * the postfix to set + */ + public void setAssistivePostfix(String postfix) { + this.postfix = postfix; + } + + /** + * Checks if an accessibility postfix is set. + * + * @return true when postfix is not null and has a length > 0, false + * otherwise + */ + public boolean hasAssistivePostfix() { + return postfix != null && !postfix.isEmpty(); + } + + /** + * Returns the WAI-ARIA role that defines how an assistive device will + * inform the user about a notification. + * + * @return the role + */ + public Role getAssistiveRole() { + return role; + } + + /** + * Sets the WAI-ARIA role that defines how an assistive device will inform + * the user about a notification. + * + * Available roles are alert, alertdialog and status (@see Roles + * Model). + * + * @param role + * the role to set + */ + public void setAssistiveRole(Role role) { + this.role = role; + } +} diff --git a/shared/src/com/vaadin/shared/ui/ui/UIState.java b/shared/src/com/vaadin/shared/ui/ui/UIState.java index 8d042a835f..4cde452a2b 100644 --- a/shared/src/com/vaadin/shared/ui/ui/UIState.java +++ b/shared/src/com/vaadin/shared/ui/ui/UIState.java @@ -23,10 +23,12 @@ import java.util.Map; import com.vaadin.shared.communication.PushMode; import com.vaadin.shared.ui.TabIndexState; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; public class UIState extends TabIndexState { public TooltipConfigurationState tooltipConfiguration = new TooltipConfigurationState(); public LoadingIndicatorConfigurationState loadingIndicatorConfiguration = new LoadingIndicatorConfigurationState(); + public NotificationConfigurationState notificationConfiguration = new NotificationConfigurationState(); public int pollInterval = -1; // Informing users of assistive devices, that the content of this container @@ -48,6 +50,22 @@ public class UIState extends TabIndexState { public int maxWidth = 500; } + public static class NotificationConfigurationState implements Serializable { + public Map setup = new HashMap(); + { + setup.put("error", new NotificationConfigurationBean("Error: ", + " - close with ESC-key", Role.ALERT)); + setup.put("warning", new NotificationConfigurationBean("Warning: ", + null, Role.ALERT)); + setup.put("humanized", new NotificationConfigurationBean("Info: ", + null, Role.ALERT)); + setup.put("tray", new NotificationConfigurationBean("Status: ", + null, Role.STATUS)); + setup.put("assistive", new NotificationConfigurationBean("Note: ", + null, Role.STATUS)); + }; + } + public static class PushConfigurationState implements Serializable { public static final String TRANSPORT_PARAM = "transport"; public static final String FALLBACK_TRANSPORT_PARAM = "fallbackTransport"; diff --git a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html new file mode 100644 index 0000000000..aa13b9e637 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html @@ -0,0 +1,117 @@ + + + + + + +NotificationsWaiAria + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NotificationsWaiAria
open/run/com.vaadin.tests.components.notification.NotificationsWaiAria?restartApplication
enterCharactervaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTextField[0]Prefix:
enterCharactervaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VTextField[0]- press ESC to close
selectvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VNativeSelect[0]/domChild[0]label=ALERT
clickvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VButton[0]/domChild[0]/domChild[0]
assertElementPresentvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]
assertAttributevaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]@rolealert
assertAttributevaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[0]@classv-assistive-device-only
assertTextvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[0]Prefix:
assertAttributevaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[2]@classv-assistive-device-only
assertTextvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[2]- press ESC to close
closeNotificationvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]0,0
selectvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VNativeSelect[0]/domChild[0]label=STATUS
clickvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VButton[0]/domChild[0]/domChild[0]
assertAttributexpath=/html/body/div[2]/div@rolestatus
closeNotificationvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]0,0
typevaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTextField[0]
typevaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VTextField[0]
clickvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VButton[0]/domChild[0]/domChild[0]
assertElementNotPresentvaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[1]
+ + diff --git a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java new file mode 100644 index 0000000000..a8861d40de --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java @@ -0,0 +1,113 @@ +package com.vaadin.tests.components.notification; + +import com.vaadin.data.Item; +import com.vaadin.server.Page; +import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Notification.Type; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.TextField; + +public class NotificationsWaiAria extends TestBase { + + private static final String CAPTION = "CAPTION"; + private static final String ROLE = "ROLE"; + + private TextField prefix; + private TextField postfix; + private NativeSelect role; + + private TextArea tf; + private ComboBox type; + + @SuppressWarnings("deprecation") + @Override + protected void setup() { + prefix = new TextField("Prefix", "Info"); + addComponent(prefix); + + postfix = new TextField("Postfix", + " - closes automatically after 10 seconds"); + addComponent(postfix); + + role = new NativeSelect("Role"); + role.addItem(Role.ALERT); + role.addItem(Role.STATUS); + role.setValue(role.getItemIds().iterator().next()); + addComponent(role); + + tf = new TextArea("Text", "Hello world"); + tf.setRows(10); + addComponent(tf); + type = new ComboBox(); + type.setNullSelectionAllowed(false); + type.addContainerProperty(CAPTION, String.class, ""); + + type.setItemCaptionPropertyId(CAPTION); + + Item item = type.addItem(Notification.TYPE_HUMANIZED_MESSAGE); + item.getItemProperty(CAPTION).setValue("Humanized"); + + item = type.addItem(Notification.TYPE_ERROR_MESSAGE); + item.getItemProperty(CAPTION).setValue("Error"); + + item = type.addItem(Notification.TYPE_WARNING_MESSAGE); + item.getItemProperty(CAPTION).setValue("Warning"); + + item = type.addItem(Notification.TYPE_TRAY_NOTIFICATION); + item.getItemProperty(CAPTION).setValue("Tray"); + + type.setValue(type.getItemIds().iterator().next()); + addComponent(type); + + Button showNotification = new Button("Show notification", + new SettingHandler()); + addComponent(showNotification); + + Button showDefaultNotification = new Button("Default notification", + new DefaultHandler()); + addComponent(showDefaultNotification); + } + + @Override + protected String getDescription() { + return "Generic test case for notifications"; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + + private class SettingHandler implements ClickListener { + @Override + public void buttonClick(ClickEvent event) { + Type typeValue = (Type) type.getValue(); + + Notification n = new Notification(tf.getValue(), typeValue); + n.setHtmlContentAllowed(true); + n.setAssistivePrefixForType(typeValue, prefix.getValue()); + n.setAssistivePostfixForType(typeValue, postfix.getValue()); + n.setAssistiveRoleForType(typeValue, (Role) role.getValue()); + + n.show(Page.getCurrent()); + } + } + + private class DefaultHandler implements ClickListener { + @Override + public void buttonClick(ClickEvent event) { + Notification n = new Notification(tf.getValue(), + (Type) type.getValue()); + n.setHtmlContentAllowed(true); + n.show(Page.getCurrent()); + } + } +} -- cgit v1.2.3 From d2c2ebb61e64bec7aaec73aa4b75d1b8044cb021 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Wed, 19 Jun 2013 16:15:23 +0300 Subject: Support screen reader only notifications (#11830) Change-Id: I09391b27c0b3df538ffe74c4edb8d96224f14cd3 --- client/src/com/vaadin/client/ui/VNotification.java | 10 ++++++++++ server/src/com/vaadin/ui/Notification.java | 14 ++++++++++++-- shared/src/com/vaadin/shared/Position.java | 7 ++++++- .../components/notification/NotificationsWaiAria.java | 3 +++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java index 0ddc8bf7c5..128de0a285 100644 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ b/client/src/com/vaadin/client/ui/VNotification.java @@ -52,6 +52,12 @@ public class VNotification extends VOverlay { public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT; public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT; + /** + * Position that is only accessible for assistive devices, invisible for + * visual users. + */ + public static final Position ASSISTIVE = Position.ASSISTIVE; + public static final int DELAY_FOREVER = -1; public static final int DELAY_NONE = 0; @@ -315,6 +321,10 @@ public class VNotification extends VOverlay { DOM.setStyleAttribute(el, "top", ""); DOM.setStyleAttribute(el, "bottom", "0px"); break; + case ASSISTIVE: + DOM.setStyleAttribute(el, "top", "-2000px"); + DOM.setStyleAttribute(el, "left", "-2000px"); + break; default: case MIDDLE_CENTER: center(); diff --git a/server/src/com/vaadin/ui/Notification.java b/server/src/com/vaadin/ui/Notification.java index f9bb1521e7..31fa265b02 100644 --- a/server/src/com/vaadin/ui/Notification.java +++ b/server/src/com/vaadin/ui/Notification.java @@ -64,7 +64,7 @@ import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role; */ public class Notification implements Serializable { public enum Type { - HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION; + HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION, ASSISTIVE_NOTIFICATION; } @Deprecated @@ -205,6 +205,12 @@ public class Notification implements Serializable { styleName = "tray"; setNavigationConfiguration("Info: ", "", Role.STATUS); break; + case ASSISTIVE_NOTIFICATION: + delayMsec = 3000; + position = Position.ASSISTIVE; + styleName = "assistive"; + setNavigationConfiguration("Note: ", "", Role.ALERT); + break; case HUMANIZED_MESSAGE: default: styleName = "humanized"; @@ -446,6 +452,10 @@ public class Notification implements Serializable { break; case TRAY_NOTIFICATION: style = "tray"; + break; + case ASSISTIVE_NOTIFICATION: + style = "assistive"; + break; case HUMANIZED_MESSAGE: default: style = "humanized"; @@ -548,4 +558,4 @@ public class Notification implements Serializable { public static void show(String caption, String description, Type type) { new Notification(caption, description, type).show(Page.getCurrent()); } -} \ No newline at end of file +} diff --git a/shared/src/com/vaadin/shared/Position.java b/shared/src/com/vaadin/shared/Position.java index cd34ee8b87..3df42d65d8 100755 --- a/shared/src/com/vaadin/shared/Position.java +++ b/shared/src/com/vaadin/shared/Position.java @@ -16,5 +16,10 @@ package com.vaadin.shared; public enum Position { - TOP_LEFT, TOP_CENTER, TOP_RIGHT, MIDDLE_LEFT, MIDDLE_CENTER, MIDDLE_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT; + TOP_LEFT, TOP_CENTER, TOP_RIGHT, MIDDLE_LEFT, MIDDLE_CENTER, MIDDLE_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, + /** + * Position that is only accessible for assistive devices, invisible for + * visual users. + **/ + ASSISTIVE; } diff --git a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java index a8861d40de..27af49a397 100644 --- a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java +++ b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.java @@ -63,6 +63,9 @@ public class NotificationsWaiAria extends TestBase { item = type.addItem(Notification.TYPE_TRAY_NOTIFICATION); item.getItemProperty(CAPTION).setValue("Tray"); + item = type.addItem(Notification.Type.ASSISTIVE_NOTIFICATION); + item.getItemProperty(CAPTION).setValue("Assistive"); + type.setValue(type.getItemIds().iterator().next()); addComponent(type); -- cgit v1.2.3 From 00c473abf05da9bc8c755e6fef54f119ece698aa Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Fri, 14 Jun 2013 11:55:21 +0300 Subject: Accessibility for Window (#11821) Change-Id: Ica5d13fb138e8ed1c1966858c9e1dd0c4aa13af5 --- client/src/com/vaadin/client/ui/VUI.java | 36 +++++ client/src/com/vaadin/client/ui/VWindow.java | 179 ++++++++++++++++++++- .../src/com/vaadin/client/ui/VWindowOverlay.java | 77 +++++++++ .../vaadin/client/ui/window/WindowConnector.java | 7 + server/src/com/vaadin/ui/Window.java | 109 ++++++++++++- .../com/vaadin/shared/ui/window/WindowState.java | 13 ++ .../components/window/ExtraWindowShownWaiAria.html | 147 +++++++++++++++++ .../components/window/ExtraWindowShownWaiAria.java | 139 ++++++++++++++++ 8 files changed, 702 insertions(+), 5 deletions(-) create mode 100644 client/src/com/vaadin/client/ui/VWindowOverlay.java create mode 100644 uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html create mode 100644 uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java index 4817bf9304..1a84613a5d 100644 --- a/client/src/com/vaadin/client/ui/VUI.java +++ b/client/src/com/vaadin/client/ui/VUI.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui; import java.util.ArrayList; +import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.HasScrollHandlers; @@ -43,6 +44,7 @@ import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.LayoutManager; import com.vaadin.client.Profiler; +import com.vaadin.client.Util; import com.vaadin.client.VConsole; import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; @@ -165,6 +167,8 @@ public class VUI extends SimplePanel implements ResizeHandler, }); + private Element storedFocus; + public VUI() { super(); // Allow focusing the view by using the focus() method, the view @@ -497,4 +501,36 @@ public class VUI extends SimplePanel implements ResizeHandler, FocusUtil.setTabIndex(this, index); } + /** + * Allows to store the currently focused Element. + * + * Current use case is to store the focus when a Window is opened. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @param focusedElement + */ + public void storeFocus() { + storedFocus = Util.getFocusedElement(); + } + + /** + * Restores the previously stored focus Element. + * + * Current use case is to restore the focus when a Window is closed. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @return the lastFocusElementBeforeDialogOpened + */ + public void focusStoredElement() { + if (storedFocus != null) { + storedFocus.focus(); + + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + storedFocus.focus(); + } + }); + } + } } diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 1331eb106a..1756c619a7 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -20,6 +20,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.RelevantValue; +import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Style; @@ -29,8 +32,11 @@ import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.user.client.Command; @@ -41,22 +47,27 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.LayoutManager; import com.vaadin.client.Util; import com.vaadin.client.debug.internal.VDebugWindow; import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.Connector; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.window.WindowMode; +import com.vaadin.shared.ui.window.WindowState.WindowRole; /** * "Sub window" component. * * @author Vaadin Ltd */ -public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, - ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { +public class VWindow extends VWindowOverlay implements + ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler, + KeyUpHandler, FocusHandler, BlurHandler, Focusable { private static ArrayList windowOrder = new ArrayList(); @@ -138,6 +149,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, private boolean closable = true; + private String assistivePrefix; + private String assistivePostfix; + /** * If centered (via UIDL), the window should stay in the centered -mode * until a position is received from the server, or the user moves or @@ -172,13 +186,47 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, // Different style of shadow for windows setShadowStyle("window"); + Roles.getDialogRole().set(getElement()); + Roles.getDialogRole().setAriaRelevantProperty(getElement(), + RelevantValue.ADDITIONS); + constructDOM(); contentPanel.addScrollHandler(this); contentPanel.addKeyDownHandler(this); + contentPanel.addKeyUpHandler(this); contentPanel.addFocusHandler(this); contentPanel.addBlurHandler(this); } + @Override + protected void onAttach() { + super.onAttach(); + + /* + * Stores the element that has focus in the application UI when the + * window is opened, so it can be restored when the window closes. + * + * This is currently implemented for the case when one non-modal window + * can be open at the same time, and the focus is not changed while the + * window is open. + */ + getApplicationConnection().getUIConnector().getWidget().storeFocus(); + } + + @Override + protected void onDetach() { + super.onDetach(); + + /* + * Restores the previously stored focused element. + * + * When the focus was changed outside the window while the window was + * open, the originally stored element is restored. + */ + getApplicationConnection().getUIConnector().getWidget() + .focusStoredElement(); + } + public void bringToFront() { int curIndex = windowOrder.indexOf(this); if (curIndex + 1 < windowOrder.size()) { @@ -256,7 +304,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, maximizeRestoreBox = DOM.createDiv(); DOM.setElementProperty(maximizeRestoreBox, "className", CLASSNAME + "-maximizebox"); + DOM.setElementAttribute(maximizeRestoreBox, "tabindex", "0"); DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox"); + DOM.setElementAttribute(closeBox, "tabindex", "0"); DOM.appendChild(footer, resizeBox); wrapper = DOM.createDiv(); @@ -275,6 +325,19 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, setWidget(contentPanel); + // Make the closebox accessible for assistive devices + Roles.getButtonRole().set(closeBox); + Roles.getButtonRole().setAriaLabelProperty(closeBox, "close button"); + + // Make the maximizebox accessible for assistive devices + Roles.getButtonRole().set(maximizeRestoreBox); + Roles.getButtonRole().setAriaLabelProperty(maximizeRestoreBox, + "maximize button"); + + // Provide the title to assistive devices + AriaHelper.ensureHasId(headerText); + Roles.getDialogRole().setAriaLabelledbyProperty(getElement(), + Id.of(headerText)); } /** @@ -622,11 +685,64 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (icon != null) { icon = client.translateVaadinUri(icon); html = "" + html; + + "\" class=\"v-icon\" alt=\"\" />" + html; } + + // Provide information to assistive device users that a sub window was + // opened + String prefix = "" + + assistivePrefix + ""; + String postfix = "" + + assistivePostfix + ""; + + html = prefix + html + postfix; DOM.setInnerHTML(headerText, html); } + /** + * Setter for the text for assistive devices the window caption is prefixed + * with. + * + * @param assistivePrefix + * the assistivePrefix to set + */ + public void setAssistivePrefix(String assistivePrefix) { + this.assistivePrefix = assistivePrefix; + } + + /** + * Getter for the text for assistive devices the window caption is prefixed + * with. + * + * @return the assistivePrefix + */ + public String getAssistivePrefix() { + return assistivePrefix; + } + + /** + * Setter for the text for assistive devices the window caption is postfixed + * with. + * + * @param assistivePostfix + * the assistivePostfix to set + */ + public void setAssistivePostfix(String assistivePostfix) { + this.assistivePostfix = assistivePostfix; + } + + /** + * Getter for the text for assistive devices the window caption is postfixed + * with. + * + * @return the assistivePostfix + */ + public String getAssistivePostfix() { + return assistivePostfix; + } + @Override protected Element getContainerElement() { // in GWT 1.5 this method is used in PopupPanel constructor @@ -994,6 +1110,13 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } } + @Override + public void onKeyUp(KeyUpEvent event) { + if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + onCloseClick(); + } + } + @Override public void onBlur(BlurEvent event) { if (client.hasEventListeners(this, EventId.BLUR)) { @@ -1030,4 +1153,54 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, - contentPanel.getElement().getOffsetWidth(); } + /** + * Allows to specify which connectors contain the description for the + * window. Text contained in the widgets of the connectors will be read by + * assistive devices when it is opened. + * + * @param connectors + * with the connectors of the widgets to use as description + */ + public void setAssistiveDescription(Connector[] connectors) { + if (connectors != null) { + Id[] ids = new Id[connectors.length]; + for (int index = 0; index < connectors.length; index++) { + if (connectors[index] == null) { + throw new IllegalArgumentException( + "All values in parameter description need to be non-null"); + } + + Element element = ((ComponentConnector) connectors[index]) + .getWidget().getElement(); + AriaHelper.ensureHasId(element); + ids[index] = Id.of(element); + } + + Roles.getDialogRole().setAriaDescribedbyProperty(getElement(), ids); + } else { + throw new IllegalArgumentException( + "Parameter description must be non-null"); + } + } + + /** + * Sets the WAI-ARIA role the window. + * + * This role defines how an assistive device handles a window. Available + * roles are alertdialog and dialog (@see Roles + * Model). + * + * The default role is dialog. + * + * @param role + * WAI-ARIA role to set for the window + */ + public void setWaiAriaRole(WindowRole role) { + if ("alertdialog".equals(role)) { + Roles.getAlertdialogRole().set(getElement()); + } else { + Roles.getDialogRole().set(getElement()); + } + } } diff --git a/client/src/com/vaadin/client/ui/VWindowOverlay.java b/client/src/com/vaadin/client/ui/VWindowOverlay.java new file mode 100644 index 0000000000..efc01cf63e --- /dev/null +++ b/client/src/com/vaadin/client/ui/VWindowOverlay.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2013 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 com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.client.ApplicationConnection; + +public class VWindowOverlay extends VOverlay { + public VWindowOverlay() { + } + + public VWindowOverlay(boolean autoHide, boolean modal, boolean showShadow) { + super(autoHide, modal, showShadow); + } + + /** + * Gets the 'overlay container' element. Tries to find the current + * {@link ApplicationConnection} using {@link #getApplicationConnection()}. + * + * @return the overlay container element for the current + * {@link ApplicationConnection} or another element if the current + * {@link ApplicationConnection} cannot be determined. + */ + @Override + public Element getOverlayContainer() { + ApplicationConnection ac = getApplicationConnection(); + if (ac == null) { + return super.getOverlayContainer(); + } else { + Element overlayContainer = getOverlayContainer(ac); + return overlayContainer; + } + } + + /** + * Gets the 'overlay container' element pertaining to the given + * {@link ApplicationConnection}. Each overlay should be created in a + * overlay container element, so that the correct theme and styles can be + * applied. + * + * @param ac + * A reference to {@link ApplicationConnection} + * @return The overlay container + */ + public static Element getOverlayContainer(ApplicationConnection ac) { + String id = ac.getConfiguration().getRootPanelId(); + id = id += "-window-overlays"; + Element container = DOM.getElementById(id); + if (container == null) { + container = DOM.createDiv(); + container.setId(id); + String styles = ac.getUIConnector().getWidget().getParent() + .getStyleName(); + container.addClassName(styles); + container.addClassName(CLASSNAME_CONTAINER); + RootPanel.get().getElement().appendChild(container); + } + + return container; + } +} diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index 4b839384a2..ea1073dd18 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -295,8 +295,15 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector if (getIcon() != null) { iconURL = getIcon(); } + + window.setAssistivePrefix(state.assistivePrefix); + window.setAssistivePostfix(state.assistivePostfix); window.setCaption(state.caption, iconURL); + window.setWaiAriaRole(getState().role); + + window.setAssistiveDescription(state.contentDescription); + clickEventHandler.handleEventHandlerRegistration(); window.immediate = state.immediate; diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 5820161c1c..980e96c384 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -33,10 +33,12 @@ import com.vaadin.event.ShortcutAction.ModifierKey; import com.vaadin.event.ShortcutListener; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; +import com.vaadin.shared.Connector; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.window.WindowMode; import com.vaadin.shared.ui.window.WindowServerRpc; import com.vaadin.shared.ui.window.WindowState; +import com.vaadin.shared.ui.window.WindowState.WindowRole; import com.vaadin.util.ReflectTools; /** @@ -228,8 +230,6 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, // Don't do anything if not attached to a UI if (uI != null) { - // focus is restored to the parent window - uI.focus(); // window is removed from the UI uI.removeWindow(this); } @@ -995,4 +995,109 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, protected WindowState getState(boolean markAsDirty) { return (WindowState) super.getState(markAsDirty); } + + /** + * Allows to specify which component contains the description for the + * window. Text contained in this component will be read by assistive + * devices when it is opened. + * + * @param connector + * with the component to use as description + */ + public void setAssistiveDescription(Connector connector) { + setAssistiveDescription(new Connector[] { connector }); + } + + /** + * Allows to specify which components contain the description for the + * window. Text contained in this component will be read by assistive + * devices when it is opened. + * + * @param connectors + * with the components to use as description + */ + public void setAssistiveDescription(Connector... connectors) { + getState().contentDescription = connectors; + } + + /** + * Sets the accessibility prefix for the window caption. + * + * This prefix is read to assistive device users before the window caption, + * but not visible on the page. + * + * @param prefix + * String that is placed before the window caption + */ + public void setAssistivePrefix(String prefix) { + getState().assistivePrefix = prefix; + } + + /** + * Gets the accessibility prefix for the window caption. + * + * This prefix is read to assistive device users before the window caption, + * but not visible on the page. + * + * @return The accessibility prefix + */ + public String getAssistivePrefix() { + return getState().assistivePrefix; + } + + /** + * Sets the accessibility postfix for the window caption. + * + * This postfix is read to assistive device users after the window caption, + * but not visible on the page. + * + * @param prefix + * String that is placed after the window caption + */ + public void setAssistivePostfix(String assistivePostfix) { + getState().assistivePostfix = assistivePostfix; + } + + /** + * Gets the accessibility postfix for the window caption. + * + * This postfix is read to assistive device users after the window caption, + * but not visible on the page. + * + * @return The accessibility postfix + */ + public String getAssistivePostfix() { + return getState().assistivePostfix; + } + + /** + * Sets the WAI-ARIA role the window. + * + * This role defines how an assistive device handles a window. Available + * roles are alertdialog and dialog (@see Roles + * Model). + * + * The default role is dialog. + * + * @param role + * WAI-ARIA role to set for the window + */ + public void setAssistiveRole(WindowRole role) { + getState().role = role; + } + + /** + * Gets the WAI-ARIA role the window. + * + * This role defines how an assistive device handles a window. Available + * roles are alertdialog and dialog (@see Roles + * Model). + * + * @return WAI-ARIA role set for the window + */ + public WindowRole getAssistiveRole() { + return getState().role; + } } diff --git a/shared/src/com/vaadin/shared/ui/window/WindowState.java b/shared/src/com/vaadin/shared/ui/window/WindowState.java index 5a2d2b81b0..fa430f6c9c 100644 --- a/shared/src/com/vaadin/shared/ui/window/WindowState.java +++ b/shared/src/com/vaadin/shared/ui/window/WindowState.java @@ -15,6 +15,7 @@ */ package com.vaadin.shared.ui.window; +import com.vaadin.shared.Connector; import com.vaadin.shared.ui.panel.PanelState; public class WindowState extends PanelState { @@ -22,6 +23,13 @@ public class WindowState extends PanelState { primaryStyleName = "v-window"; } + /** + * Available WAI-ARIA roles for a window. + */ + public enum WindowRole { + ALERTDIALOG, DIALOG + }; + public boolean modal = false; public boolean resizable = true; public boolean resizeLazy = false; @@ -30,4 +38,9 @@ public class WindowState extends PanelState { public int positionX = -1; public int positionY = -1; public WindowMode windowMode = WindowMode.NORMAL; + + public String assistivePrefix = ""; + public String assistivePostfix = ""; + public Connector[] contentDescription; + public WindowRole role = WindowRole.DIALOG; } \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html new file mode 100644 index 0000000000..76dcbddd0f --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html @@ -0,0 +1,147 @@ + + + + + + +ExtraWindowShownWaiAria + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtraWindowShownWaiAria
open/run/com.vaadin.tests.components.window.ExtraWindowShownWaiAria?restartApplication
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0]
assertElementPresentvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@roledialog
storeAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]@idheaderid
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-labelledby${headerid}
storeAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@iddescriptionid
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby${descriptionid}
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]@rolebutton
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[2]@rolebutton
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0]
assertElementNotPresentvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VCheckBox[0]/domChild[0]
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0]
storeAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@iddescriptionid
storeAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[1]@iddescription2id
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby${descriptionid} ${description2id}
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0]
assertElementNotPresentvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/
typevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[4]/VTextField[0]Important
typevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VTextField[0] - do ASAP
clickvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]Important
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]@classv-assistive-device-only
assertTextvaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]- do ASAP
assertAttributevaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]@classv-assistive-device-only
+ + diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java new file mode 100644 index 0000000000..b7c929120d --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java @@ -0,0 +1,139 @@ +package com.vaadin.tests.components.window; + +import com.vaadin.server.ThemeResource; +import com.vaadin.shared.ui.window.WindowState.WindowRole; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.Window; + +public class ExtraWindowShownWaiAria extends TestBase { + + @Override + protected void setup() { + final CheckBox modal = new CheckBox("Modal dialog"); + final CheckBox additionalDescription = new CheckBox( + "Additional Description"); + final TextField prefix = new TextField("Prefix: "); + final TextField postfix = new TextField("Postfix: "); + + Button simple = new Button("Open Alert Dialog", + new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + CssLayout layout = new CssLayout(); + + final Window w = new Window("Sub window", layout); + w.center(); + w.setModal(modal.getValue()); + w.setAssistiveRole(WindowRole.ALERTDIALOG); + w.setAssistivePrefix(prefix.getValue()); + w.setAssistivePostfix(postfix.getValue()); + + Label description1 = new Label("Simple alert dialog."); + layout.addComponent(description1); + + if (!additionalDescription.getValue()) { + w.setAssistiveDescription(description1); + } else { + Label description2 = new Label( + "Please select what to do!"); + layout.addComponent(description2); + + w.setAssistiveDescription(description1, + description2); + } + + layout.addComponent(new Button("Close", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + w.close(); + } + })); + Button iconButton = new Button("A button with icon"); + iconButton.setIcon(new ThemeResource( + "../runo/icons/16/ok.png")); + layout.addComponent(iconButton); + + event.getButton().getUI().addWindow(w); + iconButton.focus(); + } + + }); + getLayout().addComponent(simple); + + Button complex = new Button("Open Entry Dialog", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + FormLayout form = new FormLayout(); + + final Window w = new Window("Form Window", form); + w.center(); + w.setModal(modal.getValue()); + w.setAssistivePrefix(prefix.getValue()); + w.setAssistivePostfix(postfix.getValue()); + + Label description1 = new Label( + "Please fill in your data"); + form.addComponent(description1); + + if (!additionalDescription.getValue()) { + w.setAssistiveDescription(description1); + } else { + Label description2 = new Label( + "and press the button save."); + form.addComponent(description2); + + w.setAssistiveDescription(description1, + description2); + } + + TextField name = new TextField("Name:"); + form.addComponent(name); + + form.addComponent(new TextField("Address")); + + Button saveButton = new Button("Save", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + w.close(); + } + }); + form.addComponent(saveButton); + + event.getButton().getUI().addWindow(w); + name.focus(); + } + }); + getLayout().addComponent(complex); + + getLayout().addComponent(modal); + getLayout().addComponent(additionalDescription); + + getLayout().addComponent(prefix); + getLayout().addComponent(postfix); + + } + + @Override + protected String getDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} -- cgit v1.2.3 From 8b5a7e940b8fcbaaf135ec3c395ffc38c62d5a1b Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Tue, 25 Jun 2013 11:47:38 +0300 Subject: Tabsheet tabs should support alternate text (#11824) Change-Id: I000fe6102291d8bd9bbe484b5683b50c40c8470b --- client/src/com/vaadin/client/VCaption.java | 12 ++- client/src/com/vaadin/client/ui/Icon.java | 22 ++++- client/src/com/vaadin/client/ui/VTabsheet.java | 3 +- server/src/com/vaadin/ui/TabSheet.java | 108 ++++++++++++++++++++- .../shared/ui/tabsheet/TabsheetBaseConstants.java | 2 + .../tests/components/tabsheet/TabSheetIcons.html | 10 ++ .../tests/components/tabsheet/TabSheetIcons.java | 2 + .../tests/components/tabsheet/TabSheetTest.java | 2 +- 8 files changed, 153 insertions(+), 8 deletions(-) diff --git a/client/src/com/vaadin/client/VCaption.java b/client/src/com/vaadin/client/VCaption.java index d0338de4a1..b5d0230087 100644 --- a/client/src/com/vaadin/client/VCaption.java +++ b/client/src/com/vaadin/client/VCaption.java @@ -42,6 +42,8 @@ public class VCaption extends HTML { private Icon icon; + private String iconAltText = ""; + private Element captionText; private final ApplicationConnection client; @@ -300,6 +302,14 @@ public class VCaption extends HTML { @Deprecated public boolean updateCaptionWithoutOwner(String caption, boolean disabled, boolean hasDescription, boolean hasError, String iconURL) { + return updateCaptionWithoutOwner(caption, disabled, hasDescription, + hasError, iconURL, ""); + } + + @Deprecated + public boolean updateCaptionWithoutOwner(String caption, boolean disabled, + boolean hasDescription, boolean hasError, String iconURL, + String iconAltText) { boolean wasPlacedAfterComponent = placedAfterComponent; // Caption is placed after component unless there is some part which @@ -332,7 +342,7 @@ public class VCaption extends HTML { // Icon forces the caption to be above the component placedAfterComponent = false; - icon.setUri(iconURL); + icon.setUri(iconURL, iconAltText); } else if (icon != null) { // Remove existing diff --git a/client/src/com/vaadin/client/ui/Icon.java b/client/src/com/vaadin/client/ui/Icon.java index 5ba3cc6eeb..f02f0e10a8 100644 --- a/client/src/com/vaadin/client/ui/Icon.java +++ b/client/src/com/vaadin/client/ui/Icon.java @@ -34,11 +34,19 @@ public class Icon extends UIObject { } public Icon(ApplicationConnection client, String uidlUri) { + this(client, uidlUri, ""); + } + + public Icon(ApplicationConnection client, String uidlUri, String iconAltText) { this(client); - setUri(uidlUri); + setUri(uidlUri, iconAltText); } public void setUri(String uidlUri) { + setUri(uidlUri, ""); + } + + public void setUri(String uidlUri, String uidlAlt) { if (!uidlUri.equals(myUri)) { /* * Start sinking onload events, widgets responsibility to react. We @@ -51,6 +59,18 @@ public class Icon extends UIObject { DOM.setElementProperty(getElement(), "src", uri); myUri = uidlUri; } + + setAlternateText(uidlAlt); } + /** + * Sets the alternate text for the icon. + * + * @param uidlAlt + * with the alternate text. Must be non null + */ + public void setAlternateText(String uidlAlt) { + assert uidlAlt != null : "Alternate text must be non null"; + DOM.setElementProperty(getElement(), "alt", uidlAlt); + } } diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index dc20c27837..10fa0dff16 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -291,7 +291,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED), uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION), uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE), - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON)); + uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON), + uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT)); setClosable(uidl.hasAttribute("closable")); diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java index 2c85b279cd..3ba1861b6f 100644 --- a/server/src/com/vaadin/ui/TabSheet.java +++ b/server/src/com/vaadin/ui/TabSheet.java @@ -268,7 +268,34 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @return the created {@link Tab} */ public Tab addTab(Component c, String caption, Resource icon) { - return addTab(c, caption, icon, components.size()); + return addTab(c, caption, icon, "", components.size()); + } + + /** + * Adds a new tab into TabSheet. + * + * The first tab added to a tab sheet is automatically selected and a tab + * selection event is fired. + * + * If the component is already present in the tab sheet, changes its caption + * and icon and icon alternate text and returns the corresponding (old) tab, + * preserving other tab metadata. + * + * @param c + * the component to be added onto tab - should not be null. + * @param caption + * the caption to be set for the component and used rendered in + * tab bar + * @param icon + * the icon to be set for the component and used rendered in tab + * bar + * @param iconAltText + * the alternate text for the icon + * @return the created {@link Tab} + */ + public Tab addTab(Component c, String caption, Resource icon, + String iconAltText) { + return addTab(c, caption, icon, iconAltText, components.size()); } /** @@ -294,12 +321,41 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, * @return the created {@link Tab} */ public Tab addTab(Component c, String caption, Resource icon, int position) { + return addTab(c, caption, icon, "", position); + } + + /** + * Adds a new tab into TabSheet. + * + * The first tab added to a tab sheet is automatically selected and a tab + * selection event is fired. + * + * If the component is already present in the tab sheet, changes its caption + * and icon and icon alternate text and returns the corresponding (old) tab, + * preserving other tab metadata like the position. + * + * @param c + * the component to be added onto tab - should not be null. + * @param caption + * the caption to be set for the component and used rendered in + * tab bar + * @param icon + * the icon to be set for the component and used rendered in tab + * bar + * @param iconAltText + * the alternate text for the icon + * @param position + * the position at where the the tab should be added. + * @return the created {@link Tab} + */ + public Tab addTab(Component c, String caption, Resource icon, + String iconAltText, int position) { if (c == null) { return null; } else if (tabs.containsKey(c)) { Tab tab = tabs.get(c); tab.setCaption(caption); - tab.setIcon(icon); + tab.setIcon(icon, iconAltText); return tab; } else { components.add(position, c); @@ -371,13 +427,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, final Component c = i.next(); String caption = null; Resource icon = null; + String iconAltText = ""; if (TabSheet.class.isAssignableFrom(source.getClass())) { Tab tab = ((TabSheet) source).getTab(c); caption = tab.getCaption(); icon = tab.getIcon(); + iconAltText = tab.getIconAltText(); } source.removeComponent(c); - addTab(c, caption, icon); + addTab(c, caption, icon, iconAltText); } } @@ -429,6 +487,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, if (icon != null) { target.addAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON, icon); + target.addAttribute( + TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT, + tab.getIconAltText()); } final String caption = tab.getCaption(); if (caption != null && !caption.isEmpty()) { @@ -936,6 +997,27 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, */ public void setIcon(Resource icon); + /** + * Sets the icon and alt text for the tab. + * + * @param icon + * the icon to set + */ + public void setIcon(Resource icon, String iconAltText); + + /** + * Gets the icon alt text for the tab. + */ + public String getIconAltText(); + + /** + * Sets the icon alt text for the tab. + * + * @param iconAltText + * the icon to set + */ + public void setIconAltText(String iconAltText); + /** * Gets the description for the tab. The description can be used to * briefly describe the state of the tab to the user, and is typically @@ -1053,6 +1135,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, private ErrorMessage componentError = null; private String styleName; private String id; + private String iconAltText = ""; public TabSheetTabImpl(String caption, Resource icon) { if (caption == null) { @@ -1084,7 +1167,24 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, @Override public void setIcon(Resource icon) { + setIcon(icon, ""); + } + + @Override + public void setIcon(Resource icon, String iconAltText) { this.icon = icon; + this.iconAltText = iconAltText; + markAsDirty(); + } + + @Override + public String getIconAltText() { + return iconAltText; + } + + @Override + public void setIconAltText(String iconAltText) { + this.iconAltText = iconAltText; markAsDirty(); } @@ -1344,7 +1444,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, */ private static void copyTabMetadata(Tab from, Tab to) { to.setCaption(from.getCaption()); - to.setIcon(from.getIcon()); + to.setIcon(from.getIcon(), from.getIconAltText()); to.setDescription(from.getDescription()); to.setVisible(from.isVisible()); to.setEnabled(from.isEnabled()); diff --git a/shared/src/com/vaadin/shared/ui/tabsheet/TabsheetBaseConstants.java b/shared/src/com/vaadin/shared/ui/tabsheet/TabsheetBaseConstants.java index 7eb23a9887..b7f337a5a8 100644 --- a/shared/src/com/vaadin/shared/ui/tabsheet/TabsheetBaseConstants.java +++ b/shared/src/com/vaadin/shared/ui/tabsheet/TabsheetBaseConstants.java @@ -29,5 +29,7 @@ public class TabsheetBaseConstants implements Serializable { public static final String ATTRIBUTE_TAB_CAPTION = "caption"; @Deprecated public static final String ATTRIBUTE_TAB_ICON = "icon"; + @Deprecated + public static final String ATTRIBUTE_TAB_ICON_ALT = "iconalt"; } diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.html index 425da11af4..6876497a1f 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.html +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.html @@ -21,6 +21,16 @@ + + assertAttribute + vaadin=runcomvaadintestscomponentstabsheetTabSheetIcons::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]@alt + iconalt1 + + + assertAttribute + vaadin=runcomvaadintestscomponentstabsheetTabSheetIcons::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[0]/domChild[0]/domChild[0]@alt + iconalt3 + diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.java index 5d814ec48f..ccdc4ecb38 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.java +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetIcons.java @@ -46,6 +46,8 @@ public class TabSheetIcons extends TestBase { for (Component c : tab) { tabsheet.addTab(c); + tabsheet.getTab(c).setIconAltText( + "iconalt" + tabsheet.getComponentCount()); } return tabsheet; diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetTest.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetTest.java index 6c39cdab73..56836037b7 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetTest.java +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetTest.java @@ -26,7 +26,7 @@ public class TabSheetTest extends @Override public void execute(T c, Integer value, Object data) { - c.getTab(value).setIcon((Resource) data); + c.getTab(value).setIcon((Resource) data, "tabicon"); } }; -- cgit v1.2.3 From cd9bd20c52423e9970d1945c374e4cbefff82569 Mon Sep 17 00:00:00 2001 From: Fabian Lange Date: Wed, 12 Jun 2013 16:08:48 +0200 Subject: Add getCacheTime(filename) to VaadinServlet (#11744) This implementation by default adheres to the GWT Pristine Caching rules and calculates 0 seconds for ".nocache." and 1 year for ".cache." filenames. All other filenames will use the value configured in the deployment configuration. By exposing this to a method, developers can implement custom naming schemes. Developers also can opt to set an expires header using this value. Change-Id: Ibc0d17d48d38bfa3bb28bdf3929ad314828be406 --- server/src/com/vaadin/server/VaadinServlet.java | 57 ++++++++++++++++++------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index a9eb42578e..43bcb3d394 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -670,21 +670,11 @@ public class VaadinServlet extends HttpServlet implements Constants { // Provide modification timestamp to the browser if it is known. if (lastModifiedTime > 0) { response.setDateHeader("Last-Modified", lastModifiedTime); - /* - * The browser is allowed to cache for 1 hour without checking if - * the file has changed. This forces browsers to fetch a new version - * when the Vaadin version is updated. This will cause more requests - * to the servlet than without this but for high volume sites the - * static files should never be served through the servlet. The - * cache timeout can be configured by setting the resourceCacheTime - * parameter in web.xml - */ - int resourceCacheTime = getService().getDeploymentConfiguration() - .getResourceCacheTime(); - String cacheControl = "max-age=" - + String.valueOf(resourceCacheTime); - if (filename.contains("nocache")) { - cacheControl = "public, max-age=0, must-revalidate"; + + String cacheControl = "public, max-age=0, must-revalidate"; + int resourceCacheTime = getCacheTime(filename); + if (resourceCacheTime > 0) { + cacheControl = "max-age=" + String.valueOf(resourceCacheTime); } response.setHeader("Cache-Control", cacheControl); } @@ -692,6 +682,43 @@ public class VaadinServlet extends HttpServlet implements Constants { writeStaticResourceResponse(request, response, resourceUrl); } + /** + * Calculates the cache lifetime for the given filename in seconds. By + * default filenames containing ".nocache." return 0, filenames containing + * ".cache." return one year, all other return the value defined in the + * web.xml using resourceCacheTime (defaults to 1 hour). + * + * @param filename + * @return cache lifetime for the given filename in seconds + */ + protected int getCacheTime(String filename) { + /* + * GWT conventions: + * + * - files containing .nocache. will not be cached. + * + * - files containing .cache. will be cached for one year. + * + * https://developers.google.com/web-toolkit/doc/latest/ + * DevGuideCompilingAndDebugging#perfect_caching + */ + if (filename.contains(".nocache.")) { + return 0; + } + if (filename.contains(".cache.")) { + return 60 * 60 * 24 * 365; + } + /* + * For all other files, the browser is allowed to cache for 1 hour + * without checking if the file has changed. This forces browsers to + * fetch a new version when the Vaadin version is updated. This will + * cause more requests to the servlet than without this but for high + * volume sites the static files should never be served through the + * servlet. + */ + return getService().getDeploymentConfiguration().getResourceCacheTime(); + } + /** * Writes the contents of the given resourceUrl in the response. Can be * overridden to add/modify response headers and similar. -- cgit v1.2.3 From 68be95e48405be0e5d54b3d8ddb66de454fcbfed Mon Sep 17 00:00:00 2001 From: Fabian Lange Date: Wed, 12 Jun 2013 21:21:31 +0200 Subject: Support uploading files larger 2GB via drag and drop and file input (#11947). Vaadin mostly just passes through the value from the Javascript File object on drag and drop. As per specification (http://www.w3.org/TR/file-upload/#blob) the size value can be "long". The size attribute of Html5File was already long, but this patch makes sure long values actually are transported correctly from the client side Transferable. Additionally, the file upload handling in FileUploadHandler was determining the length of the upload via the Servlet Spec getContentLength method of the request. However the spec was written at a time where 2GB were unthinkable. Luckily containers return this value by querying the Content-Length header, which can transport values of "long". So instead of using the Servlet Spec method, FileUploadHandler evaluates the header itself. AbstractStreamingEvent already was supporting "long" values. For Drag&Drop, the file size needs to be serialized as double, because long is not supported in JSNI. Change-Id: I606fca7430c65c20144793fa011cb2f6ee3a0415 --- client/src/com/vaadin/client/ui/dd/VHtml5File.java | 8 ++++++- .../server/communication/FileUploadHandler.java | 25 ++++++++++++++++------ server/src/com/vaadin/ui/DragAndDropWrapper.java | 2 +- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/client/src/com/vaadin/client/ui/dd/VHtml5File.java b/client/src/com/vaadin/client/ui/dd/VHtml5File.java index 4b36e7fd1b..c4ad615fbd 100644 --- a/client/src/com/vaadin/client/ui/dd/VHtml5File.java +++ b/client/src/com/vaadin/client/ui/dd/VHtml5File.java @@ -35,7 +35,13 @@ public class VHtml5File extends JavaScriptObject { return this.type; }-*/; - public native final int getSize() + /* + * Browser implementations support files >2GB dropped and report the value + * as long. Due to JSNI limitations this value needs to be sent as double + * and then cast back to a long value. + * www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#important + */ + public native final double getSize() /*-{ return this.size ? this.size : 0; }-*/; diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java index e9569d45a1..8014dba12d 100644 --- a/server/src/com/vaadin/server/communication/FileUploadHandler.java +++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java @@ -271,7 +271,7 @@ public class FileUploadHandler implements RequestHandler { // if boundary string does not exist, the posted file is from // XHR2.post(File) doHandleXhrFilePost(session, request, response, streamVariable, - variableName, source, request.getContentLength()); + variableName, source, getContentLength(request)); } return true; } @@ -323,7 +323,7 @@ public class FileUploadHandler implements RequestHandler { final InputStream inputStream = request.getInputStream(); - int contentLength = request.getContentLength(); + long contentLength = getContentLength(request); boolean atStart = false; boolean firstFileFieldFound = false; @@ -390,9 +390,22 @@ public class FileUploadHandler implements RequestHandler { } + /* + * request.getContentLength() is limited to "int" by the Servlet + * specification. To support larger file uploads manually evaluate the + * Content-Length header which can contain long values. + */ + private long getContentLength(VaadinRequest request) { + try { + return Long.parseLong(request.getHeader("Content-Length")); + } catch (NumberFormatException e) { + return -1l; + } + } + private void handleFileUploadValidationAndData(VaadinSession session, InputStream inputStream, StreamVariable streamVariable, - String filename, String mimeType, int contentLength, + String filename, String mimeType, long contentLength, ClientConnector connector, String variableName) throws UploadException { session.lock(); @@ -461,7 +474,7 @@ public class FileUploadHandler implements RequestHandler { protected void doHandleXhrFilePost(VaadinSession session, VaadinRequest request, VaadinResponse response, StreamVariable streamVariable, String variableName, - ClientConnector owner, int contentLength) throws IOException { + ClientConnector owner, long contentLength) throws IOException { // These are unknown in filexhr ATM, maybe add to Accept header that // is accessible in portlets @@ -491,7 +504,7 @@ public class FileUploadHandler implements RequestHandler { */ protected final boolean streamToReceiver(VaadinSession session, final InputStream in, StreamVariable streamVariable, - String filename, String type, int contentLength) + String filename, String type, long contentLength) throws UploadException { if (streamVariable == null) { throw new IllegalStateException( @@ -499,7 +512,7 @@ public class FileUploadHandler implements RequestHandler { } OutputStream out = null; - int totalBytes = 0; + long totalBytes = 0; StreamingStartEventImpl startedEvent = new StreamingStartEventImpl( filename, type, contentLength); try { diff --git a/server/src/com/vaadin/ui/DragAndDropWrapper.java b/server/src/com/vaadin/ui/DragAndDropWrapper.java index 6c6aa3c3f4..7a2cfb82e4 100644 --- a/server/src/com/vaadin/ui/DragAndDropWrapper.java +++ b/server/src/com/vaadin/ui/DragAndDropWrapper.java @@ -54,7 +54,7 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, for (int i = 0; i < fc; i++) { Html5File file = new Html5File( (String) rawVariables.get("fn" + i), // name - (Integer) rawVariables.get("fs" + i), // size + ((Double) rawVariables.get("fs" + i)).longValue(), // size (String) rawVariables.get("ft" + i)); // mime String id = (String) rawVariables.get("fi" + i); files[i] = file; -- cgit v1.2.3 From 02692163274392fe1b0a33a5206390df0824703d Mon Sep 17 00:00:00 2001 From: Fabian Lange Date: Wed, 12 Jun 2013 16:48:00 +0200 Subject: simplified isStaticResourceRequest and improved its performance (#11758) The previous implementation did first check if the PathInfo was empty (null returned). This is almost never the case in reality. But if it happens, then the RequestURI would never contain contextRoot+"/VAADIN/". Next it checked that contextUri was not null, and checked if the Uri started with "/VAADIN/". This only would have worked in case the context root would have been "". The next case checked was if the Uri starts with contextRoot+"/VAADIN/". This is what you normally want to check. The only valid other case from before (contextRoot == "") is also covered by this line. What you would have seen in normal deployments is: * First if exit only for first request (http://demo.vaadin.com/sampler/) (and sometimes not even that depending on trailing slash config) * Second exit only on no context root deployments (getContextRoot() returns "") * Last exit in all other cases Additionally, the existing implementation does not work correctly for the case getContextRoot would return null (which thankfully no container does). Change-Id: I500e0c5eb0ac2bfa0b32af91800b2f7f303485ff --- server/src/com/vaadin/server/VaadinServlet.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index 43bcb3d394..6f45ee4930 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -1006,20 +1006,8 @@ public class VaadinServlet extends HttpServlet implements Constants { } protected boolean isStaticResourceRequest(HttpServletRequest request) { - String pathInfo = request.getPathInfo(); - if (pathInfo == null) { - return false; - } - - if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { - return true; - } - - return false; + return request.getRequestURI().startsWith( + request.getContextPath() + "/VAADIN/"); } /** -- cgit v1.2.3 From 104e472d21e92c3023abc4cfe96e67861424927c Mon Sep 17 00:00:00 2001 From: Joonas Lehtinen Date: Fri, 28 Jun 2013 18:19:47 +0300 Subject: Fixes @PreserveOnRefresh losing page title on refresh #11054 Moves Page title to PageState instead of using RPC for changing the title on Page.setTitle(). Change-Id: I8e5ab2064c04235503fb2531f4cdbb108530ac7e --- .../src/com/vaadin/client/ui/ui/UIConnector.java | 9 +++--- server/src/com/vaadin/server/Page.java | 2 +- .../src/com/vaadin/shared/ui/ui/PageClientRpc.java | 2 -- shared/src/com/vaadin/shared/ui/ui/PageState.java | 5 +++ .../application/RefreshStatePreserveTitle.html | 36 ++++++++++++++++++++++ .../application/RefreshStatePreserveTitle.java | 30 ++++++++++++++++++ 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.html create mode 100644 uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.java diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index 45b0a7ab9d..460cde5522 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -100,10 +100,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector protected void init() { super.init(); registerRpc(PageClientRpc.class, new PageClientRpc() { - @Override - public void setTitle(String title) { - com.google.gwt.user.client.Window.setTitle(title); - } @Override public void reload() { @@ -644,6 +640,11 @@ public class UIConnector extends AbstractSingleComponentContainerConnector configurePolling(); } + if (stateChangeEvent.hasPropertyChanged("title")) { + com.google.gwt.user.client.Window + .setTitle(getState().pageState.title); + } + if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) { getConnection().setPushEnabled( getState().pushConfiguration.mode.isEnabled()); diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java index 4dd7d34e38..4d7d2c10c5 100644 --- a/server/src/com/vaadin/server/Page.java +++ b/server/src/com/vaadin/server/Page.java @@ -1079,7 +1079,7 @@ public class Page implements Serializable { * the new page title to set */ public void setTitle(String title) { - uI.getRpcProxy(PageClientRpc.class).setTitle(title); + getState(true).title = title; } /** diff --git a/shared/src/com/vaadin/shared/ui/ui/PageClientRpc.java b/shared/src/com/vaadin/shared/ui/ui/PageClientRpc.java index eb847bacd0..76a3fbfdb6 100644 --- a/shared/src/com/vaadin/shared/ui/ui/PageClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/ui/PageClientRpc.java @@ -20,8 +20,6 @@ import com.vaadin.shared.communication.ClientRpc; public interface PageClientRpc extends ClientRpc { - public void setTitle(String title); - public void reload(); } diff --git a/shared/src/com/vaadin/shared/ui/ui/PageState.java b/shared/src/com/vaadin/shared/ui/ui/PageState.java index 0b51eb4bba..ded73d16d5 100644 --- a/shared/src/com/vaadin/shared/ui/ui/PageState.java +++ b/shared/src/com/vaadin/shared/ui/ui/PageState.java @@ -30,4 +30,9 @@ public class PageState implements Serializable { * True if the page has browser window resize listeners. */ public boolean hasResizeListeners = false; + + /** + * Non-null if the title is set. + */ + public String title = null; } \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.html b/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.html new file mode 100644 index 0000000000..f366054f45 --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.html @@ -0,0 +1,36 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.application.RefreshStatePreserveTitle?restartApplication
assertTitleTEST
open/run/com.vaadin.tests.application.RefreshStatePreserveTitle
assertTitleTEST
+ + diff --git a/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.java b/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.java new file mode 100644 index 0000000000..88b3a9b9f4 --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/RefreshStatePreserveTitle.java @@ -0,0 +1,30 @@ +package com.vaadin.tests.application; + +import com.vaadin.annotations.PreserveOnRefresh; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.util.Log; +import com.vaadin.ui.Label; + +@PreserveOnRefresh +public class RefreshStatePreserveTitle extends AbstractTestUI { + + private Log log = new Log(5); + + @Override + protected void setup(VaadinRequest request) { + getPage().setTitle("TEST"); + addComponent(new Label( + "Refresh the page and observe that window title 'TEST' is lost.")); + } + + @Override + protected String getTestDescription() { + return "Refreshing the browser window should preserve the window title"; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(11054); + } +} \ No newline at end of file -- cgit v1.2.3 From e45e036598d6efcf26ba1d2c27b4eeadcb32a9b9 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Fri, 14 Jun 2013 17:08:38 +0300 Subject: Prevent to exit a Window with the tab key (#11874) Change-Id: Icd12ec6e2eac626ad493707dfa8288d620bb9bb7 --- client/src/com/vaadin/client/ui/VWindow.java | 142 +++++++++++++++++++++ .../vaadin/client/ui/window/WindowConnector.java | 5 +- server/src/com/vaadin/ui/Window.java | 77 +++++++++++ .../com/vaadin/shared/ui/window/WindowState.java | 3 + .../components/window/ExtraWindowShownWaiAria.java | 55 ++++++-- 5 files changed, 270 insertions(+), 12 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 1756c619a7..2c85497707 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -25,6 +25,7 @@ import com.google.gwt.aria.client.RelevantValue; import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; @@ -39,10 +40,13 @@ import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; @@ -152,6 +156,18 @@ public class VWindow extends VWindowOverlay implements private String assistivePrefix; private String assistivePostfix; + private Element topTabStop; + private Element bottomTabStop; + + private NativePreviewHandler topEventBlocker; + private NativePreviewHandler bottomEventBlocker; + + private HandlerRegistration topBlockerRegistration; + private HandlerRegistration bottomBlockerRegistration; + + // Prevents leaving the window with the Tab key when true + private boolean doTabStop; + /** * If centered (via UIDL), the window should stay in the centered -mode * until a position is received from the server, or the user moves or @@ -211,6 +227,12 @@ public class VWindow extends VWindowOverlay implements * window is open. */ getApplicationConnection().getUIConnector().getWidget().storeFocus(); + + /* + * When this window gets reattached, set the tabstop to the previous + * state. + */ + setTabStopEnabled(doTabStop); } @Override @@ -225,6 +247,18 @@ public class VWindow extends VWindowOverlay implements */ getApplicationConnection().getUIConnector().getWidget() .focusStoredElement(); + + removeTabBlockHandlers(); + } + + private void removeTabBlockHandlers() { + if (topBlockerRegistration != null) { + topBlockerRegistration.removeHandler(); + topBlockerRegistration = null; + + bottomBlockerRegistration.removeHandler(); + bottomBlockerRegistration = null; + } } public void bringToFront() { @@ -290,6 +324,9 @@ public class VWindow extends VWindowOverlay implements protected void constructDOM() { setStyleName(CLASSNAME); + topTabStop = DOM.createDiv(); + DOM.setElementAttribute(topTabStop, "tabindex", "0"); + header = DOM.createDiv(); DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader"); headerText = DOM.createDiv(); @@ -309,15 +346,20 @@ public class VWindow extends VWindowOverlay implements DOM.setElementAttribute(closeBox, "tabindex", "0"); DOM.appendChild(footer, resizeBox); + bottomTabStop = DOM.createDiv(); + DOM.setElementAttribute(bottomTabStop, "tabindex", "0"); + wrapper = DOM.createDiv(); DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap"); + DOM.appendChild(wrapper, topTabStop); DOM.appendChild(wrapper, header); DOM.appendChild(wrapper, maximizeRestoreBox); DOM.appendChild(wrapper, closeBox); DOM.appendChild(header, headerText); DOM.appendChild(wrapper, contents); DOM.appendChild(wrapper, footer); + DOM.appendChild(wrapper, bottomTabStop); DOM.appendChild(super.getContainerElement(), wrapper); sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS @@ -338,6 +380,83 @@ public class VWindow extends VWindowOverlay implements AriaHelper.ensureHasId(headerText); Roles.getDialogRole().setAriaLabelledbyProperty(getElement(), Id.of(headerText)); + + // Handlers to Prevent tab to leave the window + topEventBlocker = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (nativeEvent.getEventTarget().cast() == topTabStop + && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB + && nativeEvent.getShiftKey()) { + nativeEvent.preventDefault(); + } + } + }; + + bottomEventBlocker = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (nativeEvent.getEventTarget().cast() == bottomTabStop + && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB + && !nativeEvent.getShiftKey()) { + nativeEvent.preventDefault(); + } + } + }; + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + *

+ * This message is not visible on the screen. + * + * @param topMessage + * String provided when the user navigates with Shift-Tab keys to + * the top of the window + */ + public void setTabStopTopAssistiveText(String topMessage) { + Roles.getNoteRole().setAriaLabelProperty(topTabStop, topMessage); + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + *

+ * This message is not visible on the screen. + * + * @param bottomMessage + * String provided when the user navigates with the Tab key to + * the bottom of the window + */ + public void setTabStopBottomAssistiveText(String bottomMessage) { + Roles.getNoteRole().setAriaLabelProperty(bottomTabStop, bottomMessage); + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + * + * @return the top message + */ + public String getTabStopTopAssistiveText() { + return Roles.getNoteRole().getAriaLabelProperty(topTabStop); + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + * + * @return the bottom message + */ + public String getTabStopBottomAssistiveText() { + return Roles.getNoteRole().getAriaLabelProperty(bottomTabStop); } /** @@ -1203,4 +1322,27 @@ public class VWindow extends VWindowOverlay implements Roles.getDialogRole().set(getElement()); } } + + /** + * Registers the handlers that prevent to leave the window using the + * Tab-key. + * + * @param doTabStop + * true to prevent leaving the window, false to allow leaving the + * window + */ + public void setTabStopEnabled(boolean doTabStop) { + this.doTabStop = doTabStop; + + if (doTabStop) { + if (topBlockerRegistration == null) { + topBlockerRegistration = Event + .addNativePreviewHandler(topEventBlocker); + bottomBlockerRegistration = Event + .addNativePreviewHandler(bottomEventBlocker); + } + } else { + removeTabBlockHandlers(); + } + } } diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index ea1073dd18..464ab386c1 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -301,9 +301,12 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector window.setCaption(state.caption, iconURL); window.setWaiAriaRole(getState().role); - window.setAssistiveDescription(state.contentDescription); + window.setTabStopEnabled(getState().assistiveTabStop); + window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText); + window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText); + clickEventHandler.handleEventHandlerRegistration(); window.immediate = state.immediate; diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 980e96c384..658c821f88 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -1100,4 +1100,81 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public WindowRole getAssistiveRole() { return getState().role; } + + /** + * Set if it should be prevented to set the focus to a component outside the + * window with the tab key. + *

+ * This is meant to help users of assistive devices to not leaving the + * window unintentionally. + * + * @param tabStop + * true to keep the focus inside the window when reaching the top + * or bottom, false (default) to allow leaving the window + */ + public void setTabStopEnabled(boolean tabStop) { + getState().assistiveTabStop = tabStop; + } + + /** + * Get if it is prevented to leave a window with the tab key. + * + * @return true when the focus is limited to inside the window, false when + * focus can leave the window + */ + public boolean isTabStopEnabled() { + return getState().assistiveTabStop; + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + *

+ * This message is not visible on the screen. + * + * @param topMessage + * String provided when the user navigates with Shift-Tab keys to + * the top of the window + */ + public void setTabStopTopAssistiveText(String topMessage) { + getState().assistiveTabStopTopText = topMessage; + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + *

+ * This message is not visible on the screen. + * + * @param bottomMessage + * String provided when the user navigates with the Tab key to + * the bottom of the window + */ + public void setTabStopBottomAssistiveText(String bottomMessage) { + getState().assistiveTabStopBottomText = bottomMessage; + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + * + * @return the top message + */ + public String getTabStopTopAssistiveText() { + return getState().assistiveTabStopTopText; + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + * + * @return the bottom message + */ + public String getTabStopBottomAssistiveText() { + return getState().assistiveTabStopBottomText; + } } diff --git a/shared/src/com/vaadin/shared/ui/window/WindowState.java b/shared/src/com/vaadin/shared/ui/window/WindowState.java index fa430f6c9c..55a9b3ec55 100644 --- a/shared/src/com/vaadin/shared/ui/window/WindowState.java +++ b/shared/src/com/vaadin/shared/ui/window/WindowState.java @@ -43,4 +43,7 @@ public class WindowState extends PanelState { public String assistivePostfix = ""; public Connector[] contentDescription; public WindowRole role = WindowRole.DIALOG; + public boolean assistiveTabStop = false; + public String assistiveTabStopTopText = "Top of dialog"; + public String assistiveTabStopBottomText = "Bottom of Dialog"; } \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java index b7c929120d..39989926e7 100644 --- a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java +++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java @@ -1,8 +1,9 @@ package com.vaadin.tests.components.window; import com.vaadin.server.ThemeResource; +import com.vaadin.server.VaadinRequest; import com.vaadin.shared.ui.window.WindowState.WindowRole; -import com.vaadin.tests.components.TestBase; +import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.CheckBox; @@ -12,16 +13,25 @@ import com.vaadin.ui.Label; import com.vaadin.ui.TextField; import com.vaadin.ui.Window; -public class ExtraWindowShownWaiAria extends TestBase { +public class ExtraWindowShownWaiAria extends AbstractTestUI { @Override - protected void setup() { + protected void setup(VaadinRequest request) { final CheckBox modal = new CheckBox("Modal dialog"); + modal.setTabIndex(7); final CheckBox additionalDescription = new CheckBox( "Additional Description"); + final CheckBox tabStop = new CheckBox( + "Prevent leaving window with Tab key"); + final CheckBox tabOrder = new CheckBox("Change Taborder"); final TextField prefix = new TextField("Prefix: "); final TextField postfix = new TextField("Postfix: "); + final TextField topTabStopMessage = new TextField( + "Top Tab Stop Message"); + final TextField bottomTabStopMessage = new TextField( + "Bottom Tab Stop Message"); + Button simple = new Button("Open Alert Dialog", new Button.ClickListener() { @@ -50,13 +60,20 @@ public class ExtraWindowShownWaiAria extends TestBase { description2); } - layout.addComponent(new Button("Close", + w.setTabStopEnabled(tabStop.getValue()); + w.setTabStopTopAssistiveText(topTabStopMessage + .getValue()); + w.setTabStopBottomAssistiveText(bottomTabStopMessage + .getValue()); + + Button close = new Button("Close", new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { w.close(); } - })); + }); + layout.addComponent(close); Button iconButton = new Button("A button with icon"); iconButton.setIcon(new ThemeResource( "../runo/icons/16/ok.png")); @@ -64,6 +81,10 @@ public class ExtraWindowShownWaiAria extends TestBase { event.getButton().getUI().addWindow(w); iconButton.focus(); + + if (tabOrder.getValue()) { + close.setTabIndex(5); + } } }); @@ -96,6 +117,12 @@ public class ExtraWindowShownWaiAria extends TestBase { description2); } + w.setTabStopEnabled(tabStop.getValue()); + w.setTabStopTopAssistiveText(topTabStopMessage + .getValue()); + w.setTabStopBottomAssistiveText(bottomTabStopMessage + .getValue()); + TextField name = new TextField("Name:"); form.addComponent(name); @@ -112,28 +139,34 @@ public class ExtraWindowShownWaiAria extends TestBase { event.getButton().getUI().addWindow(w); name.focus(); + + if (tabOrder.getValue()) { + name.setTabIndex(5); + } } }); getLayout().addComponent(complex); getLayout().addComponent(modal); getLayout().addComponent(additionalDescription); + getLayout().addComponent(tabStop); + getLayout().addComponent(tabOrder); getLayout().addComponent(prefix); getLayout().addComponent(postfix); + getLayout().addComponent(topTabStopMessage); + getLayout().addComponent(bottomTabStopMessage); + } @Override - protected String getDescription() { - // TODO Auto-generated method stub - return null; + protected String getTestDescription() { + return "Test for WAI-ARIA implementation"; } @Override protected Integer getTicketNumber() { - // TODO Auto-generated method stub - return null; + return 11821; } - } -- cgit v1.2.3 From c44ca1270523c544b9aedabe7f9780a5e6862859 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Mon, 24 Jun 2013 13:55:55 +0300 Subject: Tabsheet should not immediately select a tab when changing tab using keyboard (#11823) Adds the necessary WAI-ARIA roles to the component (#11827) Change-Id: Ie1ed40227679e3497971b834919713614a8cc23e --- .../VAADIN/themes/base/tabsheet/tabsheet.scss | 6 + client/src/com/vaadin/client/VTooltip.java | 11 ++ client/src/com/vaadin/client/ui/VTabsheet.java | 171 ++++++++++++++++++--- client/src/com/vaadin/client/ui/VTabsheetBase.java | 2 + server/src/com/vaadin/ui/TabSheet.java | 33 ++++ .../components/tabsheet/TabKeyboardNavigation.html | 52 ++++++- .../tabsheet/TabKeyboardNavigationWaiAria.java | 86 +++++++++++ 7 files changed, 340 insertions(+), 21 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationWaiAria.java diff --git a/WebContent/VAADIN/themes/base/tabsheet/tabsheet.scss b/WebContent/VAADIN/themes/base/tabsheet/tabsheet.scss index 1a799814c1..14def56ab5 100644 --- a/WebContent/VAADIN/themes/base/tabsheet/tabsheet.scss +++ b/WebContent/VAADIN/themes/base/tabsheet/tabsheet.scss @@ -106,6 +106,12 @@ .#{$primaryStyleName}-tabitem-selected .v-caption { cursor: default; } +.#{$primaryStyleName}-tabitem-focus .v-captiontext { + text-decoration: underline; +} +.#{$primaryStyleName}-tabitem-selected.#{$primaryStyleName}-tabitem-focus .v-captiontext { + text-decoration: inherit; +} .#{$primaryStyleName}-content { border: 1px solid #aaa; /* Vertical borders are not supported, use v-tabsheet-tabcontainer and v-tabsheet-deco to present these borders */ diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java index 6191821988..57e72aedfc 100644 --- a/client/src/com/vaadin/client/VTooltip.java +++ b/client/src/com/vaadin/client/VTooltip.java @@ -93,6 +93,17 @@ public class VTooltip extends VOverlay { Roles.getTooltipRole().setAriaHiddenState(layoutElement, true); } + /** + * Show the tooltip with the provided info for assistive devices. + * + * @param info + * with the content of the tooltip + */ + public void showAssistive(TooltipInfo info) { + updatePosition(null, true); + show(info); + } + /** * Show a popup containing the information in the "info" tooltip * diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index 10fa0dff16..729a8c5123 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -19,6 +19,9 @@ package com.vaadin.client.ui; import java.util.Iterator; import java.util.List; +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.aria.client.SelectedValue; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style; @@ -55,6 +58,7 @@ import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; import com.vaadin.client.VCaption; +import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.ComponentStateUtil; @@ -94,12 +98,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, + "-selected"; private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME + "-first"; + private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME + + "-focus"; + private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME + + "-first"; private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME + "-disabled"; private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME + "-selected"; + private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME + + "-focus"; private TabCaption tabCaption; Element td = getElement(); @@ -119,6 +129,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, this.tabBar = tabBar; setStyleName(td, TD_CLASSNAME); + Roles.getTabRole().set(getElement()); + Roles.getTabRole().setAriaSelectedState(getElement(), + SelectedValue.FALSE); + div = DOM.createDiv(); focusImpl.setTabIndex(td, -1); setStyleName(div, DIV_CLASSNAME); @@ -129,6 +143,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable, .getApplicationConnection()); add(tabCaption); + Roles.getTabRole().setAriaLabelledbyProperty(getElement(), + Id.of(tabCaption.getElement())); + addFocusHandler(getTabsheet()); addBlurHandler(getTabsheet()); addKeyDownHandler(getTabsheet()); @@ -140,6 +157,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void setHiddenOnServer(boolean hiddenOnServer) { this.hiddenOnServer = hiddenOnServer; + Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer); } @Override @@ -154,6 +172,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void setEnabledOnServer(boolean enabled) { enabledOnServer = enabled; + Roles.getTabRole().setAriaDisabledState(getElement(), !enabled); + setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); if (!enabled) { focusImpl.setTabIndex(td, -1); @@ -177,10 +197,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, * true if the Tab is the first visible Tab */ public void setStyleNames(boolean selected, boolean first) { + setStyleNames(selected, first, false); + } + + public void setStyleNames(boolean selected, boolean first, + boolean keyboardFocus) { setStyleName(td, TD_FIRST_CLASSNAME, first); setStyleName(td, TD_SELECTED_CLASSNAME, selected); setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); setStyleName(div, DIV_SELECTED_CLASSNAME, selected); + setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus); + setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first); + setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus); } public void setTabulatorIndex(int tabIndex) { @@ -260,6 +288,23 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void blur() { focusImpl.blur(td); } + + public boolean hasTooltip() { + return tabCaption.getTooltipInfo() != null; + } + + public TooltipInfo getTooltipInfo() { + return tabCaption.getTooltipInfo(); + } + + public void setAssistiveDescription(String descriptionId) { + Roles.getTablistRole().setAriaDescribedbyProperty(getElement(), + Id.of(descriptionId)); + } + + public void removeAssistiveDescription() { + Roles.getTablistRole().removeAriaDescribedbyProperty(getElement()); + } } public static class TabCaption extends VCaption { @@ -273,6 +318,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, super(client); this.client = client; this.tab = tab; + + AriaHelper.ensureHasId(getElement()); } public boolean updateCaption(UIDL uidl) { @@ -330,6 +377,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, closeButton.setInnerHTML("×"); closeButton .setClassName(VTabsheet.CLASSNAME + "-caption-close"); + + Roles.getTabRole().setAriaHiddenState(closeButton, true); + Roles.getTabRole().setAriaDisabledState(closeButton, true); + getElement().appendChild(closeButton); } else if (!closable && closeButton != null) { getElement().removeChild(closeButton); @@ -376,6 +427,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, this.tabsheet = tabsheet; Element el = DOM.createTable(); + Roles.getPresentationRole().set(el); + Element tbody = DOM.createTBody(); DOM.appendChild(el, tbody); DOM.appendChild(tbody, tr); @@ -432,11 +485,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } int index = getWidgetIndex(caption.getParent()); - // IE needs explicit focus() - if (BrowserInfo.get().isIE()) { - getTabsheet().focus(); - } - getTabsheet().onTabSelected(index); + + getTabsheet().focus(); + getTabsheet().loadTabSheet(index); } public VTabsheet getTabsheet() { @@ -454,13 +505,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, final Tab newSelected = getTab(index); final Tab oldSelected = selected; - newSelected.setStyleNames(true, isFirstVisibleTab(index)); + newSelected.setStyleNames(true, isFirstVisibleTab(index), true); newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); + Roles.getTabRole().setAriaSelectedState(newSelected.getElement(), + SelectedValue.TRUE); if (oldSelected != null && oldSelected != newSelected) { oldSelected.setStyleNames(false, isFirstVisibleTab(getWidgetIndex(oldSelected))); oldSelected.setTabulatorIndex(-1); + + Roles.getTabRole().setAriaSelectedState( + oldSelected.getElement(), SelectedValue.FALSE); } // Update the field holding the currently selected tab @@ -471,6 +527,23 @@ public class VTabsheet extends VTabsheetBase implements Focusable, getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); } + public void navigateTab(int fromIndex, int toIndex) { + Tab newNavigated = getTab(toIndex); + if (newNavigated == null) { + throw new IllegalArgumentException( + "Tab at provided index toIndex was not found"); + } + + Tab oldNavigated = getTab(fromIndex); + newNavigated.setStyleNames(newNavigated.equals(selected), + isFirstVisibleTab(toIndex), true); + + if (oldNavigated != null && fromIndex != toIndex) { + oldNavigated.setStyleNames(oldNavigated.equals(selected), + isFirstVisibleTab(fromIndex), false); + } + } + public void removeTab(int i) { Tab tab = getTab(i); if (tab == null) { @@ -603,7 +676,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * @return Whether the tab could be selected or not. */ - private boolean onTabSelected(final int tabIndex) { + private boolean canSelectTab(final int tabIndex) { Tab tab = tb.getTab(tabIndex); if (client == null || disabled || waitingForResponse) { return false; @@ -611,15 +684,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable, if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { return false; } - if (activeTabIndex != tabIndex) { + + // Note that we return true when tabIndex == activeTabIndex; the active + // tab could be selected, it's just a no-op. + return true; + } + + /** + * Load the content of a tab of the provided index. + * + * @param index + * of the tab to load + */ + public void loadTabSheet(int tabIndex) { + if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) { tb.selectTab(tabIndex); // If this TabSheet already has focus, set the new selected tab // as focused. if (focusedTab != null) { - focusedTab = tab; + focusedTab = tb.getTab(tabIndex); + focusedTab.focus(); } + activeTabIndex = tabIndex; + focusedTabIndex = tabIndex; + addStyleDependentName("loading"); // Hide the current contents so a loading indicator can be shown // instead @@ -631,9 +721,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, .toString(), true); waitingForResponse = true; } - // Note that we return true when tabIndex == activeTabIndex; the active - // tab could be selected, it's just a no-op. - return true; } public ApplicationConnection getApplicationConnection() { @@ -675,22 +762,29 @@ public class VTabsheet extends VTabsheetBase implements Focusable, DOM.setStyleAttribute(getElement(), "overflow", "hidden"); tabs = DOM.createDiv(); DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + Roles.getTablistRole().set(tabs); scroller = DOM.createDiv(); + Roles.getTablistRole().setAriaHiddenState(scroller, true); DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); scrollerPrev = DOM.createButton(); + scrollerPrev.setTabIndex(-1); DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + "Prev"); + Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true); DOM.sinkEvents(scrollerPrev, Event.ONCLICK); scrollerNext = DOM.createButton(); + scrollerNext.setTabIndex(-1); DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + "Next"); + Roles.getTablistRole().setAriaHiddenState(scrollerNext, true); DOM.sinkEvents(scrollerNext, Event.ONCLICK); DOM.appendChild(getElement(), tabs); // Tabs tp.setStyleName(CLASSNAME + "-tabsheetpanel"); contentNode = DOM.createDiv(); + Roles.getTabpanelRole().set(contentNode); deco = DOM.createDiv(); @@ -1095,7 +1189,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, @Override public void onBlur(BlurEvent event) { + getApplicationConnection().getVTooltip().hideTooltip(); + if (focusedTab != null && event.getSource() instanceof Tab) { + focusedTab.removeAssistiveDescription(); focusedTab = null; if (client.hasEventListeners(this, EventId.BLUR)) { client.updateVariable(id, EventId.BLUR, "", true); @@ -1110,6 +1207,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, if (client.hasEventListeners(this, EventId.FOCUS)) { client.updateVariable(id, EventId.FOCUS, "", true); } + + if (focusedTab.hasTooltip()) { + focusedTab.setAssistiveDescription(getApplicationConnection() + .getVTooltip().getUniqueId()); + getApplicationConnection().getVTooltip().showAssistive( + focusedTab.getTooltipInfo()); + } } } @@ -1129,13 +1233,17 @@ public class VTabsheet extends VTabsheetBase implements Focusable, if (keycode == getPreviousTabKey()) { selectPreviousTab(); + event.stopPropagation(); } else if (keycode == getNextTabKey()) { selectNextTab(); + event.stopPropagation(); } else if (keycode == getCloseTabKey()) { Tab tab = tb.getTab(activeTabIndex); if (tab.isClosable()) { tab.onClose(); } + } else if (keycode == getSelectTabKey()) { + loadTabSheet(focusedTabIndex); } } } @@ -1148,6 +1256,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return KeyCodes.KEY_LEFT; } + protected int getSelectTabKey() { + return 32; // Space key + } + /** * @return The key code of the keyboard shortcut that selects the next tab * in a focused tabsheet. @@ -1165,18 +1277,27 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } private void selectPreviousTab() { - int newTabIndex = activeTabIndex; + int newTabIndex = focusedTabIndex; // Find the previous visible and enabled tab if any. do { newTabIndex--; - } while (newTabIndex >= 0 && !onTabSelected(newTabIndex)); + } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); if (newTabIndex >= 0) { - activeTabIndex = newTabIndex; + tb.navigateTab(focusedTabIndex, newTabIndex); + focusedTabIndex = newTabIndex; + + // If this TabSheet already has focus, set the new selected tab + // as focused. + if (focusedTab != null) { + focusedTab = tb.getTab(focusedTabIndex); + focusedTab.focus(); + } + if (isScrolledTabs()) { // Scroll until the new active tab is visible int newScrollerIndex = scrollerIndex; - while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft() + while (tb.getTab(focusedTabIndex).getAbsoluteLeft() < getAbsoluteLeft() && newScrollerIndex != -1) { newScrollerIndex = tb.scrollLeft(newScrollerIndex); } @@ -1187,18 +1308,28 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } private void selectNextTab() { - int newTabIndex = activeTabIndex; + int newTabIndex = focusedTabIndex; // Find the next visible and enabled tab if any. do { newTabIndex++; - } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex)); + } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); if (newTabIndex < getTabCount()) { - activeTabIndex = newTabIndex; + + tb.navigateTab(focusedTabIndex, newTabIndex); + focusedTabIndex = newTabIndex; + + // If this TabSheet already has focus, set the new selected tab + // as focused. + if (focusedTab != null) { + focusedTab = tb.getTab(focusedTabIndex); + focusedTab.focus(); + } + if (isClippedTabs()) { // Scroll until the new active tab is completely visible int newScrollerIndex = scrollerIndex; - while (isClipped(tb.getTab(activeTabIndex)) + while (isClipped(tb.getTab(focusedTabIndex)) && newScrollerIndex != -1) { newScrollerIndex = tb.scrollRight(newScrollerIndex); } diff --git a/client/src/com/vaadin/client/ui/VTabsheetBase.java b/client/src/com/vaadin/client/ui/VTabsheetBase.java index 0923248115..bcd8811c7d 100644 --- a/client/src/com/vaadin/client/ui/VTabsheetBase.java +++ b/client/src/com/vaadin/client/ui/VTabsheetBase.java @@ -42,6 +42,8 @@ public abstract class VTabsheetBase extends ComplexPanel { /** For internal use only. May be removed or replaced in the future. */ public int activeTabIndex = 0; /** For internal use only. May be removed or replaced in the future. */ + public int focusedTabIndex = 0; + /** For internal use only. May be removed or replaced in the future. */ public boolean disabled; /** For internal use only. May be removed or replaced in the future. */ public boolean readonly; diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java index 3ba1861b6f..a1f9e9dd26 100644 --- a/server/src/com/vaadin/ui/TabSheet.java +++ b/server/src/com/vaadin/ui/TabSheet.java @@ -615,6 +615,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, // connector if (selected != null) { selected.markAsDirtyRecursive(); + + Tab tab = getTab(c); + if (tab != null && tab.getDefaultFocusComponent() != null) { + tab.getDefaultFocusComponent().focus(); + } } } @@ -954,6 +959,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, */ public void setClosable(boolean closable); + /** + * Set the component that should automatically focused when the tab is + * selected. + * + * @param component + * the component to focus + */ + public void setDefaultFocusComponent(Focusable component); + + /** + * Get the component that should be automatically focused when the tab + * is selected. + * + * @return the focusable component + */ + public Focusable getDefaultFocusComponent(); + /** * Returns the enabled status for the tab. A disabled tab is shown as * such in the tab bar and cannot be selected. @@ -1136,6 +1158,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, private String styleName; private String id; private String iconAltText = ""; + private Focusable defaultFocus; public TabSheetTabImpl(String caption, Resource icon) { if (caption == null) { @@ -1188,6 +1211,16 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, markAsDirty(); } + @Override + public void setDefaultFocusComponent(Focusable defaultFocus) { + this.defaultFocus = defaultFocus; + } + + @Override + public Focusable getDefaultFocusComponent() { + return defaultFocus; + } + @Override public boolean isEnabled() { return enabled; diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html index 129061e69c..acf66fb27f 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html @@ -3,7 +3,7 @@ - + TabKeyboardNavigation @@ -26,6 +26,16 @@ vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] right + + assertText + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0] + Tab 1 + + + pressSpecialKey + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + enter + assertText vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0] @@ -46,6 +56,16 @@ vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] right + + assertText + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0] + Tab 2 + + + pressSpecialKey + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] + enter + assertText vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0] @@ -66,6 +86,16 @@ vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] right + + assertText + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0] + Tab 5 + + + pressSpecialKey + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + enter + assertText vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0] @@ -121,6 +151,16 @@ vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10] right + + assertText + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0] + Tab 9 + + + pressSpecialKey + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10] + enter + assertText vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0] @@ -151,6 +191,16 @@ vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1] left + + assertText + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0] + Tab 5 + + + pressSpecialKey + vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1] + enter + assertText vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0] diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationWaiAria.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationWaiAria.java new file mode 100644 index 0000000000..e394594176 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationWaiAria.java @@ -0,0 +1,86 @@ +package com.vaadin.tests.components.tabsheet; + +import java.util.ArrayList; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.Layout; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TabSheet.Tab; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +public class TabKeyboardNavigationWaiAria extends AbstractTestUI { + + int index = 1; + ArrayList tabs = new ArrayList(); + TabSheet ts = new TabSheet(); + + @Override + protected void setup(VaadinRequest request) { + ts.setWidth("500px"); + ts.setHeight("500px"); + + for (int i = 0; i < 5; ++i) { + addTab(); + } + + Button addTab = new Button("Add a tab", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + addTab(); + } + }); + Button focus = new Button("Focus tabsheet", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + ts.focus(); + } + }); + + addComponent(addTab); + addComponent(focus); + + addComponent(ts); + } + + @Override + protected String getTestDescription() { + return "The tab bar should be focusable and arrow keys should change focus for tabs. Space key selects a focused tab. The del key should close a tab if closable."; + } + + @Override + protected Integer getTicketNumber() { + return 11827; + } + + private Tab addTab() { + Layout content = new VerticalLayout(); + tabs.add(content); + + TextField field = new TextField("Tab " + index + " label"); + content.addComponent(field); + + Tab tab = ts.addTab(content, "Tab " + index, null); + + if (index == 2) { + tab.setClosable(true); + tab.setDescription("Tab 2 Tooltip"); + } + + if (index == 4) { + tab.setEnabled(false); + } + + if (index == 5) { + tab.setDefaultFocusComponent(field); + } + + index++; + return tab; + } +} -- cgit v1.2.3 From 5a0a9917f1021b69cc613eb7de4c5cf81ed5afc1 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 19 Jul 2013 15:02:20 +0300 Subject: Add ServiceDestroyListerner used by PushRequestHandler (#12251, #11878) Change-Id: Id6147bbfe8da7cd3e3f3744acf3ef92b8c63b37b --- .../src/com/vaadin/server/ServiceDestroyEvent.java | 50 +++++++++++++++++++++ .../com/vaadin/server/ServiceDestroyListener.java | 39 +++++++++++++++++ server/src/com/vaadin/server/VaadinPortlet.java | 6 +++ server/src/com/vaadin/server/VaadinService.java | 51 ++++++++++++++++++++++ server/src/com/vaadin/server/VaadinServlet.java | 11 +++++ .../server/communication/PushRequestHandler.java | 9 ++++ 6 files changed, 166 insertions(+) create mode 100644 server/src/com/vaadin/server/ServiceDestroyEvent.java create mode 100644 server/src/com/vaadin/server/ServiceDestroyListener.java diff --git a/server/src/com/vaadin/server/ServiceDestroyEvent.java b/server/src/com/vaadin/server/ServiceDestroyEvent.java new file mode 100644 index 0000000000..2ae4cc10af --- /dev/null +++ b/server/src/com/vaadin/server/ServiceDestroyEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2013 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.server; + +import java.util.EventObject; + +/** + * Event fired to {@link ServiceDestroyListener} when a {@link VaadinService} is + * being destroyed. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class ServiceDestroyEvent extends EventObject { + + /** + * Creates a new event for the given service. + * + * @param service + * the service being destroyed + */ + public ServiceDestroyEvent(VaadinService service) { + super(service); + } + + /* + * (non-Javadoc) + * + * @see java.util.EventObject#getSource() + */ + @Override + public VaadinService getSource() { + return (VaadinService) super.getSource(); + } + +} diff --git a/server/src/com/vaadin/server/ServiceDestroyListener.java b/server/src/com/vaadin/server/ServiceDestroyListener.java new file mode 100644 index 0000000000..ad4966dd58 --- /dev/null +++ b/server/src/com/vaadin/server/ServiceDestroyListener.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2013 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.server; + +import java.io.Serializable; + +/** + * Listener that gets notified when the {@link VaadinService} to which it has + * been registered is destroyed. + * + * @see VaadinService#addServiceDestroyListener(ServiceDestroyListener) + * @see VaadinService#removeServiceDestroyListener(ServiceDestroyListener) + * + * @since 7.2 + * @author Vaadin Ltd + */ +public interface ServiceDestroyListener extends Serializable { + /** + * Invoked when a service is destroyed + * + * @param event + * the event + */ + public void serviceDestroy(ServiceDestroyEvent event); +} diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java index d86e5e6507..adef90c45f 100644 --- a/server/src/com/vaadin/server/VaadinPortlet.java +++ b/server/src/com/vaadin/server/VaadinPortlet.java @@ -482,6 +482,12 @@ public class VaadinPortlet extends GenericPortlet implements Constants, handleRequest(request, response); } + @Override + public void destroy() { + super.destroy(); + getService().destroy(); + } + private static final Logger getLogger() { return Logger.getLogger(VaadinPortlet.class.getName()); } diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index cfbf2606ae..7baa57c6dc 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -41,7 +41,9 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; +import javax.portlet.Portlet; import javax.portlet.PortletContext; +import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletResponse; @@ -81,6 +83,10 @@ public abstract class VaadinService implements Serializable { .findMethod(SessionDestroyListener.class, "sessionDestroy", SessionDestroyEvent.class); + private static final Method SERVICE_DESTROY_METHOD = ReflectTools + .findMethod(ServiceDestroyListener.class, "serviceDestroy", + ServiceDestroyEvent.class); + /** * @deprecated As of 7.0. Only supported for {@link LegacyApplication}. */ @@ -1698,4 +1704,49 @@ public abstract class VaadinService implements Serializable { } } + /** + * Adds a service destroy listener that gets notified when this service is + * destroyed. + * + * @since 7.2 + * @param listener + * the service destroy listener to add + * + * @see #destroy() + * @see #removeServiceDestroyListener(ServiceDestroyListener) + * @see ServiceDestroyListener + */ + public void addServiceDestroyListener(ServiceDestroyListener listener) { + eventRouter.addListener(ServiceDestroyEvent.class, listener, + SERVICE_DESTROY_METHOD); + } + + /** + * Removes a service destroy listener that was previously added with + * {@link #addServiceDestroyListener(ServiceDestroyListener)}. + * + * @since 7.2 + * @param listener + * the service destroy listener to remove + */ + public void removeServiceDestroyListener(ServiceDestroyListener listener) { + eventRouter.removeListener(ServiceDestroyEvent.class, listener, + SERVICE_DESTROY_METHOD); + } + + /** + * Called when the servlet, portlet or similar for this service is being + * destroyed. After this method has been called, no more requests will be + * handled by this service. + * + * @see #addServiceDestroyListener(ServiceDestroyListener) + * @see Servlet#destroy() + * @see Portlet#destroy() + * + * @since 7.2 + */ + public void destroy() { + eventRouter.fireEvent(new ServiceDestroyEvent(this)); + } + } diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index b820b53953..d34cd3bf0e 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -1091,6 +1091,17 @@ public class VaadinServlet extends HttpServlet implements Constants { return u; } + /* + * (non-Javadoc) + * + * @see javax.servlet.GenericServlet#destroy() + */ + @Override + public void destroy() { + super.destroy(); + getService().destroy(); + } + /** * Escapes characters to html entities. An exception is made for some * "safe characters" to keep the text somewhat readable. diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java index 8d0da24896..74595322a0 100644 --- a/server/src/com/vaadin/server/communication/PushRequestHandler.java +++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java @@ -28,6 +28,8 @@ import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResponse; import com.vaadin.server.RequestHandler; +import com.vaadin.server.ServiceDestroyEvent; +import com.vaadin.server.ServiceDestroyListener; import com.vaadin.server.ServiceException; import com.vaadin.server.ServletPortletHelper; import com.vaadin.server.SessionExpiredHandler; @@ -63,6 +65,13 @@ public class PushRequestHandler implements RequestHandler, } }; + service.addServiceDestroyListener(new ServiceDestroyListener() { + @Override + public void serviceDestroy(ServiceDestroyEvent event) { + destroy(); + } + }); + pushHandler = new PushHandler(service); atmosphere.addAtmosphereHandler("/*", pushHandler); atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT, -- cgit v1.2.3 From 7cf124627e712d8e956d7e3f60e181230075daf9 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Mon, 22 Jul 2013 11:44:45 +0300 Subject: Clears the live area flag that Firefox sets by mistake (#12257) When selecting a Tab, screen reader reads out all the tabs otherwise Change-Id: I41d4f6c103daeec06be3828eb27ee479dc92477c --- client/src/com/vaadin/client/ui/VTabsheet.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index 729a8c5123..2a949c31af 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.LiveValue; import com.google.gwt.aria.client.Roles; import com.google.gwt.aria.client.SelectedValue; import com.google.gwt.core.client.Scheduler; @@ -763,6 +764,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, tabs = DOM.createDiv(); DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); Roles.getTablistRole().set(tabs); + Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF); scroller = DOM.createDiv(); Roles.getTablistRole().setAriaHiddenState(scroller, true); -- cgit v1.2.3 From 985d661e00dabaf5aa78bf67c3d2220e05cacb1e Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Wed, 24 Jul 2013 11:17:23 +0300 Subject: Make settting of assistive description of Window optional (#12276) Throws exception on server side for null parameter Adds client side getter for descriptions Change-Id: I7d6231eec52b584f674b97b9d0c8ee0b78b19297 --- client/src/com/vaadin/client/ui/VWindow.java | 16 ++++++++++ server/src/com/vaadin/ui/Window.java | 34 +++++++++++++--------- .../com/vaadin/shared/ui/window/WindowState.java | 2 +- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 2c85497707..7c0c875072 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -18,7 +18,9 @@ package com.vaadin.client.ui; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import com.google.gwt.aria.client.Id; import com.google.gwt.aria.client.RelevantValue; @@ -153,6 +155,7 @@ public class VWindow extends VWindowOverlay implements private boolean closable = true; + private Connector[] assistiveConnectors = new Connector[0]; private String assistivePrefix; private String assistivePostfix; @@ -1282,6 +1285,8 @@ public class VWindow extends VWindowOverlay implements */ public void setAssistiveDescription(Connector[] connectors) { if (connectors != null) { + assistiveConnectors = connectors; + Id[] ids = new Id[connectors.length]; for (int index = 0; index < connectors.length; index++) { if (connectors[index] == null) { @@ -1302,6 +1307,17 @@ public class VWindow extends VWindowOverlay implements } } + /** + * Gets the connectors that are used as assistive description. Text + * contained in these connectors will be read by assistive devices when the + * window is opened. + * + * @return list of previously set connectors + */ + public List getAssistiveDescription() { + return Collections.unmodifiableList(Arrays.asList(assistiveConnectors)); + } + /** * Sets the WAI-ARIA role the window. * diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 658c821f88..dfe83d48a1 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -18,6 +18,9 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; import com.vaadin.event.FieldEvents.BlurEvent; @@ -997,27 +1000,32 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } /** - * Allows to specify which component contains the description for the - * window. Text contained in this component will be read by assistive + * Allows to specify which components contain the description for the + * window. Text contained in these components will be read by assistive * devices when it is opened. * - * @param connector - * with the component to use as description + * @param connectors + * with the components to use as description */ - public void setAssistiveDescription(Connector connector) { - setAssistiveDescription(new Connector[] { connector }); + public void setAssistiveDescription(Connector... connectors) { + if (connectors == null) { + throw new IllegalArgumentException( + "Parameter connectors must be non-null"); + } else { + getState().contentDescription = connectors; + } } /** - * Allows to specify which components contain the description for the - * window. Text contained in this component will be read by assistive - * devices when it is opened. + * Gets the components that are used as assistive description. Text + * contained in these components will be read by assistive devices when the + * window is opened. * - * @param connectors - * with the components to use as description + * @return list of previously set components */ - public void setAssistiveDescription(Connector... connectors) { - getState().contentDescription = connectors; + public List getAssistiveDescription() { + return Collections.unmodifiableList(Arrays + .asList(getState().contentDescription)); } /** diff --git a/shared/src/com/vaadin/shared/ui/window/WindowState.java b/shared/src/com/vaadin/shared/ui/window/WindowState.java index 55a9b3ec55..3cb6bbe61e 100644 --- a/shared/src/com/vaadin/shared/ui/window/WindowState.java +++ b/shared/src/com/vaadin/shared/ui/window/WindowState.java @@ -41,7 +41,7 @@ public class WindowState extends PanelState { public String assistivePrefix = ""; public String assistivePostfix = ""; - public Connector[] contentDescription; + public Connector[] contentDescription = new Connector[0]; public WindowRole role = WindowRole.DIALOG; public boolean assistiveTabStop = false; public String assistiveTabStopTopText = "Top of dialog"; -- cgit v1.2.3 From 41a5d7d952b2ff5c61a1c2a44e8cf116359f0eae Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 23 Jul 2013 14:20:17 +0300 Subject: Don't lock session for checking request details (#12265) Change-Id: Id3e2b250b6aea66859274e2a5d6046c4f80172c3 --- server/src/com/vaadin/server/BootstrapHandler.java | 13 +++++------ .../vaadin/server/SynchronizedRequestHandler.java | 25 ++++++++++++++++++++++ .../server/communication/HeartbeatHandler.java | 9 ++++---- .../vaadin/server/communication/UIInitHandler.java | 9 ++++---- .../server/communication/UidlRequestHandler.java | 8 ++++--- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index b21fdb0b74..f237d9edd4 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -145,15 +145,16 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { } + @Override + protected boolean canHandleRequest(VaadinRequest request) { + // We do not want to handle /APP requests here, instead let it fall + // through and produce a 404 + return !ServletPortletHelper.isAppRequest(request); + } + @Override public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { - if (ServletPortletHelper.isAppRequest(request)) { - // We do not want to handle /APP requests here, instead let it fall - // through and produce a 404 - return false; - } - try { // Update WebBrowser here only to make WebBrowser information // available in init for LegacyApplications diff --git a/server/src/com/vaadin/server/SynchronizedRequestHandler.java b/server/src/com/vaadin/server/SynchronizedRequestHandler.java index ac730dcecb..c695855d7d 100644 --- a/server/src/com/vaadin/server/SynchronizedRequestHandler.java +++ b/server/src/com/vaadin/server/SynchronizedRequestHandler.java @@ -32,6 +32,10 @@ public abstract class SynchronizedRequestHandler implements RequestHandler { @Override public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { + if (!canHandleRequest(request)) { + return false; + } + session.lock(); try { return synchronizedHandleRequest(session, request, response); @@ -62,4 +66,25 @@ public abstract class SynchronizedRequestHandler implements RequestHandler { public abstract boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException; + /** + * Check whether a request may be handled by this handler. This can be used + * as an optimization to avoid locking the session just to investigate some + * method property. The default implementation just returns + * true which means that all requests will be handled by + * calling + * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)} + * with the session locked. + * + * @since 7.2 + * @param request + * the request to handle + * @return true if the request handling should continue once + * the session has been locked; false if there's no + * need to lock the session since the request would still not be + * handled. + */ + protected boolean canHandleRequest(VaadinRequest request) { + return true; + } + } diff --git a/server/src/com/vaadin/server/communication/HeartbeatHandler.java b/server/src/com/vaadin/server/communication/HeartbeatHandler.java index 4c95859203..04cb1b5a25 100644 --- a/server/src/com/vaadin/server/communication/HeartbeatHandler.java +++ b/server/src/com/vaadin/server/communication/HeartbeatHandler.java @@ -43,6 +43,11 @@ import com.vaadin.ui.UI; public class HeartbeatHandler extends SynchronizedRequestHandler implements SessionExpiredHandler { + @Override + protected boolean canHandleRequest(VaadinRequest request) { + return ServletPortletHelper.isHeartbeatRequest(request); + } + /** * Handles a heartbeat request for the given session. Reads the GET * parameter named {@link UIConstants#UI_ID_PARAMETER} to identify the UI. @@ -53,10 +58,6 @@ public class HeartbeatHandler extends SynchronizedRequestHandler implements @Override public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { - if (!ServletPortletHelper.isHeartbeatRequest(request)) { - return false; - } - UI ui = session.getService().findUI(request); if (ui != null) { ui.setLastHeartbeatTimestamp(System.currentTimeMillis()); diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index d4b0bc709f..e16e4f6cdd 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -55,13 +55,14 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { protected abstract boolean isInitRequest(VaadinRequest request); + @Override + protected boolean canHandleRequest(VaadinRequest request) { + return isInitRequest(request); + } + @Override public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { - if (!isInitRequest(request)) { - return false; - } - StringWriter stringWriter = new StringWriter(); try { diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java index d52c5e9fe0..cf25910fa4 100644 --- a/server/src/com/vaadin/server/communication/UidlRequestHandler.java +++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java @@ -59,12 +59,14 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements public UidlRequestHandler() { } + @Override + protected boolean canHandleRequest(VaadinRequest request) { + return ServletPortletHelper.isUIDLRequest(request); + } + @Override public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { - if (!ServletPortletHelper.isUIDLRequest(request)) { - return false; - } UI uI = session.getService().findUI(request); if (uI == null) { // This should not happen but it will if the UI has been closed. We -- cgit v1.2.3 From 47f4c612a935ae2d5b9a6ee8626faa41a6451153 Mon Sep 17 00:00:00 2001 From: Jarno Rantala Date: Wed, 26 Jun 2013 14:56:54 +0300 Subject: BeanValidator changed to throw exception with array of causes (#11324) BeanValidator was modified to throw InvalidValueException with array of causes instead of exception with one message including HTML. This way AbstractErrorMessage is able to create correct error notification message with multiple lines. Change-Id: I414189f56ac282daad9dd3fe58d13fd99108c479 --- .../com/vaadin/data/validator/BeanValidator.java | 19 ++++++--------- .../com/vaadin/tests/data/bean/BeanToValidate.java | 13 ++++++++++ .../bean/PersonWithBeanValidationAnnotations.java | 3 +++ .../server/validation/TestBeanValidation.java | 28 ++++++++++++++++++++++ .../fieldgroup/FieldBinderWithBeanValidation.java | 7 +++--- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/server/src/com/vaadin/data/validator/BeanValidator.java b/server/src/com/vaadin/data/validator/BeanValidator.java index ea7189bc5e..54efa51ac1 100644 --- a/server/src/com/vaadin/data/validator/BeanValidator.java +++ b/server/src/com/vaadin/data/validator/BeanValidator.java @@ -17,8 +17,6 @@ package com.vaadin.data.validator; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import java.util.Set; @@ -115,7 +113,9 @@ public class BeanValidator implements Validator { Set violations = getJavaxBeanValidator().validateValue(beanClass, propertyName, value); if (violations.size() > 0) { - List exceptions = new ArrayList(); + InvalidValueException[] causes = new InvalidValueException[violations + .size()]; + int i = 0; for (Object v : violations) { final ConstraintViolation violation = (ConstraintViolation) v; String msg = getJavaxBeanValidatorFactory() @@ -123,16 +123,11 @@ public class BeanValidator implements Validator { violation.getMessageTemplate(), new SimpleContext(value, violation .getConstraintDescriptor()), locale); - exceptions.add(msg); + causes[i] = new InvalidValueException(msg); + ++i; } - StringBuilder b = new StringBuilder(); - for (int i = 0; i < exceptions.size(); i++) { - if (i != 0) { - b.append("
"); - } - b.append(exceptions.get(i)); - } - throw new InvalidValueException(b.toString()); + + throw new InvalidValueException(null, causes); } } diff --git a/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java b/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java index 416563baba..034609764f 100644 --- a/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java +++ b/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java @@ -4,6 +4,7 @@ import javax.validation.constraints.Digits; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; public class BeanToValidate { @@ -21,6 +22,10 @@ public class BeanToValidate { @Digits(integer = 3, fraction = 2) private String decimals; + @Pattern(regexp = "V*", message = "Must start with letter V") + @Size(min = 3, max = 6, message = "Must contain 3 - 6 letters") + private String nickname; + public String getFirstname() { return firstname; } @@ -53,4 +58,12 @@ public class BeanToValidate { this.decimals = decimals; } + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + } diff --git a/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java b/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java index 93b2273263..575730d946 100644 --- a/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java +++ b/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java @@ -8,12 +8,15 @@ import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Past; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; public class PersonWithBeanValidationAnnotations { @NotNull @Size(min = 5, max = 20) + @Pattern(regexp = "A.*") private String firstName; + @NotNull private String lastName; diff --git a/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java b/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java index 8f6928fc35..b4409aed26 100644 --- a/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java +++ b/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java @@ -1,5 +1,6 @@ package com.vaadin.tests.server.validation; +import org.junit.Assert; import org.junit.Test; import com.vaadin.data.Validator.InvalidValueException; @@ -54,4 +55,31 @@ public class TestBeanValidation { validator.validate("123.45"); } + @Test + public void testBeanValidationException_OneValidationError() { + InvalidValueException[] causes = null; + BeanValidator validator = new BeanValidator(BeanToValidate.class, + "lastname"); + try { + validator.validate(null); + } catch (InvalidValueException e) { + causes = e.getCauses(); + } + + Assert.assertEquals(1, causes.length); + } + + @Test + public void testBeanValidationsException_TwoValidationErrors() { + InvalidValueException[] causes = null; + BeanValidator validator = new BeanValidator(BeanToValidate.class, + "nickname"); + try { + validator.validate("A"); + } catch (InvalidValueException e) { + causes = e.getCauses(); + } + + Assert.assertEquals(2, causes.length); + } } diff --git a/uitest/src/com/vaadin/tests/fieldgroup/FieldBinderWithBeanValidation.java b/uitest/src/com/vaadin/tests/fieldgroup/FieldBinderWithBeanValidation.java index 4f83f5d0fd..2c202af02b 100644 --- a/uitest/src/com/vaadin/tests/fieldgroup/FieldBinderWithBeanValidation.java +++ b/uitest/src/com/vaadin/tests/fieldgroup/FieldBinderWithBeanValidation.java @@ -7,7 +7,6 @@ import com.vaadin.data.util.BeanItem; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.data.bean.Address; import com.vaadin.tests.data.bean.Country; -import com.vaadin.tests.data.bean.Person; import com.vaadin.tests.data.bean.PersonWithBeanValidationAnnotations; import com.vaadin.tests.data.bean.Sex; import com.vaadin.tests.util.Log; @@ -90,8 +89,10 @@ public class FieldBinderWithBeanValidation extends TestBase { p)); } - public static Person getPerson(FieldGroup binder) { - return ((BeanItem) binder.getItemDataSource()).getBean(); + public static PersonWithBeanValidationAnnotations getPerson( + FieldGroup binder) { + return ((BeanItem) binder + .getItemDataSource()).getBean(); } @Override -- cgit v1.2.3 From 6a6952749924d4aaa12c6f1354ed94d388b85e64 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 8 Jul 2013 14:48:52 +0300 Subject: Add LayoutManager.setNeedsMeasureRecursively (#12180) Change-Id: Iddedc74b471cc8a2743cdeab78e877654fce9609 --- client/src/com/vaadin/client/LayoutManager.java | 40 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/client/src/com/vaadin/client/LayoutManager.java b/client/src/com/vaadin/client/LayoutManager.java index 14b155c92f..1f9884de67 100644 --- a/client/src/com/vaadin/client/LayoutManager.java +++ b/client/src/com/vaadin/client/LayoutManager.java @@ -1437,10 +1437,15 @@ public class LayoutManager { /** * Informs this LayoutManager that the size of a component might have - * changed. If there is no upcoming layout phase, a new layout phase is - * scheduled. This method should be used whenever a size might have changed - * from outside of Vaadin's normal update phase, e.g. when an icon has been - * loaded or when the user resizes some part of the UI using the mouse. + * changed. This method should be used whenever the size of an individual + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when an icon has been loaded or when the user resizes some + * part of the UI using the mouse. + *

+ * To set an entire component hierarchy to be measured, use + * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead. + *

+ * If there is no upcoming layout phase, a new layout phase is scheduled. * * @param component * the component whose size might have changed. @@ -1454,6 +1459,33 @@ public class LayoutManager { } } + /** + * Informs this LayoutManager that some sizes in a component hierarchy might + * have changed. This method should be used whenever the size of any child + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when a CSS class name related to sizing has been changed. + *

+ * To set a single component to be measured, use + * {@link #setNeedsMeasure(ComponentConnector)} instead. + *

+ * If there is no upcoming layout phase, a new layout phase is scheduled. + * + * @since 7.2 + * @param component + * the component at the root of the component hierarchy to + * measure + */ + public void setNeedsMeasureRecursively(ComponentConnector component) { + setNeedsMeasure(component); + + if (component instanceof HasComponentsConnector) { + HasComponentsConnector hasComponents = (HasComponentsConnector) component; + for (ComponentConnector child : hasComponents.getChildComponents()) { + setNeedsMeasureRecursively(child); + } + } + } + public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } -- cgit v1.2.3 From bd923f394c7b60ea912558201bf5afd710285722 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 22 Jul 2013 09:14:30 +0300 Subject: Detach previous UI with the same window.name (#10338, #12255) Change-Id: I15234985f1591d6af383c6e014679762619d5000 --- WebContent/VAADIN/vaadinBootstrap.js | 2 + server/src/com/vaadin/server/Page.java | 14 ++++ server/src/com/vaadin/server/VaadinSession.java | 60 +++++++++++----- .../vaadin/server/communication/UIInitHandler.java | 68 +++++++++++------- server/src/com/vaadin/ui/UI.java | 25 ++++++- .../src/com/vaadin/server/VaadinSessionTest.java | 2 +- .../tests/application/DetachOldUIOnReload.html | 77 +++++++++++++++++++++ .../tests/application/DetachOldUIOnReload.java | 80 ++++++++++++++++++++++ 8 files changed, 281 insertions(+), 47 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html create mode 100644 uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java diff --git a/WebContent/VAADIN/vaadinBootstrap.js b/WebContent/VAADIN/vaadinBootstrap.js index b2995dd0bd..bab759b812 100644 --- a/WebContent/VAADIN/vaadinBootstrap.js +++ b/WebContent/VAADIN/vaadinBootstrap.js @@ -120,6 +120,8 @@ url += '&theme=' + encodeURIComponent(theme); } + url += "&v-appId=" + appId; + var extraParams = getConfig('extraParams') if (extraParams !== undefined) { url += extraParams; diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java index 4d7d2c10c5..4c19d28b9c 100644 --- a/server/src/com/vaadin/server/Page.java +++ b/server/src/com/vaadin/server/Page.java @@ -458,6 +458,8 @@ public class Page implements Serializable { private final PageState state; + private String windowName; + public Page(UI uI, PageState state) { this.uI = uI; this.state = state; @@ -592,6 +594,7 @@ public class Page implements Serializable { String location = request.getParameter("v-loc"); String clientWidth = request.getParameter("v-cw"); String clientHeight = request.getParameter("v-ch"); + windowName = request.getParameter("v-wn"); if (location != null) { try { @@ -616,6 +619,17 @@ public class Page implements Serializable { return uI.getSession().getBrowser(); } + /** + * Gets the window.name value of the browser window of this page. + * + * @since 7.2 + * + * @return the window name, null if the name is not known + */ + public String getWindowName() { + return windowName; + } + /** * Updates the internal state with the given values. Does not resize the * Page or browser window. diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java index 8f27241384..8f15dacc98 100644 --- a/server/src/com/vaadin/server/VaadinSession.java +++ b/server/src/com/vaadin/server/VaadinSession.java @@ -40,7 +40,6 @@ import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; -import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.data.util.converter.DefaultConverterFactory; @@ -170,7 +169,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { private int nextUIId = 0; private Map uIs = new HashMap(); - private final Map retainOnRefreshUIs = new HashMap(); + private final Map embedIdMap = new HashMap(); private final EventRouter eventRouter = new EventRouter(); @@ -793,10 +792,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { */ public void removeUI(UI ui) { assert hasLock(); - int id = ui.getUIId(); + Integer id = Integer.valueOf(ui.getUIId()); ui.setSession(null); uIs.remove(id); - retainOnRefreshUIs.values().remove(id); + String embedId = ui.getEmbedId(); + if (embedId != null && id.equals(embedIdMap.get(embedId))) { + embedIdMap.remove(embedId); + } } /** @@ -1049,20 +1051,6 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { return nextUIId++; } - /** - * Gets the mapping from window.name to UI id for UIs that are - * should be retained on refresh. - * - * @see VaadinService#preserveUIOnRefresh(VaadinRequest, UI, UIProvider) - * @see PreserveOnRefresh - * - * @return the mapping between window names and UI ids for this session. - */ - public Map getPreserveOnRefreshUIs() { - assert hasLock(); - return retainOnRefreshUIs; - } - /** * Adds an initialized UI to this session. * @@ -1080,7 +1068,21 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { "The UI belongs to a different session"); } - uIs.put(Integer.valueOf(ui.getUIId()), ui); + Integer uiId = Integer.valueOf(ui.getUIId()); + uIs.put(uiId, ui); + + String embedId = ui.getEmbedId(); + if (embedId != null) { + Integer previousUiId = embedIdMap.put(embedId, uiId); + if (previousUiId != null) { + UI previousUi = uIs.get(previousUiId); + assert previousUi != null + && embedId.equals(previousUi.getEmbedId()) : "UI id map and embed id map not in sync"; + + // Will fire cleanup events at the end of the request handling. + previousUi.close(); + } + } } /** @@ -1285,4 +1287,24 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { return csrfToken; } + /** + * Finds the UI with the corresponding embed id. + * + * @since 7.2 + * @param embedId + * the embed id + * @return the UI with the corresponding embed id, or null if + * no UI is found + * + * @see UI#getEmbedId() + */ + public UI getUIByEmbedId(String embedId) { + Integer uiId = embedIdMap.get(embedId); + if (uiId == null) { + return null; + } else { + return getUIById(uiId.intValue()); + } + } + } diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index e16e4f6cdd..b2c17d00c1 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -164,31 +163,29 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { return null; } - // Check for an existing UI based on window.name + // Check for an existing UI based on embed id - // Special parameter sent by vaadinBootstrap.js - String windowName = request.getParameter("v-wn"); - - Map retainOnRefreshUIs = session - .getPreserveOnRefreshUIs(); - if (windowName != null && !retainOnRefreshUIs.isEmpty()) { - // Check for a known UI + String embedId = getEmbedId(request); - Integer retainedUIId = retainOnRefreshUIs.get(windowName); - - if (retainedUIId != null) { - UI retainedUI = session.getUIById(retainedUIId.intValue()); + UI retainedUI = session.getUIByEmbedId(embedId); + if (retainedUI != null) { + if (vaadinService.preserveUIOnRefresh(provider, new UICreateEvent( + request, uiClass))) { if (uiClass.isInstance(retainedUI)) { reinitUI(retainedUI, request); return retainedUI; } else { getLogger().info( - "Not using retained UI in " + windowName - + " because retained UI was of type " + "Not using the preserved UI " + embedId + + " because it is of type " + retainedUI.getClass() + " but " + uiClass + " is expected for the request."); } } + /* + * Previous UI without preserve on refresh will be closed when the + * new UI gets added to the session. + */ } // No existing UI found - go on by creating and initializing one @@ -221,25 +218,44 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { // Set thread local here so it is available in init UI.setCurrent(ui); - ui.doInit(request, uiId.intValue()); + ui.doInit(request, uiId.intValue(), embedId); session.addUI(ui); - // Remember if it should be remembered - if (vaadinService.preserveUIOnRefresh(provider, event)) { - // Remember this UI - if (windowName == null) { - getLogger().warning( - "There is no window.name available for UI " + uiClass - + " that should be preserved."); - } else { - session.getPreserveOnRefreshUIs().put(windowName, uiId); - } + // Warn if the window can't be preserved + if (embedId == null + && vaadinService.preserveUIOnRefresh(provider, event)) { + getLogger().warning( + "There is no embed id available for UI " + uiClass + + " that should be preserved."); } return ui; } + /** + * Constructs an embed id based on information in the request. + * + * @since 7.2 + * + * @param request + * the request to get embed information from + * @return the embed id, or null if id is not available. + * + * @see UI#getEmbedId() + */ + protected String getEmbedId(VaadinRequest request) { + // Parameters sent by vaadinBootstrap.js + String windowName = request.getParameter("v-wn"); + String appId = request.getParameter("v-appId"); + + if (windowName != null && appId != null) { + return windowName + '.' + appId; + } else { + return null; + } + } + /** * Updates a UI that has already been initialized but is now loaded again, * e.g. because of {@link PreserveOnRefresh}. diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index d4756e9ec1..8beebb0f1e 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -547,6 +547,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements private LocaleService localeService = new LocaleService(this, getState(false).localeServiceState); + private String embedId; + /** * This method is used by Component.Focusable objects to request focus to * themselves. Focus renders must be handled at window level (instead of @@ -594,12 +596,19 @@ public abstract class UI extends AbstractSingleComponentContainer implements * the initialization request * @param uiId * the id of the new ui + * @param embedId + * the embed id of this UI, or null if no id is + * known + * + * @see #getUIId() + * @see #getEmbedId() */ - public void doInit(VaadinRequest request, int uiId) { + public void doInit(VaadinRequest request, int uiId, String embedId) { if (this.uiId != -1) { throw new IllegalStateException("UI id has already been defined"); } this.uiId = uiId; + this.embedId = embedId; // Actual theme - used for finding CustomLayout templates theme = request.getParameter("theme"); @@ -1498,4 +1507,18 @@ public abstract class UI extends AbstractSingleComponentContainer implements private static Logger getLogger() { return Logger.getLogger(UI.class.getName()); } + + /** + * Gets a string the uniquely distinguishes this UI instance based on where + * it is embedded. The embed identifier is based on the + * window.name DOM attribute of the browser window where the UI + * is displayed and the id of the div element where the UI is embedded. + * + * @since 7.2 + * @return the embed id for this UI, or null if no id known + */ + public String getEmbedId() { + return embedId; + } + } diff --git a/server/tests/src/com/vaadin/server/VaadinSessionTest.java b/server/tests/src/com/vaadin/server/VaadinSessionTest.java index 68f198410c..51ae2a2d13 100644 --- a/server/tests/src/com/vaadin/server/VaadinSessionTest.java +++ b/server/tests/src/com/vaadin/server/VaadinSessionTest.java @@ -100,7 +100,7 @@ public class VaadinSessionTest { } }; - ui.doInit(vaadinRequest, session.getNextUIid()); + ui.doInit(vaadinRequest, session.getNextUIid(), null); ui.setSession(session); session.addUI(ui); diff --git a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html new file mode 100644 index 0000000000..038283324d --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html @@ -0,0 +1,77 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.application.DetachOldUIOnReload?restartApplication
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[0]/VLabel[0]This is UI 0
open/run/com.vaadin.tests.application.DetachOldUIOnReload
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_01. UI 0 has been detached
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[0]/VLabel[0]This is UI 1
clickAndWaitvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[1]/VButton[0]/domChild[0]/domChild[0]
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_02. UI 1 has been detached
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[0]/VLabel[0]This is UI 2
open/
open/run/com.vaadin.tests.application.DetachOldUIOnReload
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_03. UI 2 has been detached
assertTextvaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[0]/VLabel[0]This is UI 3
+ + diff --git a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java new file mode 100644 index 0000000000..7104146203 --- /dev/null +++ b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2013 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.application; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Label; + +public class DetachOldUIOnReload extends AbstractTestUIWithLog { + private static final String PERSISTENT_MESSAGES_ATTRIBUTE = DetachOldUIOnReload.class + .getName() + ".sessionMessages"; + + @Override + protected void setup(VaadinRequest request) { + for (String message : getSessionMessages(false)) { + log(message); + } + + addComponent(new Label("This is UI " + getUIId())); + addComponent(new Button("Reload page", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + getPage().reload(); + } + })); + } + + private List getSessionMessages(boolean storeIfNeeded) { + List messages = (List) getSession().getAttribute( + PERSISTENT_MESSAGES_ATTRIBUTE); + if (messages == null) { + messages = new ArrayList(); + if (storeIfNeeded) { + getSession().setAttribute(PERSISTENT_MESSAGES_ATTRIBUTE, + messages); + } + } + return messages; + } + + private void logToSession(String message) { + getSessionMessages(true).add(message); + } + + @Override + public void detach() { + super.detach(); + logToSession("UI " + getUIId() + " has been detached"); + } + + @Override + protected String getTestDescription() { + return "Tests that the previous UI gets cleaned immediately when refreshing."; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(10338); + } + +} -- cgit v1.2.3 From 437b6de4eea416317bd79399a5dde9870b306975 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 6 Aug 2013 10:23:03 +0300 Subject: Fixes missing description text for button icon #12321 Change-Id: I88fea408e110dc454c8d37cf0e95e109e8d8c8d1 --- client/src/com/vaadin/client/ui/Icon.java | 6 +++++- client/src/com/vaadin/client/ui/button/ButtonConnector.java | 9 +++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/src/com/vaadin/client/ui/Icon.java b/client/src/com/vaadin/client/ui/Icon.java index f02f0e10a8..688507f525 100644 --- a/client/src/com/vaadin/client/ui/Icon.java +++ b/client/src/com/vaadin/client/ui/Icon.java @@ -60,7 +60,11 @@ public class Icon extends UIObject { myUri = uidlUri; } - setAlternateText(uidlAlt); + if (uidlAlt != null) { + setAlternateText(uidlAlt); + } else { + setAlternateText(""); + } } /** diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java index fff983c168..b8bbf77cc1 100644 --- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java +++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java @@ -24,7 +24,6 @@ import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.vaadin.client.EventHelper; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.communication.StateChangeEvent; @@ -84,13 +83,11 @@ public class ButtonConnector extends AbstractComponentConnector implements if (getIcon() != null) { if (getWidget().icon == null) { getWidget().icon = new Icon(getConnection()); - Element iconElement = getWidget().icon.getElement(); - iconElement.setAttribute("alt", getState().iconAltText); - - getWidget().wrapper.insertBefore(iconElement, + getWidget().wrapper.insertBefore( + getWidget().icon.getElement(), getWidget().captionElement); } - getWidget().icon.setUri(getIcon()); + getWidget().icon.setUri(getIcon(), getState().iconAltText); } else { if (getWidget().icon != null) { getWidget().wrapper.removeChild(getWidget().icon -- cgit v1.2.3 From 9f126db566b14afaa8e025056539a38a5d7b9904 Mon Sep 17 00:00:00 2001 From: Matti Hosio Date: Mon, 15 Jul 2013 15:56:07 +0300 Subject: Support for null intermediate beans in NestedMethodProperty (#11435) Allows intermediate beans to return null in the NestedMethodProperty. The feature is not enabled by default and thus should be fully backwards compatible. Change-Id: I438d0f787c5c76f61ab234f3c92dd927a8354a37 --- .../vaadin/data/util/AbstractBeanContainer.java | 57 ++++++++++++++++++++- server/src/com/vaadin/data/util/BeanItem.java | 21 ++++++++ .../com/vaadin/data/util/NestedMethodProperty.java | 59 +++++++++++++++++++++- .../vaadin/data/util/NestedPropertyDescriptor.java | 24 ++++++++- .../com/vaadin/data/util/BeanContainerTest.java | 24 +++++++++ .../vaadin/data/util/BeanItemContainerTest.java | 23 +++++++++ .../vaadin/data/util/NestedMethodPropertyTest.java | 31 ++++++++++++ .../vaadin/data/util/PropertyDescriptorTest.java | 16 ++++++ 8 files changed, 250 insertions(+), 5 deletions(-) diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java index 35403d6419..cd5c0c809d 100644 --- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -845,8 +845,32 @@ public abstract class AbstractBeanContainer extends * @return true if the property was added */ public boolean addNestedContainerProperty(String propertyId) { + return addNestedContainerProperty(propertyId, false); + } + + /** + * Adds a nested container property for the container, e.g. + * "manager.address.street". + * + * All intermediate getters must exist and must return non-null values when + * the property value is accessed or the nullBeansAllowed must + * be set to true. If the nullBeansAllowed flag is set to true, + * calling getValue of the added property will return null if the property + * or any of its intermediate getters returns null. If set to false, null + * values returned by intermediate getters will cause NullPointerException. + * The default value is false to ensure backwards compatibility. + * + * @see NestedMethodProperty + * + * @param propertyId + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @return true if the property was added + */ + public boolean addNestedContainerProperty(String propertyId, + boolean nullBeansAllowed) { return addContainerProperty(propertyId, new NestedPropertyDescriptor( - propertyId, type)); + propertyId, type, nullBeansAllowed)); } /** @@ -864,13 +888,42 @@ public abstract class AbstractBeanContainer extends */ @SuppressWarnings("unchecked") public void addNestedContainerBean(String propertyId) { + addNestedContainerBean(propertyId, false); + } + + /** + * Adds a nested container properties for all sub-properties of a named + * property to the container. The named property itself is removed from the + * model as its subproperties are added. + * + * Unless + * nullBeansAllowed is set to true, all intermediate getters must + * exist and must return non-null values when the property values are + * accessed. If the nullBeansAllowed flag is set to true, + * calling getValue of the added subproperties will return null if the + * property or any of their intermediate getters returns null. If set to + * false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @see NestedMethodProperty + * @see #addNestedContainerProperty(String) + * + * @param propertyId + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + @SuppressWarnings("unchecked") + public void addNestedContainerBean(String propertyId, + boolean nullBeansAllowed) { Class propertyType = getType(propertyId); LinkedHashMap> pds = BeanItem .getPropertyDescriptors((Class) propertyType); for (String subPropertyId : pds.keySet()) { String qualifiedPropertyId = propertyId + "." + subPropertyId; NestedPropertyDescriptor pd = new NestedPropertyDescriptor( - qualifiedPropertyId, (Class) type); + qualifiedPropertyId, (Class) type, + nullBeansAllowed); model.put(qualifiedPropertyId, pd); model.remove(propertyId); for (BeanItem item : itemIdToItem.values()) { diff --git a/server/src/com/vaadin/data/util/BeanItem.java b/server/src/com/vaadin/data/util/BeanItem.java index fc51be8f36..4834fe4f89 100644 --- a/server/src/com/vaadin/data/util/BeanItem.java +++ b/server/src/com/vaadin/data/util/BeanItem.java @@ -267,6 +267,27 @@ public class BeanItem extends PropertysetItem { getBean(), nestedPropertyId)); } + /** + * Adds a nested property to the item. If the nullBeansAllowed + * flag is set to true, calling getValue of the added property will return + * null if the property or any of its intermediate getters returns null. If + * set to false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @param nestedPropertyId + * property id to add. This property must not exist in the item + * already and must of of form "field1.field2" where field2 is a + * field in the object referenced to by field1 + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + public void addNestedProperty(String nestedPropertyId, + boolean nullBeansAllowed) { + addItemProperty(nestedPropertyId, new NestedMethodProperty( + getBean(), nestedPropertyId, nullBeansAllowed)); + } + /** * Gets the underlying JavaBean object. * diff --git a/server/src/com/vaadin/data/util/NestedMethodProperty.java b/server/src/com/vaadin/data/util/NestedMethodProperty.java index b62ecfbfc3..7a3963c17e 100644 --- a/server/src/com/vaadin/data/util/NestedMethodProperty.java +++ b/server/src/com/vaadin/data/util/NestedMethodProperty.java @@ -32,7 +32,7 @@ import com.vaadin.data.util.MethodProperty.MethodException; * can contain multiple levels of nesting. * * When accessing the property value, all intermediate getters must return - * non-null values. + * non-null values or the nullBeansAllowed must be set to true. * * @see MethodProperty * @@ -55,6 +55,15 @@ public class NestedMethodProperty extends AbstractProperty { */ private Object instance; + /** + * a boolean flag indicating whether intermediate getters may return null + * values. If the flag is set to true, calling getValue will return null if + * the property or any of its intermediate getters returns null. If set to + * false, intermediate getters returning null value will throw Exception. + * The default value is false to ensure backwards compatibility. + */ + private boolean nullBeansAllowed = false; + private Class type; /* Special serialization to handle method references */ @@ -85,7 +94,33 @@ public class NestedMethodProperty extends AbstractProperty { * if the property name is invalid */ public NestedMethodProperty(Object instance, String propertyName) { + this(instance, propertyName, false); + } + + /** + * Constructs a nested method property for a given object instance. The + * property name is a dot separated string pointing to a nested property, + * e.g. "manager.address.street". The nullBeansAllowed controls + * the behavior in cases where the intermediate getters may return null + * values. If the flag is set to true, calling getValue will return null if + * the property or any of its intermediate getters returns null. If set to + * false, null values returned by intermediate getters will cause + * NullPointerException. The default value is false to ensure backwards + * compatibility. + * + * @param instance + * top-level bean to which the property applies + * @param propertyName + * dot separated nested property name + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @throws IllegalArgumentException + * if the property name is invalid + */ + public NestedMethodProperty(Object instance, String propertyName, + boolean nullBeansAllowed) { this.instance = instance; + this.nullBeansAllowed = nullBeansAllowed; initialize(instance.getClass(), propertyName); } @@ -103,6 +138,25 @@ public class NestedMethodProperty extends AbstractProperty { initialize(instanceClass, propertyName); } + /** + * For internal use to deduce property type etc. without a bean instance. + * Calling {@link #setValue(Object)} or {@link #getValue()} on properties + * constructed this way is not supported. + * + * @param instanceClass + * class of the top-level bean + * @param propertyName + * dot separated nested property name + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + */ + NestedMethodProperty(Class instanceClass, String propertyName, + boolean nullBeansAllowed) { + instance = null; + this.nullBeansAllowed = nullBeansAllowed; + initialize(instanceClass, propertyName); + } + /** * Initializes most of the internal fields based on the top-level bean * instance and property name (dot-separated string). @@ -199,6 +253,9 @@ public class NestedMethodProperty extends AbstractProperty { Object object = instance; for (Method m : getMethods) { object = m.invoke(object); + if (object == null && nullBeansAllowed) { + return null; + } } return (T) object; } catch (final Throwable e) { diff --git a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java index b2055fe776..67eb30fae5 100644 --- a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java +++ b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java @@ -34,6 +34,7 @@ public class NestedPropertyDescriptor implements private final String name; private final Class propertyType; + private final boolean nullBeansAllowed; /** * Creates a property descriptor that can create MethodProperty instances to @@ -48,10 +49,29 @@ public class NestedPropertyDescriptor implements */ public NestedPropertyDescriptor(String name, Class beanType) throws IllegalArgumentException { + this(name, beanType, false); + } + + /** + * Creates a property descriptor that can create MethodProperty instances to + * access the underlying bean property. + * + * @param name + * of the property in a dotted path format, e.g. "address.street" + * @param beanType + * type (class) of the top-level bean + * @param nullBeansAllowed + * set true to allow null values from intermediate getters + * @throws IllegalArgumentException + * if the property name is invalid + */ + public NestedPropertyDescriptor(String name, Class beanType, + boolean nullBeansAllowed) throws IllegalArgumentException { this.name = name; NestedMethodProperty property = new NestedMethodProperty( - beanType, name); + beanType, name, nullBeansAllowed); this.propertyType = property.getType(); + this.nullBeansAllowed = nullBeansAllowed; } @Override @@ -66,7 +86,7 @@ public class NestedPropertyDescriptor implements @Override public Property createProperty(BT bean) { - return new NestedMethodProperty(bean, name); + return new NestedMethodProperty(bean, name, nullBeansAllowed); } } diff --git a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java index 9037e303a8..2dcbb4aed8 100644 --- a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java @@ -457,4 +457,28 @@ public class BeanContainerTest extends AbstractBeanContainerTest { .getValue()); } + public void testNestedContainerPropertyWithNullBean() { + BeanContainer container = new BeanContainer( + NestedMethodPropertyTest.Person.class); + container.setBeanIdProperty("name"); + + container.addBean(new NestedMethodPropertyTest.Person("John", null)); + assertTrue(container + .addNestedContainerProperty("address.postalCodeObject")); + assertTrue(container.addNestedContainerProperty("address.street", true)); + // the nested properties added with allowNullBean setting should return + // null + assertNull(container.getContainerProperty("John", "address.street") + .getValue()); + // nested properties added without allowNullBean setting should throw + // exception + try { + container.getContainerProperty("John", "address.postalCodeObject") + .getValue(); + fail(); + } catch (Exception e) { + // should throw exception + } + } + } diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java index 6b88eb336d..3a2cb268b9 100644 --- a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java @@ -714,4 +714,27 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { .getValue()); } + public void testNestedContainerPropertyWithNullBean() { + BeanItemContainer container = new BeanItemContainer( + NestedMethodPropertyTest.Person.class); + NestedMethodPropertyTest.Person john = new NestedMethodPropertyTest.Person( + "John", null); + assertNotNull(container.addBean(john)); + assertTrue(container + .addNestedContainerProperty("address.postalCodeObject")); + assertTrue(container.addNestedContainerProperty("address.street", true)); + // the nested properties added with allowNullBean setting should return + // null + assertNull(container.getContainerProperty(john, "address.street") + .getValue()); + // nested properties added without allowNullBean setting should throw + // exception + try { + container.getContainerProperty(john, "address.postalCodeObject") + .getValue(); + fail(); + } catch (Exception e) { + // should throw exception + } + } } diff --git a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java index 640ede8743..d517322010 100644 --- a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java +++ b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java @@ -273,6 +273,23 @@ public class NestedMethodPropertyTest extends TestCase { Assert.assertEquals("Joonas", managerNameProperty.getValue()); } + public void testNullNestedPropertyWithAllowNullBeans() { + NestedMethodProperty managerNameProperty = new NestedMethodProperty( + vaadin, "manager.name", true); + NestedMethodProperty streetProperty = new NestedMethodProperty( + vaadin, "manager.address.street", true); + + joonas.setAddress(null); + // should return null + Assert.assertNull(streetProperty.getValue()); + + vaadin.setManager(null); + Assert.assertNull(managerNameProperty.getValue()); + vaadin.setManager(joonas); + Assert.assertEquals("Joonas", managerNameProperty.getValue()); + Assert.assertNull(streetProperty.getValue()); + } + public void testMultiLevelNestedPropertySetValue() { NestedMethodProperty managerNameProperty = new NestedMethodProperty( vaadin, "manager.name"); @@ -314,6 +331,20 @@ public class NestedMethodPropertyTest extends TestCase { Assert.assertEquals("Ruukinkatu 2-4", property2.getValue()); } + public void testSerializationWithNullBeansAllowed() throws IOException, + ClassNotFoundException { + vaadin.setManager(null); + NestedMethodProperty streetProperty = new NestedMethodProperty( + vaadin, "manager.address.street", true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(streetProperty); + @SuppressWarnings("unchecked") + NestedMethodProperty property2 = (NestedMethodProperty) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Assert.assertNull(property2.getValue()); + } + public void testIsReadOnly() { NestedMethodProperty streetProperty = new NestedMethodProperty( vaadin, "manager.address.street"); diff --git a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java index 14e70d76d4..0ae76430f6 100644 --- a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java +++ b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java @@ -52,4 +52,20 @@ public class PropertyDescriptorTest extends TestCase { Property property = pd2.createProperty(new Person("John", null)); Assert.assertEquals("John", property.getValue()); } + + public void testNestedPropertyDescriptorWithNullBeansAllowedSerialization() + throws Exception { + NestedPropertyDescriptor pd = new NestedPropertyDescriptor( + "address.street", Person.class, true); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(pd); + @SuppressWarnings("unchecked") + VaadinPropertyDescriptor pd2 = (VaadinPropertyDescriptor) new ObjectInputStream( + new ByteArrayInputStream(baos.toByteArray())).readObject(); + + Property property = pd2.createProperty(new Person("John", null)); + Assert.assertNull(property.getValue()); + } + } -- cgit v1.2.3 From d02e9c34866913a013a78d5b4116be7fdf249ed8 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Fri, 9 Aug 2013 15:56:15 +0300 Subject: Fixed DOM path in test after accessability improvement #11820 The WAI-ARIA fix added a new element into the notification making the dom path in the test invalid. Change-Id: Id0ce89ebff618bd68f6a7db51c72b6d25227d0cb --- .../embedded/EmbeddedClickListenerRelativeCoordinates.html | 6 +++--- .../components/notification/CloseErrorNotificationWithEscape.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/embedded/EmbeddedClickListenerRelativeCoordinates.html b/uitest/src/com/vaadin/tests/components/embedded/EmbeddedClickListenerRelativeCoordinates.html index 2dcd1b5071..ae81cfe61c 100644 --- a/uitest/src/com/vaadin/tests/components/embedded/EmbeddedClickListenerRelativeCoordinates.html +++ b/uitest/src/com/vaadin/tests/components/embedded/EmbeddedClickListenerRelativeCoordinates.html @@ -23,7 +23,7 @@ assertText - vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[1] 41, 22 @@ -33,7 +33,7 @@ waitForElementNotPresent - vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[1] @@ -43,7 +43,7 @@ assertText - vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentsembeddedEmbeddedClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[1] 0, 0 diff --git a/uitest/src/com/vaadin/tests/components/notification/CloseErrorNotificationWithEscape.html b/uitest/src/com/vaadin/tests/components/notification/CloseErrorNotificationWithEscape.html index 1ab75e1176..c9d9e186bc 100644 --- a/uitest/src/com/vaadin/tests/components/notification/CloseErrorNotificationWithEscape.html +++ b/uitest/src/com/vaadin/tests/components/notification/CloseErrorNotificationWithEscape.html @@ -28,12 +28,12 @@ assertText - vaadin=runcomvaadintestscomponentsnotificationNotifications::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentsnotificationNotifications::Root/VNotification[0]/HTML[0]/domChild[1] Hello world keyDown - vaadin=runcomvaadintestscomponentsnotificationNotifications::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentsnotificationNotifications::Root/VNotification[0]/HTML[0]/domChild[1] \27 -- cgit v1.2.3 From d9479ea06c3eab2b6a5a68552261106b5872c327 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Fri, 9 Aug 2013 16:16:41 +0300 Subject: Fixed DOM path in test after accessability improvement #11874 The WAI-ARIA fix added an element to the Window header making the dom path in the test invalid. Change-Id: Ia056adc55d12800bded3e6791d119ca0a786ef33 --- .../tests/components/window/CloseSubWindow.html | 2 +- .../components/window/ExtraWindowShownWaiAria.html | 157 ++++++++++----------- .../tests/components/window/SubWindowOrder.html | 6 +- 3 files changed, 82 insertions(+), 83 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/window/CloseSubWindow.html b/uitest/src/com/vaadin/tests/components/window/CloseSubWindow.html index ac81dfdefb..ae77628bff 100644 --- a/uitest/src/com/vaadin/tests/components/window/CloseSubWindow.html +++ b/uitest/src/com/vaadin/tests/components/window/CloseSubWindow.html @@ -40,7 +40,7 @@ click - vaadin=runcomvaadintestscomponentswindowCloseSubWindow::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowCloseSubWindow::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html index 76dcbddd0f..d17c635647 100644 --- a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html +++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html @@ -12,136 +12,135 @@ ExtraWindowShownWaiAria - open - /run/com.vaadin.tests.components.window.ExtraWindowShownWaiAria?restartApplication - + open + /run/com.vaadin.tests.components.window.ExtraWindowShownWaiAria?restartApplication + - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] + - assertElementPresent - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ - + assertElementPresent + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ + - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@role - dialog + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@role + dialog - storeAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]@id - headerid + storeAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]@id + headerid - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-labelledby - ${headerid} + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-labelledby + ${headerid} - storeAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@id - descriptionid + storeAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@id + descriptionid - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby - ${descriptionid} + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby + ${descriptionid} - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]@role - button + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[2]@role + button - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[2]@role - button + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[3]@role + button - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0] + - assertElementNotPresent - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ - + assertElementNotPresent + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ + - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VCheckBox[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[3]/VCheckBox[0]/domChild[0] + - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] + - storeAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@id - descriptionid + storeAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[0]@id + descriptionid - storeAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[1]@id - description2id + storeAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VLabel[1]@id + description2id - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby - ${descriptionid} ${description2id} + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/@aria-describedby + ${descriptionid} ${description2id} - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/FocusableScrollPanel[0]/VCssLayout[0]/VButton[0]/domChild[0]/domChild[0] + - assertElementNotPresent - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ - + assertElementNotPresent + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/ + - type - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[4]/VTextField[0] - Important + type + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[6]/VTextField[0] + Important - type - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VTextField[0] - - do ASAP + type + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[7]/VTextField[0] + - do ASAP - click - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] - + click + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0] + - assertText - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] - Important + assertText + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0] + Important - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]@class - v-assistive-device-only + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0]@class + v-assistive-device-only - assertText - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1] - - do ASAP + assertText + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1] + - do ASAP - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]@class - v-assistive-device-only + assertAttribute + vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1]@class + v-assistive-device-only - diff --git a/uitest/src/com/vaadin/tests/components/window/SubWindowOrder.html b/uitest/src/com/vaadin/tests/components/window/SubWindowOrder.html index 8374a90b52..6fd99caa19 100644 --- a/uitest/src/com/vaadin/tests/components/window/SubWindowOrder.html +++ b/uitest/src/com/vaadin/tests/components/window/SubWindowOrder.html @@ -90,7 +90,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[3]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[3]/domChild[0]/domChild[0]/domChild[3] 11,15 @@ -101,7 +101,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[2]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[2]/domChild[0]/domChild[0]/domChild[3] 6,8 @@ -139,7 +139,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[2]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowSubWindowOrder::/VWindow[2]/domChild[0]/domChild[0]/domChild[3] 10,5 -- cgit v1.2.3 From 650dae3aeab76273413c39fb521ce0252ec0e162 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Mon, 12 Aug 2013 15:09:03 +0300 Subject: Fixed DOM path in test after accessability improvement #11874 The WAI-ARIA fix added an element to the Window header making the dom path in the test invalid. Change-Id: I32592496d95b8fc0238e4a3a651b6ebc21f46f46 --- .../vaadin/tests/components/window/WindowWithInvalidCloseListener.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uitest/src/com/vaadin/tests/components/window/WindowWithInvalidCloseListener.html b/uitest/src/com/vaadin/tests/components/window/WindowWithInvalidCloseListener.html index fa63e5e1e6..3ea1f8f732 100644 --- a/uitest/src/com/vaadin/tests/components/window/WindowWithInvalidCloseListener.html +++ b/uitest/src/com/vaadin/tests/components/window/WindowWithInvalidCloseListener.html @@ -18,7 +18,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowWithInvalidCloseListener::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowWindowWithInvalidCloseListener::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 6,7 -- cgit v1.2.3 From 186432e09d50435e3a85b9617e2b9d2ec4c2cc63 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Mon, 12 Aug 2013 14:52:17 +0300 Subject: Fixed regression where page title was not updated #12353 Page title was moved to PageState in #11054 causing the UIConnector state change handling to never trigger an update. Change-Id: I592d316f99b40950f7ce4dd92e3ef48f835f29df --- client/src/com/vaadin/client/ui/ui/UIConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index 460cde5522..ba291e7e1f 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -640,7 +640,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector configurePolling(); } - if (stateChangeEvent.hasPropertyChanged("title")) { + if (stateChangeEvent.hasPropertyChanged("pageState.title")) { com.google.gwt.user.client.Window .setTitle(getState().pageState.title); } -- cgit v1.2.3 From ba8eb2afd85e01ac6e4d36a75e868cab2c225af1 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Mon, 12 Aug 2013 16:40:36 +0300 Subject: Fixed DOM path in test after accessability improvement #11874 The WAI-ARIA fix added an element to the Window header making the dom path in the test invalid. Change-Id: Ifc8e993969643c32fbf552aa55b8700744ecbc9d --- .../tests/components/calendar/CalendarWeeklyViewNewEvents.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/calendar/CalendarWeeklyViewNewEvents.html b/uitest/src/com/vaadin/tests/components/calendar/CalendarWeeklyViewNewEvents.html index fd51a0daad..6add1deba5 100644 --- a/uitest/src/com/vaadin/tests/components/calendar/CalendarWeeklyViewNewEvents.html +++ b/uitest/src/com/vaadin/tests/components/calendar/CalendarWeeklyViewNewEvents.html @@ -313,7 +313,7 @@ mouseClick - vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,9 @@ -438,7 +438,7 @@ mouseClick - vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,8 @@ -558,7 +558,7 @@ mouseClick - vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 7,8 @@ -583,7 +583,7 @@ mouseClick - vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentscalendarCalendarTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 12,10 -- cgit v1.2.3 From 7e2d22659ef3417b3cdee6e36d0337337e78e3ba Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 13 Aug 2013 11:09:44 +0300 Subject: Fixed DOM path in theme tests after accessability improvement #11874 The WAI-ARIA fix added an element to the Window header making the dom path in the tests invalid. Change-Id: Iee7eb4f922153da8a413c81e6611f0d967ebe134 --- .../com/vaadin/tests/components/uitest/base_theme_test.html | 10 +++++----- .../vaadin/tests/components/uitest/chameleon_theme_test.html | 10 +++++----- .../com/vaadin/tests/components/uitest/liferay_theme_test.html | 10 +++++----- .../vaadin/tests/components/uitest/reindeer_theme_test.html | 10 +++++----- .../com/vaadin/tests/components/uitest/runo_theme_test.html | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/uitest/base_theme_test.html b/uitest/src/com/vaadin/tests/components/uitest/base_theme_test.html index 614ae7bcda..cdbcf8bacc 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/base_theme_test.html +++ b/uitest/src/com/vaadin/tests/components/uitest/base_theme_test.html @@ -289,7 +289,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,8 @@ -304,7 +304,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,6 @@ -319,7 +319,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,5 @@ -334,7 +334,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,6 @@ -349,7 +349,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 10,7 diff --git a/uitest/src/com/vaadin/tests/components/uitest/chameleon_theme_test.html b/uitest/src/com/vaadin/tests/components/uitest/chameleon_theme_test.html index 7d9ffc65b8..d5d70a62d2 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/chameleon_theme_test.html +++ b/uitest/src/com/vaadin/tests/components/uitest/chameleon_theme_test.html @@ -289,7 +289,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,8 @@ -304,7 +304,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,6 @@ -319,7 +319,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,5 @@ -334,7 +334,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,6 @@ -349,7 +349,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 10,7 diff --git a/uitest/src/com/vaadin/tests/components/uitest/liferay_theme_test.html b/uitest/src/com/vaadin/tests/components/uitest/liferay_theme_test.html index d0ee96c7ef..783784e993 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/liferay_theme_test.html +++ b/uitest/src/com/vaadin/tests/components/uitest/liferay_theme_test.html @@ -289,7 +289,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,8 @@ -304,7 +304,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,6 @@ -319,7 +319,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,5 @@ -334,7 +334,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,6 @@ -349,7 +349,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 10,7 diff --git a/uitest/src/com/vaadin/tests/components/uitest/reindeer_theme_test.html b/uitest/src/com/vaadin/tests/components/uitest/reindeer_theme_test.html index a330f5bf61..175def94d3 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/reindeer_theme_test.html +++ b/uitest/src/com/vaadin/tests/components/uitest/reindeer_theme_test.html @@ -289,7 +289,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,8 @@ -304,7 +304,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,6 @@ -319,7 +319,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,5 @@ -334,7 +334,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,6 @@ -349,7 +349,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 10,7 diff --git a/uitest/src/com/vaadin/tests/components/uitest/runo_theme_test.html b/uitest/src/com/vaadin/tests/components/uitest/runo_theme_test.html index 61ba58a0e6..0db6614a9c 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/runo_theme_test.html +++ b/uitest/src/com/vaadin/tests/components/uitest/runo_theme_test.html @@ -289,7 +289,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,8 @@ -304,7 +304,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 11,6 @@ -319,7 +319,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 8,5 @@ -334,7 +334,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 9,6 @@ -349,7 +349,7 @@ mouseClick - vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runThemeTestUI::/VWindow[0]/domChild[0]/domChild[0]/domChild[3] 10,7 -- cgit v1.2.3 From 23a0ed5c0837c2d3257a18ae67fd217bdf73cfd0 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Thu, 15 Aug 2013 09:32:54 +0300 Subject: Fixes tests broken by accessability fixes #11874 and #11820 Both WAI-ARIA fixes improves the accessability by adding a hidden element into the dom for the screen readers. Unfortunatly this broke tests based on a certain DOM path. Change-Id: I64631f22d7ef0bbb840131802dab1e50e9dac913 --- .../application/TerminalErrorNotification.html | 2 +- .../tests/application/VaadinSessionAttribute.html | 2 +- .../table/TableShouldNotEatValueChanges.html | 4 +- .../TableWithBrokenGeneratorAndContainer.html | 3 +- .../tests/components/uitest/UIScrollTest.html | 2 +- .../tests/components/window/WindowCaptionTest.html | 7 ++- .../window/WindowMaximizeRestoreTest.html | 57 +++++++++++----------- 7 files changed, 37 insertions(+), 40 deletions(-) diff --git a/uitest/src/com/vaadin/tests/application/TerminalErrorNotification.html b/uitest/src/com/vaadin/tests/application/TerminalErrorNotification.html index f20967c8de..e7d437eeca 100644 --- a/uitest/src/com/vaadin/tests/application/TerminalErrorNotification.html +++ b/uitest/src/com/vaadin/tests/application/TerminalErrorNotification.html @@ -28,7 +28,7 @@ assertText - vaadin=runcomvaadintestsapplicationTerminalErrorNotification::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestsapplicationTerminalErrorNotification::Root/VNotification[0]/HTML[0]/domChild[1] Got an exception: You asked for it diff --git a/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.html b/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.html index e03027f308..150ab3ff3d 100644 --- a/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.html +++ b/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.html @@ -23,7 +23,7 @@ assertText - //body/div[2] + //body/div[2]//h1 42 & 84 diff --git a/uitest/src/com/vaadin/tests/components/table/TableShouldNotEatValueChanges.html b/uitest/src/com/vaadin/tests/components/table/TableShouldNotEatValueChanges.html index bab6c8dc16..e24f4ddca4 100644 --- a/uitest/src/com/vaadin/tests/components/table/TableShouldNotEatValueChanges.html +++ b/uitest/src/com/vaadin/tests/components/table/TableShouldNotEatValueChanges.html @@ -33,7 +33,7 @@ assertText - vaadin=runcomvaadintestscomponentstableTableShouldNotEatValueChanges::Root/VNotification[0] + vaadin=runcomvaadintestscomponentstableTableShouldNotEatValueChanges::Root/VNotification[0]/HTML[0]/domChild[1] TF Value on the server:fooo @@ -63,7 +63,7 @@ assertText - vaadin=runcomvaadintestscomponentstableTableShouldNotEatValueChanges::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentstableTableShouldNotEatValueChanges::Root/VNotification[0]/HTML[0]/domChild[1] TF Value on the server:baar diff --git a/uitest/src/com/vaadin/tests/components/table/TableWithBrokenGeneratorAndContainer.html b/uitest/src/com/vaadin/tests/components/table/TableWithBrokenGeneratorAndContainer.html index c2481e6be6..441324bb6a 100644 --- a/uitest/src/com/vaadin/tests/components/table/TableWithBrokenGeneratorAndContainer.html +++ b/uitest/src/com/vaadin/tests/components/table/TableWithBrokenGeneratorAndContainer.html @@ -3,7 +3,6 @@ - TableWithBrokenGeneratorAndContainer @@ -163,7 +162,7 @@ assertText - vaadin=runcomvaadintestscomponentstableTableWithBrokenGeneratorAndContainer::Root/VNotification[0]/HTML[0]/domChild[0] + vaadin=runcomvaadintestscomponentstableTableWithBrokenGeneratorAndContainer::Root/VNotification[0]/HTML[0]/domChild[1] Problem updating table. Please try again later diff --git a/uitest/src/com/vaadin/tests/components/uitest/UIScrollTest.html b/uitest/src/com/vaadin/tests/components/uitest/UIScrollTest.html index b5326e4660..acbe30c5d2 100644 --- a/uitest/src/com/vaadin/tests/components/uitest/UIScrollTest.html +++ b/uitest/src/com/vaadin/tests/components/uitest/UIScrollTest.html @@ -43,7 +43,7 @@ assertText - //div[@id='runcomvaadintestscomponentsuitestUIScrollTest-1797389287-overlays']/div + //div[@id='runcomvaadintestscomponentsuitestUIScrollTest-1797389287-overlays']//h1 Scrolled to 1020 px diff --git a/uitest/src/com/vaadin/tests/components/window/WindowCaptionTest.html b/uitest/src/com/vaadin/tests/components/window/WindowCaptionTest.html index a9a6fd621e..85ab67e4f6 100644 --- a/uitest/src/com/vaadin/tests/components/window/WindowCaptionTest.html +++ b/uitest/src/com/vaadin/tests/components/window/WindowCaptionTest.html @@ -3,7 +3,6 @@ - New Test @@ -18,7 +17,7 @@ assertText - vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[1]/domChild[0] Short @@ -43,7 +42,7 @@ assertText - vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[1]/domChild[0] This is a semi-long text that might wrap. @@ -68,7 +67,7 @@ assertText - vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowTest::PID_StestComponent/domChild[0]/domChild[0]/domChild[1]/domChild[0] diff --git a/uitest/src/com/vaadin/tests/components/window/WindowMaximizeRestoreTest.html b/uitest/src/com/vaadin/tests/components/window/WindowMaximizeRestoreTest.html index dcdfa05687..a27963a066 100644 --- a/uitest/src/com/vaadin/tests/components/window/WindowMaximizeRestoreTest.html +++ b/uitest/src/com/vaadin/tests/components/window/WindowMaximizeRestoreTest.html @@ -3,7 +3,6 @@ - WindowMaximizeRestoreTest @@ -19,59 +18,59 @@ assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox assertText - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] Window 1 mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 7,8 assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-restorebox mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 9,7 assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox doubleClickAt - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-restorebox doubleClickAt - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox assertVisible - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] @@ -81,7 +80,7 @@ assertNotVisible - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] @@ -92,7 +91,7 @@ assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-restorebox @@ -102,28 +101,28 @@ assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox doubleClickAt - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox doubleClickAt - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] assertCSSClass - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] v-window-maximizebox @@ -149,7 +148,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 10,8 @@ -175,7 +174,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[2] 6,11 @@ -185,7 +184,7 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[3] 7,5 @@ -210,12 +209,12 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[1]/domChild[0]/domChild[0]/domChild[2] 6,11 doubleClickAt - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] 113,10 @@ -226,27 +225,27 @@ mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 8,4 dragAndDrop - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0] -200,-200 dragAndDrop - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[4]/domChild[0] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[5]/domChild[0] +100,+100 mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 6,5 mouseClick - vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[1] + vaadin=runcomvaadintestscomponentswindowWindowMaximizeRestoreTest::/VWindow[0]/domChild[0]/domChild[0]/domChild[2] 5,8 -- cgit v1.2.3 From bc85f31f4f17ab78a4168e333be45a90a379a952 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Thu, 15 Aug 2013 09:59:37 +0300 Subject: Fix test AbsFieldValueConversions (#12092) Change-Id: Ifa23d60e8c1dd388c0404b9c40f00f81304778cf --- .../server/component/abstractfield/AbsFieldValueConversions.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java b/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java index 9854296538..dd46b11520 100644 --- a/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java +++ b/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java @@ -205,14 +205,15 @@ public class AbsFieldValueConversions extends TestCase { } + // Now specific to Integer because StringToNumberConverter has been removed public static class NumberBean { - private Number number; + private Integer number; - public Number getNumber() { + public Integer getNumber() { return number; } - public void setNumber(Number number) { + public void setNumber(Integer number) { this.number = number; } @@ -239,7 +240,7 @@ public class AbsFieldValueConversions extends TestCase { tf.setPropertyDataSource(new MethodProperty(nb, "number")); Converter c2 = tf.getConverter(); assertTrue( - "StringToNumber converter is ok for integer types and should stay even though property is changed", + "StringToInteger converter is ok for integer types and should stay even though property is changed", c1 == c2); assertEquals(490, tf.getPropertyDataSource().getValue()); assertEquals("490", tf.getValue()); -- cgit v1.2.3 From 36d0f09420ab66ab17e1587d5150300a92af33b1 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Thu, 15 Aug 2013 12:39:46 +0300 Subject: Fixed WAI-ARIA test related to #11821 which never has succeeded Due to Testbench bug #12375 the test had never succeed on IE9/IE10. To circumvent the issue I changed the way the test was done by substituting the /@class assertion with an xpath statement instead which works on all supported browsers. Change-Id: Idd5b8cb37404a2f88c4a00dc9849e3645559551d --- .../tests/components/window/ExtraWindowShownWaiAria.html | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html index d17c635647..f5f89d13ef 100644 --- a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html +++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.html @@ -123,24 +123,14 @@ assertText - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0] + xpath=//div[@class='v-window-header']/span[@class='v-assistive-device-only'][1] Important - - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0]@class - v-assistive-device-only - assertText - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1] + xpath=//div[@class='v-window-header']/span[@class='v-assistive-device-only'][2] - do ASAP - - assertAttribute - vaadin=runcomvaadintestscomponentswindowExtraWindowShownWaiAria::/VWindow[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1]@class - v-assistive-device-only - -- cgit v1.2.3 From 84651fdb6fc695e3d0752a75f7478577ad8a0063 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Thu, 15 Aug 2013 16:27:27 +0300 Subject: Fixed WAI-ARIA test related to #11820 which never has succeeded Due to Testbench bug #12375 the test had never succeed on IE9/IE10. To circumvent the issue I changed the way the test was done by substituting the /@class assertion with an xpath statement instead which works on all supported browsers. Change-Id: Iad663ae6cf0976c09e0907942f8324d57b4328e6 --- .../notification/NotificationsWaiAria.html | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html index aa13b9e637..fb00953e48 100644 --- a/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html +++ b/uitest/src/com/vaadin/tests/components/notification/NotificationsWaiAria.html @@ -47,24 +47,14 @@ alert - assertAttribute - vaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[0]@class - v-assistive-device-only - - - assertText - vaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[0] - Prefix: + assertText + xpath=//div[@class='v-Notification humanized v-Notification-humanized']//span[@class='v-assistive-device-only'][1] + Prefix: - assertAttribute - vaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[2]@class - v-assistive-device-only - - - assertText - vaadin=runcomvaadintestscomponentsnotificationNotificationsWaiAria::Root/VNotification[0]/domChild[0]/domChild[0]/domChild[2] - - press ESC to close + assertText + xpath=//div[@class='v-Notification humanized v-Notification-humanized']//span[@class='v-assistive-device-only'][2] + - press ESC to close closeNotification -- cgit v1.2.3 From 811eb1e6d69028505f9b9ccf82196809ce1186a4 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Wed, 21 Aug 2013 14:09:19 +0200 Subject: Activate tab stop automatically for modal windows (#12344) Change-Id: Icef7a3bb3729ebd407fe53af334f93e1e5c0a7cb --- client/src/com/vaadin/client/ui/VWindow.java | 28 ++++++++++++++++++++-------- server/src/com/vaadin/ui/Window.java | 12 +++++++++--- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 7c0c875072..fd28e4137e 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -254,6 +254,15 @@ public class VWindow extends VWindowOverlay implements removeTabBlockHandlers(); } + private void addTabBlockHandlers() { + if (topBlockerRegistration == null) { + topBlockerRegistration = Event + .addNativePreviewHandler(topEventBlocker); + bottomBlockerRegistration = Event + .addNativePreviewHandler(bottomEventBlocker); + } + } + private void removeTabBlockHandlers() { if (topBlockerRegistration != null) { topBlockerRegistration.removeHandler(); @@ -646,6 +655,7 @@ public class VWindow extends VWindowOverlay implements if (isAttached()) { showModalityCurtain(); } + addTabBlockHandlers(); deferOrdering(); } else { if (modalityCurtain != null) { @@ -654,6 +664,9 @@ public class VWindow extends VWindowOverlay implements } modalityCurtain = null; } + if (!doTabStop) { + removeTabBlockHandlers(); + } } } @@ -1342,21 +1355,20 @@ public class VWindow extends VWindowOverlay implements /** * Registers the handlers that prevent to leave the window using the * Tab-key. + *

+ * The value of the parameter doTabStop is stored and used for non-modal + * windows. For modal windows, the handlers are always registered, while + * preserving the stored value. * * @param doTabStop * true to prevent leaving the window, false to allow leaving the - * window + * window for non modal windows */ public void setTabStopEnabled(boolean doTabStop) { this.doTabStop = doTabStop; - if (doTabStop) { - if (topBlockerRegistration == null) { - topBlockerRegistration = Event - .addNativePreviewHandler(topEventBlocker); - bottomBlockerRegistration = Event - .addNativePreviewHandler(bottomEventBlocker); - } + if (doTabStop || vaadinModality) { + addTabBlockHandlers(); } else { removeTabBlockHandlers(); } diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index dfe83d48a1..8e7c6dbc80 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -637,7 +637,10 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, /** * Sets window modality. When a modal window is open, components outside - * that window it cannot be accessed. + * that window cannot be accessed. + *

+ * Keyboard navigation is restricted by blocking the tab key at the top and + * bottom of the window by activating the tab stop function internally. * * @param modal * true if modality is to be turned on @@ -1110,11 +1113,14 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } /** - * Set if it should be prevented to set the focus to a component outside the - * window with the tab key. + * Set if it should be prevented to set the focus to a component outside a + * non-modal window with the tab key. *

* This is meant to help users of assistive devices to not leaving the * window unintentionally. + *

+ * For modal windows, this function is activated automatically, while + * preserving the stored value of tabStop. * * @param tabStop * true to keep the focus inside the window when reaching the top -- cgit v1.2.3 From e97fb8cbff8ef493860892055508b4db3223f1be Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 27 Aug 2013 17:09:51 +0300 Subject: Made test stable in IE8 #12406 Change-Id: If109cb760534b7d50d310b49c8a2cef86192fcca --- .../components/draganddropwrapper/DragAndDropWrapperTooltips.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/draganddropwrapper/DragAndDropWrapperTooltips.html b/uitest/src/com/vaadin/tests/components/draganddropwrapper/DragAndDropWrapperTooltips.html index 03ec163e40..3c91c8b24f 100644 --- a/uitest/src/com/vaadin/tests/components/draganddropwrapper/DragAndDropWrapperTooltips.html +++ b/uitest/src/com/vaadin/tests/components/draganddropwrapper/DragAndDropWrapperTooltips.html @@ -18,7 +18,7 @@ showTooltip - vaadin=runcomvaadintestscomponentsdraganddropwrapperDragAndDropWrapperTooltips::PID_Swrapper3 + vaadin=runcomvaadintestscomponentsdraganddropwrapperDragAndDropWrapperTooltips::PID_Swrapper3/VLabel[0] 0,0 @@ -26,9 +26,10 @@ tooltip-initial + drag - vaadin=runcomvaadintestscomponentsdraganddropwrapperDragAndDropWrapperTooltips::PID_Swrapper4 + vaadin=runcomvaadintestscomponentsdraganddropwrapperDragAndDropWrapperTooltips::PID_Swrapper4/VLabel[0] 30,41 -- cgit v1.2.3 From d827a1760da477575294f0105933e868c1bf3ad3 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Tue, 27 Aug 2013 13:13:45 +0300 Subject: Fixed failing test after accessibility changes in #11827 Previously a tab could be selected by using the enter key. This was changed in the ticket to use the space key instead due to accessability reasons. Fixed test by replacing all enter key presses with space key presses. Change-Id: I9037785be212bb62835bfb1b17629e115c3fa601 --- .../tests/components/tabsheet/TabKeyboardNavigation.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html index acf66fb27f..ee9df2a241 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html @@ -3,7 +3,6 @@ - TabKeyboardNavigation @@ -34,7 +33,7 @@ pressSpecialKey vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] - enter + space assertText @@ -64,7 +63,7 @@ pressSpecialKey vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] - enter + space assertText @@ -94,7 +93,7 @@ pressSpecialKey vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] - enter + space assertText @@ -159,7 +158,7 @@ pressSpecialKey vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10] - enter + space assertText @@ -199,7 +198,7 @@ pressSpecialKey vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1] - enter + space assertText -- cgit v1.2.3 From 261a3ba76a523d7e8d6b717f6f9d67eaef8c901f Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 2 Sep 2013 22:28:19 +0300 Subject: Improve error message for missing imports (#12494) Change-Id: Ic320ac6f54e928939773495a5168ac674d6a1a33 --- .../com/vaadin/sass/internal/ScssStylesheet.java | 26 +++++++++++++++++----- .../sass/internal/visitor/ImportNodeHandler.java | 9 ++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java index dbb3e571dc..704425e300 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java +++ b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java @@ -59,7 +59,7 @@ public class ScssStylesheet extends Node { private static HashMap lastNodeAdded = new HashMap(); - private String fileName; + private File file; private String charset; @@ -162,7 +162,7 @@ public class ScssStylesheet extends Node { InputSource source = resolver.resolve(identifier); if (source != null) { File f = new File(source.getURI()); - setFileName(f.getParent()); + setFile(f); return source; } } @@ -355,12 +355,28 @@ public class ScssStylesheet extends Node { return mixinDefs.get(name); } - public void setFileName(String fileName) { - this.fileName = fileName; + public void setFile(File file) { + this.file = file; } + /** + * Returns the directory containing this style sheet + * + * @since 7.2 + * @return The directory containing this style sheet + */ + public String getDirectory() { + return file.getParent(); + } + + /** + * Returns the full file name for this style sheet + * + * @since 7.2 + * @return The full file name for this style sheet + */ public String getFileName() { - return fileName; + return file.getPath(); } public static HashMap getLastNodeAdded() { diff --git a/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java b/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java index cb9896967a..87c7cadcf8 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java +++ b/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java @@ -58,7 +58,7 @@ public class ImportNodeHandler { if (!importNode.isPureCssImport()) { try { StringBuilder filePathBuilder = new StringBuilder( - styleSheet.getFileName()); + styleSheet.getDirectory()); filePathBuilder.append(File.separatorChar).append( importNode.getUri()); if (!filePathBuilder.toString().endsWith(".scss")) { @@ -73,9 +73,10 @@ public class ImportNodeHandler { imported = ScssStylesheet.get(importNode.getUri()); } if (imported == null) { - throw new FileNotFoundException(importNode.getUri() - + " (parent: " - + ScssStylesheet.get().getFileName() + ")"); + throw new FileNotFoundException("Import '" + + importNode.getUri() + "' in '" + + styleSheet.getFileName() + + "' could not be found"); } traverse(imported); -- cgit v1.2.3 From f14fc3cbd235b6004d93b10a660bb3a97d13cd64 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Tue, 27 Aug 2013 10:53:23 +0200 Subject: Keep the Tooltip in the DOM (#12458) Change-Id: Ic707632ee51181e660b64935f68e564bdfc4443b --- .../com/vaadin/client/ApplicationConnection.java | 4 + client/src/com/vaadin/client/VErrorMessage.java | 3 - client/src/com/vaadin/client/VTooltip.java | 59 +++++------ client/src/com/vaadin/client/ui/VTree.java | 17 +++ .../com/vaadin/client/ui/tree/TreeConnector.java | 5 + .../com/vaadin/tests/components/ErrorMessages.html | 8 +- .../tests/components/button/ButtonsWaiAria.java | 1 + .../vaadin/tests/components/form/FormTooltips.html | 36 ++++--- .../tests/components/slider/SliderTooltip.html | 15 ++- .../table/TableItemDescriptionGeneratorTest.html | 114 ++++++++++++++++----- .../tests/components/tabsheet/TabsheetTooltip.html | 2 +- .../vaadin/tests/components/tree/SimpleTree.java | 13 +++ .../tests/components/ui/TooltipConfiguration.html | 9 +- .../tests/components/window/TooltipInWindow.html | 9 +- .../tests/fieldgroup/IntegerRangeValidator.html | 41 ++++---- 15 files changed, 221 insertions(+), 115 deletions(-) diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 0d9c859ee8..2865e04757 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -490,6 +490,10 @@ public class ApplicationConnection { // initial UIDL provided in DOM, continue as if returned by request handleJSONText(jsonText, -1); + + // Tooltip can't be created earlier because the necessary fields are + // not setup to add it in the correct place in the DOM + getVTooltip().showAssistive(new TooltipInfo(" ")); } } diff --git a/client/src/com/vaadin/client/VErrorMessage.java b/client/src/com/vaadin/client/VErrorMessage.java index 2e42b98a05..a384b451dd 100644 --- a/client/src/com/vaadin/client/VErrorMessage.java +++ b/client/src/com/vaadin/client/VErrorMessage.java @@ -31,9 +31,6 @@ public class VErrorMessage extends FlowPanel { public VErrorMessage() { super(); setStyleName(CLASSNAME); - - // Needed for binding with WAI-ARIA attributes - getElement().setId(DOM.createUniqueId()); } /** diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java index 57e72aedfc..e687712b9c 100644 --- a/client/src/com/vaadin/client/VTooltip.java +++ b/client/src/com/vaadin/client/VTooltip.java @@ -15,7 +15,8 @@ */ package com.vaadin.client; -import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.LiveValue; +import com.google.gwt.aria.client.RelevantValue; import com.google.gwt.aria.client.Roles; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; @@ -36,12 +37,12 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ui.VOverlay; +import com.vaadin.client.ui.VWindowOverlay; /** * TODO open for extension */ -public class VTooltip extends VOverlay { +public class VTooltip extends VWindowOverlay { private static final String CLASSNAME = "v-tooltip"; private static final int MARGIN = 4; public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN @@ -57,7 +58,6 @@ public class VTooltip extends VOverlay { private boolean justClosed = false; private String uniqueId = DOM.createUniqueId(); - private Element layoutElement; private int maxWidth; // Delays for the tooltip, configurable on the server side @@ -80,17 +80,17 @@ public class VTooltip extends VOverlay { setWidget(layout); layout.add(em); DOM.setElementProperty(description, "className", CLASSNAME + "-text"); - - layoutElement = layout.getElement(); - DOM.appendChild(layoutElement, description); + DOM.appendChild(layout.getElement(), description); setSinkShadowEvents(true); - // Used to bind the tooltip to the owner for assistive devices - layoutElement.setId(uniqueId); - - description.setId(DOM.createUniqueId()); - Roles.getTooltipRole().set(layoutElement); - Roles.getTooltipRole().setAriaHiddenState(layoutElement, true); + // When a tooltip is shown, the content of the tooltip changes. With a + // tooltip being a live-area, this change is notified to a assistive + // device. + Roles.getTooltipRole().set(getElement()); + Roles.getTooltipRole().setAriaLiveProperty(getElement(), + LiveValue.ASSERTIVE); + Roles.getTooltipRole().setAriaRelevantProperty(getElement(), + RelevantValue.ADDITIONS); } /** @@ -240,10 +240,11 @@ public class VTooltip extends VOverlay { @Override public void hide() { - super.hide(); - Roles.getTooltipRole().setAriaHiddenState(layoutElement, true); - Roles.getTooltipRole().removeAriaDescribedbyProperty( - tooltipEventHandler.currentElement); + em.updateMessage(""); + description.setInnerHTML(""); + + updatePosition(null, true); + setPopupPosition(tooltipEventMouseX, tooltipEventMouseY); } private int tooltipEventMouseX; @@ -298,9 +299,9 @@ public class VTooltip extends VOverlay { private com.google.gwt.dom.client.Element currentElement = null; /** - * Current element focused + * Marker for handling of tooltip through focus */ - private boolean currentIsFocused; + private boolean handledByFocus; /** * Current tooltip active @@ -403,6 +404,7 @@ public class VTooltip extends VOverlay { */ @Override public void onBlur(BlurEvent be) { + handledByFocus = false; handleHideEvent(); } @@ -412,7 +414,7 @@ public class VTooltip extends VOverlay { .getEventTarget()); // We can ignore move event if it's handled by move or over already - if (currentElement == element && currentIsFocused == isFocused) { + if (currentElement == element && handledByFocus == true) { return; } @@ -420,8 +422,6 @@ public class VTooltip extends VOverlay { if (!connectorAndTooltipFound) { if (isShowing()) { handleHideEvent(); - Roles.getButtonRole() - .removeAriaDescribedbyProperty(element); } else { currentTooltipInfo = null; } @@ -430,17 +430,12 @@ public class VTooltip extends VOverlay { if (isShowing()) { replaceCurrentTooltip(); - Roles.getTooltipRole().removeAriaDescribedbyProperty( - currentElement); } else { showTooltip(); } - - Roles.getTooltipRole().setAriaDescribedbyProperty(element, - Id.of(uniqueId)); } - currentIsFocused = isFocused; + handledByFocus = isFocused; currentElement = element; } } @@ -475,9 +470,11 @@ public class VTooltip extends VOverlay { @Override public void setPopupPositionAndShow(PositionCallback callback) { - super.setPopupPositionAndShow(callback); - - Roles.getTooltipRole().setAriaHiddenState(layoutElement, false); + if (isAttached()) { + callback.setPosition(getOffsetWidth(), getOffsetHeight()); + } else { + super.setPopupPositionAndShow(callback); + } } /** diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java index 51c00ca310..1acd4bd05f 100644 --- a/client/src/com/vaadin/client/ui/VTree.java +++ b/client/src/com/vaadin/client/ui/VTree.java @@ -70,6 +70,7 @@ import com.vaadin.client.ui.dd.VDragEvent; import com.vaadin.client.ui.dd.VDropHandler; import com.vaadin.client.ui.dd.VHasDropHandler; import com.vaadin.client.ui.dd.VTransferable; +import com.vaadin.client.ui.tree.TreeConnector; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.MouseEventDetails.MouseButton; import com.vaadin.shared.ui.MultiSelectMode; @@ -162,6 +163,9 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, /** For internal use only. May be removed or replaced in the future. */ public String[] bodyActionKeys; + /** For internal use only. May be removed or replaced in the future. */ + public TreeConnector connector; + public VLazyExecutor iconLoaded = new VLazyExecutor(50, new ScheduledCommand() { @@ -1729,6 +1733,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } } } + showTooltipForKeyboardNavigation(node); return true; } @@ -1754,6 +1759,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } } } + showTooltipForKeyboardNavigation(node); return true; } @@ -1774,6 +1780,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, focusAndSelectNode(focusedNode.getParentNode()); } } + showTooltipForKeyboardNavigation(focusedNode); return true; } @@ -1792,6 +1799,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, focusAndSelectNode(focusedNode.getChildren().get(0)); } } + showTooltipForKeyboardNavigation(focusedNode); return true; } @@ -1820,6 +1828,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); return true; } @@ -1836,12 +1845,20 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); return true; } return false; } + private void showTooltipForKeyboardNavigation(TreeNode node) { + if (connector != null) { + getClient().getVTooltip().showAssistive( + connector.getTooltipInfo(node.nodeCaptionSpan)); + } + } + private void focusAndSelectNode(TreeNode node) { /* * Keyboard navigation doesn't work reliably if the tree is in diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java index ef016c31b7..6f89137918 100644 --- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java +++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java @@ -44,6 +44,11 @@ public class TreeConnector extends AbstractComponentConnector implements protected final Map tooltipMap = new HashMap(); + @Override + protected void init() { + getWidget().connector = this; + } + @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { if (!isRealUpdate(uidl)) { diff --git a/uitest/src/com/vaadin/tests/components/ErrorMessages.html b/uitest/src/com/vaadin/tests/components/ErrorMessages.html index 5379ff59e0..2b7032ea79 100644 --- a/uitest/src/com/vaadin/tests/components/ErrorMessages.html +++ b/uitest/src/com/vaadin/tests/components/ErrorMessages.html @@ -3,17 +3,17 @@ - -New Test + +ErrorMessages - + - + diff --git a/uitest/src/com/vaadin/tests/components/button/ButtonsWaiAria.java b/uitest/src/com/vaadin/tests/components/button/ButtonsWaiAria.java index 1208b8be3b..cc75f87a71 100644 --- a/uitest/src/com/vaadin/tests/components/button/ButtonsWaiAria.java +++ b/uitest/src/com/vaadin/tests/components/button/ButtonsWaiAria.java @@ -22,6 +22,7 @@ public class ButtonsWaiAria extends ComponentTestCase - - + + + + + + + @@ -64,10 +69,15 @@ - - + + + + + + + @@ -80,9 +90,9 @@ - + - + @@ -96,9 +106,9 @@ - + - + @@ -112,9 +122,9 @@ - + - + @@ -128,9 +138,9 @@ - + - +
New Test
ErrorMessages
open/run/com.vaadin.tests.components.ErrorMessages/run/com.vaadin.tests.components.ErrorMessages?restartApplication
waitForElementNotPresentvaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]pause1000
assertElementPositionLeftvaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
showTooltip
waitForElementNotPresentvaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]pause1000
assertElementPositionLeftvaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
showTooltip
assertElementNotPresentassertElementPositionLeft vaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]-1000
assertElementNotPresentassertElementPositionLeft vaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]-1000
assertElementNotPresentassertElementPositionLeft vaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
assertElementNotPresentassertElementPositionLeft vaadin=runcomvaadintestscomponentsformFormTooltips::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
diff --git a/uitest/src/com/vaadin/tests/components/slider/SliderTooltip.html b/uitest/src/com/vaadin/tests/components/slider/SliderTooltip.html index 4e8296050f..806e7d1b44 100644 --- a/uitest/src/com/vaadin/tests/components/slider/SliderTooltip.html +++ b/uitest/src/com/vaadin/tests/components/slider/SliderTooltip.html @@ -3,13 +3,13 @@ - -New Test + +SliderTooltip - + @@ -57,9 +57,14 @@ - - + + + + + + +
New Test
SliderTooltip
open40,16
waitForElementNotPresentvaadin=runcomvaadintestscomponentssliderSliderTest::Root/VTooltip[0]/FlowPanel[0]/domChild[1]pause 1000
assertElementPositionLeftvaadin=runcomvaadintestscomponentssliderSliderTest::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
diff --git a/uitest/src/com/vaadin/tests/components/table/TableItemDescriptionGeneratorTest.html b/uitest/src/com/vaadin/tests/components/table/TableItemDescriptionGeneratorTest.html index eb3efc28fd..2df9fb678c 100644 --- a/uitest/src/com/vaadin/tests/components/table/TableItemDescriptionGeneratorTest.html +++ b/uitest/src/com/vaadin/tests/components/table/TableItemDescriptionGeneratorTest.html @@ -3,13 +3,13 @@ - -New Test + +TableItemDescriptionGeneratorTest - + @@ -39,10 +39,15 @@ - - + + + + + + + @@ -65,10 +70,15 @@ - - + + + + + + + @@ -91,10 +101,15 @@ - - + + + + + + + @@ -123,10 +138,15 @@ - - + + + + + + + @@ -149,10 +169,15 @@ - - + + + + + + + @@ -175,10 +200,15 @@ - - + + + + + + + @@ -212,10 +242,15 @@ - - + + + + + + + @@ -238,10 +273,15 @@ - - + + + + + + + @@ -264,10 +304,15 @@ - - + + + + + + + @@ -296,10 +341,15 @@ - - + + + + + + + @@ -322,10 +372,15 @@ - - + + + + + + + @@ -348,10 +403,15 @@ - - + + + + + + +
New Test
TableItemDescriptionGeneratorTest
open22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseClick22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseClick22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseClick22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
mouseMoveAt22,7
waitForElementNotPresentvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]pause1000
assertElementPositionLeftvaadin=runTableItemDescriptionGeneratorTest::Root/VTooltip[0]-1000
diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabsheetTooltip.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabsheetTooltip.html index d133ab9937..38ff6ab8ca 100644 --- a/uitest/src/com/vaadin/tests/components/tabsheet/TabsheetTooltip.html +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabsheetTooltip.html @@ -13,7 +13,7 @@ open - /run/com.vaadin.tests.components.tabsheet.TabsheetTooltip + /run/com.vaadin.tests.components.tabsheet.TabsheetTooltip?restartApplication diff --git a/uitest/src/com/vaadin/tests/components/tree/SimpleTree.java b/uitest/src/com/vaadin/tests/components/tree/SimpleTree.java index 2fd3f05dbb..06b8af1ec6 100644 --- a/uitest/src/com/vaadin/tests/components/tree/SimpleTree.java +++ b/uitest/src/com/vaadin/tests/components/tree/SimpleTree.java @@ -8,6 +8,8 @@ import com.vaadin.event.Action; import com.vaadin.server.ThemeResource; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.AbstractSelect; +import com.vaadin.ui.AbstractSelect.ItemDescriptionGenerator; +import com.vaadin.ui.Component; import com.vaadin.ui.Tree; public class SimpleTree extends TestBase implements Action.Handler { @@ -53,6 +55,17 @@ public class SimpleTree extends TestBase implements Action.Handler { tree.setItemIcon(9, notCachedFolderIconLargeOther, "First Choice"); tree.setItemIcon(11, notCachedFolderIconLarge); + tree.setItemDescriptionGenerator(new ItemDescriptionGenerator() { + @Override + public String generateDescription(Component source, Object itemId, + Object propertyId) { + if ((Integer) itemId == 3) { + return "tree item tooltip"; + } + return ""; + } + }); + // Expand whole tree for (Object id : tree.rootItemIds()) { tree.expandItemsRecursively(id); diff --git a/uitest/src/com/vaadin/tests/components/ui/TooltipConfiguration.html b/uitest/src/com/vaadin/tests/components/ui/TooltipConfiguration.html index e41cf5e176..338e4a2c5b 100644 --- a/uitest/src/com/vaadin/tests/components/ui/TooltipConfiguration.html +++ b/uitest/src/com/vaadin/tests/components/ui/TooltipConfiguration.html @@ -4,12 +4,12 @@ -New Test +TooltipConfiguration - + @@ -43,9 +43,9 @@ - + - + @@ -140,6 +140,7 @@ +
New Test
TooltipConfiguration
open
assertElementNotPresentassertElementPositionLeft vaadin=runcomvaadintestscomponentsuiTooltipConfiguration::Root/VTooltip[0]/FlowPanel[0]/domChild[1]-1000
vaadin=runcomvaadintestscomponentsuiTooltipConfiguration::Root/VTooltip[0]/FlowPanel[0]/domChild[1] 100
diff --git a/uitest/src/com/vaadin/tests/components/window/TooltipInWindow.html b/uitest/src/com/vaadin/tests/components/window/TooltipInWindow.html index 63e371e379..575eb652b7 100644 --- a/uitest/src/com/vaadin/tests/components/window/TooltipInWindow.html +++ b/uitest/src/com/vaadin/tests/components/window/TooltipInWindow.html @@ -44,9 +44,9 @@ - assertElementNotPresent + assertElementPositionLeft vaadin=runcomvaadintestscomponentswindowTooltipInWindow::Root/VTooltip[0] - + -1000 @@ -76,10 +76,11 @@ - assertElementNotPresent + assertElementPositionLeft vaadin=runcomvaadintestscomponentswindowTooltipInWindow::Root/VTooltip[0]/FlowPanel[0]/domChild[1] - + -1000 + diff --git a/uitest/src/com/vaadin/tests/fieldgroup/IntegerRangeValidator.html b/uitest/src/com/vaadin/tests/fieldgroup/IntegerRangeValidator.html index b7c40b4d9e..7c6f9ceb39 100644 --- a/uitest/src/com/vaadin/tests/fieldgroup/IntegerRangeValidator.html +++ b/uitest/src/com/vaadin/tests/fieldgroup/IntegerRangeValidator.html @@ -1,17 +1,17 @@ - - - - - - - New Test - - - - - - - + + + + + + +IntegerRangeValidator + + +
New Test
+ + + + @@ -82,12 +82,7 @@ - - - - - - + @@ -95,7 +90,7 @@ -
IntegerRangeValidator
open /run/com.vaadin.tests.fieldgroup.BasicPersonForm?restartApplication
showTooltipvaadin=runcomvaadintestsfieldgroupBasicPersonForm::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[5]/VTextField[0]
waitForElementPresentvaadin=runcomvaadintestsfieldgroupBasicPersonForm::Root/VTooltip[0]vaadin=runcomvaadintestsfieldgroupBasicPersonForm::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[5]/VTextField[0]
vaadin=runcomvaadintestsfieldgroupBasicPersonForm::Root/VTooltip[0]/FlowPanel[0]/VErrorMessage[0]/HTML[0] Must be between 0 and 150, -1 is not
- - + + + -- cgit v1.2.3 From aae2fa65316a228f7e1c17bd976ab0cbcc22543c Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Fri, 9 Aug 2013 20:35:54 +0300 Subject: Add StringToLongConverter for automatic handling of long values (#12291) Change-Id: Ia2865b199303c0e35b562120f1c916f288aa458a --- .../util/converter/DefaultConverterFactory.java | 2 + .../data/util/converter/StringToLongConverter.java | 78 ++++++++++++++++++++++ .../data/converter/TestStringToLongConverter.java | 68 +++++++++++++++++++ .../abstractfield/DefaultConverterFactory.java | 42 ++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 server/src/com/vaadin/data/util/converter/StringToLongConverter.java create mode 100644 server/tests/src/com/vaadin/tests/data/converter/TestStringToLongConverter.java diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java index 0b3cfcd1b0..4d3717e9ba 100644 --- a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java +++ b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java @@ -102,6 +102,8 @@ public class DefaultConverterFactory implements ConverterFactory { return new StringToFloatConverter(); } else if (Integer.class.isAssignableFrom(sourceType)) { return new StringToIntegerConverter(); + } else if (Long.class.isAssignableFrom(sourceType)) { + return new StringToLongConverter(); } else if (BigDecimal.class.isAssignableFrom(sourceType)) { return new StringToBigDecimalConverter(); } else if (Boolean.class.isAssignableFrom(sourceType)) { diff --git a/server/src/com/vaadin/data/util/converter/StringToLongConverter.java b/server/src/com/vaadin/data/util/converter/StringToLongConverter.java new file mode 100644 index 0000000000..3532336787 --- /dev/null +++ b/server/src/com/vaadin/data/util/converter/StringToLongConverter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2013 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.data.util.converter; + +import java.text.NumberFormat; +import java.util.Locale; + +/** + * A converter that converts from {@link String} to {@link Long} and back. Uses + * the given locale and a {@link NumberFormat} instance for formatting and + * parsing. + *

+ * Override and overwrite {@link #getFormat(Locale)} to use a different format. + *

+ * + * @author Vaadin Ltd + * @since 7.2 + */ +public class StringToLongConverter extends + AbstractStringToNumberConverter { + + /** + * Returns the format used by + * {@link #convertToPresentation(Long, Class, Locale)} and + * {@link #convertToModel(String, Class, Locale)} + * + * @param locale + * The locale to use + * @return A NumberFormat instance + */ + @Override + protected NumberFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + return NumberFormat.getIntegerInstance(locale); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.lang.Class, java.util.Locale) + */ + @Override + public Long convertToModel(String value, Class targetType, + Locale locale) throws ConversionException { + Number n = convertToNumber(value, targetType, locale); + return n == null ? null : n.longValue(); + + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ + @Override + public Class getModelType() { + return Long.class; + } + +} diff --git a/server/tests/src/com/vaadin/tests/data/converter/TestStringToLongConverter.java b/server/tests/src/com/vaadin/tests/data/converter/TestStringToLongConverter.java new file mode 100644 index 0000000000..18e2ed06c0 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/data/converter/TestStringToLongConverter.java @@ -0,0 +1,68 @@ +package com.vaadin.tests.data.converter; + +import java.util.Locale; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ReverseConverter; +import com.vaadin.data.util.converter.StringToLongConverter; + +public class TestStringToLongConverter extends TestCase { + + StringToLongConverter converter = new StringToLongConverter(); + Converter reverseConverter = new ReverseConverter( + converter); + + public void testNullConversion() { + assertEquals(null, converter.convertToModel(null, Long.class, null)); + } + + public void testReverseNullConversion() { + assertEquals(null, + reverseConverter.convertToModel(null, String.class, null)); + } + + public void testEmptyStringConversion() { + assertEquals(null, converter.convertToModel("", Long.class, null)); + } + + public void testValueConversion() { + assertEquals(Long.valueOf(10), + converter.convertToModel("10", Long.class, null)); + } + + public void testReverseValueConversion() { + assertEquals(reverseConverter.convertToModel(10L, String.class, null), + "10"); + } + + public void testExtremeLongValueConversion() { + long l = converter.convertToModel("9223372036854775807", Long.class, + null); + Assert.assertEquals(Long.MAX_VALUE, l); + l = converter.convertToModel("-9223372036854775808", Long.class, null); + assertEquals(Long.MIN_VALUE, l); + } + + public void testExtremeReverseLongValueConversion() { + String str = reverseConverter.convertToModel(Long.MAX_VALUE, + String.class, Locale.ENGLISH); + Assert.assertEquals("9,223,372,036,854,775,807", str); + str = reverseConverter.convertToModel(Long.MIN_VALUE, String.class, + null); + Assert.assertEquals("-9,223,372,036,854,775,808", str); + } + + public void testOutOfBoundsValueConversion() { + // Long.MAX_VALUE+1 is converted to Long.MAX_VALUE + long l = converter.convertToModel("9223372036854775808", Long.class, + null); + Assert.assertEquals(Long.MAX_VALUE, l); + // Long.MIN_VALUE-1 is converted to Long.MIN_VALUE + l = converter.convertToModel("-9223372036854775809", Long.class, null); + assertEquals(Long.MIN_VALUE, l); + + } +} diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractfield/DefaultConverterFactory.java b/server/tests/src/com/vaadin/tests/server/component/abstractfield/DefaultConverterFactory.java index bac024725f..99397e9e8f 100644 --- a/server/tests/src/com/vaadin/tests/server/component/abstractfield/DefaultConverterFactory.java +++ b/server/tests/src/com/vaadin/tests/server/component/abstractfield/DefaultConverterFactory.java @@ -43,6 +43,33 @@ public class DefaultConverterFactory extends TestCase { } + public static class LongBean { + long l1; + Long l2; + + public LongBean(long l1, Long l2) { + this.l1 = l1; + this.l2 = l2; + } + + public long getL1() { + return l1; + } + + public void setL1(long l1) { + this.l1 = l1; + } + + public Long getL2() { + return l2; + } + + public void setL2(Long l2) { + this.l2 = l2; + } + + } + Person paulaBean = new Person("Paula", "Brilliant", "paula@brilliant.com", 34, Sex.FEMALE, new Address("Paula street 1", 12345, "P-town", Country.FINLAND)); @@ -68,6 +95,21 @@ public class DefaultConverterFactory extends TestCase { assertEquals(24f, tf.getPropertyDataSource().getValue()); } + public void testLongConversion() { + VaadinSession sess = new AlwaysLockedVaadinSession(null); + VaadinSession.setCurrent(sess); + + TextField tf = new TextField(); + tf.setLocale(new Locale("en", "US")); + tf.setPropertyDataSource(new MethodProperty(new LongBean(12, + 1982739187238L), "l2")); + assertEquals("1,982,739,187,238", tf.getValue()); + tf.setValue("1982739187239"); + assertEquals("1,982,739,187,239", tf.getValue()); + assertEquals(1982739187239L, tf.getConvertedValue()); + assertEquals(1982739187239L, tf.getPropertyDataSource().getValue()); + } + public void testDefaultNumberConversion() { VaadinSession app = new AlwaysLockedVaadinSession(null); VaadinSession.setCurrent(app); -- cgit v1.2.3 From c14334f284c7e7c344b2983726b9242e3ef8562e Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 2 Sep 2013 16:48:15 +0300 Subject: Rewrite client request into JSON (#9269, #11257) Change-Id: I0001d54f890ad8e5787d1f6c076d1f1d75dd32d2 --- .../com/vaadin/client/ApplicationConnection.java | 70 +++------- .../communication/AtmospherePushConnection.java | 18 +-- .../client/communication/PushConnection.java | 7 +- .../server/communication/ServerRpcHandler.java | 153 ++++++++++----------- .../vaadin/server/communication/UIInitHandler.java | 3 +- .../com/vaadin/shared/ApplicationConstants.java | 14 ++ 6 files changed, 121 insertions(+), 144 deletions(-) diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 2865e04757..364ce4521d 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -90,6 +90,7 @@ import com.vaadin.client.ui.ui.UIConnector; import com.vaadin.client.ui.window.WindowConnector; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.JsonConstants; import com.vaadin.shared.Version; import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; import com.vaadin.shared.communication.MethodInvocation; @@ -136,10 +137,6 @@ public class ApplicationConnection { public static final String ERROR_CLASSNAME_EXT = "-error"; - public static final char VAR_BURST_SEPARATOR = '\u001d'; - - public static final char VAR_ESCAPE_CHARACTER = '\u001b'; - /** * A string that, if found in a non-JSON response to a UIDL request, will * cause the browser to refresh the page. If followed by a colon, optional @@ -675,7 +672,7 @@ public class ApplicationConnection { }-*/; protected void repaintAll() { - makeUidlRequest("", getRepaintAllParameters()); + makeUidlRequest(new JSONArray(), getRepaintAllParameters()); } /** @@ -706,20 +703,23 @@ public class ApplicationConnection { /** * Makes an UIDL request to the server. * - * @param requestData - * Data that is passed to the server. + * @param reqInvocations + * Data containing RPC invocations and all related information. * @param extraParams * Parameters that are added as GET parameters to the url. * Contains key=value pairs joined by & characters or is empty if * no parameters should be added. Should not start with any * special character. */ - protected void makeUidlRequest(final String requestData, + protected void makeUidlRequest(final JSONArray reqInvocations, final String extraParams) { startRequest(); - // Security: double cookie submission pattern - final String payload = getCsrfToken() + VAR_BURST_SEPARATOR - + requestData; + + JSONObject payload = new JSONObject(); + payload.put(ApplicationConstants.CSRF_TOKEN, new JSONString( + getCsrfToken())); + payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations); + VConsole.log("Making UIDL Request with params: " + payload); String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX + ApplicationConstants.UIDL_PATH + '/'); @@ -743,7 +743,7 @@ public class ApplicationConnection { * @param payload * The contents of the request to send */ - protected void doUidlRequest(final String uri, final String payload) { + protected void doUidlRequest(final String uri, final JSONObject payload) { RequestCallback requestCallback = new RequestCallback() { @Override public void onError(Request request, Throwable exception) { @@ -906,14 +906,14 @@ public class ApplicationConnection { * @throws RequestException * if the request could not be sent */ - protected void doAjaxRequest(String uri, String payload, + protected void doAjaxRequest(String uri, JSONObject payload, RequestCallback requestCallback) throws RequestException { RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); // TODO enable timeout // rb.setTimeoutMillis(timeoutMillis); // TODO this should be configurable - rb.setHeader("Content-Type", "text/plain;charset=utf-8"); - rb.setRequestData(payload); + rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE); + rb.setRequestData(payload.toString()); rb.setCallback(requestCallback); final Request request = rb.send(); @@ -2468,15 +2468,13 @@ public class ApplicationConnection { */ private void buildAndSendVariableBurst( LinkedHashMap pendingInvocations) { - final StringBuffer req = new StringBuffer(); - while (!pendingInvocations.isEmpty()) { + JSONArray reqJson = new JSONArray(); + if (!pendingInvocations.isEmpty()) { if (ApplicationConfiguration.isDebugMode()) { Util.logVariableBurst(this, pendingInvocations.values()); } - JSONArray reqJson = new JSONArray(); - for (MethodInvocation invocation : pendingInvocations.values()) { JSONArray invocationJson = new JSONArray(); invocationJson.set(0, @@ -2515,9 +2513,6 @@ public class ApplicationConnection { reqJson.set(reqJson.size(), invocationJson); } - // escape burst separators (if any) - req.append(escapeBurstContents(reqJson.toString())); - pendingInvocations.clear(); // Keep tag string short lastInvocationTag = 0; @@ -2541,7 +2536,7 @@ public class ApplicationConnection { getConfiguration().setWidgetsetVersionSent(); } - makeUidlRequest(req.toString(), extraParams); + makeUidlRequest(reqJson, extraParams); } private boolean isJavascriptRpc(MethodInvocation invocation) { @@ -2784,35 +2779,6 @@ public class ApplicationConnection { addVariableToQueue(paintableId, variableName, values, immediate); } - /** - * Encode burst separator characters in a String for transport over the - * network. This protects from separator injection attacks. - * - * @param value - * to encode - * @return encoded value - */ - protected String escapeBurstContents(String value) { - final StringBuilder result = new StringBuilder(); - for (int i = 0; i < value.length(); ++i) { - char character = value.charAt(i); - switch (character) { - case VAR_ESCAPE_CHARACTER: - // fall-through - escape character is duplicated - case VAR_BURST_SEPARATOR: - result.append(VAR_ESCAPE_CHARACTER); - // encode as letters for easier reading - result.append(((char) (character + 0x30))); - break; - default: - // the char is not a special one - add it to the result as is - result.append(character); - break; - } - } - return result.toString(); - } - /** * Does absolutely nothing. Replaced by {@link LayoutManager}. * diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java index 20ccd45173..94ea0aaab2 100644 --- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java +++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Command; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; @@ -109,7 +110,7 @@ public class AtmospherePushConnection implements PushConnection { private JavaScriptObject socket; - private ArrayList messageQueue = new ArrayList(); + private ArrayList messageQueue = new ArrayList(); private State state = State.CONNECT_PENDING; @@ -190,14 +191,8 @@ public class AtmospherePushConnection implements PushConnection { } } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.communication.PushConenction#push(java.lang.String) - */ @Override - public void push(String message) { + public void push(JSONObject message) { switch (state) { case CONNECT_PENDING: assert isActive(); @@ -209,12 +204,13 @@ public class AtmospherePushConnection implements PushConnection { VConsole.log("Sending push message: " + message); if (transport.equals("websocket")) { - FragmentedMessage fragmented = new FragmentedMessage(message); + FragmentedMessage fragmented = new FragmentedMessage( + message.toString()); while (fragmented.hasNextFragment()) { doPush(socket, fragmented.getNextFragment()); } } else { - doPush(socket, message); + doPush(socket, message.toString()); } break; case DISCONNECT_PENDING: @@ -235,7 +231,7 @@ public class AtmospherePushConnection implements PushConnection { switch (state) { case CONNECT_PENDING: state = State.CONNECTED; - for (String message : messageQueue) { + for (JSONObject message : messageQueue) { push(message); } messageQueue.clear(); diff --git a/client/src/com/vaadin/client/communication/PushConnection.java b/client/src/com/vaadin/client/communication/PushConnection.java index a7eba224be..ba79af9d2c 100644 --- a/client/src/com/vaadin/client/communication/PushConnection.java +++ b/client/src/com/vaadin/client/communication/PushConnection.java @@ -16,6 +16,7 @@ package com.vaadin.client.communication; +import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Command; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; @@ -53,14 +54,14 @@ public interface PushConnection { * replay those messages in the original order when the connection has been * established. * - * @param message - * the message to push + * @param payload + * the payload to push * @throws IllegalStateException * if this connection is not active * * @see #isActive() */ - public void push(String message); + public void push(JSONObject payload); /** * Checks whether this push connection is in a state where it can push diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java index 3cc85909ee..e52ef6d037 100644 --- a/server/src/com/vaadin/server/communication/ServerRpcHandler.java +++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java @@ -20,8 +20,6 @@ import java.io.IOException; import java.io.Reader; import java.io.Serializable; import java.lang.reflect.Type; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,6 +30,7 @@ import java.util.logging.Logger; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import com.vaadin.server.ClientConnector; import com.vaadin.server.JsonCodec; @@ -62,10 +61,59 @@ import com.vaadin.ui.UI; */ public class ServerRpcHandler implements Serializable { - /* Variable records indexes */ - public static final char VAR_BURST_SEPARATOR = '\u001d'; + /** + * A data transfer object representing an RPC request sent by the client + * side. + * + * @since 7.2 + * @author Vaadin Ltd + */ + public static class RpcRequest { + + private final String csrfToken; + private final JSONArray invocations; + private final JSONObject json; - public static final char VAR_ESCAPE_CHARACTER = '\u001b'; + public RpcRequest(String jsonString) throws JSONException { + json = new JSONObject(jsonString); + csrfToken = json.getString(ApplicationConstants.CSRF_TOKEN); + invocations = new JSONArray( + json.getString(ApplicationConstants.RPC_INVOCATIONS)); + } + + /** + * Gets the CSRF security token (double submit cookie) for this request. + * + * @return the CSRF security token for this current change request + */ + public String getCsrfToken() { + return csrfToken; + } + + /** + * Gets the data to recreate the RPC as requested by the client side. + * + * @return the data describing which RPC should be made, and all their + * data + */ + public JSONArray getRpcInvocationsData() { + return invocations; + } + + /** + * Gets the entire request in JSON format, as it was received from the + * client. + *

+ * Note: This is a shared reference - any modifications made + * will be shared. + * + * @return the raw JSON object that was received from the client + * + */ + public JSONObject getRawJson() { + return json; + } + } private static final int MAX_BUFFER_SIZE = 64 * 1024; @@ -90,45 +138,42 @@ public class ServerRpcHandler implements Serializable { throws IOException, InvalidUIDLSecurityKeyException, JSONException { ui.getSession().setLastRequestTimestamp(System.currentTimeMillis()); - String changes = getMessage(reader); - - final String[] bursts = changes.split(String - .valueOf(VAR_BURST_SEPARATOR)); + String changeMessage = getMessage(reader); - if (bursts.length > 2) { - throw new RuntimeException( - "Multiple variable bursts not supported in Vaadin 7"); - } else if (bursts.length <= 1) { + if (changeMessage == null || changeMessage.equals("")) { // The client sometimes sends empty messages, this is probably a bug return; } + RpcRequest rpcRequest = new RpcRequest(changeMessage); + // Security: double cookie submission pattern unless disabled by // property - if (!VaadinService.isCsrfTokenValid(ui.getSession(), bursts[0])) { + if (!VaadinService.isCsrfTokenValid(ui.getSession(), + rpcRequest.getCsrfToken())) { throw new InvalidUIDLSecurityKeyException(""); } - handleBurst(ui, unescapeBurst(bursts[1])); + handleInvocations(ui, rpcRequest.getRpcInvocationsData()); } /** - * Processes a message burst received from the client. - * - * A burst can contain any number of RPC calls, including legacy variable - * change calls that are processed separately. - * + * Processes invocations data received from the client. + *

+ * The invocations data can contain any number of RPC calls, including + * legacy variable change calls that are processed separately. + *

* Consecutive changes to the value of the same variable are combined and * changeVariables() is only called once for them. This preserves the Vaadin * 6 semantics for components and add-ons that do not use Vaadin 7 RPC * directly. * - * @param source * @param uI - * the UI receiving the burst - * @param burst - * the content of the burst as a String to be parsed + * the UI receiving the invocations data + * @param invocationsData + * JSON containing all information needed to execute all + * requested RPC calls. */ - private void handleBurst(UI uI, String burst) { + private void handleInvocations(UI uI, JSONArray invocationsData) { // TODO PUSH Refactor so that this is not needed LegacyCommunicationManager manager = uI.getSession() .getCommunicationManager(); @@ -137,7 +182,7 @@ public class ServerRpcHandler implements Serializable { Set enabledConnectors = new HashSet(); List invocations = parseInvocations( - uI.getConnectorTracker(), burst); + uI.getConnectorTracker(), invocationsData); for (MethodInvocation invocation : invocations) { final ClientConnector connector = manager.getConnector(uI, invocation.getConnectorId()); @@ -250,21 +295,19 @@ public class ServerRpcHandler implements Serializable { } /** - * Parse a message burst from the client into a list of MethodInvocation - * instances. + * Parse JSON from the client into a list of MethodInvocation instances. * * @param connectorTracker * The ConnectorTracker used to lookup connectors - * @param burst - * message string (JSON) + * @param invocationsJson + * JSON containing all information needed to execute all + * requested RPC calls. * @return list of MethodInvocation to perform * @throws JSONException */ private List parseInvocations( - ConnectorTracker connectorTracker, String burst) + ConnectorTracker connectorTracker, JSONArray invocationsJson) throws JSONException { - JSONArray invocationsJson = new JSONArray(burst); - ArrayList invocations = new ArrayList(); MethodInvocation previousInvocation = null; @@ -403,50 +446,6 @@ public class ServerRpcHandler implements Serializable { owner.changeVariables(source, m); } - /** - * Unescape encoded burst separator characters in a burst received from the - * client. This protects from separator injection attacks. - * - * @param encodedValue - * to decode - * @return decoded value - */ - protected String unescapeBurst(String encodedValue) { - final StringBuilder result = new StringBuilder(); - final StringCharacterIterator iterator = new StringCharacterIterator( - encodedValue); - char character = iterator.current(); - while (character != CharacterIterator.DONE) { - if (VAR_ESCAPE_CHARACTER == character) { - character = iterator.next(); - switch (character) { - case VAR_ESCAPE_CHARACTER + 0x30: - // escaped escape character - result.append(VAR_ESCAPE_CHARACTER); - break; - case VAR_BURST_SEPARATOR + 0x30: - // +0x30 makes these letters for easier reading - result.append((char) (character - 0x30)); - break; - case CharacterIterator.DONE: - // error - throw new RuntimeException( - "Communication error: Unexpected end of message"); - default: - // other escaped character - probably a client-server - // version mismatch - throw new RuntimeException( - "Invalid escaped character from the client - check that the widgetset and server versions match"); - } - } else { - // not a special character - add it to the result as is - result.append(character); - } - character = iterator.next(); - } - return result.toString(); - } - protected String getMessage(Reader reader) throws IOException { StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE); diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index b2c17d00c1..6ab9d9dc58 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -37,6 +37,7 @@ import com.vaadin.server.VaadinResponse; import com.vaadin.server.VaadinService; import com.vaadin.server.VaadinSession; import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.JsonConstants; import com.vaadin.shared.communication.PushMode; import com.vaadin.shared.ui.ui.Transport; import com.vaadin.shared.ui.ui.UIConstants; @@ -107,7 +108,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { static boolean commitJsonResponse(VaadinRequest request, VaadinResponse response, String json) throws IOException { // The response was produced without errors so write it to the client - response.setContentType("application/json; charset=UTF-8"); + response.setContentType(JsonConstants.JSON_CONTENT_TYPE); // Ensure that the browser does not cache UIDL responses. // iOS 6 Safari requires this (#9732) diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java index d7de435735..1dff1e19cc 100644 --- a/shared/src/com/vaadin/shared/ApplicationConstants.java +++ b/shared/src/com/vaadin/shared/ApplicationConstants.java @@ -82,4 +82,18 @@ public class ApplicationConstants implements Serializable { * Name of the parameter used to transmit the CSRF token. */ public static final String CSRF_TOKEN_PARAMETER = "v-csrfToken"; + + /** + * The name of the parameter used to transmit RPC invocations + * + * @since 7.2 + */ + public static final String RPC_INVOCATIONS = "rpc"; + + /** + * The name of the parameter used to transmit the CSRF token + * + * @since 7.2 + */ + public static final String CSRF_TOKEN = "csrfToken"; } -- cgit v1.2.3 From 8fa480ae363a43e1f3249dc19d60d8bbe1f00b23 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Fri, 6 Sep 2013 11:26:36 +0200 Subject: Makes sure the aria-invalid attribute is removed when the caption is removed and not updated (#12517) Change-Id: I750dff060469a656e199985984794f186365f121 --- client/src/com/vaadin/client/VCaption.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/com/vaadin/client/VCaption.java b/client/src/com/vaadin/client/VCaption.java index b5d0230087..d96e6ed653 100644 --- a/client/src/com/vaadin/client/VCaption.java +++ b/client/src/com/vaadin/client/VCaption.java @@ -114,6 +114,8 @@ public class VCaption extends HTML { if (null != owner) { AriaHelper.bindCaption(owner.getWidget(), null); + AriaHelper.handleInputInvalid(owner.getWidget(), false); + AriaHelper.handleInputRequired(owner.getWidget(), false); } } -- cgit v1.2.3 From 8f63873fde0399d47e7dce8156485f03526f3a60 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Mon, 9 Sep 2013 09:59:00 +0200 Subject: Handles Tooltip on focus for ComboBox (#12536) Change-Id: I3b140bd6772ec25c57be0f519f180b626c5ff1d2 --- client/src/com/vaadin/client/ui/VFilterSelect.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 7aac581008..a4041cc34d 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -1747,6 +1747,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (client.hasEventListeners(this, EventId.FOCUS)) { client.updateVariable(paintableId, EventId.FOCUS, "", true); } + + ComponentConnector connector = ConnectorMap.get(client).getConnector( + this); + client.getVTooltip().showAssistive( + connector.getTooltipInfo(getElement())); } /** -- cgit v1.2.3 From ba76f5660bc74fc7a96b93944a79f95366c53b8d Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Thu, 5 Sep 2013 10:10:37 +0200 Subject: Prevent exception in VWindow without assistive description (#12515) Change-Id: Id16584ac2aec95981de31d713b56de42ca0cfae2 --- client/src/com/vaadin/client/ui/VWindow.java | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 18df2222a4..03ad8d03c8 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -1323,6 +1323,8 @@ public class VWindow extends VWindowOverlay implements * Allows to specify which connectors contain the description for the * window. Text contained in the widgets of the connectors will be read by * assistive devices when it is opened. + *

+ * When the provided array is empty, an existing description is removed. * * @param connectors * with the connectors of the widgets to use as description @@ -1331,20 +1333,26 @@ public class VWindow extends VWindowOverlay implements if (connectors != null) { assistiveConnectors = connectors; - Id[] ids = new Id[connectors.length]; - for (int index = 0; index < connectors.length; index++) { - if (connectors[index] == null) { - throw new IllegalArgumentException( - "All values in parameter description need to be non-null"); + if (connectors.length == 0) { + Roles.getDialogRole().removeAriaDescribedbyProperty( + getElement()); + } else { + Id[] ids = new Id[connectors.length]; + for (int index = 0; index < connectors.length; index++) { + if (connectors[index] == null) { + throw new IllegalArgumentException( + "All values in parameter description need to be non-null"); + } + + Element element = ((ComponentConnector) connectors[index]) + .getWidget().getElement(); + AriaHelper.ensureHasId(element); + ids[index] = Id.of(element); } - Element element = ((ComponentConnector) connectors[index]) - .getWidget().getElement(); - AriaHelper.ensureHasId(element); - ids[index] = Id.of(element); + Roles.getDialogRole().setAriaDescribedbyProperty(getElement(), + ids); } - - Roles.getDialogRole().setAriaDescribedbyProperty(getElement(), ids); } else { throw new IllegalArgumentException( "Parameter description must be non-null"); -- cgit v1.2.3 From 53282726c5769bf763beb5d8576c71e0e7b5bef3 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Fri, 23 Aug 2013 15:59:23 +0300 Subject: Ignore RPC calls from components that are concurrently removed (#12337) Change-Id: I8b97444d33b9535b9073fd705fed15a6cc2992e7 --- .../com/vaadin/client/ApplicationConnection.java | 49 +++++++- .../server/communication/ServerRpcHandler.java | 68 ++++++++--- .../vaadin/server/communication/UidlWriter.java | 4 + server/src/com/vaadin/ui/ConnectorTracker.java | 135 +++++++++++++++++++++ .../TableRemovedQuicklySendsInvalidRpcCalls.java | 107 ++++++++++++++++ .../com/vaadin/shared/ApplicationConstants.java | 8 ++ 6 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 364ce4521d..cd1f8a206d 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -49,6 +49,7 @@ import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.http.client.URL; import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONNumber; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.regexp.shared.MatchResult; @@ -267,8 +268,6 @@ public class ApplicationConnection { /** Event bus for communication events */ private EventBus eventBus = GWT.create(SimpleEventBus.class); - private int lastResponseId = -1; - /** * The communication handler methods are called at certain points during * communication with the server. This allows for making add-ons that keep @@ -719,6 +718,8 @@ public class ApplicationConnection { payload.put(ApplicationConstants.CSRF_TOKEN, new JSONString( getCsrfToken())); payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations); + payload.put(ApplicationConstants.SERVER_SYNC_ID, new JSONNumber( + lastSeenServerSyncId)); VConsole.log("Making UIDL Request with params: " + payload); String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX @@ -978,6 +979,29 @@ public class ApplicationConnection { */ private ValueMap serverTimingInfo; + /** + * Holds the last seen response id given by the server. + *

+ * The server generates a strictly increasing id for each response to each + * request from the client. This ID is then replayed back to the server on + * each request. This helps the server in knowing in what state the client + * is, and compare it to its own state. In short, it helps with concurrent + * changes between the client and server. + *

+ * Initial value, i.e. no responses received from the server, is + * {@link #UNDEFINED_SYNC_ID} ({@value #UNDEFINED_SYNC_ID}). This happens + * between the bootstrap HTML being loaded and the first UI being rendered; + */ + private int lastSeenServerSyncId = UNDEFINED_SYNC_ID; + + /** + * The value of an undefined sync id. + *

+ * This must be -1, because of the contract in + * {@link #getLastResponseId()} + */ + private static final int UNDEFINED_SYNC_ID = -1; + static final int MAX_CSS_WAITS = 100; protected void handleWhenCSSLoaded(final String jsonText, @@ -1258,7 +1282,13 @@ public class ApplicationConnection { * @return and id identifying the response */ public int getLastResponseId() { - return lastResponseId; + /* + * The discrepancy between field name and getter name is simply historic + * - API can't be changed, but the field was repurposed in a more + * general, yet compatible, use. "Response id" was deemed unsuitable a + * name, so it was called "server sync id" instead. + */ + return lastSeenServerSyncId; } protected void handleUIDLMessage(final Date start, final String jsonText, @@ -1285,6 +1315,17 @@ public class ApplicationConnection { VConsole.log("Handling message from server"); eventBus.fireEvent(new ResponseHandlingStartedEvent(this)); + if (json.containsKey(ApplicationConstants.SERVER_SYNC_ID)) { + int syncId = json.getInt(ApplicationConstants.SERVER_SYNC_ID); + + assert (lastSeenServerSyncId == UNDEFINED_SYNC_ID || syncId == lastSeenServerSyncId + 1) : "Newly retrieved server sync id was not exactly one larger than the previous one (new: " + + syncId + ", last seen: " + lastSeenServerSyncId + ")"; + + lastSeenServerSyncId = syncId; + } else { + VConsole.error("Server response didn't contain an id."); + } + // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); @@ -1293,8 +1334,6 @@ public class ApplicationConnection { return; } - lastResponseId++; - final MultiStepDuration handleUIDLDuration = new MultiStepDuration(); // Get security key diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java index e52ef6d037..eff9ceebf4 100644 --- a/server/src/com/vaadin/server/communication/ServerRpcHandler.java +++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java @@ -72,11 +72,13 @@ public class ServerRpcHandler implements Serializable { private final String csrfToken; private final JSONArray invocations; + private final int syncId; private final JSONObject json; public RpcRequest(String jsonString) throws JSONException { json = new JSONObject(jsonString); csrfToken = json.getString(ApplicationConstants.CSRF_TOKEN); + syncId = json.getInt(ApplicationConstants.SERVER_SYNC_ID); invocations = new JSONArray( json.getString(ApplicationConstants.RPC_INVOCATIONS)); } @@ -100,6 +102,16 @@ public class ServerRpcHandler implements Serializable { return invocations; } + /** + * Gets the sync id last seen by the client. + * + * @return the last sync id given by the server, according to the + * client's request + */ + public int getSyncId() { + return syncId; + } + /** * Gets the entire request in JSON format, as it was received from the * client. @@ -153,7 +165,11 @@ public class ServerRpcHandler implements Serializable { rpcRequest.getCsrfToken())) { throw new InvalidUIDLSecurityKeyException(""); } - handleInvocations(ui, rpcRequest.getRpcInvocationsData()); + handleInvocations(ui, rpcRequest.getSyncId(), + rpcRequest.getRpcInvocationsData()); + + ui.getConnectorTracker().cleanConcurrentlyRemovedConnectorIds( + rpcRequest.getSyncId()); } /** @@ -169,11 +185,15 @@ public class ServerRpcHandler implements Serializable { * * @param uI * the UI receiving the invocations data + * @param lastSyncIdSeenByClient + * the most recent sync id the client has seen at the time the + * request was sent * @param invocationsData * JSON containing all information needed to execute all * requested RPC calls. */ - private void handleInvocations(UI uI, JSONArray invocationsData) { + private void handleInvocations(UI uI, int lastSyncIdSeenByClient, + JSONArray invocationsData) { // TODO PUSH Refactor so that this is not needed LegacyCommunicationManager manager = uI.getSession() .getCommunicationManager(); @@ -182,7 +202,8 @@ public class ServerRpcHandler implements Serializable { Set enabledConnectors = new HashSet(); List invocations = parseInvocations( - uI.getConnectorTracker(), invocationsData); + uI.getConnectorTracker(), invocationsData, + lastSyncIdSeenByClient); for (MethodInvocation invocation : invocations) { final ClientConnector connector = manager.getConnector(uI, invocation.getConnectorId()); @@ -302,12 +323,15 @@ public class ServerRpcHandler implements Serializable { * @param invocationsJson * JSON containing all information needed to execute all * requested RPC calls. + * @param lastSyncIdSeenByClient + * the most recent sync id the client has seen at the time the + * request was sent * @return list of MethodInvocation to perform * @throws JSONException */ private List parseInvocations( - ConnectorTracker connectorTracker, JSONArray invocationsJson) - throws JSONException { + ConnectorTracker connectorTracker, JSONArray invocationsJson, + int lastSyncIdSeenByClient) throws JSONException { ArrayList invocations = new ArrayList(); MethodInvocation previousInvocation = null; @@ -317,7 +341,8 @@ public class ServerRpcHandler implements Serializable { JSONArray invocationJson = invocationsJson.getJSONArray(i); MethodInvocation invocation = parseInvocation(invocationJson, - previousInvocation, connectorTracker); + previousInvocation, connectorTracker, + lastSyncIdSeenByClient); if (invocation != null) { // Can be null if the invocation was a legacy invocation and it // was merged with the previous one or if the invocation was @@ -331,7 +356,8 @@ public class ServerRpcHandler implements Serializable { private MethodInvocation parseInvocation(JSONArray invocationJson, MethodInvocation previousInvocation, - ConnectorTracker connectorTracker) throws JSONException { + ConnectorTracker connectorTracker, long lastSyncIdSeenByClient) + throws JSONException { String connectorId = invocationJson.getString(0); String interfaceName = invocationJson.getString(1); String methodName = invocationJson.getString(2); @@ -339,18 +365,22 @@ public class ServerRpcHandler implements Serializable { if (connectorTracker.getConnector(connectorId) == null && !connectorId .equals(ApplicationConstants.DRAG_AND_DROP_CONNECTOR_ID)) { - getLogger() - .log(Level.WARNING, - "RPC call to " - + interfaceName - + "." - + methodName - + " received for connector " - + connectorId - + " but no such connector could be found. Resynchronizing client."); - // This is likely an out of sync issue (client tries to update a - // connector which is not present). Force resync. - connectorTracker.markAllConnectorsDirty(); + + if (!connectorTracker.connectorWasPresentAsRequestWasSent( + connectorId, lastSyncIdSeenByClient)) { + getLogger() + .log(Level.WARNING, + "RPC call to " + + interfaceName + + "." + + methodName + + " received for connector " + + connectorId + + " but no such connector could be found. Resynchronizing client."); + // This is likely an out of sync issue (client tries to update a + // connector which is not present). Force resync. + connectorTracker.markAllConnectorsDirty(); + } return null; } diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java index 60933a75c2..b46fbbf58a 100644 --- a/server/src/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/com/vaadin/server/communication/UidlWriter.java @@ -38,6 +38,7 @@ import com.vaadin.server.LegacyCommunicationManager; import com.vaadin.server.LegacyCommunicationManager.ClientCache; import com.vaadin.server.SystemMessages; import com.vaadin.server.VaadinSession; +import com.vaadin.shared.ApplicationConstants; import com.vaadin.ui.ConnectorTracker; import com.vaadin.ui.UI; @@ -98,6 +99,9 @@ public class UidlWriter implements Serializable { uiConnectorTracker.setWritingResponse(true); try { + writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID + + "\": " + uiConnectorTracker.getCurrentSyncId() + ", "); + writer.write("\"changes\" : "); JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer, diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java index 0f8ec60104..33d585adca 100644 --- a/server/src/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/com/vaadin/ui/ConnectorTracker.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -81,6 +82,16 @@ public class ConnectorTracker implements Serializable { private Map streamVariableToSeckey; + private int currentSyncId = 0; + + /** + * Map to track on which syncId each connector was removed. + * + * @see #getCurrentSyncId() + * @see #cleanConcurrentlyRemovedConnectorIds(long) + */ + private TreeMap> syncIdToUnregisteredConnectorIds = new TreeMap>(); + /** * Gets a logger for this class * @@ -170,6 +181,15 @@ public class ConnectorTracker implements Serializable { + " is not the one that was registered for that id"); } + Set unregisteredConnectorIds = syncIdToUnregisteredConnectorIds + .get(currentSyncId); + if (unregisteredConnectorIds == null) { + unregisteredConnectorIds = new HashSet(); + syncIdToUnregisteredConnectorIds.put(currentSyncId, + unregisteredConnectorIds); + } + unregisteredConnectorIds.add(connectorId); + dirtyConnectors.remove(connector); if (unregisteredConnectors.add(connector)) { if (getLogger().isLoggable(Level.FINE)) { @@ -570,12 +590,18 @@ public class ConnectorTracker implements Serializable { /** * Sets the current response write status. Connectors can not be marked as * dirty when the response is written. + *

+ * This method has a side-effect of incrementing the sync id by one (see + * {@link #getCurrentSyncId()}), if {@link #isWritingResponse()} returns + * false and writingResponse is set to + * true. * * @param writingResponse * the new response status. * * @see #markDirty(ClientConnector) * @see #isWritingResponse() + * @see #getCurrentSyncId() * * @throws IllegalArgumentException * if the new response status is the same as the previous value. @@ -587,6 +613,14 @@ public class ConnectorTracker implements Serializable { throw new IllegalArgumentException( "The old value is same as the new value"); } + + /* + * the right hand side of the && is unnecessary here because of the + * if-clause above, but rigorous coding is always rigorous coding. + */ + if (writingResponse && !this.writingResponse) { + currentSyncId++; + } this.writingResponse = writingResponse; } @@ -732,4 +766,105 @@ public class ConnectorTracker implements Serializable { } return streamVariableToSeckey.get(variable); } + + /** + * Check whether a connector was present on the client when the it was + * creating this request, but was removed server-side before the request + * arrived. + * + * @since 7.2 + * @param connectorId + * The connector id to check for whether it was removed + * concurrently or not. + * @param lastSyncIdSeenByClient + * the most recent sync id the client has seen at the time the + * request was sent + * @return true if the connector was removed before the client + * had a chance to react to it. + */ + public boolean connectorWasPresentAsRequestWasSent(String connectorId, + long lastSyncIdSeenByClient) { + + assert getConnector(connectorId) == null : "Connector " + connectorId + + " is still attached"; + + boolean clientRequestIsTooOld = lastSyncIdSeenByClient < currentSyncId; + if (clientRequestIsTooOld) { + /* + * The headMap call is present here because we're only interested in + * connectors removed "in the past" (i.e. the server has removed + * them before the client ever knew about that), since those are the + * ones that we choose to handle as a special case. + */ + /*- + * Server Client + * [#1 add table] ---------. + * \ + * [push: #2 remove table]-. `--> [adding table, storing #1] + * \ .- [table from request #1 needs more data] + * \/ + * /`-> [removing table, storing #2] + * [#1 < #2 - ignoring] <---´ + */ + for (Set unregisteredConnectors : syncIdToUnregisteredConnectorIds + .headMap(currentSyncId).values()) { + if (unregisteredConnectors.contains(connectorId)) { + return true; + } + } + } + + return false; + } + + /** + * Gets the most recently generated server sync id. + *

+ * The sync id is incremented by one whenever a new response is being + * written. This id is then sent over to the client. The client then adds + * the most recent sync id to each communication packet it sends back to the + * server. This way, the server knows at what state the client is when the + * packet is sent. If the state has changed on the server side since that, + * the server can try to adjust the way it handles the actions from the + * client side. + * + * @see #setWritingResponse(boolean) + * @see #connectorWasPresentAsRequestWasSent(String, long) + * @since 7.2 + * @return the current sync id + */ + public int getCurrentSyncId() { + return currentSyncId; + } + + /** + * Maintains the bookkeeping connector removal and concurrency by removing + * entries that have become too old. + *

+ * It is important to run this call for each transmission from the client + * , otherwise the bookkeeping gets out of date and the results form + * {@link #connectorWasPresentAsRequestWasSent(String, long)} will become + * invalid (that is, even though the client knew the component was removed, + * the aforementioned method would start claiming otherwise). + *

+ * Entries that both client and server agree upon are removed. Since + * argument is the last sync id that the client has seen from the server, we + * know that entries earlier than that cannot cause any problems anymore. + * + * @see #connectorWasPresentAsRequestWasSent(String, long) + * @since 7.2 + * @param lastSyncIdSeenByClient + * the sync id the client has most recently received from the + * server. + */ + public void cleanConcurrentlyRemovedConnectorIds(int lastSyncIdSeenByClient) { + /* + * We remove all entries _older_ than the one reported right now, + * because the remaining still contain components that might cause + * conflicts. In any case, it's better to clean up too little than too + * much, especially as the data will hardly grow into the kilobytes. + */ + syncIdToUnregisteredConnectorIds.headMap(lastSyncIdSeenByClient) + .clear(); + } } diff --git a/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java b/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java new file mode 100644 index 0000000000..b539e42efe --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2013 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.table; + +import com.vaadin.annotations.Push; +import com.vaadin.event.ItemClickEvent; +import com.vaadin.event.ItemClickEvent.ItemClickListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Table; + +@Push +public class TableRemovedQuicklySendsInvalidRpcCalls extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + addComponent(new Button("Blink a table", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + blinkTable(); + } + })); + } + + private void blinkTable() { + final Table table = new Table(); + table.setPageLength(5); + table.addContainerProperty(new Object(), String.class, null); + + for (int i = 0; i < 50; i++) { + table.addItem(new Object[] { "Row" }, new Object()); + } + + table.addItemClickListener(new ItemClickListener() { + private int i; + + @Override + public void itemClick(ItemClickEvent event) { + /* + * Ignore implementation. This is only an easy way to make the + * client-side update table's variables (by furiously clicking + * on the table row. + * + * This way, we get variable changes queued. The push call will + * then remove the Table, while the variable changes being still + * in the queue, leading to the issue as described in the + * ticket. + */ + System.out.println("clicky " + (++i)); + } + }); + + System.out.println("adding component"); + addComponent(table); + + new Thread() { + @Override + public void run() { + getSession().lock(); + try { + Thread.sleep(500); + access(new Runnable() { + @Override + public void run() { + System.out.println("removing component"); + removeComponent(table); + } + }); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + getSession().unlock(); + } + }; + }.start(); + } + + @Override + protected String getTestDescription() { + return "Adding and subsequently quickly removing a table " + + "should not leave any pending RPC calls waiting " + + "in a Timer. Issue can be reproduced by " + + "1) pressing the button 2) clicking furiously " + + "on a row in the table."; + } + + @Override + protected Integer getTicketNumber() { + return 12337; + } +} diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java index 1dff1e19cc..5ae52615bb 100644 --- a/shared/src/com/vaadin/shared/ApplicationConstants.java +++ b/shared/src/com/vaadin/shared/ApplicationConstants.java @@ -96,4 +96,12 @@ public class ApplicationConstants implements Serializable { * @since 7.2 */ public static final String CSRF_TOKEN = "csrfToken"; + + /** + * The name of the parameter used to transmit the sync id + * + * @see com.vaadin.ui.ConnectorTracker#getCurrentSyncId() + * @since 7.2 + */ + public static final String SERVER_SYNC_ID = "syncId"; } -- cgit v1.2.3 From 70649ac21a40a2b819856da39ebcfb5394ae3280 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 12 Sep 2013 20:57:01 +0300 Subject: Prepare for Atmosphere Javascript 2.0 (#12241) Atmosphere 2.0 has changed enableProtocol to default to true. Reopening a connection in Atmosphere 2.0 is signalled through an onReopen event (not present at all in Atmosphere 1.0.x) Change-Id: I6013a83a0239b6ab7535631120e442ece2d4481d --- .../communication/AtmospherePushConnection.java | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java index 94ea0aaab2..93d5d879dc 100644 --- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java +++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java @@ -223,10 +223,26 @@ public class AtmospherePushConnection implements PushConnection { return config; } - protected void onOpen(AtmosphereResponse response) { - transport = response.getTransport(); + protected void onReopen(AtmosphereResponse response) { + VConsole.log("Push connection re-established using " + transport); + onConnect(response); + } + protected void onOpen(AtmosphereResponse response) { VConsole.log("Push connection established using " + transport); + onConnect(response); + } + + /** + * Called whenever a server push connection is established (or + * re-established). + * + * @param response + * + * @since 7.2 + */ + protected void onConnect(AtmosphereResponse response) { + transport = response.getTransport(); switch (state) { case CONNECT_PENDING: @@ -417,6 +433,7 @@ public class AtmospherePushConnection implements PushConnection { reconnectInterval: 5000, maxReconnectOnClose: 10000000, trackMessageLength: true, + enableProtocol: false, messageDelimiter: String.fromCharCode(@com.vaadin.shared.communication.PushConstants::MESSAGE_DELIMITER) }; }-*/; @@ -430,6 +447,9 @@ public class AtmospherePushConnection implements PushConnection { config.onOpen = $entry(function(response) { self.@com.vaadin.client.communication.AtmospherePushConnection::onOpen(*)(response); }); + config.onReopen = $entry(function(response) { + self.@com.vaadin.client.communication.AtmospherePushConnection::onReopen(*)(response); + }); config.onMessage = $entry(function(response) { self.@com.vaadin.client.communication.AtmospherePushConnection::onMessage(*)(response); }); -- cgit v1.2.3 From 3940d851365ce4d18c599893aed7dfa4b5e0f46f Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 23 Sep 2013 16:06:17 +0300 Subject: Use explicit button for reading asynchronously created messages (#10338) Change-Id: Icde7e1f612ed60867b3d3d58aedbe36d16ad61a5 --- .../com/vaadin/tests/application/DetachOldUIOnReload.html | 15 +++++++++++++++ .../com/vaadin/tests/application/DetachOldUIOnReload.java | 13 +++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html index 038283324d..33fc46f060 100644 --- a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html +++ b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.html @@ -26,6 +26,11 @@ /run/com.vaadin.tests.application.DetachOldUIOnReload + + click + vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0] + + assertText vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_0 @@ -41,6 +46,11 @@ vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[1]/VButton[0]/domChild[0]/domChild[0] + + click + vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0] + + assertText vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_0 @@ -61,6 +71,11 @@ /run/com.vaadin.tests.application.DetachOldUIOnReload + + click + vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0] + + assertText vaadin=runcomvaadintestsapplicationDetachOldUIOnReload::PID_SLog_row_0 diff --git a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java index 7104146203..154c84b4f5 100644 --- a/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java +++ b/uitest/src/com/vaadin/tests/application/DetachOldUIOnReload.java @@ -31,10 +31,6 @@ public class DetachOldUIOnReload extends AbstractTestUIWithLog { @Override protected void setup(VaadinRequest request) { - for (String message : getSessionMessages(false)) { - log(message); - } - addComponent(new Label("This is UI " + getUIId())); addComponent(new Button("Reload page", new Button.ClickListener() { @Override @@ -42,6 +38,15 @@ public class DetachOldUIOnReload extends AbstractTestUIWithLog { getPage().reload(); } })); + addComponent(new Button("Read log messages from session", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + for (String message : getSessionMessages(false)) { + log(message); + } + } + })); } private List getSessionMessages(boolean storeIfNeeded) { -- cgit v1.2.3 From 6d0c6e094fe5ee1253701bd4c897be9e29eea11a Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 2 Sep 2013 22:37:59 +0300 Subject: Moved import resolving logic to resolvers (#11776) * made resolvers style sheet specific Change-Id: Iab7e755d9e3214896c100dfde10727a1353c4354 --- .../com/vaadin/sass/internal/ScssStylesheet.java | 78 ++++++++---- .../sass/internal/resolver/AbstractResolver.java | 132 +++++++++++++++++++++ .../internal/resolver/ClassloaderResolver.java | 4 +- .../sass/internal/resolver/FilesystemResolver.java | 4 +- .../internal/resolver/ScssStylesheetResolver.java | 7 +- .../sass/internal/resolver/VaadinResolver.java | 90 -------------- .../sass/internal/visitor/ImportNodeHandler.java | 15 +-- .../vaadin/sass/resolvers/VaadinResolverTest.java | 18 ++- 8 files changed, 213 insertions(+), 135 deletions(-) create mode 100644 theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java delete mode 100644 theme-compiler/src/com/vaadin/sass/internal/resolver/VaadinResolver.java diff --git a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java index 704425e300..7213553e18 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java +++ b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java @@ -19,10 +19,10 @@ package com.vaadin.sass.internal; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -35,8 +35,9 @@ import com.vaadin.sass.internal.handler.SCSSErrorHandler; import com.vaadin.sass.internal.parser.ParseException; import com.vaadin.sass.internal.parser.Parser; import com.vaadin.sass.internal.parser.SCSSParseException; +import com.vaadin.sass.internal.resolver.ClassloaderResolver; +import com.vaadin.sass.internal.resolver.FilesystemResolver; import com.vaadin.sass.internal.resolver.ScssStylesheetResolver; -import com.vaadin.sass.internal.resolver.VaadinResolver; import com.vaadin.sass.internal.tree.BlockNode; import com.vaadin.sass.internal.tree.MixinDefNode; import com.vaadin.sass.internal.tree.Node; @@ -63,6 +64,8 @@ public class ScssStylesheet extends Node { private String charset; + private List resolvers = new ArrayList(); + /** * Read in a file SCSS and parse it into a ScssStylesheet * @@ -101,8 +104,8 @@ public class ScssStylesheet extends Node { * @throws CSSException * @throws IOException */ - public static ScssStylesheet get(String identifier, String encoding) - throws CSSException, IOException { + public static ScssStylesheet get(String identifier, + ScssStylesheet parentStylesheet) throws CSSException, IOException { /* * The encoding to be used is passed through "encoding" parameter. the * imported children scss node will have the same encoding as their @@ -122,12 +125,22 @@ public class ScssStylesheet extends Node { SCSSDocumentHandler handler = new SCSSDocumentHandlerImpl(); ScssStylesheet stylesheet = handler.getStyleSheet(); - - InputSource source = stylesheet.resolveStylesheet(identifier); + if (parentStylesheet == null) { + // Use default resolvers + stylesheet.addResolver(new FilesystemResolver()); + stylesheet.addResolver(new ClassloaderResolver()); + } else { + // Use parent resolvers + stylesheet.setResolvers(parentStylesheet.getResolvers()); + } + InputSource source = stylesheet.resolveStylesheet(identifier, + parentStylesheet); if (source == null) { return null; } - source.setEncoding(encoding); + if (parentStylesheet != null) { + source.setEncoding(parentStylesheet.getCharset()); + } Parser parser = new Parser(); parser.setErrorHandler(new SCSSErrorHandler()); @@ -145,21 +158,10 @@ public class ScssStylesheet extends Node { return stylesheet; } - private static ScssStylesheetResolver[] resolvers = null; - - public static void setStylesheetResolvers( - ScssStylesheetResolver... styleSheetResolvers) { - resolvers = Arrays.copyOf(styleSheetResolvers, - styleSheetResolvers.length); - } - - public InputSource resolveStylesheet(String identifier) { - if (resolvers == null) { - setStylesheetResolvers(new VaadinResolver()); - } - - for (ScssStylesheetResolver resolver : resolvers) { - InputSource source = resolver.resolve(identifier); + public InputSource resolveStylesheet(String identifier, + ScssStylesheet parentStylesheet) { + for (ScssStylesheetResolver resolver : getResolvers()) { + InputSource source = resolver.resolve(parentStylesheet, identifier); if (source != null) { File f = new File(source.getURI()); setFile(f); @@ -170,6 +172,38 @@ public class ScssStylesheet extends Node { return null; } + /** + * Retrieves a list of resolvers to use when resolving imports + * + * @since 7.2 + * @return the resolvers used to resolving imports + */ + public List getResolvers() { + return Collections.unmodifiableList(resolvers); + } + + /** + * Sets the list of resolvers to use when resolving imports + * + * @since 7.2 + * @param resolvers + * the resolvers to set + */ + public void setResolvers(List resolvers) { + this.resolvers = new ArrayList(resolvers); + } + + /** + * Adds the given resolver to the resolver list + * + * @since 7.2 + * @param resolver + * The resolver to add + */ + public void addResolver(ScssStylesheetResolver resolver) { + resolvers.add(resolver); + } + /** * Applies all the visitors and compiles SCSS into Css. * diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java new file mode 100644 index 0000000000..ac11f1fa0e --- /dev/null +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2013 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.sass.internal.resolver; + +import java.io.File; +import java.io.Serializable; +import java.util.Stack; + +import org.w3c.css.sac.InputSource; + +import com.vaadin.sass.internal.ScssStylesheet; + +/** + * Base class for resolvers. Implements functionality for locating paths which + * an import can be relative to and helpers for extracting path information from + * the identifier. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public abstract class AbstractResolver implements ScssStylesheetResolver, + Serializable { + /* + * (non-Javadoc) + * + * @see + * com.vaadin.sass.internal.resolver.ScssStylesheetResolver#resolve(java + * .lang.String) + */ + @Override + public InputSource resolve(ScssStylesheet parentStylesheet, + String identifier) { + InputSource source = null; + if (parentStylesheet != null) { + StringBuilder filePathBuilder = new StringBuilder( + parentStylesheet.getFileName()); + filePathBuilder.append(File.separatorChar).append(identifier); + if (!filePathBuilder.toString().endsWith(".scss")) { + filePathBuilder.append(".scss"); + } + source = normalizeAndResolve(filePathBuilder.toString()); + } + + if (source == null) { + source = normalizeAndResolve(identifier); + } + + return source; + } + + /** + * Resolves the normalized version of the given identifier + * + * @param identifier + * The identifier to resolve + * @return An input source if the resolver found one or null otherwise + */ + protected InputSource normalizeAndResolve(String identifier) { + String normalized = normalize(identifier); + return resolveNormalized(normalized); + } + + /** + * Resolves the identifier after it has been normalized using + * {@link #normalize(String)}. + * + * @param identifier + * The normalized identifier + * @return an InputSource if the resolver found a source or null otherwise + */ + protected abstract InputSource resolveNormalized(String identifier); + + /** + * Normalizes "." and ".." from the path string where parent path segments + * can be removed. Preserve leading "..". Also ensure / is used instead of \ + * in all places. + * + * @param path + * A relative or absolute file path + * @return The normalized path + */ + protected String normalize(String path) { + + // Ensure only "/" is used, also in Windows + path = path.replace(File.separatorChar, '/'); + + // Split into segments + String[] segments = path.split("/"); + Stack result = new Stack(); + + // Replace '.' and '..' segments + for (int i = 0; i < segments.length; i++) { + if (segments[i].equals(".")) { + // Segments marked '.' are ignored + + } else if (segments[i].equals("..") && !result.isEmpty() + && !result.lastElement().equals("..")) { + // If segment is ".." then remove the previous iff the previous + // element is not a ".." and the result stack is not empty + result.pop(); + } else { + // Other segments are just added to the stack + result.push(segments[i]); + } + } + + // Reconstruct path + StringBuilder pathBuilder = new StringBuilder(); + for (int i = 0; i < result.size(); i++) { + if (i > 0) { + pathBuilder.append("/"); + } + pathBuilder.append(result.get(i)); + } + return pathBuilder.toString(); + } + +} diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java index 8711a0a3e9..68752b459a 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java @@ -20,10 +20,10 @@ import java.io.InputStream; import org.w3c.css.sac.InputSource; -public class ClassloaderResolver implements ScssStylesheetResolver { +public class ClassloaderResolver extends AbstractResolver { @Override - public InputSource resolve(String identifier) { + public InputSource resolveNormalized(String identifier) { // identifier should not have .scss, fileName should String ext = ".scss"; if (identifier.endsWith(".css")) { diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java index 9bb1969ab1..3fec33ae13 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java @@ -21,10 +21,10 @@ import java.io.InputStream; import org.w3c.css.sac.InputSource; -public class FilesystemResolver implements ScssStylesheetResolver { +public class FilesystemResolver extends AbstractResolver { @Override - public InputSource resolve(String identifier) { + public InputSource resolveNormalized(String identifier) { // identifier should not have .scss, fileName should String ext = ".scss"; if (identifier.endsWith(".css")) { diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/ScssStylesheetResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/ScssStylesheetResolver.java index 45f10836a3..64b3d10d88 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/ScssStylesheetResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/ScssStylesheetResolver.java @@ -17,6 +17,8 @@ package com.vaadin.sass.internal.resolver; import org.w3c.css.sac.InputSource; +import com.vaadin.sass.internal.ScssStylesheet; + public interface ScssStylesheetResolver { /** * Called with the "identifier" of a stylesheet that the resolver should try @@ -26,9 +28,12 @@ public interface ScssStylesheetResolver { * stylesheet was found, e.g "runo.scss" might result in a URI like * "VAADIN/themes/runo/runo.scss". * + * @param parentStylesheet + * The parent style sheet * @param identifier * used fo find stylesheet * @return InputSource for stylesheet (with URI set) or null if not found */ - public InputSource resolve(String identifier); + public InputSource resolve(ScssStylesheet parentStylesheet, + String identifier); } \ No newline at end of file diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/VaadinResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/VaadinResolver.java deleted file mode 100644 index fec16a54c8..0000000000 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/VaadinResolver.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2000-2013 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.sass.internal.resolver; - -import java.io.File; -import java.util.Stack; - -import org.w3c.css.sac.InputSource; - -public class VaadinResolver implements ScssStylesheetResolver { - - @Override - public InputSource resolve(String identifier) { - - // Remove extra "." and ".." - identifier = normalize(identifier); - - InputSource source = null; - - // Can we find the scss from the file system? - ScssStylesheetResolver resolver = new FilesystemResolver(); - source = resolver.resolve(identifier); - - if (source == null) { - // How about the classpath? - resolver = new ClassloaderResolver(); - source = resolver.resolve(identifier); - } - - return source; - } - - /** - * Normalizes "." and ".." from the path string where parent path segments - * can be removed. Preserve leading "..". - * - * @param path - * A relative or absolute file path - * @return The normalized path - */ - private static String normalize(String path) { - - // Ensure only "/" is used, also in Windows - path = path.replace(File.separatorChar, '/'); - - // Split into segments - String[] segments = path.split("/"); - Stack result = new Stack(); - - // Replace '.' and '..' segments - for (int i = 0; i < segments.length; i++) { - if (segments[i].equals(".")) { - // Segments marked '.' are ignored - - } else if (segments[i].equals("..") && !result.isEmpty() - && !result.lastElement().equals("..")) { - // If segment is ".." then remove the previous iff the previous - // element is not a ".." and the result stack is not empty - result.pop(); - } else { - // Other segments are just added to the stack - result.push(segments[i]); - } - } - - // Reconstruct path - StringBuilder pathBuilder = new StringBuilder(); - for (int i = 0; i < result.size(); i++) { - if (i > 0) { - pathBuilder.append("/"); - } - pathBuilder.append(result.get(i)); - } - return pathBuilder.toString(); - } - -} diff --git a/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java b/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java index 87c7cadcf8..e52767bb5a 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java +++ b/theme-compiler/src/com/vaadin/sass/internal/visitor/ImportNodeHandler.java @@ -16,7 +16,6 @@ package com.vaadin.sass.internal.visitor; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -57,21 +56,9 @@ public class ImportNodeHandler { ImportNode importNode = (ImportNode) n; if (!importNode.isPureCssImport()) { try { - StringBuilder filePathBuilder = new StringBuilder( - styleSheet.getDirectory()); - filePathBuilder.append(File.separatorChar).append( - importNode.getUri()); - if (!filePathBuilder.toString().endsWith(".scss")) { - filePathBuilder.append(".scss"); - } - // set parent's charset to imported node. ScssStylesheet imported = ScssStylesheet.get( - filePathBuilder.toString(), - styleSheet.getCharset()); - if (imported == null) { - imported = ScssStylesheet.get(importNode.getUri()); - } + importNode.getUri(), styleSheet); if (imported == null) { throw new FileNotFoundException("Import '" + importNode.getUri() + "' in '" diff --git a/theme-compiler/tests/src/com/vaadin/sass/resolvers/VaadinResolverTest.java b/theme-compiler/tests/src/com/vaadin/sass/resolvers/VaadinResolverTest.java index 59b49888c2..0183142747 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/resolvers/VaadinResolverTest.java +++ b/theme-compiler/tests/src/com/vaadin/sass/resolvers/VaadinResolverTest.java @@ -40,16 +40,26 @@ import java.lang.reflect.Method; import org.junit.Assert; import org.junit.Test; -import com.vaadin.sass.internal.resolver.VaadinResolver; +import com.vaadin.sass.internal.resolver.AbstractResolver; +import com.vaadin.sass.internal.resolver.ClassloaderResolver; +import com.vaadin.sass.internal.resolver.FilesystemResolver; public class VaadinResolverTest { @Test - public void testPathNormalization() throws Exception { + public void testFilesystemResolverPathNormalization() throws Exception { + testPathNormalization(new FilesystemResolver()); + } + + @Test + public void testClassloaderResolverPathNormalization() throws Exception { + testPathNormalization(new ClassloaderResolver()); + } - VaadinResolver resolver = new VaadinResolver(); + public void testPathNormalization(AbstractResolver resolver) + throws Exception { - Method normalizeMethod = VaadinResolver.class.getDeclaredMethod( + Method normalizeMethod = AbstractResolver.class.getDeclaredMethod( "normalize", String.class); normalizeMethod.setAccessible(true); -- cgit v1.2.3 From bad3f6a6b5102a3392472b4a3f8c7c833d94c9e3 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 2 Sep 2013 23:49:10 +0300 Subject: Refactor how base paths are determined when resolving (#11776) Change-Id: Ibf07046280d5f61df21681310ec28993b6daf50f --- .../sass/internal/resolver/AbstractResolver.java | 83 +++++++++++++++++++--- .../internal/resolver/ClassloaderResolver.java | 16 +---- .../sass/internal/resolver/FilesystemResolver.java | 42 ++++++++--- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java index ac11f1fa0e..f0464d098b 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java @@ -18,6 +18,8 @@ package com.vaadin.sass.internal.resolver; import java.io.File; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Stack; import org.w3c.css.sac.InputSource; @@ -44,22 +46,81 @@ public abstract class AbstractResolver implements ScssStylesheetResolver, @Override public InputSource resolve(ScssStylesheet parentStylesheet, String identifier) { - InputSource source = null; - if (parentStylesheet != null) { - StringBuilder filePathBuilder = new StringBuilder( - parentStylesheet.getFileName()); - filePathBuilder.append(File.separatorChar).append(identifier); - if (!filePathBuilder.toString().endsWith(".scss")) { - filePathBuilder.append(".scss"); + // Remove a possible ".scss" suffix + identifier = identifier.replaceFirst(".scss$", ""); + + List potentialParentPaths = getPotentialParentPaths( + parentStylesheet, identifier); + + // remove path from identifier as it has already been added to the + // parent path + if (identifier.contains("/")) { + identifier = identifier.substring(identifier.lastIndexOf("/") + 1); + } + + for (String path : potentialParentPaths) { + InputSource source = normalizeAndResolve(path + "/" + identifier); + + if (source != null) { + return source; } - source = normalizeAndResolve(filePathBuilder.toString()); + + } + + return normalizeAndResolve(identifier); + } + + /** + * Retrieves the parent paths which should be used while resolving relative + * identifiers. By default uses the parent stylesheet location and a + * possible absolute path in the identifier. + * + * @param parentStylesheet + * The parent stylesheet or null if there is no parent + * @param identifier + * The identifier to be resolved + * @return a list of paths in which to look for the relative import + */ + protected List getPotentialParentPaths( + ScssStylesheet parentStylesheet, String identifier) { + List potentialParents = new ArrayList(); + if (parentStylesheet != null) { + potentialParents.add(extractFullPath( + parentStylesheet.getDirectory(), identifier)); } - if (source == null) { - source = normalizeAndResolve(identifier); + // Identifier can be a full path so extract the path part also as a + // potential parent + if (identifier.contains("/")) { + potentialParents.add(extractFullPath("", identifier)); } - return source; + return potentialParents; + + } + + /** + * Extracts the full path from the path combined with the identifier + * + * @param path + * The base path + * @param identifier + * The identifier which may contain a path part, separated by "/" + * from the real identifier + * @return a normalized version of the path where identifier does not + * contain any directory information + */ + protected String extractFullPath(String path, String identifier) { + int lastSlashPosition = identifier.lastIndexOf("/"); + if (lastSlashPosition == -1) { + return path; + } + String identifierPath = identifier.substring(0, lastSlashPosition); + if ("".equals(path)) { + return identifierPath; + } else { + return path + "/" + identifierPath; + } } /** diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java index 68752b459a..755073bc4c 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/ClassloaderResolver.java @@ -15,7 +15,6 @@ */ package com.vaadin.sass.internal.resolver; -import java.io.File; import java.io.InputStream; import org.w3c.css.sac.InputSource; @@ -24,22 +23,11 @@ public class ClassloaderResolver extends AbstractResolver { @Override public InputSource resolveNormalized(String identifier) { - // identifier should not have .scss, fileName should - String ext = ".scss"; - if (identifier.endsWith(".css")) { - ext = ".css"; - } String fileName = identifier; - if (identifier.endsWith(ext)) { - identifier = identifier.substring(0, - identifier.length() - ext.length()); - } else { - fileName = fileName + ext; + if (!fileName.endsWith(".css")) { + fileName += ".scss"; } - // Ensure only "/" is used, also in Windows - fileName = fileName.replace(File.separatorChar, '/'); - // Filename should be a relative path starting with VAADIN/... int vaadinIdx = fileName.lastIndexOf("VAADIN/"); if (vaadinIdx > -1) { diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java index 3fec33ae13..786d0875da 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/FilesystemResolver.java @@ -18,24 +18,46 @@ package com.vaadin.sass.internal.resolver; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; +import java.util.List; import org.w3c.css.sac.InputSource; +import com.vaadin.sass.internal.ScssStylesheet; + public class FilesystemResolver extends AbstractResolver { + private String[] customPaths = null; + + public FilesystemResolver(String... customPaths) { + this.customPaths = customPaths; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.sass.internal.resolver.AbstractResolver#getPotentialPaths( + * com.vaadin.sass.internal.ScssStylesheet, java.lang.String) + */ @Override - public InputSource resolveNormalized(String identifier) { - // identifier should not have .scss, fileName should - String ext = ".scss"; - if (identifier.endsWith(".css")) { - ext = ".css"; + protected List getPotentialParentPaths( + ScssStylesheet parentStyleSheet, String identifier) { + List potentialPaths = super.getPotentialParentPaths( + parentStyleSheet, identifier); + if (customPaths != null) { + for (String path : customPaths) { + potentialPaths.add(extractFullPath(path, identifier)); + } } + + return potentialPaths; + } + + @Override + public InputSource resolveNormalized(String identifier) { String fileName = identifier; - if (identifier.endsWith(ext)) { - identifier = identifier.substring(0, - identifier.length() - ext.length()); - } else { - fileName = fileName + ext; + if (!fileName.endsWith(".css")) { + fileName += ".scss"; } try { -- cgit v1.2.3 From 89760ada6261e63c4964d6665729a7e43797f811 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 2 Sep 2013 23:57:30 +0300 Subject: Add support for partial imports (#11776) Change-Id: I5696e63b618294b4e6bbd3b0e407befddc5aa8a8 --- .../src/com/vaadin/sass/internal/resolver/AbstractResolver.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java index f0464d098b..5de1f95264 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java +++ b/theme-compiler/src/com/vaadin/sass/internal/resolver/AbstractResolver.java @@ -65,6 +65,13 @@ public abstract class AbstractResolver implements ScssStylesheetResolver, return source; } + // Try to find partial import (_identifier.scss) + source = normalizeAndResolve(path + "/_" + identifier); + + if (source != null) { + return source; + } + } return normalizeAndResolve(identifier); -- cgit v1.2.3 From 7c5bdce3b4c76e7f19bc46bcaed135d67a94f06a Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 2 Sep 2013 22:10:39 +0300 Subject: Compass test cases (#11776) Change-Id: Icf460eec606196bcff9b329fe7055bce6ed82c5f --- .../tests/resources/css/compass-import.css | 49 +++++++++++++ .../scss/compass-test/compass-import.scss | 4 ++ .../resources/scss/compass-test2/_compass.scss | 3 + .../scss/compass-test2/compass-import2.scss | 4 ++ .../scss/compass-test2/compass/_css3.scss | 3 + .../scss/compass-test2/compass/_typography.scss | 3 + .../scss/compass-test2/compass/_utilities.scss | 3 + .../compass-test2/compass/css3/_border-radius.scss | 4 ++ .../compass-test2/compass/css3/_inline-block.scss | 3 + .../scss/compass-test2/compass/css3/_opacity.scss | 3 + .../compass-test2/compass/typography/_links.scss | 3 + .../compass-test2/compass/typography/_lists.scss | 3 + .../compass-test2/compass/typography/_text.scss | 6 ++ .../compass-test2/compass/utilities/_color.scss | 4 ++ .../compass-test2/compass/utilities/_general.scss | 5 ++ .../compass-test2/compass/utilities/_sprites.scss | 6 ++ .../scss/compass-test2/license-readme.txt | 26 +++++++ .../vaadin/sass/testcases/scss/CompassImports.java | 80 ++++++++++++++++++++++ 18 files changed, 212 insertions(+) create mode 100644 theme-compiler/tests/resources/css/compass-import.css create mode 100644 theme-compiler/tests/resources/scss/compass-test/compass-import.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/_compass.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass-import2.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/_css3.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/_typography.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/_utilities.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/css3/_border-radius.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/css3/_inline-block.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/css3/_opacity.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/typography/_links.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/typography/_lists.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/typography/_text.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_color.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_general.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_sprites.scss create mode 100644 theme-compiler/tests/resources/scss/compass-test2/license-readme.txt create mode 100644 theme-compiler/tests/src/com/vaadin/sass/testcases/scss/CompassImports.java diff --git a/theme-compiler/tests/resources/css/compass-import.css b/theme-compiler/tests/resources/css/compass-import.css new file mode 100644 index 0000000000..e3d4b5fcca --- /dev/null +++ b/theme-compiler/tests/resources/css/compass-import.css @@ -0,0 +1,49 @@ +.content-navigation { + border-color: #3bbfce; + color: #0000ff; +} + +.border { + padding: 8px; + margin: 8px; + border-color: #3bbfce; +} + +.body { + background-image: url(compass/folder-test2/bg.png); + background: transparent url(compass/folder-test2/img/loading-indicator.gif); + background-image: url(http://abc/bg.png); + background-image: url(/abc/bg.png); +} + +.base { + color: red; +} + +.text { + font-weight: bold; +} + +.footer { + border: 2px solid black; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; +} + +.banner { + border: 1px solid black; + font-color: red; +} + +.interpolation-test { + font-size: 14px; +} + +.header { + width: 100%; +} + +.badError { + border-width: 3px; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test/compass-import.scss b/theme-compiler/tests/resources/scss/compass-test/compass-import.scss new file mode 100644 index 0000000000..36d041b33c --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test/compass-import.scss @@ -0,0 +1,4 @@ +@import "compass"; +.badError { + border-width: 3px; +} diff --git a/theme-compiler/tests/resources/scss/compass-test2/_compass.scss b/theme-compiler/tests/resources/scss/compass-test2/_compass.scss new file mode 100644 index 0000000000..9b741c0f03 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/_compass.scss @@ -0,0 +1,3 @@ +@import "compass/utilities"; +@import "compass/typography"; +@import "compass/css3"; diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass-import2.scss b/theme-compiler/tests/resources/scss/compass-test2/compass-import2.scss new file mode 100644 index 0000000000..36d041b33c --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass-import2.scss @@ -0,0 +1,4 @@ +@import "compass"; +.badError { + border-width: 3px; +} diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/_css3.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/_css3.scss new file mode 100644 index 0000000000..42163ba193 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/_css3.scss @@ -0,0 +1,3 @@ +@import "css3/border-radius"; +@import "css3/inline-block"; +@import "css3/opacity"; diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/_typography.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/_typography.scss new file mode 100644 index 0000000000..a65c1ff292 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/_typography.scss @@ -0,0 +1,3 @@ +@import "typography/links"; +@import "typography/lists"; +@import "typography/text"; diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/_utilities.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/_utilities.scss new file mode 100644 index 0000000000..644ad3368b --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/_utilities.scss @@ -0,0 +1,3 @@ +@import "utilities/color"; +@import "utilities/general"; +@import "utilities/sprites"; diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_border-radius.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_border-radius.scss new file mode 100644 index 0000000000..752003104b --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_border-radius.scss @@ -0,0 +1,4 @@ +.banner { + border: 1px solid black; + font-color: red; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_inline-block.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_inline-block.scss new file mode 100644 index 0000000000..3fefab83b2 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_inline-block.scss @@ -0,0 +1,3 @@ +.interpolation-test { + font-size: 14px; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_opacity.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_opacity.scss new file mode 100644 index 0000000000..f6bf34fe24 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/css3/_opacity.scss @@ -0,0 +1,3 @@ +.header { + width: 100%; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_links.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_links.scss new file mode 100644 index 0000000000..bc7318558e --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_links.scss @@ -0,0 +1,3 @@ +.base { + color: red; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_lists.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_lists.scss new file mode 100644 index 0000000000..af174b7095 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_lists.scss @@ -0,0 +1,3 @@ +.text { + font-weight: bold; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_text.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_text.scss new file mode 100644 index 0000000000..8239527f7b --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/typography/_text.scss @@ -0,0 +1,6 @@ +.footer { + border: 2px solid black; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_color.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_color.scss new file mode 100644 index 0000000000..ea1b7a55f0 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_color.scss @@ -0,0 +1,4 @@ +.content-navigation { + border-color: #3bbfce; + color: #0000ff; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_general.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_general.scss new file mode 100644 index 0000000000..0c58c6433d --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_general.scss @@ -0,0 +1,5 @@ +.border { + padding: 8px; + margin: 8px; + border-color: #3bbfce; +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_sprites.scss b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_sprites.scss new file mode 100644 index 0000000000..28960f89fc --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/compass/utilities/_sprites.scss @@ -0,0 +1,6 @@ +.body { + background-image: url(../folder-test2/bg.png); + background: transparent url(../folder-test2/img/loading-indicator.gif); + background-image: url(http://abc/bg.png); + background-image: url(/abc/bg.png); +} \ No newline at end of file diff --git a/theme-compiler/tests/resources/scss/compass-test2/license-readme.txt b/theme-compiler/tests/resources/scss/compass-test2/license-readme.txt new file mode 100644 index 0000000000..90ba808179 --- /dev/null +++ b/theme-compiler/tests/resources/scss/compass-test2/license-readme.txt @@ -0,0 +1,26 @@ +The design here is to use the stylesheets located at: +https://github com/chriseppstein/compass/tree/stable/frameworks/compass/stylesheets + +and update the VAADIN code to be able to read them in such that an existing JRuby implementation can be replaced with VAADIN without any changes to one's *.scss and *.css files. + +The current short snippets of SCSS that are included here only for testing Compass compatibility might not qualify as significant or substantial parts, but in any case Compass is being mentioned for related tests pointing to the original implementation. These small portions of Compass are copied and modified for the testing of compatibility only. + +The license for Compass mentioned here: +https://github.com/chriseppstein/compass/blob/stable/LICENSE.markdown + +is as follows: + + + + +Copyright (c) 2009 Christopher M. Eppstein + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. No attribution is required by products that make use of this software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. + +Contributors to this project agree to grant all rights to the copyright holder of the primary product. Attribution is maintained in the source control history of the product. diff --git a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/CompassImports.java b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/CompassImports.java new file mode 100644 index 0000000000..1e3eb09f0c --- /dev/null +++ b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/CompassImports.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2013 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.sass.testcases.scss; + +import java.io.File; +import java.io.IOException; + +import junit.framework.Assert; + +import org.junit.Test; +import org.w3c.css.sac.CSSException; + +import com.vaadin.sass.AbstractTestBase; +import com.vaadin.sass.internal.ScssStylesheet; +import com.vaadin.sass.internal.handler.SCSSDocumentHandler; +import com.vaadin.sass.internal.handler.SCSSDocumentHandlerImpl; +import com.vaadin.sass.internal.parser.Parser; +import com.vaadin.sass.internal.resolver.FilesystemResolver; +import com.vaadin.sass.internal.tree.ImportNode; + +public class CompassImports extends AbstractTestBase { + + String scssOtherDirectory = "/scss/compass-test/compass-import.scss"; + String scssSameDirectory = "/scss/compass-test2/compass-import2.scss"; + String css = "/css/compass-import.css"; + + String compassPath = "/scss/compass-test2"; + + @Test + public void testParser() throws CSSException, IOException { + Parser parser = new Parser(); + SCSSDocumentHandler handler = new SCSSDocumentHandlerImpl(); + parser.setDocumentHandler(handler); + parser.parseStyleSheet(getClass().getResource(scssOtherDirectory) + .getPath()); + ScssStylesheet root = handler.getStyleSheet(); + ImportNode importVariableNode = (ImportNode) root.getChildren().get(0); + Assert.assertEquals("compass", importVariableNode.getUri()); + Assert.assertFalse(importVariableNode.isPureCssImport()); + } + + @Test + public void testCompiler() throws Exception { + testCompiler(scssSameDirectory, css, null); + } + + @Test + public void testCompilerWithCustomPath() throws Exception { + File rootPath = new File(getClass().getResource(compassPath).toURI()); + + testCompiler(scssOtherDirectory, css, rootPath.getPath()); + } + + public void testCompiler(String scss, String css, String additionalPath) + throws Exception { + comparisonCss = getFileContent(css); + ScssStylesheet sheet = getStyleSheet(scss); + Assert.assertNotNull(sheet); + sheet.addResolver(new FilesystemResolver(additionalPath)); + + sheet.compile(); + parsedScss = sheet.toString(); + Assert.assertEquals("Original CSS and parsed CSS do not match", + comparisonCss, parsedScss); + } +} -- cgit v1.2.3 From 2bbb36705385f7c1bafce7eb527d5e66875b689c Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 1 Oct 2013 10:27:12 +0300 Subject: Moved test class to appropriate source folder (#12337) Change-Id: I01b25109a487bd0b9ff6a7903564ee5c867d6a33 --- .../TableRemovedQuicklySendsInvalidRpcCalls.java | 107 --------------------- .../TableRemovedQuicklySendsInvalidRpcCalls.java | 107 +++++++++++++++++++++ 2 files changed, 107 insertions(+), 107 deletions(-) delete mode 100644 server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java create mode 100644 uitest/src/com/vaadin/tests/components/table/TableRemovedQuicklySendsInvalidRpcCalls.java diff --git a/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java b/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java deleted file mode 100644 index b539e42efe..0000000000 --- a/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2000-2013 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.table; - -import com.vaadin.annotations.Push; -import com.vaadin.event.ItemClickEvent; -import com.vaadin.event.ItemClickEvent.ItemClickListener; -import com.vaadin.server.VaadinRequest; -import com.vaadin.tests.components.AbstractTestUI; -import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Table; - -@Push -public class TableRemovedQuicklySendsInvalidRpcCalls extends AbstractTestUI { - - @Override - protected void setup(VaadinRequest request) { - addComponent(new Button("Blink a table", new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - blinkTable(); - } - })); - } - - private void blinkTable() { - final Table table = new Table(); - table.setPageLength(5); - table.addContainerProperty(new Object(), String.class, null); - - for (int i = 0; i < 50; i++) { - table.addItem(new Object[] { "Row" }, new Object()); - } - - table.addItemClickListener(new ItemClickListener() { - private int i; - - @Override - public void itemClick(ItemClickEvent event) { - /* - * Ignore implementation. This is only an easy way to make the - * client-side update table's variables (by furiously clicking - * on the table row. - * - * This way, we get variable changes queued. The push call will - * then remove the Table, while the variable changes being still - * in the queue, leading to the issue as described in the - * ticket. - */ - System.out.println("clicky " + (++i)); - } - }); - - System.out.println("adding component"); - addComponent(table); - - new Thread() { - @Override - public void run() { - getSession().lock(); - try { - Thread.sleep(500); - access(new Runnable() { - @Override - public void run() { - System.out.println("removing component"); - removeComponent(table); - } - }); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - getSession().unlock(); - } - }; - }.start(); - } - - @Override - protected String getTestDescription() { - return "Adding and subsequently quickly removing a table " - + "should not leave any pending RPC calls waiting " - + "in a Timer. Issue can be reproduced by " - + "1) pressing the button 2) clicking furiously " - + "on a row in the table."; - } - - @Override - protected Integer getTicketNumber() { - return 12337; - } -} diff --git a/uitest/src/com/vaadin/tests/components/table/TableRemovedQuicklySendsInvalidRpcCalls.java b/uitest/src/com/vaadin/tests/components/table/TableRemovedQuicklySendsInvalidRpcCalls.java new file mode 100644 index 0000000000..6e4b62e4f7 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/TableRemovedQuicklySendsInvalidRpcCalls.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2013 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.table; + +import com.vaadin.annotations.Push; +import com.vaadin.event.ItemClickEvent; +import com.vaadin.event.ItemClickEvent.ItemClickListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Table; + +@Push +public class TableRemovedQuicklySendsInvalidRpcCalls extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + addComponent(new Button("Blink a table", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + blinkTable(); + } + })); + } + + private void blinkTable() { + final Table table = new Table(); + table.setPageLength(5); + table.addContainerProperty(new Object(), String.class, null); + + for (int i = 0; i < 50; i++) { + table.addItem(new Object[] { "Row" }, new Object()); + } + + table.addItemClickListener(new ItemClickListener() { + private int i; + + @Override + public void itemClick(ItemClickEvent event) { + /* + * Ignore implementation. This is only an easy way to make the + * client-side update table's variables (by furiously clicking + * on the table row. + * + * This way, we get variable changes queued. The push call will + * then remove the Table, while the variable changes being still + * in the queue, leading to the issue as described in the + * ticket. + */ + System.out.println("clicky " + (++i)); + } + }); + + System.out.println("adding component"); + addComponent(table); + + new Thread() { + @Override + public void run() { + getSession().lock(); + try { + Thread.sleep(500); + access(new Runnable() { + @Override + public void run() { + System.out.println("removing component"); + removeComponent(table); + } + }); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + getSession().unlock(); + } + }; + }.start(); + } + + @Override + protected String getTestDescription() { + return "Adding and subsequently quickly removing a table " + + "should not leave any pending RPC calls waiting " + + "in a Timer. Issue can be reproduced by " + + "1) pressing the button 2) clicking furiously " + + "on a row in the table."; + } + + @Override + protected Integer getTicketNumber() { + return 12337; + } +} -- cgit v1.2.3 From 2bebf738b2c6cc393b3e8d21a56535e9f1b180ea Mon Sep 17 00:00:00 2001 From: joheriks Date: Sun, 29 Sep 2013 18:22:56 +0300 Subject: Link no longer implements LegacyComponent (#10015) Change-Id: Iaadc82384d15704017077be282767141529889f2 --- client/src/com/vaadin/client/ui/VLink.java | 4 -- .../com/vaadin/client/ui/link/LinkConnector.java | 59 +++++---------- server/src/com/vaadin/ui/Link.java | 84 +++++----------------- .../src/com/vaadin/shared/ui/link/LinkState.java | 6 ++ 4 files changed, 43 insertions(+), 110 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VLink.java b/client/src/com/vaadin/client/ui/VLink.java index fa4ee36bcf..064a012873 100644 --- a/client/src/com/vaadin/client/ui/VLink.java +++ b/client/src/com/vaadin/client/ui/VLink.java @@ -23,7 +23,6 @@ import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.Util; import com.vaadin.shared.ui.BorderStyle; @@ -70,9 +69,6 @@ public class VLink extends HTML implements ClickHandler { /** For internal use only. May be removed or replaced in the future. */ public Icon icon; - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - public VLink() { super(); getElement().appendChild(anchor); diff --git a/client/src/com/vaadin/client/ui/link/LinkConnector.java b/client/src/com/vaadin/client/ui/link/LinkConnector.java index 228897278e..d2c41e9f38 100644 --- a/client/src/com/vaadin/client/ui/link/LinkConnector.java +++ b/client/src/com/vaadin/client/ui/link/LinkConnector.java @@ -17,38 +17,21 @@ package com.vaadin.client.ui.link; import com.google.gwt.user.client.DOM; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VLink; -import com.vaadin.shared.ui.BorderStyle; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.link.LinkConstants; import com.vaadin.shared.ui.link.LinkState; import com.vaadin.ui.Link; @Connect(Link.class) -public class LinkConnector extends AbstractComponentConnector implements - Paintable { +public class LinkConnector extends AbstractComponentConnector { @Override protected void init() { super.init(); - addStateChangeHandler("resources", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); - if (getWidget().src == null) { - getWidget().anchor.removeAttribute("href"); - } else { - getWidget().anchor.setAttribute("href", getWidget().src); - } - } - }); } @Override @@ -62,35 +45,30 @@ public class LinkConnector extends AbstractComponentConnector implements } @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().client = client; + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); getWidget().enabled = isEnabled(); - if (uidl.hasAttribute("name")) { - getWidget().target = uidl.getStringAttribute("name"); - getWidget().anchor.setAttribute("target", getWidget().target); - } - - if (uidl.hasAttribute("border")) { - if ("none".equals(uidl.getStringAttribute("border"))) { - getWidget().borderStyle = BorderStyle.NONE; + if (stateChangeEvent.hasPropertyChanged("resources")) { + getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); + if (getWidget().src == null) { + getWidget().anchor.removeAttribute("href"); } else { - getWidget().borderStyle = BorderStyle.MINIMAL; + getWidget().anchor.setAttribute("href", getWidget().src); } + } + + getWidget().target = getState().target; + if (getWidget().target == null) { + getWidget().anchor.removeAttribute("target"); } else { - getWidget().borderStyle = BorderStyle.DEFAULT; + getWidget().anchor.setAttribute("target", getWidget().target); } - getWidget().targetHeight = uidl.hasAttribute("targetHeight") ? uidl - .getIntAttribute("targetHeight") : -1; - getWidget().targetWidth = uidl.hasAttribute("targetWidth") ? uidl - .getIntAttribute("targetWidth") : -1; + getWidget().borderStyle = getState().targetBorder; + getWidget().targetWidth = getState().targetWidth; + getWidget().targetHeight = getState().targetHeight; // Set link caption getWidget().captionElement.setInnerText(getState().caption); @@ -111,13 +89,12 @@ public class LinkConnector extends AbstractComponentConnector implements if (getIcon() != null) { if (getWidget().icon == null) { - getWidget().icon = new Icon(client); + getWidget().icon = new Icon(getConnection()); getWidget().anchor.insertBefore(getWidget().icon.getElement(), getWidget().captionElement); } getWidget().icon.setUri(getIcon()); } - } @Override diff --git a/server/src/com/vaadin/ui/Link.java b/server/src/com/vaadin/ui/Link.java index cf8e1a9693..e1a47777bd 100644 --- a/server/src/com/vaadin/ui/Link.java +++ b/server/src/com/vaadin/ui/Link.java @@ -16,13 +16,10 @@ package com.vaadin.ui; -import java.util.Map; - -import com.vaadin.server.PaintException; -import com.vaadin.server.PaintTarget; import com.vaadin.server.Resource; import com.vaadin.shared.ui.BorderStyle; import com.vaadin.shared.ui.link.LinkConstants; +import com.vaadin.shared.ui.link.LinkState; /** * Link is used to create external or internal URL links. @@ -31,7 +28,7 @@ import com.vaadin.shared.ui.link.LinkConstants; * @since 3.0 */ @SuppressWarnings("serial") -public class Link extends AbstractComponent implements LegacyComponent { +public class Link extends AbstractComponent { /** * @deprecated As of 7.0, use {@link BorderStyle#NONE} instead @@ -51,14 +48,6 @@ public class Link extends AbstractComponent implements LegacyComponent { @Deprecated public static final BorderStyle TARGET_BORDER_DEFAULT = BorderStyle.DEFAULT; - private String targetName; - - private BorderStyle targetBorder = BorderStyle.DEFAULT; - - private int targetWidth = -1; - - private int targetHeight = -1; - /** * Creates a new link. */ @@ -105,43 +94,14 @@ public class Link extends AbstractComponent implements LegacyComponent { setTargetBorder(border); } - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ @Override - public void paintContent(PaintTarget target) throws PaintException { - if (getResource() == null) { - return; - } - - // Target window name - final String name = getTargetName(); - if (name != null && name.length() > 0) { - target.addAttribute("name", name); - } - - // Target window size - if (getTargetWidth() >= 0) { - target.addAttribute("targetWidth", getTargetWidth()); - } - if (getTargetHeight() >= 0) { - target.addAttribute("targetHeight", getTargetHeight()); - } - - // Target window border - switch (getTargetBorder()) { - case MINIMAL: - target.addAttribute("border", "minimal"); - break; - case NONE: - target.addAttribute("border", "none"); - break; - } + protected LinkState getState() { + return (LinkState) super.getState(); + } + + @Override + protected LinkState getState(boolean markAsDirty) { + return (LinkState) super.getState(markAsDirty); } /** @@ -150,7 +110,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window border. */ public BorderStyle getTargetBorder() { - return targetBorder; + return getState(false).targetBorder; } /** @@ -159,7 +119,8 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window height. */ public int getTargetHeight() { - return targetHeight < 0 ? -1 : targetHeight; + return getState(false).targetHeight < 0 ? -1 + : getState(false).targetHeight; } /** @@ -169,7 +130,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window name. */ public String getTargetName() { - return targetName; + return getState(false).target; } /** @@ -178,7 +139,8 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window width. */ public int getTargetWidth() { - return targetWidth < 0 ? -1 : targetWidth; + return getState(false).targetWidth < 0 ? -1 + : getState(false).targetWidth; } /** @@ -188,8 +150,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetBorder to set. */ public void setTargetBorder(BorderStyle targetBorder) { - this.targetBorder = targetBorder; - markAsDirty(); + getState().targetBorder = targetBorder; } /** @@ -199,8 +160,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetHeight to set. */ public void setTargetHeight(int targetHeight) { - this.targetHeight = targetHeight; - markAsDirty(); + getState().targetHeight = targetHeight; } /** @@ -210,8 +170,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetName to set. */ public void setTargetName(String targetName) { - this.targetName = targetName; - markAsDirty(); + getState().target = targetName; } /** @@ -221,8 +180,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetWidth to set. */ public void setTargetWidth(int targetWidth) { - this.targetWidth = targetWidth; - markAsDirty(); + getState().targetWidth = targetWidth; } /** @@ -244,8 +202,4 @@ public class Link extends AbstractComponent implements LegacyComponent { setResource(LinkConstants.HREF_RESOURCE, resource); } - @Override - public void changeVariables(Object source, Map variables) { - // TODO Remove once LegacyComponent is no longer implemented - } } diff --git a/shared/src/com/vaadin/shared/ui/link/LinkState.java b/shared/src/com/vaadin/shared/ui/link/LinkState.java index 269496767d..33ede86378 100644 --- a/shared/src/com/vaadin/shared/ui/link/LinkState.java +++ b/shared/src/com/vaadin/shared/ui/link/LinkState.java @@ -16,9 +16,15 @@ package com.vaadin.shared.ui.link; import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.ui.BorderStyle; public class LinkState extends AbstractComponentState { { primaryStyleName = "v-link"; } + public String name = ""; + public String target = null; + public BorderStyle targetBorder = BorderStyle.DEFAULT; + public int targetWidth = -1; + public int targetHeight = -1; } -- cgit v1.2.3 From 31a3f4fccb1a70f673fb166673d3f00a4a3f29ab Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 4 Oct 2013 12:02:13 +0300 Subject: Fix typo in automerge script Change-Id: I41ca4f5cd21b85ead3ae2888d5c1b94cb40f1bd8 --- scripts/automerge7.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/automerge7.sh b/scripts/automerge7.sh index 2b98530902..10e6ee0ffb 100755 --- a/scripts/automerge7.sh +++ b/scripts/automerge7.sh @@ -109,7 +109,7 @@ do pendingCommit= pendingCommitMessage= echo - echo "Stopping merge because $commit because of merge conflicts" + echo "Stopping merge at $commit because of merge conflicts" echo "The following commit must be manually merged." show $commit exit 3 -- cgit v1.2.3 From 9566d1e1275637da93cd6cc5d47f88c4e558c11e Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 7 Oct 2013 17:00:47 +0300 Subject: Fixed method name collision with new GWT Timer method (#12710) Change-Id: I71c62bb0c7fde101ad7b1315c277a2727bbc9e2b --- client/src/com/vaadin/client/ui/VScrollTable.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 492730259a..318a4a5f8f 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -2261,7 +2261,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private int reqFirstRow = 0; private int reqRows = 0; - private boolean isRunning = false; + private boolean isRequestHandlerRunning = false; public void triggerRowFetch(int first, int rows) { setReqFirstRow(first); @@ -2279,12 +2279,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, deferRowFetch(250); } - public boolean isRunning() { - return isRunning; + public boolean isRequestHandlerRunning() { + return isRequestHandlerRunning; } public void deferRowFetch(int msec) { - isRunning = true; + isRequestHandlerRunning = true; if (reqRows > 0 && reqFirstRow < totalRows) { schedule(msec); @@ -2426,7 +2426,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, unSyncedselectionsBeforeRowFetch = new HashSet( selectedRowKeys); } - isRunning = false; + isRequestHandlerRunning = false; } } @@ -2434,7 +2434,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Sends request to refresh content at this position. */ public void refreshContent() { - isRunning = true; + isRequestHandlerRunning = true; int first = (int) (firstRowInViewPort - pageLength * cache_rate); int reqRows = (int) (2 * pageLength * cache_rate + pageLength); if (first < 0) { -- cgit v1.2.3 From 8850d2c7a480ebbde8b1853b7fbf452bde95221f Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Wed, 9 Oct 2013 12:38:12 +0300 Subject: Table column header dnd ghost element location and styles (#12714) Change-Id: I1c5c0fee61e2a47f0f9c8fdfbbd48f49171135b6 --- client/src/com/vaadin/client/ui/VScrollTable.java | 29 +++-- .../TableDragColumnFloatingElementStyles.html | 136 +++++++++++++++++++++ 2 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 318a4a5f8f..b5a3086add 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -71,7 +71,6 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; @@ -80,6 +79,7 @@ import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.StyleConstants; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; @@ -2758,13 +2758,27 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); floatingCopyOfHeaderCell = DOM .getChild(floatingCopyOfHeaderCell, 2); - DOM.setElementProperty(floatingCopyOfHeaderCell, "className", - VScrollTable.this.getStylePrimaryName() + "-header-drag"); + // #12714 the shown "ghost element" should be inside + // v-overlay-container, and it should contain the same styles as the + // table to enable theming (except v-table & v-widget). + String stylePrimaryName = VScrollTable.this.getStylePrimaryName(); + StringBuilder sb = new StringBuilder(); + for (String s : VScrollTable.this.getStyleName().split(" ")) { + if (!s.equals(StyleConstants.UI_WIDGET)) { + sb.append(s); + if (s.equals(stylePrimaryName)) { + sb.append("-header-drag "); + } else { + sb.append(" "); + } + } + } + floatingCopyOfHeaderCell.setClassName(sb.toString().trim()); // otherwise might wrap or be cut if narrow column DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto"); updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM.getAbsoluteTop(td)); - DOM.appendChild(RootPanel.get().getElement(), + DOM.appendChild(VOverlay.getOverlayContainer(client), floatingCopyOfHeaderCell); } @@ -2779,8 +2793,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } private void hideFloatingCopy() { - DOM.removeChild(RootPanel.get().getElement(), - floatingCopyOfHeaderCell); + floatingCopyOfHeaderCell.removeFromParent(); floatingCopyOfHeaderCell = null; } @@ -7069,7 +7082,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private void deEmphasis() { UIObject.setStyleName(getElement(), - VScrollTable.this.getStylePrimaryName() + "-drag", false); + getStylePrimaryName() + "-drag", false); if (lastEmphasized == null) { return; } @@ -7095,7 +7108,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private void emphasis(TableDDDetails details) { deEmphasis(); UIObject.setStyleName(getElement(), - VScrollTable.this.getStylePrimaryName() + "-drag", true); + getStylePrimaryName() + "-drag", true); // iterate old and new emphasized row for (Widget w : scrollBody.renderedRows) { VScrollTableRow row = (VScrollTableRow) w; diff --git a/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html b/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html new file mode 100644 index 0000000000..e4bd02d620 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html @@ -0,0 +1,136 @@ + + + + + + +TableDragColumn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TableDragColumn
open/run/com.vaadin.tests.components.table.Tables?restartApplication
dragvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]51,6
assertElementPresent//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divv-table-header-drag
dropvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2]70,10
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item024,7
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item118,10
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item419,8
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[2]/VMenuBar[0]#item275,7
dragvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]51,6
assertElementPresent//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divv-table-header-drag
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divborder-red-1px
dropvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2]70,10
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item032,8
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item018,12
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item242,13
dragvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]51,6
assertElementPresent//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divv-table-header-drag
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divborder-red-1px
assertCSSClass//div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/divv-readonly
dropvaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2]70,10
+ + \ No newline at end of file -- cgit v1.2.3 From 466006c2b1d6a84944e32ea126e5def739c29046 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Fri, 11 Oct 2013 11:33:16 +0300 Subject: Updated shared-deps dependency to 1.0.3 (#12421) Change-Id: I4087b1225c5825582628d5322711e806104efca0 --- shared/ivy.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/ivy.xml b/shared/ivy.xml index 3b044e9ab4..fc54d680fa 100644 --- a/shared/ivy.xml +++ b/shared/ivy.xml @@ -21,7 +21,7 @@ + rev="1.0.3" conf="build,ide,test->default" /> -- cgit v1.2.3 From c978ad89cb27a4608c7adcda4ee606442b8c7773 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Fri, 11 Oct 2013 15:55:50 +0300 Subject: Converted test to use screenshots due to IE8 issues with TB dnd commands (#12714) Basically the screenshot for IE8 after the drag + drop commands shows that the column header drag element is not removed from DOM for some reason. In real life, column header drag and drop works for IE8. Change-Id: Ia31054e496064695708ed2392e83aa53056d2b5b --- .../TableDragColumnFloatingElementStyles.html | 84 ++++++++++------------ 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html b/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html index e4bd02d620..e225091b5f 100644 --- a/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html +++ b/uitest/src/com/vaadin/tests/components/table/TableDragColumnFloatingElementStyles.html @@ -16,26 +16,25 @@ /run/com.vaadin.tests.components.table.Tables?restartApplication - drag vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] 51,6 - assertElementPresent - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - + mouseMoveAt + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[3] + 70,10 - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - v-table-header-drag + screenCapture + + 1 basic ghost element drop - vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2] - 70,10 + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] + 10,10 @@ -61,75 +60,64 @@ drag - vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0] 51,6 - assertElementPresent - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - - - - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - v-table-header-drag + mouseMoveAt + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[3] + 70,10 - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - border-red-1px + screenCapture + + 2 themed ghost element should have red borders drop - vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2] - 70,10 + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] + 10,10 - + mouseClick vaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item0 - 32,8 + 24,7 + + + mouseClick + vaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item1 + 18,10 mouseClick - vaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item0 - 18,12 + vaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item4 + 19,8 mouseClick - vaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item2 - 42,13 + vaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[2]/VMenuBar[0]#item3 + 164,10 - drag vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] 51,6 - assertElementPresent - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - - - - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - v-table-header-drag - - - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - border-red-1px + mouseMoveAt + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[3] + 70,10 - assertCSSClass - //div[@id='runcomvaadintestscomponentstableTables-1125197179-overlays']/div - v-readonly + screenCapture + + 3 themed ghost element should have 2px blue borders drop - vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[2] - 70,10 + vaadin=runcomvaadintestscomponentstableTables::PID_StestComponent/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2] + 10,10 -- cgit v1.2.3 From 30e7e7f8463caadb55383bd45e5a8e274cb74029 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 15 Oct 2013 16:28:32 +0300 Subject: File missing from method name collision fix (#12710) Change-Id: I90b2960cc8664164962be524f41bfd3b24b933d3 --- client/src/com/vaadin/client/ui/table/TableConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java index 47229dc9c7..f1710351bf 100644 --- a/client/src/com/vaadin/client/ui/table/TableConnector.java +++ b/client/src/com/vaadin/client/ui/table/TableConnector.java @@ -264,7 +264,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements if (getWidget().focusedRow != null) { if (!getWidget().focusedRow.isAttached() - && !getWidget().rowRequestHandler.isRunning()) { + && !getWidget().rowRequestHandler.isRequestHandlerRunning()) { // focused row has been orphaned, can't focus if (getWidget().selectedRowKeys.contains(getWidget().focusedRow .getKey())) { -- cgit v1.2.3 From 85251833de3bd101d388b20fdb9b02c532a9f1c9 Mon Sep 17 00:00:00 2001 From: Jarno Rantala Date: Thu, 10 Oct 2013 13:08:09 +0300 Subject: Added ItemSetAddEvent and ItemSetRemoveEvent (#2794) The events inherits the ItemSetChangedEvent and they contain more information about the added/removed items. These events are fired from AbstractInMemoryContainer. Change-Id: I0a7ddfd38fd01fa385479efc953ab444d1ecdf4c --- server/src/com/vaadin/data/Container.java | 54 ++++++ .../vaadin/data/util/AbstractBeanContainer.java | 20 ++- .../data/util/AbstractInMemoryContainer.java | 151 +++++++++++++++-- .../src/com/vaadin/data/util/IndexedContainer.java | 7 +- .../vaadin/data/util/BeanItemContainerTest.java | 185 +++++++++++++++++++++ .../com/vaadin/data/util/TestIndexedContainer.java | 113 +++++++++++++ 6 files changed, 512 insertions(+), 18 deletions(-) diff --git a/server/src/com/vaadin/data/Container.java b/server/src/com/vaadin/data/Container.java index e93db52a35..bf553f31d2 100644 --- a/server/src/com/vaadin/data/Container.java +++ b/server/src/com/vaadin/data/Container.java @@ -582,6 +582,60 @@ public interface Container extends Serializable { public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException; + /** + * An Event object specifying information about the added + * items. + */ + public interface ItemAddEvent extends ItemSetChangeEvent { + + /** + * Gets the item id of the first added item. + * + * @return item id of the first added item + */ + public Object getFirstItemId(); + + /** + * Gets the index of the first added item. + * + * @return index of the first added item + */ + public int getFirstIndex(); + + /** + * Gets the number of the added items. + * + * @return the number of added items. + */ + public int getAddedItemsCount(); + } + + /** + * An Event object specifying information about the removed + * items. + */ + public interface ItemRemoveEvent extends ItemSetChangeEvent { + /** + * Gets the item id of the first removed item. + * + * @return item id of the first removed item + */ + public Object getFirstItemId(); + + /** + * Gets the index of the first removed item. + * + * @return index of the first removed item + */ + public int getFirstIndex(); + + /** + * Gets the number of the removed items. + * + * @return the number of removed items + */ + public int getRemovedItemsCount(); + } } /** diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java index cd5c0c809d..67239996a2 100644 --- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -222,6 +222,7 @@ public abstract class AbstractBeanContainer extends @Override public boolean removeAllItems() { int origSize = size(); + IDTYPE firstItem = getFirstVisibleItem(); internalRemoveAllItems(); @@ -234,7 +235,7 @@ public abstract class AbstractBeanContainer extends // fire event only if the visible view changed, regardless of whether // filtered out items were removed or not if (origSize != 0) { - fireItemSetChange(); + fireItemsRemoved(0, firstItem, origSize); } return true; @@ -679,6 +680,8 @@ public abstract class AbstractBeanContainer extends protected void addAll(Collection collection) throws IllegalStateException, IllegalArgumentException { boolean modified = false; + int origSize = size(); + for (BEANTYPE bean : collection) { // TODO skipping invalid beans - should not allow them in javadoc? if (bean == null @@ -699,13 +702,22 @@ public abstract class AbstractBeanContainer extends if (modified) { // Filter the contents when all items have been added if (isFiltered()) { - filterAll(); - } else { - fireItemSetChange(); + doFilterContainer(!getFilters().isEmpty()); + } + if (visibleNewItemsWasAdded(origSize)) { + // fire event about added items + int firstPosition = origSize; + IDTYPE firstItemId = getVisibleItemIds().get(firstPosition); + int affectedItems = size() - origSize; + fireItemsAdded(firstPosition, firstItemId, affectedItems); } } } + private boolean visibleNewItemsWasAdded(int origSize) { + return size() > origSize; + } + /** * Use the bean resolver to get the identifier for a bean. * diff --git a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java index 84304431bc..9a7922b928 100644 --- a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java +++ b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java @@ -15,8 +15,10 @@ */ package com.vaadin.data.util; +import java.io.Serializable; import java.util.Collection; import java.util.Collections; +import java.util.EventObject; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -146,6 +148,85 @@ public abstract class AbstractInMemoryContainerEvent object specifying information about the added + * items. + * + *

+ * This class provides information about the first added item and the number + * of added items. + *

+ */ + protected static class BaseItemAddEvent extends + BaseItemAddOrRemoveEvent implements + Container.Indexed.ItemAddEvent { + + public BaseItemAddEvent(Container source, Object itemId, int index, + int count) { + super(source, itemId, index, count); + } + + @Override + public int getAddedItemsCount() { + return getAffectedItemsCount(); + } + } + + /** + * An Event object specifying information about the removed + * items. + * + *

+ * This class provides information about the first removed item and the + * number of removed items. + *

+ */ + protected static class BaseItemRemoveEvent extends + BaseItemAddOrRemoveEvent implements + Container.Indexed.ItemRemoveEvent { + + public BaseItemRemoveEvent(Container source, Object itemId, + int index, int count) { + super(source, itemId, index, count); + } + + @Override + public int getRemovedItemsCount() { + return getAffectedItemsCount(); + } + } + /** * Get an item even if filtered out. * @@ -898,36 +979,69 @@ public abstract class AbstractInMemoryContainer= 0) { - fireItemSetChange(new IndexedContainer.ItemSetChangeEvent(this, - position)); + super.fireItemAdded(position, itemId, item); } } @@ -1211,4 +1211,5 @@ public class IndexedContainer extends public Collection getContainerFilters() { return super.getContainerFilters(); } + } diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java index 3a2cb268b9..35f09fc8f3 100644 --- a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java @@ -10,8 +10,15 @@ import java.util.Map; import junit.framework.Assert; +import org.easymock.Capture; +import org.easymock.EasyMock; + import com.vaadin.data.Container; +import com.vaadin.data.Container.Indexed.ItemAddEvent; +import com.vaadin.data.Container.Indexed.ItemRemoveEvent; +import com.vaadin.data.Container.ItemSetChangeListener; import com.vaadin.data.Item; +import com.vaadin.data.util.filter.Compare; /** * Test basic functionality of BeanItemContainer. @@ -737,4 +744,182 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { // should throw exception } } + + public void testItemAddedEvent() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + addListener.containerItemSetChange(EasyMock.isA(ItemAddEvent.class)); + EasyMock.replay(addListener); + + container.addItem(bean); + + EasyMock.verify(addListener); + } + + public void testItemAddedEvent_AddedItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItem(bean); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemAddedEvent_addItemAt_IndexOfAddedItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItemAt(1, new Person("")); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemAddedEvent_addItemAfter_IndexOfAddedItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItemAfter(bean, new Person("")); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemAddedEvent_amountOfAddedItems() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List beans = Arrays.asList(new Person("Jack"), new Person( + "John")); + + container.addAll(beans); + + assertEquals(2, capturedEvent.getValue().getAddedItemsCount()); + } + + public void testItemAddedEvent_someItemsAreFiltered_amountOfAddedItemsIsReducedByAmountOfFilteredItems() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List beans = Arrays.asList(new Person("Jack"), new Person( + "John")); + container.addFilter(new Compare.Equal("name", "John")); + + container.addAll(beans); + + assertEquals(1, capturedEvent.getValue().getAddedItemsCount()); + } + + public void testItemAddedEvent_someItemsAreFiltered_addedItemIsTheFirstVisibleItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List beans = Arrays.asList(new Person("Jack"), bean); + container.addFilter(new Compare.Equal("name", "John")); + + container.addAll(beans); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + removeListener.containerItemSetChange(EasyMock + .isA(ItemRemoveEvent.class)); + EasyMock.replay(removeListener); + + container.removeItem(bean); + + EasyMock.verify(removeListener); + } + + public void testItemRemovedEvent_RemovedItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(bean); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent_indexOfRemovedItem() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + container.addItem(new Person("Jack")); + Person secondBean = new Person("John"); + container.addItem(secondBean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(secondBean); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent_amountOfRemovedItems() { + BeanItemContainer container = new BeanItemContainer( + Person.class); + container.addItem(new Person("Jack")); + container.addItem(new Person("John")); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeAllItems(); + + assertEquals(2, capturedEvent.getValue().getRemovedItemsCount()); + } + + private Capture captureAddEvent( + ItemSetChangeListener addListener) { + Capture capturedEvent = new Capture(); + addListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private Capture captureRemoveEvent( + ItemSetChangeListener removeListener) { + Capture capturedEvent = new Capture(); + removeListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private ItemSetChangeListener createListenerMockFor( + BeanItemContainer container) { + ItemSetChangeListener listener = EasyMock + .createNiceMock(ItemSetChangeListener.class); + container.addItemSetChangeListener(listener); + return listener; + } } diff --git a/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java b/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java index 09e5a26c15..5c78965092 100644 --- a/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java +++ b/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java @@ -4,6 +4,12 @@ import java.util.List; import junit.framework.Assert; +import org.easymock.Capture; +import org.easymock.EasyMock; + +import com.vaadin.data.Container.Indexed.ItemAddEvent; +import com.vaadin.data.Container.Indexed.ItemRemoveEvent; +import com.vaadin.data.Container.ItemSetChangeListener; import com.vaadin.data.Item; public class TestIndexedContainer extends AbstractInMemoryContainerTest { @@ -271,6 +277,113 @@ public class TestIndexedContainer extends AbstractInMemoryContainerTest { counter.assertNone(); } + public void testItemAddedEvent() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + addListener.containerItemSetChange(EasyMock.isA(ItemAddEvent.class)); + EasyMock.replay(addListener); + + container.addItem(); + + EasyMock.verify(addListener); + } + + public void testItemAddedEvent_AddedItem() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + Object itemId = container.addItem(); + + assertEquals(itemId, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemAddedEvent_IndexOfAddedItem() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + container.addItem(); + Capture capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + Object itemId = container.addItemAt(1); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent() { + IndexedContainer container = new IndexedContainer(); + Object itemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + removeListener.containerItemSetChange(EasyMock + .isA(ItemRemoveEvent.class)); + EasyMock.replay(removeListener); + + container.removeItem(itemId); + + EasyMock.verify(removeListener); + } + + public void testItemRemovedEvent_RemovedItem() { + IndexedContainer container = new IndexedContainer(); + Object itemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(itemId); + + assertEquals(itemId, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent_indexOfRemovedItem() { + IndexedContainer container = new IndexedContainer(); + container.addItem(); + Object secondItemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(secondItemId); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent_amountOfRemovedItems() { + IndexedContainer container = new IndexedContainer(); + container.addItem(); + container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeAllItems(); + + assertEquals(2, capturedEvent.getValue().getRemovedItemsCount()); + } + + private Capture captureAddEvent( + ItemSetChangeListener addListener) { + Capture capturedEvent = new Capture(); + addListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private Capture captureRemoveEvent( + ItemSetChangeListener removeListener) { + Capture capturedEvent = new Capture(); + removeListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private ItemSetChangeListener createListenerMockFor( + IndexedContainer container) { + ItemSetChangeListener listener = EasyMock + .createNiceMock(ItemSetChangeListener.class); + container.addItemSetChangeListener(listener); + return listener; + } + // Ticket 8028 public void testGetItemIdsRangeIndexOutOfBounds() { IndexedContainer ic = new IndexedContainer(); -- cgit v1.2.3 From c90c0f280c37f80bcd4b4d7891ac9455436a7097 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 14 Oct 2013 22:02:28 +0300 Subject: Use alternate icon text for NativeButton (#12780) Change-Id: I1390a3f5914977895c4111d44844c7be8ed46a61 --- .../ui/nativebutton/NativeButtonConnector.java | 2 +- .../nativebutton/NativeButtonIconAndText.java | 124 +++++++++++++++++++++ .../nativebutton/NativeButtonIconAndTextTest.java | 94 ++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndText.java create mode 100644 uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndTextTest.java diff --git a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java index 11a76b483b..2253397b16 100644 --- a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java +++ b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java @@ -94,7 +94,7 @@ public class NativeButtonConnector extends AbstractComponentConnector implements getWidget().icon.getElement(), getWidget().captionElement); } - getWidget().icon.setUri(getIcon()); + getWidget().icon.setUri(getIcon(), getState().iconAltText); } else { if (getWidget().icon != null) { getWidget().getElement().removeChild( diff --git a/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndText.java b/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndText.java new file mode 100644 index 0000000000..fdeed316ba --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndText.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2013 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.nativebutton; + +import com.vaadin.server.ThemeResource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.NativeButton; + +public class NativeButtonIconAndText extends AbstractTestUI implements + ClickListener { + + static final String UPDATED_ALTERNATE_TEXT = "Now has alternate text"; + static final String INITIAL_ALTERNATE_TEXT = "Initial alternate text"; + static final String BUTTON_TEXT = "buttonText"; + static final String BUTTON_TEXT_ICON = "buttonTextIcon"; + static final String BUTTON_TEXT_ICON_ALT = "buttonTextIconAlt"; + static final String NATIVE_BUTTON_TEXT = "nativeButtonText"; + static final String NATIVE_BUTTON_TEXT_ICON = "nativeButtonTextIcon"; + static final String NATIVE_BUTTON_TEXT_ICON_ALT = "nativeButtonTextIconAlt"; + + @Override + protected void setup(VaadinRequest request) { + Button buttonText = new Button("Only text"); + + Button buttonTextIcon = new Button("Text icon"); + buttonTextIcon.setIcon(new ThemeResource("../runo/icons/64/ok.png")); + + Button buttonTextIconAlt = new Button("Text icon alt"); + buttonTextIconAlt.setIcon(new ThemeResource( + "../runo/icons/64/cancel.png")); + buttonTextIconAlt.setIconAlternateText(INITIAL_ALTERNATE_TEXT); + + buttonText.addClickListener(this); + buttonTextIcon.addClickListener(this); + buttonTextIconAlt.addClickListener(this); + + buttonText.setId(BUTTON_TEXT); + buttonTextIcon.setId(BUTTON_TEXT_ICON); + buttonTextIconAlt.setId(BUTTON_TEXT_ICON_ALT); + + addComponent(buttonText); + addComponent(buttonTextIcon); + addComponent(buttonTextIconAlt); + + NativeButton nativeButtonText = new NativeButton("Only text"); + + NativeButton nativeButtonTextIcon = new NativeButton("Text icon"); + nativeButtonTextIcon.setIcon(new ThemeResource( + "../runo/icons/64/ok.png")); + + NativeButton nativeButtonTextIconAlt = new NativeButton("Text icon alt"); + nativeButtonTextIconAlt.setIcon(new ThemeResource( + "../runo/icons/64/cancel.png")); + nativeButtonTextIconAlt.setIconAlternateText(INITIAL_ALTERNATE_TEXT); + + nativeButtonText.addClickListener(this); + nativeButtonTextIcon.addClickListener(this); + nativeButtonTextIconAlt.addClickListener(this); + + nativeButtonText.setId(NATIVE_BUTTON_TEXT); + nativeButtonTextIcon.setId(NATIVE_BUTTON_TEXT_ICON); + nativeButtonTextIconAlt.setId(NATIVE_BUTTON_TEXT_ICON_ALT); + + addComponent(nativeButtonText); + addComponent(nativeButtonTextIcon); + addComponent(nativeButtonTextIconAlt); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTestDescription() + */ + @Override + protected String getTestDescription() { + return "Click the buttons to toggle icon alternate text"; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTicketNumber() + */ + @Override + protected Integer getTicketNumber() { + return 12780; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.Button.ClickListener#buttonClick(com.vaadin.ui.Button. + * ClickEvent) + */ + @Override + public void buttonClick(ClickEvent event) { + Button b = event.getButton(); + String was = b.getIconAlternateText(); + if (was == null || was.isEmpty()) { + b.setIconAlternateText(UPDATED_ALTERNATE_TEXT); + } else { + b.setIconAlternateText(null); + } + + } + +} diff --git a/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndTextTest.java b/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndTextTest.java new file mode 100644 index 0000000000..2cb294de77 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/nativebutton/NativeButtonIconAndTextTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2013 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.nativebutton; + +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.BUTTON_TEXT; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.BUTTON_TEXT_ICON; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.BUTTON_TEXT_ICON_ALT; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.INITIAL_ALTERNATE_TEXT; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.NATIVE_BUTTON_TEXT; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.NATIVE_BUTTON_TEXT_ICON; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.NATIVE_BUTTON_TEXT_ICON_ALT; +import static com.vaadin.tests.components.nativebutton.NativeButtonIconAndText.UPDATED_ALTERNATE_TEXT; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class NativeButtonIconAndTextTest extends MultiBrowserTest { + + @Test + public void testNativeButtonIconAltText() { + openTestURL(); + assertAltText(BUTTON_TEXT, ""); + assertAltText(BUTTON_TEXT_ICON, ""); + assertAltText(BUTTON_TEXT_ICON_ALT, INITIAL_ALTERNATE_TEXT); + assertAltText(NATIVE_BUTTON_TEXT, ""); + assertAltText(NATIVE_BUTTON_TEXT_ICON, ""); + assertAltText(NATIVE_BUTTON_TEXT_ICON_ALT, INITIAL_ALTERNATE_TEXT); + + clickElements(BUTTON_TEXT, BUTTON_TEXT_ICON, BUTTON_TEXT_ICON_ALT, + NATIVE_BUTTON_TEXT, NATIVE_BUTTON_TEXT_ICON, + NATIVE_BUTTON_TEXT_ICON_ALT); + + // Button without icon - should not get alt text + assertAltText(BUTTON_TEXT, ""); + assertAltText(BUTTON_TEXT_ICON, UPDATED_ALTERNATE_TEXT); + assertAltText(BUTTON_TEXT_ICON_ALT, ""); + // Button without icon - should not get alt text + assertAltText(NATIVE_BUTTON_TEXT, ""); + assertAltText(NATIVE_BUTTON_TEXT_ICON, UPDATED_ALTERNATE_TEXT); + assertAltText(NATIVE_BUTTON_TEXT_ICON_ALT, ""); + + } + + private void clickElements(String... ids) { + for (String id : ids) { + vaadinElementById(id).click(); + } + } + + /** + * If the button identified by 'buttonId' has an icon, asserts that the + * alternate text of the icon matches 'expected'. "" and null are considered + * equivalent. + * + * @param buttonId + * the id of the button who possibly contains an icon + * @param expected + * the expected alternate text, cannot be null + */ + private void assertAltText(String buttonId, String expected) { + WebElement button = vaadinElementById(buttonId); + List imgList = button.findElements(By.xpath(".//img")); + if (imgList.isEmpty()) { + return; + } + WebElement img = imgList.get(0); + String alt = img.getAttribute("alt"); + if (alt == null) { + alt = ""; + } + + Assert.assertEquals(expected, alt); + + } +} -- cgit v1.2.3