summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WebContent/VAADIN/themes/tests-responsive/styles.css92
-rw-r--r--client/src/com/vaadin/client/extensions/ResponsiveConnector.java379
-rw-r--r--server/src/com/vaadin/server/Responsive.java161
-rw-r--r--uitest/src/com/vaadin/tests/extensions/ResponsiveUI.java87
-rw-r--r--uitest/src/com/vaadin/tests/extensions/ResponsiveUITest.java78
5 files changed, 797 insertions, 0 deletions
diff --git a/WebContent/VAADIN/themes/tests-responsive/styles.css b/WebContent/VAADIN/themes/tests-responsive/styles.css
new file mode 100644
index 0000000000..4bfc6dd768
--- /dev/null
+++ b/WebContent/VAADIN/themes/tests-responsive/styles.css
@@ -0,0 +1,92 @@
+@import url(../reindeer/legacy-styles.css);
+
+.grid {
+ overflow: visible;
+}
+
+/* Style the items in the grid */
+.grid > .v-label {
+ outline: 1px dotted rgba(0, 0, 0, .3);
+ text-align: center;
+ padding: 2em 0;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+/* We want the items in the grid to stay between 100px and 200px, if possible */
+.first[width-range~="-200px"] .v-label {
+ width: 100%;
+}
+
+.first[width-range~="201px-400px"] > .v-label {
+ width: 50%;
+}
+
+.first[width-range~="401px-600px"] > .v-label {
+ width: 33.3333%;
+}
+
+.first[width-range~="601-800"] > .v-label {
+ width: 25%;
+}
+
+.first[width-range~="801-1000px"] > .v-label {
+ width: 20%;
+}
+
+.first[width-range~="1001px-"] > .v-label {
+ width: 16.6667%;
+}
+
+.second[width-range~="0-100px"] .v-label {
+ width: 100%;
+}
+
+.second[width-range~="101px-200px"] > .v-label {
+ width: 50%;
+}
+
+.second[width-range~="201px-300px"] > .v-label {
+ width: 33.3333%;
+}
+
+.second[width-range~="301px-400px"] > .v-label {
+ width: 25%;
+}
+
+.second[width-range~="401px-500px"] > .v-label {
+ width: 20%;
+}
+
+.second[width-range~="501px-"] > .v-label {
+ width: 16.6667%;
+}
+
+.description {
+ padding: 1em;
+ line-height: 1.5;
+}
+
+.description[width-range="-100px"] {
+ font-size: 10px;
+}
+
+.description[width-range="101px-200px"] {
+ font-size: 12px;
+}
+
+.description[width-range="200px-350px"] {
+ font-size: 14px;
+}
+
+.description[width-range="351px-500px"] {
+ font-size: 18px;
+}
+
+.description[width-range="501px-700px"] {
+ font-size: 24px;
+}
+
+.description[width-range="701px-"] {
+ font-size: 30px;
+}
diff --git a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java
new file mode 100644
index 0000000000..fb1919e6c2
--- /dev/null
+++ b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java
@@ -0,0 +1,379 @@
+/*
+ * 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.extensions;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.layout.ElementResizeEvent;
+import com.vaadin.client.ui.layout.ElementResizeListener;
+import com.vaadin.server.Responsive;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * The client side connector for the Responsive extension.
+ *
+ * @author Vaadin Ltd
+ * @since 7.2
+ */
+@SuppressWarnings("GwtInconsistentSerializableClass")
+@Connect(Responsive.class)
+public class ResponsiveConnector extends AbstractExtensionConnector implements
+ ElementResizeListener {
+
+ /**
+ * The target component which we will monitor for width changes
+ */
+ protected AbstractComponentConnector target;
+
+ /**
+ * All the width breakpoints found for this particular instance
+ */
+ protected JavaScriptObject widthBreakpoints;
+
+ /**
+ * All the height breakpoints found for this particular instance
+ */
+ protected JavaScriptObject heightBreakpoints;
+
+ /**
+ * All width-range breakpoints found from the style sheets on the page.
+ * Common for all instances.
+ */
+ protected static JavaScriptObject widthRangeCache;
+
+ /**
+ * All height-range breakpoints found from the style sheets on the page.
+ * Common for all instances.
+ */
+ protected static JavaScriptObject heightRangeCache;
+
+ private static Logger getLogger() {
+ return Logger.getLogger(ResponsiveConnector.class.getName());
+ }
+
+ private static void error(String message) {
+ getLogger().log(Level.SEVERE, message);
+ }
+
+ @Override
+ protected void extend(ServerConnector target) {
+ // Initialize cache if not already done
+ if (widthRangeCache == null) {
+ searchForBreakPoints();
+ }
+
+ this.target = (AbstractComponentConnector) target;
+
+ // Get any breakpoints from the styles defined for this widget
+ getBreakPointsFor(constructSelectorsForTarget());
+
+ // Start listening for size changes
+ LayoutManager.get(getConnection()).addElementResizeListener(
+ this.target.getWidget().getElement(), this);
+ }
+
+ /**
+ * Construct the list of selectors that should be matched against in the
+ * range selectors
+ *
+ * @return The selectors in a comma delimited string.
+ */
+ protected String constructSelectorsForTarget() {
+ String primaryStyle = this.target.getState().primaryStyleName;
+ StringBuilder selectors = new StringBuilder();
+ selectors.append(".").append(primaryStyle);
+
+ if (this.target.getState().styles != null
+ && this.target.getState().styles.size() > 0) {
+ for (String style : this.target.getState().styles) {
+ selectors.append(",.").append(style);
+ selectors.append(",.").append(primaryStyle).append(".")
+ .append(style);
+ selectors.append(",.").append(style).append(".")
+ .append(primaryStyle);
+ selectors.append(",.").append(primaryStyle).append("-")
+ .append(style);
+ }
+ }
+
+ // Allow the ID to be used as the selector as well for ranges
+ if (this.target.getState().id != null) {
+ selectors.append(",#").append(this.target.getState().id);
+ }
+ return selectors.toString();
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ LayoutManager.get(getConnection()).removeElementResizeListener(
+ this.target.getWidget().getElement(), this);
+ }
+
+ /**
+ * Build a cache of all 'width-range' and 'height-range' attribute selectors
+ * found in the stylesheets.
+ */
+ private static native void searchForBreakPoints()
+ /*-{
+
+ // Initialize variables
+ @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache = [];
+ @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache = [];
+
+ var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
+ var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
+
+ // Can't do squat if we can't parse stylesheets
+ if(!$doc.styleSheets)
+ return;
+
+ var sheets = $doc.styleSheets;
+
+ // Loop all stylesheets on the page and process them individually
+ for(var i = 0, len = sheets.length; i < len; i++) {
+ var sheet = sheets[i];
+ @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(sheet);
+ }
+
+ }-*/;
+
+ /**
+ * Process an individual stylesheet object. Any @import statements are
+ * handled recursively. Regular rule declarations are searched for
+ * 'width-range' and 'height-range' attribute selectors.
+ *
+ * @param sheet
+ */
+ private static native void searchStylesheetForBreakPoints(
+ final JavaScriptObject sheet)
+ /*-{
+
+ // Inline variables for easier reading
+ var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
+ var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
+
+ // Get all the rulesets from the stylesheet
+ var theRules = new Array();
+ var IE = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()();
+ var IE8 = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE8()();
+
+ if (sheet.cssRules) {
+ theRules = sheet.cssRules
+ } else if (sheet.rules) {
+ theRules = sheet.rules
+ }
+
+ // Special import handling for IE8
+ if (IE8) {
+ try {
+ for(var i = 0, len = sheet.imports.length; i < len; i++) {
+ @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(sheet.imports[i]);
+ }
+ } catch(e) {
+ // This is added due to IE8 failing to handle imports of some sheets for unknown reason (throws a permission denied exception)
+ @com.vaadin.client.extensions.ResponsiveConnector::error(Ljava/lang/String;)("Failed to handle imports of CSS style sheet: " + sheet.href);
+ }
+ }
+
+ // Loop through the rulesets
+ for(var i = 0, len = theRules.length; i < len; i++) {
+ var rule = theRules[i];
+
+ if(rule.type == 3) {
+ // @import rule, traverse recursively
+ @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(rule.styleSheet);
+
+ } else if(rule.type == 1 || !rule.type) {
+ // Regular selector rule
+
+ // IE parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both
+
+ // Pattern for matching [width-range] selectors
+ var widths = IE? /\[width-range~?=["|'](.*)-(.*)["|']\]([\.|#]\S+)/i : /([\.|#]\S+)\[width-range~?=["|'](.*)-(.*)["|']\]/i;
+
+ // Patter for matching [height-range] selectors
+ var heights = IE? /\[height-range~?=["|'](.*)-(.*)["|']\]([\.|#]\S+)/i : /([\.|#]\S+)\[height-range~?=["|'](.*)-(.*)["|']\]/i;
+
+ // Array of all of the separate selectors in this ruleset
+ var haystack = rule.selectorText.split(",");
+
+ // Loop all the selectors in this ruleset
+ for(var k = 0, len2 = haystack.length; k < len2; k++) {
+ var result;
+
+ // Check for width-range matches
+ if(result = haystack[k].match(widths)) {
+ var selector = IE? result[3] : result[1]
+ var min = IE? result[1] : result[2];
+ var max = IE? result[2] : result[3];
+
+ // Avoid adding duplicates
+ var duplicate = false;
+ for(var l = 0, len3 = widthRanges.length; l < len3; l++) {
+ var bp = widthRanges[l];
+ if(selector == bp[0] && min == bp[1] && max == bp[2]) {
+ duplicate = true;
+ break;
+ }
+ }
+ if(!duplicate) {
+ widthRanges.push([selector, min, max]);
+ }
+ }
+
+ // Check for height-range matches
+ if(result = haystack[k].match(heights)) {
+ var selector = IE? result[3] : result[1]
+ var min = IE? result[1] : result[2];
+ var max = IE? result[2] : result[3];
+
+ // Avoid adding duplicates
+ var duplicate = false;
+ for(var l = 0, len3 = heightRanges.length; l < len3; l++) {
+ var bp = heightRanges[l];
+ if(selector == bp[0] && min == bp[1] && max == bp[2]) {
+ duplicate = true;
+ break;
+ }
+ }
+ if(!duplicate) {
+ heightRanges.push([selector, min, max]);
+ }
+ }
+ }
+ }
+ }
+
+ }-*/;
+
+ /**
+ * Get all matching ranges from the cache for this particular instance.
+ *
+ * @param selectors
+ */
+ private native void getBreakPointsFor(final String selectors)
+ /*-{
+
+ var selectors = selectors.split(",");
+
+ var widthBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints = [];
+ var heightBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints = [];
+
+ var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
+ var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
+
+ for(var i = 0, len = widthRanges.length; i < len; i++) {
+ var bp = widthRanges[i];
+ for(var j = 0, len2 = selectors.length; j < len2; j++) {
+ if(bp[0] == selectors[j])
+ widthBreakpoints.push(bp);
+ }
+ }
+
+ for(var i = 0, len = heightRanges.length; i < len; i++) {
+ var bp = heightRanges[i];
+ for(var j = 0, len2 = selectors.length; j < len2; j++) {
+ if(bp[0] == selectors[j])
+ heightBreakpoints.push(bp);
+ }
+ }
+
+ // Only for debugging
+ // console.log("Breakpoints for", selectors.join(","), widthBreakpoints, heightBreakpoints);
+
+ }-*/;
+
+ private String currentWidthRanges;
+ private String currentHeightRanges;
+
+ @Override
+ public void onElementResize(ElementResizeEvent e) {
+ int width = e.getLayoutManager().getOuterWidth(e.getElement());
+ int height = e.getLayoutManager().getOuterHeight(e.getElement());
+
+ // Loop through breakpoints and see which one applies to this width
+ currentWidthRanges = resolveBreakpoint("width", width, e.getElement());
+
+ if (!"".equals(currentWidthRanges)) {
+ this.target.getWidget().getElement()
+ .setAttribute("width-range", currentWidthRanges);
+ } else {
+ this.target.getWidget().getElement().removeAttribute("width-range");
+ }
+
+ // Loop through breakpoints and see which one applies to this height
+ currentHeightRanges = resolveBreakpoint("height", height,
+ e.getElement());
+
+ if (!"".equals(currentHeightRanges)) {
+ this.target.getWidget().getElement()
+ .setAttribute("height-range", currentHeightRanges);
+ } else {
+ this.target.getWidget().getElement()
+ .removeAttribute("height-range");
+ }
+ }
+
+ private native String resolveBreakpoint(String which, int size,
+ Element element)
+ /*-{
+
+ // Default to "width" breakpoints
+ var breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints;
+
+ // Use height breakpoints if we're measuring the height
+ if(which == "height")
+ breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints;
+
+ // Output string that goes into either the "width-range" or "height-range" attribute in the element
+ var ranges = "";
+
+ // Loop the breakpoints
+ for(var i = 0, len = breakpoints.length; i < len; i++) {
+ var bp = breakpoints[i];
+
+ var min = parseInt(bp[1]);
+ var max = parseInt(bp[2]);
+
+ if(min && max) {
+ if(min <= size && size <= max) {
+ ranges += " " + bp[1] + "-" + bp[2];
+ }
+ } else if (min) {
+ if(min <= size) {
+ ranges += " " + bp[1] + "-";
+ }
+ } else if (max) {
+ if (size <= max) {
+ ranges += " -" + bp[2];
+ }
+ }
+ }
+
+ // Trim the output and return it
+ return ranges.replace(/^\s+/, "");
+
+ }-*/;
+
+}
diff --git a/server/src/com/vaadin/server/Responsive.java b/server/src/com/vaadin/server/Responsive.java
new file mode 100644
index 0000000000..d69c204c94
--- /dev/null
+++ b/server/src/com/vaadin/server/Responsive.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.vaadin.ui.Component;
+
+/**
+ * An extension providing responsive layout capabilities to any Vaadin
+ * component. The Responsive extension allows specifying different CSS rules for
+ * different dimensions of extended components. This allows creating
+ * applications that provide an optimal viewing experience – easy reading and
+ * navigation with a minimum of resizing, panning, and scrolling – across a wide
+ * range of devices (from mobile phones to desktop computer monitors).
+ * <p>
+ * NOTE! You should always specify a relative (%) size for the extended
+ * component, doing otherwise will prevent the Responsive extension from
+ * working, as the component will not dynamically resize.
+ * </p>
+ * <p>
+ * All configuration of the visual breakpoints (ranges) for the component are
+ * done with CSS. Pixels (px) are the only supported unit. Fractional pixels are
+ * not supported.
+ * </p>
+ * <p>
+ * <i>Dynamic style injections (e.g. through
+ * <code>Page.getCurrent().getStyles().add(...)</code>) or any other style
+ * updates after the initial page load are not supported at the moment.</i>
+ * </p>
+ *
+ *
+ * <p>
+ * Example:
+ *
+ * <b>Java</b>
+ *
+ * <pre>
+ * CssLayout layout = new CssLayout();
+ * layout.setStyleName("responsive");
+ * layout.setSizeFull();
+ * Responsive.makeResponsive(layout);
+ * </pre>
+ *
+ * <b>SCSS</b>
+ *
+ * <pre>
+ * .v-csslayout.responsive {
+ * &[width-range~="0-300px"] {
+ * // Styles for the layout when its width is between 0 and 300 pixels
+ * }
+ * &[width-range~="301-500px"] {
+ * // Styles for the layout when its width is between 301 and 500 pixels
+ * }
+ * &[width-range~="501px-"] {
+ * // Styles for the layout when its width is over 500 pixels
+ * }
+ * &[height-range~="0-300px"] {
+ * // Styles for the layout when its height is between 0 and 300 pixels
+ * }
+ * &[height-range~="301-500px"] {
+ * // Styles for the layout when its height is between 301 and 500 pixels
+ * }
+ * &[height-range~="501-"] {
+ * // Styles for the layout when its height is over 500 pixels
+ * }
+ * }
+ * </pre>
+ *
+ * <b>CSS</b>
+ *
+ * <pre>
+ * .v-csslayout.responsive[width-range~="0-300px"] {
+ * // Styles for the layout when its width is between 0 and 300 pixels
+ * }
+ * .v-csslayout.responsive[width-range~="301-500px"] {
+ * // Styles for the layout when its width is between 301 and 500 pixels
+ * }
+ * .v-csslayout.responsive[width-range~="501-"] {
+ * // Styles for the layout when its width is over 500 pixels
+ * }
+ *
+ * .v-csslayout.responsive[height-range~="0-300px"] {
+ * // Styles for the layout when its height is between 0 and 300 pixels
+ * }
+ * .v-csslayout.responsive[height-range~="301-500px"] {
+ * // Styles for the layout when its height is between 301 and 500 pixels
+ * }
+ * .v-csslayout.responsive[height-range~="501px-"] {
+ * // Styles for the layout when its height is over 500 pixels
+ * }
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * <b>Note:</b> <i>The defined ranges are applied on a global context, so even
+ * if you would write your CSS to target only a given context, the ranges would
+ * be applied to all other instances with the same style name.</i>
+ * </p>
+ * <p>
+ * E.g. this would affect all CssLayout instances in the application, even
+ * though the CSS implies it would only affect CssLayout instances inside a
+ * parent with a style name "foobar":
+ * </p>
+ *
+ * <pre>
+ * .foobar .v-csslayout[width-range~="0px-100px"] {
+ * // These properties will affect all responsive CssLayout instances
+ * }
+ * </pre>
+ *
+ * <p>
+ * To scope the ranges, use an additional style name for the target component,
+ * and add that to your CSS selector:
+ * </p>
+ *
+ * <pre>
+ * .v-csslayout.mystyle[width-range="0px-100px"] {
+ * // These properties will only affect responsive CssLayout instances with an additional style name of 'mystyle'
+ * }
+ * </pre>
+ *
+ * @author Vaadin Ltd
+ * @since 7.2
+ */
+public class Responsive extends AbstractExtension {
+
+ /**
+ * Creates a new instance, which can be used to extend a component.
+ */
+ protected Responsive() {
+ }
+
+ /**
+ * Enable responsive width and height range styling for the target component
+ * or UI instance.
+ *
+ * @param target
+ * The component which should be able to respond to width and/or
+ * height changes.
+ */
+ public static void makeResponsive(Component... components) {
+ for (Component c : components) {
+ if (c instanceof AbstractClientConnector) {
+ new Responsive().extend((AbstractClientConnector) c);
+ }
+ }
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/extensions/ResponsiveUI.java b/uitest/src/com/vaadin/tests/extensions/ResponsiveUI.java
new file mode 100644
index 0000000000..417821f1ea
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/extensions/ResponsiveUI.java
@@ -0,0 +1,87 @@
+/*
+ * 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.extensions;
+
+import com.vaadin.annotations.Theme;
+import com.vaadin.server.Responsive;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.HorizontalSplitPanel;
+import com.vaadin.ui.Label;
+
+@Theme("tests-responsive")
+public class ResponsiveUI extends AbstractTestUI {
+ @Override
+ protected void setup(VaadinRequest request) {
+ HorizontalSplitPanel split = new HorizontalSplitPanel();
+ setContent(split);
+ split.setSplitPosition(50, Unit.PERCENTAGE);
+ split.setMinSplitPosition(100, Unit.PIXELS);
+ split.setMaxSplitPosition(1200, Unit.PIXELS);
+ setStyleName("responsive-test");
+
+ CssLayout firstGrid = makeGrid("first");
+ CssLayout secondGrid = makeGrid("second");
+ CssLayout grids = new CssLayout();
+ grids.setSizeFull();
+ grids.addComponent(firstGrid);
+ grids.addComponent(secondGrid);
+ split.addComponent(grids);
+
+ Label description = new Label(
+ "<h3>This application demonstrates the Responsive extension in Vaadin.</h3>"
+ + "<p>Drag the splitter to see how the boxes on the left side adapt to "
+ + "different widths. They maintain a width of 100-200px, and always "
+ + "span the entire width of the container.</p><p>This label will "
+ + "adapt its font size and line height for different widths.</p>"
+ + "<p><a href=\"http://vaadin.com/download\">Download "
+ + "Vaadin</a></p>", ContentMode.HTML);
+ description.addStyleName("description");
+ split.addComponent(description);
+
+ // Add the responsive capabilities to the components
+ Responsive.makeResponsive(firstGrid);
+ Responsive.makeResponsive(secondGrid);
+ Responsive.makeResponsive(description);
+ }
+
+ private CssLayout makeGrid(String styleName) {
+ CssLayout grid = new CssLayout();
+ grid.setWidth("100%");
+ grid.addStyleName("grid");
+ grid.addStyleName(styleName);
+
+ for (int i = 1; i < 10; i++) {
+ Label l = new Label("" + i);
+ l.setSizeUndefined();
+ grid.addComponent(l);
+ }
+ return grid;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "The CssLayouts (grids) and Label should be responsive";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 12394;
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/extensions/ResponsiveUITest.java b/uitest/src/com/vaadin/tests/extensions/ResponsiveUITest.java
new file mode 100644
index 0000000000..76babed333
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/extensions/ResponsiveUITest.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.tests.extensions;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.By;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class ResponsiveUITest extends MultiBrowserTest {
+
+ @Before
+ public void setUp() throws Exception {
+ // We need this in order to ensure that the initial width-range is
+ // 401px-600px
+ testBench().resizeViewPortTo(1024, 768);
+ }
+
+ // JQuery style selector
+ private WebElement $(String cssSelector) {
+ return getDriver().findElement(By.cssSelector(cssSelector));
+ }
+
+ @Test
+ public void testResizingSplitPanelReflowsLayout() throws Exception {
+ openTestURL();
+
+ assertEquals("401px-600px",
+ $(".v-csslayout-grid.first").getAttribute("width-range"));
+ assertEquals("501px-",
+ $(".v-csslayout-grid.second").getAttribute("width-range"));
+
+ moveSplitter(200);
+
+ assertEquals("601-800",
+ $(".v-csslayout-grid.first").getAttribute("width-range"));
+ assertEquals("501px-",
+ $(".v-csslayout-grid.second").getAttribute("width-range"));
+
+ moveSplitter(-350);
+
+ assertEquals("201px-400px",
+ $(".v-csslayout-grid.first").getAttribute("width-range"));
+ assertEquals("301px-400px",
+ $(".v-csslayout-grid.second").getAttribute("width-range"));
+
+ compareScreen("responsive");
+
+ moveSplitter(-200);
+ assertEquals("-200px",
+ $(".v-csslayout-grid.first").getAttribute("width-range"));
+
+ }
+
+ private void moveSplitter(int xOffset) {
+ new Actions(getDriver()).clickAndHold($(".v-splitpanel-hsplitter"))
+ .moveByOffset(xOffset, 0).release().build().perform();
+ }
+}