From ccb76e6608adbdf652748899779a458cd795ac3a Mon Sep 17 00:00:00 2001 From: Matti Hosio Date: Thu, 11 Dec 2014 16:22:41 +0200 Subject: [PATCH] Declarative support for TabSheet and Accordion (#7749) Change-Id: Iee4689814f08ddbb852cfb3e51c9873fbe42d901 --- server/src/com/vaadin/ui/TabSheet.java | 167 ++++++++++++++++++ .../declarative/DesignAttributeHandler.java | 4 +- .../tests/layoutparser/all-components.html | 31 ++++ .../TestSynchronizeFromDesign.java | 5 +- .../tabsheet/TestSynchronizeFromDesign.java | 137 ++++++++++++++ .../tabsheet/TestSynchronizeToDesign.java | 109 ++++++++++++ 6 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeFromDesign.java create mode 100644 server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeToDesign.java diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java index d3410464a2..dd9beb07f0 100644 --- a/server/src/com/vaadin/ui/TabSheet.java +++ b/server/src/com/vaadin/ui/TabSheet.java @@ -19,11 +19,15 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; @@ -40,6 +44,9 @@ import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc; import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; import com.vaadin.shared.ui.tabsheet.TabsheetState; import com.vaadin.ui.Component.Focusable; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.ui.themes.Reindeer; import com.vaadin.ui.themes.Runo; @@ -1447,4 +1454,164 @@ public class TabSheet extends AbstractComponentContainer implements Focusable, protected TabsheetState getState() { return (TabsheetState) super.getState(); } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.AbstractComponent#synchronizeFromDesign(org.jsoup.nodes + * .Element, com.vaadin.ui.declarative.DesignContext) + */ + @Override + public void synchronizeFromDesign(Element design, + DesignContext designContext) { + super.synchronizeFromDesign(design, designContext); + Attributes attr = design.attributes(); + TabSheet def = designContext.getDefaultInstance(this.getClass()); + // handle tab index + int tabIndex = DesignAttributeHandler.readAttribute("tabindex", attr, + def.getTabIndex(), Integer.class); + setTabIndex(tabIndex); + // clear old tabs + removeAllComponents(); + // create new tabs + for (Element tab : design.children()) { + if (!tab.tagName().equals("tab")) { + throw new DesignException("Invalid tag name for tabsheet tab " + + tab.tagName()); + } + readTabFromDesign(tab, designContext); + } + } + + /** + * Reads the given tab element from design + * + * @since 7.4 + * + * @param tabElement + * the element to be read + * @param designContext + * the design context + */ + private void readTabFromDesign(Element tabElement, + DesignContext designContext) { + Attributes attr = tabElement.attributes(); + if (tabElement.children().size() != 1) { + throw new DesignException( + "A tab must have exactly one child element"); + } + // create the component that is in tab content + Element content = tabElement.child(0); + DesignSynchronizable child = designContext.createChild(content); + Tab tab = this.addTab(child); + tab.setVisible(DesignAttributeHandler.readAttribute("visible", attr, + tab.isVisible(), Boolean.class)); + tab.setClosable(DesignAttributeHandler.readAttribute("closable", attr, + tab.isClosable(), Boolean.class)); + tab.setCaption(DesignAttributeHandler.readAttribute("caption", attr, + tab.getCaption(), String.class)); + tab.setEnabled(DesignAttributeHandler.readAttribute("enabled", attr, + tab.isEnabled(), Boolean.class)); + tab.setIcon(DesignAttributeHandler.readAttribute("icon", attr, + tab.getIcon(), Resource.class)); + tab.setIconAlternateText(DesignAttributeHandler.readAttribute( + "icon-alt", attr, tab.getIconAlternateText(), String.class)); + tab.setDescription(DesignAttributeHandler.readAttribute("description", + attr, tab.getDescription(), String.class)); + tab.setStyleName(DesignAttributeHandler.readAttribute("style-name", + attr, tab.getStyleName(), String.class)); + tab.setId(DesignAttributeHandler.readAttribute("id", attr, tab.getId(), + String.class)); + boolean selected = DesignAttributeHandler.readAttribute("selected", + attr, false, Boolean.class); + if (selected) { + this.setSelectedTab(tab.getComponent()); + } + } + + /** + * Writes the given tab to design + * + * @since 7.4 + * @param design + * the design node for tabsheet + * @param designContext + * the design context + * @param tab + * the tab to be written + */ + private void writeTabToDesign(Element design, DesignContext designContext, + Tab tab) { + // get default tab instance + Tab def = new TabSheetTabImpl(null, null, null); + // create element for tab + Element tabElement = design.appendElement("tab"); + // add tab content + tabElement.appendChild(designContext + .createNode((DesignSynchronizable) tab.getComponent())); + Attributes attr = tabElement.attributes(); + // write attributes + DesignAttributeHandler.writeAttribute("visible", attr, tab.isVisible(), + def.isVisible(), Boolean.class); + DesignAttributeHandler.writeAttribute("closable", attr, + tab.isClosable(), def.isClosable(), Boolean.class); + DesignAttributeHandler.writeAttribute("caption", attr, + tab.getCaption(), def.getCaption(), String.class); + DesignAttributeHandler.writeAttribute("enabled", attr, tab.isEnabled(), + def.isEnabled(), Boolean.class); + DesignAttributeHandler.writeAttribute("icon", attr, tab.getIcon(), + def.getIcon(), Resource.class); + DesignAttributeHandler.writeAttribute("icon-alt", attr, + tab.getIconAlternateText(), def.getIconAlternateText(), + String.class); + DesignAttributeHandler.writeAttribute("description", attr, + tab.getDescription(), def.getDescription(), String.class); + DesignAttributeHandler.writeAttribute("style-name", attr, + tab.getStyleName(), def.getStyleName(), String.class); + DesignAttributeHandler.writeAttribute("id", attr, tab.getId(), + def.getId(), String.class); + if (getSelectedTab() != null + && getSelectedTab().equals(tab.getComponent())) { + // use write attribute to get consistent handling for boolean + DesignAttributeHandler.writeAttribute("selected", attr, true, + false, boolean.class); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.AbstractComponent#getCustomAttributes() + */ + @Override + protected Collection getCustomAttributes() { + Collection attributes = super.getCustomAttributes(); + attributes.add("tabindex"); + // no need to list tab attributes since they are considered internal + return attributes; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.AbstractComponent#synchronizeToDesign(org.jsoup.nodes.Element + * , com.vaadin.ui.declarative.DesignContext) + */ + @Override + public void synchronizeToDesign(Element design, DesignContext designContext) { + super.synchronizeToDesign(design, designContext); + TabSheet def = designContext.getDefaultInstance(this.getClass()); + Attributes attr = design.attributes(); + // handle tab index + DesignAttributeHandler.writeAttribute("tabindex", attr, getTabIndex(), + def.getTabIndex(), Integer.class); + // write tabs + for (Component component : this) { + Tab tab = this.getTab(component); + writeTabToDesign(design, designContext, tab); + } + } + } diff --git a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java index 2992771521..0c370b8da7 100644 --- a/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java +++ b/server/src/com/vaadin/ui/declarative/DesignAttributeHandler.java @@ -295,7 +295,7 @@ public class DesignAttributeHandler implements Serializable { + inputType.getName() + " not supported"); } if (!SharedUtil.equals(value, defaultValue)) { - String attributeValue = toAttributeValue(value.getClass(), value); + String attributeValue = toAttributeValue(inputType, value); attributes.put(attribute, attributeValue); } } @@ -464,7 +464,7 @@ public class DesignAttributeHandler implements Serializable { */ private static Resource parseResource(String value) { if (value.startsWith("http://")) { - return new ExternalResource("value"); + return new ExternalResource(value); } else if (value.startsWith("theme://")) { return new ThemeResource(value.substring(8)); } else if (value.startsWith("font://")) { diff --git a/server/tests/src/com/vaadin/tests/layoutparser/all-components.html b/server/tests/src/com/vaadin/tests/layoutparser/all-components.html index f633b06cb3..3fdae56e76 100644 --- a/server/tests/src/com/vaadin/tests/layoutparser/all-components.html +++ b/server/tests/src/com/vaadin/tests/layoutparser/all-components.html @@ -47,6 +47,37 @@ test value + + + + + + + + + In disabled tab - can’t be shown by default + + + + + + + + + + + + + + + In disabled tab - can’t be shown by default + + + + + + + \ No newline at end of file diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractcomponent/TestSynchronizeFromDesign.java b/server/tests/src/com/vaadin/tests/server/component/abstractcomponent/TestSynchronizeFromDesign.java index 5153d92706..df6fb47bf2 100644 --- a/server/tests/src/com/vaadin/tests/server/component/abstractcomponent/TestSynchronizeFromDesign.java +++ b/server/tests/src/com/vaadin/tests/server/component/abstractcomponent/TestSynchronizeFromDesign.java @@ -81,6 +81,8 @@ public class TestSynchronizeFromDesign extends TestCase { component.synchronizeFromDesign(design, ctx); assertTrue("Incorrect resource type returned", component.getIcon() .getClass().isAssignableFrom(ExternalResource.class)); + assertEquals("http://example.com/example.gif", + ((ExternalResource) component.getIcon()).getURL()); } public void testSynchronizeThemeIcon() { @@ -115,7 +117,8 @@ public class TestSynchronizeFromDesign extends TestCase { component.synchronizeFromDesign(design, ctx); assertEquals(false, component.isImmediate()); assertEquals(Boolean.FALSE, getExplicitImmediate(component)); - // Synchronize with a design having immediate = "" - should correspond to + // Synchronize with a design having immediate = "" - should correspond + // to // true. design = createDesign("immediate", ""); component.synchronizeFromDesign(design, ctx); diff --git a/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeFromDesign.java b/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeFromDesign.java new file mode 100644 index 0000000000..4c0a2863f0 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeFromDesign.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.tabsheet; + +import junit.framework.TestCase; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; + +import com.vaadin.server.ExternalResource; +import com.vaadin.ui.Label; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TabSheet.Tab; +import com.vaadin.ui.TextField; +import com.vaadin.ui.declarative.DesignContext; + +/** + * Test case from reading TabSheet from design + * + * @since + * @author Vaadin Ltd + */ +public class TestSynchronizeFromDesign extends TestCase { + + private TabSheet sheet; + + @Override + protected void setUp() throws Exception { + super.setUp(); + sheet = createTabSheet(); + } + + public void testChildCount() { + assertEquals(1, sheet.getComponentCount()); + } + + public void testTabIndex() { + assertEquals(5, sheet.getTabIndex()); + } + + public void testTabAttributes() { + Tab tab = sheet.getTab(0); + assertEquals("test-caption", tab.getCaption()); + assertEquals(false, tab.isVisible()); + assertEquals(false, tab.isClosable()); + assertEquals(false, tab.isEnabled()); + assertEquals("http://www.vaadin.com/test.png", + ((ExternalResource) tab.getIcon()).getURL()); + assertEquals("OK", tab.getIconAlternateText()); + assertEquals("test-desc", tab.getDescription()); + assertEquals("test-style", tab.getStyleName()); + assertEquals("test-id", tab.getId()); + } + + public void testSelectedComponent() { + TabSheet tabSheet = new TabSheet(); + tabSheet.synchronizeFromDesign(createFirstTabSelectedDesign(), + new DesignContext()); + assertEquals(tabSheet.getTab(0).getComponent(), + tabSheet.getSelectedTab()); + } + + public void testTabContent() { + assertTrue("The child for the tabsheet should be textfield", sheet + .getTab(0).getComponent() instanceof TextField); + } + + private TabSheet createTabSheet() { + TabSheet tabSheet = new TabSheet(); + // add some tabs that should be cleared on sync + tabSheet.addComponent(new Label("tab1")); + tabSheet.addComponent(new Label("tab2")); + DesignContext ctx = new DesignContext(); + Element design = createDesign(); + tabSheet.synchronizeFromDesign(design, ctx); + return tabSheet; + } + + private Element createDesign() { + // create root design + Attributes rootAttributes = new Attributes(); + rootAttributes.put("tabindex", "5"); + Element node = new Element(Tag.valueOf("v-tab-sheet"), "", + rootAttributes); + // create tab design + Attributes tabAttributes = new Attributes(); + tabAttributes.put("caption", "test-caption"); + tabAttributes.put("visible", "false"); + tabAttributes.put("closable", "false"); + tabAttributes.put("enabled", "false"); + tabAttributes.put("icon", "http://www.vaadin.com/test.png"); + tabAttributes.put("icon-alt", "OK"); + tabAttributes.put("description", "test-desc"); + tabAttributes.put("style-name", "test-style"); + tabAttributes.put("id", "test-id"); + Element tab = new Element(Tag.valueOf("tab"), "", tabAttributes); + // add child component to tab + tab.appendChild(new Element(Tag.valueOf("v-text-field"), "", + new Attributes())); + // add tab to root design + node.appendChild(tab); + return node; + } + + private Element createFirstTabSelectedDesign() { + // create root design + Attributes rootAttributes = new Attributes(); + Element node = new Element(Tag.valueOf("v-tab-sheet"), "", + rootAttributes); + // create tab design + Attributes tabAttributes = new Attributes(); + tabAttributes.put("selected", ""); + tabAttributes.put("caption", "test-caption"); + Element tab = new Element(Tag.valueOf("tab"), "", tabAttributes); + // add child component to tab + tab.appendChild(new Element(Tag.valueOf("v-text-field"), "", + new Attributes())); + // add tab to root design + node.appendChild(tab); + return node; + + } +} diff --git a/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeToDesign.java b/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeToDesign.java new file mode 100644 index 0000000000..c33a1da4d7 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/tabsheet/TestSynchronizeToDesign.java @@ -0,0 +1,109 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.tabsheet; + +import junit.framework.TestCase; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; + +import com.vaadin.server.ExternalResource; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TabSheet.Tab; +import com.vaadin.ui.TextField; +import com.vaadin.ui.declarative.DesignContext; + +/** + * Test case for writing TabSheet to design + * + * @since + * @author Vaadin Ltd + */ +public class TestSynchronizeToDesign extends TestCase { + + private TabSheet sheet; + private Element design; + + @Override + protected void setUp() throws Exception { + super.setUp(); + sheet = createTabSheet(); + design = createDesign(); + sheet.synchronizeToDesign(design, createDesignContext()); + } + + public void testOnlyOneTab() { + assertEquals("There should be only one child", 1, design.children() + .size()); + } + + public void testAttributes() { + Element tabDesign = design.child(0); + assertEquals("5", design.attr("tabindex")); + assertEquals("test-caption", tabDesign.attr("caption")); + assertEquals("false", tabDesign.attr("visible")); + assertTrue(tabDesign.hasAttr("closable")); + assertTrue(tabDesign.attr("closable").equals("true") + || tabDesign.attr("closable").equals("")); + assertEquals("false", tabDesign.attr("enabled")); + assertEquals("http://www.vaadin.com/test.png", tabDesign.attr("icon")); + assertEquals("OK", tabDesign.attr("icon-alt")); + assertEquals("test-desc", tabDesign.attr("description")); + assertEquals("test-style", tabDesign.attr("style-name")); + assertEquals("test-id", tabDesign.attr("id")); + } + + public void testContent() { + Element tabDesign = design.child(0); + Element content = tabDesign.child(0); + assertEquals("Tab must have only one child", 1, tabDesign.children() + .size()); + assertEquals("v-text-field", content.tagName()); + } + + private Element createDesign() { + // make sure that the design node has old content that should be removed + Element node = new Element(Tag.valueOf("v-tab-sheet"), "", + new Attributes()); + node.appendChild(new Element(Tag.valueOf("tab"), "", new Attributes())); + node.appendChild(new Element(Tag.valueOf("tab"), "", new Attributes())); + node.appendChild(new Element(Tag.valueOf("tab"), "", new Attributes())); + return node; + } + + private DesignContext createDesignContext() { + return new DesignContext(); + } + + private TabSheet createTabSheet() { + TabSheet sheet = new TabSheet(); + sheet.setTabIndex(5); + sheet.addTab(new TextField()); + Tab tab = sheet.getTab(0); + tab.setCaption("test-caption"); + tab.setVisible(false); + tab.setClosable(true); + tab.setEnabled(false); + tab.setIcon(new ExternalResource("http://www.vaadin.com/test.png")); + tab.setIconAlternateText("OK"); + tab.setDescription("test-desc"); + tab.setStyleName("test-style"); + tab.setId("test-id"); + return sheet; + } + +} -- 2.39.5